Skip to content

时区处理

时区问题

做国际化应用或对接海外服务,时区是绕不开的问题。

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 保持同一时刻

基于 VitePress 构建