swiftdate-formattingnsdatecomponentsformatter

NSDateComponentsFormatter adds additional hour


I found rather strange behavior of NSDateComponentsFormatter - for some time intervals it just adds additional hour to the result.

I assume that for time intervals over 1 year, it could add a "leap hour", but in this case I see that time interval as short as 5 days results to 5 days and one hour.

Consider this Test Case:

func testStringFromTimeInterval()
{
    let formatter = NSDateComponentsFormatter()
    formatter.unitsStyle = .Positional
    formatter.allowedUnits = [.Year, .Month, .WeekOfMonth, .Day, .Hour, .Minute]

    XCTAssertEqual(formatter.stringFromTimeInterval(3600) ?? "", "1:00") // Pass
    XCTAssertEqual(formatter.stringFromTimeInterval(3600*24) ?? "", "1d 0:00") // Pass
    XCTAssertEqual(formatter.stringFromTimeInterval(3600*24*5) ?? "", "5d 0:00") // Fail: actual "5d 1:00"
    XCTAssertEqual(formatter.stringFromTimeInterval(3600*24*365) ?? "", "1y 0m 0w 0d 0:00") // Pass
    XCTAssertEqual(formatter.stringFromTimeInterval((3600*24*365)+60) ?? "", "1y 0m 0w 0d 0:01") // Pass
    XCTAssertEqual(formatter.stringFromTimeInterval((3600*24*365)+3600) ?? "", "1y 0m 0w 0d 1:00") // Pass
    XCTAssertEqual(formatter.stringFromTimeInterval((3600*24*365)+(3600*24)) ?? "", "1y 0m 0w 1d 0:00") // Pass
    XCTAssertEqual(formatter.stringFromTimeInterval((3600*24*365)+(3600*24*5)) ?? "", "1y 0m 0w 5d 0:00") // Fail: actual "1y 0m 0w 5d 1:00"
}

Can you help to find a way to avoid adding that 1 hour?

Thanks!

Maris


Solution

  • This is caused by Daylight saving time change which happens in Europe in a few days. I am not sure whether this is the expected behavior of the interval formatter (it definitely seems a bit strange). However, as all time zone problems, you can fix it by setting the time zone to a specific GMT timezone. In this case GMT-00:00 is probably the expected time zone:

    let formatter = NSDateComponentsFormatter()
    formatter.unitsStyle = .Positional
    formatter.allowedUnits = [.Year, .Month, .WeekOfMonth, .Day, .Hour, .Minute]
    
    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    calendar.locale = NSLocale.currentLocale()
    calendar.timeZone = NSTimeZone(forSecondsFromGMT: 0)
    
    formatter.calendar = calendar
    
    print(formatter.stringFromTimeInterval(3600)) // "1:00"
    print(formatter.stringFromTimeInterval(3600*24)) // "1d 0:00"
    print(formatter.stringFromTimeInterval(3600*24*5)) // "5d 0:00"
    print(formatter.stringFromTimeInterval(3600*24*365)) // "1y 0m 0w 0d 0:00"