在编程圈流传着一个不成文的排序:新手怕递归,熟手怕并发,老手怕时区。有一条规定了几代人避坑姿势的视频,把“为什么时区是软件世界最深的坑”讲得清清楚楚——Tom Scott 在 Computerphile 频道的《The Problem with Time & Timezones》。这条白板讲解视频自2016年发布以来,常年出现在“每个程序员都该看的视频”清单里,至今仍是无数团队新人入职时被塞过来的第一课。本文带你逐段拆解它的论证结构,补上视频没来得及展开的标准、事故与库设计史,最后给出三条可以直接落地的工程守则。看完这篇,你等于看完了视频,还多懂了一层为什么。
数据说明:本次发布环境未能实时抓取 YouTube 页面,播放量等时效性数字从略,以下基础信息据频道页面与公开资料整理,未经本次实时核实。
| 项目 | 内容 |
|---|---|
| 标题 | The Problem with Time & Timezones(时区的问题所在) |
| 频道 | Computerphile |
| 主讲人 | Tom Scott(英国科普人,其主频道订阅数达数百万级) |
| 出品背景 | Computerphile 为英国电影人 Brady Haran 团队在诺丁汉大学创办的计算机科普频道 |
| 发布时间 | 2016年初(据频道页面) |
| 播放量 | 数百万级(精确数字本次从略) |
| 视频形式 | 白板单人口播,约15分钟 |
| 观看渠道 | YouTube 搜索标题即可找到,链接见文末参考来源 |
Tom Scott 在视频里的论证,可以按叙事顺序拆成五个要点。它们层层递进,本质上是一次“破妄”:把工程师脑中那个干净利落的“时间=一个数字”模型,一块一块敲碎。
绝大多数工程师对时间的第一直觉是:存一个时间戳,用时区偏移量加减一下就能换算。Tom Scott 开篇瞄准的就是这个直觉。他在白板上指出的核心事实是:时间不是物理量,是行政产物。GMT、UTC、夏令时、时区边界,没有一样由自然规律决定,全部由政府和标准组织开会决定。工程师以为自己在处理物理,实际在处理法律条文。
视频中花了不少篇幅讲夏令时(DST)。看似规则简单:夏天把钟拨快一小时,偏移量从 +00:00 变成 +01:00。但工程上的含义是:同一个时区,一年之内偏移量会变两次,而且每个国家变的时间和规则都不一样。北半球夏天进入夏令时,南半球恰好相反;亚利桑那州大部分地区根本不实行夏令时,但同州的纳瓦霍族保留地实行;澳大利亚的豪勋爵岛实行的是只拨半小时的夏令时。你的“偏移量”不是一个常量,甚至不是一个能从时区名字推出的常量——它是一张随年份变化的表。
这是全片最锋利的一击,也是被引用最多的一段。Tom Scott 强调(大意):未来的本地时间根本不存在确定的时间戳。 Governments 会修改时区规则——美国2005年通过《能源政策法》,把2007年起夏令时的起止日期整体挪动,全球大量系统措手不及,媒体当时甚至造了个词叫"Y2K7"。你给明年的会议存一个时间戳,等于赌明年政府的法律不变。对日程、闹钟、订阅这类“未来的本地事件”,存 UTC 时间戳本身就是错误的设计。
往前看同样危险。Tom Scott 提醒观众:标准时区制度是19世纪的发明。美国直到1883年11月18日才由铁路公司推动统一时区,那天被报纸称为“两个正午之日”——在此之前,每个城市用自己的太阳时。你问“1850年6月1日早上8点,丹佛对应 UTC 几点”,这个问题在当年的世界里没有答案,只有换算约定。任何试图用一个数学公式统一古今时区的代码,都会在历史数据面前翻车。
视频的落点是建设性的。全球时区规则的“官方账本”,是 IANA 维护的 tz database(时区数据库),它把“某一地区、某一年份、某条法律”对应的偏移量和夏令时规则全部记录在案,随各国修法持续更新。Tom Scott 给出的工程建议(大意)是:存储一律用 UTC,展示和换算交给成熟库去查这张表,永远不要自己算偏移量。他还留下那句在 HN 和 Stack Overflow 被反复引用的感慨,大意是:当你觉得自己搞懂了时区,恰恰说明你还没遇到下一个例外。
tz database 起源于1986年前后 Arthur David Olson 在加州大学伯克利分校的工作,现由 IANA 托管、Paul Eggert 担任协调维护人。它的数据格式本身就说明了问题:每条记录是“区域名(如 Asia/Shanghai)+ 一段带生效日期的历史规则序列”,因为同一个地区不同年代适用不同法律。数据库的更新日志几乎就是一部当代政治日历——某国决定取消夏令时、某地调整边界,都会触发一次发布。这也是为什么所有主流操作系统和语言运行时都要内置“tzdata 更新通道”:规则库过期,等于法律知识过期。
视频围绕时区展开,再往下挖一层是 UTC 本身。UTC 由国际计量局基于原子时(TAI)生成,为了让“秒长恒定”的原子钟不脱离地球自转,1972年起引入了“闰秒”机制——不定期在6月30日或12月31日最后一分钟加一秒。工程师为此付出了惨痛代价:2012年6月30日闰秒当晚,Linux 内核在高精度计时路径上死锁,Reddit、Mozilla 等多家网站集体宕机;2017年元旦闰秒,Cloudflare 的 DNS 服务因 Go 代码算出负的时间差而崩溃,其工程博客公开复盘了全过程。谷歌自2008年底的闰秒起采用“平滑抹秒”(leap smear)——把多出的一秒摊进当天,让服务器时间“体面地慢下来”。这场混乱在2022年有了终局性决议:国际计量大会(CGPM)通过决定,要求在2035年前废止闰秒机制——连 UTC 这个“标准答案”自己都在版本迭代。
Tom Scott 的建议后来被各个生态制度化:Java 8 引入 java.time(JSR-310,源自 Stephen Colebourne 领导的 Joda-Time 项目);.NET 世界有 Jon Skeet 主导的 Noda Time;C++20 把 Howard Hinnant 的时区库正式并入 std::chrono;JavaScript 生态的 TC39 Temporal 提案就是为了替代臭名昭著的 Date 对象而设计,多年处于推进阶段、自带 IANA 时区支持。另外,网络上流传甚广的清单《Falsehoods Programmers Believe About Time》(程序员对时间的错误迷思)罗列了几十条“想当然”,比如“一天永远24小时”“两个时间戳之差与地区无关”——几乎每一条背后都有真实事故背书。学术侧,Reingold 与 Dershowitz 的专著《Calendrical Calculations》证明了历法换算本身就足以支撑一整个研究方向。
视频强调的“规则会变”从未停歇:欧盟2018年就废除季节性拨钟做过数十万人参与的公众咨询并提交立法提案,后因成员国各有取舍而搁置(据欧盟委员会公开文件);美国参议院2022年一致通过《阳光保护法案》试图永久固定时间制度,未能在众议院过关(据国会公开记录)。对工程师而言,这意味着无论结局如何,tzdb 都会继续更新,你的系统都必须跟上。
证据充分的部分:时区规则由行政决定、历史日期换算需要规则库、“存 UTC + 用成熟库”的基本姿势——这三点在工程界是压倒性共识,且有 java.time、Noda Time 等一线库的设计文档直接背书。视频在这部分没有任何夸大。
需要补足的部分:“一切时间都存 UTC”这句话有适用边界。对于未来的本地日程(预约、航班、定时任务),正确做法是存“本地墙钟时间 + IANA 时区 ID”,到执行时刻才换算成时间戳——否则政府半夜改法,你的闹钟就集体迟到。值得注意的是,视频其实已经点到了“未来时间戳不可靠”,但广为流传的口号版把它简化成了“无脑存 UTC”,这层细微差别是十年来社区反复补课的重点。
时代局限:2016年的视频没有覆盖 C++20 chrono、Temporal 这些后来的基础设施,也没有涉及闰秒废止决议等新进展。把它当“思维方式教材”看是超值的,当“API 参考”看就会错过新工具。
第一,给存储分层:过去的用 UTC,未来的存“墙钟 + 时区 ID”。 日志、审计、历史数据存 UTC 时间戳没有争议;日程、闹钟、账单周期这类面向未来的本地事件,必须存本地时间和 IANA 区域名(如 Europe/Berlin),渲染或触发时才查表换算。这一条能避开90%的真实事故。
第二,把“时区计算”从代码里彻底驱逐。 团队规范里写死:禁止手写偏移量、禁止把时间当字符串截取运算,一律走 java.time / Noda Time / Temporal / std::chrono tz 这类经过千锤百炼的库,并把 tzdata 包纳入常规升级清单——规则库过期就是法律知识过期。
第三,把“时间的边界”写进测试用例。 让 CI 至少跑在一个夏令时时区上;为“春令时不存在的那一个小时”和“秋令时重复的那一个小时”构造边界用例;对闰秒公告、tzdb 版本保持订阅关注。时间bug的特点是平时毫无症状、在每年两次的固定时刻准时引爆——测试就该卡着这两个时刻设计。
flowchart TD
A["一个天真的假设<br/>时间就是一个数字"] --> B["直觉做法<br/>存时间戳加偏移量"]
B --> C["坑一:夏令时<br/>同区偏移一年两变"]
B --> D["坑二:政府修法<br/>规则说改就改"]
B --> E["坑三:历史日期<br/>标准时间未必存在"]
C --> F["唯一的账本<br/>IANA时区数据库"]
D --> F
E --> F
F --> G["工程铁律三件套<br/>存UTC+存时区ID+用成熟库"]
G --> H["持续更新tzdata<br/>边界写进测试用例"]
说明:本次发布环境网络抓取通道受限,未能实时访问下列页面核验时效性数据,播放量等精确数字已从略;下列链接均为长期稳定的官方地址。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
