Java ZonedDateTime
ZonedDateTime 是 Java 8 引入的日期時間類別,包含完整的日期、時間和時區資訊,適合處理跨時區的應用場景。
引入套件
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
建立 ZonedDateTime
取得當前日期時間(含時區)
// 使用系統預設時區
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now); // 2024-12-10T14:30:45.123+08:00[Asia/Taipei]
// 指定時區
ZonedDateTime nowInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(nowInTokyo); // 2024-12-10T15:30:45.123+09:00[Asia/Tokyo]
指定日期時間和時區
// of(年, 月, 日, 時, 分, 秒, 奈秒, 時區)
ZonedDateTime zdt = ZonedDateTime.of(
2024, 12, 25, 14, 30, 0, 0,
ZoneId.of("Asia/Taipei")
);
System.out.println(zdt); // 2024-12-25T14:30+08:00[Asia/Taipei]
// 使用 LocalDateTime + 時區
LocalDateTime ldt = LocalDateTime.of(2024, 12, 25, 14, 30);
ZonedDateTime zdt2 = ZonedDateTime.of(ldt, ZoneId.of("Asia/Taipei"));
ZonedDateTime zdt3 = ldt.atZone(ZoneId.of("Asia/Taipei"));
從字串解析
ZonedDateTime zdt = ZonedDateTime.parse("2024-12-25T14:30:00+08:00[Asia/Taipei]");
// 使用自訂格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
ZonedDateTime zdt2 = ZonedDateTime.parse("2024-12-25 14:30:00 CST", formatter);
時區 ZoneId
常用時區
ZoneId taipei = ZoneId.of("Asia/Taipei"); // 台北 UTC+8
ZoneId tokyo = ZoneId.of("Asia/Tokyo"); // 東京 UTC+9
ZoneId shanghai = ZoneId.of("Asia/Shanghai"); // 上海 UTC+8
ZoneId newYork = ZoneId.of("America/New_York"); // 紐約 UTC-5/-4
ZoneId london = ZoneId.of("Europe/London"); // 倫敦 UTC+0/+1
ZoneId utc = ZoneId.of("UTC"); // UTC 時區
// 系統預設時區
ZoneId defaultZone = ZoneId.systemDefault();
列出所有可用時區
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.stream()
.filter(z -> z.startsWith("Asia"))
.sorted()
.forEach(System.out::println);
使用 ZoneOffset
// 直接指定時區偏移
ZoneOffset offset = ZoneOffset.ofHours(8); // +08:00
ZoneOffset offset2 = ZoneOffset.of("+08:00"); // +08:00
ZoneOffset offset3 = ZoneOffset.ofHoursMinutes(5, 30); // +05:30
ZonedDateTime zdt = ZonedDateTime.of(
LocalDateTime.now(),
offset
);
取得日期時間資訊
ZonedDateTime zdt = ZonedDateTime.of(2024, 12, 25, 14, 30, 45, 0, ZoneId.of("Asia/Taipei"));
// 日期部分
int year = zdt.getYear(); // 2024
int month = zdt.getMonthValue(); // 12
int day = zdt.getDayOfMonth(); // 25
DayOfWeek dayOfWeek = zdt.getDayOfWeek(); // WEDNESDAY
// 時間部分
int hour = zdt.getHour(); // 14
int minute = zdt.getMinute(); // 30
int second = zdt.getSecond(); // 45
// 時區資訊
ZoneId zone = zdt.getZone(); // Asia/Taipei
ZoneOffset offset = zdt.getOffset(); // +08:00
// 轉換為其他類型
LocalDateTime ldt = zdt.toLocalDateTime();
LocalDate ld = zdt.toLocalDate();
LocalTime lt = zdt.toLocalTime();
Instant instant = zdt.toInstant();
時區轉換
withZoneSameInstant(保持同一時刻)
ZonedDateTime taipeiTime = ZonedDateTime.of(2024, 12, 25, 14, 0, 0, 0, ZoneId.of("Asia/Taipei"));
System.out.println("台北時間: " + taipeiTime); // 2024-12-25T14:00+08:00[Asia/Taipei]
// 轉換到東京(同一時刻,不同時區)
ZonedDateTime tokyoTime = taipeiTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("東京時間: " + tokyoTime); // 2024-12-25T15:00+09:00[Asia/Tokyo]
// 轉換到紐約
ZonedDateTime newYorkTime = taipeiTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("紐約時間: " + newYorkTime); // 2024-12-25T01:00-05:00[America/New_York]
withZoneSameLocal(保持相同本地時間)
ZonedDateTime taipeiTime = ZonedDateTime.of(2024, 12, 25, 14, 0, 0, 0, ZoneId.of("Asia/Taipei"));
// 保持本地時間 14:00,但改變時區
ZonedDateTime tokyoSameLocal = taipeiTime.withZoneSameLocal(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyoSameLocal); // 2024-12-25T14:00+09:00[Asia/Tokyo]
日期時間運算
ZonedDateTime zdt = ZonedDateTime.of(2024, 12, 25, 14, 0, 0, 0, ZoneId.of("Asia/Taipei"));
// 加減運算
ZonedDateTime plus1Day = zdt.plusDays(1);
ZonedDateTime plus2Hours = zdt.plusHours(2);
ZonedDateTime minus1Month = zdt.minusMonths(1);
// 修改特定欄位
ZonedDateTime newHour = zdt.withHour(10);
ZonedDateTime newMonth = zdt.withMonth(6);
日光節約時間處理
// 洛杉磯有日光節約時間
ZoneId la = ZoneId.of("America/Los_Angeles");
// 2024年3月10日 02:00 跳到 03:00(日光節約開始)
LocalDateTime beforeDST = LocalDateTime.of(2024, 3, 10, 1, 30);
ZonedDateTime zdtBefore = ZonedDateTime.of(beforeDST, la);
System.out.println(zdtBefore); // 2024-03-10T01:30-08:00[America/Los_Angeles]
ZonedDateTime zdtAfter = zdtBefore.plusHours(1);
System.out.println(zdtAfter); // 2024-03-10T03:30-07:00[America/Los_Angeles]
// 注意:02:30 被跳過了!
// 2024年11月3日 02:00 回到 01:00(日光節約結束)
// 這時 01:30 會出現兩次
比較 ZonedDateTime
ZonedDateTime taipei = ZonedDateTime.of(2024, 12, 25, 14, 0, 0, 0, ZoneId.of("Asia/Taipei"));
ZonedDateTime tokyo = ZonedDateTime.of(2024, 12, 25, 15, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
// 比較(會考慮時區,這兩個其實是同一時刻)
boolean isBefore = taipei.isBefore(tokyo); // false
boolean isAfter = taipei.isAfter(tokyo); // false
boolean isEqual = taipei.isEqual(tokyo); // true(同一時刻)
// compareTo 比較
int result = taipei.compareTo(tokyo); // 0(相等)
格式化輸出
ZonedDateTime zdt = ZonedDateTime.of(2024, 12, 25, 14, 30, 45, 0, ZoneId.of("Asia/Taipei"));
// 預設格式
System.out.println(zdt.toString());
// 2024-12-25T14:30:45+08:00[Asia/Taipei]
// 自訂格式
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
System.out.println(zdt.format(f1)); // 2024-12-25 14:30:45 CST
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
System.out.println(zdt.format(f2)); // 2024-12-25 14:30:45 +0800
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss VV");
System.out.println(zdt.format(f3)); // 2024-12-25 14:30:45 Asia/Taipei
實用範例
世界時鐘
public static void showWorldClock() {
ZonedDateTime now = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
String[] zones = {
"Asia/Taipei", "Asia/Tokyo", "Asia/Shanghai",
"Europe/London", "America/New_York", "America/Los_Angeles"
};
String[] names = {
"台北", "東京", "上海", "倫敦", "紐約", "洛杉磯"
};
for (int i = 0; i < zones.length; i++) {
ZonedDateTime zdt = now.withZoneSameInstant(ZoneId.of(zones[i]));
System.out.printf("%s: %s%n", names[i], zdt.format(formatter));
}
}
安排跨時區會議
public static void scheduleMeeting(ZonedDateTime meetingTime, String... zones) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm (z)");
System.out.println("會議時間:");
for (String zone : zones) {
ZonedDateTime localTime = meetingTime.withZoneSameInstant(ZoneId.of(zone));
System.out.println(" " + zone + ": " + localTime.format(formatter));
}
}
// 台北時間下午3點的會議
ZonedDateTime meeting = ZonedDateTime.of(2024, 12, 25, 15, 0, 0, 0, ZoneId.of("Asia/Taipei"));
scheduleMeeting(meeting, "Asia/Taipei", "Asia/Tokyo", "America/New_York");
// 會議時間:
// Asia/Taipei: 2024-12-25 15:00 (CST)
// Asia/Tokyo: 2024-12-25 16:00 (JST)
// America/New_York: 2024-12-25 02:00 (EST)
計算兩地時差
public static String getTimeDifference(String zone1, String zone2) {
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime time1 = now.withZoneSameInstant(ZoneId.of(zone1));
ZonedDateTime time2 = now.withZoneSameInstant(ZoneId.of(zone2));
int diff = time2.getOffset().getTotalSeconds() - time1.getOffset().getTotalSeconds();
int hours = diff / 3600;
return zone2 + " 比 " + zone1 + (hours >= 0 ? " 快 " : " 慢 ") + Math.abs(hours) + " 小時";
}
System.out.println(getTimeDifference("Asia/Taipei", "America/New_York"));
// America/New_York 比 Asia/Taipei 慢 13 小時
轉換為 UTC 時間戳
public static long toUtcTimestamp(ZonedDateTime zdt) {
return zdt.toInstant().toEpochMilli();
}
public static ZonedDateTime fromUtcTimestamp(long timestamp, String zone) {
return ZonedDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.of(zone)
);
}
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Taipei"));
long timestamp = toUtcTimestamp(zdt);
System.out.println("UTC 時間戳: " + timestamp);
ZonedDateTime restored = fromUtcTimestamp(timestamp, "Asia/Taipei");
System.out.println("還原時間: " + restored);
重點整理
ZonedDateTime包含完整的日期 + 時間 + 時區資訊- 使用
ZoneId表示時區(如Asia/Taipei) withZoneSameInstant()轉換時區但保持同一時刻withZoneSameLocal()保持本地時間但改變時區- 自動處理日光節約時間(DST)
- 跨時區應用建議使用
ZonedDateTime或Instant - 儲存到資料庫時建議轉為 UTC