测试提了个 bug:订单列表里的下单时间,比实际下单时间早了 8 小时。 我查了半天,后端返回的时间戳没错,数据库存的也对 —— 问题出在前端直接把后端的 UTC 时间字符串 当本地时间显示了。
时间和时区是开发里最容易踩坑的地方之一,几乎每个项目都会在这上面栽过跟头。 这篇讲清楚 Unix 时间戳的本质、时区怎么处理、前后端怎么约定,以及 3 个最经典的坑。
先理解:Unix 时间戳是绝对时间
Unix 时间戳是从 1970 年 1 月 1 日 00:00:00 UTC 到现在经过的秒数。
关键点:时间戳本身不带时区,它是一个绝对的"瞬间"。 比如时间戳 1700000000:
- 在伦敦(UTC+0)解析:2023-11-14 22:13:20
- 在北京(UTC+8)解析:2023-11-15 06:13:20
- 在纽约(UTC-5)解析:2023-11-14 17:13:20
这是同一个瞬间,只是各地显示成本地时间的数字不同。 这是时间戳的最大优点:无歧义。
坑 1:时间差 8 小时(时区没处理)
最常见的坑。后端返回的是 UTC 时间,前端没转时区直接显示, 中国用户看到的时间就少了 8 小时(因为中国是 UTC+8)。
怎么发生的
后端返回 2026-05-15T03:00:00Z(UTC 时间,Z 表示 UTC)。 前端如果只截取字符串显示成 "03:00",就错了 —— 北京时间应该是 11:00。
怎么解决
- 后端返回 Unix 时间戳(无时区歧义),前端用
new Date(时间戳)自动按本地时区显示 - 或后端返回带时区标识的 ISO 字符串(带 Z 或 +08:00),前端用日期库正确解析
- 绝对不要后端返回 "2026-05-15 11:00:00" 这种不带时区的字符串,前端没法判断这是哪个时区
坑 2:秒和毫秒搞混(日期跳到 1970)
时间戳有两种精度:
- 10 位:秒级(PHP / Python 的
time()返回这个) - 13 位:毫秒级(JavaScript 的
Date.now()返回这个)
怎么发生的
后端(PHP)返回 10 位秒级时间戳 1700000000,前端(JS)直接 new Date(1700000000) —— JS 把它当成毫秒,认为是 1970 年起第 170 万毫秒, 日期变成 1970 年 1 月 20 日。
怎么解决
- 前后端约定统一用毫秒(跟 JS 一致最省事)
- 如果后端给的是秒,前端乘以 1000 转毫秒:
new Date(秒级时间戳 * 1000) - 或者判断位数:10 位乘 1000,13 位直接用
坑 3:数据库存本地时间(跨时区全乱)
有些项目图省事,数据库直接存 "2026-05-15 11:00:00" 这种本地时间字符串。 单一时区时没问题,一旦业务跨时区立刻崩。
怎么发生的
数据库存的是北京时间字符串,服务器迁移到美国机房后, 新写入的数据用了美国时间,新老数据时区不一致,根本没法比较和排序。
怎么解决
- 数据库统一存 UTC 时间戳(或 UTC 的 datetime)
- 只在显示给用户的最后一刻转成用户本地时区
- 服务器时区设为 UTC,避免依赖机器本地时区
前后端时间约定的最佳实践
综合上面 3 个坑,一个项目的时间处理应该这样约定:
- 存储:数据库存 UTC 时间戳(毫秒)或 UTC datetime
- 传输:后端 API 返回 Unix 时间戳(毫秒)或带时区的 ISO 8601 字符串
- 显示:前端按用户本地时区格式化显示
- 精度:统一用毫秒(跟 JS 一致),或明确约定秒
核心原则:底层用绝对时间(UTC / 时间戳),只在最后显示时转本地时区。
JavaScript 处理时间的额外坑
如果用原生 Date,还有几个隐藏坑:
- 月份从 0 开始:
new Date(2026, 0, 1)是 1 月不是 0 月 - 横杠和斜杠不同:
new Date("2026-05-15")当 UTC,new Date("2026/05/15")当本地时间 - Date.parse 行为不一致:非标准格式各浏览器解析不同
建议用 dayjs / date-fns 等库,别裸用 Date,这些库帮你处理了所有边界情况。
调试时间问题的工具
遇到时间显示不对,快速排查:
- 把后端返回的时间戳粘到时间戳转换工具,看它解析出来是几点
- 对比这个时间和"应该显示的时间",差 8 小时就是时区问题,差几万年就是秒/毫秒问题
- 确认时间戳位数(10 位秒 / 13 位毫秒)
总结
时间戳的核心是它是无时区的绝对时间。 3 个经典坑:时区没转(差 8 小时)、秒毫秒混淆(跳 1970)、存本地时间(跨时区乱)。 记住:底层全用 UTC / 时间戳,显示时才转本地时区。
站里的时间戳转换工具 支持秒 / 毫秒时间戳 ↔ 日期互转,自动识别位数 + 显示北京时间和 UTC,调试时间问题很顺手。 如果接口返回的时间藏在 JSON 里,先用JSON 格式化展开看。