JS and ECMAScript date formatting with ordinal indicators
When formatting dates in JS, the newish Intl.DateTimeFormat standard comes in handy. However, one thing it doesn’t do by default is formatting of the day with ordinal indicators (i.e. the th in November 25th). So, we have to find a way to enhance the default behaviour.
I needed to do this again today, and this might be the first, second, third or fourth time I’ve done this, but somehow, I still end up having to work it out each time. In the spirit of Learning In Public, here’s how I ended up doing it.
TL;DR
(plus https://gist.github.com/goofballLogic/c81644000e9555f8001424838ef83f6f for ordinal-formatting.js)
Intl
The Intl family of objects in JavaScript establish a standard for providing formatting functionality in a JavaScript runtime. The exact details of format are usually delegated to some flavour of ICU — the Unicode standard definition of rules for dealing with things like dates and numbers in the context of different locales. The rules for different locales are often based on the Unicode CLDR which attempts to capture the different variants of language and locale (along with other variations).
Intl.DateTimeFormat
In order to format a date, we need to tell JS what culture should be used for formatting purposes. Here I’m using the language subtag for “English” — en.
We can also inspect the format to see what options have been selected by default:
Because I was lazy and used en instead of a specific format (like en-GB or en-US) I will usually end up with date formats specified for the generic en region in the Unicode Common Locale Data Repository, such as shown below:
We end up with a formatting object which I can use in at least two ways:
Using format
, I get a formatted string and using formatToParts
I get an array of objects in which the “day” is represented as a string encoded integer: “28”.
Our goal is to convert “28” to “28th”. We could attack this problem by either regexing our way into the pre-formatted string, or we could assemble the string ourselves using the “parts” array produced by formatToParts
.
Either way, we’re going to need to determine the ordinal suffix for the number. And for that, we can make use of Intl.PluralRules.
Intl.PluralRules
The Intl.PluralRules object enables us to explore the cardinality and ordinality of numbers. These rules are different for different locales, so just like with Intl.DateTimeFormat, we initialize this object by specifying a locale:
The plural rules object doesn’t give us the suffix we are looking for (28 -> 28th). It merely knows how to inspect the number 28 in the context of the locale we specify, and tells us which ordinality to use (28 -> other)
As this is still an abstract mapping, we need to encode which suffix to use for each ordinal.
This is where it feels like we lose out using just standard ECMAScript components vs an additional library (such as Luxon). But note that even for these libraries, we normally have to tell them to load in internationalization mappings, so having to specify our own doesn’t feel like too bad a deal:
So now we can create a simple function to form any number by adding its ordinal suffix:
Putting it all together
Given that we now have the parts of our date string provided by Intl.DateTimeFormat, and our suffix generating code based on Intl.PluralRules, we can now stitch them together like this:
Bingo! We have the format we were looking for. Notice that the ordinal functionality is entirely reusable so lets move that into a web module:
And our web-module can look like this:
And that’s it
Hopefully this prevents either you or I from having to work this out all over again next time :)