One of html's deficiencies is that it doesn't have a tag to mark up a date.

If html had such a tag, then browsers could display dates using your time zone, and in whatever format you like. Rather than in some random time zone, that is often not even shown, and some random format.

My fav way to see a date is as relative date, such as "two days ago". With a mouseover so I can see the exact, absolute date in the rare case I need it.

It's possible to combine some javascript with html, so that, if javascript is available, a nicely formatted relative date is displayed. And with no javascript, an plain old absolute date is shown. Perhaps the cleanest way to do it is using html like this, combined with javascript that fires on page load, parses the date and replaces it with something nicer:

<span class="date">Fri Oct 17 16:17:02 2008 -0400</span>

But you might want to avoid using the ugly machine-parseable date, since it will still display in any situation where javascript isn't available. To do that, I added an attribute containing the machine-parseable date. I chose to use the "title" attribute, so as a bonus you can hover over it to get a popup with the raw date.

<span class="date" title="Fri Oct 17 16:17:02 2008 -0400">Friday afternoon, October 17th</span>

Update: Happily, html5 adds a time tag. Unhappily, it needs the date in RFC 3339 format, which javascript can't parse with "new Date". So in order to use it, I include the javascript-parsable (also human-readable) date redundantly either inside the body of the tag, or in the title attribute.

<time datetime="2010-04-22T12:07:05.001-04:00">Thu, 22 Apr 2010 12:07:05 -0400</time>

<time datetime="2010-04-22T12:07:05.001-04:00" title="Thu, 22 Apr 2010 12:07:05 -0400" class="relativedate">at noon on Thursday, April 22nd, 2010</time>

So, relatively clean html out of the way, now we just need some javascript to munge in the relative dates. There are various examples out there. The best one, requires the prototype framework.

I try to stay away from javascript frameworks, since I'm not a javascript programmer, and since I dislike page loads pulling in thousands of lines of irrelevant code. So I came up with my own more bare metal implementation, which handles both types of html above.

You can see it in action on my website, or by enabling the relativedate plugin on your own ikiwiki.

(This code is in the public domain, but do note that I'm not a javascript programmer, really. Also, ikiwiki contains a newer (maintained) version.)


var dateElements;
window.onload = getDates;

function getDates() {
    dateElements = getElementsByClass('date');
    for (var i = 0; i < dateElements.length; i++) {
        var elt = dateElements[i];
        var title = elt.attributes.title;
        var d = new Date(title ? title.value : elt.innerHTML);
        if (! isNaN(d)) {
            dateElements[i].date=d;
            if (! title) {
                elt.title=elt.innerHTML;
            }
        }
    }

    showDates();
}

function showDates() {
    for (var i = 0; i < dateElements.length; i++) {
        var elt = dateElements[i];
        var d = elt.date;
        if (! isNaN(d)) {
            elt.innerHTML=relativeDate(d);
        }
    }
    setTimeout(showDates,30000); // keep updating every 30s
}

function getElementsByClass(cls, node, tag) {
        if (document.getElementsByClass)
                return document.getElementsByClass(cls, node, tag);
        if (! node) node = document;
        if (! tag) tag = '*';
        var ret = new Array();
        var pattern = new RegExp("(^|\\s)"+cls+"(\\s|$)");
        var els = node.getElementsByTagName(tag);
        for (i = 0; i < els.length; i++) {
                if ( pattern.test(els[i].className) ) {
                        ret.push(els[i]);
                }
        }
        return ret;
}

var timeUnits = new Array;
timeUnits['minute'] = 60;
timeUnits['hour'] = timeUnits['minute'] * 60;
timeUnits['day'] = timeUnits['hour'] * 24;
timeUnits['month'] = timeUnits['day'] * 30;
timeUnits['year'] = timeUnits['day'] * 364;
var timeUnitOrder = ['year', 'month', 'day', 'hour', 'minute'];

function relativeDate(date) {
    var now = new Date();
    var offset = date.getTime() - now.getTime();
    var seconds = Math.round(Math.abs(offset) / 1000);

    var ret = "";
    var shown = 0;
    for (i = 0; i < timeUnitOrder.length; i++) {
        var unit = timeUnitOrder[i];
        if (seconds >= timeUnits[unit]) {
            var num = Math.floor(seconds / timeUnits[unit]);
            seconds -= num * timeUnits[unit];
            if (ret)
                ret += "and ";
            ret += num + " " + unit + (num > 1 ? "s" : "") + " ";

            if (++shown == 2)
                break;
        }
        else if (shown)
            break;
    }

    if (! ret)
        ret = "less than a minute "

    return ret + (offset < 0 ? "ago" : "from now");
}

discussion