核心類均為 不可變、線程安全,領域模型清晰:
| 類 | 含義 | 典型場景 |
|---|---|---|
| Instant | 時間線上的瞬間 (UTC、納秒精度) | 時間戳、日誌記錄、數據庫存儲 |
| LocalDate | 無時區日期 (年月日) | 生日、合同簽署日、截止日期 |
| LocalTime | 無時區時間 (時分秒納秒) | 鬧鐘、營業時間、每日定時任務 |
| LocalDateTime | 無時區日期時間 | 本地排程、表單提交時間 |
| ZonedDateTime | 帶時區日期時間 | 用戶顯示、跨時區業務、定時任務調度 |
| OffsetDateTime | 帶偏移量日期時間 | API 交互、數據庫 TIMESTAMP WITH TIME ZONE |
| Duration | 時間間隔 (秒/納秒) | 執行耗時、倒計時、TTL |
| Period | 日期間隔 (年月日) | 年齡計算、訂閱周期 |
// 當前瞬間 (UTC)
Instant nowInstant = Instant.now();
// 系統默認時區的日期時間
LocalDate today = LocalDate.now();
LocalTime nowTime = LocalTime.now();
LocalDateTime nowDateTime = LocalDateTime.now();
ZonedDateTime nowZoned = ZonedDateTime.now(); // 推薦:含時區信息
// 指定時區 (如中國標準時間 UTC+8)
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime shanghaiNow = ZonedDateTime.now(shanghai);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = nowDateTime.format(formatter); // "2024-03-15 14:30:05"
LocalDateTime parsed = LocalDateTime.parse("2024-03-15 14:30:05", formatter);
// ISO 標準格式 (無需定義 formatter)
String iso = nowInstant.toString(); // "2024-03-15T06:30:05.123Z"
Instant parsedInstant = Instant.parse(iso);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate lastMonth = today.minusMonths(1);
LocalDateTime in3Hours = nowDateTime.plusHours(3);
ZonedDateTime in45Days = nowZoned.plusDays(45);
// 精確調整
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
LocalDate nextFriday = today.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
// 同一瞬間,不同時區的表達
ZonedDateTime nyTime = shanghaiNow.withZoneSameInstant(ZoneId.of("America/New_York"));
// 僅改變時區標譤,保持本地時間不變 (極少用)
ZonedDateTime sameLocal = shanghaiNow.withZoneSameLocal(ZoneId.of("UTC"));
// 兩個時間點之間的間隔
Duration duration = Duration.between(startInstant, endInstant);
long millis = duration.toMillis();
long seconds = duration.getSeconds();
Period period = Period.between(startDate, endDate);
int years = period.getYears();
int months = period.getMonths();
int days = period.getDays();
// Date -> Instant
Instant instant = oldDate.toInstant();
Date date = Date.from(instant);
// Calendar -> ZonedDateTime
ZonedDateTime zdt = calendar.toInstant().atZone(calendar.getTimeZone().toZoneId());
// sql.Date / sql.Timestamp
java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);
LocalDate localDate = sqlDate.toLocalDate();
Timestamp ts = Timestamp.valueOf(localDateTime);
LocalDateTime ldt = ts.toLocalDateTime();
// Quartz / Spring Task 使用 ZonedDateTime 避免夏令時陷阱
@Scheduled(cron = "0 0 3 * * ?", zone = "Asia/Shanghai")
public void dailyJob() { ... }
public final class DateUtils {
private static final DateTimeFormatter DEFAULT_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");
public static String now() {
return LocalDateTime.now(DEFAULT_ZONE).format(DEFAULT_FMT);
}
public static LocalDateTime parse(String str) {
return LocalDateTime.parse(str, DEFAULT_FMT);
}
// 禁止實例化
private DateUtils() {}
}
| 錯誤代碼 | 問題 | 正確寫法 |
|---|---|---|
| new Date() | 已棄用構造器,無時區 | Instant.now() / ZonedDateTime.now() |
| Calendar.getInstance() | 可變、線程不安全、月份0基 | LocalDate.now() / ZonedDateTime.now() |
| SimpleDateFormat | 線程不安全 | DateTimeFormatter (不可變、線程安全) |
| LocalDateTime 存數據庫 | 無時區信息,跨時區錯誤 | 存 Instant/UTC,或存 OffsetDateTime |
| Duration.between(LocalDate...) | 類型不匹配 | Period.between 計算年月日,Duration 計算時分秒 |
Java 8 `java.time` 包徹底解決了舊 API 的所有痛點:不可變、線程安全、領域模型清晰、時區支持完善、函數式風格。新項目全面禁用 `Date`、`Calendar`、`SimpleDateFormat`,舊項目逐步遷移。掌握 Instant (機器時間) ↔ ZonedDateTime (人類時間) ↔ LocalDateTime (本地時間) 三者轉換,是處理好時間業務的關鍵。