时间戳与时区:秒 / 毫秒、2038 问题与常见坑

「差 1000 倍」和「差 8 小时」是时间处理里最高频的两类 bug。

一、Unix 时间戳的定义

Unix 时间戳指从 1970-01-01 00:00:00 UTC 起到现在经过的秒数(或毫秒数)。它是一个绝对时刻,本身不带任何时区信息。

关键认知:时间戳不需要「加 8 小时」。所谓「加 8 小时」只在把时间戳格式化成人类可读字符串时才发生,是格式化阶段的事。

二、秒还是毫秒?先看位数

单位位数典型值出现场景
秒10 位1762000000Unix / PHP / Go / MySQL
毫秒13 位1762000000000Java / JavaScript / 大部分 API
微秒16 位1762000000000000Python time.time_ns()/1000、部分数据库

一个稳妥的自动判断:

function toMs(ts) {
  const s = String(ts);
  if (s.length <= 10) return ts * 1000;      // 秒
  if (s.length <= 13) return ts;             // 毫秒
  return Math.floor(ts / 1000);              // 微秒
}

把 10 位秒数直接丢给 new Date(),会得到 1970 年 1 月 20 日左右的时间——因为它被当成毫秒了。这是最经典的「时间显示成 1970」现象。

三、时区:时间戳是 UTC,展示是本地

同一个时间戳,在不同时区的人眼里是不同钟表时间,但指向同一时刻:

时区本地时间偏移
UTC2026-11-01 05:45+00:00
中国 (CST)2026-11-01 13:45+08:00
纽约 (EST)2026-11-01 00:45-05:00

所以设计接口时:存储与传输一律用时间戳或带偏移的 ISO 8601,只有展示层才做时区转换。不要在数据库里存「本地时间字符串」,否则服务器迁移到别的时区就全乱了。

四、new Date 解析字符串的坑

这是前端最容易踩的隐式时区转换:

new Date('2026-11-01')          // 按 UTC 零点解析(ES 规范)
new Date('2026-11-01T00:00')    // 按本地时区解析
new Date('2026/11/01')          // 按本地时区解析(非标准格式)

// 结果可能相差一整天

带 T 和不带 T 的行为不一致,是规范遗留问题。稳妥做法:自己解析并用 Date.UTC() 构造,或者明确带上 Z / 偏移量。

五、2038 年问题

32 位有符号整数能表示的最大值是 2147483647,对应 2038-01-19 03:14:07 UTC。用 32 位 time_t 存储秒数的系统,过了这一刻会溢出成 1901 年。

影响范围:老版本 32 位 Linux / 嵌入式设备 / 部分数据库的 INT 字段。规避方式很简单——时间字段用 64 位整数(BIGINT)或 DATETIME 类型,不要用 32 位 INT 存秒数。

六、ISO 8601 还是时间戳?

时间戳ISO 8601
可读性差好
体积10~13 字节20~25 字节
时区信息隐含 UTC可显式携带
适合存储、排序、计算日志、API 返回、人工排查

常见折中:数据库存时间戳(排序快、无歧义),接口返回时同时给 timestamp 和 iso 两个字段。

七、动手转换

🕒时间戳转换
秒 / 毫秒 / 日期互转
🌏时区转换
跨时区对照
📅日期差值计算
相差天数与倒计时
⏰Cron 生成器
定时表达式可视化