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:


This post is tagged , , , , , ,

  • http://www.blitsoftware.com/ jorge cabezas

    Hello,

    thanks for all your effort. If it helps to anyone, there is still one bug with 12 p.m. that can be fixed this way:

    if (hour > 12) {
    result = [dateFormatter dateFromString:[NSString stringWithFormat:@"%02d %02d %02d %02d:%02d p.m.", year, month, day, hour - 12, minutes]];
    } else if(hour == 12){
    result = [dateFormatter dateFromString:[NSString stringWithFormat:@"%02d %02d %02d %02d:%02d p.m.", year, month, day, hour, minutes]];
    } else {
    result = [dateFormatter dateFromString:[NSString stringWithFormat:@"%02d %02d %02d %02d:%02d a.m.", year, month, day, hour, minutes]];
    }

    Best regards,

    Jorge

  • http://twitter.com/idotter Marc Widmer

    Hello

    I couldn't faind any contact-info, so i decided to post here (OT).
    I'd like to use the WP Plugin Social Privacy with WP(MU) and Buddypress, but it seems not to work with actual versions, are you still developing it ?

  • Jasper Bryant-Greene

    A simpler workaround for date parsing could be:

    NSString *dateStr = @”2010-07-05″;
    NSString *timeStr = @”13:30″;

    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.year = [[dateStr substringToIndex:4] intValue];
    components.month = [[dateStr substringWithRange:NSMakeRange(5, 2)] intValue];
    components.day = [[dateStr substringFromIndex:8] intValue];
    components.hour = [[timeStr substringToIndex:2] intValue];
    components.minute = [[timeStr substringFromIndex:3] intValue];

    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

    NSDate *date = [calendar dateFromComponents:components];

    [components release];
    [calendar release];

  • jl75

    Nice catch!

    This bug is also present in: “+ (NSString *)time24FromDate:(NSDate *)date withTimeZone:(NSTimeZone *)timeZone”A possible fix is to replace: if (range.length==0)
    hour += 12;
    With: if (range.length == 0) {//PM

    //12:mm PM should be 12:mm, not 24:mm.

    if (hour != 12) {

    hour += 12;

    }

    }

    else {//AM

    //12:mm AM should be 00:mm, not 12:mm.

    if (hour == 12) {

    hour -= 12;

    }

    }Kind regards,Jan Lubbers





About MultiNC

Think up & Code up

Categories