趁手iKit

Unix 时间戳和时区:开发者最容易踩的 6 个坑

时间戳算出来差 8 小时?日期跨日变量?夏令时切换日跨度异常?6 个真实生产事故 + 复现 + 解决方法。

发布于 2026-05-23

时间戳问题为什么这么多

JavaScript 的 Date、Python 的 datetime、Java 的 LocalDateTime、MySQL 的 DATETIME —— 每种语言 / 数据库对时间的抽象都不一样,而它们却经常需要互通。

下面是 6 个真实踩过的坑。

坑 1:UTC 和本地时间混用

症状:同一个 Unix 时间戳,前端显示 14:00,后端日志写的是 06:00,差 8 小时。

原因:服务器 OS 设的是 UTC,前端跑用户浏览器时区(GMT+8)。两边都对,只是没说清显示的是哪个时区

修法:

  • 存数据库永远用 UTC(MySQL TIMESTAMP 字段自动是 UTC)
  • API 返回 Unix 时间戳(秒或毫秒数字,无时区歧义)
  • 前端展示时才转用户本地时区:new Date(timestamp * 1000).toLocaleString()

调试:把疑似有问题的时间戳粘到 时间戳转换 查看,工具同时显示本地时区 + UTC,一眼看清。

坑 2:Unix 时间戳的 10 位 vs 13 位

症状:new Date(1779489600) 显示 1970 年,但同事说应该是 2026 年。

原因:JavaScript Date 接收毫秒时间戳(13 位),你给的是(10 位)。

new Date(1779489600)        // 1970-01-21 18:38(把秒当毫秒了)
new Date(1779489600 * 1000) // 2026-05-22 12:00 ✓

修法:

  • 看到 10 位时间戳,记得乘 1000
  • Java System.currentTimeMillis() 是毫秒,Python time.time() 是秒(浮点),Go time.Now().Unix() 是秒,各语言不一

自动识别:时间戳转换工具 会自动按长度判断秒 / 毫秒,不用记。

坑 3:跨日变量

症状:用户报「我 5 月 1 日下午 10 点下的单,系统显示是 5 月 2 日」。

原因:服务器跑 UTC,5 月 1 日 22:00 中国 = 5 月 1 日 14:00 UTC,没问题。但有人把 UTC 时间直接拿去 .format('YYYY-MM-DD'),没转用户时区就格式化,结果跨日。

修法:格式化"日期"时必须显式指定时区。dayjs / moment 都支持:

dayjs(timestamp).tz('Asia/Shanghai').format('YYYY-MM-DD')

调试:把这种边界时刻丢 跨时区计算 比对,横排看不同时区的日期到底是哪天。

坑 4:夏令时(DST)切换日

症状:报表统计「凌晨 2-3 点订单数」,某天数据是平时的 0 或 2 倍。

原因:夏令时切换日(美国春季):本地时间从 02:00 直接跳到 03:00,2 点 -3 点这段时间根本不存在。秋季反过来,02:00-03:00 重复出现 1 次,数据翻倍。

修法:

  • 报表按 UTC 小时统计,不按本地时间
  • 或显式声明「按 EST 时间统计,夏令时切换日为预期异常」

中国不行夏令时,但只要业务覆盖美国 / 欧洲就要处理。

坑 5:数据库时区设置不对

症状:SELECT NOW() 在 MySQL 里返回的时间和 OS 时间差了几小时。

原因:MySQL time_zone 设置和服务器 OS 不一致:

SHOW VARIABLES LIKE 'time_zone'; -- 看当前 MySQL 时区
SET time_zone = '+08:00'; -- 临时改成北京时间

修法:

  • 推荐 time_zone = 'UTC',避免任何隐式转换
  • TIMESTAMP 字段会自动按 session time_zone 转,DATETIME 字段不会(纯文本存)
  • 跨地区项目优先用 TIMESTAMP 字段

坑 6:cron 表达式时区

症状:跑了个 cron 0 9 * * *(每天 9 点跑),但实际触发是 17:00。

原因:cron 默认按系统时区触发,服务器是 UTC → 9:00 UTC = 17:00 北京。

修法:

  • linux 系统 cron:TZ=Asia/Shanghai 写在 crontab 顶部

  • node-cron / cronstrue:每个 schedule 单独传 timezone 参数

    cron.schedule('0 9 * * *', task, { timezone: 'Asia/Shanghai' })
    
  • 或者统统按 UTC 思考,要北京 9 点就写 0 1 * * *(UTC 1 点)

调试:用 Cron 表达式工具 输入表达式,显示「下一次触发时间」,看是不是预期。

速查表

现象 大概率原因 修法
时间差 8 小时 UTC vs 本地时区 显式声明时区 + Unix 时间戳传值
1970 年 秒 vs 毫秒 乘 / 除 1000
跨日不对 没转时区就格式化 tz('Asia/Shanghai') 后再 format
DST 切换日数据异常 夏令时跳 / 重 按 UTC 小时统计
NOW() 不对 数据库 time_zone SET time_zone = '+08:00' 或全 UTC
cron 触发不对 cron 时区 crontab 加 TZ=... 或 schedule 加 timezone

工具组合

排查时间问题时,以下 3 个工具配合使用:

  1. 时间戳转换 - 任意时间戳秒 / 毫秒互转,显示多时区
  2. 跨时区计算 - 横排多时区查看同一时刻,会议规划神器
  3. Cron 表达式 - 解析 cron + 显示下一次触发

把可疑数据 / 表达式分别丢进去对比,通常 5 分钟内能定位问题。

一条核心建议

永远不要在内部传"日期字符串",传 Unix 时间戳数字

  • 数字无歧义,绝对时刻
  • 字符串 2026-05-23 14:00 既可能是 UTC 也可能是本地时区,处理一不小心就错
  • 字符串只在最外层展示前转(知道用户是哪个时区时)

记住这一条,80% 时间问题不会发生。

同作者另一产品:棱镜简历 - AI 在线简历制作

← 所有博客 | 回首页