Calendar's date func is not behaving as I'd expect...

When I run this in a playground:

var meDate = Calendar.current.date(from: DateComponents(year: 2024, hour: 7, weekday: 3, weekdayOrdinal: 2))!
print(meDate)

I see: 2024-01-09 15:00:00 +0000

This seems correct to me.

  • jan 9th is the second Tuesday in 2024
  • I'm in the pacific TZ, 07:00 PDT matches 15:00GMT

But then I do this:

meDate = Calendar.current.date(bySetting: .weekday, value: 4, of: meDate)!
print(meDate)

and I see: 2024-01-10 08:00:00 +0000

I would have expected my hour value (7PST/15GMT) to have been preserved. Is there a way I can update weekday, but not lose my hour?

Answered by Claude31 in 861151022

As explained in doc, Changing a component’s value often will require higher or coupled components to change as well… The exact behavior of this method is implementation-defined:

Changing a component’s value often will require higher or coupled components to change as well. For example, setting the Weekday to Thursday usually will require the Day component to change its value, and possibly the Month and Year as well. If no such time exists, the next available time is returned (which could, for example, be in a different day, week, month, … than the nominal target date). Setting a component to something which would be inconsistent forces other components to change; for example, setting the Weekday to Thursday probably shifts the Day and possibly Month and Year. The exact behavior of this method is implementation-defined. For example, if changing the weekday to Thursday, does that move forward to the next, backward to the previous, or to the nearest Thursday? The algorithm will try to produce a result which is in the next-larger component to the one given (there’s a table of this mapping at the top of this document). So for the “set to Thursday” example, find the Thursday in the Week in which the given date resides (which could be a forwards or backwards move, and not necessarily the nearest Thursday). For more control over the exact behavior, use nextDate(after:matching:matchingPolicy:behavior:direction:).

Note that if you set the weekday to the same value (3), results are kept the same

meDate = Calendar.current.date(bySetting: .weekday, value: 3, of: meDate)!

Interesting discussion here: https://forums.swift.org/t/date-from-date-components-is-incorrect-after-changing-month/54192/5

Accepted Answer

As explained in doc, Changing a component’s value often will require higher or coupled components to change as well… The exact behavior of this method is implementation-defined:

Changing a component’s value often will require higher or coupled components to change as well. For example, setting the Weekday to Thursday usually will require the Day component to change its value, and possibly the Month and Year as well. If no such time exists, the next available time is returned (which could, for example, be in a different day, week, month, … than the nominal target date). Setting a component to something which would be inconsistent forces other components to change; for example, setting the Weekday to Thursday probably shifts the Day and possibly Month and Year. The exact behavior of this method is implementation-defined. For example, if changing the weekday to Thursday, does that move forward to the next, backward to the previous, or to the nearest Thursday? The algorithm will try to produce a result which is in the next-larger component to the one given (there’s a table of this mapping at the top of this document). So for the “set to Thursday” example, find the Thursday in the Week in which the given date resides (which could be a forwards or backwards move, and not necessarily the nearest Thursday). For more control over the exact behavior, use nextDate(after:matching:matchingPolicy:behavior:direction:).

Note that if you set the weekday to the same value (3), results are kept the same

meDate = Calendar.current.date(bySetting: .weekday, value: 3, of: meDate)!

Interesting discussion here: https://forums.swift.org/t/date-from-date-components-is-incorrect-after-changing-month/54192/5

Thanks @Claude31 for pointing me to a couple of helpful sources. I definitely understand that changing weekday will have unpredictable results for day, month, year. but I'm still not sure I understand why hour, minute need to get clobbered. Having said that, thanks to your links I've changed my approach and am using Calendar's nextDate func

    mutating func update(updatedWeekday: Int? = nil, updatedHour: Int? = nil, updatedMinute: Int? = nil) {
        var components = DateComponents()
        components.weekday = updatedWeekday == nil ? weekday : updatedWeekday
        components.hour = updatedHour == nil ? hour : updatedHour
        components.minute = updatedMinute == nil ? minute : updatedMinute

        let nextDate = Self.calendar.nextDate(after: underlyingDate, matching: components, matchingPolicy: .nextTime, direction: .forward)
        if let nextDate = nextDate {
            underlyingDate = nextDate
        }
    }
Calendar's date func is not behaving as I'd expect...
 
 
Q