毫秒 vs 秒:让每个应用都崩溃的单位混淆
最常见的时间戳 bug 是在期望秒的地方传入毫秒,或反过来。学习 10 位与 13 位法则、各语言默认值、对数据库的影响,以及安全的转换模式。
为什么有两个标准
Unix 时间最初以秒定义——对一个每个时钟滴答递增一次的系统来说,整数很自然。JavaScript 的 Date 对象于 1995 年引入,选择毫秒以支持浏览器中亚秒级的事件计时。许多数据库和后端语言保留 Unix 秒为默认。如今两种标准在每段跨越 JavaScript 与服务器边界的代码中并存,这就是为什么一个值可能看起来有效却表示数万年之后的日期。
每种语言使用的单位
记住这种分裂最稳妥的方法是:浏览器 Date API 通常要毫秒,Unix 风格的服务器 API 通常暴露秒,而较新的时间库往往两者都提供。阅读第三方 API 文档时,不要仅凭语言推断单位;查看字段说明和示例。
- Milliseconds: JavaScript Date.now(), Java System.currentTimeMillis(), Java Instant.toEpochMilli(), .NET ToUnixTimeMilliseconds()
- Seconds: Python time.time() (float), PHP time(), Go time.Now().Unix(), Ruby Time.now.to_i, C time(NULL), PostgreSQL EXTRACT(EPOCH)
- Both: Rust — duration.as_secs() for seconds, duration.as_millis() for milliseconds
- Python note: time.time() returns a float, so milliseconds are available as int(time.time() * 1000)
从值自动识别单位
对现代日期可靠的经验法则:10 位数字是秒,13 位数字是毫秒。当前 Unix 秒约为 17–21 亿(10 位),直到公元 33658 年才会达到 13 位。当前 Unix 毫秒已是 13 位。该法则在从 2000 年代到未来数千年间最为可靠;对历史日期、负日期或紧凑的测试夹具,请使用显式单位而非猜测。
- 10 位的值 → 秒(如 1700000000 = 2023-11-14 UTC)
- 13 位的值 → 毫秒(如 1700000000000 = 2023-11-14 UTC)
- 16 位的值 → 微秒(除以 1,000,000 得到秒)
- 负值 → 1970 年 1 月 1 日之前的日期(秒或毫秒)
安全的转换模式
转换本身是简单的算术;难点在于选择在哪里进行转换。在系统边界处转换,为转换后的值命名,避免把含糊的原始数字穿过多层传递。
- Seconds to milliseconds: seconds * 1000
- Milliseconds to whole seconds — JavaScript: Math.floor(ms / 1000)
- Milliseconds to whole seconds — Python: ms // 1000
- Milliseconds to whole seconds — Go: ms / 1000 (integer division)
- Universal guard in JavaScript: const toMs = ts => ts < 1e11 ? ts * 1000 : ts
单位 bug 在生产中如何显现
秒对毫秒的 bug 常能通过校验,因为两个值都只是数字。它通常稍后表现为不可能的日期:把秒当作毫秒时 JavaScript 显示 1970 年 1 月,后端把毫秒当作秒时显示遥远的未来年份。
- JavaScript UI 中出现 1970 年日期 → 把秒传给 new Date() 而未乘以 1000
- Python、Go 或 PHP 中出现 50000 年以上 → 把毫秒传给了期望秒的 API
- 永不过期的过期令牌 → 过期时间戳以错误单位存储
- 立即消失的缓存项 → 毫秒被除了两次或秒被乘了两次
- 范围为空的分析图表 → 查询边界使用了与已存事件时间戳不同的单位
API 与数据库的命名约定
一个很小的命名约定就能避免大多数此类 bug。除非文档异常清晰,否则永远不要发布名为 timestamp 的 API 字段。优先使用同时包含含义和单位的字段名。
- createdAtMs — Unix milliseconds, best for JavaScript clients
- createdAtSeconds — Unix seconds, common for backend services
- createdAtIso — ISO 8601 string, useful for human-readable API responses
- expiresAtUnixSeconds — explicit enough for auth tokens and signed URLs
- event_time TIMESTAMPTZ — native database time, with conversion handled by the database
毫秒 vs 秒常见问题
- 13 位时间戳总是毫秒吗?
- 对现代真实的 Unix 时间戳而言,是的:13 位通常表示毫秒。非常遥远的未来秒时间戳也可能达到 13 位,因此关键系统仍应携带显式单位元数据。
- 应该存秒还是毫秒?
- 存储系统自然使用的单位,但要记录并保持一致。以 JavaScript 为主的系统常用毫秒;Unix 风格的后端和许多数据库常用秒或原生 datetime 列。
- 为什么用 Math.floor(ms / 1000) 而不是 ms / 1000?
- Unix 秒通常是整数秒。Math.floor 去掉小数部分,使期望整数秒的 API 不会收到小数。
- 如何把毫秒转换为秒?
- 除以 1000 并舍去小数:JavaScript 用 Math.floor(ms / 1000),Python 用 ms // 1000,Go 用整数除法 ms / 1000。反向则把秒乘以 1000。
More posts
什么是 UTC 时间?含义、偏移与转换详解Unix 时间转日期:把 Unix 时间戳转换为可读时间纪元时间戳详解:Unix 时间、POSIX 时间与自 1970 年起的秒epoch 转换器指南:转换纪元时间、Unix 时间和时间戳日期转 epoch:把时间转换为 Unix 时间戳或纪元时间epoch 毫秒转日期:转换 13 位时间戳和 long 值当前 Unix 时间戳:纪元时钟、UTC 时间与实时 Unix 时间2026 年初的 Unix 时间戳:1767225600 详解JavaScript Date.now():获取与转换 Unix 时间戳在数据库中存储时间戳:DATETIME vs INT vs BIGINT无需库的 JavaScript 时区正确日期格式化每个开发者都发布过的 7 个 Unix 时间戳 bug纪元时间详解:Unix 时间戳零是什么?