How to optimize Date format operations<!-- --> | <!-- -->Web Performance Tips

How to optimize Date format operations

Many web applications require the presentation of user-friendly date strings within their UI. For example, most users would prefer seeing Monday, September 6, 2021 rather than the ISO time string 2021-09-07T00:00:00.000Z!

In order to translate JavaScript Date objects into user-friendly date strings, web developers must utilize the available in-browser date string formatting capabilities.

Beware of Date.toLocaleDateString(...)

Date.toLocaleDateString(...) is the go-to API JavaScript developers use for formatting dates due to its convenient access and straightforward API.

It accepts both a locale (for example, en-US) and a set of formatter options, which specify how the date should be formatted.

Consider this example:

const today = new Date();

const todayFormattedNicely = today.toLocaleDateString('en-US', {
    month: 'long',
    weekday: 'long',
    day: 'numeric',
    year: 'numeric'
});

// "Sunday, September 5, 2021"
console.log(todayFormattedNicely);

There are various options one can provide in the second argument to produce different kinds of date and time format strings.

Performance Implications

Since the Date object is a native JavaScript built-in, Date.toLocaleDateString invokes native codepaths within the JavaScript engine.

Specifically, this API instructs the engine to:

  1. Create a new DateTimeFormat object for the specified locale and format arguments. ⚠️This operation is expensive!⚠️
  2. Format the Date as a formatted date string utilizing the DateTimeFormat
  3. Return the formatted date string, discarding the DateTimeFormat

Note: I encourage you to read the V8 source code of this yourself and compare with my summary!

If a web application only requires formatting of a single Date within its lifetime, then this series of steps is acceptable. But modern web apps often have long lifetimes and format multiple dates (consider a web dashboard, CRM, or social network). It's therefore wasteful to keep creating and discarding DateTimeFormat objects to format each date string.

Consider the following example:

const datesToFormat = [
    new Date("1775-04-18T00:00:00.000Z"),
    new Date("1775-04-19T00:00:00.000Z"),
    new Date("1775-04-20T00:00:00.000Z"),
    new Date("1775-04-21T00:00:00.000Z"),
    new Date("1775-04-22T00:00:00.000Z"),
];

for (const date of datesToFormat) {
    const formattedDateString = date.toLocaleDateString('en-US', {
        day: 'numeric',
        month: 'long',
        year: 'numeric'
    });

    // "April 19, 1775" ... "April 23, 1775"
    console.log(formattedDateString);
}

We are formatting 5 Date objects here, but each call to toLocaleDateString() is internally creating an expensive new DateTimeFormatand discarding it after formatting the Date to a string.

As a result, we are wasting CPU time, degrading the browser's ability to generate frames, and degrading user experience.

Solution: Intl.DateTimeFormat

Fortunately, browsers support an API called Intl.DateTimeFormat that allows web developers to create DateTimeFormat objects, and cache them in-memory for usage across multiple dates.

This API utilizes the same arguments as Date.toLocaleDateString(...), but produces a re-usable formatter instead.

Consider this example:

// Expensive to create, but re-usable and only created once!
const formatter = new Intl.DateTimeFormat('en-US', {
    day: 'numeric',
    month: 'long',
    year: 'numeric'
});

const datesToFormat = [
    new Date("1775-04-18T00:00:00.000Z"),
    new Date("1775-04-19T00:00:00.000Z"),
    new Date("1775-04-20T00:00:00.000Z"),
    new Date("1775-04-21T00:00:00.000Z"),
    new Date("1775-04-22T00:00:00.000Z"),
];

for (const date of datesToFormat) {
    // This operation is very fast!
    const dateString = formatter.format(date);

    // "April 19, 1775" ... "April 23, 1775"
    console.log(dateString);
}

By using Intl.DateTimeFormat, we are reducing overall work by in-memory caching the DateTimeFormat and re-using it for each date format operation, instead of continuously creating and discarding DateTimeFormat objects internally with toLocaleDateString().

Profiled Example

I've put together an demo page that formats 1000 dates utilizing both approaches.

On my machine, formatting 1000 Date objects using toLocaleDateString() took about 217ms:

A screenshot of the Chromium Profiler showing a long runtime when using Date.toLocaleDateString

This time was mostly spent creating 1000 distinct DateTimeFormat objects.

On the same machine, creating a single Intl.DateTimeFormat object and using it to format 1000 Date objects took around 4ms:

A screenshot of the Chromium Profiler showing a short runtime when using Intl.DateTimeFormat

If you want to see try this yourself check out the demo page with your profiler running!

That's all for this tip! Thanks for reading! Discover more similar tips matching CPU and JS Optimization.