Javascript is required
踩坑避坑发布于 2026-07-28更新于 2026-08-08审校于 2026-08-087 分钟阅读

10 位与 13 位时间戳误用导致的“1970年”或“55000年”Bug 排查

在调用 new Date(timestamp) 时,前端经常碰到时间变为“1970-01-01”或后端接收到“55800年”的奇葩事故。造成这类 Bug 的根因在于混淆了秒 (10位) 与毫秒 (13位) 时间戳单位。本文针对这一问题给出单位校验、格式化与工程化防错规则。

Timestamp10-digit13-digitYear 1970 Bug

一、问题概述:秒与毫秒物理单位混淆的渲染灾难

Unix 时间戳定义为自 1970-01-01 00:00:00 UTC 起经过的时间总量。C++、Python、PHP、Go 默认返回秒级 (10 位) 时间戳(如 1700000000);而 JavaScript Date.now() 和 Java System.currentTimeMillis() 默认返回毫秒级 (13 位) 时间戳(如 1700000000000)。若将 10 位秒级时间戳直接传给 JS new Date(1700000000),得到的其实是 1970 年 1 月的第 1,700,000 秒!

二、最小复现:相同数值在不同单位下解析结果对比

下面的对比展示了将 10 位秒级时间戳与 13 位毫秒级时间戳传入 JavaScript Date 产生的巨大物理偏差:

/* 示例原始秒级时间戳: 1700000000 (代表 2023-11-14 22:13:20 UTC) */
const timestamp10 = 1700000000;

// 1. 错误用法:将 10 位时间戳直接传入 JS Date
const wrongDate = new Date(timestamp10);
console.log(wrongDate.toISOString()); // "1970-01-20T16:13:20.000Z" (错退回 1970 年!)

// 2. 正确用法:秒级乘以 1000 转换为毫秒
const correctDate = new Date(timestamp10 * 1000);
console.log(correctDate.toISOString()); // "2023-11-14T22:13:20.000Z" (正确!)

/* 反向错误:将 13 位毫秒时间戳传给期望秒级的后端 API,数值多乘 1000 会溢出到 55800 年! */

三、根因分析:运行时单位差异与位数启发式的局限

1. 运行时设计差异:Unix API 常用秒,JavaScript Date 使用毫秒,日志系统还可能使用微秒或纳秒。2. 位数只适合诊断:当前时期的秒与毫秒通常分别是 10 位和 13 位,但负数、历史日期、相对时长与远期日期都会破坏该规则。3. 单位属于数据契约:生产代码不应靠 1e11 猜测;Schema、字段名或显式参数必须携带单位。

四、推荐方案:显式时间戳单位与 BigInt 边界

1. API Schema 明确使用 secondsmillisecondsmicrosecondsnanoseconds。2. 微秒和纳秒通过字符串或 BigInt 进入 JavaScript,避免先被 Number 四舍五入。3. 归一化时保留负数时间戳,并校验结果是否落在 JavaScript Date 的有效范围内。

五、完整代码:显式单位的时间戳归一化纯函数

下面的 TypeScript 代码不再猜测位数。调用方必须传入单位;所有整数先转为 BigInt,再安全缩放到毫秒。

type TimestampUnit =
  | "seconds"
  | "milliseconds"
  | "microseconds"
  | "nanoseconds";

const MAX_DATE_MS = 8_640_000_000_000_000n;

function normalizeToMs(
  input: string | number | bigint,
  unit: TimestampUnit,
): number {
  if (typeof input === "number" && !Number.isSafeInteger(input)) {
    throw new RangeError("Number 输入必须是安全整数;高精度时间戳请传字符串或 BigInt");
  }
  if (typeof input === "string" && !/^-?\d+$/.test(input)) {
    throw new TypeError("时间戳字符串必须是十进制整数");
  }

  const raw = BigInt(input);
  const milliseconds = unit === "seconds" ? raw * 1_000n
    : unit === "milliseconds" ? raw
    : unit === "microseconds" ? raw / 1_000n
    : raw / 1_000_000n;

  if (milliseconds < -MAX_DATE_MS || milliseconds > MAX_DATE_MS) {
    throw new RangeError("时间戳超出 JavaScript Date 的有效范围");
  }
  return Number(milliseconds);
}

function createDate(input: string | number | bigint, unit: TimestampUnit): Date {
  return new Date(normalizeToMs(input, unit));
}

console.log(createDate(1_700_000_000, "seconds").toISOString());
console.log(createDate("1700000000000000000", "nanoseconds").toISOString());
console.log(createDate(-3600, "seconds").toISOString());

六、常见错误方案

盲目对所有输入做 String(ts).length === 10 校验(当处理小数值或包含浮点数时字符串长度判断不可靠);在没有检查数据源单位的情况下直接在前端到处 * 1000

七、边界条件:负数、截断策略与无效日期

1970 年以前的时间戳是负数,不能一律拒绝。微秒或纳秒转毫秒时,整数除法会朝零截断;如果业务要求向下取整或保留亚毫秒精度,应在契约中另行定义,不能靠 Date 承载。

八、如何验证时间戳解析正确性

为每一种单位建立相同时间点的对照向量,并覆盖 1969 年负值、纪元零点、微秒/纳秒字符串、非安全 Number 和 Date 上下界。测试应断言精确 ISO 输出与预期异常,而不是只检查年份处于宽泛区间。

九、FAQ

问:为什么 Python time.time() 返回浮点数?答:Python 默认返回秒为单位的浮点数(如 1700000000.123),小数部分代表毫秒和微秒,前端处理时应先乘以 1000 再用 Math.floor 取整。

问:JavaScript Date.now() 会有精度丢失吗?答:不会,Date.now() 返回标准 Safe Integer 毫秒整数;若需要微秒级高精度计时应使用 performance.now()

问:2038 年问题 (Year 2038 Problem) 对前端有影响吗?答:32 位有符号整型溢出会影响旧版 32 位 C/C++ 后端,而 JavaScript Number 采用 64 位 IEEE 754 双精度浮点数,可安全表示时间戳直至 275760 年。

十、总结

弄清时间戳的物理单位(秒 vs 毫秒)是防止 1970 年与 55800 年渲染事故的关键。在 API 边界建立统一的归一化函数,能彻底消除跨语言调用的时间单位偏置 Bug。

来源与延伸阅读

技术审校所依据的规范与权威参考资料。

相关文章

继续阅读

可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。

打开关联工具