Unix 时间戳:秒级还是毫秒级——前后端各踩各的坑
时间戳长得都差不多——一串数字。但前端默认毫秒、后端 Go 默认秒、MySQL 默认秒、Java 默认毫秒——每次联调都得对一次格式。这篇按踩过的坑列:怎么判断精度、各语言怎么取、时区坑在哪。
⚠️
真实事故:订单 API 后端返回秒级时间戳 1716340000,前端直接 new Date(1716340000) 渲染。结果显示 1970-01-21。bug 出现在大版本上线两小时后才被用户发现——是因为 JS 把这个当成毫秒了。一个 * 1000 就能避免。
一分钟判断你拿到的是秒还是毫秒
看长度:
- 10 位(如
1716340000)→ 秒级 - 13 位(如
1716340000000)→ 毫秒级 - 16 / 19 位 → 微秒 / 纳秒(少见,但 Go
time.Now().UnixNano()会给)
不靠数位数?看年份:
// 一个数字 ts,是秒还是毫秒?
const year = new Date(ts).getFullYear();
// 如果是合理年份(2000-2100)→ 当前用的是正确精度
// 如果年份是 1970 → 你把秒当毫秒用了,乘 1000
// 如果年份很离谱(几万年后)→ 你把毫秒当秒用了,除 1000
各语言/平台默认精度对照表
| 语言/平台 | 默认 | 取当前时间戳 |
|---|---|---|
| JavaScript | 毫秒 | Date.now() |
| Java | 毫秒 | System.currentTimeMillis() |
| Python | 秒(float) | time.time() |
| Go | 秒 | time.Now().Unix() |
| PHP | 秒 | time() |
| Rust | 秒 | SystemTime::now().duration_since(UNIX_EPOCH) |
| MySQL | 秒 | UNIX_TIMESTAMP() |
| PostgreSQL | 秒 | EXTRACT(EPOCH FROM NOW()) |
| Redis | 秒 | TIME 命令 |
结论:JS 和 Java 是异类。其他大多数语言/数据库都秒。前后端联调最常见的坑就是 JS(前端)和 Go/Python(后端)对接。
各语言的取法
JavaScript
// 毫秒
Date.now(); // 1716340000000
new Date().getTime(); // 同上
// 秒(要除)
Math.floor(Date.now() / 1000);
// 高精度(性能埋点用,不是 epoch)
performance.now(); // 浏览器开页以来的毫秒
Python
import time
time.time() # 1716340000.123 (float, 秒)
int(time.time()) # 1716340000 (整数秒)
int(time.time() * 1000) # 1716340000123 (毫秒)
# 高精度计时器(非 epoch)
time.perf_counter() # 纳秒级,但起点不固定
Go
import "time"
time.Now().Unix() // 1716340000 (秒, int64)
time.Now().UnixMilli() // 1716340000123 (毫秒)
time.Now().UnixMicro() // 1716340000123456 (微秒)
time.Now().UnixNano() // 1716340000123456789 (纳秒)
Java
// 老 API
System.currentTimeMillis(); // 毫秒
// Java 8+
java.time.Instant.now().getEpochSecond(); // 秒
java.time.Instant.now().toEpochMilli(); // 毫秒
转日期 / 解析 ISO 8601
时间戳转可读时间,几个语言对比:
// JavaScript:传毫秒时间戳到 Date
new Date(1716340000000).toISOString();
// "2026-05-22T01:46:40.000Z"
new Date(1716340000000).toLocaleString();
// "2026/5/22 上午9:46:40" (本地时区)
from datetime import datetime, timezone
# 秒级时间戳
datetime.fromtimestamp(1716340000, tz=timezone.utc)
# datetime(2026, 5, 22, 1, 46, 40, tzinfo=timezone.utc)
datetime.fromtimestamp(1716340000).isoformat()
# '2026-05-22T09:46:40' (本地时区)
t := time.Unix(1716340000, 0)
t.Format(time.RFC3339) // "2026-05-22T09:46:40+08:00"
📌
跨时区场景永远传 UTC 时间戳(Unix epoch 本来就是 UTC)。展示时再按用户时区格式化。在数据库存"本地时间字符串"是日后所有时区相关 bug 的根源。
时区相关的坑
- Unix 时间戳本身没有时区——它表示 UTC 起点到现在的秒数,是全球同步的
- 问题出在解析和展示。同一个 timestamp,在中国服务器
localtime()显示 +08:00,在 AWS us-east-1 显示 -05:00 - 数据库里存时间戳就行,不要存
"2026-05-22 09:46:40"这种本地字符串——丢了时区信息 - JS Date 对象内部是 UTC 毫秒,
.getDate()/.getHours()返回的是本地时区。这两点搞清楚能省 80% 的时区 bug
2038 年问题
32 位有符号整数能表示的最大 Unix 时间戳是 2147483647,对应 2038-01-19 03:14:07 UTC。再加 1 秒,溢出变成 1901 年。
这是新版 Y2K 问题,但只影响:
- 遗留 C 代码用
time_t且是 32 位的 - 32 位嵌入式系统
- MySQL 早期版本的
TIMESTAMP类型(5.6.4 之前)
现代语言/系统基本都用 64 位时间戳,可以撑到几千亿年。不用慌。
JS 里 Date 类的怪行为
JS Date 有几个反直觉的设计:
// 月份是 0-based!
new Date(2026, 5, 22); // 实际是 6 月 22 日
new Date(2026, 0, 1); // 1 月 1 日(这种 API 设计也是没谁了)
// 但用 ISO 字符串就正常
new Date("2026-06-22"); // 6 月 22 日
// 解析格式不一致:YYYY-MM-DD 按 UTC,YYYY/MM/DD 按本地
new Date("2026-06-22").toISOString(); // "2026-06-22T00:00:00.000Z"
new Date("2026/06/22").toISOString(); // 比上面晚 8 小时(中国时区)
// 现代项目推荐用 date-fns 或 dayjs,原生 Date 坑太多
💡
需要在浏览器里互转时间戳和日期?粘到 时间戳转换工具,自动识别秒/毫秒,同时显示 UTC 和本地两个时区。