2038 年問題(Y2K38)詳解
2038 年 1 月 19 日 03:14:07 UTC,Unix 時間戳 2,147,483,647 —— 有號 32 位元整數的最大值 —— 將溢位並回繞到 1901 年的日期。本文講解這意味著什麼、哪些系統受影響、64 位元 time_t 修正如何運作,以及如何測試你自己的程式碼。
一行說清溢位
有號 32 位元整數最大到 2,147,483,647。存為 Unix 秒時,該值是 2038-01-19 03:14:07 UTC——一秒後回繞到 -2,147,483,648,被讀作 1901 年 12 月 13 日。任何仍把時間儲存在有號 32 位元整數裡的系統,都會在那一刻跳回 137 年前。修正就一個詞:64 位元。下表顯示哪些儲存類型受影響、哪些已經安全。
| 儲存類型 | 溢位於 / 最大日期 | 2038 年後安全? |
|---|---|---|
| 有號 32 位元 time_t / SQL INT | 2038-01-19 03:14:07 UTC | 否 |
| MySQL TIMESTAMP | 2038-01-19 03:14:07 UTC | 否 —— 用 DATETIME |
| 無號 32 位元 | 2106-02-07 06:28:15 UTC | 多爭取 68 年,非解決辦法 |
| 有號 64 位元 time_t / SQL BIGINT | 約 2920 億年 | 是 |
| 64 位元浮點(JavaScript、Python) | 西元 275,760 年 | 是 |
| MySQL DATETIME(打包欄位) | 西元 9999 年 | 是 |
什麼是 2038 年問題?
Unix 時間戳傳統上存為一個有號 32 位元整數,計自 1970 年 1 月 1 日 00:00:00 UTC 起的秒數。有號 32 位元整數的最大值是 2,147,483,647——對應 2038 年 1 月 19 日 03:14:07 UTC。那一刻之後一秒,整數溢位到 -2,147,483,648。作為 Unix 時間戳解讀時,這個負值對應 1901 年 12 月 13 日——把受影響的系統送回近 137 年前。
- 2^31 − 1 = 2,147,483,647(有號 32 位元整數最大值)
- 作為日期的 2,147,483,647 = 2038 年 1 月 19 日 03:14:07 UTC
- 2,147,483,647 + 1 溢位到 −2,147,483,648(最小的 32 位元值)
- 作為日期的 −2,147,483,648 = 1901 年 12 月 13 日 20:45:52 UTC
- 溢位的當地時間各不相同:1 月 18 日 22:14:07 EST,1 月 19 日 14:14:07 JST
哪些系統有風險?
該問題影響把時間戳存為有號 32 位元整數的系統:
- 使用 32 位元 time_t 的嵌入式系統和微控制器
- 為 32 位元目標編譯、用 time_t 或 int 存時間戳的老舊 C/C++ 程式碼
- 仍在生產中的 32 位元硬體上的老舊 Linux 核心(5.6 之前)
- MySQL TIMESTAMP 欄位——限制到 2038-01-19;改用 DATETIME
- 一些把時間戳編碼為 32 位元值的網路協定和檔案格式
- 壽命長達數十年的工業控制系統和 IoT 裝置中的韌體
哪些系統是安全的?
大多數現代程式碼和基礎設施已不受影響:
- 任何 64 位元作業系統——Linux(glibc 2.34+ 也修正 32 位元硬體)、macOS、Windows
- Python——時間戳使用 64 位元浮點,遠超西元 2.92 億年仍安全
- JavaScript——Date 使用 64 位元 IEEE 754 浮點,安全到西元 275,760 年
- Go 和 Rust——內部用 int64 表示時間,安全數十億年
- Java——java.time.Instant 用 long(64 位元),安全到約西元 2.92 億年
- PostgreSQL TIMESTAMP——正確儲存 2038 年以後的日期
- MySQL DATETIME——範圍 1000-01-01 到 9999-12-31,不受影響
MySQL TIMESTAMP 與 2038 年上限
MySQL 有兩種主要的日期時間欄位類型,在 2038 年前後表現迥異:
- TIMESTAMP 在內部存為有號 32 位元 Unix 整數——於 2038-01-19 03:14:07 UTC 溢位
- DATETIME 存為打包的 年/月/日 時/分/秒 欄位,支援到西元 9999 年——任何可能晚於 2038 的日期都用它
- TIMESTAMP 還會隱式在伺服器工作階段時區之間轉換;DATETIME 按原樣儲存該值
- 對於原始 epoch 欄位,存 BIGINT 而非 INT,使該值本身不會溢位
- 稽核現有結構:SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE DATA_TYPE = "timestamp"
嵌入式 Linux 與 32 位元 time_t
剩下最大的 Y2038 風險在於長壽命的嵌入式系統:路由器、工業控制器、汽車 ECU、智慧電表,以及以 32 位元核心出廠、可能在 2038 年後仍服役的 IoT 裝置。C 函式庫和任何自訂二進位協定都必須是 64 位元乾淨的。
- glibc 透過建置巨集 -D_TIME_BITS=64 為 32 位元 ABI 取得 64 位元 time_t(glibc 2.34+)
- musl libc 自 1.2.0 版(2020)起預設 64 位元 time_t
- Debian、Ubuntu 和 OpenWrt 都已把 32 位元移植遷移到 64 位元 time_t
- 檔案系統時間戳是另一個問題:ext2/ext3 用 32 位元時間;ext4 在 ≥ 256 位元組的 inode 上支援 64 位元
- 對 2026 年後仍出貨的硬體,確認 C 函式庫與任何線路格式都是 64 位元
如何修正並測試 Y2038
修正始終相同——把時間拓寬到 64 位元——但必須在每一個儲存或傳輸時間戳的層級套用:C 函式庫、資料庫欄位,以及任何二進位協定或檔案格式。最快找出問題的辦法,是在測試環境把時鐘撥過該邊界,盯住落到 1901 年的日期。
- 在 glibc 2.34+ 上用 -D_TIME_BITS=64(以及 -D_FILE_OFFSET_BITS=64)編譯 32 位元程式碼
- 對任何儲存原始 Unix 秒或毫秒的欄位,使用 BIGINT 而非 INT
- 對可能超過 2038 的日期,優先用 DATETIME 或 TIMESTAMPTZ,而非 MySQL TIMESTAMP
- 測試時,把系統或容器時鐘設為 2038-01-19T03:14:08Z,並斷言日期仍在 2038 年
- 檢查線路格式和檔案標頭——如果協定欄位是 32 位元,64 位元作業系統也救不了
相關翻轉:Y2K、GPS、NTP 與 2106
2038 年是一族定寬翻轉中的一個。每一個都源於把時鐘塞進太少的位元裡,知道這些日期有助於在下一個上線前發現它:
| 翻轉 | 日期 | 原因 |
|---|---|---|
| Y2K | 2000-01-01 | 兩位數年份儲存 |
| GPS 週翻轉 | 2019-04-06 | 10 位元 GPS 週計數器 |
| NTP 紀元翻轉 | 2036-02-07 | 自 1900 年起的無號 32 位元秒 |
| 2038 年(Y2K38) | 2038-01-19 | 自 1970 年起的有號 32 位元秒 |
| 2106 年 | 2106-02-07 | 自 1970 年起的無號 32 位元秒 |
相關參考
FAQ
- 什麼是 2038 年問題?
- 2038 年問題(Y2K38)是一個 bug:用有號 32 位元整數儲存 Unix 時間戳的系統將在 2038 年 1 月 19 日 03:14:07 UTC 溢位。有號 32 位元整數的最大值是 2,147,483,647。一秒後,整數回繞到 -2,147,483,648,對應 1901 年 12 月 13 日。
- 2038 年 1 月 19 日的 Unix 時間戳是多少?
- 2038 年 1 月 19 日 03:14:07 UTC 的 Unix 時間戳是 2,147,483,647 —— 有號 32 位元整數的最大值(2^31 - 1)。這是 32 位元系統上發生 2038 年溢位的確切時刻。
- 如何修正 2038 年問題?
- 用有號 64 位元整數而非 32 位元來儲存時間。使用 64 位元 time_t(在 glibc 2.34+ 上用 -D_TIME_BITS=64 編譯 32 位元程式碼),epoch 欄位用 BIGINT 而非 INT,用 DATETIME 而非 MySQL TIMESTAMP。有號 64 位元時間值約 2920 億年都不會溢位。
- Y2K38 和 Y2K 一樣嗎?
- 不。Y2K(2000 年問題)源於程式把年份存成兩位數。Y2K38 源於把 Unix 時間戳存為有號 32 位元整數,在 2038 年溢位。成因不同,受影響的系統也不同。
- 什麼是 2106 年問題?
- 2106 年是無號 32 位元時間戳的同一種溢位,於 2106 年 2 月 7 日 06:28:15 UTC 耗盡。從有號改為無號 32 位元多爭取 68 年,但不是真正的解決辦法;64 位元時間才是。
- 2038 年網際網路會崩潰嗎?
- 大機率不會有重大崩潰。大多數網際網路基礎設施已遷移到 64 位元系統。風險集中在嵌入式系統、老舊 IoT 裝置,以及數十年未更新的軟體。現代作業系統、程式語言和資料庫都已經安全。