Search code examples
ioscocoa-touchnsdatenscalendar

NSCalendar components returning different values


I have 2 dates that I would like to compare. A date in the future, and the current date. When I compare them I want to retrieve their difference in terms of hours, minutes, and seconds. I believe the code I have to do this is correct, however, I get different values returned depending on the components I request.

I am using the following calendar for all examples:

let calendar = NSCalendar.currentCalendar()

A:

let dateComponents = calendar.components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Second, NSCalendarUnit.Minute], fromDate: NSDate(), toDate: date, options: [])

B:

let dateComponents2 = calendar.components(.Hour, fromDate: NSDate(), toDate: date, options: [])

So, with the above example, dateComponents.hour does not equal dateComponents2.hour, and dateComponents2.hour returns the correct value.

Even more interesting is the following case:

A:

let dateComponents = calendar.components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Second, NSCalendarUnit.Minute], fromDate: NSDate(), toDate: date, options: [])

B:

let dateComponents2 = calendar.components(.Minute, fromDate: NSDate(), toDate: date, options: [])

Now, dateComponents.minute does not equal dateComponents2.minute, but now dateComponents.minute returns the correct value.

Why could this be happening? Does the fact that I am requesting multiple components at the same time affect the return value?

EDIT Here are some examples using the following dates. The return values are shown as hours, minutes, seconds:

2015-11-29 10:59:00 +0000

2015-11-28 07:57:20 +0000

Using dateComponents:

3, 1, 39

Using dateComponents2 (one for each component):

27, 1621, 97299

The hours value is correct from dateComponents2, and the minutes and seconds values are correct from dateComponents.


Solution

  • Let's imagine that the two NSDate objects are 61 seconds apart, if you get both minute and second at the same time, you get 1 minute and 1 second, but if you just ask for just seconds, you get 61 seconds:

    let date1 = NSDate()
    let date2 = date1.dateByAddingTimeInterval(61)
    
    let calendar = NSCalendar.currentCalendar()
    let components1 = calendar.components([.Minute, .Second], fromDate: date1, toDate: date2, options: [])
    let components2 = calendar.components([.Second], fromDate: date1, toDate: date2, options: [])
    
    print("\(components1.minute) minute and \(components1.second) second")
    print("\(components2.second) seconds")
    

    That yields:

    1 minute and 1 second
    61 seconds


    Using your dates (and adding days and hours to the calculations):

    let dateString1 = "2015-11-28 07:57:20 +0000"
    let dateString2 = "2015-11-29 10:59:00 +0000"
    
    let formatter = NSDateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
    
    let date1 = formatter.dateFromString(dateString1)!
    let date2 = formatter.dateFromString(dateString2)!
    
    let calendar = NSCalendar.currentCalendar()
    let components1 = calendar.components([.Day, .Hour, .Minute, .Second], fromDate: date1, toDate: date2, options: [])
    let components2 = calendar.components([.Second], fromDate: date1, toDate: date2, options: [])
    
    print("\(components1.day) day, \(components1.hour) hours, \(components1.minute) minute, and \(components1.second) seconds")
    print("\(components2.second) seconds")
    

    That yields:

    1 day, 3 hours, 1 minute, and 40 seconds
    97300 seconds

    And if you multiply that out, you'll see that 1 day, 3 hours, 1 minute and 40 seconds is equal to 97300 seconds.


    You can also consider using NSDateComponentsFormatter to create a nicely formatted (and localized) string representation of the time elapsed:

    let componentsFormatter = NSDateComponentsFormatter()
    componentsFormatter.allowedUnits = [.Day, .Hour, .Minute, .Second]
    componentsFormatter.unitsStyle = .Full
    print(componentsFormatter.stringFromDate(date1, toDate: date2))
    
    // or just show the top two units (because if you're showing days/hours, you probably no longer care about minutes/seconds
    
    componentsFormatter.maximumUnitCount = 2
    print(componentsFormatter.stringFromDate(date1, toDate: date2))
    
    // but if you want actual total number of seconds, then change `allowedUnits` to use only that
    
    componentsFormatter.allowedUnits = [.Second]
    print(componentsFormatter.stringFromDate(date1, toDate: date2))
    

    That will display three optional strings:

    1 day, 3 hours, 1 minute, 40 seconds
    1 day, 3 hours
    97,300 seconds