Swift 中的高级日期和时间处理:从基础到优化
\n日期和时间处理是 iOS/macOS 开发中最常见也最棘手的任务之一。Foundation API(Date、DateFormatter、Calendar)看似简单,却常常导致与时区、本地化或性能相关的错误。本文将深入探讨 Swift 中日期处理的高级技巧:从规范的解析到高性能计算,这些技巧将在生产项目中大有用处。
\n\n1. DateFormatter:精细配置与缓存
\n新手最常见的错误是在循环或频繁调用的方法中创建新的 DateFormatter 对象。创建 DateFormatter 是一项昂贵的操作(比重复使用它慢多达 1000 倍)。
\n\n// ❌ 不好:每次都创建 formatter\nfunc parseDate(from string: String) -> Date? {\n let formatter = DateFormatter()\n formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"\n formatter.locale = Locale(identifier: "ru_RU")\n formatter.timeZone = TimeZone(secondsFromGMT: 0)\n return formatter.date(from: string)\n}\n\n// ✅ 好:缓存 formatter\nprivate let isoDateFormatter: DateFormatter = {\n let f = DateFormatter()\n f.dateFormat = "yyyy-MM-dd HH:mm:ss"\n f.locale = Locale(identifier: "ru_RU")\n f.timeZone = TimeZone(secondsFromGMT: 0)\n return f\n}()\n\nfunc parseDateFast(from string: String) -> Date? {\n return isoDateFormatter.date(from: string)\n}\n\n高级建议: 对于固定格式(例如 ISO 8601),请使用 ISO8601DateFormatter——它比标准的 DateFormatter 更快。对于需要高性能的自定义格式,请考虑使用带有线程本地存储(thread-local storage)的 DateFormatter:
extension DateFormatter {\n static let threadLocal: ThreadLocal<DateFormatter> = ThreadLocal {\n let f = DateFormatter()\n f.dateFormat = "dd.MM.yyyy"\n f.locale = Locale(identifier: "ru_RU")\n return f\n }\n}\n\n2. Calendar 和 DateComponents:无差错计算
\n对于日期运算(例如添加一天、查找月初),请使用 Calendar,而不是手动添加秒数。手动添加 86400 秒(24*60*60)会在夏令时切换时出错。
let calendar = Calendar.current\nlet now = Date()\n\n// ✅ 安全地添加 1 天\nif let tomorrow = calendar.date(byAdding: .day, value: 1, to: now) {\n print("明天:\\(tomorrow)")\n}\n\n// ✅ 查找当前月的第一天\nlet startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: now))!\n\n// ✅ 获取考虑本地化的组件\nlet components = calendar.dateComponents([.weekday, .weekOfYear], from: now)\nprint("星期几:\\(components.weekday ?? 0)") // 1 = 美国周日,2 = 俄罗斯周一\n\n高级建议: 对于高负载计算(例如生成日历网格),请缓存 Calendar 和 TimeZone。避免在循环中使用 Calendar.current——它每次都会读取系统设置。
3. 面向用户的本地化和格式化
\n切勿向用户显示原始日期。请使用带有预定义样式(.short、.medium、.long、.full)的 DateFormatter 和用于人类可读相对日期(例如“昨天”、“3 天前”)的 RelativeDateTimeFormatter。
// 考虑用户本地化的绝对日期\nlet userFormatter = DateFormatter()\nuserFormatter.dateStyle = .long\nuserFormatter.timeStyle = .short\nprint(userFormatter.string(from: Date())) // "2025年6月15日 14:30"(对于 zh_CN)\n\n// 相对日期(iOS 13+)\nlet relativeFormatter = RelativeDateTimeFormatter()\nrelat