时间戳是什么
Unix 时间戳 = 自 1970-01-01 00:00:00 UTC 起经过的秒数。它本身与时区无关:全球任何一个角落,同一时刻读到的值完全相同——这正是它适合存储与传输的原因。
秒、毫秒、微秒
- 10 位 = 秒(Linux/数据库常用);
- 13 位 = 毫秒(Java、JavaScript 常用);
- 16 位 = 微秒(部分高精度系统)。
混用位数会得到 1970 年或五万年后的日期。看到离谱的转换结果,先数一下位数。
时区只影响显示
同一时间戳在 UTC+8 显示为 19:00,在 UTC 显示为 11:00——变的是显示,不是值。多数“时间对不上”的问题,本质是把本地时间当 UTC 存储(或反之)。
实用建议
- 数据库统一存 UTC 时间戳或 ISO 8601 字符串;
- 前端拿到后再按用户时区本地化显示;
- 留意 2038 问题:32 位秒级时间戳将溢出,新系统一律用 64 位。
各语言与数据库对照
| 环境 | 获取当前时间戳 | 位数 |
|---|---|---|
| JavaScript | Date.now() | 13 位(毫秒) |
| Python | time.time() | 10 位(秒,带小数) |
| Java | System.currentTimeMillis() | 13 位(毫秒) |
| Go | time.Now().Unix() | 10 位(秒) |
| MySQL | UNIX_TIMESTAMP() / FROM_UNIXTIME(1700000000) | 秒 |
| PostgreSQL | EXTRACT(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。
实战案例:三个时区相关的故障
- “日志里时间差 8 小时”:服务端按 UTC 记录、前端按本地显示,属正常换算,别误当成 bug。
- “存了 13 位却按 10 位解析”:单位不一致导致时间差 1000 倍,跨服务传参务必约定单位。
- “夏令时切换当天算错”:用 UTC 时间戳存储即天然规避;只有本地化格式串才会踩到时区规则。
常见问题(FAQ)
该存秒还是毫秒?存整数秒或毫秒皆可,关键是全链路统一;毫秒精度更高,前端 Date.now() 默认毫秒。时间戳能为负吗?能,表示 1970 年之前。怎么避免 2038 问题?用 64 位整数存储。展示给用户该用什么?按用户时区格式化,别直接丢一串时间戳。
跨系统传递时间的规范
- 传输统一用 UTC:接口、消息与日志中都使用带时区标识的标准格式,把本地化转换留给展示层。任何把本地时间字符串直接放进接口的做法,都会在跨时区协作中产生歧义。
- 存储区分类型:需要计算、比较与排序的时间以整数或带时区的类型保存;仅需展示的历史数据可存字符串,但应统一格式并标注时区。
- 避免隐式转换:很多语言在解析不带时区的字符串时会按本地时区解释,从而在不同机器上得到不同结果。解析时应显式指定时区,或只接受带偏移量的输入。
- 注意夏令时:夏令时切换当天存在跳过或重复的时间段,定时任务与时间计算都应基于绝对时间而非本地时间差。
- 精度要一致:秒与毫秒混用是本类问题的高发原因,接口约定应写明单位,并在校验中强制检查位数或取值范围。
时间相关缺陷通常不影响功能演示,却会在跨区域部署、跨年结算与定时任务中集中爆发,因此更值得在开发阶段就建立规则。