Unix 时间详解:从 1970 年到 2038 年

2026 年 11 月更新

像 1767225600 这样的 Unix 时间戳,表示从一个固定起点——Unix 纪元,即 UTC 时间 1970 年 1 月 1 日 00:00:00——开始经过的秒数。这个数字恰好对应 UTC 时间 2026 年 1 月 1 日零点。时间戳只是一个不带时区的数字,因此成了计算机存储和交换时间点最常用的方式。本文介绍如何读懂时间戳、如何在常用语言中正确转换,以及那些真正会引发 bug 的边界情况:毫秒、时区、闰秒和 2038 年问题。

Unix 时间是怎样计算的

Unix 时间每秒加 1,在同一时刻,地球上任何地方的值都相同。下面是几个参考点:

时间戳日期和时间(UTC)说明
01970-01-01 00:00:00纪元起点
864001970-01-02 00:00:00一天是 86,400 秒
-864001969-12-31 00:00:00负数表示 1970 年之前
10000000002001-09-09 01:46:40第一个 10 位数的时间戳
17672256002026-01-01 00:00:00下文示例使用
20000000002033-05-18 03:33:20
21474836472038-01-19 03:14:07有符号 32 位整数的最大值

在 Unix 时间里,每天都恰好是 86,400 秒,所以计算很简单:1,767,225,600 ÷ 86,400 = 20,454,也就是说 2026 年 1 月 1 日是纪元之后的第 20,454 天;给时间戳加上 7 × 86,400,就正好往后推一周。至于当地时间,那是另一回事,下面的“时区”部分会讲到。

秒、毫秒和更小的单位

传统的 Unix 约定以秒为单位,但很多系统使用更小的单位。通常看位数就能分辨:对于 2001 年到 2286 年之间的日期,以秒为单位的时间戳是 10 位数。

位数单位常见来源UTC 2026 年 1 月 1 日 0 点
10秒Unix 命令行、PHP、大多数数据库、JWT 的 exp 声明1767225600
13毫秒JavaScript 的 Date.now()、Java 的 System.currentTimeMillis()1767225600000
16微秒部分 API、日志和分析系统1767225600000000
19纳秒Go 的 UnixNano()、Python 的 time.time_ns()1767225600000000000

单位搞混是非常常见的 bug。把毫秒当成秒,会得到几万年后的日期;把秒当成毫秒,则会回到 1970 年 1 月。如果转换出来的日期是 1970 年,首先怀疑这一点。Unix 时间戳转换工具会根据位数自动识别单位。

时区与 UTC

时间戳本身没有时区,它只标记一个时刻;只有在显示时才需要考虑时区。时间戳 1767225600 在不同城市是:

  • 伦敦:2026 年 1 月 1 日 00:00(冬季为 UTC+0)
  • 北京、上海:2026 年 1 月 1 日 08:00(UTC+8)
  • 首尔、东京:2026 年 1 月 1 日 09:00(UTC+9)
  • 纽约:2025 年 12 月 31 日 19:00(冬季为 UTC−5)

由此可以总结出几条好习惯:

  • 以 UTC 存储和传输时间点,用 Unix 时间戳,或以 Z 或时区偏移结尾的 ISO 8601 字符串。
  • 只在“边缘”转换成当地时间:给人看日期,或从人那里读取日期的时候。
  • 使用 Asia/Shanghai、America/New_York 这样的 IANA 时区名称,而不是固定偏移或缩写。名称包含该地区的夏令时规则及其历史;而像 −05:00 这样的偏移,在纽约一年中只有部分时间是对的。中国目前不实行夏令时,全年都是 UTC+8,但处理欧美时间时,这个区别就很重要了。
  • 对于按当地时间安排的未来事件,比如“每周一柏林时间 9 点开会”,应保存当地时间和时区名称,而不是预先算好的时间戳。各国政府会调整夏令时规则,今天算出的时间戳将来可能差一个小时。

要比较不同城市的时间,可以用自动处理夏令时的时区转换工具;要计算两个日期之间相差多少天,或给日期加上若干天,可以用日期计算器。

在代码中转换时间戳

所有示例都使用 1767225600,即 2026-01-01 00:00:00 UTC。

JavaScript

任务代码
当前时间(秒)Math.floor(Date.now() / 1000)
时间戳 → ISO 字符串new Date(1767225600 * 1000).toISOString() 结果为 "2026-01-01T00:00:00.000Z"
按指定时区显示new Date(1767225600 * 1000).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })
日期字符串 → 时间戳Date.parse("2026-01-01T08:00:00+08:00") / 1000 结果为 1767225600

JavaScript 的日期以毫秒为单位,所以传入时要乘以 1,000,取出时要除以 1,000。另外,new Date("2026-01-01")(只有日期)会被当作 UTC 零点,而 new Date("2026-01-01T00:00")(有日期和时间但没有偏移)会被当作当地时间。在中国运行时,这两个值相差 8 小时。只要你指的是一个确定的时刻,就一定要加上 Z 或时区偏移。

Python

任务代码
当前时间(秒)time.time()(浮点数)
时间戳 → UTC datetimedatetime.fromtimestamp(1767225600, tz=timezone.utc)
时间戳 → 指定时区datetime.fromtimestamp(1767225600, tz=ZoneInfo("Asia/Shanghai"))
datetime → 时间戳datetime(2026, 1, 1, tzinfo=timezone.utc).timestamp() 结果为 1767225600.0

需要导入 from datetime import datetime, timezone 和 from zoneinfo import ZoneInfo(Python 3.9 及以上)。避免使用没有 tzinfo 的“naive”datetime:对它调用 .timestamp() 时,会假定它是计算机的本地时区;而返回 naive 值的 datetime.utcfromtimestamp() 从 Python 3.12 起已被弃用。在 Windows 上,系统没有自带 IANA 时区数据库,所以使用 zoneinfo 还需要安装 tzdata 包(pip install tzdata)。

SQL

数据库时间戳 → 日期日期 → 时间戳
PostgreSQLto_timestamp(1767225600)EXTRACT(EPOCH FROM TIMESTAMPTZ '2026-01-01 00:00:00+00')
MySQLFROM_UNIXTIME(1767225600)UNIX_TIMESTAMP('2026-01-01 00:00:00')
SQLitedatetime(1767225600, 'unixepoch')unixepoch('2026-01-01 00:00:00')

PostgreSQL 的 to_timestamp 返回 timestamptz,按会话的 TimeZone 设置显示。MySQL 的 FROM_UNIXTIME 和 UNIX_TIMESTAMP 都使用会话时区,所以同一条查询在两台服务器上可能得到不同结果;如果想用 UTC,先执行 SET time_zone = '+00:00'。SQLite 以 UTC 工作;unixepoch() 需要 SQLite 3.38 或更高版本,旧版本可以用 strftime('%s', '2026-01-01 00:00:00'),它返回的是文本。

命令行

任务Linux(GNU date)macOS(BSD date)
当前时间(秒)date +%sdate +%s
时间戳 → 日期(UTC)date -u -d @1767225600date -u -r 1767225600
日期 → 时间戳date -u -d '2026-01-01 00:00:00' +%sdate -u -j -f '%Y-%m-%d %H:%M:%S' '2026-01-01 00:00:00' +%s

闰秒

为了跟上地球略不规则的自转,UTC 偶尔会插入一个闰秒。自 1972 年以来共增加了 27 次,最近一次是在 UTC 时间 2016 年 12 月 31 日 23:59:60,也就是北京时间 2017 年 1 月 1 日 7:59:60。Unix 时间假装闰秒不存在:每天都恰好是 86,400 秒,所以在闰秒期间,Unix 时钟会重复或停住一秒;一些大型服务商则把多出来的这一秒分摊到好几个小时里慢慢消化(称为“闰秒平滑”)。对大多数软件来说,这是察觉不到的。在测量短时间间隔这类会受影响的场景中,请使用单调时钟,例如 JavaScript 的 performance.now() 或 Python 的 time.monotonic(),而不是挂钟时间。2022 年,国际计量大会通过决议,将在 2035 年前实际上取消闰秒。

2038 年问题

许多旧系统用有符号 32 位整数存储 Unix 时间,它的最大值是 2,147,483,647,即 UTC 时间 2038 年 1 月 19 日(星期二)03:14:07,北京时间同日 11:14:07。再过一秒,计数器就会溢出变成 −2,147,483,648,这样的系统会把它读成 UTC 时间 1901 年 12 月 13 日 20:45:52。

现代的 64 位操作系统和编程语言使用 64 位时间戳,大约 2,920 亿年内都不会溢出。风险在于仍使用 32 位的旧设备和嵌入式设备、文件格式和数据库字段。例如,MySQL 的 TIMESTAMP 列类型最大只能到 2038-01-19 03:14:07 UTC,而 DATETIME 没有这个限制。那些需要计算遥远未来日期的软件,比如 15 年期的贷款还款计划或有效期很长的证书,可能在 2038 年到来之前很久就会碰到这个上限。

ISO 8601:便于人类阅读的格式

如果时间既要给程序读,也要给人看,就使用 ISO 8601,或者它面向互联网的更严格版本 RFC 3339。各字段从大单位排到小单位:

  • 2026-01-01T00:00:00Z:UTC 时间,Z 表示偏移为零
  • 2026-01-01T08:00:00+08:00:同一时刻,用北京时间表示
  • 2026-01-01:不带时间的日历日期

由于字段从年排到秒,格式和偏移相同的 ISO 8601 字符串按纯文本排序就是时间顺序,非常适合用在文件名和日志里。而 01/02/2026 这样的写法在美国表示 1 月 2 日,在欧洲大部分地区却表示 2 月 1 日,最好避免。

相关工具

  • Unix 时间戳转换 – 在秒、毫秒、微秒、纳秒时间戳与日期之间互相转换。
  • 时区转换 – 比较不同城市的日期和时间,自动处理夏令时。
  • 日期计算器 – 计算两个日期相差的天数,或给日期加上天、周、月。