How is Time Calculated and Stored in Programming?

程序中时间是如何计算与存储的?

前言

我一直对程序中日期存在个模糊不可靠的概念,通常依靠方便的库像是:dayjs🔗 去处理日期转换,但底层的原理至今仍不是很清楚,只知道时间是相对的,且存储方式可能是某种标准格式,这篇文章将尽可能补全程序处理时间所需要的知识。

时间有很多陷阱

仔细想想时间是一个看起来简单实际复杂的东西:

  • 时间是有上下文为前提的(时区、各种地区的奇怪时间规则)
  • 存储时间有不同的格式(各种时间戳记)
  • 显示时间有不同的格式习惯

时区的概念

地球自转一圈约 24 小时,但不同经度「正中午」的时间不同,为了避免时出现「在半夜 3 点吃午餐」的奇怪描述,时区的目的是让「太阳在天空最高点大约等于中午 12 点」这件事在每个地区都大致成立。

不同地区会为了方便而统一时区,像是「把整个国家都划分在同个时区」、「对齐某个政治或经济圈」即便可能相差数个小时。表示上会依照某个标准加减小时来代表时区:UTC+8、GMT-2。

夏令时间(Daylight Saving Time, DST)

暂时性的时区改变,夏天把时间拨快 1 小时。夏季昼长夜短,借由调快时间显示多利用自然的光照资源早睡早起。

电脑怎么运算时间?

大多系统使用 Unix Timestamp 沿用至今:

  • UTC Coordinated Universal Time 世界协调时间:以原子钟计时,无关时区的时间标准
  • Unix Timestamp:自 1970-01-01 00:00:00 UTC 起经过的秒数。毫秒、微秒与纳秒都是各语言为了实务需求所做的延伸。
JavaScript
Date.now() // 1765893674273
Go
time.Now().Unix() // 秒
time.Now().UnixMilli() // 毫秒
time.Now().UnixMicro() // 微秒
time.Now().UnixNano() // 纳秒

如何存储时间?

总结以上时间应被分为「时刻」和「时区」两种概念,存储时间依照使用性质选择不同的方式:「不该随时区改变意义的时间」和「会随时区改变意义的时间」。

  • 不随时区改变意义:发文时间
    • 想像如果 DB Timezone 设置、时区规则改动会连带改变发文时间是很奇怪的
  • 随时区改变意义:会议时间
    • 想像公司约定“每周一早上 9 点开会”,如果我们一开始就把它存成某个 UTC 时刻,当夏令时间切换时,会议就会自动变成 10 点或 8 点,但约定从来没有改变。

对已经发生的事件 UTC 是事实,对尚未发生的排程,Local Time 才是事实。以下是数据库常见的日期存储对照:

DB类型是否存 UTC是否自动转时区
MySQLTIMESTAMP
MySQLDATETIME
PostgreSQLTIMESTAMPTZ
PostgreSQLTIMESTAMP
MongoDBDate

ISO 8601 时间格式

YYYY-MM-DDTHH:mm:ss
  • Z: UTC
  • +08:00:时区偏移

为什么 UTC + Offset 还不够?什么是 IANA?

ISO 8601 解决“怎么写时间”, IANA (Internet Assigned Numbers Authority) 解决“这个时间在那个地方到底几点”。

因为 ISO 8601 只能表示“当下与 UTC 的差距”,却无法描述:

  • +08:00 这个地方是不是有夏令时间?
  • 2026 年会不会改规则?
  • 历史上某一年是不是曾经用过别的 offset?

因此在排程或未来事件中,应该加上 IANA 标示:

2025-12-17T09:23:45+08:00[Asia/Taipei]

延伸阅读