Calendar not enumerating all expected dates

Does anybody know what is going on here?

I'm looking for all first weekdays in a year.

let calendar = Calendar(identifier: .gregorian)
let year = calendar.dateInterval(of: .year, for: .now)!
// ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
print(calendar.standaloneWeekdaySymbols)
// 1
print(calendar.firstWeekday)

let firstWeekdayDateComponents = DateComponents(
    hour: 0,
    minute: 0,
    second: 0,
    nanosecond: 0,
    weekday: calendar.firstWeekday
)
calendar.enumerateDates(
    startingAfter: year.start,
    matching: firstWeekdayDateComponents,
    matchingPolicy: .strict
) { date, _, stop in
    guard let date = date, date < year.end else {
        stop = true
        return
    }
    print(DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .none))
}

It prints

02/01/2022
09/01/2022
16/01/2022
...
04/12/2022
11/12/2022
18/12/2022

where I'm missing the last expected value - 25/12/2022.

If I try to reconstruct that expected date, with components with the same precision, the calendar is able to do so.

let missingDateDateComponents = DateComponents(
    year: 2022,
    month: 12,
    day: 25,
    hour: 0,
    minute: 0,
    second: 0,
    nanosecond: 0,
    weekday: calendar.firstWeekday
)
let missingDate = calendar.date(from: missingDateDateComponents)!
// 25/12/2022
print(DateFormatter.localizedString(from: missingDate, dateStyle: .short, timeStyle: .none))
// year: 2022 month: 12 day: 25 hour: 0 minute: 0 second: 0 nanosecond: 0 weekday: 1 isLeapMonth: false
print(calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday], from: missingDate))
// true
print(missingDate < year.end)

Am I missing something?

Maybe that's a bug but if you comment out "nanoseconds" (or set it to nil) it gives 25th as expected. cc-ing @eskimo who might shed some light.

There’s definitely something wonky going on with the nanoseconds date component. If you remove the date < year.end clause it stops on 18 Dec 2022 as well, which is weird ’cause it should just keep printing dates. And if you then remove nanosecond: 0, it does exactly that.

IMO this is bugworthy. Please post your bug number, just for the record.

Having said that, supplying the time components here is both unnecessary and potentially worrisome. Remember that some time zones don’t have midnight on some days of the year (due to daylight saving time changes) so I expect you’ll see other oddities. In this context it’s best to just leave them out. Or, if you’re not up for that, specify midday rather than midnight.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Thank you, sir! And thank you tera for summoning the Master of Daemonology.

I had found out both, that nanosecond makes the difference and the enumeration is broken from any expected 52nd value on. Didn't know what to do with that information though.

What I didn't realize is that some countries may have DST shifts occurring at midnight. Thanks for bringing that up. My first thought is that I'll prefer to change matchingPolicy to .nextTime and keep the time components since I want to make sure I'm getting the very first moment of a week.

Anyways, here is the bug number @eskimo: FB10022941.

What is the blessed way to list all sundays of the year?