I had to deal with handling of various date formats for S3Hub and totally munged it the first couple passes. This post lists some things to be aware of when parsing and formatting HTTP and XML style dates correctly using the Cocoa API’s.
HTTP Date
Some quick background on HTTP dates first. S3 requires sending a ‘Date’ header field as part of an authenticated request. (This and signing the request is a common countermeasure against a replay attack.) The spec says, the format of the date should be “one of the RFC 2616 formats (http://www.ietf.org/rfc/rfc2616.txt)”.
HTTP applications have historically allowed three different formats
for the representation of date/time stamps:
Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
Sun Nov 6 08:49:37 1994 ; ANSI C’s asctime() format
The first format is preferred as an Internet standard and represents
a fixed-length subset of that defined by RFC 1123 [8] (an update to
RFC 822 [9]). The second format is in common use, but is based on the
obsolete RFC 850 [12] date format and lacks a four-digit year.
HTTP/1.1 clients and servers that parse the date value MUST accept
all three formats (for compatibility with HTTP/1.0), though they MUST
only generate the RFC 1123 format for representing HTTP-date values
in header fields. See section 19.3 for further information.
So these are the standard formats. When sending a formatted date you should only use RFC1123 but should be able to handle any of the formats listed. This is just a way to maintain compatibility while trying to move everything to the latest format. If you are wondering what the difference is, RFC 1123 updated 822 by changing the time zone from hour format (±0000) to GMT format (GMT).
For example, the ‘Date’ or ‘Expires’ header fields for requests have this (RFC1123) format. Replies might have an HTTP date format in a Last-Modified or Date header as well.
ISO Date
XML data which has formatted date fields might use a date format which is defined by the ISO8601 standard and looks like ’2006-02-03T16:45:09.000Z’.
Date parsing in Cocoa
Prior to 10.4, the Cocoa API supported “strftime-style conversion specifiers”. In 10.4, they added support for the Unicode Technical Standard #35 (version tr35-6).
The pre-10.4 strftime patterns look like: “%m/%d/%y” and the Unicode standard looks like “MM/dd/yyyy”. The default behavior (for backwards compatibility) is strftime format. In order to use the Unicode standard (which you will, its way better), you need to set the formatter behavior to NSDateFormatterBehavior10_4 either by calling setFormatterBehavior or setting the classes formatter behavior globally. Do NOT use the initWithDateFormat:allowNaturalLanguage: constructor, because it will give you a pre-10.4 date formatter. Using the strftime style of pattern when configured for the 10.4 (Unicode) behavior will fail silently.
So fast forward a few weeks, an S3Hub user told me he was getting authentication errors (invalid signing) trying to connect. I couldn’t replicate the problem but I eventually got the raw HTTP request header it was sending and the Date header looked like.
Fr, 06 Jun 2008 08:49:37 GMT.
Turns out the user is in Germany and when I change my locale to something German, I can reproduce the problem. Date formatters themselves can have specific styles (and symbols) that are modified by the locale. In the German locale the weekday symbol for friday is ‘Fr’ instead of English locale which is ‘Fri’. So when you use a date formatter in this way (with day or month symbols, for example) make sure to set the locale to en_US. The final date formatter looks like:
NSDateFormatter *rfc1123DateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[rfc1123DateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[rfc1123DateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]];
[rfc1123DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[rfc1123DateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
Update: See GHNSDate+Parsing gh-kit category on github for a collection of these date formatters.