毫秒 vs 秒:讓每個應用都崩潰的單位混淆
最常見的時間戳 bug 是在期望秒的地方傳入毫秒,或反過來。學習 10 位與 13 位法則、各語言預設值、對資料庫的影響,以及安全的轉換模式。
為什麼有兩個標準
Unix 時間最初以秒定義——對一個每個時鐘滴答遞增一次的系統來說,整數很自然。JavaScript 的 Date 物件於 1995 年引入,選擇毫秒以支援瀏覽器中次秒級的事件計時。許多資料庫與後端語言保留 Unix 秒為預設。如今兩種標準在每段跨越 JavaScript 與伺服器邊界的程式碼中並存,這就是為什麼一個值可能看起來有效卻表示數萬年之後的日期。
每種語言使用的單位
記住這種分裂最穩妥的方法是:瀏覽器 Date API 通常要毫秒,Unix 風格的伺服器 API 通常暴露秒,而較新的時間函式庫往往兩者都提供。閱讀第三方 API 文件時,不要僅憑語言推斷單位;查看欄位說明與範例。
- Milliseconds: JavaScript Date.now(), Java System.currentTimeMillis(), Java Instant.toEpochMilli(), .NET ToUnixTimeMilliseconds()
- Seconds: Python time.time() (float), PHP time(), Go time.Now().Unix(), Ruby Time.now.to_i, C time(NULL), PostgreSQL EXTRACT(EPOCH)
- Both: Rust — duration.as_secs() for seconds, duration.as_millis() for milliseconds
- Python note: time.time() returns a float, so milliseconds are available as int(time.time() * 1000)
從值自動辨識單位
對現代日期可靠的經驗法則:10 位數字是秒,13 位數字是毫秒。當前 Unix 秒約為 17–21 億(10 位),直到西元 33658 年才會達到 13 位。當前 Unix 毫秒已是 13 位。該法則在從 2000 年代到未來數千年間最為可靠;對歷史日期、負日期或精簡的測試夾具,請使用顯式單位而非猜測。
- 10 位的值 → 秒(如 1700000000 = 2023-11-14 UTC)
- 13 位的值 → 毫秒(如 1700000000000 = 2023-11-14 UTC)
- 16 位的值 → 微秒(除以 1,000,000 得到秒)
- 負值 → 1970 年 1 月 1 日之前的日期(秒或毫秒)
安全的轉換模式
轉換本身是簡單的算術;難點在於選擇在哪裡進行轉換。在系統邊界處轉換,為轉換後的值命名,避免把含糊的原始數字穿過多層傳遞。
- Seconds to milliseconds: seconds * 1000
- Milliseconds to whole seconds — JavaScript: Math.floor(ms / 1000)
- Milliseconds to whole seconds — Python: ms // 1000
- Milliseconds to whole seconds — Go: ms / 1000 (integer division)
- Universal guard in JavaScript: const toMs = ts => ts < 1e11 ? ts * 1000 : ts
單位 bug 在生產中如何顯現
秒對毫秒的 bug 常能通過驗證,因為兩個值都只是數字。它通常稍後表現為不可能的日期:把秒當作毫秒時 JavaScript 顯示 1970 年 1 月,後端把毫秒當作秒時顯示遙遠的未來年份。
- JavaScript UI 中出現 1970 年日期 → 把秒傳給 new Date() 而未乘以 1000
- Python、Go 或 PHP 中出現 50000 年以上 → 把毫秒傳給了期望秒的 API
- 永不過期的過期權杖 → 過期時間戳以錯誤單位儲存
- 立即消失的快取項 → 毫秒被除了兩次或秒被乘了兩次
- 範圍為空的分析圖表 → 查詢邊界使用了與已存事件時間戳不同的單位
API 與資料庫的命名慣例
一個很小的命名慣例就能避免大多數此類 bug。除非文件異常清晰,否則永遠不要發布名為 timestamp 的 API 欄位。優先使用同時包含含義與單位的欄位名。
- createdAtMs — Unix milliseconds, best for JavaScript clients
- createdAtSeconds — Unix seconds, common for backend services
- createdAtIso — ISO 8601 string, useful for human-readable API responses
- expiresAtUnixSeconds — explicit enough for auth tokens and signed URLs
- event_time TIMESTAMPTZ — native database time, with conversion handled by the database
毫秒 vs 秒常見問題
- 13 位時間戳總是毫秒嗎?
- 對現代真實的 Unix 時間戳而言,是的:13 位通常表示毫秒。非常遙遠的未來秒時間戳也可能達到 13 位,因此關鍵系統仍應攜帶顯式單位中繼資料。
- 應該存秒還是毫秒?
- 儲存系統自然使用的單位,但要記錄並保持一致。以 JavaScript 為主的系統常用毫秒;Unix 風格的後端與許多資料庫常用秒或原生 datetime 欄位。
- 為什麼用 Math.floor(ms / 1000) 而不是 ms / 1000?
- Unix 秒通常是整數秒。Math.floor 去掉小數部分,使期望整數秒的 API 不會收到小數。
- 如何把毫秒轉換為秒?
- 除以 1000 並捨去小數:JavaScript 用 Math.floor(ms / 1000),Python 用 ms // 1000,Go 用整數除法 ms / 1000。反向則把秒乘以 1000。
More posts
什麼是 UTC 時間?含義、偏移與轉換詳解Unix 時間轉日期:把 Unix 時間戳轉換為可讀時間紀元時間戳詳解:Unix 時間、POSIX 時間與自 1970 年起的秒epoch 轉換器指南:轉換紀元時間、Unix 時間和時間戳日期轉 epoch:把時間轉換為 Unix 時間戳或紀元時間epoch 毫秒轉日期:轉換 13 位時間戳和 long 值當前 Unix 時間戳:紀元時鐘、UTC 時間與即時 Unix 時間2026 年初的 Unix 時間戳:1767225600 詳解JavaScript Date.now():取得與轉換 Unix 時間戳在資料庫中儲存時間戳:DATETIME vs INT vs BIGINT無需函式庫的 JavaScript 時區正確日期格式化每個開發者都發布過的 7 個 Unix 時間戳 bug紀元時間詳解:Unix 時間戳零是什麼?