Temporal API to fix JavaScript Date


Let's get one thing out of the way: working with JavaScript dates is an absolute nightmare.
The Date
API is unwieldy, to say the least, and immutability is almost non-existent. This was more than enough to nudge most developers to third-party libraries like Luxon (remember Moment.js?) and date-fns to streamline the experience of working with date and time.
These third party libraries might not be needed anymore. Let’s take a look at the new Temporal
object, with all its improvements over Date
.
Issues with the Date
object
Some of the biggest issues are that it only supports UTC and local (machine) time, with no time zone support. This also means that daylight savings aren’t really respected, as they are tied to a specific time zone.
Parsing a date from a string is also quite error prone, and the Date
object is also mutable, which is its own can of worms.
As a last issue, only the Gregorian calendar is supported by the Date
implementation.
What does Temporal
do?
Citing the MDN docs: “It is designed as a full replacement for the Date object.”
It adds time zone support, 200+ methods that make working with dates much more straight forward, and more explicit date parsing/creation.
As the API is quite broad, it might take a bit of getting used to, but in the end, it should be a native replacement for most (if not all) date-related third party libraries.
To mention some added benefits: Temporal
is globally available (just like Math
, there’s no need to import it), and everything that it exposes is immutable. You can say goodbye to those esoteric bugs that were introduced by overwriting Date
's behavior.
Temporal
data types
There are two main categories when it comes to the Temporal
API:
Plain - denoting a date or time devoid of any time zone information
Zoned - representing a date or time paired with a time zone
Each one of these has its own use case. We might use a PlainDateTime
when we only care about a specific time with no regards to the time zone, while a ZonedDateTime
is a better fit when we want to do any operations (like adding/substracting hours or calculating the difference between dates) that need to take the current time zone (and DST) into consideration.
Another new concept would be Duration
, which represents the difference between two dates (like C#’s TimeSpan).
Knowing this, it’s easy to tell at a glance what each of the new classes is supposed to represent:
Temporal.Duration
- difference between two time pointsTemporal.Instant
- unique point in history (represented as the number of nanoseconds since the Unix epoch)Temporal.Now
- provides methods to get the current time in various formatsTemporal.PlainDate
- calendar date; no time or tzTemporal.PlainDateTime
- calendar date and time; no tzTemporal.PlainMonthDay
- calendar month + day; no year or tzTemporal.PlainTime
- time without date or tzTemporal.PlainYearMonth
- calendar year + month; no day or tzTemporal.ZonedDateTime
- date and time with a tz; represented as a combination of aninstant
, tz and a calendar system
There are of course conversion methods as well between these classes.
Methods representing operations
Some often-encountered requirements are “adding/substracting X days to a date” or “calculating days left until X”. Comparing dates has also been a challenge, not to mention serialization to the proper format.
All use cases above have new methods in each of the mentioned classes.
Let’s take a look at some often encountered scenarios, starting with plain/zoned ISO times:
const now = Temporal.Now.plainDateTimeISO();
console.log(now.toString());
// 2025-01-28T18:48:02.118476193
const nowNewYork = Temporal.Now.plainDateTimeISO("America/New_York");
console.log(nowNewYork.toString());
// 2025-01-28T12:48:21.966577614
A couple manipulation methods highlighted below as well:
add
/subtract
The two methods above are identical when it comes to usage: pass in a temporalDurationLike
(basically an object specifying the number of years/months/days/hours/mins/etc).
// Adding
const now = Temporal.Now.plainDateTimeISO();
console.log(now.toString()); // 2025-01-28T18:52:35.647752417
const sameTimeNextMonth = now.add({ months: 1 });
console.log(sameTimeNextMonth.toString()); // 2025-02-28T18:52:35.647752417
// Subtracting
const sameTimePrevMonth = now.subtract({ months: 1 });
console.log(sameTimePrevMonth.toString()); // 2024-12-28T18:52:35.647752417
since
/until
As above, identical in usage: pass in the point of comparison, and an optional options
object, containing the keys largestUnit
, roundingIncrement
, roundingMode
, and smallestUnit
.
As working with Unix timestamps is quite common, let’s use one as our starting point:
const lastUpdated = Temporal.Instant.fromEpochMilliseconds(1735235418000);
const now = Temporal.Now.instant();
const duration = now.since(lastUpdated, { smallestUnit: "minute" });
console.log(`Last updated ${duration.toLocaleString("en-US")} ago`);
// Last updated 47,529 min ago
const christmas = Temporal.Instant.fromEpochMilliseconds(1766664000000);
const timespan = now.until(christmas, { smallestUnit: "hour" });
console.log(`There are ${timespan.toLocaleString("en-US")}s until Christmas!`);
equals
Compares two Temporal.Instant
instances, and returns true if the two represent the same point in time:
const nowString = Temporal.Instant.from("2025-01-28T19:13:04.145+01:00[Europe/Budapest]");
const nowMs = Temporal.Instant.fromEpochMilliseconds(1738087984145);
console.log(instant1.equals(instant2)); // true
Trying out the Temporal API
Currently no major browsers ship with the Temporal API, but most browsers include it in their canary/nightly builds. As of now, Firefox has it enabled in the nightly version by default, Safari has it behind the --use-temporal
flag, and Chrome (as all V8 engines) uses the --harmony-temporal
flag.
If you’re not keen on setting up canary builds of different browsers, there are two other paths you can take: using the @js-temporal/polyfill directly, or accessing the Temporal
proposal docs which has the polyfill set up and available, so you can mess around with the new API in the devtools of your browser.
Closing thoughts
While the new APIs aren’t quite yet available, support is slowly but surely landing in all modern browsers. Given the robustness of the API and the wide range of friction points it addresses, it’s fair to say that it’ll be the way to go when it comes to any kind of date/time manipulation in JavaScript - be that on the frontend or in a Node.js app/backend.
Should we go ahead and use the new API with the help of the polyfill? Probably not.
The roadmap on NPM clearly states that it is not production ready yet. If you’re feeling particularly open for some risk, you could always adopt the new way of working in an isolated, non-”business critical” part of your application (although I wouldn’t persist it in a database, as things might still change). This would give the developers on your team a head start on familiarizing themselves with the new API.
Don’t forget to explore the huge amount of documentation and examples that MDN provides on the Temporal API and all of its bits and pieces.
Some links to the proposal/cookbook for further reading:
Subscribe to my newsletter
Read articles from Ákos (heu) directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
