Skip to content

Instantly share code, notes, and snippets.

@ole
Last active December 12, 2016 12:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ole/0c81c03a0734df30e9ba2227b7ba8d30 to your computer and use it in GitHub Desktop.
Save ole/0c81c03a0734df30e9ba2227b7ba8d30 to your computer and use it in GitHub Desktop.
Paste into a playground in Xcode 8.

I found a weird inconsistency or bug in Calendar.nextDate(after:matching:).

The result I'm getting changes depending on whether the DateComponents value I pass in includes a nanosecond component (even if the value is 0).

The only "weirdness" is that the startDate value lies in the "repeating hour" between 2am and 3am during the daylight saving switch. This means there is an ambiguity for the result of nextDate(after:matching:).

But what does that have to do with the nanoseconds?

(For Apple folks: Radar 29612754.)

// macOS 10.12.1 or iOS 10, Swift 3.0.1
import Foundation
var calendar = Calendar(identifier: .gregorian)
// GMT+1 (GMT+2 under daylight saving)
calendar.timeZone = TimeZone(identifier: "Europe/Berlin")!
// 2016-10-30 02:30:00 +02:00
// Europe/Berlin switched from daylight saving to winter time on this date, i.e. on 2016-10-30 03:00:00 +02:00 the clock was moved back by one hour.
let startDate = calendar.date(from: DateComponents(year: 2016, month: 10, day: 30, hour: 2, minute: 30, second: 0))!
print("startDate:", startDate) // startDate: 2016-10-30 00:30:00 +0000
// Verify that nanoseconds are 0 for this date
let comps = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: startDate)
assert(comps.nanosecond == 0) // passes
// Find the next full hour, i.e. the next date where minute == 0 and second == 0.
// Two variants:
// 1) Pass date components minute, second, and nanosecond
let nextDate1 = calendar.nextDate(after: startDate, matching: DateComponents(minute: 0, second: 0, nanosecond: 0), matchingPolicy: .strict, repeatedTimePolicy: .first)!
// 2) Pass only minute and second
let nextDate2 = calendar.nextDate(after: startDate, matching: DateComponents(minute: 0, second: 0), matchingPolicy: .strict, repeatedTimePolicy: .first)!
// These should be equal!
print("nextDate1:", nextDate1) // nextDate1: 2016-10-30 02:00:00 +0000
print("nextDate2:", nextDate2) // nextDate2: 2016-10-30 01:00:00 +0000
assert(nextDate1 == nextDate2) // should pass, but fails
// I tried various variations for matchingPolicy and repeatedTimePolicy, but always got the same result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment