I've created an extension for NSDate which removes the time component to allow equality checks for NSDate based on date alone. I have achieved this by taking the original NSDate object, obtaining the day, month and year using the DateComponent class and then constructing a new NSDate using the information obtained. Although the NSDate objects obtained look correct when printed to the console (i.e. timestamp is 00:00:00) and using the NSDate.compare function on two identical dates returns NSComparisonResult.OrderedSame, if you deconstruct them using DateComponent once more, some of them have the hour set to 1. This appears to be a random event with this error being present about 55% of the time. Forcing the hour, minute and second properties of DateComponent to zero before constructing the new NSDate rather than assuming they will default to these values does not rectify the situation. Ensuring the timezone is set helps a little but again does not fix it.
I am guessing there may be a rounding error somewhere (possibly in my test code), I've fluffed the conversion or there is a Swift bug but would appreciate comments. Code and output from a unit test below.
Code as follows:
extension NSDate { // creates a NSDate object with time set to 00:00:00 which allows equality checks on dates alone var asDateOnly: NSDate { get { let userCalendar = NSCalendar.currentCalendar() let dayMonthYearUnits: NSCalendarUnit = .CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear var dateComponents = userCalendar.components(dayMonthYearUnits, fromDate: self) dateComponents.timeZone = NSTimeZone(name: "GMT") // dateComponents.hour = 0 // dateComponents.minute = 0 // dateComponents.second = 0 let result = userCalendar.dateFromComponents(dateComponents)! return result } } Test func:
func testRemovingTimeComponentFromRandomNSDateObjectsAlwaysResultsInNSDateSetToMidnight() { var dates = [NSDate]() let dateRange = NSDate.timeIntervalSinceReferenceDate() for var i = 0; i < 30; i++ { let randomTimeInterval = Double(arc4random_uniform(UInt32(dateRange))) let date = NSDate(timeIntervalSinceReferenceDate: randomTimeInterval).asDateOnly let dateStrippedOfTime = date.asDateOnly // get the hour, minute and second components from the stripped date let userCalendar = NSCalendar.currentCalendar() var hourMinuteSecondUnits: NSCalendarUnit = .CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitSecond var dateComponents = userCalendar.components(hourMinuteSecondUnits, fromDate: dateStrippedOfTime) dateComponents.timeZone = NSTimeZone(name: "GMT") XCTAssertTrue((dateComponents.hour == 0) && (dateComponents.minute == 0) && (dateComponents.second == 0), "Time components were not set to zero - \nNSDate: \(date) \nIndex: \(i) H: \(dateComponents.hour) M: \(dateComponents.minute) S: \(dateComponents.second)") } } Output:
testRemovingTimeComponentFromRandomNSDateObjectsAlwaysResultsInNSDateSetToMidnight] : XCTAssertTrue failed - Time components were not set to zero - NSDate: 2009-06-19 00:00:00 +0000 Index: 29 H: 1 M: 0 S: 0