JavaScript <time> to time ago / future

A small JS library to convert HTML 5 <time> tags to an x time ago / in future. For example:

<time datetime="2016-08-03">3 Aug 2016</time>

Will be displayed as instead of .

Why re-invent the wheel? The only JS libraries I could find were either jQuery based (I’m not using jQuery so don’t want the extra bloat) or did far more than I actually wanted. So I decided to create this one.

Date parsing

The hardest part is parsing the date. According to the HTML5 W3 spec the <time> tag’s datetime attribute should be in RFC 3339 format. The RFC 3339 is a subset of ISO 8601 with the exception that it allows -00:00 timezones.

The Date.parse method will parse a subset of ISO 8601 which, as far as I can tell, covers all the RFC 3339 subset. So by removing any trailing -00:00 we should be able to parse any RFC 3339 date:

var date = Date.parse(dateTime.replace(/\-00:?00$/, ""));

Browser support

This works in Chrome, Firefox, Safari, Opera, IE 9+ and Edge.

In IE < 9 the Date.parse method doesn’t support ISO 8601. You could add a polyfill for it but, for my purposes, I decided to just let IE < 9 fallback to the default date instead.

Source Code

/**
 * Take an RFC 3339 or ISO 8601 date and returns
 * the date in human readable form.
 *
 * Will return undefined if lacks browser support
 * or it cannot parse the date.
 *
 * @param  {string} time
 * @param  {object} [lang] Optional language object
 * @return {string|undefined}
 * @license MIT
 * @author Sam Clarke <sam@samclarke.com>
 */
function timeToWords(time, lang) {
  lang = lang || {
    postfixes: {
      "<": " ago",
      ">": " from now",
    },
    1000: {
      singular: "a few moments",
      plural: "a few moments",
    },
    60000: {
      singular: "about a minute",
      plural: "# minutes",
    },
    3600000: {
      singular: "about an hour",
      plural: "# hours",
    },
    86400000: {
      singular: "a day",
      plural: "# days",
    },
    31540000000: {
      singular: "a year",
      plural: "# years",
    },
  };

  var timespans = [1000, 60000, 3600000, 86400000, 31540000000];
  var parsedTime = Date.parse(time.replace(/\-00:?00$/, ""));

  if (parsedTime && Date.now) {
    var timeAgo = parsedTime - Date.now();
    var diff = Math.abs(timeAgo);
    var postfix = lang.postfixes[timeAgo < 0 ? "<" : ">"];
    var timespan = timespans[0];

    for (var i = 1; i < timespans.length; i++) {
      if (diff > timespans[i]) {
        timespan = timespans[i];
      }
    }

    var n = Math.round(diff / timespan);

    return (
      lang[timespan][n > 1 ? "plural" : "singular"].replace("#", n) + postfix
    );
  }
}

Example usage

document.addEventListener("DOMContentLoaded", function () {
  var elements = document.getElementsByTagName("time");
  for (var i = 0; i < elements.length; i++) {
    var elm = elements[i];
    // The date should be either in the datetime attribute
    // or in the text contents if no datetime attribute
    var date = elm.getAttribute("datetime") || elm.textContent;

    var dateInWords = timeToWords(date);
    if (dateInWords) {
      elm.textContent = dateInWords;
    }

    // you could use setInterval to automatically update
    // the timestamps every so often if wanted
  }
});

The timeToWords function also accepts a language object so the output can be translated. For example:

var norwegian = {
  // Postfixes - ago / from now
  postfixes: {
    "<": " siden",
    ">": " fra nå",
  },
  // Seconds
  1000: {
    singular: "et øyeblikk",
    plural: "et øyeblikk",
  },
  // Minutes
  60000: {
    singular: "omtrent et minutt",
    plural: "# minutter",
  },
  // Hours
  3600000: {
    singular: "omtrent en time",
    plural: "# timer",
  },
  // Days
  86400000: {
    singular: "en dag",
    plural: "# dager",
  },
  // Years
  31540000000: {
    singular: "et år",
    plural: "# år",
  },
};

// Will set dateInWords to a Norwegian translation
var dateInWords = timeToWords(date, norwegian);

Demo

View on CodePen

Comments