NSDateFormatter & Http Header

A.S. Update 2, License: as I’ve been asked about which license this code is under: I put this into Public Domain. No warranty whatsoever. Still I’d be happy about attribution but don’t require such.

Ever tried to get e.g. the “Last-Modified” HTTP response header field into a NSDate object? That’s no real fun, because

  1. this standard formatting isn’t digested by default,
  2. the required format string doesn’t quite work as documented.


Let’s concentrate on “Full Date” according to RFC 1123:

NSString *src = @"Fri, 14 Aug 2009 14:45:31 GMT";

The NSDateFormatter switched to Unicode Format strings with OSX 10.4 (see the reference), so I use

NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";

Caution: ‘zzz‘ as timezone format should give the same results, but doesn’t. It says ...GMT+00:00.

As the textual parts must be english (see RFC 1123 for all the wording), I add

df.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];

to be independent of the user’s current locale.

That’s it!

Update:

If [NSDateFormatter dateFromString:...] and [NSDateFormatter stringFromDate:...] are threadsafe, a category-implementation could look like:

#import 
 
/** Category on NSDate to support rfc1123 formatted date strings.
 http://blog.mro.name/2009/08/nsdateformatter-http-header/ and
 http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
 */
@interface NSDate (NSDateRFC1123)
 
/**
 Convert a RFC1123 'Full-Date' string
 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)
 into NSDate.
 */
+(NSDate*)dateFromRFC1123:(NSString*)value_;
 
/**
 Convert NSDate into a RFC1123 'Full-Date' string
 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1).
 */
-(NSString*)rfc1123String;
 
@end
 
@implementation NSDate (NSDateRFC1123)
 
+(NSDate*)dateFromRFC1123:(NSString*)value_
{
    if(value_ == nil)
        return nil;
    static NSDateFormatter *rfc1123 = nil;
    if(rfc1123 == nil)
    {
        rfc1123 = [[NSDateFormatter alloc] init];
        rfc1123.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
        rfc1123.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
        rfc1123.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z";
    }
    NSDate *ret = [rfc1123 dateFromString:value_];
    if(ret != nil)
        return ret;
 
    static NSDateFormatter *rfc850 = nil;
    if(rfc850 == nil)
    {
        rfc850 = [[NSDateFormatter alloc] init];
        rfc850.locale = rfc1123.locale;
        rfc850.timeZone = rfc1123.timeZone;
        rfc850.dateFormat = @"EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z";
    }
    ret = [rfc850 dateFromString:value_];
    if(ret != nil)
        return ret;
 
    static NSDateFormatter *asctime = nil;
    if(asctime == nil)
    {
        asctime = [[NSDateFormatter alloc] init];
        asctime.locale = rfc1123.locale;
        asctime.timeZone = rfc1123.timeZone;
        asctime.dateFormat = @"EEE MMM d HH':'mm':'ss yyyy";
    }
    return [asctime dateFromString:value_];
}
 
-(NSString*)rfc1123String
{
    static NSDateFormatter *df = nil;
    if(df == nil)
    {
        df = [[NSDateFormatter alloc] init];
        df.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
        df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
        df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
    }
    return [df stringFromDate:self];
}
 
@end
 
//////////////////////////////////////////////////////////
// A testcase to try things out.
//////////////////////////////////////////////////////////
 
#import 
 
/**
 http://blog.mro.name/2009/08/nsdateformatter-http-header/ and
 http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
 */
@interface NSDateFormatterTC : SenTestCase
{
 
}
 
@end
 
@implementation NSDateFormatterTC
 
-(void)testAsctime
{
    STAssertEqualObjects(@"Sun, 06 Nov 1994 08:49:37 GMT",
        [[NSDate dateFromRFC1123:@"Sun Nov  6 08:49:37 1994"] rfc1123String], @"fail");
    STAssertEqualObjects(@"Wed, 16 Nov 1994 08:49:37 GMT",
        [[NSDate dateFromRFC1123:@"Wed Nov 16 08:49:37 1994"] rfc1123String], @"fail");
}
 
-(void)testRFC850
{
    STAssertEqualObjects(@"Sun, 06 Nov 1994 08:49:37 GMT",
        [[NSDate dateFromRFC1123:@"Sunday, 06-Nov-94 08:49:37 GMT"] rfc1123String], @"fail");
}
 
-(void)testRFC1123
{
    NSString *s = @"Fri, 14 Aug 2009 14:45:31 GMT";
    STAssertEqualObjects(s, [[NSDate dateFromRFC1123:s] rfc1123String], @"fail");
 
    s = @"Sun, 06 Nov 1994 08:49:37 GMT";
    STAssertEqualObjects(s, [[NSDate dateFromRFC1123:s] rfc1123String], @"fail");
 
    // a strange asymmetry:
    s = @"Sat, 01 Jan 0001 00:00:00 GMT";
    STAssertEqualObjects(@"Sat, 01 Jan 0001 01:27:48 GMT",
        [[NSDate dateFromRFC1123:s] rfc1123String], @"fail");
}
 
-(void)testRawRFC1123
{
    NSDateFormatter *rfc1123 = [[NSDateFormatter alloc] init];
    rfc1123.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
    rfc1123.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
    rfc1123.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
 
    NSString *s = @"Fri, 14 Aug 2009 14:45:31 GMT";
    STAssertEqualObjects(s, [rfc1123 stringFromDate:[rfc1123 dateFromString:s]], @"fail");
 
    s = @"Sun, 06 Nov 1994 08:49:37 GMT";
    STAssertEqualObjects(s, [rfc1123 stringFromDate:[rfc1123 dateFromString:s]], @"fail");
 
    s = @"Sat, 01 Jan 0001 08:00:00 GMT";
    STAssertEqualObjects(s, [rfc1123 stringFromDate:[rfc1123 dateFromString:s]], @"fail");
 
    [rfc1123 release];
}
@end

flattr this!

Comments 10

  1. Jeff Garbers wrote:

    Thanks very much for this code… was having trouble getting Amazon S3 to accept my signatures, and it turned out to be because of the “GMT+00:00″ you mentioned. Your code worked great!

    Posted 07 Okt 2009 at 12:38 am
  2. Joseph Russell wrote:

    Thank you for posting this. I also found it very helpful in an issue I encountered with non-US locale settings on iphone OS causing API signature issues.

    Posted 10 Nov 2009 at 2:31 am
  3. Benjamin Ragheb wrote:

    Thank you for sharing, the part about setting the locale never would have occurred to me.

    Posted 20 Dez 2009 at 11:54 pm
  4. mro wrote:

    this guy blogged almost the same some months earlier: http://www.websector.de/blog/2009/01/25/quick-tip-objective-c-formatting-an-rfc2822-date-of-an-rss-feed-using-nsdateformatter/

    Posted 22 Apr 2010 at 5:07 pm
  5. Michael wrote:

    Thanks for sharing this, came in useful today!

    Posted 12 Jan 2011 at 5:07 pm
  6. bob wrote:

    Your methods are not thread-safe. Consider that one thread runs the method and creates a formatter, and sets the static variable, then gets interrupted before it can set the date format, etc. Then another thread runs and sees that it is not nil, and then proceeds to use it, when it has not been set up. See?

    Also, I am doubtful about your statement that -stringFromDate: is thread-safe. I’ve read from many places on the Internet that it is not.

    Posted 27 Jun 2011 at 11:09 pm
  7. Tony Mann wrote:

    Thanks, worked perfectly.

    Posted 26 Jul 2012 at 5:02 pm
  8. Izzy wrote:

    Thanks for the code. Can you add a license for this code?

    Posted 12 Dez 2012 at 1:01 am
  9. Mark Aufflick wrote:

    FYI. NSDateFormatter is not thread safe, and neither is the icu library which it uses. In some client code I just saw a crash from this code in dateFromString: calling icu::VTimeZone.

    Posted 14 Feb 2013 at 1:20 am
  10. Marcus Rohrmoser wrote:

    I find the plain C approach of MKNetworkKit appealing, but think it brings additional timezone offset. Didn’t try yet, though.

    Posted 01 Mrz 2013 at 2:51 pm

Trackbacks & Pingbacks 4

  1. From Der M-Blog - NSDateFormatter case sensitive trap on 03 Dez 2009 at 4:06 pm

    [...] NSDateFormatter behaves slightly different than documented, the following might even be correct, as strange as it might look (mind the last two lines): [...]

  2. From iWyre on 18 Okt 2010 at 7:50 pm

    [...] responds in English) otherwise the code will fail when run on devices in non-English countries. See this post for more [...]

  3. From How To Download a File Only If It Has Been Updated « iPhone App Development on 22 Dez 2010 at 12:01 pm

    [...] responds in English) otherwise the code will fail when run on devices in non-English countries. See this post for more [...]

  4. From How To Download a File Only If It Has Been Updated | iPhone Development Blog on 18 Jun 2011 at 5:52 pm

    [...] responds in English) otherwise the code will fail when run on devices in non-English countries. See this post for more [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *