Unix 时间戳怎么转换?10 位和 13 位的坑一次说清

2026-10-14约 8 分钟开发辅助涉及 5 个工具

时间戳只有一串数字,看不出单位;用错了单位,2026 年的日期会显示成 1970 年,或者变成五万年以后。位数、时区、精度这三件事搞清,绝大多数时间戳问题就不会再出现。

核心要点
  • 时间戳的位数定义了单位:10 位是秒、13 位是毫秒、16 位是微秒、19 位是纳秒,相邻两档相差 1000 倍
  • 时间戳本身永远是 UTC,不含时区信息;时区只在「把时间戳显示成人能读的日期」这一步参与计算
  • 把 10 位当毫秒解析会得到 1970-01-01 附近的日期,把 13 位当秒解析会得到几万年后的日期 —— 这两种错误最容易识别
  • 32 位有符号整数在 2147483647 处溢出,对应 2038-01-19 03:14:07 UTC,这是「2038 问题」
  • 时间戳统一用 UTC 存储、只在展示时转本地时区,可以避免跨时区与夏令时导致的偏一小时问题

#先看位数,单位就定了

时间戳是从 1970-01-01 00:00:00 UTC 起算的偏移量,位数决定了单位。 记住这张表,绝大多数「日期怎么不对」的问题当场就能定位:

位数单位典型来源可表示到
10 位秒PHP time()、Linux date +%s2286 年
13 位毫秒JavaScript Date.now()、Java System.currentTimeMillis()约 28 万年
16 位微秒Python time.time_ns() // 1000—
19 位纳秒Go time.Now().UnixNano()—

判断方法很直接:先数位数,再决定要不要除以 1000。 相邻两档永远差 1000 倍,所以单位错了 1000 倍,日期就会离谱地偏。

注意

这两种错误最好认,遇到时直接调单位即可:把 13 位字符串当秒解析 → 得到 1970-01-01 附近的日期;把 10 位当毫秒解析 → 得到 几万年以后的日期。如果日期落在 1970 年 1 月,几乎一定是「毫秒被当成了秒」。

#时区为什么和时间戳无关

时间戳描述的是一个绝对时刻,这个时刻不因为观察者在哪个时区而改变。 北京时间 2026-10-14 12:00 与 UTC 时间 2026-10-14 04:00 是同一个瞬间,时间戳完全相同。

时区只在最后一步参与计算 —— 把那个瞬间「翻译」成人能读的日期字符串:

时间戳(秒)UTC 显示北京时间(UTC+8)
01970-01-01 00:001970-01-01 08:00
17919504002026-10-14 04:002026-10-14 12:00

所以正确的做法是:数据库和接口里一律存 UTC 时间戳,只在渲染给用户看的时候按用户所在时区转换。 一旦把本地时间当字符串存进去,跨时区排查和夏令时切换时就会持续出错。

说明

中国不使用夏令时,所以国内业务很少遇到这一类的坑。但如果服务面向欧美用户,或者依赖境外服务器,夏令时切换当天会出现「时间偏移一小时」的现象,根源通常是代码里存了本地时间而不是时间戳。

#三种转换场景的写法

#场景一:把日期转成时间戳

不同语言和数据库的单位默认值不一样,这是团队协作里最容易踩的地方:

环境返回单位备注
JavaScript毫秒Date.now()、new Date().getTime() 都是 13 位
PHP秒time()、strtotime() 都是 10 位
Python秒(浮点)time.time() 带小数;//1000 可转毫秒
Go秒time.Now().Unix() 秒,UnixMilli() 毫秒
MySQL秒UNIX_TIMESTAMP();处理毫秒需 timestamp/1000

跨语言传递时在字段名里写明单位是最省事的做法:created_at_ms 比 created_at 让人放心得多,团队里不会再有人猜。

需要给记录分配唯一编号时,可以考虑时间有序的 ID 方案 —— UUID / GUID 生成 的 v7 版本就是按时间递增的,能同时满足唯一性和排序需求。

#场景二:把时间戳转成日期

用 Unix 时间戳 直接粘贴转换最方便,工具会按位数判断单位并允许手动切换。命令行里也能做:

Shell
# 秒 → 本地时间
date -d @1791950400

# 毫秒 → 秒 → 本地时间
date -d @$((1791950400000/1000))

#场景三:算出两个时间之间的间隔

注意用同一单位相减。时长通常用秒或毫秒表示,如果一边是秒一边是毫秒,算出的间隔会差 1000 倍:

JSON
{ "created_at_ms": 1791950400000, "ttl_sec": 3600 }

上面这个字段命名法能避免大部分误用:单位写在名字里,读代码的人不需要回头查文档。要核对迁移前后两批数据是否一致,用 文本差异对比 把两列时间戳并排看,比逐条比对快得多。如果要检查接口返回里的时间字段,先把它丢进 JSON 格式化校验 展开层级,字段名一眼可见。

#2038 问题:什么时候真的会遇到

32 位有符号整数能表示的最大值是 2147483647,对应 2038-01-19 03:14:07 UTC。 超过这个时刻,用 32 位存秒级时间戳的程序会溢出到负数,日期回到 1901 年。

现在还需要关心它吗?看环境:

环境是否受影响说明
64 位 Linux / macOS / Windows否64 位时间戳可用数十亿年
现代语言运行时(Go / Rust / Node / Java 8+)否内部用 64 位
嵌入式设备、旧 C 代码、32 位系统是仍在大量产线上运行
数据库列定义为 INT 而非 BIGINT是常见于十年前设计的表结构
技巧

新建表结构时把时间戳列定义为 BIGINT,并且用毫秒存。成本是每条记录多 4 字节,换来的是不必在 2038 年前做数据迁移。

#常见问题

#时间戳 0 是什么时间?

时间戳 0 对应 1970-01-01 00:00:00 UTC,也就是北京时间 1970-01-01 08:00。它常被当作「未设置」的默认值 —— 如果你在数据库里看到大量记录都是 1970 年,那通常是字段没写入而不是真的那个时间。负数表示 1970 年之前的时刻,是合法的。

#为什么前端拿到的时间和后端差 8 小时?

两种可能。一是后端返回了「本地时间字符串」而不是时间戳,前端又按 UTC 解析了一次;二是后端直接把本地时间当成时间戳算了。判断方法:把返回值除以 3600,如果差值正好是 8 小时,就是时区处理错了,不是时间戳本身的问题。

#毫秒时间戳可以截断成秒吗?

可以,但要清楚代价:截断会把精度降到秒,同一秒内创建的记录顺序就分不出来了。如果只是做「某天有多少条数据」这类统计,截断没问题;如果要靠时间戳排序,保留毫秒。

#时间戳能表示闰秒吗?

不能。Unix 时间戳的处理方式是把闰秒「抹平」——一天固定按 86400 秒计算,闰秒发生时系统时间会重复或跳过一个瞬间。所以时间戳与世界时(UTC)之间有极小的差异,对绝大多数业务没有影响,只有天文、卫星等需要精确到秒级的领域才会专门处理。

#怎么快速确认转换结果对不对?

用已知锚点自检:0 应该是 1970-01-01 00:00 UTC,1000000000 应该是 2001-09-09 01:46:40 UTC。如果这两个都对,说明工具和时区设置是正常的,再查具体数值即可。要按固定间隔批量生成时间点,可以配合 Cron 表达式生成 理解周期规律。

本文提到的工具

相关文章