There are two basic methods you use to create a string representation of a date and to parse a string to get a date object using a date formatter—dateFromString:
and stringFromDate:
respectively. You can also use getObjectValue:forString:range:error:
if you need more control over the range of a string you want to parse.
There are many attributes you can get and set on a date formatter. When you present information to the user, you should typically simply use the NSDateFormatter
style constants to specify pre-defined sets of attributes that determine how a formatted date is displayed. If you need to generate a representation of a date in a precise format, however, you should use a format string.
If you need to parse a date string, the approach you take again depends on what you want to accomplish. If you want to parse input from the user, you should typically use the style constants so as to match their expectations. If you want to parse dates you get from a database or a web service, for example, you should use a format string.
In all cases, you should consider that formatters default to using the user’s locale (currentLocale
) superimposed with the user’s preference settings. If you want to use the user’s locale but without their individual settings, you can get the locale id from the current user locale (localeIdentifier
) and make a new "standard” locale with that, then set the standard locale as the formatter’s locale
.
NSDateFormatter
makes it easy for you to format a date using the settings a user configured in the International preferences panel in System Preferences. The NSDateFormatter
style constants—NSDateFormatterNoStyle
, NSDateFormatterShortStyle
, NSDateFormatterMediumStyle
, NSDateFormatterLongStyle
, and NSDateFormatterFullStyle
—specify sets of attributes that determine how a date is displayed according to the user’s preferences.
You specify the style of the date and time components of a date formatter independently using setDateStyle:
and setTimeStyle:
respectively. Listing 1 illustrates how you can format a date using formatter styles. Notice the use of NSDateFormatterNoStyle
to suppress the time component and yield a string containing just the date.
Formatting a date using formatter styles
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; |
[dateFormatter setDateStyle:NSDateFormatterMediumStyle]; |
[dateFormatter setTimeStyle:NSDateFormatterNoStyle]; |
|
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:162000]; |
|
NSString *formattedDateString = [dateFormatter stringFromDate:date]; |
NSLog(@"formattedDateString: %@", formattedDateString); |
// Output for locale en_US: "formattedDateString: Jan 2, 2001". |
There are broadly speaking two situations in which you need to use custom formats:
For fixed format strings, like Internet dates.
For user-visible elements that don’t match any of the existing styles
To specify a custom fixed format for a date formatter, you use setDateFormat:
. The format string uses the format patterns from the Unicode Technical Standard #35. The version of the standard varies with release of the operating system:
Formatters in OS X v10.8 and iOS 6.0 use version tr35-25.
Formatters in iOS 5.0-5.1 use version tr35-19.
Formatters in OS X v10.7 and iOS 4.3 use version tr35-17.
Formatters in iOS 4.0-4.2 use version tr35-15.
Formatters in iOS 3.2 use version tr35-12.
Formatters in OS X v10.6 and iOS 3.0-3.1 use version tr35-10.
Although in principle a format string specifies a fixed format, by default NSDateFormater
still takes the user’s preferences (including the locale setting) into account. You must consider the following points when using format strings:
NSDateFormatter
treats the numbers in a string you parse as if they were in the user’s chosen calendar. For example, if the user selects the Buddhist calendar, parsing the year 2010
yields an NSDate
object in 1467
in the Gregorian calendar. (For more about different calendrical systems and how to use them, see Date and Time Programming Guide.)
In iOS, the user can override the default AM/PM versus 24-hour time setting. This may cause NSDateFormatter
to rewrite the format string you set.
Note with the Unicode format string format, you should enclose literal text in the format string between apostrophes (''
).
The following example illustrates using a format string to generate a string:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; |
[dateFormatter setDateFormat:@"yyyy-MM-dd 'at' HH:mm"]; |
|
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:162000]; |
|
NSString *formattedDateString = [dateFormatter stringFromDate:date]; |
NSLog(@"formattedDateString: %@", formattedDateString); |
// For US English, the output may be: |
// formattedDateString: 2001-01-02 at 13:00 |
There are two things to note about this example:
It uses yyyy
to specify the year component. A common mistake is to use YYYY
. yyyy
specifies the calendar year whereas YYYY
specifies the year (of “Week of Year”), used in the ISO year-week calendar. In most cases, yyyy
and YYYY
yield the same number, however they may be different. Typically you should use the calendar year.
The representation of the time may be 13:00
. In iOS, however, if the user has switched 24-Hour Time to Off, the time may be 1:00 pm
.
To display a date that contains a specific set of elements, you use dateFormatFromTemplate:options:locale:
]. The method generates a format string with the date components you want to use, but with the correct punctuation and order appropriate for the user (that is, customized for the user’s locale and preferences). You then use the format string to create a formatter.
For example, to create a formatter to display today’s day name, day, and month using the current locale, you would write:
NSString *formatString = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 |
locale:[NSLocale currentLocale]]; |
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; |
[dateFormatter setDateFormat:formatString]; |
|
NSString *todayString = [dateFormatter stringFromDate:[NSDate date]]; |
NSLog(@"todayString: %@", todayString); |
To understand the need for this, consider a situation where you want to display the day name, day, and month. You cannot create this representation of a date using formatter styles (there is no style that omits the year). Neither, though, can you easily and consistently create the representation correctly using format strings. Although at first glance it may seem straightforward, there’s a complication: a user from the United States would typically expect dates in the form, “Mon, Jan 3”, whereas a user from Great Britain would typically expect dates in the form “Mon 31 Jan”.
The following example illustrates the point:
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; |
NSString *usFormatString = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 locale:usLocale]; |
NSLog(@"usFormatterString: %@", usFormatString); |
// Output: usFormatterString: EEE, MMM d. |
|
NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"]; |
NSString *gbFormatString = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 locale:gbLocale]; |
NSLog(@"gbFormatterString: %@", gbFormatString); |
// Output: gbFormatterString: EEE d MMM. |
In addition to the methods inherited from NSFormatter
(such as getObjectValue:forString:errorDescription:
), NSDateFormatter
adds dateFromString:
andgetObjectValue:forString:range:error:
. These methods make it easier for you to use an NSDateFormatter
object directly in code, and make it easier to format dates into strings more complex and more convenient ways than NSString
formatting allows.
The getObjectValue:forString:range:error:
method allows you to specify a subrange of the string to be parsed, and it returns the range of the string that was actually parsed (in the case of failure, it indicates where the failure occurred). It also returns an NSError
object that can contain richer information than the failure string returned by thegetObjectValue:forString:errorDescription:
method inherited from NSFormatter
.
If you're working with fixed-format dates, you should first set the locale of the date formatter to something appropriate for your fixed format. In most cases the best locale to choose isen_US_POSIX
, a locale that's specifically designed to yield US English results regardless of both user and system preferences. en_US_POSIX
is also invariant in time (if the US, at some point in the future, changes the way it formats dates, en_US
will change to reflect the new behavior, but en_US_POSIX
will not), and between platforms (en_US_POSIX
works the same on iPhone OS as it does on OS X, and as it does on other platforms).
Once you’ve set en_US_POSIX
as the locale of the date formatter, you can then set the date format string and the date formatter will behave consistently for all users.
Listing 2 shows how to use NSDateFormatter
for both of the roles described above. First it creates a en_US_POSIX
date formatter to parse the incoming RFC 3339 date string, using a fixed date format string and UTC as the time zone. Next, it creates a standard date formatter to render the date as a string to display to the user.
Parsing an RFC 3339 date-time
- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString { |
/* |
Returns a user-visible date time string that corresponds to the specified |
RFC 3339 date time string. Note that this does not handle all possible |
RFC 3339 date time strings, just one of the most common styles. |
*/ |
|
NSDateFormatter *rfc3339DateFormatter = [[NSDateFormatter alloc] init]; |
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; |
|
[rfc3339DateFormatter setLocale:enUSPOSIXLocale]; |
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; |
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; |
|
// Convert the RFC 3339 date time string to an NSDate. |
NSDate *date = [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; |
|
NSString *userVisibleDateTimeString; |
if (date != nil) { |
// Convert the date object to a user-visible date string. |
NSDateFormatter *userVisibleDateFormatter = [[NSDateFormatter alloc] init]; |
assert(userVisibleDateFormatter != nil); |
|
[userVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle]; |
[userVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle]; |
|
userVisibleDateTimeString = [userVisibleDateFormatter stringFromDate:date]; |
} |
return userVisibleDateTimeString; |
} |
Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, it is typically more efficient to cache a single instance than to create and dispose of multiple instances. One approach is to use a static
variable.
Listing 3 re-implements the method shown in Listing 2 to hold on to the date formatters for subsequent reuse.
Parsing an RFC 3339 date-time using a cached formatter
static NSDateFormatter *sUserVisibleDateFormatter = nil; |
|
- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString { |
/* |
Returns a user-visible date time string that corresponds to the specified |
RFC 3339 date time string. Note that this does not handle all possible |
RFC 3339 date time strings, just one of the most common styles. |
*/ |
|
// If the date formatters aren't already set up, create them and cache them for reuse. |
static NSDateFormatter *sRFC3339DateFormatter = nil; |
if (sRFC3339DateFormatter == nil) { |
sRFC3339DateFormatter = [[NSDateFormatter alloc] init]; |
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; |
|
[sRFC3339DateFormatter setLocale:enUSPOSIXLocale]; |
[sRFC3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; |
[sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; |
} |
|
// Convert the RFC 3339 date time string to an NSDate. |
NSDate *date = [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; |
|
NSString *userVisibleDateTimeString; |
if (date != nil) { |
if (sUserVisibleDateFormatter == nil) { |
sUserVisibleDateFormatter = [[NSDateFormatter alloc] init]; |
[sUserVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle]; |
[sUserVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle]; |
} |
// Convert the date object to a user-visible date string. |
userVisibleDateTimeString = [sUserVisibleDateFormatter stringFromDate:date]; |
} |
return userVisibleDateTimeString; |
} |
If you cache date formatters (or any other objects that depend on the user’s current locale), you should subscribe to the NSCurrentLocaleDidChangeNotification
notification and update your cached objects when the current locale changes. The code in Listing 3 defines sUserVisibleDateFormatter
outside of the method so that other code, not shown, can update it as necessary. In contrast, sRFC3339DateFormatter
is defined inside the method because, by design, it is not dependent on the user’s locale settings.
Note: In theory you could use an auto-updating locale (autoupdatingCurrentLocale
) to create a locale that automatically accounts for changes in the user’s locale settings. In practice this currently does not work with date formatters.
For date and times in a fixed, unlocalized format, that are always guaranteed to use the same calendar, it may sometimes be easier and more efficient to use the standard C library functionsstrptime_l
and strftime_l
.
Be aware that the C library also has the idea of a current locale. To guarantee a fixed date format, you should pass NULL
as the loc
parameter of these routines. This causes them to use the POSIX locale (also known as the C locale), which is equivalent to Cocoa's en_US_POSIX
locale, as illustrated in this example.
struct tm sometime; |
const char *formatString = "%Y-%m-%d %H:%M:%S %z"; |
(void) strptime_l("2005-07-01 12:00:00 -0700", formatString, &sometime, NULL); |
NSLog(@"NSDate is %@", [NSDate dateWithTimeIntervalSince1970: mktime(&sometime)]); |
// Output: NSDate is 2005-07-01 12:00:00 -0700 |