日曆翻了 31 頁,第一格只寫著 30
一條從 2026-03-01 00:00 倒數到 2026-04-01 00:00 的條目,機器時區 America/New_York。日曆上明明隔著 31 天,頁面第一格寫的卻是 30天 23時 00分 00秒,第二格那個 23 格外刺眼。第一反應是算錯了,讀完程式碼才知道是兩套「天」放在一起記帳。
前端只有模板那 181 行、7374 位元組,算術統統 5 行:第 121 行取 c.target - now 的毫秒,第 128 行 floor 掉毫秒位,第 129 行 days = Math.floor(totalSec / 86400)——除數是硬編碼字面量 86400,第 130—132 行再取餘攤成時、分、秒。沒有 round、ceil、toFixed,每一步向零截斷,這條折算在倒數計時頁每秒重跑一次。
2674800000 毫秒攤開是 743 個真實小時,按 86400 秒一塊只數得出 30 塊,餘下 82800 秒讀成 23 時;同組日期在無夏令時間的 Asia/Shanghai 是 2678400000 毫秒、31天 00時。虧掉的只是 1 小時,被 floor 一擋就成了少 1 天。
只有帶夏令時間的地區會錯開
下列幾組是腳本口徑實測(Node 24 注入 TZ、FakeDate 凍結基準 1793070000000):模板裡沒有任何時區或夏令時間分支,出入來自執行機器的 IANA 規則,凡是有夏令時間的地區都會撞上;臺北與上海一樣常年不設夏令時間,所以下面那組對照讀數在臺北同樣成立。
- 紐約 3-01 到 4-01:日曆 31 天 →
30天 23時;倫敦同月段一致。 - 紐約 11-01 到 11-03:回撥多給 1 小時,176400000 毫秒、49 小時、日曆 2 天 →
02天 01時。 - 雪梨 3-31 到 4-30:2595600000 毫秒、721 小時、日曆 30 天 →
30天 01時。 - 對照 3-07 到 3-09:紐約 169200000 毫秒、47 小時 →
01天 23時;同組在上海是 172800000 毫秒 →02天 00時,逐位一致。
截斷只在臨界值上看得見
把 diff 餵進那 5 行,臨界點如下(四個時區一致):
| diff 毫秒 | 頁面顯示 |
|---|---|
| 86399000 | 00天 23時 59分 59秒 |
| 86400000 | 01天 00時 00分 00秒 |
離 1 天只差 1000 毫秒時第一格仍是 0,換成 round 就會跳成 01天。四格之間不可能自相矛盾,第 130—132 行是取餘分解,harness 用 8 組 diff 驗過 days*86400 + hours*3600 + mins*60 + secs === totalSec 恆成立,矛盾的只有天格與日曆天數。
目標過了只給你一句「已到達!」
第 124 行寫的是 if (diff <= 0),含等於 0;第 125 行把四個數歸零,可第 143—144 行只輸出綠色的「已到達!」,那 4 個 0 從不上屏。diffMs=0、100 年前的 -3155760000000、連 1970-01-01T08:00 的 -1793070000000,都是同一句:沒有負號、沒有報錯,也沒有「已過期 N 天」的反向計時,卡片只留著「目標:」一行和刪除按鈕。
別把另一排 0 當成過期信號:diff 在 1 到 999 毫秒之間是 00天 00時 00分 00秒 且 reached=NO,真實 Chrome 新增瞬間 diff=268 毫秒重現的正是它,跨過 1000 毫秒才換 01秒。兩分支之間沒有第三條路,最後一秒的讀數天生少 1 秒。
預設值只留給你 1 到 3600 秒
目標框是第 20 行的 <input type="datetime-local">,不是 date,也沒有 min、max、step:日期加時刻強制成對,步進 60 秒。只寫 2026-12-31 會被淨化成空字串,彈出第 67 行「請選擇目標日期時間」;第 70 行那句「日期格式無效」在真瀏覽器裡只有 275760-09-13T00:00 打得出來。
首屏預填不是 00:00 也不是目前時刻,而是下一個整點(第 51—52 行 setHours(getHours() + 1) 後把分秒毫秒歸零):基準 11:00:00 預填 2026-10-27T12:00,剩 3600 秒;23:59:59 預填次日 00:00,只剩 1 秒。不直接改動就按新增,最壞當場就是「已到達!」。
一支計時器管 200 條,卻什麼都不記得
狀態只活在第 44 行那個陣列裡:全文 grep localStorage、sessionStorage 命中 0 處,重新整理一次就回到第 34 行「暫無倒數,請在上方新增」。第 98—101 行 if (timer) return; 之後才掛上 setInterval(render, 1000),1 條和 200 條都只有 1 個句柄,後者產物 295582 位元組;3 條全部過期後仍是 intervals=1、clearInterval 0 次。
誤差也不累積:每次都是 c.target - new Date() 重算(第 120—121 行)而非自減,時鐘往後推 86437400 毫秒,讀數當場追上。但到點什麼都不會響——Audio、Notification、document.title 全部 0 命中,「過期提醒:倒數結束後自動提示」沒有載體。
同一個 10 年有兩種折法
日曆上的 10 年是 315619200000 毫秒 → 3653天 00時;按 365.25 天折的 10 年是 315576000000 毫秒 → 3652天 12時,差 43200000 毫秒即 12 小時。100 年更乾脆,36524天 與 36525天 正好差 86400000 毫秒、1 天。
模板對外那 13 條斷言實測一致 8 條、衝突 5 條,最硬的三條正是精確到秒、過期提醒、線上天數:這裡的天是 86400 秒一塊的存貨,不是日曆撕掉的頁數。
順手答疑
Q:30天 23時 是頁面算錯了嗎?
是口徑不同:第 129 行拿 86400 秒當一天,數的是整塊時間,日曆數的是日期換了幾頁。所在地區實行夏令時間才會錯開,進入夏令時少 1 小時、回撥多 1 小時,被 floor 一擋就體現成 1 天的出入。
Q:目標過了能顯示負數或已過幾天嗎?
不能。diff <= 0 一命中就走第 144 行那句「已到達!」,1 天前和 100 年前一模一樣;那排 00天 00時 00分 00秒 只在目標前最後 999 毫秒裡出現,不是過期標誌。
Q:只想數相差幾天,或者要一個到點提醒?
數天數請換口徑:站內日期天數計算按日曆日數,不會掛著 23 時的尾巴。提醒這裡給不了——本頁既不存清單也不出聲,到點只有畫面上一句綠字。