NSDateFormatter – Proceed with Caution!

It will never cease to amaze me how difficult it is to correctly handle times and dates in computer applications. For a concept that is so widely used by just about every person on the planet, it is truly sad how many bugs I have seen due to improper use of date/time libraries.

The most recent date/time bug that I ran into was in iOS application. We were using the NSDateFormatter class to format a printable month and year from an NSDate instance. It seems like that should be a pretty straightforward thing to do, right? Take a look at the following test code and see if you can figure out why it fails.

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [NSLocale autoupdatingCurrentLocale];
dateFormatter.dateFormat = @"MMM YYYY";
dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];

NSString * result = [dateFormatter stringFromDate:[NSDate dateWithISO8601String:@"2015-12-27 12:00:00EST"]];
expect(result).to.equal(@"Dec 2015");

The result of running this test is, “expected: Dec 2015, got: Dec 2016”!

“How can it think that December 12, 2105 is in 2016?” you might ask. That is a great question! It turns out that the problem is with the date format string “MMM YYYY.” The NSDateFormatter uses Unicode Locale Data Markup Language standard for specifying the desired date format. If you look at the official documentation and find the section that describes the date symbol, you will find that capital “Y” is used for:

Year (in “Week of Year” based calendars). Normally the length specifies the padding, but for two letters it also specifies the maximum length. This year designation is used in ISO year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems where week date processing is desired. May not always be the same value as calendar year.

If you were to just skim over that quickly, you might not notice the last, very important, sentence: “May not always be the same value as calendar year.” That is a very crucial detail, and unfortunately, I don’t fully understand why the date referenced above works out to be in 2016–but I do know how to solve the problem.

The important thing to know is that, in most cases, you want to use lowercase “y” for the year component of your date format string. Changing the above code to use “MMM yyyy” for the date format results in the test passing, and sanity is restored.