ESNext: Relatively format time with Intl.RelativeTimeFormat

A proposal to the ECMAScript Internationalization API Specification (ECMA-402) that just moved to Stage-4, is Intl.RelativeTimeFormat. It’s a way to format time in a relative manner – e.g. “10 minutes ago” or “in 3 quarters” – while also taking a language into account.

🤔 Note that this proposal is part of ECMA-402 (the ECMAScript Internationalization API Specification) and not ECMA-262 (The ECMAScript Language Specification). Therefore this proposal will not be part of the yearly "ES20XX”, as that term only applies to ECMA-262.

To advance proposals in ECMA-402, TC39 uses the same 5-stage-process as they use in ECMA-262.

💁‍♂️ Stage-4?

The Technical Committee which is concerned with the standardization of ECMAScript (i.e. TC39) has a 5 stage process in place, ranging from stage-0 to stage-4, by which it develops a new language feature.

Stage-4 is the Finished Stage and indicates that the proposal is ready to become part of the ECMAScript Specification.

const rtf_en = new Intl.RelativeTimeFormat('en');

console.log(rtf_en.format(-1, 'day')); // ~> 1 day ago
console.log(rtf_en.format(30, 'seconds')); // ~> in 30 seconds
const rtf_nl = new Intl.RelativeTimeFormat('nl-BE', {
    localeMatcher: 'best fit', // other values: 'lookup'
    numeric: 'auto', // other values: 'always'
    style: 'short', // other values: 'long' or 'narrow'
});

console.log(rtf_nl.format(-1, 'day')); // ~> gisteren
console.log(rtf_nl.format(30, 'seconds')); // ~> over 30 sec.

~

Syntax

The constructor takes two arguments: locales and options. Both are optional:

new Intl.RelativeTimeFormat([locales[, options]]);

Possible units to use while formatting are "year", "quarter", "month", "week", "day", "hour", "minute", and "second".

~

Constructor Arguments

locales

The locales argument is a BCP 47 language tag which identifies the locale to use. The main parts, amongst others, of a BCP 47 language tag are language, script, and region

  • The language part is a 2/3 character long ISO 639-1 code
  • The (optional) script part is a 4 character long ISO 15924 code
  • The (optional) region part is a 2 alpha or 3 digit long ISO 3166-1 code

When more than one part is present, they are separated by a dash (-)

Examples:

  • "nl": Dutch (language).
  • "nl-BE": Dutch as used in Belgium (language + region).
  • "zh-Hans-CN": Chinese written in simplified characters as used in China (language + script + region).

When locale is omitted, it will default to the language-setting of the browser/system.

options

The options object allows you to tweak how Intl.RelativeTimeFormat behaves. It has the following properties:

  • localeMatcher

    The locale matching algorithm to use. Possible values are "lookup" and "best fit" (default)

  • numeric

    The format of output message. Possible values are "always" (always include a number in the output, default) or "auto" (which allows output such as “in 1 day” to be rewritten in the more natural "tomorrow")

  • style

    The length of the output message, e.g. "in 1 month" vs. "in 1 mo.". Possible values are "long" (default), "short", or “narrow” (which mostly yields the same result as "short", depending on the language)

~

Extracting all parts with formatToParts

If you don’t want a string to be returned, you can use formatToParts() to get an object describing all parts:

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });

// Format relative time using the day unit.
rtf.formatToParts(-1, "day");
// > [{ type: "literal", value: "yesterday"}]

rtf.formatToParts(100, "day");
// > [{ type: "literal", value: "in " }, { type: "integer", value: "100", unit: "day" }, { type: "literal", value: " days" }]

~

JS/Browser Support

Intl.RelativeTimeFormat shipped in V8 v7.1.179 (included Chrome 71) and Firefox 65.

Polyfills are available:

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don\'t do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

BuymeaCoffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

4 Comments

  1. Thanks for this post. I read it last year and kept postponing to try this out. Today I did try it in the browser and it worked as explained. However, when I decided to add it to my project, where I list some entities with a created at timestamp I was wondering how to make it work. So I quickly added dayjs module to my project and found it has a relativeTime plugin.
    With that in hand, what would be the use case for this newety?

    1. Having a native feature for this will save you some extra bytes sent over the wire, and will be faster to execute. This because you don’t need to send/load the extra library/plugin.

      Use cases remain the same.

Leave a comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.