理解 Unix 时间戳:开发者指南
什么是 Unix 时间戳?
Unix 时间戳(Unix timestamp,又称 Unix epoch time)是从 1970 年 1 月 1 日 00:00:00 UTC(即 Unix 纪元)开始所经过的秒数。它是计算机科学中最广泛使用的时间表示方式之一,几乎所有操作系统和编程语言都原生支持。
当前时间戳示例:1718448000
对应时间:2024-06-15 12:00:00 UTC
Unix 时间戳的核心优势在于时区无关——它始终表示 UTC 时间,避免了时区转换的复杂性。无论你的服务器在东京、伦敦还是纽约,同一时刻的时间戳值完全相同。
秒 vs 毫秒:一个常见的陷阱
Unix 时间戳有两种常见精度:秒和毫秒。这是开发者最容易踩的坑之一。
| 精度 | 典型值 | 使用场景 |
|---|---|---|
| 秒 | 1718448000 | Unix 系统调用、Cron、API |
| 毫秒 | 1718448000000 | JavaScript、Java、数据库 |
JavaScript 的 Date.now() 返回毫秒,而 Unix date 命令和大多数 REST API 返回秒。混用会导致日期显示在 1970 年或远在未来。
// JavaScript:毫秒
const ms = Date.now();
console.log(ms); // 1718448000000
// 转换为秒
const sec = Math.floor(ms / 1000);
console.log(sec); // 1718448000
// 从秒构造 Date 对象
const date = new Date(sec * 1000); // 注意乘以 1000
# Python:秒(浮点数)
import time
print(time.time()) # 1718448000.123456
# 毫秒
print(int(time.time() * 1000)) # 1718448000123
经验法则:13 位数字通常是毫秒,10 位数字通常是秒。但永远不要靠猜——查阅所用 API 的文档。
2038 年问题
32 位有符号整数能表示的最大值是 2,147,483,647。作为 Unix 时间戳(秒),这对应 2038 年 1 月 19 日 03:14:07 UTC。下一秒,32 位系统上的时间戳会溢出为负数,被解释为 1901 年。
这就是著名的 Y2038 问题(Year 2038 problem)。
2038-01-19 03:14:07 UTC → 2147483647
2038-01-19 03:14:08 UTC → -2147483648(32 位有符号溢出)
对于现代 64 位系统,这个问题基本不存在——64 位时间戳可以表示到 2920 亿年之后。但嵌入式系统、遗留数据库字段(INT 而非 BIGINT)和某些文件格式仍然可能受影响。
最佳实践:在数据库 schema 中始终使用 BIGINT 或 TIMESTAMP 类型存储时间戳,避免使用 32 位整数。
时区处理
Unix 时间戳本身是 UTC,但显示给用户时需要转换为本地时间。这是另一个常见错误来源。
// 错误:直接使用本地时间构造时间戳
const date = new Date('2024-06-15 12:00:00'); // 解析为本地时间
const ts = date.getTime(); // 不同时区的服务器得到不同结果
// 正确:显式指定时区
const dateUTC = new Date('2024-06-15T12:00:00Z'); // Z 表示 UTC
const ts = dateUTC.getTime(); // 所有服务器得到相同结果
# Python:时区感知的时间戳
from datetime import datetime, timezone
# 当前 UTC 时间戳
ts = datetime.now(timezone.utc).timestamp()
# 从时间戳构造时区感知的 datetime
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt) # 2024-06-15 12:00:00+00:00
建议:在系统内部始终使用 UTC 和时间戳存储,只在展示层转换为用户本地时区。
各语言示例
// JavaScript
const now = Date.now(); // 毫秒
const sec = Math.floor(now / 1000); // 秒
const date = new Date(now);
console.log(date.toISOString()); // "2024-06-15T12:00:00.000Z"
# Python
import time
from datetime import datetime, timezone
ts = time.time() # 秒(浮点)
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt.isoformat()) # "2024-06-15T12:00:00+00:00"
// Go
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now().Unix() // 秒
ms := time.Now().UnixMilli() // 毫秒
fmt.Println(now, ms)
}
-- PostgreSQL
SELECT EXTRACT(EPOCH FROM NOW()); -- 秒(浮点)
SELECT (EXTRACT(EPOCH FROM NOW()) * 1000)::bigint; -- 毫秒
SELECT to_timestamp(1718448000); -- 转换为时间
常见陷阱
1. 混淆秒和毫秒
如前所述,这是最常见的错误。始终确认 API 返回的是哪种精度。
2. 忽略时区
// 危险:依赖服务器时区
const ts = new Date('2024-06-15').getTime();
// 安全:显式 UTC
const ts = new Date('2024-06-15T00:00:00Z').getTime();
3. 浮点精度问题
JavaScript 中所有数字都是 64 位浮点数,能精确表示的最大整数是 2⁵³。毫秒级时间戳(约 13 位)远低于此限制,但如果你拼接其他数据,需要注意精度。
4. 闰秒
Unix 时间戳忽略闰秒——它假设每天都是 86400 秒。这意味着时间戳与真实的 UTC 时间在闰秒时会有微小偏差。对于绝大多数应用,这可以忽略。
总结
Unix 时间戳是处理时间的最简单、最可靠的方式。记住三个要点:明确精度(秒还是毫秒)、内部用 UTC、展示时转本地时区。遵循这些原则,可以避免 90% 的时间相关 bug。
需要快速转换时间戳?试试 CodeKit 上的 时间戳转换器——支持秒和毫秒,自动检测精度,所有计算在浏览器中完成。