工具拆解

Unix 时间戳的坑:秒、毫秒和 2038 年

10 位和 13 位混用会差出五十年,同样的数字按不同单位解释能跑到公元五万年。把时间戳最容易踩的四个坑讲清楚。

翻日志的时候经常看到这种数字:1760947200。知道它是时间,但不知道是哪天, 得去换算一下才认得出。

时间戳的设计其实很聪明:用单个数字记录时间点,没有格式歧义、没有时区歧义, 比较大小、算差值都是纯算术。但正因为「太简单」,用错的方式也特别隐蔽。

它到底是什么

Unix 时间戳 = 从 1970 年 1 月 1 日 00:00:00 UTC 起,到现在经过的秒数。

为什么是 1970?因为 Unix 系统在 1971 年发布,设计者取了一个「足够近又足够整」的起点。 这个日期本身没什么特殊含义,纯粹是工程上的选择。

坑一:秒和毫秒差 1000 倍

同一个时刻,秒级是 1760947200(10 位),毫秒级是 1760947200000(13 位)。看起来只是多了三个零,但解释错了, 时间会跑到完全离谱的地方。

  • 把秒当毫秒:1760947200 会被理解成 1970 年 1 月 21 日—— 差了 55 年,而且落在 1970 年附近这个特征非常明显。
  • 把毫秒当秒:1760947200000 按秒解释,会得到公元 57772 年。 这个更夸张,但有些系统的处理方式是直接报错或者归零,反而不容易发现。

经验法则:10 位是秒,13 位是毫秒。位数介于中间(11、12 位)说明这个时间 在 2286 年到 33658 年之间——基本可以断定是数据错了。

为什么会有两种?因为不同语言的默认行为不一样。Java 的 System.currentTimeMillis() 和 JavaScript 的 Date.now() 返回毫秒;Python 的 time.time() 和绝大多数 C 接口返回秒。跨语言对接时,这一层几乎总要出错一次。

坑二:时间戳没有时区,显示才有

这一点最容易被误解。同一个时间戳 1760947200:

  • 在北京(UTC+8)显示为 2025-10-20 16:00
  • 在伦敦(UTC+0)显示为 2025-10-20 08:00

数字只有一个,显示出来的样子有好几个。所以排查「时间对不上」的问题时, 正确的顺序是:先确认两边显示的是哪个时区,再怀疑数据本身。

很多线上事故的根因就是这一条:服务器用 UTC,日志按 UTC 打印, 运营同学按北京时间去核对,怎么都对不上,最后发现差了整整 8 小时。

坑三:2038 年问题

如果时间戳用 32 位有符号整数存储,最大值是 2147483647, 对应 2038 年 1 月 19 日 03:14:07 UTC。再往后一秒,就会溢出成负数, 时间直接跳回 1901 年。

这和 Y2K(千年虫)是同一类问题:当年为了省空间用两位存年份,结果 2000 年变成 1900 年。 现在大部分 64 位系统已经改用 64 位时间戳(能表示到约 2920 亿年后), 但嵌入式设备、老旧数据库字段、某些协议的固定 4 字节字段里,这个隐患还在。

如果你在维护用 int 存时间戳的表,现在就该考虑迁到 bigint。 距离 2038 年还有十二年,听起来很远,但数据迁移、兼容性测试、灰度发布加起来, 不会比这更短。

坑四:闰秒

Unix 时间在定义上忽略闰秒:每一天都当作正好 86400 秒。 而 UTC 因为有闰秒,实际某一天可能是 86401 秒。

结果是:从 1972 年到现在,Unix 时间累计比 UTC 慢了约 27 秒。 日常应用完全感知不到,但做天文观测、卫星轨道、高频交易这类需要精确对齐的场景, 必须用 TAI(国际原子时)或者专门的时间同步方案,不能用 Unix 时间戳硬扛。

一个自查习惯

看到任何时间戳,先数位数:10 位秒、13 位毫秒。然后换算出来看一眼年份对不对—— 如果落在 1970 年或者几万年后,基本就是单位搞错了。

工具里我让它同时显示本地时间和 UTC,就是为了省掉「脑子换算时区」这一步。 排查问题的时候,两个都摆出来,对不上的地方会自己跳出来。

打开「时间戳转换」→ 更多文章