雜記 › 生活隨筆 › 世界時間的偏移是誰給的:一條正則,和一個沒有夏令時間的寫死小時數

世界時間的偏移是誰給的:一條正則,和一個沒有夏令時間的寫死小時數

週日正午那封跨洲會議邀請

2026-03-08T12:00:00.000Z,UTC 的週日中午。人在臺北的我,給雪梨、紐約、倫敦各發一條「下午三點上線」,核對完還是把邀請撤了:同一頁上擺著兩個數,一個來自牆上的時鐘,一個來自表裡寫死的小時數。

一、13 座城是寫死在常數裡的

城市清單是模板第 32-46 行的常數,13 條,用得到的只有名稱、一個偏移小時數、一個 tz,英文名稱不渲染。列表頁文案寫「等 20+ 主要城市」,實測陣列長度 13;本機 Intl 列舉出 418 個時區,表裡只佔 13 個,臺北、首爾、多倫多、奧克蘭都不在。

城市寫死偏移2026 全年不符
北京 / 東京 / 杜拜 / 莫斯科 / 夏威夷+8 / +9 / +4 / +3 / -100 個樣點
雪梨+108737 個樣點 = 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 行一個都不命中,標籤退回寫死值。夏令時間被丟掉只有這一個入口——它不在時鐘上,在標籤上。

三、八座城的全年不符天數

Los Angeles 237.958 天 New York 237.958 天 Chicago 237.958 天 Denver 237.958 天 London 210 天 Paris 210 天 Berlin 210 天 Sydney 182.02 天 每城 17473 個樣點 · 13 城合計 227149 個 84665 個不符 = 37.3% · 不符城數 8/13

圖 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 張。

白天 6–18 工作時間 9–18 0 6 9 12 18 24 含 09:00:00、不含 18:00:00 · 有效區間跨 9 小時 = 32400 秒 星期不參與這兩處判斷

圖 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 天。

📎 相關文章

字帖每頁放幾個字:預設 A4 是 20 個,不是 100÷4=25 2026-10-27 同一天既是週末又被登記成補班,工作日計算器聽誰的 2026-10-27 倒數計時裡的 86400 秒一天,碰上夏令時間會少給你 1 天 2026-10-27 同一次計算給出兩個體脂率:預設一組差 6.1 個百分點,腰圍 1 cm 值 0.7 2026-10-26 照片轉了 90°,換一部裝置方向又回來了:EXIF 標記與真實旋轉的分工 2026-10-22