时区究竟是怎么运作的
· 约 1 分钟
时区不是地球上的一道条带,而是一组有名字的规则,讲的是某个地方的钟曾经显示什么、正在显示什么、将要显示什么。这个区别就是全部主题。
随便问一个人时区是什么,他多半会描述地图上的一道竖条:世界被切成二十四等份,彼此相差一小时,由经度说了算。这个模型整齐、好记,而且错得会在某一天让你搞砸一场会议。
时区是一组有名字的规则,由某国政府维护,规定某片领土上的钟显示什么。这些规则是会变的。它们因政治原因而变,因经济原因而变,偶尔只提前两周通知。经度是这个念头的起点,而不是它的落点。
这个念头从哪儿来
1880 年代之前,每个城镇各守各的时间,按当地正午来对——也就是太阳越过头顶子午线的那一刻。布里斯托比伦敦晚十分钟,没人在意,因为当时还没有什么东西跑得快到让十分钟变得要紧。
铁路改变了这一点。一张写着列车 10:15 发车的时刻表毫无用处,如果 10:15 在线路两端意味着不同的时刻。到 1840 年代,英国的铁路公司干脆把伦敦时间强加给了自己的整个路网。美国在 1883 年跟进,铁路公司自行做主把国家切成四个时区。1884 年的国际子午线会议把格林尼治定为本初子午线,给了世界一个共同的度量起点。
这段历史的要害在于:时区是为了解决协调问题而被发明的,发明者正是遇到这个问题的那些组织。它从来不是对太阳位置的描述。
一个时区里究竟有什么
权威记录是 IANA 时区数据库,也叫 tzdata 或 Olson 数据库。它是一个文本文件,一年更新好几次,而你用的每一个操作系统、浏览器和编程语言,读的都是它的某个版本。
对每个时区,它记录:
- 一个标识符,比如
America/New_York或Asia/Kathmandu - 该时区历史上每一段时期所适用的相对 UTC 的偏移量
- 若偏移量会变,则记录它何时变的规则
- 每一段时期的缩写,如果存在的话
标识符遵循 区域/地点 的约定,而地点是一座有代表性的城市而非国家——因为国家会分裂、会合并,也会不顾边界地更改自己的规则。America/Argentina/Buenos_Aires 之所以存在,是因为阿根廷各省曾经彼此保持着不同的时间。
请注意时区不是什么:它不是一个偏移量。America/New_York 不是“UTC-5”。它是这样一个时区:一年中一部分时间偏移为 UTC-5,其余为 UTC-4;二战期间全年停在 UTC-5;1966 年之前用的是另一套规则。把“UTC-5”存下来并称之为纽约,是软件里最常见的时区缺陷。
规则会变,而且不是慢慢地变
2016 年,土耳其在大约三周的通知期后放弃夏令时,永久停在 UTC+3。2018 年,朝鲜把钟拨了 30 分钟,以便重新与南方对齐。2022 年伊朗废除夏令时;同年墨西哥在大部分国土上取消了它。埃及在 2014 年废除之后,又于 2023 年恢复。
这里的每一件事都对应一次 tzdata 发布,而每一次都会弄坏所有把偏移量表写死在代码里的系统。这也正是本站每个页面背后的引擎从运行环境自带的 tzdata 副本读取偏移量、而不是从这里存的任何东西读取的原因。重新构建一次就接上了新规则,什么都不用改。
为什么偏移量并非都是整小时
全世界大约五分之一的人口生活在与 UTC 相差非整小时的偏移量上。印度是 UTC+05:30,这是为一个宽到足以分成两个时区的国家所选的单一折中方案。尼泊尔是 UTC+05:45,那四十五分钟也有几分是为了表明自己不随印度时间。查塔姆群岛是 UTC+12:45。纽芬兰是 UTC-03:30。
假定偏移量都是整小时的软件,不只是四舍五入而已:它会给数以亿计的人算出差了 30 或 45 分钟的答案,而这恰恰是那种在通话当中、而不是在通话之前才被发现的误差量级。
一年中当地时间不再是函数的那两天
夏令时每年制造两处不连续,这也正是“把这个当地时间换算一下”比看上去更难的原因。
往前拨会删掉一小时。 在纽约,切换当天 01:59:59 之后紧接着就是 03:00:00。当地时间 02:30 根本不发生。若有人输入它,就不存在正确的时刻可以返回——只有关于该改返回什么的政策。
往回拨会重复一小时。 01:30 出现两次,一次在夏令时,一小时后又一次在标准时。孤零零的当地时间是真正有歧义的;你得知道指的是哪一次,而几乎没有哪个界面会问。
本站的每件工具都会明确处理这两种情况,并告诉你它做了什么。悄悄挑一个,正是日历上的一条安排偏出一小时、却直到通话开始都没人察觉的那条路。
这在实践中意味着什么
从这套机制里推出三条规则,出错的情形大半都被它们覆盖:
- 存时刻,不要存当地时间。 UTC 时间戳没有歧义。当地时间等于一个时间戳,加上一个时区,再加上应对那两个尴尬日子的一条政策。
- 存时区标识符,不要存偏移量。
Europe/London熬得过规则变更;UTC+0熬不过,而且它一年里有一半时间本来就是错的。 - 在展示的那一刻用运行环境的 tzdata 做换算。 不要在存储的那一刻做,更不要用自己维护的表。
本站在提到偏移量时反复写“截至上一次构建”,并把当前时间放到你的浏览器里去算,原因完全相同。那些规则不归我们所有,我们没有资格把它们冻住。