Javascript: ISO8601 parser and pretty dates
Compared to PHP, with its many helpful date functions, working with dates in Javascript is a pain in the neck.
The js Date object can parse some human-readable strings into a date, but with the rise of RSS etc (in my case it was working with Google gdata apis…) you’ll often be encountering ISO8601 date strings. Apparently ECMAScript 5 will include built in methods to parse this standard format, but until that’s available it’s a bit of a headache.
I went looking around and soon discovered that none other than John Resig, of jQuery fame, had posted a script to not only parse the ISO8601 date (provided it’s in UTC time) but also output a nice string like ‘5 minutes ago’, ‘1 year ago’ etc.
http://ejohn.org/blog/javascript-pretty-date/
I tried it out but couldn’t get it to work. In the comments on his page there were other people posting adapted versions. Zach Leatherman’s one looked promising:
http://www.zachleat.com/web/2008/03/23/yet-another-pretty-date-javascript/
I couldn’t get that to work either. The functions were just returning the original UTC date string back to me. Looking into the code it was clear they were returning that as a default, because they were unable to parse the date string.
The problem seems to be that they all do something like this:
var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," ");
var date = Date(time);
That means asking the js Date object to parse a string like ‘2008/11/23 08:00:00.000′ into a Date.
As far as I can tell it just doesn’t work. No one else seems to have pointed this out so I’m prepared to accept that I may have missed something – if you know what, let me know.
Perhaps it’s locale/platform dependent (I’m on Win XP, UK, FF3)… maybe on a Mac or in the U.S.A. these scripts work fine.
The examples given in most Javascript documentation for parsing date strings use a format like: ‘23 Nov, 2008 08:00:00′ …but that’s hard to generate from a ISO string.
Well, recently I recalled having to write an ISO8601 parser in Actionscript 2, so I dug it up, removed all the type-hints etc and ended up with this:
function parseISO8601(str) {
// we assume str is a UTC date ending in 'Z'
var parts = str.split('T'),
dateParts = parts[0].split('-'),
timeParts = parts[1].split('Z'),
timeSubParts = timeParts[0].split(':'),
timeSecParts = timeSubParts[2].split('.'),
timeHours = Number(timeSubParts[0]),
_date = new Date;
_date.setUTCFullYear(Number(dateParts[0]));
_date.setUTCMonth(Number(dateParts[1])-1);
_date.setUTCDate(Number(dateParts[2]));
_date.setUTCHours(Number(timeHours));
_date.setUTCMinutes(Number(timeSubParts[1]));
_date.setUTCSeconds(Number(timeSecParts[0]));
if (timeSecParts[1]) _date.setUTCMilliseconds(Number(timeSecParts[1]));
// by using setUTC methods the date has already been converted to local time(?)
return _date;
}
It’s nowhere near as elegant as John Resig’s one-liner, but it does seem to work ok for me.
A couple of things to note:
- It won’t parse any old ISO8601 date, it requires a UTC date (ending in ‘Z’)
- I discovered with fascination/horror that the setUTCMonth() method needs a month value in the range 0-11 ! Days start at 1 as you’d expect.
- I am pretty sure that by using the setUTC methods the resulting Date object has already converted the supplied date to local timezone
- The milliseconds part of the date string is optional but all the other parts are required. Should seconds be optional? Should the whole time be optional?
So with this parser function I was able to get Zach’s ‘humane_date’ script working, by changing the following lines:
var time = parseISO8601(date_str), dt = new Date, seconds = (dt.getTime() - time.getTime()) / 1000, token = ' Ago', i = 0, format;
I’d love to get your feedback, especially any bugs or improvements that could be made.