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 相差非整数分钟数。为了准确表示此类历史时间戳, 应用程序必须将它们转换为可表示的时区。
历史时区示例:
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 的地球上失败,
强制性要求
互联网协议必须生成完全限定的时间戳。
这意味着互联网协议不得使用没有时区信息的本地时间。
错误示例:
❌ 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 存储, 显示时转换为本地时区。永远不要在数据交换中使用没有时区信息的时间戳。