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

Java 判斷兩個日期是否為同一天的多種做法與效能比較

     編輯:Java 相關知識

前言

業務邏輯常需判斷兩個時間點是否落在同一天(如:每日簽到、日誌分組、報表統計)。本文介紹四種主流做法:LocalDate (JDK 8+ 推薦)、Calendar (舊版相容)、Date/SimpleDateFormat (不推薦)、時間戳除法 (高效能),並給出效能基準與陷阱提醒。

做法一:LocalDate (JDK 8+ 標準,最推薦)

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());
}
  • ✅ 不可變、執行緒安全、語意清晰。
  • ✅ 內建時區支援,跨時區比較正確。
  • ✅ JDK 8+ 專案首選。

做法二:Calendar (JDK 7 或 Android 早期)

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;
}
  • ✅ 極致效能,適合大資料流處理 (Flink/Spark UDF)。
  • ❌ 需自行處理時區、夏令時間、閏秒邊界,極易出錯。
  • ❌ 僅適用於「以 UTC 午夜為界」的業務定義。

做法四:SimpleDateFormat (不推薦,僅維護舊碼)

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。

效能微基準 (JMH, 10M 次呼叫)

方法平均耗時 (ns/op)分配率 (B/op)
LocalDate.equals~120 (逃逸分析優化)
Calendar DAY_OF_YEAR~45~80
時間戳除法~30
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,切勿用時間戳除法。

最佳實踐建議

  1. 新專案全面遷移至 java.time:LocalDate, LocalDateTime, ZonedDateTime, Instant。
  2. 資料庫層面統一 UTC:儲存 TIMESTAMP WITH TIME ZONE 或 BIGINT(epoch_millis),應用層轉換為使用者時區再比較。
  3. 工具類封裝:提供 DateUtils.isSameDay(a, b, zoneId) 統一入口,內部委派 LocalDate。
  4. 單元測試覆蓋邊界:夏令時間切換日、跨年、閏年、跨時區比較。

常見 API 速查

// 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)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved