Hmmm, this is a tricky one. The date(bySetting:value:of:) method is more-or-less a wrapper around the Objective-C -nextDateAfterDate:matchingUnit:value:options: method. By default this searches forwards, with the options parameter letting you request a backwards search (NSCalendarSearchBackwards). So things (kinda) work if you try to set the year to 2022:
let date2 = Calendar.current.date(bySetting: .year, value: 2022, of: date)
print(date2)
// -> Optional(2022-01-01 00:00:00 +0000)
However, this still isn’t the result you’re looking for, because the semantics of this method don’t really align with your requirements. Try this instead:
let c = Calendar(identifier: .gregorian)
var dc = c.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: date)
dc.year = 2020
let date2 = c.date(from: dc)
print(date2)
// -> Optional(2020-07-29 08:17:33 +0000)
There are some serious caveats here:
-
In my code I hard-wired the calendar to Gregorian. The year 2020 only makes sense in that calendar. If you use a year like that and work in the current calendar and the user’s current calendar is something non-Gregorian you will run into problems. To see this in action, tweak the code above to use .buddhist (-:
-
There’s no guarantee that date2 won’t be nil. The date you’re looking for may not exist, or the current time might not exist within that date (due to a time discontinuity, typically a daylight saving time jump). Most calendars try to avoid returning nil and give you back something that’s kinda close, but nil is always a possibility.
If you can explain more about your high-level goal I may be able to suggest an alternative. For example, if you’re trying to calculate this time last year, date(byAdding:value:to:) is often the best choice.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"