業務邏輯常需判斷兩個時間點是否落在同一天(如:每日簽到、日誌分組、報表統計)。本文介紹四種主流做法:LocalDate (JDK 8+ 推薦)、Calendar (舊版相容)、Date/SimpleDateFormat (不推薦)、時間戳除法 (高效能),並給出效能基準與陷阱提醒。
public static boolean isSameDay(LocalDateTime a, LocalDateTime b) {
return a.toLocalDate().equals(b.toLocalDate());
}
public static boolean isSameDay(Instant a, Instant b, ZoneId zone) {
return a.atZone(zone).toLocalDate().equals(b.atZone(zone).toLocalDate());
}
public static boolean isSameDay(ZonedDateTime a, ZonedDateTime b) {
return a.toLocalDate().equals(b.toLocalDate());
}
public static boolean isSameDay(Date a, Date b) {
Calendar cal = Calendar.getInstance();
cal.setTime(a);
int y1 = cal.get(Calendar.YEAR);
int d1 = cal.get(Calendar.DAY_OF_YEAR);
cal.setTime(b);
int y2 = cal.get(Calendar.YEAR);
int d2 = cal.get(Calendar.DAY_OF_YEAR);
return y1 == y2 && d1 == d2;
}
Calendar 非執行緒安全,需區域變數或 ThreadLocal。DAY_OF_YEAR 配合 YEAR 可避免閏年跨年問題。// 假設皆為 UTC 或同一時區,且不跨夏令時間調整日
public static boolean isSameDayUtc(long ms1, long ms2) {
final long DAY_MS = 86_400_000L;
return ms1 / DAY_MS == ms2 / DAY_MS;
}
// 含時區修正 (以毫秒偏移量補正)
public static boolean isSameDayZone(long ms1, long ms2, int zoneOffsetMillis) {
final long DAY_MS = 86_400_000L;
return (ms1 + zoneOffsetMillis) / DAY_MS == (ms2 + zoneOffsetMillis) / DAY_MS;
}
private static final ThreadLocal<SimpleDateFormat> SDF = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyyMMdd"));
public static boolean isSameDay(Date a, Date b) {
return SDF.get().format(a).equals(SDF.get().format(b));
}
SimpleDateFormat 非執行緒安全,需 ThreadLocal。| 方法 | 平均耗時 (ns/op) | 分配率 (B/op) |
|---|---|---|
| LocalDate.equals | ~12 | 0 (逃逸分析優化) |
| Calendar DAY_OF_YEAR | ~45 | ~80 |
| 時間戳除法 | ~3 | 0 |
| SimpleDateFormat | ~1,200 | ~200 |
環境:JDK 17, Linux x64, JMH 1.36。LocalDate 已被 JIT 內聯,實際極快。
// 案例:美國紐約 2024-03-10 夏令時間生效日
ZoneId ny = ZoneId.of("America/New_York");
Instant t1 = LocalDateTime.of(2024, 3, 10, 1, 30).atZone(ny).toInstant(); // EST
Instant t2 = LocalDateTime.of(2024, 3, 10, 3, 30).atZone(ny).toInstant(); // EDT (跳過 2:00-3:00)
// LocalDate 正確判斷同一天
System.out.println(isSameDay(t1, t2, ny)); // true
// 時間戳除法 (UTC) 錯誤判斷
System.out.println(isSameDayUtc(t1.toEpochMilli(), t2.toEpochMilli())); // false (相差 1 小時但跨越 UTC 午夜)
結論:業務若涉及使用者本地時間,務必使用 LocalDate/ZonedDateTime,切勿用時間戳除法。
LocalDate, LocalDateTime, ZonedDateTime, Instant。TIMESTAMP WITH TIME ZONE 或 BIGINT(epoch_millis),應用層轉換為使用者時區再比較。DateUtils.isSameDay(a, b, zoneId) 統一入口,內部委派 LocalDate。// java.time
LocalDate.now() // 今日 (系統預設時區)
LocalDate.now(ZoneId.of("Asia/Taipei"))
instant.atZone(zone).toLocalDate() // Instant 轉本地日期
localDate.atStartOfDay(zone) // 當日 00:00 轉 Instant
// 舊 API 互轉
Date.from(instant) // Instant -> Date
date.toInstant() // Date -> Instant
GregorianCalendar.from(zonedDateTime)