5. Date and Time format (日期和时间格式)
本节讨论日期和时间格式的理想特性, 并定义了用于互联网的 ISO 8601 配置文件。
5.1. Ordering (排序)
如果日期和时间组件按从最不精确到最精确的顺序排列, 则可以实现有用的属性。假设日期和时间的时区相同 (例如, 全部在 UTC), 使用相同的字符串表示 (例如, 全部 "Z" 或全部 "+00:00"), 并且所有时间具有相同的小数秒位数, 则日期和时间字符串可以作为字符串排序 (例如, 使用 C 中的 strcmp() 函数), 并将产生按时间排序的序列。可选标点符号的存在会违反此特性。
示例:
正确排序 (年-月-日 时:分:秒):
2002-01-15T10:00:00Z
2002-07-20T15:30:00Z
2002-12-31T23:59:59Z
错误格式 (月/日/年) 无法正确排序:
01/15/2002 10:00:00
12/31/2002 23:59:59 ← 字符串排序将此放在 7 月之前
07/20/2002 15:30:00
5.2. Human Readability (人类可读性)
人类可读性已被证明是互联网协议的一个宝贵特性。人类可读的协议大大降低了调试成本, 因为 telnet 通常足以作为测试客户端, 并且网络分析器不需要根据协议知识进行修改。另一方面, 人类可读性有时会导致互操作性问题。
问题示例:
❌ "10/11/1996" 完全不适合全球交换
美国: 1996 年 10 月 11 日
欧洲: 1996 年 11 月 10 日
❌ 翻译月份缩写
英语: "Jan", "Feb", "Mar"
法语: "Jan", "Fév", "Mar" ← 破坏互操作性
由于没有日期和时间格式根据所有国家的约定都是可读的, 互联网客户端应该准备将日期转换为适合当地的显示格式。这可能包括将 UTC 转换为本地时间。
5.3. Rarely Used Options (很少使用的选项)
包含很少使用的选项的格式可能会导致互操作性问题。这是因为很少使用的选项不太可能在 alpha 或 beta 测试中使用, 因此不太可能发现解析中的错误。为了互操作性, 应尽可能将很少使用的选项设为强制性或省略。
下面定义的格式仅包含一个很少使用的选项: 秒的小数部分 (Fractions of a Second)。预计这仅由需要严格排序日期/时间戳或具有异常精度要求的应用程序使用。
5.4. Redundant Information (冗余信息)
如果日期/时间格式包含冗余信息, 则引入了冗余信息可能不相关的可能性。例如, 在日期/时间格式中包含星期几会引入星期几不正确但日期正确, 反之亦然的可能性。由于从日期计算星期几并不困难 (参见附录 B), 因此不应在日期/时间格式中包含星期几。
问题示例:
❌ "Monday, 2002-07-16T10:00:00Z"
问题: 2002 年 7 月 16 日实际上是星期二, 不是星期一
如果星期几和日期不匹配, 应该信任哪一个?
✅ "2002-07-16T10:00:00Z"
解决方案: 省略星期几, 需要时计算
5.5. Simplicity (简洁性)
ISO 8601 [ISO8601] 中指定的完整日期和时间格式集相当复杂, 试图提供多种表示和部分表示。附录 A 包含将 ISO 8601 的完整语法转换为 ABNF 的尝试。互联网协议有 somewhat 不同的要求, 简洁性已被证明是一个重要特性。此外, 互联网协议通常需要完整的数据规范才能实现真正的互操作性。因此, ISO 8601 的完整语法被认为对大多数互联网协议来说过于复杂。
以下部分定义了用于互联网的 ISO 8601 配置文件。它是 ISO 8601 扩展格式的一致子集。通过使大多数字段和标点符号成为强制性来实现简洁性。
5.6. Internet Date/Time Format (互联网日期/时间格式)
以下 ISO 8601 [ISO8601] 日期配置文件应该在互联网上的新协议中使用。这是使用 [ABNF] 中定义的语法描述符号指定的。
ABNF 语法定义
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
time-hour = 2DIGIT ; 00-23
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
; rules
time-secfrac = "." 1*DIGIT
time-numoffset = ("+" / "-") time-hour ":" time-minute
time-offset = "Z" / time-numoffset
partial-time = time-hour ":" time-minute ":" time-second
[time-secfrac]
full-date = date-fullyear "-" date-month "-" date-mday
full-time = partial-time time-offset
date-time = full-date "T" full-time
重要说明
大小写敏感性: 根据 [ABNF] 和 ISO8601, 此语法中的 "T" 和 "Z" 字符可以分别替换为小写 "t" 或 "z"。
在大小写重要的环境 (如 XML) 中使用此格式的规范可以进一步限制日期/时间语法, 以便日期/时间语法中使用的字母 'T' 和 'Z' 必须始终为大写。生成此格式的应用程序应该使用大写字母。
分隔符: ISO 8601 定义日期和时间由 "T" 分隔。使用此语法的应用程序可以选择, 为了可读性, 指定由 (例如) 空格字符分隔的完整日期和完整时间。
格式示例
标准格式:
2002-07-15T10:30:00Z
2002-07-15T10:30:00.123Z
2002-07-15T10:30:00+08:00
2002-07-15T10:30:00-04:00
带小数秒:
2002-07-15T10:30:00.123456Z
2002-07-15T10:30:00.52Z
可读变体 (非标准但允许):
2002-07-15 10:30:00Z
2002-07-15t10:30:00z
5.7. Restrictions (限制)
语法元素 date-mday 表示当前月内的日期编号。最大值根据月份和年份而变化:
| 月份编号 | 月份/年份 | 最大 date-mday |
|---|---|---|
| 01 | 一月 | 31 |
| 02 | 二月, 平年 | 28 |
| 02 | 二月, 闰年 | 29 |
| 03 | 三月 | 31 |
| 04 | 四月 | 30 |
| 05 | 五月 | 31 |
| 06 | 六月 | 30 |
| 07 | 七月 | 31 |
| 08 | 八月 | 31 |
| 09 | 九月 | 30 |
| 10 | 十月 | 31 |
| 11 | 十一月 | 30 |
| 12 | 十二月 | 31 |
Leap Seconds (闰秒)
语法元素 time-second 在发生闰秒的月末可能具有值 "60"。闰秒用于使 UTC 接近地球的旋转时间。有关闰秒的更多信息, 请参见附录 D。
闰秒示例:
1990-12-31T23:59:60Z ✅ 有效 (1990 年 12 月 31 日发生闰秒)
1990-12-31T23:59:61Z ❌ 无效 (最大值为 60)
1990-06-15T23:59:60Z ❌ 无效 (闰秒仅在月末)
5.8. Examples (示例)
以下是一些有效的 RFC 3339 日期-时间戳示例:
1985-04-12T23:20:50.52Z
表示: 1985 年 4 月 12 日 23:20:50.52 UTC
1996-12-19T16:39:57-08:00
表示: 1996 年 12 月 19 日 16:39:57 太平洋标准时间 (PST)
等效 UTC: 1996-12-20T00:39:57Z
1990-12-31T23:59:60Z
表示: 1990 年 12 月 31 日的闰秒
1990-12-31T15:59:60-08:00
表示: 1990 年 12 月 31 日 PST 的闰秒
等效 UTC: 1990-12-31T23:59:60Z
1937-01-01T12:00:27.87+00:20
表示: 1937 年 1 月 1 日 12:00:27.87, UTC+00:20
(历史时区示例)
无效示例
❌ 1985-04-12 (缺少时间)
❌ 23:20:50.52Z (缺少日期)
❌ 1985-04-12 23:20:50.52Z (应使用 'T' 而不是空格, 尽管某些实现允许)
❌ 1985-04-32T23:20:50.52Z (无效日期: 4 月没有第 32 天)
❌ 1985-02-29T23:20:50.52Z (无效日期: 1985 年不是闰年)
实现建议: 始终生成标准格式 (使用 'T' 分隔符和大写 'Z'), 但宽松解析 (接受 't', 'z', 以及可能的空格分隔符)。