The ECMAScript Internationalization API is a namespace that contains several utilities to format values in a language-sensitive way. It solves the issue of outputting dates, numbers, collections, and more built into the browser.
In this post, we will visit these utilities, show how to use them and where they are already available.
Intl.DateTimeFormat
DateTimeFormat
is a constructor for language-sensitive time formatting. It supports date, time, a combination of both and you can control each part of the result. The syntax to create a new formatter is:
new Intl.DateTimeFormat([locales[, options]])
If you omit both parameters, it will use the current browser locale and default options for such locale.
Throughout the article, I will use November 5th, 2020 at 01:02:03 AM GMT+1 (new Date("2020-11-05T00:01:02+01:00")
) as the example date.
When to use it?
DateTimeFormat
is useful if you want to format dates in several different places using the same formatting rules, as it allows to concentrate the options in a single place. For example, you can export it in your l10n/i18n context if using React.
For one-off formatting, you may prefer to use Date.toLocaleString
and its partners, Date.toLocaleDateString
and Date.toLocaleTimeString
to avoid the extra step of instantiating the DateTimeFormat
object. All of these functions take the same attributes the constructor for DateTimeFormat
takes: locale
and options
. Let's see what those are.
locale
The first parameter, locales
, is one or a list of possible locales for negotiation. Language negotiation is an intricate subject, and I don't want to dive into it yet, so check the MDN reference if you need details.
Here are some examples of how the locale will change the output:
const dtf = new Intl.DateTimeFormat();
dtf.format(date);
//=> "11/5/2020"
// equivalent to date.toLocaleDateString()
const dtf = new Intl.DateTimeFormat('pt-BR');
dtf.format(date);
//=> "05/11/2020"
// equivalent to date.toLocaleDateString('pt-BR')
Note the difference: the default, which in my case is 'en-US'
, uses the M/D/Y format. pt-BR
, however, uses D/M/Y with zero left padded day and month values.
options
By default, DateTimeFormat
only outputs the date, without the time. We can tweak using the options
parameter, which may include the following attributes:
year
, month
, day
These attributes define how to format each part of the date individually.
For year
, you can specify either:
-
numeric
, which will display the year in full -
2-digit
, which will only display the last 2 digits of the year
const dtf = new Intl.DateTimeFormat('en', { year: 'numeric' });
dtf.format(date);
//=> "2020"
// equivalent to date.toLocaleDateString('en', { year: 'numeric' })
// you got it already, right?
const dtf = new Intl.DateTimeFormat('en', { year: '2-digit' });
dtf.format(date);
//=> "20"
month
can be either numeric
and 2-digit
(zero-padded if lower than 10), but also has textual representations:
-
long
, which will display the whole month name -short
, which will display an abbreviation -
narrow
, which will display the first letter only (in English)
const dtf = new Intl.DateTimeFormat('en', { month: 'long' });
dtf.format(date);
//=> "November"
const dtf = new Intl.DateTimeFormat('en', { month: 'short' });
dtf.format(date);
//=> "Nov
const dtf = new Intl.DateTimeFormat('en', { month: 'narrow' });
dtf.format(date);
//=> "N"
Note that two months may have the same narrow
representation in some languages, and it's not guaranteed to be a single character when not in English.
For day
, there's only numeric
and 2-digit
(zero-padded).
const dtf = new Intl.DateTimeFormat('en', { day: 'numeric' });
dtf.format(date);
//=> "5"
const dtf = new Intl.DateTimeFormat('en', { day: '2-digit' });
dtf.format(date);
//=> "05"
Note that when you omit the options
parameter, the default is assumed to be numeric
for year
, month
, and day
, but if you specify any combination, it will only output the values specified.
Depending on the combination of options, the output will be slightly different. You could expect that using { year: 'numeric', month: 'short', day: '2-digit' }
would output Nov/05/2020
for that same date, however it does not:
const dtf = new Intl.DateTimeFormat('en', { year: 'numeric', month: 'short', day: '2-digit' });
dtf.format(date);
//=> "Nov 05, 2020"
era
If you are displaying dates more than 2000 years ago, you may be interested in using era
. It can be long
, short
and narrow
similar to how month
works, and will display the current era (either AD or BC):
const dtf = new Intl.DateTimeFormat('en', { era: 'long' });
dtf.format(date);
//=> "11 5, 2020 Anno Domini"
const dtf = new Intl.DateTimeFormat('en', { era: 'short' });
dtf.format(date);
//=> "11 5, 2020 AD"
const dtf = new Intl.DateTimeFormat('en', { era: 'narrow' });
dtf.format(date);
//=> "11 5, 2020 A"
Note the full date is displayed too, not only the era.
weekday
This attribute controls if the weekday name should be displayed. It can be long
, short
and narrow
similar to how month
works:
const dtf = new Intl.DateTimeFormat('en', { weekday: 'long' });
dtf.format(date);
//=> "Thursday"
const dtf = new Intl.DateTimeFormat('en', { weekday: 'short' });
dtf.format(date);
//=> "Thu"
const dtf = new Intl.DateTimeFormat('en', { weekday: 'narrow' });
dtf.format(date);
//=> "T"
Note that two weekdays may have the same narrow
representation in some languages, and it's not guaranteed to be a single character when not in English.
hour
, minute
and second
Both these options have only two possibilities, numeric
and 2-digit
. However, there are two catches.
First, for minute
and second
, numeric
will behave the same as 2-digit
unless only one of three options is specified:
const dtf = new Intl.DateTimeFormat('en', { minute: 'numeric', second: 'numeric' });
dtf.format(date);
//=> "01:02"
const dtf = new Intl.DateTimeFormat('en', { minute: 'numeric' });
dtf.format(date);
//=> "1"
Second, for 12-hour clocks, displaying hour
will also append AM
/PM
to the output:
const dtf = new Intl.DateTimeFormat('en', { hour: 'numeric', minute: 'numeric', second: 'numeric' });
dtf.format(date);
//=> "12:01:02 AM"
You can control AM/PM behavior with the next options.
hourCycle
and h12
These options control the hour cycle to use, that is if the clock has 24 hours a day or two 12-hour periods and whether it starts at 0 or 1. The defaults are locale-dependent.
hourCycle |
output |
---|---|
'h11' |
"0:01:02 AM" |
'h12' |
"12:01:02 AM" |
'h23' |
"00:01:02" |
'h24' |
"24:01:02" |
Notes:
-
hourCycle: 'h12'
is the default for US English -
hour: '2-digit'
is forced when usingh23
/h24
As for hour12
, it will toggle between h11
and h23
, or between h12
and h24
depending on the locale.
locale |
h12 |
output |
---|---|---|
'en' |
false |
"12:01:02 AM" |
'pt-BR' |
true |
"0:01:02 AM" |
Notes:
- US English uses
h12
by default, thereforehour12: false
setshourCycle: 'h24'
- BR Portuguese uses
h23
by default, thereforehour12: true
setshourCycle: 'h12'
timeZone
The timeZone
attribute is self-explanatory, and defines the time zone to use. The default is to use the system time zone:
const dtf = new Intl.DateTimeFormat('en', { ... });
dtf.format(date);
//=> "12:01:02 AM"
const dtf = new Intl.DateTimeFormat('en', { ..., timeZone: 'America/Sao_Paulo' });
dtf.format(date);
//=> "8:01:02 PM"
moving from GMT+1 to GMT-3
timeZoneName
It is responsible for the representation of the time zone name, and can be long
or short
. It can't be used together with timeStyle
, which we will cover next:
const dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'long' });
dtf.format(date);
//=> "12/5/2020, Coordinated Universal Time"
const dtf = new Intl.DateTimeFormat('en', { timeZoneName: 'short' });
dtf.format(date);
//=> "12/5/2020, UTC"
dateStyle
and timeStyle
Now, in case you don't know, default date formats may differ between locales, so you can't rely on setting year: 'numeric'
for a short date string since some locales would prefer year: '2-digit'
for short dates, while others simply don't use 2-digit years, never.
For that reason, you can use dateStyle
and timeStyle
, which will set reasonable values for all date- and time-related options at once! Both can be full
, long
, medium
and short
and can't be mixed with other options, only together.
Say you want the most verbose representation of a date:
const dtf = new Intl.DateTimeFormat('en', { dateStyle: 'full', timeStyle: 'full' });
dtf.format(date);
//=> "Saturday, December 5, 2020 at 12:00:00 AM Coordinated Universal Time"
That is similar to setting every option to long
or equivalent, with a single option.
Or maybe you want the most concise display for the given locale:
const dtf = new Intl.DateTimeFormat('en', { dateStyle: 'short', timeStyle: 'short' });
dtf.format(date);
//=> "12/5/20, 12:00 AM"
const dtf = new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' });
dtf.format(date);
//=> "05/12/2020 00:00"
Note that the style is different, as English uses { year: '2-digit', month: 'numeric' }
, has a comma separator, and day period, but Brazilian Portuguese uses { year: 'numeric', month: '2-digit' }
, has no comma separator, and 24-hour clock? This is simply not feasible with plain options, so dateStyle
and timeStyle
are really handy.
But there is a catch. dateStyle
and timeStyle
are recent additions, and only available in Firefox >= 79, Chrome >= 76, and are not available in Edge and Safari at the time of writing. You can polyfill with polyfill.io.
That pretty much sums up what I wanted to show about Intl.DateTimeFormat
! I will write about formatToParts
and formatRange
, and about other utilities inside Intl
such as ListFormat
and RelativeTimeFormat
, follow me to stay tuned!
Top comments (1)
I think you have a typo here:
Based on the aforementioned logic of switching between
h11
andh23
this sentence should read instead:Also, I believe the
en
row's output value in the example table below should display24:01:02
instead of12:01:02 AM
based on that same logic. The pt-BR row looks correct.