直接用 Date.getTime() 相減除以 24*3600*1000 會在夏令時切換日產生誤差。正確做法:使用 Calendar 的 DAY_OF_YEAR 與 YEAR 欄位,逐年累加每年實際天數(getActualMaximum(DAY_OF_YEAR)),實現**與 Calendar 實現無關、無視時區/夏令時**的精確天數差。附生產級工具方法代碼。
d1 ≤ d2(否則交換)。d2.DAY_OF_YEAR - d1.DAY_OF_YEAR。d1 逐年 add(YEAR, 1) 累加 getActualMaximum(DAY_OF_YEAR),直到年份追上 d2,再加上同年差值。
/**
* 計算兩個 Calendar 日期之間的天數差(忽略時間分量)。
* @param d1 起始日期
* @param d2 結束日期
* @return 天數差,>=0。順序不影響結果。
* @throws IllegalArgumentException 若傳入 null
*/
public static int getDaysBetween(java.util.Calendar d1, java.util.Calendar d2) {
if (d1 == null || d2 == null) throw new IllegalArgumentException("Calendar 參數不能為 null");
// 確保 d1 <= d2
if (d1.after(d2)) {
java.util.Calendar tmp = d1; d1 = d2; d2 = tmp;
}
int days = d2.get(java.util.Calendar.DAY_OF_YEAR) - d1.get(java.util.Calendar.DAY_OF_YEAR);
int y2 = d2.get(java.util.Calendar.YEAR);
if (d1.get(java.util.Calendar.YEAR) != y2) {
d1 = (java.util.Calendar) d1.clone(); // 不修改原對象
do {
days += d1.getActualMaximum(java.util.Calendar.DAY_OF_YEAR);
d1.add(java.util.Calendar.YEAR, 1);
} while (d1.get(java.util.Calendar.YEAR) != y2);
}
return days;
}
| 細節 | 作用 |
|---|---|
d1.clone() | 避免修改呼叫端傳入的 Calendar 實例(副作用預防)。 |
getActualMaximum(DAY_OF_YEAR) | 自動處理閏年(366 天)與平年(365 天),無需硬編碼判斷邏輯。 |
僅比較 DAY_OF_YEAR + YEAR | 忽略 HOUR_OF_DAY、MINUTE 等時間分量,專注「日期」差。 |
拋檢 null | 防禦性編程,早失敗易排查。 |
Calendar c1 = Calendar.getInstance(); c1.set(2024, Calendar.FEBRUARY, 28); // 2024-02-28
Calendar c2 = Calendar.getInstance(); c2.set(2024, Calendar.MARCH, 1); // 2024-03-01
System.out.println(getDaysBetween(c1, c2)); // 輸出 2 (閏年 2/29 存在)
Calendar c3 = Calendar.getInstance(); c3.set(2023, Calendar.FEBRUARY, 28);
Calendar c4 = Calendar.getInstance(); c4.set(2023, Calendar.MARCH, 1);
System.out.println(getDaysBetween(c3, c4)); // 輸出 1 (平年無 2/29)
| 錯誤做法 | 問題 | 正確做法 |
|---|---|---|
(d2.getTimeInMillis() - d1.getTimeInMillis()) / (24*3600*1000) | 夏令時切換日相差 23/25 小時,導致天數誤差 ±1 | 使用 Calendar 欄位運算(本文方法) |
SimpleDateFormat.parse() 後相減 | 同樣受時區/夏令時影響,且格式解析效能低 | 直接構造 Calendar 或用 LocalDate (Java 8+) |
| 手寫閏年判斷 | 易漏邊界、代碼冗餘 | getActualMaximum(DAY_OF_YEAR) 一行搞定 |
// Java 8+ 推薦:LocalDate 天生無時區、不可變、API 簡潔
public static long daysBetween(java.time.LocalDate d1, java.time.LocalDate d2) {
return Math.abs(java.time.temporal.ChronoUnit.DAYS.between(d1, d2));
}
舊專案維護期繼續用 Calendar 版本;新專案**首選 java.time 包**。