NSDateFormatter & Http Header

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 are in english, 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

Comments 4

  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:

    a collegue of mine 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

Trackbacks & Pingbacks 1

  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): [...]

Post a Comment

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