土薯工具 Toolshu.com 登录 用户注册

时间戳传错单位,日期就跑到 1970 年或者五万年后,是怎么回事?

作者:bhnw 于 2026-04-25 10:38 发布 5次浏览 收藏 (0)

日期显示 1970 年,或者年份跑到了五万多年后

这两个症状,来自同一个 bug。

你的代码里有个时间戳,传给了某个函数,结果要么输出 1970-01-20,要么输出某个几万年后的日期。翻来覆去找不到哪里写错了,但其实原因就一个字:位。

时间戳有两种精度——毫秒——差了 1000 倍。两者混用,日期就会错得离谱。


为什么 JavaScript 用毫秒,其他语言用秒?

Unix 标准定义时间戳是"从 1970-01-01 00:00:00 UTC 到现在经过的数",这是历史标准,Python、PHP、Go、数据库基本都遵循。

JavaScript 是个例外。Brendan Eich 在设计 JavaScript 的 Date 对象时,选择了毫秒作为单位,原因大概是当时认为毫秒精度对前端更合适。这个决定定下来就改不了了,现在整个前端生态都是毫秒。

所以现状是:

语言/系统 默认精度 典型位数
Unix / Linux 10位
Python time.time() 秒(浮点) 10位整数部分
PHP time() 10位
Go time.Now().Unix() 10位
MySQL UNIX_TIMESTAMP() 10位
JavaScript Date.now() 毫秒 13位
Java System.currentTimeMillis() 毫秒 13位

看位数是最快的判断方式:10位是秒,13位是毫秒


四种混用场景和对应修复

场景1:把秒传给了 JavaScript new Date()

// 后端返回了秒级时间戳
const ts = 1745000000;  // 10位,秒

// 错误写法:new Date() 期望毫秒
const date = new Date(ts);
console.log(date.toISOString());
// 输出: "1970-01-20T20:06:40.000Z"  ← 1970年!

// 正确写法:乘以1000
const date = new Date(ts * 1000);
console.log(date.toISOString());
// 输出: "2026-04-11T02:13:20.000Z"  ← 正确

场景2:把毫秒传给了 Python datetime.fromtimestamp()

from datetime import datetime

ts = 1745000000000  # 13位,毫秒(比如从前端拿到的)

# 错误写法
dt = datetime.fromtimestamp(ts)
# ValueError: year 55916 is out of range  ← 五万多年后!

# 正确写法:除以1000
dt = datetime.fromtimestamp(ts / 1000)
print(dt)  # 2026-04-11 02:13:20

场景3:前端发给后端的时间戳没转换

这是最容易被忽视的场景,因为前后端各自测试都正常,联调才出问题。

// 前端:用 Date.now() 获取当前时间戳
const payload = {
    created_at: Date.now()  // 1745000000000,毫秒
};

fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(payload)
});
# 后端 Python:以为收到的是秒
from datetime import datetime

data = request.json
ts = data['created_at']  # 实际是 1745000000000
dt = datetime.fromtimestamp(ts)  # 报错:year out of range

# 修复方案1:前端转成秒再发
# JavaScript: Math.floor(Date.now() / 1000)

# 修复方案2:后端判断位数自动处理
if ts > 1e10:  # 超过100亿,说明是毫秒
    ts = ts / 1000
dt = datetime.fromtimestamp(ts)

场景4:数据库存的是秒,Node.js 读出来当毫秒用

// 从数据库读出来的是秒
const row = await db.query('SELECT created_at FROM users WHERE id = 1');
const ts = row.created_at;  // 1745000000,秒

// 直接用,结果显示 1970 年
const date = new Date(ts);  // 错!

// 正确
const date = new Date(ts * 1000);

怎么判断手上的时间戳是秒还是毫秒?

方法1:看位数

2026年的秒级时间戳是10位(1700000000 到 1800000000之间),毫秒是13位。这个规律在2001年之后到2286年之前都成立。

方法2:写一个检测函数

function detectTimestamp(ts) {
    if (ts > 1e12) return 'milliseconds';
    if (ts > 1e9)  return 'seconds';
    return 'unknown';
}

// 统一转成毫秒给 Date 用
function toDate(ts) {
    return new Date(ts > 1e12 ? ts : ts * 1000);
}
def detect_timestamp(ts):
    if ts > 1e12:
        return 'milliseconds'
    if ts > 1e9:
        return 'seconds'
    return 'unknown'

def to_datetime(ts):
    from datetime import datetime, timezone
    if ts > 1e12:
        ts = ts / 1000
    return datetime.fromtimestamp(ts, tz=timezone.utc)

根治这个问题的两条原则

原则1:API 边界用 ISO 8601 字符串,不传裸时间戳

ISO 8601 格式是 2026-04-11T02:13:20Z,带时区、精度明确、人类可读,不存在秒/毫秒歧义。如果你能控制接口设计,优先用这种格式传时间。

// 前端发
const payload = {
    created_at: new Date().toISOString()  // "2026-04-11T02:13:20.123Z"
};

// 后端收
from datetime import datetime
dt = datetime.fromisoformat("2026-04-11T02:13:20.123Z".replace('Z', '+00:00'))

原则2:如果必须传时间戳,在接口文档里明确写单位

在接口文档里写清楚 created_at: Unix timestamp (seconds) 还是 (milliseconds),并且在入口处做校验:

def validate_timestamp(ts: int, unit: str = 'seconds'):
    if unit == 'seconds' and ts > 1e12:
        raise ValueError(f"期望秒级时间戳,收到了疑似毫秒的值: {ts}")
    if unit == 'milliseconds' and ts < 1e10:
        raise ValueError(f"期望毫秒级时间戳,收到了疑似秒的值: {ts}")

遇到不确定的时间戳,粘到 时间戳在线转换工具 里,自动识别秒还是毫秒,直接显示对应的日期,比自己心算省事。

发现周边 发现周边
评论区

加载中...