Parsing rfc3339 dates with NSDateFormatter in iOS

2019-03-14 19:05发布

Parsing a rfc3339 date with NSDateFormatter appears to be impossible, in the general case. Am I wrong? [Edit 2 years later: there is now a way! See below and footnote.]

A not-especially-malleable web service is feeding me dates like:

2009-12-31T00:00:00-06:00

Rfc3339 compliant, default output of the jaxb library they're using. Note the colon, which rfc3339 requires when the offset isn't a literal "z":

time-numoffset  = ("+" / "-") time-hour ":" time-minute
time-offset     = "Z" / time-numoffset

I want to parse these into NSDates.

NSDateFormatter wants patterns in the syntax specified by Unicode, which offers date field symbols for timezones like "PDT", "-0800", "GMT-08:00" but not "-08:00".

Googling, and other similar SO questions, produces only date formats like

[myDateParser setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"];
/* or: */ [myDateParser setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];

The latter of which requires a literal "Z", and the former insists either the absence of a colon or presence of a "GMT". However, they appeared to work before ios 4.x (possibly by discarding the tz offset completely; my data aren't clear.)

My options at this point are a sorry lot:

  • discover some undocumented format specifier, or some strange mode to put NSDateFormatter into, that will accept the stray colon: longshot, likely nonexistent. [footnote]
  • persuade my service publisher to turn all dates into zulu time and specify 'Z': politically challenging.
  • write my own NSFormatter subclass or research good old strptime_l: work. :)
  • string-manipulate my input and strip the last colon: brittle and ugly, but the likely path of least resistance.

Have I understood the situation accurately, that current NSDateFormatter follows unicode strictly without extensions; and the unicode formats are insufficient to fully describe an rfc3339 date?

[FOOTNOTE] I come back to this three years later to tack on a small addendum: Unicode and Apple have added this feature to the format strings, as of iOS6/OSX10.8. Compare The latest revision as of this writing with its immediate predecessor, and note the addition of 5 "Z"s, which yields a zone format like "-08:00". So if you can get away with ditching support for 5.x/10.7, there's a new right way to do it. I'll leave the previous answer stand, as it's still the best approach when backward compatibility is required.

3条回答
我命由我不由天
2楼-- · 2019-03-14 19:52

Date string parsing in Cocoa can be a pain, especially if you have to deal with dates generated by .NET based web services.

I'd suggest looking at the NSDate+InternetDateTime category that Michael Waterfall has on NSDate as part of his MWFeedParser project on github. It's worked well for me parsing exactly the format of date you describe.

https://github.com/mwaterfall/MWFeedParser/

查看更多
不美不萌又怎样
3楼-- · 2019-03-14 19:53

The string format characters are documented nowhere in the Apple documentation. Instead there is a link hidden deep in some document pointing to the Unicode standard at

http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns

Using the information at that link it's quite simple:

- (NSDate*)dateFromRFC3339String:(NSString*)aString
{
    static NSDateFormatter* sRFC3339DateFormatter = nil;
    static NSDateFormatter* sRFC3339DateFormatterSubSeconds = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

        sRFC3339DateFormatter = [[NSDateFormatter alloc] init];
        [sRFC3339DateFormatter setLocale:enUSPOSIXLocale];
        [sRFC3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssXXXXX"];
        [sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

        sRFC3339DateFormatterSubSeconds = [[NSDateFormatter alloc] init];
        [sRFC3339DateFormatterSubSeconds setLocale:enUSPOSIXLocale];
        [sRFC3339DateFormatterSubSeconds setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSXXXXX"];
        [sRFC3339DateFormatterSubSeconds setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    });

    NSDate* date = [sRFC3339DateFormatter dateFromString:aString];
    if (date == nil)
        date = [sRFC3339DateFormatterSubSeconds dateFromString:aString];

    return date;
}
查看更多
Ridiculous、
4楼-- · 2019-03-14 20:03

– getObjectValue:forString:range:error: can actually parse RFC3339 dates correctly. I have no idea why - dateWithString: cannot:

// RFC3339 date formatting
NSString *dateString = @"2012-04-11T18:34:19+00:00";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";

NSDate *date;
NSError *error;
[formatter getObjectValue:&date forString:dateString range:nil error:&error];
查看更多
登录 后发表回答