在前幾天的文章中,我們已經完成了 MyMomentum 專案的核心功能開發,包括活動管理、記錄追蹤等。今天想跟大家聊聊一個看似簡單但其實很重要的技術選擇:在 Java 中應該用什麼型別來儲存日期時間?
今天就舉我自己在開發維護的時候最常看到的三個日期時間型別為例,來比較一下:
Date、LocalDateTime 和 Instant。
雖然 Date 現在還有很多專案在使用,但它的設計已經不符合現代 Java 的開發需求。它本身只存毫秒數,但輸出時會依 JVM 時區顯示,常讓人誤以為內含時區,導致時區處理變得複雜。
Date now = new Date();
System.out.println(now); // 輸出:Mon Jan 15 10:30:00 CST 2024
java.util.Date
是 Java 很早期(JDK 1.0)就有的日期類別。getYear()
、getMonth()
這些方法回傳的值跟實際年份/月分有偏差(已標示為過時)。Date
本身只有「時間點」,沒有明確的時區資訊。Date
,容易造成 bug。LocalDateTime 適合用在「不需要考慮時區」的場景,比如:
LocalDateTime now = LocalDateTime.now();
System.out.println(now); // 輸出:2024-01-15T10:30:00
java.time.LocalDateTime
是 Java 8 推出的 java.time(JSR-310)新日期時間 API 之一。plusDays()
、minusHours()
、withMonth()
等直觀的方法。但問題來了:如果你的應用要服務全球用戶,LocalDateTime 就會造成困擾。同樣的 2024-01-15T10:30:00,在台灣是上午 10:30,在美國東岸卻是前一天的晚上 9:30。
java.time.Instant 代表的是「從 1970-01-01T00:00:00Z 開始的秒數」
Instant 最適合用在:
Instant now = Instant.now();
System.out.println(now); // 輸出:2024-01-15T02:30:00Z
java.time.Instant
也是 Java 8 推出的新 API。Date
類似,儲存「自 1970-01-01 00:00:00 UTC 起算的秒數 + 奈秒數」。在設計 MyMomentum 的活動記錄功能時,我選擇了 Instant,原因如下:
1. 跨時區用戶支援
我想要假設MyMomentum是可以推到世界各地的產品 。如果我用 LocalDateTime 儲存「早上 8 點運動」,台灣用戶看到的是 8:00,但美國用戶看到的是前一天的 19:00,這會造成很大的混淆。
用 Instant 儲存後,每個用戶都能看到正確的本地時間:
// 儲存時
@Column(name = "executed_at")
private Instant executedAt;
// 前端顯示時轉換為用戶當地時間
Instant utcTime = record.getExecutedAt();
ZonedDateTime localTime = utcTime.atZone(userTimeZone);
2. 資料庫查詢的一致性
PostgreSQL 的 TIMESTAMPTZ 型別與 Instant 完美對應,查詢和排序都很直觀:
-- 查詢最近 7 天的記錄
SELECT * FROM activity_records
WHERE executed_at >= NOW() - INTERVAL '7 days'
ORDER BY executed_at DESC;
3. API 設計的標準化
REST API 使用 ISO 8601 格式傳輸時間,Instant 天然支援:
{
"executedAt": "2024-01-15T02:30:00Z",
"duration": 3600
}
4. 避免時區轉換錯誤
使用 LocalDateTime 時,開發者很容易忘記時區轉換,導致資料不一致。Instant 強制我們在儲存時就考慮時區問題,減少了這類 bug。
經過比較,大概可以這樣看:
型別 | 適用場景 | 不適用場景 |
---|---|---|
Date | 維護舊專案 | 新專案開發 |
LocalDateTime | 純本地業務、生日記錄 | 跨時區應用 |
Instant | 跨時區應用、資料庫儲存、API 設計 | 純本地時間顯示 |