Blog Archives

Taming NSDate-Utilities

NSDate-Utilities is a part of a great library for working with NSCalendar and NSDate. It provides uber-convenient methods on NSDate for finding out, for example, whether the NSDate receiver falls within the current day. These utilities are fantastic, but unfortunately, they’re also a bit on the slow side.

Why so slow?

Many of the methods rely on getting NSDateComponents from a date via the NSCalendar. Each sends an independent call to [NSCalendar currentCalendar]. Unfortunately, getting the current calendar seems really slow. The latest SalesBag release will feature a very robust set of calendar views to help salespeople schedule their meetings. This means a ton of calculations on NSDate inside tight loops. Using the NSDate-Utilities methods and the time profiler, I found the app spending nearly half a second in [NSCalendar currentCalendar]. This is not good.

Diagnosis

The NSDate-Utilities methods that spend time with NSCalendar all call out to [NSCalendar currentCalendar] independently. For general use, this is probably fine and leaves the app a little leaner on memory. It also (seems to) guarantee that the calendar will always be right if the user changes it mid stream in the app. For Salesbag, this won’t be a problem. We assume that salespeople in the US will be using the gregorian calendar.

Fixing

To speed things up I first created a static NSCalendar variable at the top of the m file that we’ll use in place of grabbing NSCalendar fresh every time.

static NSCalendar *curCalendar = nil;

Then I replaced the #define for current calendar with my static variable

#define CURRENT_CALENDAR curCalendar

Lastly, I need to create the calendar when needed. For this I created a new macro that I placed at the head of any method using the CURRENT_CALENDAR macro:

#define INIT_CURRENT_CALENDAR if (curCalendar == nil) curCalendar = [[NSCalendar currentCalendar] retain];

// later ...
- (BOOL) isSameYearAsDate: (NSDate *) aDate
{
    INIT_CURRENT_CALENDAR
	NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self];
	NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:aDate];
	return ([components1 year] == [components2 year]);
}

Improvement

The time profiler showed the time spent in [NSCalendar currentCalendar] dropped to a measly 35ms. Something I can certainly live with. Of course there’s no such thing as a free lunch and any developer would point out that my static won’t be deallocated until the app terminates. In this case though, the memory trade off was worth it for the speed increase.

In case you didn’t know, NSDateFormatter is SLOOOOOW

NSDateFormatter is the defacto way of turning dates into strings and strings into dates.  Unfortunately it’s also slow as molasses even with reuse and never changing the format string.  This quick tip is about using NSCalendar to handle getting dates from strings, especially in cases where you have to perform a ton of these operations (thousands) in a short amount of time.

Let’s take some dates in a format that looks like this: YYYYMMDDHHMMSS (this is a real example from some work I’ve been doing).  Also, in this case all months and days are padded with zeros such that 1/1/2010 comes across as 20100101000000.  To turn the string into a date we take the following approach:


NSString *yearString = [dateString substringWithRange:NSMakeRange(0, 4)];
NSString *monthString = [dateString substringWithRange:NSMakeRange(4, 2)];
NSString *dayString = [dateString substringWithRange:NSMakeRange(6, 2)];
NSString *hourString = [dateString substringWithRange:NSMakeRange(8, 2)];
NSString *minString = [dateString substringWithRange:NSMakeRange(10, 2)];
NSString *secString = [dateString substringWithRange:NSMakeRange(12, 2)];

NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:[yearString intValue]];
[comps setMonth:[monthString intValue]];
[comps setDay:[dayString intValue]];
[comps setHour:[hourString intValue]];
[comps setMinute:[minString intValue]];
[comps setSecond:[secString intValue]];
NSDate *theDate = [[NSCalendar currentCalendar] dateFromComponents:comps];

Believe it or not the above code cut the time spent formatting dates by about 75% (looking at it through time profiler).