务实的答案:输出一个 RFC 3339 形状的字符串

当某个接口说它要一个“ISO 8601 时间戳”时,最稳妥的输出通常长这样:

2024-03-15T14:30:00.123Z

它按从大到小的顺序排列各字段,数字定宽,带秒,也写明了与 UTC 的关系。这个组合人能读懂,在恰当条件下可以直接排序,而且开发者最可能碰到的那些日期时间解析器都认它。

ISO 8601 本身比这一种写法大得多。它支持日历日期、时间、周日期、序数日期、时长、区间和重复区间。RFC 3339 则把这些选择收窄成一种可预期的互联网时间戳。当“合法的 ISO”和“这个 API 认的”不是同一个集合时,这个区别就要命了。

从左往右读懂这个常见时间戳

2024-03-15T14:30:00.123Z 拆成这几段:

组成部分 含义
2024 四位日历年
03 三月,补零
15 当月第几天,补零
分隔符 T 分隔日期和时间
时间 14:30:00 24 小时制的本地钟面读数
小数 .123 可选的小数秒
偏移 Z UTC,等价于 +00:00

那些扩展格式里的标点不是装饰。连字符、冒号、T 和偏移量让约定一目了然,也防止各地的书写习惯改变解释方式。

下面这几个字符串表示的是同一个瞬间:

2024-03-15T14:30:00Z
2024-03-15T09:30:00-05:00
2024-03-15T15:30:00+01:00

如果要把时间戳当纯文本来比较,请先把它们归一到相同的偏移和相同的小数精度。一堆混着 Z、正偏移和不同小数位数的值,不会仅仅因为每一个都长得像 ISO 8601,就自动按时间顺序排好。

偏移量确定的是瞬间,不是时区

-05:00 说的是:此刻显示的钟面比 UTC 慢五小时。它不说明为什么、这块钟在哪儿,也不说下个月这个偏移会不会变。

America/New_York 这样的 IANA 时区是一套规则集,里面装着历史上和已排定的偏移切换,包括夏令时变更。这个区别决定了你到底该存什么:

需求 该存什么
记录事件发生的时刻 瞬间,通常是 UTC 或带偏移的时间戳
在用户所在时区展示事件 瞬间 + 用户的 IANA 时区
夏令时变了之后,会议仍在当地上午 9 点 本地日期时间 + IANA 时区 + 一条消歧策略
保留最初收到的那个偏移 原始字符串,或一个专门的偏移字段

ESTCSTIST 这类时区缩写不适合做交换值:它们有歧义,也不携带切换历史。

ISO 8601 和 RFC 3339 解决的问题不是一个量级

RFC 3339 刻意砍掉了 ISO 8601 的许多可选形式,好让互联网解析器少写点分支。

特性 ISO 8601 家族 RFC 3339 子集
扩展格式日历日期 2024-03-15 必须用这种
基本格式 20240315T143000Z 不允许
与 UTC 的关系 某些表示法里可以省略 必须有偏移或 Z
周日期与序数日期 支持 不支持
小数秒 支持 秒之后可跟
日期时间分隔符 ISO 里有多种语境 文法用 T
主要用途 广义的信息交换 互联网协议时间戳

该 RFC 的基础文法允许小写的 tz,但建议输出大写。它也注明其他规范可以用空格代替 T——可这条注记并不等于承诺任意一个 RFC 3339 解析器都会接受空格。老老实实生成保守的那种形式。

RFC 9557 是在偏移量之外追加时区信息,而不是取代它

RFC 9557 定义了互联网扩展日期时间格式(IXDTF)。它最显眼的特性是方括号时区后缀:

2024-03-15T14:30:00+01:00[Europe/Paris]

这个时间戳依然是靠那个数值偏移锚定到某个瞬间的;方括号里的名字提供的是“巴黎的一天之后”这类运算所需要的规则。当时区规则变更,或者生产方用了过期数据时,这两者是可能互相矛盾的。所以解析器需要一条处理不一致的策略,而不是默默相信自己先读到的那个字段。

RFC 9557 还支持一个关键性标记:

2024-03-15T14:30:00+01:00[!Europe/Paris]

! 告诉接收方:必须理解这条标注。如果标注被标记为关键,就不要随手把方括号里的数据剥掉。

ISO 8601 还能表示日期、时长和区间

不是每个 ISO 8601 值都表示一个瞬间:

类型 示例 含义
日历日期 2024-03-15 只有日期,不含时间和时区
序数日期 2024-075 2024 年的第 75 天
周日期 2024-W11-5 ISO 第 11 周的星期五
时长 P3DT4H30M 三天四小时三十分
区间 2024-03-01/2024-04-01 起点和终点
起点加时长 2024-03-01/P1M 从起点算一个日历月
重复区间 R3/2024-03-01/P1D 三个每日区间

日历意义上的时长,并不总能换算成固定的秒数。P1M 取决于起始日期;而某个时区里的 P1D 在跨越夏令时切换时,实际可能是 23 小时或 25 小时。所以“按瞬间算的时长”和“按日历算的时长”,你得有意识地选一个。

在代码里输出和解析这个窄格式

JavaScript 输出的是毫秒精度的 UTC 字符串:

const nowIso = new Date().toISOString();

const input = "2024-03-15T14:30:00Z";
const parsed = new Date(input);

if (Number.isNaN(parsed.getTime())) {
  throw new RangeError("Invalid timestamp");
}

console.log(parsed.toISOString());
// "2024-03-15T14:30:00.000Z"

Python 可以保留明确的偏移,再归一到 UTC:

from datetime import datetime, timezone

value = datetime.fromisoformat("2024-03-15T09:30:00-05:00")
utc_value = value.astimezone(timezone.utc)

print(utc_value.isoformat().replace("+00:00", "Z"))
# 2024-03-15T14:30:00Z

库的名字里带着“ISO”,不代表它覆盖了同样的文法范围。请拿你实际要对接的那个边界,去逐一试这些格式:

  • 扩展年份
  • 闰秒写法
  • 降低精度的形式
  • 方括号标注
  • 超过毫秒的小数精度

常见的互操作翻车现场

  • 值本该标识一个瞬间,却漏掉了 Z 或偏移量
  • 接受了一个本地时间,然后悄悄按服务器时区去解释它
  • 想当然地认为 +00:00Z 和 RFC 9557 更新后的 -00:00 语义在任何约定下都可以互换
  • 把一个偏移量当成了永久的 IANA 时区
  • 输出 +0000,而接收方要求的是 RFC 3339 那种带冒号的 +00:00
  • 拿偏移不同、小数位数也不同的字符串做比较,好像字典序就等于时间序
  • 截断小数秒,却没写明到底是四舍五入还是直接截断
  • 明明只接受 RFC 3339 时间戳,却对外宣称“全面支持 ISO 8601”

真正有用的 API 约定不是一句“发 ISO 过来”,而更接近于:“请发 RFC 3339 时间戳,大写的 TZ,只用 UTC,小数最多三位。”越具体的约定越好测,也越不容易被误解。

官方参考与延伸阅读

Frequent questions:

Q: 「2024-03-15 14:30:00Z」是合法的 RFC 3339 吗?
A: RFC 3339 的文法在日期和时间之间用的是 T。规范里有一条注记允许下游规范放行空格之类的其他分隔符,但这种支持并不可移植。除非接收方的约定明确说了可以,否则请输出 2024-03-15T14:30:00Z。
Q: 结尾的 Z 是什么意思?
A: Z 表示 UTC 偏移为 +00:00。因为通话字母表里字母 Z 读作 Zulu,所以它也常被念成「Zulu」。
Q: JavaScript 里怎么解析 ISO 风格的时间戳?
A: new Date("2024-03-15T14:30:00Z") 能解析常见的 RFC 3339 风格写法。记得检查是否得到了 Invalid Date,也别想当然地以为 Date.parse() 能吃下 ISO 8601 的每一种变体。
Q: ISO 8601 和 RFC 3339 是一回事吗?
A: 不是。RFC 3339 定义的是一个刻意做小的互联网子集。ISO 8601 还涵盖基本格式日期、周日期、序数日期、时长、区间等等,这些 RFC 3339 都不接受。
Q: RFC 9557 是什么?
A: RFC 9557 定义了「互联网扩展日期时间格式」,它可以在方括号里追加信息,比如 IANA 时区:2024-03-15T14:30:00+01:00[Europe/Paris]。偏移量负责确定瞬间,方括号里的时区名则提供本地时间运算所需的规则。
Q: T 这个分隔符是必须的吗?
A: 要输出可互操作的机器格式,就用 T。RFC 3339 的文法要求它,尽管该 RFC 也注明其他规范可以为了可读性允许空格。ISO 8601 另有基本格式和约定格式,但 API 很少全都支持。
Q: ISO 8601-1 和 ISO 8601-2 有什么区别?
A: ISO 8601-1 定义日历日期和 24 小时制时间的基本规则。ISO 8601-2 追加了扩展能力,比如不确定或近似日期、扩展区间、日期集合、重复规则和日期时间算术。