后端 · 时间

Unix 时间戳:秒级还是毫秒级——前后端各踩各的坑

时间戳长得都差不多——一串数字。但前端默认毫秒、后端 Go 默认秒、MySQL 默认秒、Java 默认毫秒——每次联调都得对一次格式。这篇按踩过的坑列:怎么判断精度、各语言怎么取、时区坑在哪。

⚠️

真实事故:订单 API 后端返回秒级时间戳 1716340000,前端直接 new Date(1716340000) 渲染。结果显示 1970-01-21。bug 出现在大版本上线两小时后才被用户发现——是因为 JS 把这个当成毫秒了。一个 * 1000 就能避免。

一分钟判断你拿到的是秒还是毫秒

看长度:

不靠数位数?看年份:

// 一个数字 ts,是秒还是毫秒?
const year = new Date(ts).getFullYear();
// 如果是合理年份(2000-2100)→ 当前用的是正确精度
// 如果年份是 1970 → 你把秒当毫秒用了,乘 1000
// 如果年份很离谱(几万年后)→ 你把毫秒当秒用了,除 1000

各语言/平台默认精度对照表

语言/平台默认取当前时间戳
JavaScript毫秒Date.now()
Java毫秒System.currentTimeMillis()
Python秒(float)time.time()
Gotime.Now().Unix()
PHPtime()
RustSystemTime::now().duration_since(UNIX_EPOCH)
MySQLUNIX_TIMESTAMP()
PostgreSQLEXTRACT(EPOCH FROM NOW())
RedisTIME 命令

结论: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 的根源。

时区相关的坑

2038 年问题

32 位有符号整数能表示的最大 Unix 时间戳是 2147483647,对应 2038-01-19 03:14:07 UTC。再加 1 秒,溢出变成 1901 年。

这是新版 Y2K 问题,但只影响:

现代语言/系统基本都用 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 和本地两个时区。

在线时间戳转换

自动识别精度、双时区对照、一键复制当前时间戳

打开时间戳工具