时间戳就一个数,坑在两层
结论先说:时间戳本质就是"从 1970-01-01 00:00:00 UTC 起过了多少秒"这一个绝对数,出错几乎都栽在两层——秒和毫秒单位搞反、时区显示读法不同。一个 Android 开发者在 Stack Overflow 贴过崩溃日志:他存的当前时间值是 1436374427923,一转成日期系统告诉他那是公元 47486 年——比现在晚四万多年。问题朴素:他拿到的是毫秒(System.currentTimeMillis() 返回值),却当成秒处理。除以 1000,立刻变成 2015 年 7 月 8 日,一切正常(这条问答底下几百个程序员踩过同一个坑)。
那个本地转换器把数字只在这台设备上算、不联网,帮你一眼转对。关键有四条:
- 一个 Unix 时间戳,就是"从 1970-01-01 00:00:00 UTC 起过了多少秒"——全球统一一个数,本身不带时区。
- 10 位通常是秒、13 位通常是毫秒;把毫秒当秒用,日期会跑到几万年后;把秒当毫秒用,会退回 1970 年初。
- 时区不改变这个时间戳的值,只改变它显示成"几点几分"——同样的数字,北京和伦敦看着差 8 小时,其实指同一瞬间。
- 32 位系统里的时间戳会在 2038-01-19 03:14:07 UTC 溢出、回跳到 1901 年——老设备、老数据库得留神。
时间戳的"零"为什么是 1970
时间戳不是随便选的起点。Unix 在贝尔实验室诞生于 1969—1970 年前后,开发者需要一个"最近、好记、又省比特"的基准点,于是定下 1970 年 1 月 1 日零点 UTC 作为纪元(Epoch)。这个点后来被 POSIX 标准正式写死,全名叫"Seconds Since the Epoch"。权威定义见 The Open Group 的 POSIX 基础定义第 4.16 节。
几个里程碑帮你建立直觉:
0= 1970-01-01 00:00:00 UTC(纪元起点)1000000000= 2001-09-09 01:46:40 UTC(当年程序员还庆祝过"十亿秒")1700000000= 2023-11-14 22:13:20 UTC
关键点:这个数永远是 UTC,跟你在哪无关。你在北京看到的"几点",只是把同一个绝对瞬间套上了东八区的壳。
秒和毫秒,一眼看错就差五万年
这是最常见的线上 bug,没有之一。规则简单:
- 10 位:
1717333200量级,秒级(Python、PHP、MySQL 默认) - 13 位:
1717333200000量级,毫秒级(JavaScript 的Date.now()、Java 默认)
两个方向搞反,后果极端:
- 把毫秒值当秒喂给只认秒的系统 → 日期落到 1970 年附近(几秒过纪元)。
- 把秒值当毫秒喂给 JavaScript 的
new Date()→ 日期飞到几万年后,就像开头那位仁兄的 47486 年。
补充一句:除秒和毫秒,个别高频交易、性能监控场景还会用微秒(16 位)甚至纳秒(19 位)时间戳。但日常开发、日志、接口里 99% 遇到的是秒和毫秒这两种,本文也聚焦这两个——认准位数,够用了。
所以拿到一串数字,先数位数。前面 Stack Overflow 那个 Android 案例就是典型:值本身是毫秒,被当成秒,差出 1000 倍,时间直接错位数。还有个 Angular 解析案例:开发者拿到 1378028575(秒),直接喂给期望毫秒的日期过滤器,结果显示成 1970 年 1 月——把秒值乘 1000 才恢复正常。这类"前端后端单位没对上"的事故,在生产环境里非常普遍。
写接口、对日志时,最稳的做法是在文档里写清"这是秒还是毫秒",别让下游自己猜。一个好习惯是用字段名直接带单位,比如 created_at_ms 和 created_at_s,光看名字就不会混。
换个时区显示差 8 小时,是谁错了
经常有人问:"我在 A 站转出来是今天上午 9 点,B 站转出来是凌晨 1 点,差了 8 小时,哪个对?"——都对,只是时区不同。
时间戳 1713772800 是同一个瞬间。它落到:
- UTC:2024-04-22 08:00:00
- 纽约(夏令时 UTC−4):04:00:00
- 伦敦(夏令时 UTC+1):09:00:00
- 东京(UTC+9):17:00:00
没有任何一个是"错的"。所谓差 8 小时,通常是 A 站按设备本地时区显示、B 站按 UTC 显示。时间戳本身是绝对值,差异只在"怎么读给人看"。所以排查时区问题,记住一条:存和传都用 UTC(时间戳或带偏移的 ISO 8601),只在最后显示那一步转成本地时间。
怎么用那个本地转换器
粘个数字试一次就懂了。那个转换器网页直接打开就能用,不用注册,你粘进去的数字也只在本地算。就三步:
选方向。 顶部切"时间戳 → 日期"还是"日期 → 时间戳"。日常排查日志选前者,想把某个日期存成数字选后者。
填时间戳(或日期)。 转日期时直接粘那串数字进去——1700000000 这种。工具会自动判断:数值小于 1e12(也就是 10 位)按秒处理,否则按毫秒处理,不用你手动换算。底下同时给你"对应日期(本地)"和"毫秒级"两个值。转日期时,如果输入的是 2026-08-25 这种,它会按你设备的本地零点,给出秒级和毫秒级两个时间戳。
看结果、核对时区。 结果按你设备的本地时区显示。如果你怀疑差了 8 小时,那多半是别的工具显示的是 UTC——时间戳本身没错,只是读法不同。拿不准就把两个值并排比一下。
提醒一句:转换结果用的是你本机的时区,跨时区核对日志时,记得心里有数"它显示的是我这边的本地时间"。 另外把"日期 → 时间戳"时,工具按你本地的零点(比如北京 00:00)去算,不是 UTC 零点——这点跟有些只认 UTC 的网站不一样。所以同一天在东京和北京转出来的秒级值会差 9 个小时,属正常,选哪个口径看你后续要跟谁对。
2038 年那道坎,不是吓唬人
32 位有符号整数最大值是 2147483647。从纪元起数这么多秒,正好到 2038-01-19 03:14:07 UTC。再往后一秒,数值溢出翻成负数,系统会把它理解成 1901 年 12 月 13 日。这就是有名的 Year 2038 问题(Y2K38),跟千年虫类似,但根子在二进制位数不够。
哪些地方还危险?主要是不常升级的老家伙:嵌入式设备、物联网固件、老数据库的 32 位时间字段、某些老文件系统。现代 64 位系统早换了大整数,能撑到约 2920 亿年后,基本等于"永远不会溢出"。但如果你维护的是长跑设备、遗留系统,或者要在数据库里存几十年后的日期,这坑得现在就留心——别等 2037 年才想起来改。