Linux 时区文件格式 (TZif文件格式详解)
时区文件格式标准https://datatracker.ietf.org/doc/html/rfc8536一、摘要TZif- The Time Zone Information Format格式化的时区信息。TZif 文件或称时区文件是按照特定格式排列时区相关数据而得到的一个二进制文件本文主旨既详细说明该文件中数据是如何排布的。时区文件一般存放在 Linux 系统的 /usr/share/zoneinfo/ 路径下。用法将 /etc/locatime 链接到 /usr/share/zoneinfo/ 路径下某个具体的时区文件Linux 系统就立即采用了该文件所指定的时区规则。Linux 系统是如何感知时区文件的、时区文件是如何生效的、不同的进程修改软连接为啥有的进程能感知有的却感知不到这些问题也够写一篇文章的了还愿删二、TZif 文件格式总览┌──────────────────────────────┐ │ 32-bit Header (44 bytes) │ ├──────────────────────────────┤ │ 32-bit Data (minimal) │ ├──────────────────────────────┤ │ 64-bit Header (44 bytes) │ ├──────────────────────────────┤ │ 64-bit Data (full) │ ├──────────────────────────────┤ │ \n POSIX TZ string \n │ ← footer只此一份 如 \nSTD-8DST,M3.2.0/2,M11.1.0/2\n └──────────────────────────────┘ 注意 POSIX 符号反转STD-8表示在UTC基础上加8小时每个时区文件都由5部分组成分别是32位头、32位数据区、64位头、64位数据区以及一个Footer字符串。32-bit 区和 64-bit 区的格式是完全相同的唯一不同的就是时间戳长度32位时间戳存在2038年溢出的问题因此后来新增了 64-bit 区以解决2038的时间戳溢出问题。最后一个 Footer 字符串是一个安全措施为的是夏令时存在时在数据区规则耗尽的无穷未来让系统也有规则可依。三、时区文件头32-bit 头和 64-bit 头格式完全相同因此本节三、时区文件头内容就不再区分32-bit 头和 64-bit 头了。时区文件头固定为 44 字节如下表1tzh_ 前缀表示 Time Zone Header时区文件头表1 时区文件头字节偏移 长度 字段 值 十六进制 0–3 4 tzh_magic TZif 54 5A 69 66 4 1 tzh_version 0版本1 00 ← 2表示版本2十六进制为0x32 5–19 15 tzh_reserved 全零 00 … 00 20–23 4 tzh_ttisutcnt 0 00 00 00 00 ← 可认为已弃用 24–27 4 tzh_ttisstdcnt 0 00 00 00 00 ← 可认为已弃用 28–31 4 tzh_leapcnt 0 00 00 00 00 ← 可认为已弃用 32–35 4 tzh_timecnt 0 00 00 00 00 36–39 4 tzh_typecnt 1 00 00 00 01 40–43 4 tzh_charcnt 4 00 00 00 044字节魔法字符串 TZif 将其标识为时区信息文件本文简称时区文件。1字节标识文件格式版本的单个字符‘\0’(0x0) 或 ‘2’(0x32) 或 ‘3’(0x33) 或 ‘4’(0x34)。15字节全零保留以备将来使用。24字节6个 long 类型的4字节值按“标准”字节顺序写入值的高字节先写入。这些值依次为① tzh_ttisutcnt - time type is UTC conut设置为0即可。(如果想了解可以看附录)② tzh_ttisstdcnt - time type is standard conut设置为0即可。③ tzh_leapcnt - leap second count设置为0即可。④ tzh_timecnt - 时间类型发生转换的时刻的数量。无夏令时此值为0有夏令时此值大小与时区文件所覆盖的 min_year ~ max_year 有关。例如下表2min_year2022max_year2025且均启用夏令时每年有2次转换因此该值为8。⑤ tzh_typecnt - 时间类型的数量。例如无夏令时值为1有夏令时值为2表示存在两种时间类型一种是标准时间另一种是夏令时有些年份采用夏令时A另外一些年份采用夏令时B则值为3依此类推。不得为零。⑥ tzh_charcnt - 记录了缩写字符池中的总字符数包括’\0’。表2 以2022年 ~ 2025年 为例的时间类型说明和转换时刻列举attypes[] 索引 .atUTC时间戳 .type 含义 0 1647108000 (2022-03-13 02:00 UTC) 1 STD → DST 1 1667703600 (2022-11-06 02:00 UTC) 0 DST → STD 2 1678624800 (2023-03-12 02:00 UTC) 1 STD → DST 3 1699153200 (2023-11-05 02:00 UTC) 0 DST → STD 4 1710074400 (2024-03-10 02:00 UTC) 1 STD → DST 5 1730602800 (2024-11-03 02:00 UTC) 0 DST → STD 6 1741524000 (2025-03-09 02:00 UTC) 1 STD → DST 7 1762052400 (2025-11-02 02:00 UTC) 0 DST → STD四、时区文件数据区文件头之后就是数据区排布如下┌─────────────────────────────┐ │ Header (44 bytes) │← 含 timecnt, typecnt, charcnt 等计数器 ├─────────────────────────────┤ │ (1) Transition times │← timecnt 条每条 4 字节(pass1) 或 8 字节(pass2) │ │ ├─────────────────────────────┤ │ (2) Transition types │← timecnt 条每条 1 字节值为 type 索引 │ │ ├─────────────────────────────┤ │ (3) Local time types │← typecnt 条 每条 6字节 │ utoff(4) isdst(1) idx(1)│ ├─────────────────────────────┤ │ (4) Abbreviation chars │← charcnt 字节 连续字符池 │ EST\0EDT\0 │ ├─────────────────────────────┤ │ (5) Leap seconds │← leapcnt0, 无该条目 │ (6) std/wall indicators │← ttisstdcnt0无该条目 │ (7) UT/local indicators │← ttisutcnt0无该条目 └─────────────────────────────┘上面的标题后面跟着tzh_timecnt四个字节的值类型为long按升序排序。这些值以标准字节顺序写入。每个都被用作转换时间如时间2返回的用于计算本地时间的规则在该转换时间改变。接下来是tzh_timecnt类型为unsigned char的单字节值每一个都告诉文件中描述的不同类型的本地时间类型中的哪一个与相同的索引转换时间相关联。这些值用作文件中下一个出现的ttinfo结构数组带有tzh_typecnt条目的索引这些结构定义如下五、时区文件 Footer/usr/local/etc/zoneinfo用于创建时区文件的标准目录。5. NOTES注意事项对于具有两种以上本地时间类型的地区您可能需要在最早转换时间规则的 AT 字段中使用本地标准时间以确保编译文件中记录的最早过渡时间是正确的。6. SEE ALSO另请参阅tzfile(5) zdump(8)附录A、tzh_ttisutcnt 和 tzh_ttisstdcnt这俩主要用于处理历史时区数据。实际应用场景1、历史时区标准化之前1883 年之前各地使用本地太阳时没有统一时区。例如地点实际偏移纽约UTC-4:56:02太阳时巴黎UTC0:09:21太阳时这些偏移不是整小时需要精确记录转换时间是 UTC 还是本地时间。2、夏令时规则变更历史上 DST 规则多次变更转换时刻可能用不同基准记录UT 基准转换时刻以 UTC 记录国际通用标准时间基准转换时刻以标准时间记录不含 DST 偏移墙钟时间基准转换时刻以实际钟表时间记录含 DST 偏移3、IANA tzdb 中的真实例子# 美国东部历史时区节选 Zone America/New_York -4:56:02 - LMT 1883 Nov 18 # 本地太阳时 -5:00 US E%sT 1942 # 标准时间 -5:00 US E%sT # 后续读取器需要指示器来判断① 1883 年之前的转换是本地太阳时非 UTC② 1942 年二战期间的 “War Time” 转换基准指示器的实际值示例:TZif 文件America/New_York 历史数据 tzh_ttisutcnt 2 ← 有 2 个转换使用 UT 指示器 tzh_ttisstdcnt 1 ← 有 1 个转换使用标准时间指示器 数据区 [1, 0] ← 第1个转换是UTC第2个是本地时间 [1] ← 第1个转换是标准时间简言之这两个字段是 TZif 格式为完整支持全球历史时区而设计的IANA tzdb 大量使用一般开发项目只需考虑现代附近至整个未来的时区 DST 规则所以不需要这俩字段。B、leapcnt从2026a版本的tzdata时区数据库开始主流Linux发行版默认不再安装包含闰秒信息的right时区文件。这意味着你系统中大多数标准的TZif文件其tzh_leapcnt值都会是0。正被逐步淘汰国际计量大会已决议计划在2035年前取消闰秒制度。因此闰秒的支持正在从系统底层淡化。