1. 歷史背景:在 Java 8 之前如何處理日期
在遙遠的過去(Java 8 之前),處理日期與時間會使用 java.util.Date、java.util.Calendar,以及用於格式化的 java.text.SimpleDateFormat。這就是那種情況,讓全世界的程式設計師一起嘆氣,咬著牙寫出類似下面的程式碼:
import java.util.Date;
Date now = new Date();
System.out.println(now); // 會輸出一些奇怪的東西,例如 "Wed Jun 05 14:15:22 MSK 2025"
聽起來很簡單,但實際上並不美好。以下只是舊 API 的一些「樂趣」:
- Date 是可變(mutable)的物件。很容易被不小心改動,經常導致 bug。
- Date 與 Calendar 的月份從 0 開始(1 月是 0,12 月是 11),而天數從 1 開始。
- SimpleDateFormat 非執行緒安全:若兩個執行緒同時格式化日期,可能得到不可預期的結果。
- 大量方法被標記為 @Deprecated(已淘汰),而 IDE 不斷用黃色警告嚇你。
- 處理時區簡直是場噩夢:很容易把本地時間與 UTC 混淆,更別提夏令/冬令時間的切換。
痛點示例
import java.util.Date;
Date date = new Date(2025, 5, 1); // 年:2025,月:5(六月?),日:1
System.out.println(date); // 跟你想的不一樣!
2. 出現 java.time:全新思路
到了 2014 年,人們已經明白:舊的 API 不只是不好用——還有風險。因此 Java 引入了新的套件 —— java.time,實作 JSR‑310 規範。這個 API 受到熱門函式庫 Joda-Time 的啟發,並迅速成為事實上的標準。
主要套件與類別
- java.time —— 主要套件,新的日期與時間類別都在這裡。
- java.time.format —— 用於日期與時間的格式化與解析。
- java.time.temporal —— 進階的時間運算。
- java.time.zone —— 處理時區。
以下是新 API 的主角:
| 類別 | 用途 | 使用範例 |
|---|---|---|
|
只有日期(年、月、日) | 生日,不含時間 |
|
只有時間(時、分、秒) | 會議時間,不含日期 |
|
日期與時間,不含時區 | 本地事件 |
|
日期與時間,含時區 | 以明斯克當地時間開會 |
|
絕對時間點(UTC) | 日誌事件標記 |
|
時間間隔(時、分、秒) | 通話時長 |
|
期間(年、月、日) | 工作年資、年齡 |
範例:用新方式建立日期
import java.time.LocalDate;
LocalDate today = LocalDate.now();
System.out.println(today); // 例如 "2025-06-05"
3. 新 API 的優勢
不可變(immutable)
java.time 中的所有類別都是不可變的。也就是說:如果你建立了一個 LocalDate 物件,就不能修改它。任何操作(例如加一天)都會回傳新的物件。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
System.out.println(today); // 2025-06-05
System.out.println(tomorrow); // 2025-06-06
明確處理時區
在舊 API 中很容易忘記日期位於哪個時區。在 java.time 中一切都是明確的:如果需要時區 —— 使用 ZonedDateTime;如果不需要 —— 使用 LocalDateTime。
import java.time.ZonedDateTime;
import java.time.ZoneId;
ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]
便捷的運算與比較方法
LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true
格式化與解析
格式化與解析 —— 透過 DateTimeFormatter(詳細內容在下一堂課):
import java.time.format.DateTimeFormatter;
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"
4. 相容性:如何與舊程式碼共存
在真實世界中,非常常見 需要與使用 Date 與 Calendar 的函式庫或舊系統協作。所幸,新 API 對既有程式碼很友善:可以在新舊型別之間互相轉換。
轉換:Date ↔ Instant
import java.util.Date;
import java.time.Instant;
// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();
// Instant → Date
Date dateBack = Date.from(instant);
轉換:Calendar ↔ ZonedDateTime
import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;
// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
calendar.toInstant(),
calendar.getTimeZone().toZoneId()
);
// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));
表格:舊類別與新類別的對照
| 舊類別 | 新類別 | 備註 |
|---|---|---|
|
|
絕對時間 |
|
|
含時區的日期與時間 |
|
|
日期的格式化/解析 |
5. 實作:初步上手 java.time
假設你有一位使用者的生日。來儲存並輸出這個日期:
import java.time.LocalDate;
public class UserProfile {
private String name;
private LocalDate birthDate;
public UserProfile(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
public void printProfile() {
System.out.println("姓名: " + name);
System.out.println("生日: " + birthDate);
}
}
public class Main {
public static void main(String[] args) {
UserProfile user = new UserProfile("Alice", LocalDate.of(1998, 12, 25));
user.printProfile();
}
}
輸出:
姓名: Alice
生日: 1998-12-25
6. 比較:舊 API vs 新 API
範例:在生日上加一週
舊方法(Date/Calendar):
import java.util.Calendar;
Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // 繁瑣且不直觀
新方法(java.time):
import java.time.LocalDate;
LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01
在新 API 中,程式碼更短、更簡潔也更安全。
7. 使用 java.time 時的常見錯誤
錯誤 1:忘了物件是不可變的。
如果呼叫 date.plusDays(1); 但沒有保存回傳結果,原本的日期不會改變。
錯誤 2:混淆 LocalDate 與 LocalDateTime。
LocalDate 只保存日期(年、月、日),而 LocalDateTime 還包含時間。若需要處理時、分,別用錯型別。
錯誤 3:在新專案中使用舊類別。
只要有可能 —— 總是使用 java.time。舊類別只用於相容性需求。
錯誤 4:錯誤處理時區。
如果要保存對不同地區都重要的事件,請使用 ZonedDateTime,或至少用 Instant。LocalDateTime 不包含任何時區資訊!
錯誤 5:嘗試直接比較 LocalDate 和 LocalDateTime。
它們是不同的型別,不能直接比較。先將其轉成同一型別再比較。
GO TO FULL VERSION