1
votes

I create an instance of DateComponents using the following code:

let dateComponents = DateComponents(
            calendar: .current,
            timeZone: Calendar.current.timeZone,
            era: nil,
            year: nil,
            month: nil,
            day: nil,
            hour: 9,
            minute: 0,
            second: 0,
            nanosecond: 0,
            weekday: 2,
            weekdayOrdinal: nil,
            quarter: nil,
            weekOfMonth: nil,
            weekOfYear: nil,
            yearForWeekOfYear: nil)

I then print the dateComponents object and get the following (expected) output:

calendar: gregorian (current) timeZone: Europe/London (current) hour: 9 minute: 0 second: 0 nanosecond: 0 weekday: 2 isLeapMonth: false

Immediately following this, I print the date created using the following code:

print(Calendar.current.date(from: dateComponents)!)

To my great dismay and thorough unhappiness, the following is outputted:

0001-01-01 09:01:15 +0000

The date(from: dateComponents) function appears to have added just over a minute to the dateComponents before creating a date from them.

Thanks for any help in advance.

1
What's the point of creating a date that only has an hour and no other components? What's your goal? - rmaddy
You need to specify a date otherwise it will go back to year 1 and have a huge time offset applied to it. Just choose something like era 1 year 2001 month 1 day 1 - Leo Dabus
BTW you can omit the parameters you are not setting any value (nil is default) as long as you keep the same order. - Leo Dabus
@rmaddy I'm using it to hold information about a schedule that repeats daily or weekly, and therefore other components, like date and month aren't important to me. - Loic Verrall
@Leo Dabus it looks like it defaults to 1/1/2001, are you saying if I specify a date, ideally sooner, that would fix it? Thanks for the tip with parameters, I didn't know that - Loic Verrall

1 Answers

4
votes

NSDate has some strange and undocumented behaviors for ancient dates. The change seems to have happened around 1895:

for year in 1890..<1900 {
    // January 1 of each year @ 9AM
    let dateComponents = DateComponents(
        calendar: .current,
        timeZone: Calendar.current.timeZone,
        year: year,
        month: 1,
        day: 1,
        hour: 9)

    if dateComponents.isValidDate {
        print(dateComponents.date!)
    }
}

My calendar is Gregorian and timezone is EDT (UTC -0500). This is the output:

1890-01-01 14:17:32 +0000
1891-01-01 14:17:32 +0000
1892-01-01 14:17:32 +0000
1893-01-01 14:17:32 +0000
1894-01-01 14:17:32 +0000 // not correct
1895-01-01 14:00:00 +0000 // correct
1896-01-01 14:00:00 +0000
1897-01-01 14:00:00 +0000
1898-01-01 14:00:00 +0000
1899-01-01 14:00:00 +0000

So for the years prior to 1895, Apple somehow added 17 minutes and 32 second to my time. You got a different offset, which is likely due your locale settings.

I couldn't find anything historical event about the Gregorian calendar in 1895. This question mentions that Britain started to switch over to GMT and the Greenwich Observatory started adjusting date/time standards across the British Isles in the 1890s so that may have accounted for this offset. Perhaps someone can delve into the source code for Date / NSDate and figure it out?


If you want to use DateComponent to store a repeating schedule, use nextDate(after:matching:matchingPolicy:) to find the next occurance of your schedule:

let dateComponents = DateComponents(calendar: .current, timeZone: .current, hour: 9, weekday: 2)

// 9AM of the next Monday
let nextOccurance = Calendar.current.nextDate(after: Date(), matching: dateComponents, matchingPolicy: .nextTime)!