在 Java 中計算兩個日期相差的天數看似簡單,實則暗藏時區、夏令時間、閏年等陷阱。本文分析常見錯誤做法,提供正確且健壯的解決方案。
許多初學者直接使用毫秒差值除以一天的毫秒數:
// ❌ 錯誤做法
long diff = date2.getTime() - date1.getTime();
long days = diff / (24 * 60 * 60 * 1000);
致命缺陷:忽略夏令時間切換導致某天非 24 小時、跨時區計算偏差、閏秒影響。
利用 Calendar.DAY_OF_YEAR 與年份差值計算:
public static int getDaysBetween(Date date1, Date date2) {
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTime(date1);
cal2.setTime(date2);
int days = cal2.get(Calendar.DAY_OF_YEAR) - cal1.get(Calendar.DAY_OF_YEAR);
int year2 = cal2.get(Calendar.YEAR);
if (cal1.get(Calendar.YEAR) != year2) {
Calendar temp = (Calendar) cal1.clone();
do {
days += temp.getActualMaximum(Calendar.DAY_OF_YEAR);
temp.add(Calendar.YEAR, 1);
} while (temp.get(Calendar.YEAR) != year2);
}
return days;
}
優點:正確處理跨年、閏年。
缺點:Calendar 可變性差、API 繁瑣、時區處理易出錯。
java.time 套件提供不可變、執行緒安全的日期類別:
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
public static long daysBetween(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);
}
核心優勢:
LocalDate 只有日期無時間,天然適合「天數差」計算ChronoUnit.DAYS.between 內建處理閏年、月份長短// 以紐約時區為準計算天數差
ZoneId ny = ZoneId.of("America/New_York");
LocalDate ld1 = date1.toInstant().atZone(ny).toLocalDate();
LocalDate ld2 = date2.toInstant().atZone(ny).toLocalDate();
// 含首尾日:+1
long inclusiveDays = ChronoUnit.DAYS.between(start, end) + 1;
// 僅計算完整 24 小時週期
long fullDays = ChronoUnit.DAYS.between(start.atStartOfDay(), end.atStartOfDay());
| 方案 | 適用版本 | 執行緒安全 | 可讀性 | 推薦指數 |
|---|---|---|---|---|
| 毫秒相除 | 所有版本 | 是 | 高 | ⭐ (錯誤) |
| Calendar | Java 1.1+ | 否 | 低 | ⭐⭐ |
| java.time | Java 8+ | 是 | 高 | ⭐⭐⭐⭐⭐ |
專案若使用 Java 8 或更新版本,首選 java.time.LocalDate 與 ChronoUnit。舊版專案可使用 Calendar 方案,但需謹慎處理時區與可變性問題。切勿使用毫秒相除的簡易做法,除非業務極其簡單且能容忍誤差。