Search code examples
swiftcalendar

Get the number of months in a year for non-gregorian calendar


I would like to get the number of months in a year for non-gregorian calendars. For example, in the Hebrew calendar, year 5784 has 13 months, but year 5783 has 12 months.

I tried:

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
    }
}

with no luck (result is always 1..<14).

Note that:

public extension Calendar {

    func dayRange(year: Int, month: Int) -> Range<Int> {
        let components  = DateComponents(calendar:  self,
                                         year:      year,
                                         month:     month,
                                         day:       1)  //  I assume 1 is always valid
        let date        = self.date(from: components)!
        let dayRange    = self.range(of: .day, in: .month, for: date)!
        return dayRange
    }
}

Is working fine (at least for Gregorian calendar, it gives indeed 29 days for leap-year February, 28 otherwise, and 30/31 days for other months). It also works correctly for Chinese calendar (30 or 31 days).


Solution

  • This is because of the meaning of the month component in the Hebrew calendar. A month value of 13 means Elul, and 1 means Tishri. The months Elul and Tishri are in every year, so the range is always 1..<14.

    The difference between leap and non-leap years is whether the month represented by the value 6 exists or not. 6 means Adar I, and 7 is Adar or Adar II depending on whether it is a leap year.

    If you just want to find out whether it is a year with a leap month, just check the number of days in a year.

    let components = DateComponents(calendar: self, year: year)
    guard let date = components.date,
          let rangeOfDays = range(of: .day, in: .year, for: date) else {
        // something went wrong...
    }
    if rangeOfDays.upperBound > 380 {
        // leap year!
    }