在業務開發中,計算兩個日期之間的天數差是高頻需求(如物流時效、租賃到期、會員剩餘天數等)。本文整理了從 JDK 原生 API 到第三方庫的多種實現方式,並分析其優缺點,助你根據場景選擇最優方案。
適用於 JDK 7 及以下環境,邏輯較繁瑣,需手動處理跨年、閏年情況。
public static int dateInterval(long date1, long date2) {
// 確保 date1 >= date2,保證結果為正數
if (date2 > date1) { /* 交換邏輯 */ }
Calendar cal1 = Calendar.getInstance();
cal1.setTimeInMillis(date1);
Calendar cal2 = Calendar.getInstance();
cal2.setTimeInMillis(date2);
int y1 = cal1.get(Calendar.YEAR);
int y2 = cal2.get(Calendar.YEAR);
int d1 = cal1.get(Calendar.DAY_OF_YEAR);
int d2 = cal2.get(Calendar.DAY_OF_YEAR);
if (y1 == y2) {
return d1 - d2; // 同年直接相減
}
// 跨年需累加中間整年的天數(含閏年判斷)
return calculateCrossYearDays(d1, d2, y1, y2, cal2);
}
缺點:代碼量大、易出錯、需自行處理閏年與跨年邏輯。
將日期轉為毫秒時間戳,相減後除以一天的毫秒數。
public static int daysBetween(Date d1, Date d2) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 關鍵:格式化後再解析,將時分秒歸零,避免時間差導致天數誤差
d1 = sdf.parse(sdf.format(d1));
d2 = sdf.parse(sdf.format(d2));
long diff = d2.getTime() - d1.getTime();
return (int) (diff / (1000 * 60 * 60 * 24));
}
注意:必須將時分秒歸零,否則「2023-01-01 23:00」到「2023-01-02 01:00」僅 2 小時,卻會被算作 1 天差。
利用 Calendar.add(Calendar.DATE, 1) 逐日遞增計數,絕對精確但大跨度日期性能差。
public int getDays(GregorianCalendar g1, GregorianCalendar g2) {
// 清除時分秒毫秒,確保按「日」比較
clearTimeFields(g1); clearTimeFields(g2);
int elapsed = 0;
while (g1.before(g2)) {
g1.add(Calendar.DATE, 1);
elapsed++;
}
return elapsed;
}
語義清晰、API 豐富、處理時區/閏年極其可靠。
// 判斷租賃是否超期(2天期限)
public boolean isRentalOverdue(DateTime rented) {
return rented.plus(Period.days(2)).isBeforeNow();
}
強烈推薦:java.time 包不可變、線程安全、API 直觀。
// LocalDate 適合無時間、無時區的日期計算
long days = ChronoUnit.DAYS.between(
LocalDate.parse("2023-01-01"),
LocalDate.parse("2023-01-10")
); // 返回 9
// 或使用 Period 獲取年/月/日差
Period p = Period.between(startDate, endDate);
int totalDays = p.getDays() + p.getMonths() * 30 + p.getYears() * 365; // 近似
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| Calendar 手算 | JDK 7 維護老項目 | 無依賴 | 代碼複雜、易錯 |
| 毫秒差值法 | 簡單快速原型、同時區 | 代碼極短 | 需手動歸零時間、跨 DST 可能誤差 |
| 逐日疊加 | 極高精度要求、小跨度 | 絕對精確 | 大跨度性能極差 |
| Joda-Time | JDK 8 前新項目 | 功能強大、業界標準 | 需引入第三方依賴 |
| Java 8 java.time | 所有新項目首選 | 標準庫、不可變、線程安全、API 現代 | 需 JDK 8+ |
java.time.LocalDate + ChronoUnit.DAYS.between(),一行代碼解決。