bigywater.tech
Java · 348

ZoneRules:时区规则不是固定偏移

时区不是一个固定的偏移。这篇直接读 ZoneRules,看夏令时的空洞和重叠、一天的长度、历史上的偏移、未来时间如何推算,以及 ZoneId.of 的边界。

0 / 2
2026-03-08T02:30 与 2026-11-01T01:30 在纽约的有效偏移个数(空洞与重叠)
23 / 24 / 25 h
纽约 2026-03-08、03-07、11-01 这几天的实际长度
177 + 2
America/New_York 的显式转换条数与重复规则条数

空洞与重叠

实测 · 本机执行输出
  [gap]
    2026-06-01T12:00 的有效偏移:[-04:00];getTransition = null
    2026-03-08T02:30 的有效偏移:[];getTransition = Transition[Gap at 2026-03-08T02:00-05:00 to -04:00]
    2026-11-01T01:30 的有效偏移:[-04:00, -05:00];getTransition = Transition[Overlap at 2026-11-01T02:00-04:00 to -05:00]
    ZonedDateTime.of(gap) → 2026-03-08T03:30-04:00[America/New_York](落在空洞里的时间被顺延)
    ZonedDateTime.of(overlap) → 2026-11-01T01:30-04:00[America/New_York](默认取较早的偏移)
      .withLaterOffsetAtOverlap() → 2026-11-01T01:30-05:00[America/New_York];两者相差 PT1H
    OffsetDateTime 的偏移要自己给:LocalDateTime.of(gap).atOffset(-05:00) → 2026-03-08T02:30-05:00(这个本地时间在纽约并不存在,API 不报错)

一天的长度

实测 · 本机执行输出
  [daylen]
    2026-03-07 在 New_York 有 24 小时(atStartOfDay 2026-03-07T00:00-05:00[America/New_York])
    2026-03-08 在 New_York 有 23 小时(atStartOfDay 2026-03-08T00:00-05:00[America/New_York])
    2026-11-01 在 New_York 有 25 小时(atStartOfDay 2026-11-01T00:00-04:00[America/New_York])
    2026-11-02 在 New_York 有 24 小时(atStartOfDay 2026-11-02T00:00-05:00[America/New_York])
    2026-03-07T12:00 纽约:plusDays(1) = 2026-03-08T12:00-04:00[America/New_York];plus(Duration.ofHours(24)) = 2026-03-08T13:00-04:00[America/New_York]
      Period.ofDays(1) 与 Duration.ofDays(1) 在 ZonedDateTime 上:12:00 vs 13:00
    同一天在 Asia/Shanghai(2026-03-08):24 小时

Asia/Shanghai 的历史

实测 · 本机执行输出
  [history]
    Asia/Shanghai isFixedOffset = false;ZoneOffset.ofHours(8) 的规则 isFixedOffset = true
      1850-01-01T00:00:00Z → 偏移 +08:05:43 夏令时偏移量 PT0S
      1901-01-01T00:00:00Z → 偏移 +08:00    夏令时偏移量 PT0S
      1940-07-01T00:00:00Z → 偏移 +09:00    夏令时偏移量 PT1H
      1988-07-01T00:00:00Z → 偏移 +09:00    夏令时偏移量 PT1H
      1992-07-01T00:00:00Z → 偏移 +08:00    夏令时偏移量 PT0S
      2026-07-01T00:00:00Z → 偏移 +08:00    夏令时偏移量 PT0S
    Asia/Shanghai 的显式转换共 29 条;最早 Transition[Overlap at 1901-01-01T00:00+08:05:43 to +08:00];最晚 Transition[Overlap at 1991-09-15T02:00+09:00 to +08:00]
    1988 夏令时开始与结束:[Transition[Gap at 1988-04-17T02:00+08:00 to +09:00], Transition[Overlap at 1988-09-11T02:00+09:00 to +08:00]]
    Europe/London 1970-07-01 的偏移 = +01:00(1968–1971 全年夏令时);1970-01-01 = +01:00

未来如何推算

实测 · 本机执行输出
  [future]
    New_York 显式转换 177 条,最晚一条在 2008-11-02T02:00;重复规则 2 条:
        TransitionRule[Gap -05:00 to -04:00, SUNDAY on or after MARCH 8 at 02:00 WALL, standard offset -05:00]
        TransitionRule[Overlap -04:00 to -05:00, SUNDAY on or after NOVEMBER 1 at 02:00 WALL, standard offset -05:00]
    2026-07-01 偏移 -04:00;该年下一次转换 Transition[Overlap at 2026-11-01T02:00-04:00 to -05:00]
    2100-07-01 偏移 -04:00;该年下一次转换 Transition[Overlap at 2100-11-07T02:00-04:00 to -05:00]
    3000-07-01 偏移 -04:00;该年下一次转换 Transition[Overlap at 3000-11-02T02:00-04:00 to -05:00]
    2100 年之后的第一次转换:2100-03-14T03:00
      Asia/Shanghai        显式转换  29 条,重复规则 0 条,最晚的显式转换 1991-09-14T17:00:00Z
      Asia/Tokyo           显式转换   9 条,重复规则 0 条,最晚的显式转换 1951-09-08T15:00:00Z
      Europe/Paris         显式转换 102 条,重复规则 2 条,最晚的显式转换 1997-10-26T01:00:00Z
      Australia/Sydney     显式转换  86 条,重复规则 2 条,最晚的显式转换 2009-10-03T16:00:00Z
      Africa/Casablanca    显式转换 196 条,重复规则 0 条,最晚的显式转换 2087-05-11T02:00:00Z
      America/Sao_Paulo    显式转换  91 条,重复规则 0 条,最晚的显式转换 2019-02-17T02:00:00Z
      Asia/Kolkata         显式转换   7 条,重复规则 0 条,最晚的显式转换 1945-10-14T17:30:00Z

tzdata 版本

实测 · 本机执行输出
  [JDK 17]
    JDK 17.0.20.1;tzdata 版本(Asia/Shanghai)= [2026b];可用 ZoneId 数 604
      Asia/Almaty            2024-06-01 偏移 +05:00  2026-07-01 偏移 +05:00  夏令时=false
      America/Mexico_City    2024-06-01 偏移 -06:00  2026-07-01 偏移 -06:00  夏令时=false
      America/Ciudad_Juarez  2024-06-01 偏移 -06:00  2026-07-01 偏移 -06:00  夏令时=true
      Europe/Kyiv            2024-06-01 偏移 +03:00  2026-07-01 偏移 +03:00  夏令时=true
      Europe/Kiev            2024-06-01 偏移 +03:00  2026-07-01 偏移 +03:00  夏令时=true
      Asia/Yangon            2024-06-01 偏移 +06:30  2026-07-01 偏移 +06:30  夏令时=false
      Mexico_City 2022-07-01 偏移 -05:00;2023-07-01 偏移 -06:00
      Almaty 2024-02-28 偏移 +06:00;2024-03-02 偏移 +05:00
  [JDK 21]
    JDK 21.0.12.1;tzdata 版本(Asia/Shanghai)= [2026b];可用 ZoneId 数 604
      Asia/Almaty            2024-06-01 偏移 +05:00  2026-07-01 偏移 +05:00  夏令时=false
      America/Mexico_City    2024-06-01 偏移 -06:00  2026-07-01 偏移 -06:00  夏令时=false
      America/Ciudad_Juarez  2024-06-01 偏移 -06:00  2026-07-01 偏移 -06:00  夏令时=true
      Europe/Kyiv            2024-06-01 偏移 +03:00  2026-07-01 偏移 +03:00  夏令时=true
      Europe/Kiev            2024-06-01 偏移 +03:00  2026-07-01 偏移 +03:00  夏令时=true
      Asia/Yangon            2024-06-01 偏移 +06:30  2026-07-01 偏移 +06:30  夏令时=false
      Mexico_City 2022-07-01 偏移 -05:00;2023-07-01 偏移 -06:00
      Almaty 2024-02-28 偏移 +06:00;2024-03-02 偏移 +05:00
  [JDK 27]
    JDK 27;tzdata 版本(Asia/Shanghai)= [2026b];可用 ZoneId 数 604
      Asia/Almaty            2024-06-01 偏移 +05:00  2026-07-01 偏移 +05:00  夏令时=false
      America/Mexico_City    2024-06-01 偏移 -06:00  2026-07-01 偏移 -06:00  夏令时=false
      America/Ciudad_Juarez  2024-06-01 偏移 -06:00  2026-07-01 偏移 -06:00  夏令时=true
      Europe/Kyiv            2024-06-01 偏移 +03:00  2026-07-01 偏移 +03:00  夏令时=true
      Europe/Kiev            2024-06-01 偏移 +03:00  2026-07-01 偏移 +03:00  夏令时=true
      Asia/Yangon            2024-06-01 偏移 +06:30  2026-07-01 偏移 +06:30  夏令时=false
      Mexico_City 2022-07-01 偏移 -05:00;2023-07-01 偏移 -06:00
      Almaty 2024-02-28 偏移 +06:00;2024-03-02 偏移 +05:00

ZoneId.of 的边界

实测 · 本机执行输出
  [ids]
      ZoneId.of("EST"             ) → ZoneRulesException: Unknown time-zone ID: EST
      ZoneId.of("PST"             ) → ZoneRulesException: Unknown time-zone ID: PST
      ZoneId.of("Asia/Calcutta"   ) → Asia/Calcutta  ZoneRegion
      ZoneId.of("Asia/Kolkata"    ) → Asia/Kolkata   ZoneRegion
      ZoneId.of("GMT+8"           ) → GMT+08:00      ZoneRegion
      ZoneId.of("UTC+08:00"       ) → UTC+08:00      ZoneRegion
      ZoneId.of("UTC"             ) → UTC            ZoneRegion
      ZoneId.of("Z"               ) → Z              ZoneOffset
      ZoneId.of("+08"             ) → +08:00         ZoneOffset
      ZoneId.of("Asia/Shangai"    ) → ZoneRulesException: Unknown time-zone ID: Asia/Shangai
      ZoneId.of("asia/shanghai"   ) → ZoneRulesException: Unknown time-zone ID: asia/shanghai
      ZoneId.of("America/New_York ") → DateTimeException: Invalid ID for region-based ZoneId, invalid format: America/New_York
    ZoneId.of("EST", ZoneId.SHORT_IDS) = -05:00;PST → America/Los_Angeles;CST → America/Chicago;IST → Asia/Kolkata
    Asia/Calcutta 与 Asia/Kolkata 是否 equals:false;规则对象是否相同:true

分步演示

ZoneRules
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 17.0.20.1、21.0.12.1、27,三者 tzdata 版本均为 2026b;默认时区 Asia/Shanghai
    • 没有验证:更旧 tzdata 版本的 JDK 上这些规则的不同(没有装旧 JDK)、tzupdater 工具替换 tzdb.dat 的效果、java.util.TimeZone 与 ZoneId 的行为差异、Linux 上 /usr/share/zoneinfo 与 JDK 自带数据的先后、负夏令时(如 Europe/Dublin)与 Morocco 斋月暂停夏令时的细节
    京ICP备2026067502号-1京公网安备11010502063487号