Unix 时间与 Unix 时间戳
什么是 Unix 时间?
Unix 时间(或称“unixtime”)是一种通过计算自某个固定参考时刻以来经过的秒数来表示时间点的系统。这个参考时刻称为 Unix 纪元(UTC 1970年1月1日 00:00:00)。
Unix 时间戳本质上是以简单数字的形式存储数值。许多计算机系统和 Linux 机器都采用 Unix 时间作为基础计时方式。
在实际使用中,同一概念会有不同的叫法:Unix 时间戳、纪元时间(epoch time)、POSIX 时间,或者干脆叫“Unix 戳”。它们指的都是同一个值:自 Unix 纪元以来经过的秒数。“Unix 时间”通常指这套系统本身,而“Unix 时间戳”往往指某个具体存储的数字,但在实践中,这两个术语可以互换使用。
Unix 时间的起源
Unix 时间(或称 unixtime)的概念源自早期的 Linux 和 Unix 操作系统,旨在提供一种紧凑且一致的时间格式。在许多基于 Linux 的环境中,它也被称为 POSIX 时间。
许多不同的系统都采用了 Unix 时间,将其作为时间数据的统一数据格式。选择 1970年1月1日作为 Unix 纪元是一个任意的决定,但它为系统间的同步提供了坚实的基准。
POSIX 时间与“date”命令
在 Linux、macOS 及其他类 Unix 系统上,操作系统内核在内部以 Unix 时间戳的形式记录时间。POSIX 时间指的是同一个概念,这个名称只是体现了在各类类 Unix 环境中规范这一行为的 IEEE Std 1003.1 标准。在日常使用中,POSIX 时间和 Unix 时间是同一个数字。
Shell 通过 date 工具提供这个数字。运行“date +%s”会以秒为单位输出当前的 Unix 时间戳,而“date -d @1700000000”则会将存储的值转换回可读的本地日期。在 macOS 上,对应的命令是“date -r 1700000000”。正是这些单行命令,让 Unix 时间成为 shell 脚本、cron 任务和日志轮换工具事实上的通用语言。
如何计算 Unix 时间
要计算 Unix 时间,需要统计自 UTC Unix 纪元以来经过的秒数,不计闰秒。例如,对于 1970年1月1日之后的任意日期,用该日期减去纪元时间,再将差值换算成秒。
处理时区时,需要先将本地时间转换为 UTC,再进行计算。由于 Unix 时间忽略闰秒,每一天都按正好 86,400 秒计算,这意味着在插入闰秒时,它可能与真正的原子时及 UTC 时间产生偏差。
在 Unix 时间中,大多数日子都按正好 86,400 秒计算。这意味着增加一天通常会使时间戳增加 86,400(只有含闰秒的日子例外)。
使用 JavaScript 时,可以调用 Date.now() 获取当前的 Unix 值(它返回自纪元以来的毫秒数),然后除以 1000,得到以秒为单位的 Unix 时间戳。
Unix 时间戳格式与示例
标准的 Unix 时间戳是一个以秒为单位的 10 位整数。例如,1700000000 对应 UTC 2023年11月14日 22:13:20。再加三位数字就是毫秒(13 位),这也是 JavaScript 和许多 Web API 默认返回的格式。
这种格式只是一个计数,没有分隔符、没有时区标记,也没有日历元数据。所有信息都编码在数字本身之中。下面几个参考点可以帮助您更直观地理解其数量级:
|
日期和时间(UTC) |
Unix 时间戳 |
|---|---|
|
1970年1月1日 00:00:00 |
0 |
|
1969年7月20日 20:17:40 |
-14182940 |
|
2000年1月1日 00:00:00 |
946684800 |
|
2001年9月9日 01:46:40 |
1000000000 |
|
2024年1月1日 00:00:00 |
1704067200 |
|
2038年1月19日 03:14:07 |
2147483647 |
纪元之前的日期也完全有效,只是带有负号。数值 -14182940 恰好对应 Apollo 11(阿波罗 11 号)在月球着陆的那一刻。表格最后一行标出了有符号 32 位 Unix 时间戳的上限,也就是 2038年问题开始出现的地方。
在代码中获取 Unix 时间戳
每种主流编程语言都可以通过一行代码获取 Unix 时间。它们返回的值完全相同,因为按照定义,时间戳与时区无关。
- JavaScript:Math.floor(Date.now() / 1000) 获取秒数,Date.now() 获取毫秒数。
- Python:import time; int(time.time()) 获取整秒数;去掉 int() 即可保留亚秒级精度。
- Bash / Linux:date +%s 直接在终端中输出当前的 Unix 时间戳。
- PHP:time() 获取秒数,microtime(true) 获取带小数的精度。
- Java:System.currentTimeMillis() / 1000L 获取秒数。
- Go:使用 time.Now().Unix()。
- SQL:MySQL 中使用 UNIX_TIMESTAMP(),PostgreSQL 中使用 EXTRACT(EPOCH FROM NOW())。
反向转换,即把存储的时间戳转换回人类可读的日期,同样是内置功能。在 Python 中使用 datetime.utcfromtimestamp(ts);在 JavaScript 中使用 new Date(ts * 1000).toISOString()。显示时务必明确指定目标时区,因为时间戳本身不包含任何时区信息。
秒、毫秒与更高精度
最初的 Unix 时间戳以整秒计数,但现代系统往往需要更高的分辨率。通过位数很容易判断所用的约定:10 位是秒,13 位是毫秒,16 位是微秒,19 位是纳秒。
JavaScript 默认使用毫秒,Python 和大多数数据库默认使用秒,而交易引擎或分布式追踪工具等高频系统通常以纳秒为单位。当您从不熟悉的来源收到时间戳时,请在解析前先看一眼位数:不知不觉地混用秒和毫秒,是生产代码中最常见的日期错误之一,会使结果在任一方向上偏差 1,000 倍。
Unix 时间的常见用途
Unix 时间通常用于在数据库、日志、API 和文件格式中存储时间戳。借助这种时间格式,系统可以统一地存储时间值。
由于它避免了时区和本地时间带来的歧义,因此在系统之间交换数值时,人们更倾向于使用这种数据格式。许多 Web API 返回 Unix 时间戳,以便客户端和服务器将其转换为人类可读的日期格式。它还用于按时间顺序排序、任务调度以及计算机系统中的事件日志记录。
2038年问题
Unix 时间有一个著名的边界情况。将该值存储为有符号 32 位整数的系统最多只能计数到 2,147,483,647 秒,而这个上限会在 UTC 2038年1月19日 03:14:07 达到。再过一秒,数值就会溢出到负数范围,使日期回到 1901年。它有时也被称为 Y2038 或“Unix 千年虫”。
大多数现代操作系统、数据库和编程语言已经改用 64 位时间戳,将上限推到数千亿年之后。如今的风险主要存在于遗留代码、嵌入式固件、较旧的文件格式,以及从未重新编译过的 32 位二进制文件中。实际的做法是审查长期运行的系统中是否存在硬编码的 32 位时间类型。
常见问题
Unix 时间戳有多少位?
以秒为单位时为 10 位(直到2286年),以毫秒为单位时为 13 位。长度不同的时间戳几乎总是精度不匹配造成的,在解析前应先进行合理性检查。
Unix 时间与 UTC 相同吗?
不完全相同。Unix 时间从一个固定的 UTC 时刻开始计秒,因此按照定义,它以 UTC 为基准,但它不像真正的 UTC 那样计入闰秒。对大多数应用来说,这种差异察觉不到;但对高精度计时来说则不然。
Unix 时间戳可以是负数吗?
可以。负值表示 1970年1月1日之前的日期。这种计算是有效的,但并非每个库都能妥善处理负值,依赖它们之前请先进行测试。
Unix 时间与纪元时间有什么区别?
在日常使用中,没有区别。这两个术语都表示自 UTC 1970年1月1日以来经过的秒数。“纪元时间”的含义稍宽泛一些,因为其他系统会定义自己的纪元(如 .NET 纪元、Mac OS 纪元等),但在 99% 的场景中,人们指的都是 Unix 时间。
