NSDateFormatter & Http Header

Tue, 18. Aug 2009

Categories: en development Tags: Asctime Cocoa Date HTTP Header If-Modified-Since iPhone Last-Modified NSDate NSDateFormatter NSHTTPURLResponse Objective C Request Header Response Header RFC1123 RFC850 SenTestingKit

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:

1NSString *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

1NSDateFormatter *df = [[NSDateFormatter alloc] init];
2df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
3df.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

1df.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:

  1#import 
  2
  3/** Category on NSDate to support rfc1123 formatted date strings.
  4 http://blog.mro.name/2009/08/nsdateformatter-http-header/ and
  5 http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
  6 */
  7@interface NSDate (NSDateRFC1123)
  8
  9/**
 10 Convert a RFC1123 'Full-Date' string
 11 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)
 12 into NSDate.
 13 */
 14+(NSDate*)dateFromRFC1123:(NSString*)value_;
 15
 16/**
 17 Convert NSDate into a RFC1123 'Full-Date' string
 18 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1).
 19 */
 20-(NSString*)rfc1123String;
 21
 22@end
 23
 24@implementation NSDate (NSDateRFC1123)
 25
 26+(NSDate*)dateFromRFC1123:(NSString*)value_
 27{
 28    if(value_ == nil)
 29        return nil;
 30    static NSDateFormatter *rfc1123 = nil;
 31    if(rfc1123 == nil)
 32    {
 33        rfc1123 = [[NSDateFormatter alloc] init];
 34        rfc1123.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
 35        rfc1123.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
 36        rfc1123.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z";
 37    }
 38    NSDate *ret = [rfc1123 dateFromString:value_];
 39    if(ret != nil)
 40        return ret;
 41
 42    static NSDateFormatter *rfc850 = nil;
 43    if(rfc850 == nil)
 44    {
 45        rfc850 = [[NSDateFormatter alloc] init];
 46        rfc850.locale = rfc1123.locale;
 47        rfc850.timeZone = rfc1123.timeZone;
 48        rfc850.dateFormat = @"EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z";
 49    }
 50    ret = [rfc850 dateFromString:value_];
 51    if(ret != nil)
 52        return ret;
 53
 54    static NSDateFormatter *asctime = nil;
 55    if(asctime == nil)
 56    {
 57        asctime = [[NSDateFormatter alloc] init];
 58        asctime.locale = rfc1123.locale;
 59        asctime.timeZone = rfc1123.timeZone;
 60        asctime.dateFormat = @"EEE MMM d HH':'mm':'ss yyyy";
 61    }
 62    return [asctime dateFromString:value_];
 63}
 64
 65-(NSString*)rfc1123String
 66{
 67    static NSDateFormatter *df = nil;
 68    if(df == nil)
 69    {
 70        df = [[NSDateFormatter alloc] init];
 71        df.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
 72        df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
 73        df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
 74    }
 75    return [df stringFromDate:self];
 76}
 77
 78@end
 79
 80//////////////////////////////////////////////////////////
 81// A testcase to try things out.
 82//////////////////////////////////////////////////////////
 83
 84#import 
 85
 86/**
 87 http://blog.mro.name/2009/08/nsdateformatter-http-header/ and
 88 http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
 89 */
 90@interface NSDateFormatterTC : SenTestCase
 91{
 92
 93}
 94
 95@end
 96
 97@implementation NSDateFormatterTC
 98
 99-(void)testAsctime
100{
101    STAssertEqualObjects(@"Sun, 06 Nov 1994 08:49:37 GMT",
102        [[NSDate dateFromRFC1123:@"Sun Nov  6 08:49:37 1994"] rfc1123String], @"fail");
103    STAssertEqualObjects(@"Wed, 16 Nov 1994 08:49:37 GMT",
104        [[NSDate dateFromRFC1123:@"Wed Nov 16 08:49:37 1994"] rfc1123String], @"fail");
105}
106
107-(void)testRFC850
108{
109    STAssertEqualObjects(@"Sun, 06 Nov 1994 08:49:37 GMT",
110        [[NSDate dateFromRFC1123:@"Sunday, 06-Nov-94 08:49:37 GMT"] rfc1123String], @"fail");
111}
112
113-(void)testRFC1123
114{
115    NSString *s = @"Fri, 14 Aug 2009 14:45:31 GMT";
116    STAssertEqualObjects(s, [[NSDate dateFromRFC1123:s] rfc1123String], @"fail");
117
118    s = @"Sun, 06 Nov 1994 08:49:37 GMT";
119    STAssertEqualObjects(s, [[NSDate dateFromRFC1123:s] rfc1123String], @"fail");
120
121    // a strange asymmetry:
122    s = @"Sat, 01 Jan 0001 00:00:00 GMT";
123    STAssertEqualObjects(@"Sat, 01 Jan 0001 01:27:48 GMT",
124        [[NSDate dateFromRFC1123:s] rfc1123String], @"fail");
125}
126
127-(void)testRawRFC1123
128{
129    NSDateFormatter *rfc1123 = [[NSDateFormatter alloc] init];
130    rfc1123.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
131    rfc1123.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
132    rfc1123.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
133
134    NSString *s = @"Fri, 14 Aug 2009 14:45:31 GMT";
135    STAssertEqualObjects(s, [rfc1123 stringFromDate:[rfc1123 dateFromString:s]], @"fail");
136
137    s = @"Sun, 06 Nov 1994 08:49:37 GMT";
138    STAssertEqualObjects(s, [rfc1123 stringFromDate:[rfc1123 dateFromString:s]], @"fail");
139
140    s = @"Sat, 01 Jan 0001 08:00:00 GMT";
141    STAssertEqualObjects(s, [rfc1123 stringFromDate:[rfc1123 dateFromString:s]], @"fail");
142
143    [rfc1123 release];
144}
145@end