DateComponents doesn't seem to honor year

234 Views Asked by At

I'm attempting to convert a Date to a DateComponents, change a few properties, and get a new Date back. I've written the following code:

import Foundation
var components = Calendar.current.dateComponents(in: .current, from: Date())
components.day = 7
components.month = 3
components.year = 1900
DateFormatter.localizedString(from: components.date!, dateStyle: .long, timeStyle: .long)

I've tested this in the America/Denver time zone/locale on an iOS Simulator on iOS 15.0 as well as a Swift REPL on my Mac running the latest macOS Big Sur and Xcode 13.0, and in both places, I get approximately the following output at the time of this writing:

March 7, 2021 at 9:38:13 AM MST

Everything about this is as expected, except for the year. I had explicitly set the year to be 1900, but the year in the output is 2021. How can I make DateComponents honor the year when generating a date, or how can I do this same kind of thing manually so it'll actually work?

2

There are 2 best solutions below

3
vadian On BEST ANSWER

If you get all components from a date with dateComponents(in:from:), you have to set also yearForWeekOfYear accordingly

components.yearForWeekOfYear = 1900

Or specify only the date and time components including calendar and timeZone you really need for example

var components = Calendar.current.dateComponents([.calendar, .timeZone, .year, .month, .day, .hour, .minute, .second], from: Date())
components.day = 7
components.month = 3
components.year = 1900
DateFormatter.localizedString(from: components.date!, dateStyle: .long, timeStyle: .long)
0
Sweeper On

Note that you are getting all the date components, including weekOfYear, weekOfMonth, dayOfWeek, and most importantly, yearForWeekOfYear. When you set the date components to 1900-03-07, you did not set all those components correctly either, and when resolving a Date from a DateComponents that has such conflicting components, the algorithm just so happens to prefer the year they got from yearForWeekOfYear.

If you set those fields to nil, you will get the expected result:

var components = Calendar.current.dateComponents(in: .current, from: Date())
components.day = 7
components.month = 3
components.year = 1900

components.yearForWeekOfYear = nil

There will still be conflicts in the date components though, and although this doesn't cause a crash, I'm not sure if this behaviour will change in the future. I suggest that you get just the components you need. For example:

var components = Calendar.current.dateComponents([
    .calendar, .timeZone, .era, .year, .month, .day, .hour, .minute, .second, .nanosecond
], from: Date())