Compiled Chronicles

A software development blog by Angelo Villegas

NSDate & NSDateFormatter

One of the commonly used classes in building iOS apps are the NSDate (which allows us to deal with dates) and, of course, NSDateFormatter (which allows us to format the date and time to be printed). It is very likely that somewhere in your app, you may need to work with a lot of dates and times; it may be parsing, displaying or performing calendrical calculations.

Date & Time Styles

The most important properties for an NSDateFormatter object are its dateStyle and timeStyle. dateStyle provides five default styles to be used for the most commonly used date format.

StyleExamples
 DateTime
NSDateFormatterNoStyle  
NSDateFormatterShortStyle1/10/149:05 PM
NSDateFormatterMediumStyleJan 10, 20149:05:41 PM
NSDateFormatterLongStyleJanuary 10, 20149:05:41 PM GMT+8
NSDateFormatterFullStyleFriday, January 10, 20149:05:41 PM Philippine Standard Time

dateStyle and timeStyle are set independently to be used for converting to and from a human-readable form, as well as machine-readable form (i.e. string with schema). For example to display just the time, an NSDateFormatter would be configured with a dateStyle of NSDateFormatterNoStyle:

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterNoStyle;
formatter.timeStyle = NSDateFormatterFullStyle;
NSLog( @"date: %@" , [formatter stringFromDate: [NSDate date]] );

You can just leave one of them blank as NSDateFormatterNoStyle is the default. However, it may result to unwanted format in the future.

And the result will look like:

date: 9:05:41 PM Philippine Standard Time

While setting both NSDateFormatterFullStyle will give the following result:

date: Friday, January 10, 2014 at 11:06:00 PM Philippine Standard Time

Date & Time Format

A date pattern is a string of characters, where specific strings of characters are replaced with date and time data from a calendar when formatting or used to generate data for a calendar when parsing.

If you are making a format that is not in the default style, you can create your own using the dateFormat property. This one should be used to customize the date in a way other than the default styles. A format string is used to set the appropriate format for the NSDateFormatter using the patterns described in the unicode’s date format patterns.

PatternResult (in a particular locale)
yyyy.MM.dd G 'at' HH:mm:ss zzz2014.01.10 AD at 09:05:41 GMT+8
EEE, MMM d, ''yySat, Jan 10, ’14
h:mm a9:05 PM
hh 'o''clock' a, zzzz09 o’clock PM, Philippine Standard Time
K:mm a, z9:05 PM, GMT+8
yyyyy.MMMM.dd GGG hh:mm aaa02014.January.10 AD 09:05 PM

The most common format used in the date field symbol table are the year, month, day for date and hour, minute, second, and period.

Be careful with the capitalisation of the letters though, it may give you different results than what you expect. The day pattern, for example, uses the small letter d to display the current date while using capital D will output the day of year.

Relative Date Formatting

As of iOS 4 and OS x 10.6, NSDateFormatter started supporting relative date formatting for certain locales with the doesRelativeDateFormatting property.

Setting the doesRelativeDateFormatting to YES

dateFormatter.doesRelativeDateFormatting = YES;
NSLog( @"date: %@" , [formatter stringFromDate: [NSDate date]] );

will render the result

date: Today

This is a good alternative than rendering the current date for some cases.

NSDateFormatter: A Singleton

As you may know, initialising an NSDateFormatter is a very expensive move. This has been already discussed many times and is a known performance issue especially when using in a list of dates; if you have to parse a lot of dates, expect your app to slow down.

dispatch_once guarantees that the specified block is called only the first time it’s encountered.

A good idea is to use a cached instance and reuse it as proposed by Apple in Cache Formatters for Efficiency where they suggest the use of a static variable to hold the shared instance, as you would in the case of a singleton. We can do this by using the function dispatch_once.

There are two types of singleton the same as creating a method: a static instance or a singleton method.

Static Instance

- (void)formatter:(NSDate *)date
{
    static NSDateFormatter *dateFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
		dateFormatter = [[NSDateFormatter alloc] init];
		dateFormatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss Z";
    });

    NSString *string = [dateFormatter stringFromDate: date];

    // ...
}

Singleton Method

+ (NSDateFormatter *)formatter
{
    static NSDateFormatter *dateFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.dateStyle = NSDateFormatterFullStyle;
        dateFormatter.timeStyle = NSDateFormatterFullStyle;
    });

    return dateFormatter;
}

You must not mutate a given date formatter simultaneously from multiple threads

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.

This solution is simple and works very well except in case of multithreading; NSDateFormatter is not thread safe.

Thread-safety

Since NSDateFormatter is not thread-safe, for this reason, very bad things can happen to your app if you try to access it from multiple thread.

static NSString * const ThreadSafeFormatterKey = @"ThreadSafeFormatterKey";

- (NSDateFormatter *)threadSafeFormatter
{
	NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary];
	NSDateFormatter *dateFormatter = dictionary[ThreadSafeFormatterKey];
	if ( dateFormatter == nil )
	{
		dateFormatter = [[NSDateFormatter alloc] init];
		dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier: @"en_US_POSIX"];
		dateFormatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss Z";
		[dictionary setObject: dateFormatter forKey: ThreadSafeFormatterKey];
	}
	return dateFormatter;
}

The code above will always return the same instance if called in the same thread, if it’s called in a new and different thread, a new instance is created and returned.

Conclusion

If your app deals heavily with date formats, you’ll probably want to create your own NSDateFormatter subclass or category. This methods may help your app perform more efficiently dealing with dates.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *