UTC 是那一层用来比较的公共基准

UTC 是零偏移的参照,有了它,各个系统不用先就“用谁的本地钟”达成一致,就能比较瞬间。存储和传输一律用 UTC 表示瞬间;只有在有人要读它或者要按它排期时,才挑一个本地时区。

如果某个接口返回:

2023-11-14T22:13:20Z

结尾的 Z 就表示 UTC。同一个瞬间可以显示在别的时区里:

显示时区 同一瞬间显示为
UTC 2023-11-14 22:13:20 UTC
纽约 2023-11-14 17:13:20 EST
洛杉矶 2023-11-14 14:13:20 PST
东京 2023-11-15 07:13:20 JST

瞬间没变,变的只是显示用的时区。

Unix 时间戳的心智模型也是这个。Unix 时间戳 1700000000 表示 2023-11-14T22:13:20Z,它不表示你笔记本、你服务器或用户手机上的本地时间。

UTC 在实践中意味着什么

UTC 是协调世界时的缩写。下面这些东西背后共用的时间参照,就是它:

  • 服务器日志
  • API 时间戳
  • 数据库审计列
  • Unix 时间戳
  • Z 结尾的 ISO 8601 和 RFC 3339 字符串
  • 云监控图表
  • cron 任务和后台定时 worker
  • 跨地域的故障时间线

对开发者最实用的那版表述很简单:

瞬间一律以 UTC 存储。
只有当人要读它的时候,才转成本地时区。

这条规则能挡掉一类常见事故:一个系统写下 2026-06-20 09:00,另一个系统按不同的时区去读它,于是会议、扣款、报表或提醒整体错开好几个小时。

UTC 被视作一套国际时标。BIPM 的说法是:UTC 是国际时标,以国际原子时(TAI)为基础,按 IERS 的建议插入闰秒,以保持与地球自转时间接近。

UTC、偏移量、时区是三样不同的东西

这三个词经常被混为一谈:

术语 示例 含义
UTC 2026-06-20T14:30:00Z 零偏移的参考时间
偏移量 -04:00 某一瞬间相对 UTC 的数值差
时区 America/New_York 一套具名规则,含夏令时和政治变更

偏移量不等于时区。

America/New_York 通常冬季是 UTC-5、夏季是 UTC-4。如果你只存了 -05:00,就把夏令时规则弄丢了;如果你只存了 EST,那是个人们经常乱用的缩写——哪怕当时实际生效的是夏令时,也照样有人这么写。

按这条规则来:

  • 精确的瞬间存成 UTC。
  • 用户的本地语境重要时,存一个 IANA 时区名。
  • 只有当源数据真的给的就是一个固定偏移时,才存偏移量。

Unix 时间戳一定是 UTC 吗?

是的。Unix 时间戳的起算点是:

1970-01-01 00:00:00 UTC

POSIX 的 time() 返回自纪元以来的秒数,如今的开发文档通常用“Unix 时间戳”指同一个计数。JavaScript 用的是同一个纪元,只不过 Date 里存的是毫秒。

new Date(1700000000 * 1000).toISOString();
// "2023-11-14T22:13:20.000Z"

这个整数完全不记得自己曾被怎样表示过。这正是同一个时间戳在浏览器、服务器日志、SQL 客户端和电子表格里看起来各不相同的原因。

糟糕的心智模型:

1700000000 是纽约时间。

更好的心智模型:

1700000000 是一个瞬间。纽约只是它的一种可能显示方式。

那个 Z 是什么意思?

在互联网时间戳里,Z 表示 UTC,是零偏移的标记。

下面这两个字符串描述的是同一个瞬间:

2026-06-20T14:30:00Z
2026-06-20T14:30:00+00:00

RFC 3339 定义的互联网日期时间格式,允许用 Z,也允许用数值偏移:

2026-06-20T14:30:00Z
2026-06-20T10:30:00-04:00
2026-06-20T23:30:00+09:00

日志、接口、数据库导出和测试夹具里,如果你希望这个时间戳在哪儿都被读作 UTC,那就用 Z

“Zulu 时间”说的是同一件事,只是换了套词汇。NIST 的解释是:Z 时区就是 UTC,而在通话字母表里 Z 读作 “Zulu”。

UTC 与 GMT

在日常开发里,UTC 和 GMT 都表示零偏移。时间戳上写着 GMT,通常按 +00:00 处理就行。

真正需要区分的场合是写文档的时候:

标签 最适合用在
UTC 软件、日志、接口、数据库、时间戳文档
GMT 英国民用时间语境、老协议、部分面向人的钟面
Europe/London 冬季走 GMT、夏季走 BST 的那个时区

NIST 指出,GMT 原本是格林尼治平太阳时的缩写,现在则常用来指本初子午线所在的那个时区;而 UTC 才是通用计时的国际标准。

几个实际例子:

  • 2026-01-15 12:00 UTC2026-01-15 12:00 GMT 是同一个民用钟面读数。
  • 2026-07-15 12:00 Europe/London 是英国夏令时,也就是 UTC+1。
  • 日志里应该写 UTC 而不是 GMT,除非来源系统本身就标的是 GMT。

UTC 有夏令时吗?

没有。UTC 不会前拨也不会回拨。

会改变自身相对 UTC 偏移的是本地时区:

具名时区 冬季偏移 夏季偏移
America/New_York UTC-5 UTC-4
America/Chicago UTC-6 UTC-5
America/Denver UTC-7 UTC-6
America/Los_Angeles UTC-8 UTC-7

有些地方并不跟随常规的夏令时安排。美国最经典的例子是亚利桑那州:该州大部分地区用 America/Phoenix,全年保持 UTC-7。夏威夷用 Pacific/Honolulu,全年 UTC-10。

这正是代码里该用 America/New_York 这类 IANA 名字,而不是 ESTCSTPST 这类缩写的原因。

把 UTC 转成 EST、EDT、PST、PDT

只有当你确实指的就是那个固定偏移时,才使用具体的缩写:

缩写 含义 偏移
EST 东部标准时间 UTC-5
EDT 东部夏令时 UTC-4
CST 中部标准时间 UTC-6
CDT 中部夏令时 UTC-5
MST 山区标准时间 UTC-7
MDT 山区夏令时 UTC-6
PST 太平洋标准时间 UTC-8
PDT 太平洋夏令时 UTC-7

临时手算一个冬天的换算,EST 减 5 小时、PST 减 8 小时,没问题。

但写代码请这么写:

const instant = new Date("2023-11-14T22:13:20Z");

new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  year: "numeric",
  month: "short",
  day: "numeric",
  hour: "numeric",
  minute: "2-digit",
  second: "2-digit",
  timeZoneName: "short",
}).format(instant);
// "Nov 14, 2023, 5:13:20 PM EST"

IANA 名字会根据日期自动在 EST 和 EDT 之间选。手工偏移算术做不到这一点。

在代码里取 UTC 时间

在系统边界上使用 UTC 相关的 API:

环境 UTC 写法
JavaScript new Date().toISOString()
Python datetime.now(timezone.utc)
Java Instant.now()
C# DateTimeOffset.UtcNow
Go time.Now().UTC()
Shell date -u
PostgreSQL now() AT TIME ZONE 'UTC'

JavaScript 示例:

const nowUtc = new Date().toISOString();

Python 示例:

from datetime import datetime, timezone

now_utc = datetime.now(timezone.utc)

Shell 示例:

date -u +'%Y-%m-%dT%H:%M:%SZ'

存储和比较用 UTC,只有展示时才转成用户的时区:

const createdAt = new Date("2026-06-20T14:30:00Z");

new Intl.DateTimeFormat("zh-CN", {
  timeZone: "Asia/Tokyo",
  dateStyle: "medium",
  timeStyle: "short",
}).format(createdAt);
// "2026年6月20日 23:30"

日志、接口和数据库里的 UTC

下面这套约定,能省下最多的排查时间:

场合 推荐值
API 响应 2026-06-20T14:30:00Z,或者单位写明白的 Unix 毫秒
日志行 Z 的 UTC 时间戳,或显式标注 UTC
数据库瞬间 归一到 UTC 的原生时间戳类型,或者单位写进名字的纪元整数
按本地时间排的事件 本地日期 + 本地时间 + IANA 时区
仪表盘 提供 UTC 切换,同时显示用户本地时区

示例:

{
  "createdAt": "2026-06-20T14:30:00Z",
  "createdAtMs": 1781965800000
}

而对于周期性会议,光有 UTC 是不够的:

{
  "localTime": "09:00",
  "weekday": "Monday",
  "timeZone": "America/New_York"
}

第一个例子记录的是一次具体发生;第二个保留的是一条钟面规则,每次重复时都得拿时区规则重新解析一遍。

服务器设成 UTC,但别依赖默认值

把服务器设成 UTC 是个不错的运维基线,能让日志、指标和 cron 任务更好比对。

但格式化时间戳时,千万别依赖机器的默认时区。请在代码里把时区显式传进去。

几个顺手的运维检查:

date -u
date +%Z
timedatectl 2>/dev/null | grep 'Time zone'

常见的配置写法:

ENV TZ=UTC
ALTER DATABASE app SET timezone TO 'UTC';
CRON_TZ=UTC

即便如此,代码还是要按“默认值随时可能变”来写:

new Intl.DateTimeFormat("zh-CN", {
  timeZone: "UTC",
  dateStyle: "medium",
  timeStyle: "long",
}).format(new Date());

多写的这一句 timeZone: "UTC",能防止笔记本、CI、容器和生产环境之间的差异渗进输出里。

UTC、TAI、GPS 时间和闰秒

大多数 Web 应用不需要拿 TAI 或 GPS 时间做计算,但知道它们的区别,能让你少下一些过于自信的时间戳断言。

时标 是什么 开发者须知
UTC 民用参考时间 日志、接口、数据库和界面基准都用它
TAI 连续的原子时 计量和科学计时场景有用
GPS 时间 卫星导航时标 不插入 UTC 闰秒
Unix 时间 从 UTC 纪元起算的 POSIX 式计数 软件里最常见的时间戳表示

截至 2026 年 7 月 15 日:

  • TAI 比 UTC 快 37 秒。
  • GPS 时间比 UTC 快 18 秒。
  • 最近一次插入 UTC 闰秒是在 2016 年 12 月 31 日。

NIST 说明 UTC 以 TAI 为基础,并通过闰秒调整以顾及地球自转,目前 TAI 领先 UTC 37 秒。ITU 则介绍了正在推进的、改变 UTC 处理闰秒方式的流程,新机制预计在 2035 年生效。

对普通业务软件,实用建议就这几条:

  • 别自己手搓闰秒表。
  • 用平台自带的时间 API。
  • 时长和超时用单调时钟。
  • 事件瞬间用 UTC 时间戳。
  • 本地民用时间用 IANA 时区规则。

UTC 常见错误

错误做法 为什么会出问题 更好的做法
2026-06-20 09:00 这样的本地字符串 时区信息缺失 存带 Z 的时间戳,或本地时间加 IANA 时区
说“这个时间戳是 PST 的” Unix 时间戳不带时区 应该说“它按太平洋时间显示”
全年都写 EST 东部时间夏季会变成 EDT America/New_York
服务端格式化时不指定时区 服务器默认值可能不一样 显式传 timeZone: "UTC" 或用户时区
拿本地日期字符串做比较 偏移会让日历日整体错位 先比较瞬间,再做格式化
夏天还把 GMT、UTC 和伦敦当成一回事 伦敦夏季走的是 BST 零偏移用 UTC,英国民用时间用 Europe/London
用墙上时钟测时长 NTP、夏令时或人工改时间都会让它跳 测流逝时间请用单调时钟

套路始终一样,而且很简单:把瞬间、时区、显示格式当成三个各自独立的决定。只要这三个选择都没有藏在机器默认值里,代码就好审得多。

相关时间戳指南

官方参考资料

Frequent questions:

Q: 什么是 UTC 时间?
A: UTC 是协调世界时(Coordinated Universal Time)。它是全球通用的零偏移基准,民用时间、互联网时间戳、日志、接口、数据库和 Unix 时间都以它为参照。本地时区通常表示为相对 UTC 的偏移,比如 UTC-5 或 UTC+9。
Q: UTC 算是一个时区吗?
A: 严格说 UTC 是一个时间标准,而不是某地的民用时区。但在软件里它也当时区标签用:JavaScript 的 Intl 接受 timeZone: 'UTC',操作系统接受 TZ=UTC,IANA 也把 UTC 收录为一个类时区标识。
Q: UTC 和 GMT 是一回事吗?
A: 对普通时间戳来说,UTC 和 GMT 都表示零偏移。技术上,UTC 是基于原子钟的时间标准,而 GMT 如今多用来指格林尼治本初子午线所在的零偏移民用时区,或者英国的冬令时。软件文档里请统一用 UTC。
Q: Unix 时间戳一定是 UTC 吗?
A: 是的。Unix 时间戳从 Unix 纪元 1970-01-01 00:00:00 UTC 起算。这个整数里不存 PST、EST、东京时间或服务器本地时间。时区只在时间戳被格式化显示时才登场。
Q: 时间戳里的 Z 是什么意思?
A: ISO 8601 或 RFC 3339 时间戳结尾的 Z 表示 UTC。比如 2026-06-20T14:30:00Z 和 2026-06-20T14:30:00+00:00 是同一个瞬间。在航空、军事和航海语境里,Z 也被念作 Zulu。
Q: UTC 会因为夏令时而变化吗?
A: 不会。UTC 没有夏令时。地区和具名时区会因夏令时改变自己相对 UTC 的偏移,但 UTC 本身全年都是 +00:00。
Q: 怎么把 UTC 转成 EST 或 PST?
A: EST 是 UTC-5,PST 是 UTC-8,但这两个都是标准时的缩写。东部夏令时是 UTC-4,太平洋夏令时是 UTC-7。写代码时请用 America/New_York、America/Los_Angeles 这样的 IANA 名字,别用 EST 或 PST。
Q: 代码里怎么取当前的 UTC 时间?
A: JavaScript:new Date().toISOString()。Python:datetime.now(timezone.utc)。Shell:date -u。PostgreSQL:now() AT TIME ZONE 'UTC'。要展示给用户看,就把同一个瞬间格式化到用户的 IANA 时区。
Q: UTC 和 Unix 时间有什么区别?
A: UTC 是一种钟面/日历表示,比如 2023-11-14T22:13:20Z。Unix 时间是从 1970-01-01 00:00:00 UTC 起算的数值计数,比如 1700000000 秒。两者是同一个瞬间的两种编码。
Q: UTC、TAI、GPS 时间有什么区别?
A: TAI 是连续的原子时标。UTC 以 TAI 为基础,但通过闰秒调整来贴近地球自转。截至 2026 年 7 月 15 日,TAI 比 UTC 快 37 秒。GPS 时间不套用 UTC 的闰秒,目前比 UTC 快 18 秒。