ISO8601DateFormatter:关键概念与 ISO 8601 格式化

作者: IT Sectr 发布日期: 2026-07-13 阅读时间: 9 分钟

ISO8601DateFormatter — 是 iOS 和 macOS 中的 Foundation 类,用于在 ISO 8601 国际标准中格式化和解析日期。根据 Apple Developer Documentation, 2024ISO8601DateFormatter 自动处理包含毫秒、时区和秒小数的格式,无需手动设置 DateFormat。与 DateFormatter 不同,此类不依赖于 Locale 和 TimeZone — 它严格遵循 ISO 8601 规范,使其成为服务器和客户端之间日期交换的理想选择。该类从 iOS 10 和 macOS 10.12 起可用。

要点

  • ISO8601DateFormatter — 用于根据 ISO 8601 标准格式化日期的 Foundation 类
  • 不需要 DateFormat — 格式根据选项设置自动确定
  • 独立于区域设置 — 无需配置 Locale,在所有设备上工作相同
  • 支持毫秒 — 处理任意精度的秒小数(三位、六位及更多字符)
  • 格式化选项 — withFullDate、withTime、withMilliseconds、withTimeZone 等控制输出组件

什么是 ISO8601DateFormatter?

ISO8601DateFormatter — 是 Foundation 中 Formatter 的一个专门子类,实现了 Date 和 ISO 8601 格式字符串之间的双向转换。ISO 8601 标准(International Standard for the Representation of Dates and Times)定义了日期和时间交换的国际格式:2024-07-21T14:30:00+00:00。与 DateFormatter 不同,此类不需要指定 dateFormat,并根据给定的选项自动确定字符串的结构。

ISO8601DateFormatter 相对于 DateFormatter 的主要优势:不依赖 locale(解析在任何设备上工作相同)、内置支持秒小数(任意小数位数)以及基于所传递选项自动确定格式。该类还正确处理 Z 后缀(UTC 标记)、+HH:mm 格式的时区和降低的精度(仅日期无时间)。

根据 ISO Specification (ISO 8601-1:2019),该标准支持四个精度级别:年 (2024)、年-月 (2024-07)、完整日期 (2024-07-21) 和带时区的日期-时间 (2024-07-21T14:30:00+00:00)。ISO8601DateFormatter 通过组合格式选项覆盖所有这些级别,使开发人员无需手动构建 dateFormat 字符串。

ISO8601DateFormatter 在 Foundation 中如何工作?

工作原理 ISO8601DateFormatter 基于位选项 (formatOptions) 的组合,每个选项在输出中激活特定的日期或时间组件。例如,.withFullDate 选项激活年、月和日;.withTime — 小时、分钟和秒。通过组合选项,开发人员无需编写 dateFormat 字符串即可获得所需的精度级别。

在内部,ISO8601DateFormatter 使用 ICU 库进行解析,但使用固定的 ISO 8601 规则。这意味着它忽略设备上安装的 Locale 和 TimeZone 设置 — 结果始终可预测。时区通过 timeZone 属性设置,默认为 UTC。如果 timeZone 设置为 nil,则使用设备的本地时间。

选项描述输出示例
.withFullDate年、月、日2024-07-21
.withTime小时、分钟、秒14:30:00
.withMilliseconds秒小数(最多 3 个字符).123
.withFractionalSeconds秒小数(任意精度).123456
.withTimeZone时区+03:00
.withColonSeparatorInTimeZone时区中的冒号分隔符+03:00(代替 +0300)
.withInternetDateTime完整格式 (date + time + tz)2024-07-21T14:30:00+00:00

组合选项: .withInternetDateTime 等同于组合 .withFullDate、.withTime 和 .withTimeZone。要解析带有毫秒的字符串,请添加 .withFractionalSeconds。重要的是要记住,.withMilliseconds 将秒小数限制为三个字符,而 .withFractionalSeconds 支持任意精度 — 从一到九位小数。

ISO 8601 格式设置

格式选项 ISO8601DateFormatter 分为三组:日期组件 (withFullDate、withYear、withMonth、withDay、withWeekOfYear)、时间组件 (withTime、withHours、withMinutes、withSeconds) 和附加设置 (withMilliseconds、withFractionalSeconds、withTimeZone、withColonSeparatorInTimeZone、withDashSeparatorInDate、withFullTime)。通过组合它们,可以获得几乎任何 ISO 8601 子格式。

主要选项组合

  • .withFullDate — 仅日期:2024-07-21。用于解析 YYYY-MM-DD 格式的字符串
  • .withFullDate + .withTime — 无时区的日期和时间:2024-07-21T14:30:00
  • .withInternetDateTime — 完整格式:2024-07-21T14:30:00Z 或 2024-07-21T14:30:00+03:00
  • .withInternetDateTime + .withFractionalSeconds — 带秒小数:2024-07-21T14:30:00.123456+00:00
  • .withFullDate + .withTime + .withTimeZone — tz 中无冒号的完整格式:2024-07-21T14:30:00+0300

重要细节: .withFractionalSeconds 和 .withMilliseconds 互斥 — 如果两者都设置,则应用 .withFractionalSeconds。对于从服务器数据解析毫秒,建议使用 .withFractionalSeconds,因为许多服务器发送三位、六位或九位的秒小数,而 .withFractionalSeconds 处理任意长度。

swift
import Foundation

// 配置 ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)

// 不同的格式选项组合
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("日期:\(dateOnly)")

formatter.formatOptions = [.withFullDate, .withTime]
let dateTime = formatter.string(from: Date())
print("DateTime:\(dateTime)")

formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let full = formatter.string(from: Date())
print("完整:\(full)")

// 解析带毫秒的字符串
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
    print("已解析:\(parsed)")
}

Swift 中的 ISO8601DateFormatter:代码示例

基本用法 ISO8601DateFormatter 的使用归结为创建实例、设置 timeZone(建议服务器数据使用 UTC)和 formatOptions,之后可以调用 string(from:) 进行格式化和 date(from:) 进行解析。与 DateFormatter 不同,无需担心 Locale — 该类忽略区域设置。

swift
import Foundation

let formatter = ISO8601DateFormatter()

// 解析不同的 ISO 8601 格式
let strings: [String] = [
    "2024-07-21T14:30:00Z",
    "2024-07-21T14:30:00+03:00",
    "2024-07-21T14:30:00.123Z",
    "2024-07-21"
]

for str in strings {
    if let autoParsed = formatter.date(from: str) {
        print("已解析 '\(str)':\(autoParsed)")
    } else {
        // 对于仅日期的字符串使用 withFullDate
        formatter.formatOptions = [.withFullDate]
        if let fallback = formatter.date(from: str) {
            print("回退已解析 '\(str)':\(fallback)")
        }
        formatter.formatOptions = [.withInternetDateTime]
    }
}

// 序列化为 RFC 3339(GitHub API)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339:\(rfc3339)")

解析具有可变长度小数秒的日期 — 许多现代 API 的特性。服务器可以发送 2024-07-21T14:30:00.123Z(3 个字符)和 2024-07-21T14:30:00.123456Z(6 个字符)。使用 .withFractionalSeconds 选项的 ISO8601DateFormatter 将正确处理两种变体,而使用 dateFormat = “yyyy-MM-dd’T’HH:mm:ss.SSSZ” 的 DateFormatter 只处理三位数的毫秒。

swift
import Foundation

let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
    .withInternetDateTime,
    .withFractionalSeconds
]

// 不同的小数秒精度
let variants: [String] = [
    "2024-07-21T14:30:00.1Z",
    "2024-07-21T14:30:00.12Z",
    "2024-07-21T14:30:00.123Z",
    "2024-07-21T14:30:00.123456Z",
    "2024-07-21T14:30:00.123456789Z"
]

for variant in variants {
    if let parsed = variantFormatter.date(from: variant) {
        print("OK:\(variant) -> \(parsed)")
    } else {
        print("FAIL:\(variant)")
    }
}

// 使用 withMilliseconds(仅 3 位数字)
variantFormatter.formatOptions = [
    .withInternetDateTime,
    .withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("带毫秒:\(milliParsed)")

测试所有变体的解析: 上述代码表明,使用 .withFractionalSeconds 的 ISO8601DateFormatter 成功处理长度为 1 到 9 个字符的任意秒小数。这对于与不同服务器平台的兼容性很重要:.NET 通常生成 7 个字符(100 纳秒刻度),Python — 6,Java — 取决于版本生成 3 或 9 个字符。

与 DateFormatter 在 ISO 8601 方面的比较

DateFormatter 也可以解析 ISO 8601,但需要手动设置 dateFormat、locale 和 timeZone。主要问题是 DateFormatter 依赖于 Locale,如果未设置 en_US_POSIX,解析可能在具有非标准日期格式地区的用户中出错。ISO8601DateFormatter 在架构层面解决了这个问题:它不使用 Locale。

参数ISO8601DateFormatterDateFormatter
Locale 设置不需要(忽略)en_US_POSIX 必需
DateFormat自动(通过选项)手动格式字符串
小数秒任意精度 (.withFractionalSeconds)固定 SSS
Z 后缀正确处理通过 dateFormat
性能更高(专用)更低(通用)
标准仅 ISO 8601任意格式
iOS 版本iOS 10+iOS 2+

何时使用 DateFormatter: 如果需要在非 ISO 8601 格式中格式化日期(例如,UI 显示 “2024 年 7 月 21 日”)或者需要支持 iOS 9 及更早版本。对于所有与服务器交换日期的任务,请使用 ISO8601DateFormatter — 它更安全、更高效,且需要更少的代码。DateFormatter 用于 ISO 8601 是与区域设置相关的潜在错误来源。

从 DateFormatter 迁移到 ISO8601DateFormatter: 将创建 DateFormatter + 设置 dateFormat + locale + timeZone 替换为创建 ISO8601DateFormatter + 设置 formatOptions + timeZone。字符串解析通过 date(from:) 保持不变。为了向后兼容,可以使用 #available(iOS 10, *) 并回退到 DateFormatter。

解析 ISO 8601 时的常见错误

忘记设置 formatOptions 导致格式化程序使用默认值 — .withInternetDateTime。如果服务器仅发送不带时间的日期 (2024-07-21),解析将返回 nil。始终检查 formatOptions 是否涵盖服务器可能发送的所有格式。对于格式可变的 API,使用不同选项组合进行回退尝试。

混淆 withMilliseconds 和 withFractionalSeconds — 解析带有秒小数的日期时的常见错误。withMilliseconds 期望恰好 3 位小数。如果服务器发送 6 位数字(微秒),使用 withMilliseconds 的解析将失败。使用 .withFractionalSeconds 以兼容任意数量的字符。.withFractionalSeconds 出现在 iOS 13 中;对于更早版本,使用带有 dateFormat 的 DateFormatter。

忽略时区 — 另一个常见问题。如果服务器发送带时区的日期 (+03:00) 且格式化程序设置为 UTC,解析不会失败,但结果将在 UTC 中。开发人员经常期望 Date 保留时区,但 Date 是绝对时间点,不存储时区信息。要正确显示,请单独保存时区或使用具有正确 timeZone 的 ISO8601DateFormatter。

根据 Apple Forum (2024),大约 20% 关于 ISO8601DateFormatter 的问题涉及秒为可选的格式。ISO 8601 标准允许不带秒的格式:2024-07-21T14:30+03:00。带有 .withInternetDateTime 的 ISO8601DateFormatter 不支持此格式 — 解析它需要 DateFormatter 和 dateFormat = “yyyy-MM-dd’T’HH:mmZ”。在使用缩短时间格式的 API 时,必须考虑此限制。

常见问题

什么是 ISO8601DateFormatter?

ISO8601DateFormatter — 用于在 ISO 8601 格式中格式化和解析日期的专门 Foundation 类,从 iOS 10 起可用。无需手动设置 dateFormat 即可自动处理标准格式。

ISO8601DateFormatter 与 DateFormatter 有何不同?

ISO8601DateFormatter 不依赖于 Locale,使用选项代替 dateFormat,并正确处理任意长度的秒小数。DateFormatter 是通用的,但需要手动配置,并且容易出现与区域设置相关的错误。

如何处理可变长度的小数秒?

使用 .withFractionalSeconds 选项 — 支持小数点后 1 到 9 个字符。如果精度可能变化,不要使用 .withMilliseconds。.withFractionalSeconds 从 iOS 13 起可用。

ISO8601DateFormatter 使用什么时区?

默认为 UTC。 要更改,请设置 timeZone 属性。如果 timeZone = nil,则使用设备的本地时间。在解析带有 +HH:MM 格式的显式时区的字符串时,格式化程序会自动考虑它。

为什么解析不带时间的日期返回 nil?

因为默认 formatOptions = .withInternetDateTime, 它期望日期 + 时间 + 时区。要仅解析日期,请设置 formatOptions = [.withFullDate]。要支持两种变体,请使用不同选项进行回退。

总结

  • ISO8601DateFormatter — 用于 ISO 8601 的专门类,比 DateFormatter 更安全、更简单
  • 格式选项 替代手动 dateFormat — 组合 .withFullDate、.withTime、.withTimeZone
  • 不依赖 Locale — 无需 locale 配置,在所有设备上解析相同
  • .withFractionalSeconds 处理任意精度的秒小数(1–9 个字符)
  • DateFormatter 在 ISO 8601 任务中 在性能、安全性和简单性方面处于劣势
  • 选项混淆 — withMilliseconds 和 withFractionalSeconds 不可互换
  • 无秒格式 (2024-07-21T14:30+03:00) 不受支持 — 需要 DateFormatter

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读