Jon
Jon

Reputation: 3238

Enumerate Nth day of every month for the year

I'm getting unexpected results when attempting to enumerate days for a range of months. For example, I want to determine the 29th day for every month in 2018. As February does not have a day 29, I expect 11 dates: Jan 29, Mar 29, April 29, etc... Instead, I only get Jan 29 returned.

Paste into a playground:

var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_US")
calendar.timeZone = TimeZone(identifier: "America/Chicago")!

let firstOfYear = Date(timeIntervalSince1970: 1514786400)
let endDate = calendar.date(byAdding: .year,
                               value: 1,
                                  to: firstOfYear,
                  wrappingComponents: false)!

var components = DateComponents()
components.day = 29   // unexpected results
// components.day = 5 // correct results

var dates = [Date]()

calendar.enumerateDates(startingAfter: firstOfYear,
                        matching: components,
                        matchingPolicy: .strict,
                        using: { (nextDate: Date?, exactMatch: Bool, stop: inout Bool) in

    if nextDate?.compare(endDate) == .orderedDescending {
        stop = true
        return
    }

    dates.append(nextDate!)
})

dates

Note that I have tried all 4 matchingPolicy types with the same results. Anyone able to shed light on what is happening? It seems that the enumeration stops after it cannot find a date in a month. Is it best practice to create your own loop to determine dates?

Upvotes: 0

Views: 429

Answers (1)

rmaddy
rmaddy

Reputation: 318824

From the documentation for Calendar enumerateDates:

If an exact match is not possible, and requested with the strict option, nil is passed to the closure and the enumeration ends

This is why you only get the date for January 29. Using other matching modes doesn't work well when asking for a non-existent date such as February 29 on a non-leap year.

The following code gives you the results you want:

func datesFor(day: Int, year: Int) -> [Date] {
    var res = [Date]()
    var components = DateComponents(year: year, day: day)
    for month in 1...12 {
        components.month = month
        if let date = Calendar.current.date(from: components) {
            // Feb 29, 2018 results in Mar 1, 2018. This check skips such dates
            if Calendar.current.date(date, matchesComponents: components) {
                res.append(date)
            }
        }
    }

    return res
}

print(datesFor(day: 29, year: 2018))

Upvotes: 4

Related Questions