Relative Dates

The other day I had to find a way to output relative dates. So instead of a localized version of “on the 19th of December 2017 at 16:24 the User x did Y” the requirement was to say “3 minutes ago the User X did Y” (or “last Wednesday at 16:24 the User X did Y”… you get the picture).

Challenge accepted!

First stop: Packagist (where else). Relative Date. Some packages around, though not really something I was happy with. The mostly installed one is 2 years old and was never tested on PHP7. So not really a good option.

And – to be honest – isn’t that more of an eyecandy feature? A goodie for people using a fancy browser? After all the “real” date and time are as good as the relative one and might give some users better information. So why not use some nifty HTML5 and JavaScript-features…

Enter moment.js and the time-tag.

time-tag

The HTML5 time-tag is a great way to mark times within a website. Sadly not all browsers fully support it but from what I’ve seen so far all Browsers display the content of the tag and using JavaScript it’s also possible to access the attributes of the tag within all browsers. So the idea was to grab the datetime-string from the datetime-attribute and replace the tags content with the relative information. And that works in all browsers I’ve tested so far.

And for those cases where JavaScript does not work I’ll set a fallback as content of the time-tag. I’ll leave implementing that with PHPs DateFormatter as an exercise for you or for a later blog-post.

Accessing the tag and replacing the content can be done with your favourite JavaScript-Library, I’ve used jQuery, but use whatever you like.

moment.js

For converting the value of the datetime-attribute I’Ve used moment.js. The moment.js-library has support for (localized) relative dates out of the box. And even in two different ways like “3 minutes ago” or “last Wednesday at 17:40”. These are the fromNow or toNow or to/fromX-Methods and the calendar-time.

I decided to use a mixture of both. When the event was less than 1 day away I wanted to use the fromNow-version and after that the calendar one.

So I threw together some JavaScript to make things happen:

moment.locale('en');
$('time').each(function(){
  timestring = $(this).attr('datetime');
  var mom = moment(timestring);
  var now = moment();
  if (now.diff(mom, 'days') > 0) {
    $(this).text(mom.calendar(now));
  } else {
    $(this).text(mom.from(now));
  }
});

This will convert all contents of a time-tag to the relative version between now and the datetime from the datetime-attribute. So my HTML looks like this:

<span class="time_relative">
    <time datetime="2017-12-19 17:40:23+01:00">
        on 19. Dec 2017 at 17:40
    </time> 
    User X did Y
</span>

And the finished output looks like this (on the respective dates):

Pretty good for out of the box…

But still something to do: the last few lines are not exactly what I want. I’d rather read something like “On 12/19/17 at 5:40 PM User X did Y” instead of the current output. So let’s adapt that format a bit using the locale-settings of moment.js

moment.locale('en');
$('time').each(function(){
  timestring = $(this).attr('datetime');
  var mom = moment(timestring);
  var now = moment();
  if (now.diff(mom, 'days') > 0) {
    $(this).text(mom.calendar(now, {
        sameElse: '[on] L [at] LT'
    }));
  } else {
    $(this).text(mom.from(now));
  }
});

And that replaces the last 9 lines with this:

For a multilingual setup I’d replace the moment.locale('en'); with the required locale and would also localize the {sameElse: '[on] L [at] LT'}-part.

If you’re used to the ICU-formatting you’ll need to take care! moment.js uses it’s own formatting!

That’s my approach. But perhaps you have a different solution that has an advantage that I missed?

Leave a Reply

Your email address will not be published. Required fields are marked *