Calculate previous Sunday when DST changed on the previous Sunday

Today is Tuesday March 13th, 2018. The previous Sunday was March 11th, 2018. My code to calculate the previous Sunday is:

// NOTE that this particular code block may return the correct result for you IF your timezone 
// is something other than "America/Chicago". E.g. "America/Belize". Use the longer code 
// block that begins with "var calendar = ..." if you are trying to replicate the issue
Calendar.current.nextDate(after: Date(),
                          matching: DateComponents(weekday: 1),
                          matchingPolicy: .nextTime,
                          repeatedTimePolicy: .first,
                          direction: .backward)!

This returns the date of March 4th, not the expected value of March 11th. This is due to a DST change. I've tried every combination of matchingPolicy and repeatedTimePolicy but it always returns March 4th. Any thoughts on how I could get the calendar to return March 11th?

var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_US")
calendar.timeZone = TimeZone(identifier: "America/Chicago")!
// Tuesday March 13th @ 3:10 PM (America/Chicago)
let today = Date(timeIntervalSince1970: 1520971846)

let d1 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .nextTime,
                           repeatedTimePolicy: .first,
                           direction: .backward)!

let d2 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .nextTimePreservingSmallerComponents,
                           repeatedTimePolicy: .first,
                           direction: .backward)!

let d3 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .previousTimePreservingSmallerComponents,
                           repeatedTimePolicy: .first,
                           direction: .backward)!

let d4 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .strict,
                           repeatedTimePolicy: .first,
                           direction: .backward)!

let d5 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .nextTime,
                           repeatedTimePolicy: .last,
                           direction: .backward)!

let d6 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .nextTimePreservingSmallerComponents,
                           repeatedTimePolicy: .last,
                           direction: .backward)!

let d7 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .previousTimePreservingSmallerComponents,
                           repeatedTimePolicy: .last,
                           direction: .backward)!

let d8 = calendar.nextDate(after: today,
                           matching: DateComponents(weekday: 1),
                           matchingPolicy: .strict,
                           repeatedTimePolicy: .last,
                           direction: .backward)!

Bug report has been filed per suggestion.

2 answers

  • answered 2018-03-13 21:38 picciano

    This is likely an Apple bug, as the comment above says.

    There is a very ugly workaround by first finding the "next" Sunday, then using that date to find the previous Sunday. I am sure there are some edge cases where this won't work, but maybe it might help you.

    // Not recommended for production code
    
    var nextSunday = Calendar.current.nextDate(after: Date(),
                                           matching: DateComponents(weekday: 1),
                                           matchingPolicy: .nextTime,
                                           repeatedTimePolicy: .first,
                                           direction: .forward)!
    
    var lastSunday = Calendar.current.nextDate(after: nextSunday,
                                           matching: DateComponents(weekday: 1),
                                           matchingPolicy: .nextTime,
                                           repeatedTimePolicy: .first,
                                           direction: .backward)!
    

    enter image description here

  • answered 2018-03-13 22:46 Jon

    Another option is to subtract seven days from "today" and then just search .forward. Personally, I won't be searching .backward due to this DST issue and due to this other post.

    var calendar = Calendar(identifier: .gregorian)
    calendar.locale = Locale(identifier: "en_US")
    calendar.timeZone = TimeZone(identifier: "America/Chicago")!
    // Tuesday March 13th @ 3:10 PM (America/Chicago)
    var date = Date(timeIntervalSince1970: 1520971846)
    date = calendar.date(byAdding: .day, value: -7, to: date)!
    //or = calendar.date(byAdding: .weekOfYear, value: -1, to: date)!
    
    let lastSunday = calendar.nextDate(after: date,
                               matching: DateComponents(weekday: 1),
                               matchingPolicy: .nextTime,
                               repeatedTimePolicy: .first,
                               direction: .forward)!