// Unobtrusive DOM scripting originally developed to provide live item ages on
// http://news.po-ru.com/.
//
// Paul Battley 2005 * pbattley @ gmail.com
//
// Anybody may freely copy and reuse this code.  I hope the comments help.
//
// To use it, just put <span class="date"> </span> around times in your
// document.  They will be postfixed automatically with the constantly-updating
// age.  If you aren't using UTC (though really, on the *World* Wide Web, you
// ought to be) or YYYY-MM-DD and the 24-hour clock, you'll need to make some
// changes.  Please send me any improvements!

// Namespace
if (!DateClock) { var DateClock = {}; };

// Configuration options
// Maximum age in minutes: above this, items will not be given ages.
// 0 means no limit.
DateClock.maximumAge = 0;

//
// Given a date of the format used on the page, calculates its age in days,
// hours, and minutes.
// Returns a string representing the age (e.g. '2h 3m').
// There's a lot of scope for personalisation/localisation.
//
DateClock.calculateAge = function(timestamp) {
    var timeThen = DateClock.dateToUTC(DateClock.parseTimestamp(timestamp));
    var timeNow = DateClock.dateToUTC(new Date());
    var age = timeNow - timeThen;
    if (age < 0) {
        return 0;
    }
    age = age / 60000; // In minutes now

    if (0 != DateClock.maximumAge && age > DateClock.maximumAge) {
        return 0;
    }
    
    var minutes = age % 60;
    var hours = parseInt((age / 60) % 24);
    var days = parseInt(age / 60 / 24);
    // Yes, 365.25 isn't exactly correct, but in this context, it will never
    // show
    var months = parseInt(age / 60 / 24 / (365.25 / 12));
    var years = parseInt(age / 60 / 24 / 365.25);

    if (years > 1) {
        return years + ' years';
    }
    if (years > 0) {
        return years + ' year';
    }
    if (months > 1) {
        return months + ' months';
    }
    if (months > 0) {
        return months + ' month';
    }
    if (days > 1) {
        return days + ' days';
    }
    if (days > 0) {
        return (days * 24) + hours + 'h';
    }
    if (hours > 0) {
        return hours + 'h ' + minutes + 'm ';
    }
    return minutes + 'm ';
}

//
// Parses a string containing humane ISO 8601 date ('YYYY-MM-DD HH:MM UTC').
// Returns a Date object.
//
DateClock.parseTimestamp = function(s) {
    var d = new Date();
    d.setISO8601(s.replace(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/, "$1-$2-$3T$4:$5Z"));
    return d;
}

//
// Converts a Date object to its 'UTC' value.
// Returns the date as the number of ms since 1970-01-01T00:00Z
//
DateClock.dateToUTC = function(d) {
    return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), 0);
}

//
// Updates the ages of all the news items, and sets a timeout handler to call
// itself again in 60 seconds.
// The non-standard but widely-supported innerHTML is used for efficiency.
//
DateClock.updater = function() {
    for (var i = 0; i < DateClock.timestamps.length; i++) {
        var date = DateClock.timestamps[i];
        var age = DateClock.calculateAge(date);
        if (age) {
            DateClock.elements[i].innerHTML = date + ' (' + age + ' ago)';
        } else {
            DateClock.elements[i].innerHTML = date;
        }
    }
    setTimeout('DateClock.updater()', 60000)
}

//
// Walk the DOM to find the elements containing the publication time and age
// of each news item.
// After running:
// DateClock.timestamps contains a list of timestamp-containing elements.
// DateClock.ages is a list of elements that will hold the corresponding ages.
//
DateClock.findElements = function() {
    DateClock.timestamps = new Array;
    DateClock.elements = new Array;
    var spans = document.getElementsByTagName('span');
    var j = 0;
    for (var i = 0; i < spans.length; i++) {
        var s = spans[i];
        if ('date' == s.className) {
            DateClock.timestamps[j] = s.innerHTML;
            DateClock.elements[j] = s;
            j++;
        }
    }
}

//
// Onload handler.  Starts everything working.
//
DateClock.loadHandler = function() {
    DateClock.findElements();
    DateClock.updater();
}

// 
// Obtained from http://delete.me.uk/2005/03/iso8601.html
// Parses an ISO 8601 time.
// Sets the value of the Date object on which it is called to the time
// indicated by the content of 'string'.
//
Date.prototype.setISO8601 = function (string) {
    var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
        "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
        "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
    var d = string.match(new RegExp(regexp));

    var offset = 0;
    var date = new Date(d[1], 0, 1);

    if (d[3]) { date.setMonth(d[3] - 1); }
    if (d[5]) { date.setDate(d[5]); }
    if (d[7]) { date.setHours(d[7]); }
    if (d[8]) { date.setMinutes(d[8]); }
    if (d[10]) { date.setSeconds(d[10]); }
    if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
    if (d[14]) {
        offset = (Number(d[16]) * 60) + Number(d[17]);
        offset *= ((d[15] == '-') ? 1 : -1);
    }

    offset -= date.getTimezoneOffset();
    time = (Number(date) + (offset * 60 * 1000));
    this.setTime(Number(time));
}

//
// A portable means of attaching an onload handler to a window.
//
window.addLoadEvent = function(func) {
    var oldonload = window.onload;
    if (typeof window.onload != 'function') {
        window.onload = func;
    } else {
        window.onload = function() {
            oldonload();
            func();
        }
    }
};

window.addLoadEvent(DateClock.loadHandler);

// END OF FILE

