Arsalan Khattak
14 March 2025

JavaScript Temporal – is it finally here?

Firefox Nightly, the version of the browser developers can use to test the latest pre-release Firefox features, recently added the […]

Firefox Nightly, the version of the browser developers can use to test the latest pre-release Firefox features, recently added the Temporal proposal for experimentation, bringing us a step closer to replacing the problematic Date object. Once the Temporal object is stable and has cross-browser support, Date will be considered a legacy feature.

The problems with the Date object include:

You can see an example of one of the issues with the Date object in the code below. You may be surprised to find that the value of today gets seven days added to it:

// From https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/
function addOneWeek(myDate) {
    myDate.setDate(myDate.getDate() + 7);
    return myDate;
}
 
const today = new Date();
const oneWeekFromNow = addOneWeek(today);
 
console.log(`today is ${today.toLocaleString()}, and one week from today will be ${oneWeekFromNow.toLocaleString()}`);
// today is 2025/02/17, 14:51:26, and one week from today will be 2025/02/17, 14:51:26

The value of today changes because the Date object is mutable. The addOneWeek function changes the today variable by adding seven days to it.

Problems with the Date object stem from JavaScript’s origins. In 1995, Brendan Eich was tasked with writing JavaScript for the Netscape browser, given just ten days to go to market, and told to make it “like Java.” Eich adapted the problematic Java Date object, which was fixed by the release of Java 1.1 two years later. Most of its date methods were deprecated and replaced. The JavaScript implementation couldn’t be entirely fixed without breaking the web, though, and over the years, the quirks of the Date object – like its mutability and parsing issues – have become legendary pain points for developers, despite the emergence of popular libraries like Day.jsdate-fns, and Luxon for working with dates and times.

Temporal promises to fix all the problems associated with working with dates in JavaScript. It fully replaces the Date object with a better API for working with dates, times, time zones, and calendars. Temporal simplifies daylight saving time calculations and makes working with historical calendar changes easier.

Let’s compare Temporal with the Date object to see how they differ in code readability, clarity, and performance.

What is the Temporal API and how does it compare to Date?

The Temporal API is a namespace that consists of classes and sub-namespaces that each handle different parts of dates and times:

Compared to using Date, Temporal separates functionality, resulting in more-readable code that clearly shows which time zone is used.

Consider the Date object’s parse method for converting a date string into a timestamp:

const d = Date.parse('01 Jan 2025');
console.log(d); // 1735682400000

The value of the returned timestamp depends on the user’s time zone, a detail that’s easy to miss and can cause bugs in your code.

Let’s look at some code examples of common date tasks to see how Temporal compares to Date.

How to get a Unix timestamp

A Unix timestamp is the number of seconds since the beginning of the Unix epoch, which is midnight on the first of January 1970, UTC. To get this timestamp using the Date object, use the Date.now() method:

const timeStamp = Math.floor(Date.now() / 1000);
console.log(timeStamp); // 1739180687

To do the same using the Temporal object, use Temporal.now to get the current time and then use the Instant method to return an Instant object, which represents the number of nanoseconds since the Unix epoch:

const timeStamp = Math.floor(Temporal.Now.instant().epochMilliseconds / 1000);
console.log(timeStamp); // 1739180687

Both the Instant object and Date object represent a point in time since the Unix epoch, but the Instant object is more precise as it stores nanoseconds, not milliseconds. To get date or time information from an Instant object, you need to first convert it to a Temporal.ZonedDateTime object, which represents a date and time with a time zone. To do this, use the toZonedDateTimeISO() method.

In contrast to the Temporal object, the Date object implicitly uses the local time zone, which can lead to unexpected results if you’re expecting UTC. For example, the toString() method returns a string representing the date in the local time zone, which may not be obvious just by looking at the code:

const isoString = "2025-03-20T23:00:00Z";
const date = new Date(isoString);
console.log(date.toString()); // Fri Mar 21 2025 01:00:00 GMT+0200 (South Africa Standard Time)

The equivalent code using Temporal requires explicit time zone conversion:

const instant = Temporal.Instant.from(isoString);
const zonedDateTime = instant.toZonedDateTimeISO('Africa/Johannesburg');
console.log(zonedDateTime.toString({ timeZoneName: "never" })); //2025-03-21T01:00:00+02:00

The timeZoneName option is set to "never" to exclude the time zone name in the returned string.

How to get the current date and time as an ISO 8601 string in the user’s local time

Using the Date object, getting a string of the current date and time in ISO 8601 format using UTC is straightforward:

const d = new Date(Date.now()).toISOString();
console.log(d); // 2025-02-10T09:04:28.402

However, getting the current date and time in ISO 8601 format in the user’s time zone is a little more complicated. Here’s one possible implementation:

function toISOLocal(d) {
    const padToTwoDigits = n => ('0' + n).slice(-2);
    const padToThreeDigits = n => ('00' + n).slice(-3);
    let timezoneOffset = d.getTimezoneOffset();
    const sign = timezoneOffset > 0 ? '-' : '+';
    timezoneOffset = Math.abs(timezoneOffset);
    return d.getFullYear() + '-' +
        padToTwoDigits(d.getMonth() + 1) + '-' +
        padToTwoDigits(d.getDate()) + 'T' +
        padToTwoDigits(d.getHours()) + ':' +
        padToTwoDigits(d.getMinutes()) + ':' +
        padToTwoDigits(d.getSeconds()) + '.' +
        padToThreeDigits(d.getMilliseconds()) +
        sign + padToTwoDigits(timezoneOffset / 60 | 0) + ':' +   padToTwoDigits(timezoneOffset % 60);
}
console.log(toISOLocal(new Date()));// 2025-02-10T11:04:28.402+02:00

Using the Temporal object makes this task much easier:

const zonedDateTime = Temporal.Now.zonedDateTimeISO(); 
console.log(zonedDateTime.toString({ timeZoneName: "never" })); // 2025-02-10T11:04:28.402+02:00

The Temporal.Now.zonedDateTimeISO() method returns the current date and time as a Temporal.ZonedDateTime object, in the ISO 8601 calendar and the specified time zone. It uses the user’s time zone by default. You can change the time zone by passing in a time zone identifier string as an argument.

How to get the number of days and months until a future event

Another common date task is to calculate the duration between now and a date in the future. For example, the following code uses the Date object to calculate the number of days and months until a future date:

const d = '2028-10-11';
// Parse the date string into a Date (months are zero-based)
const [year, month, day] = d.split('-').map(Number);
const futureDate = new Date(year, month - 1, day);
// Get "today" as a Date at local midnight - so only the calendar date matters
const today = new Date();
today.setHours(0, 0, 0, 0);
const msPerDay = 1000 * 60 * 60 * 24;
const diffDays = Math.round((futureDate - today) / msPerDay);
// Calculate an approximate month difference
// First, get the month difference from the year and month parts
let diffMonths = (futureDate.getFullYear() - today.getFullYear()) * 12 +
                 (futureDate.getMonth() - today.getMonth());
// Then adjust based on the day of the month for a rough "rounding" effect.
if (futureDate.getDate() >= today.getDate()) {
    diffMonths++;  // Add a month if the day is later in the month
}
else if (futureDate.getDate() < today.getDate()) {
    diffMonths--;  // Subtract a month if the day hasn't been reached yet
}
console.log(diffDays, diffMonths);

This code first parses a string representing a future date and creates a Date object from it. The number of days between today and the future date is calculated by subtracting the today object from the futureDate object and converting the resulting millisecond difference into days and months, and rounding the result.

The equivalent code using the Temporal object requires less than half of the code and it’s more readable:

const d = '2028-10-11';
const futureDate = Temporal.PlainDate.from(d);
const today = Temporal.Now.plainDateISO();
const until = today.until(futureDate, { largestUnit: 'day' });
const untilMonths = until.round({ largestUnit: 'month', relativeTo: today });

console.log(until.days, untilMonths.months); 

The futureDate is a plain date, without a time zone. The today variable is the current date using the user’s time zone. The until variable is a Temporal.Duration object that represents the duration in days between today and the futureDate. It uses the until() method for the calculation. The untilMonths variable uses the round() method to round the duration to months.

To see more examples of common date operations, take a look at the Ecma Technical Committee 39 (TC39) Temporal proposal cookbook.

How to use Temporal

The Temporal proposal is at stage 3 in the TC39 proposal process, which means it’s recommended for implementation and no changes to the API are expected. However, changes may be made due to web incompatibilities and feedback from user testing. The last step in the process is stage 4.

The quickest way to try out Temporal is by visiting the TC39 Temporal reference documentation and opening your browser’s developer tools console. A non-production Temporal polyfill is automatically loaded in your browser.

Currently, the only browser with experimental support for Temporal is Firefox Nightly, from Firefox 135. It’s available under a flag. Enable Temporal by visiting about:config and setting javascript.options.experimental.temporal to true.

Two polyfills are available to ensure cross-browser compatibility in your projects:

PolyfillGitHub repositoryStatus
@js-temporal/polyfilljs-temporal/temporal-polyfillAlpha release
temporal-polyfillfullcalendar/temporal-polyfillBeta release

Performance comparison: Temporal vs. Date

To compare the performance of Temporal and Date, we ran benchmarking tests using the code examples from the comparison section of this post and Tinybench, a lightweight JavaScript benchmarking library. The tests were conducted on a MacBook Air M1 using Firefox Nightly version 137.0a1 (2025-02-13) (64-bit) with the javascript.options.experimental.temporal preference enabled. We integrated the benchmarking code into a vanilla JavaScript Vite application built with vite build.

We used the following code to benchmark the date and time operations using Date and Temporal:

import { Bench } from 'tinybench';

const bench = new Bench({
    name             : 'Date vs. Temporal',
    warmupIterations : 100,
    iterations       : 10000
});

bench
    .add('Get Unix timestamp - Date', () => {
        const timeStamp = Math.floor(Date.now() / 1000);
        console.log(timeStamp);
    })
    .add('Get Unix timestamp - Temporal', () => {
        const timeStamp = Math.floor(Temporal.Now.instant().epochMilliseconds / 1000);
        console.log(timeStamp);
    })
    .add('Local time ISO 8601 date string - Date', () => {
        function toISOLocal(d) {
            const padToTwoDigits = n => ('0' + n).slice(-2);
            const padToThreeDigits = n => ('00' + n).slice(-3);
            let timezoneOffset = d.getTimezoneOffset();
            const sign = timezoneOffset > 0 ? '-' : '+';
            timezoneOffset = Math.abs(timezoneOffset);

            return d.getFullYear() + '-' +
        padToTwoDigits(d.getMonth() + 1) + '-' +
        padToTwoDigits(d.getDate()) + 'T' +
        padToTwoDigits(d.getHours()) + ':' +
        padToTwoDigits(d.getMinutes()) + ':' +
        padToTwoDigits(d.getSeconds()) + '.' +
        padToThreeDigits(d.getMilliseconds()) +
        sign + padToTwoDigits(timezoneOffset / 60 | 0) + ':' +   padToTwoDigits(timezoneOffset % 60);
        }

        console.log(toISOLocal(new Date()));
    })
    .add('Local time ISO 8601 date string - Temporal', () => {
        const zonedDateTime = Temporal.Now.zonedDateTimeISO(); console.log(zonedDateTime.toString({ timeZoneName : 'never' }));
    })
    .add('Days and months until a date - Date', () => {
        const d = '2028-10-11';
        // Parse the date string into a Date (months are zero-based)
        const [year, month, day] = d.split('-').map(Number);
        const futureDate = new Date(year, month - 1, day);

        // Get "today" as a Date at local midnight - so only the calendar date matters
        const today = new Date();
        today.setHours(0, 0, 0, 0);

        const msPerDay = 1000 * 60 * 60 * 24;
        const diffDays = Math.round((futureDate - today) / msPerDay);

        // Calculate an approximate month difference
        // First, get the month difference from the year and month parts
        let diffMonths = (futureDate.getFullYear() - today.getFullYear()) * 12 + (futureDate.getMonth() - today.getMonth());

        // Then adjust based on the day of the month for a rough "rounding" effect.
	  if (futureDate.getDate() >= today.getDate()) {
		  diffMonths++;  // Add a month if the day is later in the month
	  }
        else if (futureDate.getDate() < today.getDate()) {
		  diffMonths--;  // Subtract a month if the day hasn't been reached yet
	  }

        console.log(diffDays, diffMonths);
    })
    .add('Days and months until a date - Temporal', () => {
        const d = '2028-10-11';
        const futureDate = Temporal.PlainDate.from(d);
        const today = Temporal.Now.plainDateISO();
        const until = today.until(futureDate, { largestUnit : 'day' });
        const untilMonths = until.round({ largestUnit : 'month', relativeTo : today });

        console.log(until.days, untilMonths.months);
    });

await bench.run();

console.log(bench.name);
console.table(bench.table());

The benchmark results are as follows:

Task nameLatency avg (ns)Latency med (ns)Throughput avg (ops/s)Throughput med (ops/s)Samples
Get Unix timestamp – Date4972.1 ± 8.65%0.00 ± 0.00200235 ± 0.03%201124 ± 0201124
Get Unix timestamp – Temporal5133.5 ± 8.94%0.00 ± 0.00193927 ± 0.03%194797 ± 0194797
Local time ISO 8601 date string – Date6485.2 ± 11.19%0.00 ± 0.00153377 ± 0.04%154197 ± 0154197
Local time ISO 8601 date string – Temporal12344 ± 13.79%0.00 ± 0.0080365 ± 0.06%81014 ± 081500
Days and months until a date – Date6422.3 ± 8.99%0.00 ± 0.00154824 ± 0.04%155708 ± 0155708
Days and months until a date – Temporal12100 ± 9.94%0.00 ± 0.0081915 ± 0.06%82648 ± 082648

The throughput average (number of operations performed per second) shows that Date calculations outperform Temporal â€“ they’re only 3% faster for getting a Unix timestamp but nearly twice as fast for the other calculations.

Although the Date code examples were more verbose than the Temporal ones, they performed substantially better in retrieving the local time ISO 8601 date string and calculating the time to a specific date. This difference in performance would be significant in complex components like a Bryntum Grid, which carries out hundreds or thousands of date calculations.

The improved clarity and immutability of Temporal might be worth the performance cost in some cases. Since the API is still experimental, its performance will likely improve before it’s production-ready.

Is Temporal finally here?

Not quite, but it’s coming.

Firefox has experimental support in the Nightly version, and the @js-temporal/polyfill and temporal-polyfill libraries allow you to experiment with Temporal in your projects. You can contribute to the proposal by reporting bugs or suggesting ideas in the Temporal proposal GitHub repo.

When Temporal is finally here, it will make working with dates, times, time zones, and calendars much more pleasant. You’ll have clearer, less buggy code, and less dependency on date-handling libraries. Here at Bryntum, we use the Date object for time zone support in our components. We plan on using Temporal instead for time zone support once it’s ready, which will drastically simplify our implementation.

Arsalan Khattak

Development JavaScript