3

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?

Michael Hulet
  • 3,253
  • 1
  • 16
  • 33

2 Answers2

3

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)
vadian
  • 274,689
  • 30
  • 353
  • 361
  • My ultimate end goal here is that I have 2 `UIDatePickers`. One where it sets both a date and a time, and the other where it just sets the time. I want the one that just sets the time to have its `date` be on the same calendar day as the first one, which selects both the date and time. I've been trying to keep those in sync manually, but I'm not sure if there's a better way to do that. This answer appears to work, though, so I'm accepting it. Thanks! – Michael Hulet Oct 22 '21 at 16:03
  • Good catch (voted). I did not realize that if you try to create a Date from all of the values in DateComponents you had to set both `year` and `yearForWeekOfYear`. – Duncan C Oct 22 '21 at 16:15
  • @DuncanC As Sweeper pointed out you can set `yearForWeekOfYear` also to `nil`. – vadian Oct 22 '21 at 16:16
1

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())
Sweeper
  • 213,210
  • 22
  • 193
  • 313