DateIntervalFormatter 是 iOS 和 macOS 中的 Foundation 类,用于将日期范围格式化为本地化字符串:"2024年7月21–25日"、"从2024年7月21日到25日"或"Jul 21–25, 2024"。根据 Apple Developer Documentation, 2024,DateIntervalFormatter 自动确定哪些日期组件需要重复(如果范围在同一个月内——月份只显示一次)并本地化分隔符和词序。与手动拼接两个格式化日期不同,此类考虑语言的语法规则:中文使用"到",英语使用不带介词的连字符。此类自 iOS 8 和 macOS 10.10 起可用。
要点
DateIntervalFormatter 是 Foundation 中 Formatter 的子类,它接收 DateInterval 对象(两个日期之间的范围)并返回带有此范围描述的本地化字符串。例如,对于2024年7月21日至25日的范围,结果将是中文的"2024年7月21–25日"和英语的"Jul 21–25, 2024"。此类智能地分组重复元素:如果两个日期在同一个月,月份名称只显示一次。
DateIntervalFormatter 的主要价值是自动处理范围的语法。在手动格式化时,开发者需要比较开始和结束的月份/年份,选择分隔符(连字符、"从–到"、"to"、"bis")并监控变格。DateIntervalFormatter 基于 CLDR(通用区域数据仓库)数据自动完成此操作,从而减少国际化应用程序中的错误数量,特别是对于语法复杂的语言。
根据 Unicode CLDR v44 (2024),日期范围格式化规则在不同语言之间差异显著。德语使用介词"bis"(21.–25. Juli 2024),法语使用"au"(du 21 au 25 juillet 2024),日语使用连字符(2024 Jul 21–25)。DateIntervalFormatter 无需开发者编写额外代码即可覆盖所有这些变化。
工作原理 DateIntervalFormatter 基于对构成范围的两个日期的分析以及考虑 locale 的最佳文本表示的选择。此类比较日期组件(年、月、日)并就分组做出决定:如果年份相同——显示一次;如果月份相同——显示一次;如果天数不同——显示天数范围。
| 范围 | zh_CN | en_US |
|---|---|---|
| 一天 | 2024年7月21日 | Jul 21, 2024 |
| 同月内 | 2024年7月21–25日 | Jul 21–25, 2024 |
| 不同月份 | 2024年7月21日 – 8月5日 | Jul 21 – Aug 5, 2024 |
| 不同年份 | 2024年12月25日 – 2025年1月5日 | Dec 25, 2024 – Jan 5, 2025 |
| 带有时间 | 2024年7月21日, 14:30–16:45 | Jul 21, 2024, 2:30–4:45 PM |
分组逻辑 基于 CLDR 模式实现。格式化器按从大到小的顺序检查每个日期组件(Calendar.Component: year, month, day)的相等性。一旦某个组件不同,所有较小的组件都会为两个日期显示。例如,7月21–25日:年份匹配,月份匹配,天数不同 → 月份一次,天数——每个日期分别显示。
重要细微差别: DateIntervalFormatter 不支持通过 API 配置分隔符。分隔符由 locale 确定且无法覆盖。如果需要自定义分隔符(例如,使用"至"代替"–"),您需要通过 DateFormatter 手动格式化日期并使用自定义分隔符连接字符串。
DateIntervalFormatter 提供两种样式集:dateStyle 和 timeStyle — 用于范围的每一端。这些样式与 DateFormatter 类似:.none 隐藏组件,.short 提供短格式(21.07.2024),.medium — 中等(2024年7月21日),.long — 长格式(2024年7月21日),.full — 最大详细程度(星期日,2024年7月21日)。通过组合 dateStyle 和 timeStyle,可以获得不同的详细级别。
重要: dateStyle 和 timeStyle 同等地应用于范围的两端。不能为开始和结束设置不同的样式(例如,开始使用 .medium,结束使用 .short)。如果需要不同的样式,您必须使用单独的 DateFormatter 格式化每个日期并连接结果。
import Foundation
let formatter = DateIntervalFormatter()
// 不同样式组合
let startDate = Date()
let endDate = Date().addingTimeInterval(345600) // +4天
let interval = DateInterval(start: startDate, end: endDate)
// 仅日期,中等格式
formatter.dateStyle = .medium
formatter.timeStyle = .none
print("仅日期:\(formatter.string(from: interval))")
// 带时间的短格式
formatter.dateStyle = .short
formatter.timeStyle = .short
print("带时间的短格式:\(formatter.string(from: interval))")
// 完整格式
formatter.dateStyle = .full
formatter.timeStyle = .none
print("完整日期:\(formatter.string(from: interval))")
基本使用 DateIntervalFormatter 包括创建实例、配置样式和调用 string(from:)。该方法接受 DateInterval — 一个具有 start 和 end 属性的结构。DateInterval 可以从两个 Date 创建,也可以使用 Duration 初始化(iOS 16+)。格式化后,结果即可在 UI 中显示,无需额外处理。
import Foundation
let calendar = Calendar.current
let now = Date()
// 特定间隔示例
let examples: [(title: String, start: Date, end: Date)] = [
(
"2-hour event",
now,
now.addingTimeInterval(7200)
),
(
"Week-long vacation",
now,
now.addingTimeInterval(604800)
),
(
"Cross-year period",
calendar.date(from: DateComponents(
year: 2024, month: 12, day: 25
))!,
calendar.date(from: DateComponents(
year: 2025, month: 1, day: 5
))!
)
]
let intervalFormatter = DateIntervalFormatter()
intervalFormatter.dateStyle = .medium
intervalFormatter.timeStyle = .short
for (title, start, end) in examples {
let interval = DateInterval(start: start, end: end)
let result = intervalFormatter.string(from: interval)
print("\(title): \(result)")
}
// 通过 Calendar 格式化 (iOS 16+)
if let nextWeek = calendar.date(
byAdding: .day, value: 7, to: now
) {
let weekInterval = DateInterval(start: now, end: nextWeek)
print("接下来7天:\(intervalFormatter.string(from: weekInterval))")
}
DateIntervalFormatter 和 Calendar Booking: 在预订应用(酒店、门票)中,日期范围是 UI 的关键元素。DateIntervalFormatter 正确处理跨年范围(例如,从12月28日到1月3日),为每个日期分别分组月份和年份。对于此类场景,建议使用 dateStyle = .medium 和 timeStyle = .none — 对于没有时间的日期,这是最可读的格式。
import Foundation
let formatter = DateIntervalFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
// 预订示例
struct Booking {
let checkIn: Date
let checkOut: Date
var formattedInterval: String {
let interval = DateInterval(start: checkIn, end: checkOut)
return formatter.string(from: interval)
}
}
let booking = Booking(
checkIn: calendar.date(from: DateComponents(
year: 2024, month: 7, day: 21
))!,
checkOut: calendar.date(from: DateComponents(
year: 2024, month: 7, day: 25
))!
)
print("预订:\(booking.formattedInterval)")
// 使用不同区域设置
let enFormatter = DateIntervalFormatter()
enFormatter.dateStyle = .medium
enFormatter.timeStyle = .none
enFormatter.locale = Locale(identifier: "en_US")
print("英语:\(enFormatter.string(from: interval))")
DateInterval 与 DateIntervalFormatter: DateInterval — 表示日期范围的结构(开始和结束)。DateIntervalFormatter — 将此结构转换为字符串的格式化器。DateInterval 还支持 contains(Date) 和 intersection(with:) 检查,这对于预订逻辑(检查日期重叠)很有用。
DateIntervalFormatter 通过 locale 属性本地化输出。默认使用 Locale.current — 设备的区域设置。要以其他语言显示间隔(例如,中文界面中的英文内容),请显式设置 locale。与 DateFormatter 不同,DateIntervalFormatter 不需要为服务器数据设置 Locale — 它始终使用自己基于 CLDR 的内部分组逻辑。
日期范围格式化的语言差异比单个日期复杂得多。英语使用简单连字符:Jul 21–25。中文使用"从–到":从7月21日到25日。法语使用"au":du 21 au 25 juillet。德语使用数字后加点加连字符:21.–25. Juli。日语使用不带介词的连字符:Jul 21–25。DateIntervalFormatter 覆盖所有这些变体。
import Foundation
let formatter = DateIntervalFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
let start = Date()
let end = Date().addingTimeInterval(259200) // +3天
let interval = DateInterval(start: start, end: end)
// 展示不同区域设置
let localeIds = ["ru_RU", "en_US", "de_DE", "fr_FR", "ja_JP", "zh_CN"]
for localeId in localeIds {
formatter.locale = Locale(identifier: localeId)
print("\(localeId): \(formatter.string(from: interval))")
}
// 时间间隔的本地化
formatter.dateStyle = .none
formatter.timeStyle = .short
let todayStart = Date()
let todayEnd = Date().addingTimeInterval(3600)
let timeInterval = DateInterval(start: todayStart, end: todayEnd)
for localeId in ["ru_RU", "en_US", "de_DE"] {
formatter.locale = Locale(identifier: localeId)
print("\(localeId)时间:\(formatter.string(from: timeInterval))")
}
本地化特点: 对于中文,DateIntervalFormatter 在长日期格式中使用"从"和"到"介词。对于短格式 (short) — 仅连字符:21.07–25.07.2024。对于完整格式 (full) — 带有星期几。格式的选择取决于上下文:对于预订列表使用 .medium,对于详细视图使用 .long 或 .full。
DateIntervalFormatter 填补了其他 Foundation 类未覆盖的日期范围格式化的领域。DateFormatter 格式化单个日期,RelativeDateTimeFormatter 格式化相对日期(之前/之后),DateComponentsFormatter 格式化持续时间(2小时30分钟)。这些类各自解决自己的任务,不能互换使用。
| 类 | 用途 | 输出示例 |
|---|---|---|
| DateIntervalFormatter | 日期范围 | 2024年7月21–25日 |
| DateFormatter | 单个日期 | 2024年7月21日 |
| RelativeDateTimeFormatter | 相对时间 | 3天前 |
| DateComponentsFormatter | 持续时间 | 2小时30分钟 |
| ISO8601DateFormatter | ISO 8601 格式 | 2024-07-21T14:30:00Z |
何时使用 DateIntervalFormatter: 用于在 UI 中显示日期范围 — 酒店预订、活动、按日期筛选、操作历史。要计算日期之间的持续时间,请使用 DateComponentsFormatter,而不是 DateIntervalFormatter — 后者不用于显示持续时间。DateFormatter 应仅用于单个日期,而不是通过字符串拼接来连接两个格式化日期 — 这会导致分隔符本地化的丢失。
边缘情况: DateIntervalFormatter 不支持显示开放范围(无结束日期)。如果范围结束未知(例如,"从7月21日起"),对于中文请使用带有"从"介词的 DateFormatter。DateIntervalFormatter 也不支持为不同日期指定时间的范围 — 时间分别应用于每个日期,对于短范围这可能显得多余。
常见问题
DateIntervalFormatter — 用于将日期范围格式化为本地化字符串的 Foundation 类。自 iOS 8 起可用,自动分组重复组件(月、年)并为语言选择正确的分隔符。
比较组件 从年到日。如果年份匹配 — 显示一次。如果月份匹配 — 显示一次。不同的天显示为范围。这给出"2024年7月21–25日"而不是"2024年7月21日 – 2024年7月25日"。
不能,分隔符由 locale 确定且不能通过 API 配置。对于自定义分隔符(例如,使用"至"代替"–"),使用单独的 DateFormatter 格式化每个日期并手动连接字符串。
DateIntervalFormatter 将日期范围(两个日期)格式化为一个带有组件分组的整体。DateFormatter 格式化单个日期。对于范围,始终使用 DateIntervalFormatter — 这能确保分隔符的正确本地化。
自动 — DateIntervalFormatter 检测到年份不同并为两端显示完整日期:"2024年12月25日 – 2025年1月5日"。无需额外设置,只需设置 dateStyle 即可。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。