直接给结论:Unix 时间戳数的是从 1970 年 1 月 1 日 00:00:00 UTC 起经过的秒数, 它本身不带任何时区。十位数字是秒,十三位是同一个时刻的毫秒表示。如果同一个事件的两个时间戳正好差一个你的 UTC 偏移量,那其中必有一个是用本地时间写下来的。
几乎所有让人困惑的时间戳问题,都逃不出三种原因:单位错了、时区丢了,或者时钟从一开始就不准。
Unix 时间戳到底数的是什么
一个 Unix 时间戳,是从 1970 年 1 月 1 日 00:00:00 UTC 起经过的秒数,不计闰秒。 它标记的那个时刻毫无歧义,因为它完全不带时区 —— 它是一个计数,不是一个日期。
整个设计就是这样。一个秒数在东京、拉各斯和圣保罗是同一个数字,这正是你在机器可读的「某事发生在何时」记录里 想要的性质。歧义只在有人把这个数转成人能读的东西、并且必须决定用哪个钟来显示的时候才出现。
0 → 1970-01-01T00:00:00Z
1735689600 → 2025-01-01T00:00:00Z
-86400 → 1969-12-31T00:00:00Z (负值是合法的)
单位问题,最常见的那个
现代系统越来越多地记录毫秒。于是同一个时刻有了两种完全不同的表示:
| 那个时刻 | 秒 | 毫秒 |
|---|---|---|
| 2025-01-01 00:00:00 UTC | 1735689600 | 1735689600000 |
| 现在(2026 年,约) | 1.7 × 10⁹ | 1.7 × 10¹² |
知道了之后,失败的样子很好认。把一个毫秒值当秒读,你会落到公元 56000 年附近; 把一个秒值当毫秒读,你会落到 1970 年 1 月。两种都会给出一个自信、具体、但完全错误的日期。
数值过大也是 JavaScript 的 Date 构造函数出毛病的根源。它接受的是毫秒,
所以把一个以秒为单位的时间戳传进去,它会静悄悄地给你一个 1970 年的日期,而不是报错。
怎么不靠猜就分辨出来
不要去写死一个年份阈值 —— 那种做法会失效。可靠的判断基于数量级,它成立的原因是: 对任何合理的日期来说,这两个区间都不重叠。
- 小于大约 10¹¹ 的是秒。从 1970 起 10¹¹ 秒是公元 5138 年。
- 等于或大于 10¹¹ 的是毫秒。从 1970 起 10¹¹ 毫秒是 1973 年。
所以 1735689600 是秒,1735689600000 是毫秒,同一条规则就能读对两者。
这里的转换工具用的正是这个判断,并且会告诉你它认定你给的是哪一种 ——
猜是被显示出来的,而不是静默进行的。
时区问题,更安静的那个
时间戳本身没有时区,但它的表示形式有,而这个表示形式经常缺东西。
看 2026-03-01 09:00:00。没有偏移量,这就不算一个时刻 ——
它只是一个墙上钟表的读数,你在不同地方站,它对应九个不同的时刻。
写完整的话,它要么是 2026-03-01T09:00:00+08:00,要么是 2026-03-01T01:00:00Z,
而这两个是同一个时刻。
两条做法能挡掉几乎所有由此产生的混乱:
- 存储和传输一律用 UTC。永远如此。只在显示的那一刻才转成本地时间。
- 用文本写日期时永远带上偏移量。ISO 8601 里结尾那个
Z表示 UTC,成本只有一个字符。
本地时间是一个「展示层」的事。一旦被用作存储格式,你就引入了一个含义取决于读者所在位置的值, 而它迟早会在别的地方被读。
夏令时,那个特殊情况
在会调钟的时区里,每年都会发生两件会让天真日期算术崩掉的事:
- 有一小时的本地时间不存在 —— 拨快时钟时它被跳过了。
- 有一小时的本地时间出现两次 —— 拨回时钟时它重复了。
所以「加 24 小时」和「加一天」不是同一个操作。给一个时刻加 86,400 秒定义明确; 加一个日历日跨越夏令时边界时含义不同。如果你在做排期,用懂时区数据库的库, 而不是自己对钟做算术。如果你只是在记录某事发生在何时,存 UTC,问题就消失了。
第三种问题:时钟本来就是错的
时间戳记录的是那台机器的时钟当时认为的时间。如果某台服务器漂了, 或者某个容器启动时宿主时钟没设好,那记录下来的时间就是「忠实地错了」—— 再怎么转换也修不回来。
日志里的几个典型信号:
- 事件顺序颠倒,相对依赖关系说不通,日志里效果出现在原因之前。
- 时间戳扎堆在某一个整数上,通常意味着时钟是被一次性校正的,而不是渐渐调整的。
- 随着时间推移规律地偏移越来越大,指向一台从未与时间源同步过的机器。
系统应该从网络时间源同步;日志也应该区分「事件发生的时刻」和「它被写下来的时刻」—— 后者往往在前者拿不到的时候还拿得到。
闰秒,一句话说完
Unix 时间戳忽略它。地球自转并不完全规律,UTC 会偶尔插入一秒来补偿; Unix 时间有意不这么做,所以它会比严格的 UTC 时钟略慢一点。对几乎所有软件来说这都无关紧要, 它只对天文计算和原子时标准有意义,几乎不涉及别的场合。
遇到任何「像时间戳的东西」时的简短流程
- 转换之前先确定单位。用数量级判断,再检查得出的年份是否说得通。
- 确定有没有偏移量。如果没有,就从上下文推断原本想表达哪个时区,并把它写下来, 免得下一个读的人还得再猜一遍。
- 如果这些值来自日志,就用本地工具转换。日志行里通常同时带着主机名、内网 IP、会话标识和用户 ID, 所以最好不要把它们发到任何地方去。
- 跨机器比较时只用 UTC。
Unix 时间戳转换工具处理前面两步,把本地时间、ISO 8601 和 UTC 并排列出来, 所以你在读的是哪一种毫无歧义。它就在页面里运行,因此没有任何一行日志需要离开它本来就在的那台机器。