理解 Unix 时间戳:开发者指南

CodeKit
时间戳日期时间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 时间戳有两种常见精度:毫秒。这是开发者最容易踩的坑之一。

精度典型值使用场景
1718448000Unix 系统调用、Cron、API
毫秒1718448000000JavaScript、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 中始终使用 BIGINTTIMESTAMP 类型存储时间戳,避免使用 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 上的 时间戳转换器——支持秒和毫秒,自动检测精度,所有计算在浏览器中完成。