周日正午那条跨洲会议邀请
2026-03-08T12:00:00.000Z,UTC 周日正午。我给悉尼、纽约、伦敦各发一条“下午三点上线”,核对完还是撤了邀请:同一页上摆着两个数,一个来自墙上时钟,一个来自表里写死的小时数。
一、13 座城是写死在常量里的
城市清单是模板第 32-46 行的常量,13 条,用得上的只有名字、一个偏移小时数、一个 tz,英文名不渲染。列表页文案写「等 20+ 主要城市」,实测数组长度 13;本机 Intl 枚举出 418 个时区,表里只占 13 个,台北、首尔、多伦多、奥克兰都不在。
| 城市 | 写死偏移 | 2026 全年不符 |
|---|---|---|
| 北京 / 东京 / 迪拜 / 莫斯科 / 夏威夷 | +8 / +9 / +4 / +3 / -10 | 0 个样点 |
| 悉尼 | +10 | 8737 个样点 = 182.02 天 |
| 伦敦 / 巴黎 / 柏林 | 0 / +1 / +1 | 各 10080 个 = 210 天 |
| 纽约 / 芝加哥 / 丹佛 / 洛杉矶 | -5 / -6 / -7 / -8 | 各 11422 个 = 237.958 天 |
每城按 30 分钟一步取 17473 个样点,13 城 227149 个,84665 个不符,占 37.3%。
二、偏移是谁给的:一条正则,和一个写死的小时数
标签有两条来源。主路径在第 92-112 行:向 Intl 要 shortOffset 那串文本,喂给第 101 行的正则 /GMT([+-])(\d{1,2})(?::(\d{2}))?/,命中才折成分;命中不了就落到第 111 行 return fallbackOffset * 60,即表里写死的小时数乘以 60。
parse 那条路对非整点偏移算得对:实测 "GMT+5:30" → 330 分、"GMT+8:45" → 525、"GMT-3:30" → -210,标签打成 UTC+5:30;回退那条的粒度是 60 分钟,只出整小时,裸串 "GMT" 与缩写一律给 null。
同一瞬时把 Intl 的选项换成 short,纽约给 "EDT"、洛杉矶给 "PDT",4 座美国城市在第 101 行一个都不命中,标签退回写死值。夏令时被丢掉只有这一个入口——它不在时钟上,在标签上。
三、八座城的不符天数
图 1:写死 offset 与真实偏移全年不符的天数
三档:悉尼 182.02 天,伦敦、巴黎、柏林各 210 天,4 座美国城市各 237.958 天,即 4368.5、5040、5711 小时,剩下 5 座全年 0 个不符。基准 1772971200000 上墙钟没毛病,这堆天数量的是标签会退回哪个数。
四、降级路径连墙钟一起错
第 115-129 行的兜底不问 tzdb:第 116 行 getTimezoneOffset()*60000 先按宿主偏移平移瞬时,第 121-125 行再从平移后的瞬时取值。基准 1772971200000 实测 5 座城慢 1 小时——悉尼 22:00:00(真值 23:00:00)、纽约 07:00:00、芝加哥、丹佛、洛杉矶同样,其余 8 城 ±0h;换成 1784116800000(同年 7 月)变 7 座,1768478400000(1 月)只剩 1 座。
第二处误差更隐蔽。模拟一台洛杉矶宿主(T=1772962200000)后 13 城分两组:7 座被拖快 1 小时、6 座显示 0h,悉尼恰在这 0h 里——“宿主 +1h”与“写死 -1h”互相抵消,看着严丝合缝,其实两处各错 1 小时。发邀请时这种“刚好对上”最贵。
五、没有“相差 N 小时”,差值是你自己减的
两城时差在页面上读不到:<input、<select、<button 命中 0 处,也没有“相差”这个词。能相减的只有两个标签,而标签来自第 111 行、墙钟来自第 61-66 行,切换日两个口径各给一个数:北京—纽约 1772971200000 差 13h、30 分钟后 1772955000000 变 12h;北京—伦敦 1792888200000 差 7h、1792891800000 变 8h,回拨日差值反而变大;北京—悉尼 1775316600000 是 -3h、1775320200000 变 -2h。
星期也不总是同一个:1772947800000 那刻,北京已是周日 13:30,芝加哥、丹佛、洛杉矶、夏威夷还停在周六。我那句“周日加班”对半屏收件人其实是周六。
六、徽章只认小时
第 152 行 t.hour >= 6 && t.hour < 18 决定卡上是太阳图标还是月亮图标,第 153 行那对阈值是 9 与 18,决定绿边框与「工作时间」徽章;第 174 行把星期打了出来,两处判断都不看它。基准那天恰是周日,实测仍有 5 张卡挂着徽章,同一时刻太阳图标 8 张、月亮图标 5 张。
图 2:两套小时阈值画在同一条 24 小时轴上
边界实测:1776257999000 false、1776258000000 true、1776290399000 true、1776290400000 false。
七、一秒重写一次,且什么都不记得
刷新只有第 204 行一个 setInterval(render, 1000),第 185 行整块 innerHTML 重建 13 张卡、每次 8067 字节,代码口径 86400 次/天 ≈ 664.7 MB/天;visibilitychange、clearInterval 命中 0 处,后台照写。访客时区靠字符串全等,418 个时区只有 13 个在表内,Asia/Taipei、Europe/Zurich 的高亮卡数都是 0。
现在我把它当对照表读:只信墙钟那一格,标签要和第 111 行的写死值对上号才敢抄进邮件。表里没有的城市,得先在世界时间之外另找一份 tzdb 口径,毫秒级的核对按时间戳换算那篇;“同一件事两种数法”这类坑,每 28 天与每月同日那篇里踩过一模一样的。
常被问反的三件事
Q:那这页还能不能用?
能,只读卡片时刻那一格:墙钟走第 61-66 行,标签走第 111 行,两者在切换日分成两个数。
Q:换个浏览器就绕过去了?
不能。Node 与 headless Chrome 在同一基准逐字节一致,innerHTML 同为 8067 字节。
Q:哪几座城一年到头不会错?
北京、东京、迪拜、莫斯科、夏威夷 5 座,不符样点 0 个;其余 8 座最少 182.02 天、最多 237.958 天。