歷史包袱:為什麼要棄用 java.util.Date?
- 線程不安全:
SimpleDateFormat 非線程安全,多線程下易拋 NumberFormatException 或產生錯誤日期;
- 設計缺陷:月份 0-11、年份從 1900 起、可變對象導致防禦性拷貝泛濫;
- 時區處理痛苦:
Date 本質是 UTC 時間戳,顯示時才套用默認時區,極易產生「同一時刻顯示不同日期」的 Bug。
Java 8 三大核心類:Instant、LocalDateTime、ZonedDateTime
| 類 | 含義 | 適用場景 |
Instant | 時間線上的瞬間(UTC 時間戳,納秒精度) | 日誌時間戳、數據庫存儲、跨系統傳輸 |
LocalDateTime | 無時區的日期時間(如 2024-06-15T14:30) | 業務邏輯、表單綁定、定時任務觸發時間 |
ZonedDateTime | 帶時區的日期時間(如 2024-06-15T14:30+08:00[Asia/Shanghai]) | 用戶面向顯示、跨時區業務、國際化系統 |
常用操作速查表
1. 獲取當前時間
Instant nowInstant = Instant.now(); // UTC 納秒
LocalDateTime nowLocal = LocalDateTime.now(); // 系統默認時區
ZonedDateTime nowZoned = ZonedDateTime.now(); // 帶時區
ZonedDateTime nyTime = ZonedDateTime.now(ZoneId.of("America/New_York")); // 指定時區
2. 字符串解析與格式化(線程安全!)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-06-15 14:30:00", fmt);
// 格式化
String formatted = nowLocal.format(fmt); // "2024-06-15 14:30:00"
最佳實踐:DateTimeFormatter 為不可變對象,可定義為 static final 常量,全局共享,零同步開銷。
3. 日期時間運算(不可變,鏈式調用)
LocalDateTime future = nowLocal.plusYears(1).plusMonths(2).minusDays(3);
LocalDateTime past = nowLocal.minusWeeks(1).withHour(0).withMinute(0).withSecond(0); // 當天零點
4. 互轉:Instant ⇄ LocalDateTime ⇄ ZonedDateTime
// Instant -> LocalDateTime (需指定時區)
LocalDateTime ldt = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
// LocalDateTime -> Instant (需指定時區)
Instant instant = ldt.atZone(ZoneId.systemDefault()).toInstant();
// LocalDateTime -> ZonedDateTime
ZonedDateTime zdt = ldt.atZone(ZoneId.of("Asia/Shanghai"));
// 時間戳長整型互轉
long epochMilli = instant.toEpochMilli();
Instant fromMilli = Instant.ofEpochMilli(epochMilli);
5. 截取/比較/區間
LocalDate date = ldt.toLocalDate(); // 只要日期部分
LocalTime time = ldt.toLocalTime(); // 只要時間部分
boolean isBefore = ldt1.isBefore(ldt2); // 比較
Duration gap = Duration.between(ldt1, ldt2); // 精確間隔(納秒級)
Period period = Period.between(date1, date2); // 日期間隔(年/月/日)
與舊 API 互操作(遷移期必備)
// Date -> Instant
Instant instant = oldDate.toInstant();
// Instant -> Date
Date date = Date.from(instant);
// Calendar -> ZonedDateTime
ZonedDateTime zdt = calendar.toInstant().atZone(calendar.getTimeZone().toZoneId());
// ZonedDateTime -> Calendar
Calendar cal = Calendar.from(zdt);
格式化模式字符對照表
| 符號 | 含義 | 示例 |
| y | 年 | 2024 |
| M | 月 | 06 / Jun / June |
| d | 日 | 15 |
| H | 時(0-23) | 14 |
| h | 時(1-12) | 02 |
| m | 分 | 30 |
| s | 秒 | 05 |
| S | 毫秒 | 123 |
| z | 時區名 | CST |
| X | 時區偏移 | +0800 / +08:00 |
總結:遷移清單
- 新代碼全面使用 java.time.*,禁用
java.util.Date、Calendar、SimpleDateFormat;
- 數據庫存儲用
Instant(對應 TIMESTAMP WITH TIME ZONE)或 LocalDateTime(對應 TIMESTAMP);
- 所有格式化器定義為
static final DateTimeFormatter;
- 業務邏輯傳遞
LocalDateTime,跨時區顯示轉 ZonedDateTime;
- 遺留系統邊界用
.toInstant() / Date.from() 無縫銜接。