Calendar nextDate/enumerateDates methods with backward direction does not work for September

I’m trying to get the previous date that matches the 9nth month in the Gregorian calendar (which is September) from Date.now (which is in December 2023 right now). The expected date is then in 2023. The first date returned is in 1995. Why?

I filed the feedback FB13462533

var calendar: Calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone.autoupdatingCurrent

let matchingDateComponents: DateComponents = DateComponents(month: 09)
                        
let date: Date? = calendar.nextDate(
    after: Date.now,
    matching: matchingDateComponents,
    matchingPolicy: .nextTime,
    direction: .backward
)
            
print(date) // Optional(1995-08-31 23:00:00 +0000)

Replies

I just tested with other time zones and the results are different... For some time zones, the result is correct, for others, not. I used the following code to print the result for all time zones.

  • Optional(1970-08-31 23:00:00 +0000) for Europe/London
  • Optional(1995-08-31 23:00:00 +0000) for Europe/Paris
  • Optional(2002-08-31 22:00:00 +0000) for Europe/Vilnius
  • Optional(2023-09-01 07:00:00 +0000) for America/Los_Angeles
for zone in TimeZone.knownTimeZoneIdentifiers {
    var calendar: Calendar = Calendar(identifier: .gregorian)
    calendar.timeZone = TimeZone(identifier: zone) ?? .autoupdatingCurrent
                
    let matchingDateComponents: DateComponents = DateComponents(month: 9)
                
    let date: Date? = calendar.nextDate(
        after: Date.now,
        matching: matchingDateComponents,
        matchingPolicy: .nextTime,
        direction: .backward
    )
                
    print(zone, date)
}

It work better (not perfectly) with month: 11 or 10 or 8. But not 9 !!! Very strange.

That seems to be an old bug with .backwards: https://stackoverflow.com/questions/49202865/calendar-nextdate-is-acting-really-weird-when-using-backward-for-direction-o

I tested with other date components to see if the dates provided by these methods are correct when searching backward. I tested using a .nextTime and .strict matchingPolicy.

DateComponents(month: 1) Correct years, wrong days. I mainly get dates around January the 5th. But the years seem to be correct. In this case, only the America/Los_Angeles date seems to be correct. Even GMT is wrong.

  • 2023-01-05 00:00:00 +0000 for Europe/London
  • 2023-01-04 23:00:00 +0000 for Europe/Paris
  • 2023-01-04 22:00:00 +0000 for Europe/Vilnius
  • 2023-01-01 08:00:00 +0000 for America/Los_Angeles
  • 2023-01-05 00:00:00 +0000 for GMT

DateComponents(month: 2) Correct years, correct days

DateComponents(month: 3) Correct years, wrong days

DateComponents(month: 4) Correct years, correct days

DateComponents(month: 5) Correct years, wrong days. I mainly get dates around May the 3rd.

  • 2023-05-02 23:00:00 +0000 for Europe/London
  • 2023-05-02 22:00:00 +0000 for Europe/Paris
  • 2023-05-02 21:00:00 +0000 for Europe/Vilnius
  • 2023-05-03 07:00:00 +0000 for America/Los_Angeles
  • 2023-05-03 00:00:00 +0000 for GMT

DateComponents(month: 6) Correct years, correct days

DateComponents(month: 7) Correct years, wrong days

DateComponents(month: 8) Correct years, wrong days

DateComponents(month: 9) Wrong years, correct days

DateComponents(month: 10) Correct years, wrong days

DateComponents(month: 11) Correct years, correct days

DateComponents(month: 12) Wrong years, wrong days

  • Expected Result: first day of December of this year (when after is December the 5th for example).
  • Actual Result: It provides the last day of December for the previous year.

If I additionally specify a day in the date components like DateComponents(month: 1, day: 1), I get the correct results for all time zones, except for September (still wrong years).

So two issues:

  1. If only a month is specified, the backward results are often wrong: either the year (like in September or December), or the days (if I only provide the month, I expect the function to give me the start of the month that matches this month date component).
  2. If the specified month is September, the year and day in provided date are wrong for a lot of time zones.