核心類別速覽
| 類別 | 用途 | 特點 |
| LocalDate | 日期 (年月日) | 無時區、不可變 |
| LocalTime | 時間 (時分秒) | 無時區、不可變 |
| LocalDateTime | 日期時間 | 無時區、不可變 |
| ZonedDateTime | 帶時區日期時間 | 含 ZoneId、DST 自動處理 |
| Instant | 時間戳 (UTC) | 機器時間、ナノ秒精度 |
| Period | 日期量 (年月日) | 人類概念 |
| Duration | 時間量 (時分秒) | 機器概念 |
完整操作範例
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;
public class DateTimeDemo {
public static void main(String[] args) {
// 1. 取得當前
LocalDateTime now = LocalDateTime.now();
System.out.println("現在: " + now); // 2024-05-20T14:30:00.123
// 2. 建構指定時間
LocalDate date = LocalDate.of(2024, Month.DECEMBER, 25);
LocalTime time = LocalTime.of(23, 59, 59);
LocalDateTime dt = LocalDateTime.of(date, time);
System.out.println("聖誕夜: " + dt);
// 3. 解析字串
LocalTime parsedTime = LocalTime.parse("13:37", DateTimeFormatter.ofPattern("HH:mm"));
LocalDate parsedDate = LocalDate.parse("2024-12-25", DateTimeFormatter.ISO_LOCAL_DATE);
// 4. 取得欄位
System.out.println("年: " + now.getYear());
System.out.println("月: " + now.getMonth()); // MAY
System.out.println("日: " + now.getDayOfMonth());
System.out.println("星期: " + now.getDayOfWeek()); // MONDAY
System.out.println("秒: " + now.getSecond());
// 5. 時間運算 (回傳新物件,原物件不變)
LocalDateTime future = now.plusWeeks(2).plusHours(3).minusMinutes(30);
LocalDateTime past = now.minus(Period.ofDays(10));
System.out.println("兩周後: " + future);
// 6. TemporalAdjusters 進階調整
System.out.println("本月最後一天: " + now.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println("下個週三: " + now.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY)));
System.out.println("本月第一個週五: " + now.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.FRIDAY)));
// 7. 截斷精度
System.out.println("截至分鐘: " + now.truncatedTo(ChronoUnit.MINUTES));
System.out.println("截至秒: " + now.truncatedTo(ChronoUnit.SECONDS));
// 8. 時區轉換
ZonedDateTime taipei = now.atZone(ZoneId.of("Asia/Taipei"));
ZonedDateTime utc = taipei.withZoneSameInstant(ZoneOffset.UTC);
System.out.println("台北: " + taipei);
System.out.println("UTC: " + utc);
// 9. OffsetTime (固定偏移)
OffsetTime offsetTime = OffsetTime.now();
OffsetTime tokyo = offsetTime.withOffsetSameInstant(ZoneOffset.of("+09:00"));
System.out.println("東京時間: " + tokyo);
// 10. 期間與持續時間
Period period = Period.of(1, 2, 3); // 1年2月3日
Duration duration = Duration.ofHours(5).plusMinutes(30);
System.out.println("加上期間: " + now.plus(period));
System.out.println("加上持續: " + now.plus(duration));
// 11. 兩時間差
Duration between = Duration.between(
ZonedDateTime.now(ZoneId.of("Asia/Taipei")),
ZonedDateTime.now(ZoneOffset.UTC)
);
System.out.println("時差: " + between.toHours() + " 小時");
// 12. 格式化輸出 (執行緒安全)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss", Locale.TRADITIONAL_CHINESE);
System.out.println("中文格式: " + now.format(fmt));
}
}
關鍵最佳實踐
- 優先用
LocalDate/LocalTime/LocalDateTime:無時區、不可變、執行緒安全、效能最好。
- 跨時區業務必用
ZonedDateTime:自動處理夏令時間 (DST),ZoneId.of("Asia/Taipei") 而非固定偏移。
- 持久化存
Instant (UTC):資料庫存 TIMESTAMP WITH TIME ZONE 或 BIGINT (epoch milliseconds),顯示時轉換使用者時區。
DateTimeFormatter 可重用、執行緒安全:宣告為 static final 常數,避免 SimpleDateFormat 非執行緒安全的坑。
- 期間用
Period,持續時間用 Duration:Period.between(date1, date2) 算年月日,Duration.between(instant1, instant2) 算時分秒。
遷移舊代碼對照表
| 舊 API (java.util / java.sql) | 新 API (java.time) | 轉換方式 |
| new Date() | Instant.now() | Date.from(instant) / instant.atZone(zone).toLocalDateTime() |
| Calendar.getInstance() | LocalDateTime.now() / ZonedDateTime.now() | calendar.toInstant() |
| SimpleDateFormat | DateTimeFormatter | formatter.format(temporal) / formatter.parse(str) |
| TimeZone.getTimeZone("GMT+8") | ZoneId.of("Asia/Taipei") | zoneId.getRules() |
| GregorianCalendar | ZonedDateTime | gcal.toZonedDateTime() |
總結
JDK 8 java.time 以**不可變、領域驅動、執行緒安全**三大特性,徹底解決舊 API 易誤用、難國際化、效能差的痛點。新專案全面採用,舊專案逐步遷移,享受現代化時間處理體驗。