Ever tried to get e.g. the “Last-Modified” HTTP response header field into a NSDate object? That’s no real fun, because
- this standard formatting isn’t digested by default,
- 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
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 ¶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 ¶Thank you for sharing, the part about setting the locale never would have occurred to me.
Posted 20 Dez 2009 at 11:54 pm ¶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
[...] 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