Unix 时间戳排错:秒、毫秒、UTC、时区与夏令时
时间戳 bug 很常见,因为 Unix 时间戳本身简单,但围绕它的系统并不简单。同一个数字可能代表秒,也可能代表毫秒。数据库可能存 UTC,前端却按本地时区显示。日志可能用 ISO 8601,API 却传 epoch time。到了夏令时附近,“本地午夜”也可能变得不稳定。
这篇指南给出一套稳定的排错流程。
第一步永远是确认单位
先问:这是秒还是毫秒?
传统 Unix 时间是从 1970-01-01 00:00:00 UTC 开始经过的秒数。JavaScript Date、Java System.currentTimeMillis() 和很多前端 API 使用毫秒。
常见长度:
- 10 位:秒,例如
1721347200 - 13 位:毫秒,例如
1721347200000
如果日期跑到 1970 年附近,通常是把秒传给了需要毫秒的函数。如果日期跑到很远的未来,通常是把毫秒又乘了一次 1000。
// 秒级时间戳转 JavaScript Date
new Date(1721347200 * 1000)
// 毫秒级时间戳转 JavaScript Date
new Date(1721347200000)
UTC 存储,本地展示
Unix 时间戳表示一个绝对时刻,本身不包含时区。时区只在展示为日历日期时出现。
同一个时间戳,在纽约、伦敦、上海、东京会显示成不同本地时间。这是正常现象。变的是展示规则,不是时间戳。
排查时同时比较两种输出:
const d = new Date(1721347200 * 1000)
console.log(d.toISOString()) // UTC
console.log(d.toString()) // 本地时间
如果 UTC 正确但 UI 看起来差一天,问题多半在本地时区展示,而不是时间戳本身。
日期型值不要随便转时间戳
2026-07-19 这种只有日期、没有时间的字符串很容易被误用。有些系统把它当作 UTC 零点,有些系统当作本地零点。转成时间戳后,不同时区用户可能看到前一天或后一天。
生日、账单日期、活动日期这类“日历概念”,最好存原始日期。如果你需要的是具体瞬间,再转成时间戳。
夏令时问题
夏令时会让本地时间不连续。某个本地小时可能不存在,也可能出现两次。如果任务调度设在本地 02:30,有些日期可能根本没有这个时间点。
可靠系统建议:
- 用 UTC 时间戳存储
- 需要时另存用户时区
- 只在展示层转换为本地时间
- 用户期待“本地日历的一天后”时,不要简单加
86400秒
数据库和 API 排查清单
当时间戳跨越数据库和 API 时,写下:
- 原始值
- 单位:秒还是毫秒
- UTC ISO 字符串
- 本地展示字符串
- 数据库字段类型
- API 合约
大多数问题会在这里暴露。BIGINT 可能存毫秒,timestamp with time zone 可能已经存了规范化后的时刻。API 文档可能写秒,前端代码却发送毫秒。
日志和监控
日志里经常混用格式:
2026-07-19T10:30:00Z
1721385000
1721385000000
Jul 19 18:30:00
比较事件顺序前,先把格式统一。建议以 UTC ISO 字符串作为排查基准,因为可读且不容易误解。
当两个系统的事件顺序对不上,检查:
- 两台机器时间是否同步
- 两边时间戳单位是否一致
- 一个字段是接收时间,另一个是否是事件发生时间
- 一个日志查看器是否显示本地时间,另一个是否显示 UTC
JavaScript 解析提醒
JavaScript 的日期解析比较宽松,但并不总是跨环境一致。优先使用明确的时间戳、带时区的 ISO 字符串,或由表单字段主动转换。
推荐:
new Date('2026-07-19T10:30:00Z')
new Date(1721385000000)
风险较高:
new Date('2026/07/19 10:30:00')
new Date('07/19/2026')
模糊格式在不同浏览器或运行环境中可能得到不同结果。
实用排错流程
用 Unix 时间戳转换工具 把原始值双向转换。如果工具显示的 UTC 时刻正确,检查你的 UI 展示逻辑。如果工具显示的时刻就不对,检查单位和来源系统。
如果时间戳在 JSON payload 里,先用 JSON 格式化工具 验证结构。如果时间戳在 URL 参数中,例如 ?expires=1721385000,可以用 URL 参数解析工具 单独提取再转换。