时区处理
时区问题
做国际化应用或对接海外服务,时区是绕不开的问题。
JDK 8+ 的时区 API 设计清晰,核心类是 ZoneId,配合 ZonedDateTime 使用。
基本概念
UTC 和时区偏移
UTC (Coordinated Universal Time) 是全球统一的时间基准。
各个地区在此基础上加减偏移量得到当地时间:
UTC +8:00 = 北京时间
UTC -5:00 = 纽约时间ZoneId vs ZoneOffset
- ZoneId:地区标识,如
Asia/Shanghai - ZoneOffset:固定偏移,如
+08:00
java
// 地区 ID
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId newYork = ZoneId.of("America/New_York");
// 固定偏移
ZoneOffset offset8 = ZoneOffset.ofHours(8);
ZoneOffset offset530 = ZoneOffset.ofHoursMinutes(5, 30); // 印度
// 从 ZoneId 获取偏移
ZoneOffset currentOffset = ZoneId.systemDefault().getRules().getOffset(Instant.now());ZonedDateTime
带时区的日期时间,完整的时间表示。
创建
java
// 当前时区的当前时间
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now); // 2026-03-22T14:30:45.123456+08:00[Asia/Shanghai]
// 指定时区
ZonedDateTime tokyoTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime nyTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
// 从 LocalDateTime 转换
LocalDateTime ldt = LocalDateTime.of(2026, 3, 22, 14, 30);
ZonedDateTime zdt = ldt.atZone(ZoneId.of("Asia/Shanghai"));
// 直接创建
ZonedDateTime zdt2 = ZonedDateTime.of(2026, 3, 22, 14, 30, 0, 0, ZoneId.of("Asia/Shanghai"));计算
java
ZonedDateTime now = ZonedDateTime.now();
// 加减会自动处理夏令时
ZonedDateTime later = now.plusHours(1);
ZonedDateTime tomorrow = now.plusDays(1);
// 转换时区
ZonedDateTime tokyo = now.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyo); // 同一时刻,不同时间显示转换
java
ZonedDateTime zdt = ZonedDateTime.now();
// 转其他类型
Instant instant = zdt.toInstant(); // → Instant
LocalDateTime ldt = zdt.toLocalDateTime(); // → LocalDateTime
LocalDate date = zdt.toLocalDate(); // → LocalDate
LocalTime time = zdt.toLocalTime(); // → LocalTime实际应用
场景一:跨时区会议时间
java
// 用户输入的是北京时间
ZonedDateTime meetingBeijing = ZonedDateTime.of(
LocalDateTime.of(2026, 3, 22, 14, 0),
ZoneId.of("Asia/Shanghai")
);
// 转换为纽约时间
ZonedDateTime meetingNewYork = meetingBeijing.withZoneSameInstant(
ZoneId.of("America/New_York")
);
System.out.println("北京: " + meetingBeijing);
System.out.println("纽约: " + meetingNewYork);场景二:存储与显示分离
java
// 数据库/后端统一用 UTC 存储
ZonedDateTime utcTime = ZonedDateTime.now(ZoneOffset.UTC);
// 2026-03-22T06:30:45.123Z
// 显示时转换为本地时区
String displayTime = utcTime
.withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));场景三:夏令时处理
java
// 美国夏令时转换
ZoneId usZone = ZoneId.of("America/New_York");
// 冬令时(UTC-5)
ZonedDateTime winter = ZonedDateTime.of(
LocalDateTime.of(2026, 1, 15, 12, 0),
usZone
);
System.out.println("冬令时: " + winter); // 2026-01-15T12:00-05:00[America/New_York]
// 夏令时(UTC-4)
ZonedDateTime summer = ZonedDateTime.of(
LocalDateTime.of(2026, 7, 15, 12, 0),
usZone
);
System.out.println("夏令时: " + summer); // 2026-07-15T12:00-04:00[America/New_York]OffsetDateTime
固定偏移的日期时间,不关心地区规则:
java
// UTC 偏移
OffsetDateTime odt = OffsetDateTime.now(ZoneOffset.ofHours(8));
System.out.println(odt); // 2026-03-22T14:30:45+08:00
// 适合 API 交换(如 HTTP header 的 Date)
// ISO 8601 格式
String iso = odt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);时区工具
ZoneId 常用值
java
// 系统默认时区
ZoneId.systemDefault()
// 常用时区
ZoneId.of("Asia/Shanghai")
ZoneId.of("Asia/Tokyo")
ZoneId.of("Asia/Hong_Kong")
ZoneId.of("America/New_York")
ZoneId.of("America/Los_Angeles")
ZoneId.of("Europe/London")
ZoneId.of("UTC")遍历所有可用时区
java
ZoneId.getAvailableZoneIds().forEach(zone -> {
// 打印所有时区
// 如 Asia/Shanghai, Europe/Paris, America/New_York ...
});总结
| 类 | 用途 | 示例 |
|---|---|---|
| ZoneId | 地区标识 | Asia/Shanghai |
| ZoneOffset | 固定偏移 | +08:00 |
| ZonedDateTime | 带时区的日期时间 | 2026-03-22T14:30+08:00 |
| OffsetDateTime | 固定偏移的日期时间 | 2026-03-22T14:30+08:00 |
记住:
- 存储:统一用 UTC(Instant 或 ZonedDateTime + UTC)
- 显示:转换为用户本地时区
- 计算:用
withZoneSameInstant保持同一时刻
