毫秒 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。