If your application is acting strange when your users are switching between 12-hour and 24-hour mode in their iPhone settings, you may be experiencing the same thing we are: an NSDateFormatter bug in the iPhone SDK 3.1 (or earlier).
MultiNC develops iPhone applications for a number of global markets, and sometimes we run into bugs unique to globalized applications. One application that we’re developing has France as primary market. U.S. users very rarely change their settings from the usual 12-hour mode (AM/PM mode) to 24-hour mode. However, in France it’s not rare for users to switch from the more common 24-hour mode to 12-hour mode. And that’s when things start acting strange, even causing your application to crash.
Because of users’ regional habits, developers of globalized applications are more likely to encounter this time bug than for US applications.
Region format & 24-hour mode setting
First, a little background on the iPhone user interface. When iPhone users change their region format between, say, “United States” and “France”, the users’ “24-Hour Time” setting is automatically switched to the mode that is most prevalent in that region. In France, that would set 24-Hour Time to “ON”, and in the U.S., that would set it to “OFF”. The users can then manually override that setting and that’s where trouble starts.
NSDateFormatter bug
The pattern of this SDK bug was relatively tricky to determine because of the specific combination and sequence of user settings that would trigger it, but now it’s actually quite simple to explain.
The problem comes from NSDateFormatter somehow “getting stuck” in the 12 or 24-hour time mode that the user has manually selected. So if a French user manually selects 12-hour mode, and the application requested NSDateFormatter to output time with the 24-hour format “HHmm”, it would actually receive time in a 12-hour format, e.g. “01:00 PM”, as if the application had instead requested “hhmm aa”. The reverse would happen if a US user manually selected 24-hour mode: outputting time with the 12-hour format “hhmm aa” would actually get you time in the 24-hour format instead, e.g. “17:00″.
This bug turns especially nasty when your application is trying to parse time from a string, rather than outputing. Similar to the above, the NSDateFormatter seems to be stuck in the time mode that the user has manually chosen and insists on reading time that way, regardless of the string format. What you can end up with is an incomplete or invalid NSDate, which when later used can cause other parts of the application to crash, as the UIDatePicker did for us.
We are developing with the iPhone SDK version 3.1 beta, but we wouldn’t be surprised if it applied to all previous SDKs.
Workaround
Now that you know the source of the bug, a workaround is straightforward. But to save you time until Apple fixes it, with our client faberNovel’s gracious permission, here’s our workaround when dealing in 24-hour time (dealing in 12-hour time would be very similar):
// Returns time string in 24-hour mode from the given NSDate +(NSString *)time24FromDate:(NSDate *)date withTimeZone:(NSTimeZone *)timeZone { NSDateFormatter *dateFormatter= [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"HH:mm"]; [dateFormatter setTimeZone:timeZone]; NSString* time = [dateFormatter stringFromDate:date]; [dateFormatter release]; if (time.length > 5) { NSRange range; range.location = 3; range.length = 2; int hour = [[time substringToIndex:2] intValue]; NSString *minute = [time substringWithRange:range]; range = [time rangeOfString:@"AM"]; if (range.length==0) hour += 12; time = [NSString stringWithFormat:@"%02d:%@", hour, minute]; } return time; } // Returns a proper NSDate given a time string in 24-hour mode +(NSDate *)dateFromTime24:(NSString *)time24String withTimeZone:(NSTimeZone *)timeZone { int hour = [[time24String substringToIndex:2] intValue]; int minute = [[time24String substringFromIndex:3] intValue]; NSDateFormatter *dateFormatter= [[NSDateFormatter alloc] init]; [dateFormatter setTimeZone:timeZone]; NSDate *result; if ([Util userSetTwelveHourMode]) { [dateFormatter setDateFormat:@"hh:mm aa"]; if (hour > 12) { result = [dateFormatter dateFromString:[NSString stringWithFormat:@"%02d:%02d PM", hour - 12, minute]]; } else { result = [dateFormatter dateFromString:[NSString stringWithFormat:@"%02d:%02d AM", hour, minute]]; } } else { [dateFormatter setDateFormat:@"HH:mm"]; result = [dateFormatter dateFromString:[NSString stringWithFormat:@"%02d:%02d", hour, minute]]; } [dateFormatter release]; return result; } // Tests whether the user has set the 12-hour or 24-hour mode in their settings. +(BOOL)userSetTwelveHourMode { NSDateFormatter *testFormatter = [[NSDateFormatter alloc] init]; [testFormatter setTimeStyle:NSDateFormatterShortStyle]; NSString *testTime = [testFormatter stringFromDate:[NSDate date]]; [testFormatter release]; return [testTime hasSuffix:@"M"] || [testTime hasSuffix:@"m"]; } // Converts a 24-hour time string to 12-hour time string +(NSString *)time12FromTime24:(NSString *)time24String { NSDateFormatter *testFormatter = [[NSDateFormatter alloc] init]; int hour = [[time24String substringToIndex:2] intValue]; int minute = [[time24String substringFromIndex:3] intValue]; NSString *result = [NSString stringWithFormat:@"%02d:%02d %@", hour % 12, minute, hour > 12 ? [testFormatter PMSymbol] : [testFormatter AMSymbol]]; [testFormatter release]; return result; }
References:
- StackOverflow – NSDateFormatter, am I doing something wrong or is this a bug?
