米格速压
时间戳 时区 开发

时间戳是什么时区的?后端给前端时间数据的 3 个经典坑

前端显示的时间比实际差 8 小时、同一个时间戳两个人看到不同结果、毫秒和秒搞混导致日期跳到 1970 年 —— 时间戳处理的经典坑。讲清楚 Unix 时间戳的本质、时区怎么处理、前后端怎么约定。

米格速压
2026-05-158 分钟
分享

测试提了个 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 个坑,一个项目的时间处理应该这样约定:

  1. 存储:数据库存 UTC 时间戳(毫秒)或 UTC datetime
  2. 传输:后端 API 返回 Unix 时间戳(毫秒)或带时区的 ISO 8601 字符串
  3. 显示:前端按用户本地时区格式化显示
  4. 精度:统一用毫秒(跟 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,这些库帮你处理了所有边界情况。

调试时间问题的工具

遇到时间显示不对,快速排查:

  1. 把后端返回的时间戳粘到时间戳转换工具,看它解析出来是几点
  2. 对比这个时间和"应该显示的时间",差 8 小时就是时区问题,差几万年就是秒/毫秒问题
  3. 确认时间戳位数(10 位秒 / 13 位毫秒)

总结

时间戳的核心是它是无时区的绝对时间。 3 个经典坑:时区没转(差 8 小时)、秒毫秒混淆(跳 1970)、存本地时间(跨时区乱)。 记住:底层全用 UTC / 时间戳,显示时才转本地时区

站里的时间戳转换工具 支持秒 / 毫秒时间戳 ↔ 日期互转,自动识别位数 + 显示北京时间和 UTC,调试时间问题很顺手。 如果接口返回的时间藏在 JSON 里,先用JSON 格式化展开看。

常见疑问

Unix 时间戳到底是什么?
Unix 时间戳是"从 1970 年 1 月 1 日 00:00:00 UTC 到现在经过的秒数"。它是一个绝对的时间点,本身不带时区。比如 1700000000 这个时间戳,全世界任何地方解析出来都是同一个"瞬间",只是显示成各地本地时间时数字不同(北京显示比 UTC 晚 8 小时)。这是时间戳最大的优点:无歧义的绝对时间。
为什么前端显示的时间差 8 小时?
中国是 UTC+8 时区。如果后端返回的时间字符串是 UTC 时间(如 2026-05-15T03:00:00Z),前端没做时区转换直接显示,就比北京时间少 8 小时。解决:① 后端返回 Unix 时间戳(无时区歧义),前端按本地时区格式化;② 或者后端返回带时区标识的 ISO 字符串(带 Z 或 +08:00),前端用日期库正确解析。
时间戳是 10 位还是 13 位?
10 位是秒级(到秒),13 位是毫秒级(到毫秒)。JavaScript 的 Date.now() 返回 13 位毫秒,而很多后端语言(PHP / Python 的 time())返回 10 位秒。<strong>混用是经典 bug</strong>:把 10 位秒当 13 位毫秒解析,日期会跳到 1970 年;反过来日期会跳到几万年后。前后端必须约定清楚用哪种。
前后端时间应该怎么约定?
业界推荐:① 后端存储和传输统一用 UTC 时间戳(或带时区的 ISO 8601 字符串);② 前端负责按用户本地时区显示;③ 明确约定秒还是毫秒(推荐统一毫秒,跟 JS 一致)。最忌讳的是后端返回"2026-05-15 11:00:00"这种不带时区的字符串 —— 前端不知道这是 UTC 还是北京时间,只能猜。
跨时区的应用怎么处理时间?
原则:① 存储用 UTC(数据库存 UTC 时间戳);② 传输用 UTC 或带时区标识;③ 只在"显示给用户"的最后一刻转成用户本地时区。比如一个会议 UTC 03:00 开始,北京用户看到 11:00,纽约用户看到 23:00(前一天),但底层都是同一个时间戳。千万不要在数据库存本地时间,跨时区会乱。
为什么有的时间戳解析出来是 1970 年?
几乎都是"秒/毫秒搞混"。后端给了 10 位秒级时间戳(如 1700000000),前端当成毫秒直接 new Date(1700000000),JS 认为这是 1970 年起第 170 万毫秒,也就是 1970 年 1 月 20 日。解决:10 位时间戳乘以 1000 转成毫秒再给 JS,或者判断位数自动处理。
JavaScript 处理时间有什么坑?
① Date 月份从 0 开始(0 是一月,11 是十二月);② new Date("2026-05-15") 被当 UTC,new Date("2026/05/15") 被当本地时间,斜杠和横杠结果不同;③ Date.parse 对非标准格式行为不一致。建议用 dayjs / date-fns 等库统一处理,别裸用 Date,坑太多。

看完即用

时间戳转换

Unix 时间戳 ↔ 日期格式,实时显示当前秒/毫秒

立即免费使用
作者
米格速压

米格速压编辑组,专注于办公文件处理场景的教程编写。每周二、五更新。