Friday, October 16, 2009

JavaScript Parser for RFC 3339 or ISO 8601 Timestamp Strings

A few days ago I wrote writing a generator for Atom 1.0 timestamps, which are defined as an RFC 3339 profile, which is defined as an ISO 8601 profile. Now I had to parse these strings in another application for chronological sorting. There are several implementations published already but for various reasons they are all lacking. Either the code base has huge dependencies, licensing is unclear, the implementation contains bugs, and so on. So why not invent a rounder wheel?

This is what I came up with:

/*
Internet Timestamp Parser
Copyright (c) 2009 Sebastiaan Deckers
License: GNU General Public License version 3 or later
*/
Date.prototype.setISO8601 = function (timestamp) {
var match = timestamp.match(
"^([-+]?)(\\d{4,})(?:-?(\\d{2})(?:-?(\\d{2})" +
"(?:[Tt ](\\d{2})(?::?(\\d{2})(?::?(\\d{2})(?:\\.(\\d{1,3})(?:\\d+)?)?)?)?" +
"(?:[Zz]|(?:([-+])(\\d{2})(?::?(\\d{2}))?)?)?)?)?)?$");
if (match) {
for (var ints = [2, 3, 4, 5, 6, 7, 8, 10, 11], i = ints.length - 1; i >= 0; --i)
match[ints[i]] = (typeof match[ints[i]] != "undefined"
&& match[ints[i]].length > 0) ? parseInt(match[ints[i]], 10) : 0;
if (match[1] == '-') // BC/AD
match[2] *= -1;
var ms = Date.UTC(
match[2], // Y
match[3] - 1, // M
match[4], // D
match[5], // h
match[6], // m
match[7], // s
match[8] // ms
);
if (typeof match[9] != "undefined" && match[9].length > 0) // offset
ms += (match[9] == '+' ? -1 : 1) *
(match[10]*3600*1000 + match[11]*60*1000); // oh om
if (match[2] >= 0 && match[2] <= 99) // 1-99 AD
ms -= 59958144000000;
this.setTime(ms);
return this;
}
else
return null;
}


Examples:

var foo = (new Date()).setISO8601("2009-10-16T20:11:36.456-07:00");
// Date: Sat Oct 17 05:11:36 UTC+0200 2009

var bar = (new Date()).setISO8601("-0448-05-27T05:52:08.97+11:30");
// Date: Mon May 26 20:51:38 UTC+0200 449 B.C.


Note:
This method can parse any RFC 3339 timestamp, but does not enforce strict validity. It can handle dates that are not valid RFC 3339 such as negative years or years with more than four digits. It also accepts lowercase "T" and "Z", as described in ISO 8601, and shows other minor leniencies. Do not use this method as a validator.

*Edit*
Fixed Firefox support.

*Edit*
Fixed offset minutes calculation.

5 comments:

Vitaly said...

Excellent! Just what I need. Thanks!

Vitaly said...

Although, you code doesn't seem to work for "2004-09-01T00:00:00.000" :(

Sebastiaan Deckers said...

Previously it was working only in Internet Explorer.

I just added Firefox support. Try again.

FrVIPofm said...

I think there is a mistake in
(match[10] * 3600 * 1000 + match[11] * 1000); // oh om

should be
(match[10] * 3600 * 1000 + match[11] * 60000); // oh om

Sebastiaan Deckers said...

Thanks FrVIPofm, I've updated the post with the fix.