AZ Tools
← 指南

无 bug 地处理时区与 Unix 时间戳

日期和时间会带来不成比例的大量 bug,因为对人来说显而易见的事,对计算机来说确实复杂: 时区会偏移、时钟一年两次跳过一小时,而「今天」取决于你站在哪里。本指南解释核心概念,并给出几条能预先避免大多数日期处理错误的规则。

把时间存为一个绝对时刻

最重要的一条规则: 把时刻存储和传输为一个绝对、与时区无关的时刻 — 一个 Unix 时间戳(自 1970 年 1 月 1 日 UTC 起的秒或毫秒),或一个 UTC 的 ISO 8601 字符串(以 Z 结尾)。一个时刻在地球上各处都相同; 变的只是给人看的表示。

只在最外层、当你把值展示给人时,才转换到本地时区。让系统内部保持 UTC。最痛苦的日期 bug 大多源于存了一个「本地」时间却没记录它属于哪个时区,使该值永远含糊不清。

UTC、偏移量与时区不是一回事

UTC 是全球基准时钟。像 +09:00 这样的偏移量,是某一时刻相对 UTC 的固定位移。而像 America/New_York 这样的时区,是一组有名字的规则,描述历史上每个时间点适用哪个偏移量 — 包括它何时变化。

这个区别之所以重要,是因为在同一时区内偏移量也会变。纽约冬天是 -05:00,夏天是 -04:00。只存偏移量会丢掉规则,于是你无法正确计算未来的本地时间。这正是为什么安排「每天纽约时间上午 9 点」需要时区名,而非固定偏移量。

夏令时会打破天真的假设

一年两次,实行夏令时的时区会跳变。春天会跳过一小时,于是那天像 02:30 这样的本地时间根本不存在; 秋天会重复一小时,于是 01:30 发生两次、变得含糊。假设每天都有 24 小时、或假设某个给定本地时间总是存在的代码,迟早会产生错误结果或崩溃。

用 UTC 工作可以完全绕开这一点,因为 UTC 没有夏令时。只有在为显示而转换到本地时间、或在解释人的输入时,你才会碰到这些缺口与重叠 — 而那正是应当由经过测试的日期库来处理、而非手写算术的地方。

秒、毫秒,以及 2038 年

Unix 时间戳常见有两种单位,混用是常见的 bug: 大多数 Unix 工具和 API 用秒,而 JavaScript 的 Date 用毫秒。相差 1000 倍的值通常意味着单位被搞混了 — 时间戳突然跑到 56000 年,或退回 1970 年。

还有大小上限: 存于有符号 32 位整数的 Unix 时间会在 2038 年 1 月 19 日溢出。现代系统使用 64 位值,在实用层面消除了这个问题,但了解旧系统为何会错误处理遥远未来的日期仍有价值。

能避免大多数 bug 的规则

这些在日常中都不需要高深专业知识 — 几个习惯就能覆盖绝大多数情况。

  • 以 UTC 存储和交换时刻(Unix 时间戳或带 Z 的 ISO 8601); 仅在显示时转换到本地。
  • 当未来的本地时间重要时,保留 IANA 时区名(如 Europe/Paris); 仅有偏移量不够。
  • 绝不要靠为「一天」或「一个月」加固定秒数来做日历运算 — 使用了解夏令时与月份长度的日期库。
  • 在每个边界都明确是秒还是毫秒,并标注你指的是哪一个。

相关工具