纪元时间详解:Unix 时间戳零是什么?

Unix 时间戳 0 是 1970 年 1 月 1 日 00:00:00 UTC。了解为什么那个日期成为 Unix 纪元、负时间戳代表什么、纪元的秒/毫秒/微秒有何不同,以及现代系统适用哪些限制。

Unix 时间戳 0 代表什么

每个 Unix 时间戳都计数自单一参考点——1970 年 1 月 1 日 00:00:00 UTC——以来经过的时间。在 Unix 的原始定义中单位是秒,所以时间戳 0 正好是 1970 年 1 月 1 日的 UTC 午夜,86400 正好是 24 小时之后。现代系统也使用毫秒、微秒和纳秒,但参考点相同。每个正值表示纪元之后的某个瞬间;每个负值表示之前的瞬间。

为什么是 1970 年 1 月 1 日?

这个日期由贝尔实验室的 Unix 开发者在 20 世纪 70 年代初选定,作为略早于 Unix 本身诞生的实用参考点。它需要足够早,使普通系统时间戳为正;足够近,以适配早期计算机的小整数范围;又足够简单,便于心算核对。当 Unix 变得有影响力后,语言、操作系统、数据库、日志格式和网络协议都继承了同一个起点。

  • Unix 自 1969 年起在贝尔实验室开发 —— 纪元设在其稍前
  • 选择 1 月 1 日作为干净的日历边界
  • 用 1970 年而非 1969 年,因为实现使用从零开始的计数器
  • 现代所有操作系统、语言和协议都继承了这个纪元 —— 没有竞争标准

负时间戳:1970 年之前的日期

负的 Unix 时间戳表示 1970 年 1 月 1 日 00:00:00 UTC 之前的瞬间。大多数现代 64 位系统都能正确支持,这对历史数据、出生日期、归档记录,以及与纪元早于 Unix 的系统互操作很有用。较旧的库和数据库可能拒绝负值,因此历史数据集值得做一次显式的兼容性检查。

  • -1 = December 31, 1969 23:59:59 UTC (one second before the epoch)
  • -86400 = December 31, 1969 00:00:00 UTC (one day before the epoch)
  • -2208988800 = January 1, 1900 00:00:00 UTC
  • JavaScript: new Date(-86400 * 1000).toISOString() → '1969-12-31T00:00:00.000Z'

格式的实际上限

Unix 纪元本身不是上限;存储类型才是。存储在 32 位有符号整数中的时间戳,会比存储在 64 位整数、JavaScript 数字或数据库原生 timestamp 类型中的时间戳早得多地失败。评估时间戳安全性时,既要问使用什么单位,也要问用什么数值类型存储。

  • 32-bit signed integer max: 2,147,483,647 = January 19, 2038 03:14:07 UTC (the Year 2038 problem)
  • JavaScript Date max: 8,640,000,000,000,000 ms = September 13, 275760 CE
  • 64-bit signed integer max: ~9.2 × 10^18 seconds = year ~292 billion
  • PostgreSQL TIMESTAMPTZ max: January 1, 294276 CE

其他系统的纪元

Unix 并不是计算中使用的唯一纪元。其他系统出于历史或技术原因定义了自己的参考点。

  • GPS 纪元:1980 年 1 月 6 日 00:00:00 UTC —— 由 GPS 卫星和接收机使用
  • Windows FILETIME:1601 年 1 月 1 日 00:00:00 UTC —— 以 100 纳秒为间隔
  • Apple Cocoa / NSDate:2001 年 1 月 1 日 00:00:00 UTC
  • Excel / Lotus 1-2-3:1900 年 1 月 1 日 —— 为兼容 Lotus 而故意保留的闰年差一错误
  • FAT 文件系统:1980 年 1 月 1 日 00:00:00 本地时间

纪元的秒、毫秒、微秒与纳秒

timestamp 这个词并不总是表明单位。Unix 秒在操作系统和后端语言中常见。Unix 毫秒在 JavaScript 和浏览器 API 中常见。微秒和纳秒出现在数据库、追踪系统和高分辨率日志中。单位同时决定精度和你看到的位数。

  • Seconds: 1700000000 — common in Python, PHP, Go, Ruby, C, and Unix command-line tools
  • Milliseconds: 1700000000000 — common in JavaScript Date and many web analytics systems
  • Microseconds: 1700000000000000 — common in some databases and event pipelines
  • Nanoseconds: 1700000000000000000 — common in high-resolution tracing and systems languages
  • Always document the unit when storing epoch values in APIs, CSV files, and database columns

纪元时间 vs UTC vs 本地时间

纪元时间是一个计数。UTC 是定义参考时刻的全球时间标准。本地时间是基于 America/New_York 或 Asia/Tokyo 这类时区的显示选择。Unix 时间戳不会因用户出行而改变;改变的只是格式化的日历日期和时间。

  • 同一个时间戳在不同时区可能显示为不同的本地日期
  • UTC 输出最适合日志、API 和跨服务器调试
  • 本地输出最适合日历、用户界面和报表
  • 时区偏移在格式化时应用,并不存储在 Unix 时间戳内部

纪元时间常见问题

纪元时间总是以秒计吗?
经典的 Unix 时间戳是自 1970-01-01 00:00:00 UTC 起的秒,但许多系统在同一纪元下使用毫秒、微秒或纳秒。
Unix 时间戳能表示 1970 年之前的日期吗?
可以。负时间戳表示 Unix 纪元之前的日期,不过较旧的库和数据库可能不支持。
Unix 时间戳包含时区吗?
不包含。Unix 时间戳表示 UTC 中的一个瞬间。只有在把该瞬间转换为可读的本地日期时才会使用时区信息。
Unix 时间戳的最大值是多少?
取决于存储类型,而非纪元。32 位有符号整数最大为 2,147,483,647(2038 年 1 月 19 日),而 64 位整数或 JavaScript 数字可达数亿年。详情见 2038 年问题。