跳到正文

← 全部工具

给 iOS 和 Mac 开发者的免费工具

Swift DateFormatter 格式测试

输入像 yyyy-MM-dd'T'HH:mm:ssZZZZZ 这样的 dateFormat 格式串,边输入边看到 DateFormatter 在你选的日期、Locale 和时区下到底输出什么。本页会标出真正引发 bug 的写法(该用 yyyy 却写了 YYYY、把 dd 写成 DD、hh 没有配 a、没有设 en_US_POSIX),列出每个符号并给出实时示例,还提供 Swift 代码,合适时附上 ISO8601DateFormatter 或 Date.FormatStyle 的写法。输出已在 Mac 上与 DateFormatter 逐一核对。所有内容都不离开你的浏览器。

字母是字段(yyyy、MM、HH)。其他文字放在单引号里:'T'、'at'。两个单引号('')输出一个单引号。

常用格式

en_US_POSIX 固定使用英文名称、公历,并严格按写下的格式输出,不受用户在“设置”里的选择影响。凡是给程序读的内容(接口、文件名、日志)都用它。

日期

DateFormatter 输出

…

格式检查

这个格式没有已知的陷阱。

Swift 代码

DateFormatter

创建 DateFormatter 很慢:建一个,保存起来(例如放在 static let 里)反复使用。

let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")!
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let text = formatter.string(from: Date())
let parsed = formatter.date(from: text) // nil when the text does not match

ISO8601DateFormatter 与 Date.ISO8601FormatStyle

专为这种格式设计,不需要 Locale。注意:带小数秒时,Date.ISO8601FormatStyle 可能比 DateFormatter 少一毫秒(.123 输出 .122),因为它截断二进制数值而不是四舍五入。

let iso = ISO8601DateFormatter()
iso.formatOptions = [.withInternetDateTime]
iso.timeZone = TimeZone(identifier: "Asia/Shanghai")!
let text = iso.string(from: Date())

// Or, without a formatter object (iOS 15, macOS 12 and later):
let style = Date.ISO8601FormatStyle(timeZoneSeparator: .colon, timeZone: TimeZone(identifier: "Asia/Shanghai")!)
let text2 = Date().formatted(style)

这里每种代码写法都用 swiftc 编译运行过,输出与本页一致。

格式符号

DateFormatter 认识的每个字段,以及它在上面所选日期、Locale 和时区下的输出。点一下格式即可加到你的格式串里。

纪元

纪元:缩写、完整、窄格式。

年

日历年份。yy 是后两位,yyyy 补足四位。用这个。

按周计算的年份,配合 w 使用。在年末和年初几天与 y 不同。

扩展年份:类似 y,但 yy 不会截成两位。

U(干支纪年)和 r(对应的公历年)在公历里都输出普通年份。

季度

季度:数字、补零、缩写、完整、窄格式。

单独使用的季度(独立形式)。

月

月份:3、03,然后是名称。日期里用 MMM 和 MMMM。

单独使用的月份,用于标题。有些语言在日期里会变格,L 给出原形。

周

一年中的第几周。一周从哪天开始、第一周至少几天,取决于 Locale。

一个月中的第几周(某些 Locale 下,月初不满的一周记为 0)。

日

月中的日期。ddd 是 Apple 的扩展:英语里输出序数(7th)。

一年中的第几天,1 到 366。不是月中的日期。

本月第几个这样的星期几(2 = 第二个星期六)。

本地日期的儒略日数。

星期

星期名称:缩写、完整、窄格式、短格式。

从 Locale 的一周第一天算起的星期数字(美国星期日 = 1,德国星期一 = 1),eee 起是名称。

单独使用的星期(独立形式);c 和 cc 是本地数字。

上午、下午与时段

上午或下午:缩写、完整、窄格式。

类似 a,但在语言有对应词时,12:00 会输出“中午”之类的词。

灵活时段:凌晨、早上、下午、晚上。

小时

小时 1-12,需要配 a。

小时 0-23,固定格式通常用它。

小时 0-11,需要配 a。

小时 1-24(午夜是 24)。

分钟

分钟。

秒

秒。

秒的小数部分,按 S 的个数截断。只有毫秒是真实的。

从本地午夜起的毫秒数。

时区

区分夏令时的时区名:EST 和 EDT。Locale 没有短名称时退回为 GMT-5。

Z:-0500;ZZZZ:GMT-05:00;ZZZZZ:-05:00,UTC 时为 Z。

本地化的 GMT 偏移,短格式和长格式。

不区分夏令时的时区名:ET、Eastern Time。

时区标识:短代码、IANA 标识、城市、地区名称。

UTC 时输出 Z 的 ISO 8601 偏移。

从不输出 Z 的 ISO 8601 偏移。

名称(月份、星期、时段、时区名)取自 macOS 26 上 DateFormatter 的实际输出。较早的 iOS 和 macOS 版本使用较早的地区数据,时区名(z、zzzz、v、vvvv、VVVV)和 B 时段在那里可能不同。

它是怎么工作的

  • •格式串按 Apple 的 ICU 库的规则拆分:同一字母连写是一个字段,单引号里的是普通文字,'' 是一个单引号,其他字符原样输出。
  • •每个字段用取自 macOS 26 上 DateFormatter 实际输出的数据来打印:各 Locale 的月份、星期、季度、纪元名称,上午/下午和时段,以及时区名。没有使用浏览器自带的名称,因为 Apple 的名称有些不同(例如“中国大陆时间”,以及用“GMT+0”而不是“GMT”)。
  • •所选时刻的 UTC 偏移来自浏览器的时区规则。周数遵循 DateFormatter 报告的各 Locale 规则:en_US、ja_JP 和 zh_Hans_CN 的一周从星期日开始,de_DE 从星期一开始,第一周至少需要几天也各不相同。
  • •发布前,我们在 Mac 上把 130,800 种组合与 DateFormatter 逐一比对:所有符号和预设,在五个 Locale、三十个日期(YYYY 相关的新年前后、闰日、纽约和柏林的夏令时切换)和四个时区下输出,另外把时区符号放到全部十个时区里核对,结果全部一致。Swift 代码片段也在同一轮检查里编译运行过。
  • •格式检查会列出真正引发 bug 的陷阱:按周计算的年份、一年中的第几天、没有上午/下午的 12 小时制、加引号的 'Z'、不带冒号的时区格式,以及 DateFormatter 拒绝或会崩溃的格式。

它做不到的事

  • •它不是 Apple 的 ICU,而是独立的实现,在测试过的所有情况下都与 macOS 26 上的 DateFormatter 一致。较早的 iOS 和 macOS 版本带的是较早的地区数据,在那些系统上,时区名(z、zzzz、v、vvvv、VVVV)和 B 时段只是近似结果。
  • •只支持五个 Locale 和十个时区,日期范围是 2000 年到 2100 年。其他时区的名称需要先与 Apple 核对,所以暂不提供。
  • •它不模拟设备上的设置。除 en_US_POSIX 外的任何 Locale,用户的 24 小时制开关、日历类型(佛历、和历)或一周的第一天都可能改变真机上 DateFormatter 的输出。
  • •它只做格式化,不做解析。date(from:) 有自己的规则(宽松解析、两位数年份、默认字段),本页不模拟。
  • •时间精确到毫秒。DateFormatter 同样只到毫秒,所以 SSSS 及更长的格式后面都是 0。
  • •对会让 DateFormatter 崩溃的格式(六个或更多 B,或中午时六个或更多 b),本页给出提示,而不是模拟崩溃。

常见问题

为什么 YYYY 显示的年份不对?
YYYY 是“周所属的年份”,不是日历年份,只有和 w(一年中的第几周)一起用才有意义。新年前后的几天,一个日期可能属于下一年的第 1 周或上一年的最后一周:在 en_US 下,2024 年 12 月 31 日用 YYYY 会输出 2025。普通日期请用 yyyy;这个 bug 每年只出现几天,所以常常被带到线上。
为什么要把 Locale 设为 en_US_POSIX?
因为固定格式不能依赖用户。使用设备的 Locale 时,DateFormatter 可能换成佛历或和历(年份变成 2569 而不是 2026)、把名称本地化,或按用户的 24 小时制设置改写小时,解析随之失败。en_US_POSIX 保证不变。Apple 的技术问答 QA1480 建议所有固定格式的日期都这样做,同时也要明确设置格式化器的时区。
怎么解析带小数秒的 ISO 8601 日期?
用 ISO8601DateFormatter,formatOptions 设为 [.withInternetDateTime, .withFractionalSeconds]。这样设置后它会拒绝不带小数的字符串,所以如果接口两种都会发,就准备一个带小数、一个不带小数的格式化器依次尝试。用 DateFormatter 的话,格式是 yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX,配 en_US_POSIX。JSONDecoder 的 .iso8601 策略完全不接受小数秒,请用 .custom 配合格式化器。
用 ISO8601DateFormatter 还是 DateFormatter?
ISO 8601 文本用 ISO8601DateFormatter:没有会被忘记设置的 Locale,UTC 时输出 Z,其他时区输出带冒号的偏移,选项直接说明要哪些部分(.withFullDate、.withInternetDateTime)。其他固定格式,例如 HTTP 日期(EEE, dd MMM yyyy HH:mm:ss 'GMT'),以及用 dateStyle/timeStyle 显示的文字,用 DateFormatter。两者创建都很慢,建一个实例反复使用。
应该改用 Date.FormatStyle 吗?
在 iOS 15、macOS 12 及以上,通常可以。给人看的文字,date.formatted(date: .abbreviated, time: .shortened) 不需要格式串,就会遵循用户的 Locale 和设置。固定格式可以用 Date.ISO8601FormatStyle 和 Date.VerbatimFormatStyle,用有类型的字段代替格式串;只要格式串里每个字段都有完全对应的写法,本页就会给出 verbatim 版本。这些样式值创建起来很便宜。
怎样尊重用户的 12/24 小时制设置?
显示用的时间不要写死 hh:mm a 或 HH:mm。用 timeStyle = .short、Date.FormatStyle,或 setLocalizedDateFormatFromTemplate("jmm"):j 表示按用户选择的小时制显示小时。给数据用的固定格式继续配 en_US_POSIX,那里不应受用户设置影响。

相关工具