JavaScript ISO8601/RFC3339 Date Parser

Updated (bug fix): 2-11-09

In need of a JavaScript function that would parse an ISO8601 compliant date, I came across an attempt (http://delete.me.uk/2005/03/iso8601.html) and rewrote it (because I'm all about reinventing the wheel). My function extends the Date object and allows you to pass in an ISO8601 date (2008-11-01T20:39:57.78-06:00). The function will then return the Date. In the date string argument the dashes and colons are optional. The decimal point for milliseconds is mandatory (although specifying milliseconds isn't). If a timezone offset is specified, the '+' or '-' sign must be included. This function should also work with iCalendar(RFC2445) formatted dates. If a the date string doesn't match the format, there will be a final attempt to parse it with the built in Date.parse() method.

Code:


Date.prototype.setISO8601 = function(dString){

var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)(\.\d+)?(Z|([+-])(\d\d)(:)?(\d\d))/;

if (dString.toString().match(new RegExp(regexp))) {
var d = dString.match(new RegExp(regexp));
var offset = 0;

this.setUTCDate(1);
this.setUTCFullYear(parseInt(d[1],10));
this.setUTCMonth(parseInt(d[3],10) - 1);
this.setUTCDate(parseInt(d[5],10));
this.setUTCHours(parseInt(d[7],10));
this.setUTCMinutes(parseInt(d[9],10));
this.setUTCSeconds(parseInt(d[11],10));
if (d[12])
this.setUTCMilliseconds(parseFloat(d[12]) * 1000);
else
this.setUTCMilliseconds(0);
if (d[13] != 'Z') {
offset = (d[15] * 60) + parseInt(d[17],10);
offset *= ((d[14] == '-') ? -1 : 1);
this.setTime(this.getTime() - offset * 60 * 1000);
}
}
else {
this.setTime(Date.parse(dString));
}
return this;
};

Usage:


var today = new Date();
today.setISO8601('2008-12-19T16:39:57.67Z');

Tags: , ,

  • http://carter.awarenessnetworks.com David Carter

    Thanks, it helped with an Atom feed I am playing around with!

  • Jordan

    Try this simplification of lines 3 thru 8:

    var d, regexp = /(dddd)(-)?(dd)(-)?(dd)(T)?(dd)(:)?(dd)(:)?(dd)(.d+)?(Z|([+-])(dd)(:)?(dd))/;

    if ((d = dString.toString().match(regexp))) {

    You can perform an assignment within an if statement condition expression (or within any other expression) if you simply add a layer of parentheses; the assigned value becomes the value of the parenthetical expression. In some cases, this simplifies logic, while in other cases (like this one) it avoids redundant computations. (What you cannot do within the if condition is declare the var.) And there is no need to declare a new RegExp object: for the match method: the original object will do just fine.

    A related tip is that you can perform multiple assignments within the initialization, test and increment terms of a for statement, merely by delimiting them with commas. In the case of the test term, it is important to remember that the value of the LAST (rightmost) delimited expression or assignment will be the value used for the test. Thus:

    for (i=0,k=4; z=k*i,i<limit; j++, k–) { …

  • http://reminderer.net Daniel

    Shorter function:

    function parseIsoDate(s){
    var d=new Date(s.slice(0,19).replace(‘T’,’ ‘)+’ GMT’);
    // you can return d here if you’re willing to truncate milliseconds
    var ms=s.split(/\D/)[6]; milli/micro-seconds are 7th: y,m,d,H,M,S,MS
    d.setUTCMilliseconds(ms? parseInt(ms.slice(0,3),10): 0);
    return d;
    }

  • http://haltingthought.posterous.com Daniel

    Your handling of milliseconds is wrong. You should convert the digits after the decimal to a decimal fraction and then multiply by 1000 to get milliseconds.

    Here’s another shorter function:

    function parseIsoDate(s){
    var re=/(\d\d\d\d)\D?(\d\d)\D?(\d\d)\D?(\d\d)\D?(\d\d\D?(\d\d\.?(\d*))?)(Z|[+-]\d\d?(:\d\d)?)?/;
    var a=re(s).slice(1).map(function(x,i){
    if (i==6 && x) x=parseInt(x,10)/Math.pow(10,x.length)*1000; // convert to milliseconds
    return parseInt(x,10)||0;
    });
    return new Date(
    Date.UTC(a[0],a[1]-1,a[2],a[3]-(a[7]||0),a[4],a[5],a[6]));
    };