Swift日期时间类Calendar
Swift日期时间类Calendar

Swift日期时间类Calendar

Calendar 是 Foundation 框架中的一个类,用于表示一个日期时间系统,例如公历(Gregorian)、农历(Chinese Lunar)、伊斯兰历(Islamic)等。

它主要用于管理时间和日期的操作,例如日期的计算、提取日期组件、比较日期等。

基本用法

1、初始化日历

默认使用当前用户设备设置的日历:

let calendar = Calendar.current // 当前日历快照(非自动更新)
let auto = Calendar.autoupdatingCurrent  // 会随系统设置变化自动更新

2、指定类型的日历

如果需要特定类型的日历(例如公历或农历),可以直接指定:

let gregorianCalendar = Calendar(identifier: .gregorian)    // 公历
let chineseCalendar = Calendar(identifier: .chinese)    // 农历
let islamicCalendar = Calendar(identifier: .islamic)    // 伊斯兰历

常见的identifier:.gregorian:公历、.buddhist:佛教日历、.iso8601:ISO 8601 标准日历、.japanese:日本日历、.hebrew:犹太日历等日历。

常用属性

1、identifier:日历的标识符,表示该 Calendar 实例使用的是哪种日历系统。

2、locale:语言/本地化(可选),例如”en_CN”。

3、timeZone:当前日历使用的时区。比如,UTC、Asia/Shanghai 等。

4、firstWeekday:一周的第一天(1 通常表示周日)。

5、minimumDaysInFirstWeek:第一周至少包含几天(用于 weekOfYear)。

常用方法

1、判断日期、获取区间或起点

1、startOfDay(for:):获取某个日期的开始时间(零点)。

let startDate = Date()  // 2025年12月10日 10: 40
var calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: Date())
print(startOfDay.formatted())  // 输出 2025-12-10 00:00:00

2、dateInterval(of:for:):获取某个时间单位(如一周、一月)的起始和结束时间。

let startDate = Date()  // 2025年12月10日 10: 40
var calendar = Calendar.currentC
let interval = calendar.dateInterval(of: .month, for: Date())!
print(interval.start, interval.end) // 输出 2025-12-01 2026-01-01

3、nextWeekend(startingAfter:):返回下一个周末的时间区间。

if let weekend = Calendar.current.nextWeekend(startingAfter: Date()) {
    print(weekend.start) // 周五或周六的起点, 2025-12-13
    print(weekend.end)   // 周日结束, 2025-12-15
}

4、isDate(_:inSameDayAs:):判断两个日期是否在同一天。

let startDate = Date()  // 2025年12月10日 12: 30
let calendar = Calendar.current
let endDate = calendar.date(byAdding: .hour, value: 12, to: startDate)!   // 计算12小时后的日期
let isSameDay = calendar.isDate(Date(), inSameDayAs: endDate)
print(isSameDay)  // 输出 false

5、isDate(_:equalTo:toGranularity):判断两个日期在某个精度下是否相等。

let date1 = Date()
let date2 = Calendar.current.date(byAdding: .hour, value: 2, to: date1)!
Calendar.current.isDate(date1, equalTo: date2, toGranularity: .day) // true,如果同一天

6、isDateInToday(_:)、isDateInTomorrow(_:)、isDateInYesterday(_:)、isDateInWeekend(_:):判断某个日期是否是今天/明天/昨天/周末。

Calendar.current.isDateInToday(Date())       // true
Calendar.current.isDateInTomorrow(Date())    // false
Calendar.current.isDateInYesterday(Date())   // false
Calendar.current.isDateInYesterday(Date())   // false

2、组合日期时间

1、date(from:):从 DateComponents 创建一个 Date(可选)。如果 DateComponents 是有效的日期成分(比如正确的年份、月份、日期),它会返回一个合法的 Date。

var calendar = Calendar.current
let components = DateComponents(year: 2025, month: 1, day: 1)
if let date = calendar.date(from: components) {
    print(date) // 2025-01-01 00:00:00
}

如果缺失关键字段,则使用默认值补齐(0001-01-01 00:00:00 +0000)。

3、提取时间

1、component(_:from:):提取某个日期的特定组件,例如年、月、日、小时等。

let calendar = Calendar.current 
let components = calendar.component(.day, from: Date()) // 2025年2月12日
print(components)  // 输出 12

component返回的是具体的组件,通常为Int类型。

常用枚举值:.year, .month, .day, .hour等枚举,更多枚举值请见《Swift日期组件DateComponents》的结构体部分。

2、dateComponents(_:from):从Date中获取DateComponents组件,提取多个日期组件,例如同时获取年、月、日。

let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: Date())   // 2025年2月12日
print(components.year, components.month, components.day)    // 输出 可选类型(2025) 可选类型(2) 可选类型(12)

4、计算时间序号/差额

1、dateComponents(_:from:to:):计算两个日期的差值,返回差值的DateComponents,包含年、月、日等信息。

let startDate = Date()  // 2025年2月12日
let calendar = Calendar.current
let endDate = calendar.date(byAdding: .day, value: 5, to: startDate)!   // 计算5天后的日期
let components = calendar.dateComponents([.day], from: startDate, to: endDate)  // 计算两个日期之间的差异,按照 .day 进行计算
print(components.day)  // 输出 5

常用于倒计时。

2、ordinality(of:in:for):在时间单位内的组件序号,例如今天是今年的第多少天。

let n = calendar.ordinality(of: .day, in: .year, for: Date())
print(n)  // 如 343,表示当年的第 343 天

5、计算范围

1、range(of: in: for:):获取某个时间单位在一个范围内的合法取值。

let date = DateComponents(calendar: .current, year: 2025, month: 12, day: 10).date!
let calendar = Calendar.current

let r = calendar.range(of: .day, in: .month, for: date)
print(r)   // 1..<32(说明该月有 31 天)

2、maximumRange (of:):该组件在任何情况下最大可能范围。

calendar.maximumRange(of: .day) // 1..<32(最多有 31 天)
calendar.maximumRange(of: .hour) // 0..<24
calendar.maximumRange(of: .month) // 1..<13

3、minimumRange(of:):该组件在任何情况下最小可能范围。

calendar.minimumRange(of: .day) // 1..<29(2月最少28天)
calendar.minimumRange(of: .hour) // 0..<24

6、加减时间

1、date(byAdding:to:):DateComponents对日期进行加减操作,例如增加天数、月份等,返回一个新的 Date。

let calendar = Calendar.current 	// 2025年12月10日 加 1 个月
let newDate = calendar.date(byAdding: DateComponents(month: 1), to: Date())  // 2025年12月10日 加 1 个月
print(newDate)  // 输出 2026-01-10

2、date(byAdding:to:wrappingComponents):DateComponents对日期进行加减操作,例如增加天数、月份等,返回一个新的 Date,wrappingComponents表示是否溢出进位到更高组件。

let calendar = Calendar.current     // 2025年12月10日
let newDate = cal.date(byAdding: DateComponents(month: 1), to: Date(),wrappingComponents:true)  // 2025年12月10日 加 1 个月
print(newDate)  // 输出 2025-01-10

wrappingComponents为true,表示不进位最高组件,年份仍然保持2025年,默认值为false。

3、date(byAdding:value:to:):对日期进行加减操作(不使用DateComponents),返回一个Date。

let calendar = Calendar.current     // 2025年12月10日
let now = Date()
let in3Days = calendar.date(byAdding: .day, value: 3, to: now)  // 2025年12月13日
let minus2Hours = calendar.date(byAdding: .weekOfYear, value: -2, to: now)  // 2025年11月26日

7、设置时间

1、date(bySetting:value:of:):根据指定的日期成分(如年、月、日等)设置新的日期。

let now = Date()  // 2025年2月12日 10: 40
var calendar = Calendar.current
let newDate = calendar.date(bySetting: .day, value: 1, of: now)  // 设置日期为本月的 1 日
print(newDate)  // 可选类型 2036年2月1日 00:00:00

如果当前时间已经没有“1日”(不存在的情况),会自动跳到下一个时间点。

2、date(bySettingHour:,minute,second,of:):设置指定的是分秒,返回对应Date。

let today = Date()
let atNine = cal.date(bySettingHour: 9, minute: 0, second: 0, of: today)
// 结果:今天 09:00:00(同一时区语义)

8、匹配上/下一个日期

1、nextDate(after:matching:matchingPolicy:repeatedTimePolicy:direction):从时间点之后开始搜索新的时间点。

let nextDate = calendar.nextDate(after: now,
    matching: DateComponents(hour: 9),
    matchingPolicy: .nextTime,
    repeatedTimePolicy: .first,
    direction: .forward)

表示查找下一个9点的时间。

2、enumerateDates(staringAfter:matching:matchingPolicy:repeatedTimePolicy:direction:using):连续枚举匹配的日期,每次回调一个匹配的Date,直到stop停止或枚举完成。

var cal = Calendar.current     // 2025年12月10日
cal.enumerateDates(
    startingAfter: now,
    matching: DateComponents(hour: 9),
    matchingPolicy: .nextTime,
    repeatedTimePolicy: .first,
    direction: .forward
) { date, exactMatch, stop in
    if let date = date, cal.component(.day, from: date) > 20 {
        stop = true  // 超过 20 号就停止
    } else {
        print(date?.formatted())
    }
}

matching、matchingPolicy、repeatedTimePolicy、direction等参数解释,请见底部的扩展知识。

9、其他方法

1、.minimumDaysInFirstWeek:定义了一个年份中第一周最少要有几天。某些国家的日历可能在一年的开始处会有不完整的一周,因此可以使用此属性进行配置。

例如:

ISO 8601 标准(如大多数欧洲国家)规定第一周至少包含 4 天,否则就属于上一年。

美国日历可能认为第一周至少包含 1 天。

这个属性会影响 weekOfYear 的计算方式,例如:

如果 minimumDaysInFirstWeek = 4,那么如果 1 月 1 日是周五,该周会算作 前一年的最后一周。

如果 minimumDaysInFirstWeek = 1,那么 1 月 1 日就会被认为是 新年的第一周。

1)查看当前 Calendar 的 minimumDaysInFirstWeek:

let now = Date()  // 2025年2月12日 10: 40
var calendar = Calendar.current
calendar.timeZone = TimeZone(abbreviation: "UTC")!  // 设置时区为UTC
print("当前日历的最小第一周天数: \(calendar.minimumDaysInFirstWeek)")   // 当前日历的最小第一周天数: 1

2)更改 minimumDaysInFirstWeek:

let now = Date()  // 2025年2月12日 10: 40
var calendar = Calendar(identifier: .gregorian)
print("自定义日历的最小第一周天数: \(calendar.minimumDaysInFirstWeek)")  // 自定义日历的最小第一周天数: 1
calendar.minimumDaysInFirstWeek = 2 // 设定第一周至少包含 1 天
print("自定义日历的最小第一周天数: \(calendar.minimumDaysInFirstWeek)")  // 自定义日历的最小第一周天数: 2

注意事项

weekday 的范围是 1…7,且 1 始终代表 Sunday(数值与 firstWeekday 不同)。

firstWeekday 只影响 weekOfMonth / weekOfYear 的划分,不改变 weekday 的数值含义。

date(from:) 受 calendar.timeZone 与 calendar.locale 影响(以及闰秒/夏令时)。

使用 nextDate(…matching:) 时要了解 matchingPolicy 与 repeatedTimePolicy(处理 DST 重复/缺失时间)。

dateComponents(_:from:to:) 计算的“差值”按 Calendar 语义返回(例如跨月、跨年会按月/年整除)。

Calendar 是值类型(struct),但内部可能持有引用式存储;复制开销小。

使用 Calendar.autoupdatingCurrent 可自动跟随系统语言/时区变化;但若需要确定性,使用 Calendar(identifier:) 或 Calendar.current 的快照。

如果按周加减,使用weekOfYear,而不是week。

总结

Calendar 是日期和时间管理的核心工具,支持复杂的日期操作和计算。

它结合了 Date 和 DateComponents,实现日期组件的提取、计算日期之间的差异、操作日期(如添加天数、月份等)。

默认是公历(Gregorian),但可以配置其他历法。

相关文章

1、Swift日期组件DateComponents:https://fangjunyu.com/2024/12/14/swift%e6%97%a5%e6%9c%9f%e7%bb%84%e4%bb%b6datecomponents/

扩展知识

1、参数详解

1、nextDate(after:matching:matchingPolicy:repeatedTimePolicy:direction):从时间点之后开始搜索新的时间点。

let nextDate = calendar.nextDate(after: now,
    matching: DateComponents(hour: 9),
    matchingPolicy: .nextTime,
    repeatedTimePolicy: .first,
    direction: .forward)

1)after:now表示从now之后开始(>now)搜索,以 Calendar 的时区和规则为依据。

2)matching表示匹配DateComponents时间组件,这里表示09:00:00,未指定的字段视为通配。

3)matchingPolicy表示精准匹配不可用或不唯一时的决策。

enum MatchingPolicy {
    case nextTime                      // 找下一个可能的匹配时间
    case strict                        // 必须严格匹配,否则返回 nil
    case nextTimePreservingSmallerComponents   // 提升最高单位,尽可能保持分钟、秒等小组件不变
    case previousTimePreservingSmallerComponents    // 降低最高单位,降尽可能保持分钟、秒等小组件不变。
}

.nextTime:找到第一个能表示的时间,允许跳过不严格匹配的情况(例如夏令时缺失时间会跳到下一个有效时间),最常用。

.nextTimePreservingSmallerComponents:如果指定时间不存在,则提升最高单位(如小时),尽量保留提供的最小组件。

.previousTimePreservingSmallerComponents:如果指定时间不存在,则降低最高单位,但保留分钟、秒。

.strict:指定组件完全匹配,否则直接返回nil。

4)repeatedTimePolicy:处理重复时间的策略(夏令时结束时钟回拨导致同一时刻出现两次)。

enum RepeatedTimePolicy {
    case first      // 重复时间中的第一次出现
    case last       // 重复时间中的第二次出现
}

.first:若匹配到重复时间,返回第一个Date。

.last:返回重复时间中较晚的Date。

5)direction:查找时间的方向。

enum SearchDirection {
    case forward  // 向未来找
    case backward // 向过去找
}

.forward:向未来搜索(>after)。

.backward:向过去搜索(<after)。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注