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 UTC和2026-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 名字,而不是 EST、CST、PST 这类缩写的原因。
把 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 秒。