首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 計算兩日期天數差的三種核心方法與陷阱避坑指南

     編輯:Java 相關知識

前言

計算兩個日期相差天數是極高頻需求,但「是否跨年、是否含時間、夏令時、閏年」等細節極易導致 Bug。本文對比三種主流實現,深度分析差異並給出生產級推薦方案。

一、方法對比總覽

方法核心 API語義適用場景陷阱
Calendar 日期字段差Calendar.DAY_OF_YEAR + 年份循環日曆日差(忽略具體時間)業務按「自然日」結算跨年閏年邏輯易寫錯
毫秒數相減(d2.getTime()-d1.getTime())/(1000*3600*24)24小時倍數差(精確到毫秒)科學計算、精確間隔夏令時、閏秒導致非整數天
Java 8 LocalDateChronoUnit.DAYS.between(d1, d2)日曆日差(無時間分量,不可變線程安全)現代項目首選需 Java 8+,舊 Date 需轉換

二、方案一:Calendar 實現(傳統項目)

public static int daysDiffCalendar(Date date1, Date date2) {
    Calendar cal1 = Calendar.getInstance();
    Calendar cal2 = Calendar.getInstance();
    cal1.setTime(date1);
    cal2.setTime(date2);
    
    // 統一歸零時分秒毫秒,純比較「日期」
    cal1.set(Calendar.HOUR_OF_DAY, 0);
    cal1.set(Calendar.MINUTE, 0);
    cal1.set(Calendar.SECOND, 0);
    cal1.set(Calendar.MILLISECOND, 0);
    cal2.set(Calendar.HOUR_OF_DAY, 0);
    cal2.set(Calendar.MINUTE, 0);
    cal2.set(Calendar.SECOND, 0);
    cal2.set(Calendar.MILLISECOND, 0);
    
    long diffMillis = cal2.getTimeInMillis() - cal1.getTimeInMillis();
    return (int) (diffMillis / (1000 * 60 * 60 * 24));
}

關鍵點:必須歸零時間分量,否則 23:59 和 00:01 相差 2 分鐘卻算 1 天。

三、方案二:毫秒數直接相減(簡單但危險)

public static int daysDiffMillis(Date date1, Date date2) {
    long diff = date2.getTime() - date1.getTime();
    return (int) (diff / (1000L * 60 * 60 * 24));
}

實測陷阱

Date d1 = parse("2015-01-01 21:00:00");
Date d2 = parse("2015-01-02 01:00:00"); // 相差 4 小時
daysDiffMillis(d1, d2); // 返回 0(不足 24h)
daysDiffCalendar(d1, d2); // 返回 1(跨自然日)

結論:業務上「跨自然日算 1 天」時禁用此法。

四、方案三:Java 8+ LocalDate(現代標準)

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;

public static long daysDiffModern(Date date1, Date date2) {
    LocalDate ld1 = date1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    LocalDate ld2 = date2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    return ChronoUnit.DAYS.between(ld1, ld2); // 可為負
}

// 純字符串解析
public static long daysDiffString(String d1, String d2, String pattern) {
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
    LocalDate ld1 = LocalDate.parse(d1, fmt);
    LocalDate ld2 = LocalDate.parse(d2, fmt);
    return ChronoUnit.DAYS.between(ld1, ld2);
}

優勢

  • 不可變、線程安全
  • 自動處理閏年、月份長短
  • API 語義清晰:DAYS.between 即「日曆日差」
  • 支援 Period 獲取年/月/日組合差值

五、特殊場景處理

1. 包含起始日(如住酒店:入住至退房天數 = 日期差 + 1)

long stayDays = ChronoUnit.DAYS.between(checkIn, checkOut) + 1;

2. 工作日差(排除週末)

public static long workDaysBetween(LocalDate start, LocalDate end) {
    long days = 0;
    LocalDate curr = start;
    while (!curr.isAfter(end)) {
        if (curr.getDayOfWeek().getValue() < 6) days++; // 1-5 週一至週五
        curr = curr.plusDays(1);
    }
    return days;
}
// 生產環境建議引入 ThreeTen-Extra 的 WorkingDays 或自定義節假日日曆

3. 夏令時/時區跨越

  • 使用 ZoneId 明確指定業務時區(如 ZoneId.of("Asia/Shanghai"))
  • 避免使用系統默認時區 ZoneId.systemDefault()(容器部署可能為 UTC)

六、避坑檢查清單

  1. Date 對象是否含時間:含時間必須歸零或用 LocalDate
  2. 時區一致性:解析、存儲、計算全鏈路統一時區
  3. 閏年邏輯:Calendar 手寫循環易錯,優先用 Java 8 API
  4. 負數處理:between 結果可為負,業務層取絕對值或拋異常
  5. 性能敏感:高頻調用避免重複創建 Calendar/DateTimeFormatter,改用緩存或工具類

七、工具類封裝建議

public final class DateUtils {
    private static final DateTimeFormatter DEFAULT_FMT = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd").withResolverStyle(ResolverStyle.STRICT);
    
    public static long daysBetween(Date d1, Date d2) {
        if (d1 == null || d2 == null) throw new IllegalArgumentException();
        LocalDate ld1 = d1.toInstant().atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();
        LocalDate ld2 = d2.toInstant().atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();
        return ChronoUnit.DAYS.between(ld1, ld2);
    }
    
    public static long daysBetween(String d1, String d2) {
        return daysBetween(LocalDate.parse(d1, DEFAULT_FMT), LocalDate.parse(d2, DEFAULT_FMT));
    }
    
    public static long daysBetween(LocalDate d1, LocalDate d2) {
        return ChronoUnit.DAYS.between(d1, d2);
    }
}

結語

計算天數差語義優於實現:業務按自然日 → LocalDate + ChronoUnit.DAYS;科學計算精確間隔 → 毫秒數 / 86400000。Java 8+ 專案強制遷移至 java.time,舊項目封裝工具類統一歸零時間分量。牢記「歸零時分秒、鎖定時區、不可變 API」三板斧,杜絕日期計算 Bug。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved