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,显示时转换为本地时区。永远不要使用没有时区信息的时间戳进行数据交换。