Home

Awesome

ISO 8601: The only date format worth using

Image: Status of most recent build and test attempt. Image: Test code coverage percentage.

Obligatory relevant xkcd:

Seriously now. “ISO 8601 was published on 06/05/88 and most recently amended on 12/01/04.”

How to use this code in your program

Add the source files to your project.

Parsing

Create an ISO 8601 date formatter, then call [formatter dateFromString:myString]. The method will return either an NSDate or nil.

There are a total of six parser methods. The one that contains the actual parser is -[ISO8601DateFormatter dateComponentsFromString:timeZone:range:]. The other five are based on this one.

The “outTimeZone” parameter, when not set to NULL, is a pointer to an NSTimeZone *variable. If the string specified a time zone, you'll receive the time zone object in that variable. If the string didn't specify a time zone, you'll receive nil.

The “outRange” parameter, when not set to NULL, is a pointer to NSRange storage. You will receive the range of the parsed substring in that storage.

Unparsing

Create an ISO 8601 date formatter, then call [formatter stringFromDate:myDate]. The method will return a string.

The formatter has several properties that control its behavior:

How to test that this code works

UPDATE from the year 2013: Conversion from the old make-based test monsters to modern OCUnit-based tests is underway. Contributions of new and old test cases will be greatly appreciated.

'make test' will perform all tests. If you want to perform only some tests:

Parsing

Type 'make parser-test'. make will build the test program (testparser), then invoke testparser.sh.py to generate testparser.sh. Then make will invoke testparser.sh, which will invoke the test program with various dates.

If you don't want to use my tests, 'make testparser' will create the test program without running it. You can then invoke testparser yourself with any date you want to. If it doesn't give you the result you expected, contact me, making sure to provide me with both the input and the output.

Unparsing

Type 'make unparser-test'. make will build the test programs, then invoke testunparser.sh. This shell script invokes each test program for -01-01 of every year from 1991 to 2010, writing the output to a file, and then runs diff -qs between that file (testunparser.out) and a file (testunparser-expected.out) containing known correct output. diff should report that the files are identical.

Three test programs are included: unparse-date, unparse-weekdate, and unparse-ordinal date. If you don't want to use my tests, you can make these test programs separately. Each takes a date specified by ISO 8601 (parsed with my own ISO 8601 parser), and outputs a string that should represent the same date.

Notes

Version history

This version is 0.8. Changes from 0.7:

This version is 0.7. Changes from 0.6:

Changes in 0.6 from 0.5:

Changes in 0.5 from 0.4:

Changes in 0.4 from 0.3:

Changes in 0.3 from 0.2:

Changes in 0.2 from 0.1:

Implementation details

Parsing

Whitespace before a date, and anything after a date, is ignored. Thus, " T23 and all's well" is a valid date for the purpose of this method. (Yes, T23 is a valid ISO 8601 date. It means 23:00:00, or 11 PM.)

All of the frills of ISO 8601 are supported, except for extended dates (years longer than 4 digits). Specifically, you can use week-based dates (2006-W2 for the second week of 2006), ordinal dates (2006-365 for December 31), decimal minutes (11:30.5 == 11:30:30), and decimal seconds (11:30:10.5). All methods of specifying a time zone are supported.

ISO 8601 leaves quite a bit up to the parties exchanging dates. I hope I've chosen reasonable defaults. For example (note that I'm writing this on 2006-02-24):

None of the above defaults apply if you're using the NSDateComponents-based API.

When a date is parsed that has a year but no century, this implementation adds the current century.

The implementation is tolerant of out-of-range numbers. For example, "2005-13-40T24:62:89" == 1:02 AM on 2006-02-10. Notice that the month (13 > 12), date (40 > 31), hour (24 > 23), minute (62 > 59), and second (89 > 59) are all out-of-range.

As mentioned above, there is a "strict" mode that enforces sanity checks. In particular, the date must be the entire contents of the string, and numbers are range-checked. If you have any suggestions on how to make this mode more strict, please file an enhancement request in the Issues section.

Unparsing

I use Rick McCarty's algorithm for converting calendar dates to week dates, slightly tweaked.

Bugs

Parsing

Contributing

This project adheres to [the Contributor Covenant, version 1.4](CODE OF CONDUCT.md). Please read that before deciding whether you are willing to contribute.

You're welcome to work on any open bug in the issue tracker, but we do have some that are up-for-grabs that are particularly easy or potentially interesting.

Please don't break compatibility with old OS versions (OS X 10.7/iOS 4) until work begins on the 1.0 release. That won't happen until 0.9 is done, and starting 1.0-only work (e.g., ARCification) early will lead to conflicts.

The tests use ARC, but the formatter itself does not (yet). Please be sure to add the needed release or autorelease messages when adding code that creates objects.

Adding test cases is highly encouraged, especially if adding new functionality. Ideally, please test a comprehensive set of:

Travis CI will run the tests automatically when you submit a pull request, and a PR with failing tests will not be accepted.

Contributions consisting of nothing but test cases are welcome. If one/some of those cases fail, please include fixes, or at least, submit an issue report for the failure(s) and surround the failing tests with:

#if FIXED_(issue number)
- (void) test_thingThatDoesntWorkButShould {
    ⋮
}
#endif FIXED_(issue number)

As far as code style, mostly, please try to remain consistent with the existing code. Please do add nullability annotations (ISO8601_NULLABLE/ISO8601_NONNULL) and const to new code whenever applicable.

Copyright

This code is copyright 2006–2016 Peter Hosey. It is under the BSD license; see LICENSE.txt for the full text of the license.