Swift中获取非公历历法一年月份数的实现难题
解决非公历历法中获取指定年份月份数的问题
问题描述
想要获取非公历(如希伯来历)指定年份的月份数量,比如希伯来历5784年有13个月,5783年为12个月。但实现的Calendar扩展方法monthRange始终返回1..<14,无法正确返回目标年份的实际月份数;而逻辑类似的dayRange方法在公历、农历中都能正常返回对应月份的天数范围。
原实现的monthRange代码:
public extension Calendar { /// The range of months for a given year in the calendar func monthRange(year: Int) -> Range<Int> { let components = DateComponents(calendar: self, year: year, month: 1, // I assume 1 is always valid day: 1) // I assume 1 is always valid let date = self.date(from: components)! let monthRange = self.range(of: .month, in: .year, for: date)! return monthRange } }
问题原因
range(of: .month, in: .year, for: date)方法返回的是该历法支持的月份通用范围,而非传入日期所在年份的实际月份数。以希伯来历为例,该历法本身支持12或13个月的年份,所以这个方法会直接返回最大可能的范围1..<14,不会针对具体年份做适配。
而dayRange方法能正常工作,是因为range(of: .day, in: .month, for: date)是针对指定月份的实际天数范围,每个月份的天数是确定的,因此能返回正确结果。
解决方案
通过获取指定年份的最后一天,提取该日期所在的月份数,以此确定年份的实际月份总数。修改后的扩展方法如下:
public extension Calendar { /// 获取指定年份的月份范围(适配非公历历法的闰月情况) func monthRange(year: Int) -> Range<Int>? { // 构造目标年份的第一天 guard let startDate = date(from: DateComponents(calendar: self, year: year, month: 1, day: 1)) else { return nil } // 构造下一年第一天,再往前推1天得到目标年份的最后一天 guard let nextYearStart = date(from: DateComponents(calendar: self, year: year + 1, month: 1, day: 1)) else { return nil } let endDate = date(byAdding: .day, value: -1, to: nextYearStart)! // 获取最后一天所在的月份,即为该年份的总月份数 let endMonth = component(.month, from: endDate) return 1..<(endMonth + 1) } }
测试示例
以希伯来历为例验证:
let hebrewCalendar = Calendar(identifier: .hebrew) print(hebrewCalendar.monthRange(year: 5783)) // 输出 1..<13 print(hebrewCalendar.monthRange(year: 5784)) // 输出 1..<14
内容的提问来源于stack exchange,提问作者AirXygène
相关产品推荐
相关产品推荐

