UrsaKit 封面圖:把一個 Unix 時間戳拆成秒、毫秒與 2038 年溢位界線

Unix 時間戳看起來是 payload 裡最單純的東西:一個整數,好排序也好比較。然後某個 13 位數被當成秒讀、匯出的試算表早了一天,或某個欄位在 2038 年悄悄壞掉。本文談這個數字到底在數什麼、怎麼分辨單位,以及真實系統裡仍然存在的 epoch 陷阱。

閱讀時可以跟著 UrsaKit 的 時間戳轉換工具 操作。它會自動辨識秒與毫秒,在結果裡標出偵測到的單位,而且完全在瀏覽器裡執行。

01Unix 時間到底在數什麼

Unix 時間是從 1970 年 1 月 1 日 00:00:00 UTC(也就是 Unix epoch)起算、已經過去的非閏秒數量。它通常以帶號整數儲存,而 0 就正好是那一天的 UTC 午夜。

這個定義直接推出三個性質,每一個被忘記時都會產生 bug:

  • 它是一個瞬間,不是日曆。同一個時間戳在不同時區會顯示成不同的牆上時間。
  • 它是秒的計數,但扣掉閏秒。計數器跳過閏秒,所以能維持單純的整數。
  • 它有正負號。負數時間戳描述 1970 年之前的瞬間。

把時間戳當成瞬間,不要當成日期。

本文所有轉換問題,都來自太早把日曆意義套到這個數字上。只在系統邊緣、真的要顯示給人看的時候,才轉成當地字串。

02秒還是毫秒?數碼數最快

辨識單位最快的方法就是數碼數,因為每往上一級大約差一千倍。

位數單位範例值代表
10秒17911081632026-10-04T10:02:43Z
13毫秒1791108163000同一個瞬間
16微秒1791108163000000同一個瞬間
19奈秒1791108163000000000同一個瞬間

JavaScript 屬於毫秒陣營:Date.now() 回傳的是從 epoch 起算的毫秒數。光是這個事實,就解釋了 JavaScript 前端與「後端存秒」之間大部分的對不上。

位數法則不是鐵律。10 位數要到 2001 年 9 月才開始出現,而看起來像秒的值,也可能是 1973 年的毫秒時間戳。這也是為什麼轉換工具應該把偵測到的單位顯示出來,而不是默默猜一個。

03怎麼在瀏覽器裡轉換時間戳

UrsaKit 的轉換工具兩個方向都支援,可以從原始數字變成可讀日期,也可以反過來。

  1. 把值貼進 Timestamp or date 欄位。佔位範例顯示它接受的三種形狀:1700000000、1700000000000 與 2026-10-04T09:30:00Z。
  2. 想要當下瞬間就按 Use now;想從固定日期開始就按 Use ISO time。
  3. 按 Convert。
  4. 讀 Result 表格。它會列出 Unix 秒、Unix 毫秒、UTC 的 ISO 8601、本機日期時間、純日期、純時間、UTC 字串、相對時間、星期幾,以及偵測到的格式。
  5. 在你要的那一列按 Copy。

如果值解析不了,結果區會直接說這不是有效的時間戳或日期,而不是顯示一個錯的答案。看到這個訊息,就是回頭數碼數的訊號。

04實際範例

以下輸出是在 2026-10-04 轉換得到的。

輸入解讀ISO 8601(UTC)
0秒1970-01-01T00:00:00Z
1000000000秒2001-09-09T01:46:40Z
1000000000000毫秒2001-09-09T01:46:40Z
1791108163秒2026-10-04T10:02:43Z
2147483647秒2038-01-19T03:14:07Z

第三列最有用:同一個瞬間、兩種精度。看懂這個規律之後,單位對不上就不再像「日期壞掉」,而會像一個很明顯的一千倍差距。

052038 年問題

帶號 32 位元整數最大只能到 2147483647。以 32 位元的 Unix 秒來讀,那是 2038-01-19T03:14:07Z,再過一秒,值就會繞到範圍的負端——1901-12-13。

現代 64 位元系統早就跨過去了,所以風險不在你的筆電,而在還留著 32 位元時間型別的地方:

  • 用固定寬度 time_t 的韌體與嵌入式裝置
  • 把時間戳保留四個位元組的二進位檔案格式
  • 宣告成帶號 32 位元整數的資料庫欄位
  • 在 64 位元成為常態之前就定案的序列化格式

解法並不炫:把型別加寬,或在邊界改存 ISO 8601 字串。真正危險的是格式本身改不了的那些情況,這也正是這個問題至今還被討論的原因。

06你會遇到的其他 epoch

Unix 時間是主流,但不是唯一在流通的 epoch,混用就會得到固定偏移的錯誤日期。

系統Epoch單位備註
Unix / POSIX1970-01-01秒或毫秒幾乎所有地方的預設
Excel 1900 系統文件寫 1900-01-01天內含從不存在的 1900-02-29
Apple Cocoa2001-01-01秒舊 Apple API 常見
Discord1970-01-01秒用 <t:…> 標記輸出,不以文字儲存

Excel 是處理匯出資料的人最常踩的那個。微軟文件把 1900 系統寫成從 1900 年 1 月 1 日起算的天數,並舉 2011 年 7 月 5 日為序號 40729。這個數字只有在等效 epoch 是 1899-12-30 時才成立——系統帶著一個幻影 1900-02-29,所以 1900-03-01 之後的每個日期都偏移一天。2026-10-04 在本機用 python3 -c "import datetime; print((datetime.date(2011,7,5)-datetime.date(1899,12,30)).days)" 驗證,輸出 40729。

Apple 的 Cocoa epoch 是 2001 年 1 月 1 日,對應 Unix 時間 978307200。同一天用 python3 -c "import datetime; print(int(datetime.datetime(2001,1,1,tzinfo=datetime.timezone.utc).timestamp()))" 驗證。

Discord 吃 Unix 時間戳,再用標記後綴在讀者自己的時區裡渲染:<t:1791108163:R> 是相對時間,<t:1791108163:F> 是完整日期。因為標記是在客戶端解讀,同一則訊息對不同讀者會顯示不同文字——對「一個瞬間」來說,這才是正確行為。

07時區與日光節約時間的陷阱

時間戳沒有時區。時區規則只在你渲染它的時候進來,而 bug 就住在這裡。

存 UTC,在邊緣轉換。需要對齊多個時區開會時,時區轉換工具 會幫你算好。如果你發現自己把本機牆上時間當成 Unix 時間戳存起來,那已經輸了:之後新增的日光節約規則,沒辦法跟原本的意圖區分開。

有兩個具體陷阱值得點名。第一,「午夜」不是一天開始的安全標記,因為時鐘往前撥時,某些時區會直接從 23:59 跳到 01:00。第二,HTTP 日期與 cron 排程都是牆上時間概念,所以排在 02:30 的工作,在某些時區一年會有一次被跳過。測試之前,先把它換成明確的時區。

08常見錯誤

症狀原因修正
年份變成 1970 或 55000秒被當毫秒讀,或反過來數碼數,並確認偵測到的單位
日期早了一天把 Excel 序號當 Unix 時間戳用 1899-12-30 這個等效 epoch 轉換
一年兩次差一小時把本機牆上時間當瞬間存存 UTC,顯示時再轉
值剛好是 2147483647你正看著 32 位元的天花板在 2038-01-19 之前規劃遷移
相對時間顯示「4 年前」輸入是毫秒卻被當秒讀依需求乘或除以 1000

如果你真正要的是兩個日期之間的距離,而不是渲染結果,日期差距計算器 可以算天數、週數與工作日。

09資料來源

常見問題

Unix 時間戳是什麼?

它是從 1970 年 1 月 1 日 00:00:00 UTC 起算、已經過去的非閏秒數量。它是一個計數器,不是日期格式,本身不帶時區。

怎麼知道時間戳是秒還是毫秒?

數碼數。10 位數幾乎都是秒,13 位數幾乎都是毫秒;16 位數是微秒,19 位數是奈秒。

什麼是 2038 年問題?

帶號 32 位元整數在 2147483647 就用完了,也就是 2038-01-19T03:14:07Z。還用 32 位元存時間的系統,下一秒就會繞回 1901 年。

為什麼我的 Excel 日期會差一天?

Excel 的 1900 日期系統裡有一個從不存在的 1900-02-29。對 1900-03-01 之後的日期來說,序號等效於從 1899-12-30 起算,而不是 1900-01-01。

換時區會改變 Unix 時間戳嗎?

不會。時間戳在任何地方都是同一個瞬間,改變的只是它顯示成人類可讀格式的樣子,因為時區與日光節約規則是在那一步才套上去的。

Unix 時間戳可以是負數嗎?

可以。負數代表 1970-01-01 UTC 之前的瞬間。多數 API 與資料庫接受,但有些 schema 與舊函數庫不接受,依賴之前先確認。

為什麼我的時間戳少了閏秒?

Unix 時間刻意忽略閏秒,只計算非閏秒,所以時間戳維持簡單的整數,代價是在閏秒前後不會與原子時完全一致。

返回文章列表

相關文章

相關工具