Skip to main content

4. Local Time (本地时间)

4.1. Coordinated Universal Time (UTC) (协调世界时)

由于本地时区的夏令时规则非常复杂,并且可能在不可预测的时间基于当地法律而改变,因此通过使用协调世界时 (Coordinated Universal Time, UTC) 最能实现真正的互操作性。本规范不迎合本地时区规则。

为什么使用UTC?

  • ✅ 全球统一的时间基准
  • ✅ 不受夏令时影响
  • ✅ 不受政治决策影响
  • ✅ 确保互操作性

4.2. Local Offsets (本地偏移量)

本地时间与UTC之间的偏移量通常是有用的信息。例如,在电子邮件 (RFC2822, [IMAIL-UPDATE]) 中,本地偏移量提供了一个有用的启发式方法来确定及时响应的可能性。过去尝试使用字母字符串标记本地偏移量导致了互操作性差 [IMAIL], [HOST-REQ]。因此,RFC2822 [IMAIL-UPDATE] 已将数字偏移量设为强制性。

数字偏移量的计算

数字偏移量计算为 "本地时间减去UTC"。因此,可以通过从本地时间中减去偏移量来确定UTC中的等效时间。

示例1:

本地时间: 18:50:00-04:00
UTC时间: 18:50:00 - (-04:00) = 18:50:00 + 04:00 = 22:50:00Z

验证: 北美东部时区夏令时 (EDT) 为 UTC-4

示例2:

本地时间: 15:30:00+08:00
UTC时间: 15:30:00 - (+08:00) = 15:30:00 - 08:00 = 07:30:00Z

验证: 中国标准时间 (CST) 为 UTC+8

注意事项

注意: 遵循ISO 8601,数字偏移量仅表示与UTC相差整数分钟的时区。然而,许多历史时区与UTC相差非整数分钟。要精确表示此类历史时间戳,应用程序必须 (MUST) 将它们转换为可表示的时区。

历史时区示例:

19世纪末的某些地方时间:
- 阿姆斯特丹: UTC+00:19:32
- 巴黎: UTC+00:09:21

这些无法用RFC 3339精确表示,需要舍入到最接近的分钟

4.3. Unknown Local Offset Convention (未知本地偏移约定)

如果UTC时间已知,但本地时间的偏移量未知,则可以用偏移量 "-00:00" 表示。这在语义上不同于偏移量 "Z" 或 "+00:00",后者意味着UTC是指定时间的首选参考点。RFC2822 [IMAIL-UPDATE] 为电子邮件描述了类似的约定。

三种表示方式的区别

Z 或 +00:00:

2002-07-15T10:30:00Z
2002-07-15T10:30:00+00:00

含义: 这个时间就是UTC时间,UTC是首选参考点

-00:00:

2002-07-15T10:30:00-00:00

含义: UTC时间是10:30:00,但本地时区偏移未知
(可能来自不知道时区设置的系统)

使用场景:

场景1: 服务器日志,知道是UTC → 使用 Z
场景2: 设备生成的时间戳,设备在UTC但不知道本地时区 → 使用 -00:00
场景3: 明确知道在伦敦(GMT) → 使用 +00:00

4.4. Unqualified Local Time (非限定本地时间)

目前连接到互联网的许多设备以本地时间运行其内部时钟,并且不知道UTC。虽然互联网在创建规范时确实有接受现实的传统,但这不应该以牺牲互操作性为代价。由于对非限定本地时区的解释将在全球约23/24的地方失败,

强制要求

互联网协议必须 (MUST) 生成完全限定的时间戳。

这意味着互联网协议不应该 (MUST NOT) 使用没有时区信息的本地时间。

错误示例:

❌ 2002-07-15T10:30:00 (没有时区信息)

正确示例:

✅ 2002-07-15T10:30:00Z (UTC)
✅ 2002-07-15T10:30:00+08:00 (明确时区)
✅ 2002-07-15T10:30:00-00:00 (UTC但时区未知)

互操作性问题

如果使用非限定本地时间:

发送方: 2002-07-15T10:30:00 (纽约本地时间,实际为UTC-4)
接收方在东京: 误解为东京时间 (UTC+9)
时间差异: 13小时的错误!

实施建议

系统设计

# 推荐: 始终以UTC存储时间
def store_timestamp():
utc_time = datetime.now(timezone.utc)
return utc_time.isoformat() # 2024-12-21T10:30:00+00:00

# 显示时: 转换为用户本地时区
def display_timestamp(utc_time, user_timezone):
local_time = utc_time.astimezone(user_timezone)
return local_time.isoformat()

数据库存储

-- 推荐: 使用TIMESTAMP WITH TIME ZONE
CREATE TABLE events (
id SERIAL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 避免: TIMESTAMP WITHOUT TIME ZONE (会导致歧义)

关键原则: 内部存储使用UTC,显示时转换为本地时区。永远不要使用没有时区信息的时间戳进行数据交换。