Saturday, October 24, 2009

Draw your own conclusions

Entrepreneurs in Kunshan, China, 24/10/2009
3G Industry Summit
3G Industry Summit

Entrepreneurs in Brussels, Belgium, 24/10/2009
Bizcamp
Bizcamp

Lest we forget...
Entrepreneurs in Brussels, Belgium, anno 1911
Solvay Conference
Solvay Conference
(Curie, Poincaré, Rutherford, Einstein, ...)

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.

Tuesday, October 13, 2009

Generating RFC 3339 or ISO 8601 Timestamps in JavaScript

I've been working on a generator for a custom Atom 1.0 feed. The Atom 1.0 specification (RFC 4287) requires Internet Timestamps (RFC 3339) which are based on ISO 8601 but these are not supported by the JavaScript Date object. Failing to find a drop-in JavaScript snippet which could generate the necessary timestamp format, I created the following function. It generates the Internet timestamp string from either the current time or a JavasScript Date object passed as an argument.

/*
Internet Timestamp Generator
Copyright (c) 2009 Sebastiaan Deckers
License: GNU General Public License version 3 or later
*/
var timestamp: function (date) {
var pad = function (amount, width) {
var padding = "";
while (padding.length < width - 1 && amount < Math.pow(10, width - padding.length - 1))
padding += "0";
return padding + amount.toString();
}
date = date ? date : new Date();
var offset = date.getTimezoneOffset();
return pad(date.getFullYear(), 4)
+ "-" + pad(date.getMonth() + 1, 2)
+ "-" + pad(date.getDate(), 2)
+ "T" + pad(date.getHours(), 2)
+ ":" + pad(date.getMinutes(), 2)
+ ":" + pad(date.getSeconds(), 2)
+ "." + pad(date.getMilliseconds(), 3)
+ (offset > 0 ? "-" : "+")
+ pad(Math.floor(Math.abs(offset) / 60), 2)
+ ":" + pad(Math.abs(offset) % 60, 2);
}


Examples:
timestamp();
// "2009-10-13T13:34:52.000+02:00"

timestamp(new Date(1983, 4, 9, 11, 5, 17));
// "1983-05-09T11:05:17.000+02:00"


Hope this helps anyone.