← 返回文章列表

Unix 时间戳入门:秒与毫秒、UTC 与时区的正确打开方式

时间避坑

时间戳是什么

Unix 时间戳 = 自 1970-01-01 00:00:00 UTC 起经过的秒数。它本身与时区无关:全球任何一个角落,同一时刻读到的值完全相同——这正是它适合存储与传输的原因。

秒、毫秒、微秒

  • 10 位 = 秒(Linux/数据库常用);
  • 13 位 = 毫秒(Java、JavaScript 常用);
  • 16 位 = 微秒(部分高精度系统)。

混用位数会得到 1970 年或五万年后的日期。看到离谱的转换结果,先数一下位数。

时区只影响显示

同一时间戳在 UTC+8 显示为 19:00,在 UTC 显示为 11:00——变的是显示,不是值。多数“时间对不上”的问题,本质是把本地时间当 UTC 存储(或反之)。

实用建议

  1. 数据库统一存 UTC 时间戳或 ISO 8601 字符串;
  2. 前端拿到后再按用户时区本地化显示;
  3. 留意 2038 问题:32 位秒级时间戳将溢出,新系统一律用 64 位。

各语言与数据库对照

环境获取当前时间戳位数
JavaScriptDate.now()13 位(毫秒)
Pythontime.time()10 位(秒,带小数)
JavaSystem.currentTimeMillis()13 位(毫秒)
Gotime.Now().Unix()10 位(秒)
MySQLUNIX_TIMESTAMP() / FROM_UNIXTIME(1700000000)秒
PostgreSQLEXTRACT(EPOCH FROM now())秒(带小数)

命令行验证

# Linux / macOS:时间戳 → 可读 UTC 时间
date -u -d @1700000000

# 反向:可读时间 → 时间戳
date -u -d '2023-11-14 22:13:20' +%s

# 按指定时区渲染
TZ=Asia/Shanghai date -d @1700000000

四个最容易踩的坑

  • 位数混用:把 10 位秒当毫秒读,会得到 1970-01-01;把 13 位毫秒当秒读,会得到五万年后的日期;
  • 把本地时间当 UTC 存:接口只传 "2026-09-23 10:00" 而不带时区,跨时区用户就会看到错误时刻;
  • 直接比较字符串:拿 ISO 字符串和时间戳比较、或比较带不同偏移量的时间,结果都不可靠;
  • 忽略夏令时:部分地区一年两次跳变,跨时区调度务必以 UTC 为基准。

存储与展示建议

后端统一用 UTC 时间戳或带 Z/偏移量的 ISO 8601 存储与传输;前端用 Intl.DateTimeFormat 按用户时区渲染。这样“同一时刻在不同地区显示不同本地时间”是正确行为,而不是 bug。

实战案例:三个时区相关的故障

  1. “日志里时间差 8 小时”:服务端按 UTC 记录、前端按本地显示,属正常换算,别误当成 bug。
  2. “存了 13 位却按 10 位解析”:单位不一致导致时间差 1000 倍,跨服务传参务必约定单位。
  3. “夏令时切换当天算错”:用 UTC 时间戳存储即天然规避;只有本地化格式串才会踩到时区规则。

常见问题(FAQ)

该存秒还是毫秒?存整数秒或毫秒皆可,关键是全链路统一;毫秒精度更高,前端 Date.now() 默认毫秒。时间戳能为负吗?能,表示 1970 年之前。怎么避免 2038 问题?用 64 位整数存储。展示给用户该用什么?按用户时区格式化,别直接丢一串时间戳。

动手试试:时间戳转换、Cron 表达式

跨系统传递时间的规范

  1. 传输统一用 UTC:接口、消息与日志中都使用带时区标识的标准格式,把本地化转换留给展示层。任何把本地时间字符串直接放进接口的做法,都会在跨时区协作中产生歧义。
  2. 存储区分类型:需要计算、比较与排序的时间以整数或带时区的类型保存;仅需展示的历史数据可存字符串,但应统一格式并标注时区。
  3. 避免隐式转换:很多语言在解析不带时区的字符串时会按本地时区解释,从而在不同机器上得到不同结果。解析时应显式指定时区,或只接受带偏移量的输入。
  4. 注意夏令时:夏令时切换当天存在跳过或重复的时间段,定时任务与时间计算都应基于绝对时间而非本地时间差。
  5. 精度要一致:秒与毫秒混用是本类问题的高发原因,接口约定应写明单位,并在校验中强制检查位数或取值范围。

时间相关缺陷通常不影响功能演示,却会在跨区域部署、跨年结算与定时任务中集中爆发,因此更值得在开发阶段就建立规则。