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
- 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 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