JavaScript Mapping Library
I’ve been singing the praises of the web platform’s Intl object for years now, but it still continues to impress me. While I’ve seen it before, today I came across the RelativeTimeFormat API which looks absolutely fabulous. I played with it a bit and thought I’d share some tips.
The RelativeTimeFormat API works like so:
RelativeTimeFormat
Report the difference in the user’s desired language. So for example:
Also, you can specify whether or not to always have a numeric answer. This comes into play when the value of 1 is used. So for example, if you specify 1 and ‘day’, you can either get, "in 1 day", or "tomorrow".
Here’s an example in code:
const rtf1 = new Intl.RelativeTimeFormat('en');let diffInDays = rtf1.format(2, 'days');
Will return ‘in 2 days’. Simple, right? Let’s look at the options, and remember the MDN doc is your best bet for a full description.
RelativeTimeFormat takes a second optional argument for options. Those options include:
long
short
narrow
always
auto
The format method only takes two arguments:
format
RelativeTimeFormat also has a few other related methods you may need I won’t cover today, but check the docs for info.
I wanted to play with this myself, so I built a quick demo in CodePen. As a quick aside, when I build demos in CodePen for my blog, I try to avoid using console as you can’t see it in the embed. To make it easier to share the outputs with you, I used an empty div and this little bit of JavaScript:
console
const $ log = document.querySelector('#log');const log = s => { $ log.innerHTML += s + '<br/>';};
This then lets me do stuff like log('here is the result', foo).
log('here is the result', foo)
With that out of the way, here’s the embed, and you can see two formatters in play – one using the defaults, and one using numeric=auto to show you the difference. Also, note that ‘0’ is a valid value for the difference.
numeric=auto
See the Pen Intl.RelativeTimeFormat by Raymond Camden (@cfjedimaster) on CodePen.
Easy, right?
I think you can see this is an easy API to use, but how do you actually use it with, you know, real data? What I mean is this. Given two dates, what makes sense for the unit?
For example:
Also, I think the nature of your content also has a big impact on this decision. For a social media network, I think you would want as precise of a difference as possible: "Ray’s Cat posted ‘X’ one minute ago." Or heck, maybe even to the second: "Ray’s Cat posted ‘X’ 50 seconds ago."
But would that make sense for a blog, or press release? If you read this post an hour after I post it, I’m probably fine with the difference being reported as ‘Today’ versus ‘One hour ago’, or ’50 minutes ago’.
To handle this, we need to first figure out what makes sense for our content, and then handle the technical aspect.
Like most things in life, the ‘solution’ here will come down to…
Here’s one take on handling it.
Given two dates, in this case, a user selected date and right now, I’m going to do the following:
Whew, got that?
I began with this HTML:
<p><label for="otherDate"> Select the date, and a relative value will be passed:</label><input type="datetime-local" id="otherDate"></p><p> Relative value: <span id="output"></span></p>
Note I’m using datetime-local as my input type so I can pick both dates and times. Now for the code.
datetime-local
First, some constants:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });const $ output = document.querySelector('#output');const $ otherDate = document.querySelector('#otherDate');
Then I added a change handler to the datetime field:
change
$ otherDate.addEventListener('change', () => { let date = new Date($ otherDate.value); let now = new Date(); let diff = date - now; // negative values are in the past, positive in the future let unit = determineUnit(diff); console.log(`for $ {diff}, unit is $ {unit}`); let inUnit = toUnit(diff, unit); console.log(`value in unit $ {inUnit}`); $ output.innerText = `$ {rtf.format(inUnit, unit)}`});
As you can see, I get the date, get the difference based on right now, and then call two helper functions. The first determines the unit to use:
// Set of constants that represent how many ms per unitconst MINUTE = 60 * 1000;const HOUR = 60 * MINUTE;const DAY = 24 * HOUR;const WEEK = 7 * DAY;// yeah, this is probably not perfectconst MONTH = 4 * WEEK;const YEAR = MONTH * 12;function determineUnit(x) { x = Math.abs(x); if(x < MINUTE) return 'second'; if(x < HOUR) return 'minute'; if(x < DAY) return 'hour'; if(x < WEEK) return 'day'; if(x < MONTH) return 'week'; if(x < YEAR) return 'month'; return 'year';}
By the way, I skipped quarter, but you could modify the code to support that.
quarter
Then I used this function to change the difference to the right value:
// given a value of x, how many of unit is it?function toUnit(x, unit) { if(unit === 'minute') return Math.round(x / MINUTE); if(unit === 'hour') return Math.round(x / HOUR); if(unit === 'day') return Math.round(x / DAY); if(unit === 'week') return Math.round(x / WEEK); if(unit === 'month') return Math.round(x / MONTH); if(unit === 'year') return Math.round(x / YEAR);}
You can play with the demo below:
This works, but brings up yet another issue! Imagine today is March 2nd and I selected February 28th. You could say, rightly, that was 3 days ago. You could also say last month. Last month is simpler, but kinda vague. But maybe months are really important in your content. Like, stuff/data/whatever from a previous month has some implications that are important. In that case, you would want to show ‘last month’, not ‘3 days ago’.
Again, it depends.
That being said, I really love Intl and it’s one of my favorite parts of the web platform. I’d love to know if you’ve used this, or any part of Intl really. Leave me a comment below!
Raymond Camden
You must be logged in to post a comment.
This site uses Akismet to reduce spam. Learn how your comment data is processed.