什么是 Unix 时间
Unix 时间(也称为纪元时间或 POSIX 时间)计算的是自 1970-01-01 00:00:00 UTC(即 Unix 纪元)以来经过的秒数。0 就是纪元本身,1700000000 是 2023-11-14 22:13:20 UTC,负值表示 1970 年之前的时刻:-1 是 1969-12-31 23:59:59 UTC。
这个数字不包含时区:它在任何地方都表示同一时刻,因此非常适合用来存储、排序和比较事件。
Unix 时间忽略闰秒:每一天都恰好是 86400 秒。当 UTC 增加一个闰秒时,系统会重复一秒,或把它分摊到更长的时间段中(即“闰秒平滑”),因此跨越闰秒计算出的时间差会比实际流逝的时间少一秒。
秒、毫秒、微秒还是纳秒?
许多系统以更小的单位计数,而数字本身并不会说明单位。同一时刻可能表现为四个相差悬殊的数字:
| 单位 | 2023-11-14 22:13:20 UTC | 位数(2001–2286 年) | 常见于 |
|---|---|---|---|
| 秒 | 1700000000 |
10 | date +%s、许多 API、JWT 的 exp 声明 |
| 毫秒 | 1700000000000 |
13 | JavaScript Date.now()、Java System.currentTimeMillis() |
| 微秒 | 1700000000000000 |
16 | 一些数据库和日志系统 |
| 纳秒 | 1700000000000000000 |
19 | Go UnixNano()、Python time.time_ns() |
对于当前的日期,数一数位数是最快的检查方法。LeedyBox 转换器也采用同样的思路:不超过 1011 的值按秒读取,不超过 1014 的按毫秒,不超过 1017 的按微秒,更大的值按纳秒。对于离今天很远的日期,这条经验法则会失效,因此当结果看起来不对劲时,请检查单位。
精度同样重要。JavaScript 数字只能精确表示不超过 253(约 9 × 1015)的整数,因此像 1700000000123456789 这样的纳秒值在按普通数字解析时会丢失末尾几位。请把这类值保留为字符串,或使用 BigInt;转换器内部就是这样处理的。要读取令牌的 exp 声明,请用 Base64 / URL / JWT 工具解码。
时区与夏令时
要把时间戳显示为日期和时间,需要一个时区。UTC 是基准;本地时间会加上一个偏移量,例如首尔为 +09:00,纽约冬季为 −05:00。
请优先使用 Asia/Seoul 或 America/New_York 这样的 IANA 时区名称,而不是固定偏移量和缩写:名称包含了该时区的历史和夏令时规则,而 CST 这样的缩写可以代表好几个时区。这些规则来自时区数据库,该数据库每年会更新好几次,而浏览器、操作系统和编程语言各自附带自己的副本,因此对过去或未来日期的计算结果可能不同。
把本地时间转换为时间戳时,夏令时会造成两种棘手的情况:
- 间隙:时钟拨快时,有些本地时间并不存在。在
America/New_York时区,2026-03-08 02:30 会被跳过,因为时钟从 02:00 直接跳到 03:00。 - 重叠:时钟拨回时,有些本地时间会出现两次。2026-11-01 01:30 先在 EDT(UTC−04:00)中出现一次,一小时后又在 EST(UTC−05:00)中再出现一次。
不同的库处理这些情况的方式各不相同。LeedyBox 转换器在间隙中采用较晚的有效时间(此例中为 03:30),在重叠中采用较早的那一次,并会在提示中加以说明。定时任务也面临同样的问题;请参阅如何读懂 cron 表达式。
ISO 8601、RFC 3339 与 HTTP 日期
当某个时刻需要便于人阅读时,请使用 ISO 8601 或其更严格的互联网规范 RFC 3339 来书写:
2023-11-14T22:13:20Z
2023-11-15T07:13:20+09:00
2023-11-14T22:13:20.500Z
Z 表示 UTC。带有 Z 或偏移量的字符串没有歧义。两者都没有的字符串,例如 2023-11-14T22:13:20,表示时区未知的本地时间,不同解析器对它的解读也不一致。
Last-Modified 和 Expires 等 HTTP 头使用一种较旧的格式,它源自电子邮件的日期格式(RFC 2822,现为 RFC 5322),并以 GMT 表示:Tue, 14 Nov 2023 22:13:20 GMT。
2038 年问题
许多较旧的系统用有符号 32 位整数存储 Unix 时间。它的最大值 2147483647 对应 2038-01-19 03:14:07 UTC。再过一秒,计数器就会溢出为 -2147483648,被解读为 1901-12-13 20:45:52 UTC。当前的操作系统和编程语言大多使用 64 位时间值,可以覆盖数千亿年,但 32 位字段仍然存在于嵌入式设备、文件格式、数据库列和网络协议中。如果您的代码或数据用 32 位存储秒数,请尽早规划迁移,并且现在就测试 2038 年之后的日期。
代码和数据库中的时间戳
JavaScript 以毫秒为单位:
Math.floor(Date.now() / 1000); // 当前 Unix 时间(秒)
new Date(1700000000 * 1000).toISOString(); // "2023-11-14T22:13:20.000Z"
Date.parse("2026-10-03T09:00:00+09:00") / 1000; // 1790985600
Python 的 time.time() 以浮点数返回秒数。请传入 tz=timezone.utc,否则 fromtimestamp() 会返回不带时区信息的本机本地时间:
import time
from datetime import datetime, timezone
int(time.time()) # 秒
datetime.fromtimestamp(1700000000, tz=timezone.utc) # 2023-11-14 22:13:20+00:00
datetime.fromisoformat("2026-10-03T09:00:00+09:00").timestamp() # 1790985600.0
在 SQL 中,不同数据库的函数各不相同:
-- PostgreSQL
SELECT extract(epoch FROM now()); -- 秒,带小数部分
SELECT to_timestamp(1700000000); -- timestamp with time zone
-- MySQL
SELECT UNIX_TIMESTAMP();
SELECT FROM_UNIXTIME(1700000000); -- 按会话时区
在存储方面,养成几个习惯就能避免大多数问题:
- 已经发生的时刻请用 UTC 存储:可以存入带时区的时间戳列,也可以存为 64 位整数,并在列名中注明单位,例如
created_at_ms。 - 未来的本地事件(例如首尔 09:00 的会议)请存储为本地日期和时间加上 IANA 时区名称,在需要时再计算时间戳,这样日后的规则变更就不会让会议时间发生偏移。
- 不要在同一列中混用不同的单位;单位一旦弄错,就会相差 1000 倍甚至更多。
常见问题
为什么我的时间戳转换出来是 1970 年的日期?
这个值很可能是按错误的单位读取的:把秒当作毫秒读取,会落在 1970 年 1 月;把毫秒当作秒读取,则会落在数万年之后。请数一下位数,或在转换器中选择单位。
Unix 时间戳取决于我所在的时区吗?
不取决于。它从 UTC 中一个固定的时刻开始计算秒数。只有为它显示的日期和时间才取决于时区。
Unix 时间能表示闰秒吗?
不能。Unix 时间把每一天都视为 86400 秒,因此像 23:59:60 这样的 UTC 时间没有属于自己的时间戳。