Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSDateComponentsFormatter stringFromTimeInterval returns incorrect string for intervals shorter than 1 minute

I'm using NSDateComponentsFormatter on iOS 8 and i've noticed a possible bug in the implementation of the stringFromTimeInterval: method, but I can't say it for sure.

This is how I have set up the formatter:

NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDefault;
formatter.allowedUnits = NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;

Now when I pass anything less than 60 seconds to the stringFromTimeInterval: method, the formatter returns either 1 or 01, which is obviously wrong, it should return something like 0:XX.

What I have also noticed is that by changing the unitsStyle to anything but NSDateComponentsFormatterUnitsStylePositional the method returns a correct string!

Example:

With unitsStyle = NSDateComponentsFormatterUnitsStylePositional:

(lldb) po [formatter stringFromTimeInterval:12]
= "1"

=> Should return "0:12"!!

With unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated:

(lldb) po [formatter stringFromTimeInterval:12]
= "12 s"

=> Correct!

This lets me think that it is a bug in Apple's implementation of the API, or am I missing something?

like image 468
JonasG Avatar asked Jan 22 '15 11:01

JonasG


2 Answers

This seems to still be an issue with iOS 8.3 and Xcode 6.3. There is a way to work around it however, which, if not perfect, at least allows you to get proper formatting without having to deal with splitting time relevant components yourself.

Here is an example workaround in Swift (which you can test in a Playground)

let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Positional
formatter.zeroFormattingBehavior = .Pad
formatter.allowedUnits = .CalendarUnitMinute | .CalendarUnitSecond

Calling let string = formatter.stringFromTimeInterval(4) will print "00:04".

Calling let string = formatter.stringFromTimeInterval(88) will print "01:28".

Now you can play with the allowedUnits depending on whether you have more than 1 hour or 1 day. For example in a class which has a formatter property:

func updateTimeFormatterForTime(time: NSTimeInterval) {
   if time > 3600 && time < 24 * 3600 {
      self.formatter.allowedUnits = .CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitSecond
   } else if time > 24 * 3600 {
      self.formatter.allowedUnits = .CalendarUnitDay | .CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitSecond
   }
}
like image 194
MiKL Avatar answered Oct 21 '22 10:10

MiKL


NSDateComponentsFormatterZeroFormattingBehaviorDefault means drop leading zeros, pad other zeros for Positional units style.

e.g. with the allowedUnits values in your code, [formatter stringFromTimeInterval:120] result is "2:00" in instead of "0d 0:02:00", because formatter object removed all leading zero values.

If you want to reserve the zero value places, you can use NSDateComponentsFormatterZeroFormattingBehaviorNone

For why [formatter stringFromTimeInterval:12] result is 1, I think formatter object finds "12" alone is ambiguous as it has no trailing values or indicator for meaning like in "1d" for example.

like image 42
merocode Avatar answered Oct 21 '22 09:10

merocode