ISO8601DateFormatter — 是 iOS 和 macOS 中的 Foundation 类,用于在 ISO 8601 国际标准中格式化和解析日期。根据 Apple Developer Documentation, 2024,ISO8601DateFormatter 自动处理包含毫秒、时区和秒小数的格式,无需手动设置 DateFormat。与 DateFormatter 不同,此类不依赖于 Locale 和 TimeZone — 它严格遵循 ISO 8601 规范,使其成为服务器和客户端之间日期交换的理想选择。该类从 iOS 10 和 macOS 10.12 起可用。
要点
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 基于位选项 (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 支持任意精度 — 从一到九位小数。
格式选项 ISO8601DateFormatter 分为三组:日期组件 (withFullDate、withYear、withMonth、withDay、withWeekOfYear)、时间组件 (withTime、withHours、withMinutes、withSeconds) 和附加设置 (withMilliseconds、withFractionalSeconds、withTimeZone、withColonSeparatorInTimeZone、withDashSeparatorInDate、withFullTime)。通过组合它们,可以获得几乎任何 ISO 8601 子格式。
重要细节: .withFractionalSeconds 和 .withMilliseconds 互斥 — 如果两者都设置,则应用 .withFractionalSeconds。对于从服务器数据解析毫秒,建议使用 .withFractionalSeconds,因为许多服务器发送三位、六位或九位的秒小数,而 .withFractionalSeconds 处理任意长度。
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)")
}
基本用法 ISO8601DateFormatter 的使用归结为创建实例、设置 timeZone(建议服务器数据使用 UTC)和 formatOptions,之后可以调用 string(from:) 进行格式化和 date(from:) 进行解析。与 DateFormatter 不同,无需担心 Locale — 该类忽略区域设置。
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 只处理三位数的毫秒。
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,但需要手动设置 dateFormat、locale 和 timeZone。主要问题是 DateFormatter 依赖于 Locale,如果未设置 en_US_POSIX,解析可能在具有非标准日期格式地区的用户中出错。ISO8601DateFormatter 在架构层面解决了这个问题:它不使用 Locale。
| 参数 | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| 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。
忘记设置 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 — 用于在 ISO 8601 格式中格式化和解析日期的专门 Foundation 类,从 iOS 10 起可用。无需手动设置 dateFormat 即可自动处理标准格式。
ISO8601DateFormatter 不依赖于 Locale,使用选项代替 dateFormat,并正确处理任意长度的秒小数。DateFormatter 是通用的,但需要手动配置,并且容易出现与区域设置相关的错误。
使用 .withFractionalSeconds 选项 — 支持小数点后 1 到 9 个字符。如果精度可能变化,不要使用 .withMilliseconds。.withFractionalSeconds 从 iOS 13 起可用。
默认为 UTC。 要更改,请设置 timeZone 属性。如果 timeZone = nil,则使用设备的本地时间。在解析带有 +HH:MM 格式的显式时区的字符串时,格式化程序会自动考虑它。
因为默认 formatOptions = .withInternetDateTime, 它期望日期 + 时间 + 时区。要仅解析日期,请设置 formatOptions = [.withFullDate]。要支持两种变体,请使用不同选项进行回退。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。