Manbolo Blog

Manbolo Team Blog, creators of MeonArchives

NSDateFormatter and Internet Dates

If you want to send or receive dates and times in your iOS app, you will need to insure that you’re parsing or creating the right format string. Let’s take an example: assume we’re creating some NSDate object and we need to send a JSON representation of this NSDate to some web service. In this example, we want to format the date with the RFC 3339-style, a common format used by many Internet protocol.

A first naive approach looks like:

- (NSString *)dateString
{
    // Create our date formatter.
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy’-’MM’-’dd’T’HH’:’mm’:’ssZ"];

    // We will upload the date at UTC 0.
    dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];

    // Format our date object to a suitable string.
    NSDate *date = [[NSDate alloc] init];
    NSString *dateString = [dateFormatter stringFromDate:now];

    return dateString;
}

For instance, if we call this method in France (GMT+2), at 2:02 PM the 3 October 2013, dateString will be 2013-10-03T12:01:07+0000. As you can see, one central class in Cocoa for dates and times handling is NSDateFormatter. NSDateFormater will be your companion when going to/from NSDate and NSString; given NSDateFormater the right format string (in case of RFC 3339-style, you can use @"yyyy’-’MM’-’dd’T’HH’:’mm’:’ssZ"), and you’re good.

Or not… In fact, this innocuous sample code has some issues.

First of all, there may be a performance problem. NSDateFormater can be expensive to create each time you need to format a string. A better approach would be to create an ivar and cache it in order to reuse it each time we need it.

- (NSDateFormatter *)dateFormatter
{
    if (_dateFormatter){
        return _dateFormatter;
    }
    _dateFormatter = [[NSDateFormatter alloc] init];
    [_dateFormatter setDateFormat:@"yyyy’-’MM’-’dd’T’HH’:’mm’:’ssZ"];
    [_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

    return _dateFormatter;
}

- (NSString *)dateString
{
    NSDateFormater *dateFormatter = [self dateFormatter];
    NSDate *date = [[NSDate alloc] init];
    NSString *dateString = [dateFormatter stringFromDate:date];

    return dateString;
}

But there are still some issues. On his iPhone, the user can have chosen a different calendar (Settings > General > International > Calendar), for instance the Buddhist one. Running the example, dateString will return 2556-10-03T12:20:20+0000 simply because in the Buddhist calendar we are in 2556!

You can force your date formatter to use the Gregorian calendar simply by setting the calendar property like this:

_dateFormatter.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

And no matter what calendar the user has chosen, your NSDateFormatter will always return the Gregorian year.

But there is still an issue with this code! On iOS, the user can override the default AM/PM versus 24-hour time setting (via Settings > General > Date & Time > 24-Hour Time), and cause your formatting to failed. Arrgghhhh…

The real solution to this problem is addressed by Apple in the Technical Q&A QA1480 NSDateFormatter and Internet Dates. To format a date to a fixed format regardless of both user and system preferences, you should use an particular local en_US_POSIX that is designed to be independent and fixed between machine. Using en_US_POSIX, you even don’t need to specify to use the Gregorian calendar.

Now our fixed example is:

- (NSDateFormatter *)dateFormatter
{
    if (_dateFormatter){
        return _dateFormatter;
    }
    _dateFormatter = [[NSDateFormatter alloc] init];

    NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

    [_dateFormatter setDateFormat:@"yyyy’-’MM’-’dd’T’HH’:’mm’:’ssZ"];
    [_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [_dateFormatter setLocale:enUSPOSIXLocale];

    return _dateFormatter;
}

- (NSString *)dateString
{
    NSDateFormatter *dateFormatter = [self dateFormatter];
    NSDate *date = [[NSDate alloc] init];
    NSString *dateString = [dateFormatter stringFromDate:date];

    return dateString;
}

Who said handling dates was easy?

If you need more information about how to deal with dates on Cocoa, be sure to read Ole Begemann’s epic serie on Working with Date and Time in Cocoa:

You can also check the Data Formatting Guide on Apple Developer Library to get some additional informations on formatting datas.

From jc.

All Posts