2038 年问题(Y2K38)详解

2038 年 1 月 19 日 03:14:07 UTC,Unix 时间戳 2,147,483,647 —— 32 位有符号整数的最大值 —— 将会溢出。本文说明这意味着什么、哪些系统受影响,以及为什么大多数现代代码已经安全。

什么是 2038 年问题?

Unix 时间戳传统上以 32 位有符号整数存储,计数自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数。32 位有符号整数的最大值是 2,147,483,647,对应 2038 年 1 月 19 日 03:14:07 UTC。其后 1 秒,该整数溢出为 -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

哪些系统有风险?

该问题影响以 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,不受影响

2038 年问题常见问题

什么是 2038 年问题?
2038 年问题(Y2K38)是一个 bug:使用 32 位有符号整数存储 Unix 时间戳的系统会在 2038 年 1 月 19 日 03:14:07 UTC 溢出。32 位有符号整数的最大值是 2,147,483,647。1 秒后,该整数回绕到 -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 年溢出的确切时刻。
Y2K38 和 Y2K 一样吗?
不一样。Y2K(2000 年问题)是程序用两位数存储年份造成的。Y2K38 是把 Unix 时间戳存为 32 位有符号整数、在 2038 年溢出造成的。根因不同,受影响的系统也不同。
2038 年互联网会崩溃吗?
大概率不会有重大影响。大部分互联网基础设施已迁移到 64 位系统。风险集中在嵌入式系统、老旧 IoT 设备,以及数十年未更新的软件。现代操作系统、语言和数据库已经安全。