The End of Date Libraries? Exploring JavaScript's Built-in Temporal API
5 min read
1209 words
Working with date objects in Javascript can be quite a pain. That is why I
usually use a library like date-fns
for comparing, adding hours to my dates or
working with dates and times in general.
The TC39, which is the group of people who develop the definition of JavaScript, is working on the Temporal API. You can check out the proposal and documentation here.
With this, we no longer need date libraries, because the API itself is a breeze to work with.
Browser support
Most browsers are in the process of implementing this API. There is already a polyfill available for us to use in unsupported environments.
The Deno runtime has already implemented it behind an --unstable-temporal
flag
and gives a taste of how this may work in the future. So we need to start our
Deno application as deno run --unstable-temporal main.ts
.
In the following we will compare how we can replace the date objects in Javascript with the new Temporal API. We will look at how to get the current timestamp, how to add hours or days to dates, how to format dates, and how to get information about the days between two dates.
Get the current time
The traditional Date object returns a less intuitive timestamp that includes the
time zone offset, making it easy to confuse. In contrast, the Temporal API
provides a more structured and clear PlainDateTime
object that explicitly
separates the date and time components. Temporal's output is more readable and
less prone to timezone-related errors. As we can see in the following example,
the Date object is in UTC
by default, but since I am in Germany with the CET
timezone, we see a difference.
// Get the current date and time in the ISO 8601 format using the Temporal API
const date = new Date();
const temporal = Temporal.Now.plainDateTimeISO();
console.log("date:", date); // 2025-01-05T10:55:57.623Z
console.log("temporal:", temporal); // 2025-01-05T11:55:57.63658496
Get the date in epoch milliseconds
While both methods return the same millisecond value, Temporal.Now.instant()
provides a more semantic approach.
const dateEpoch = Date.now();
const temporalEpoch = Temporal.Now.instant().epochMilliseconds;
console.log("date epoch:", dateEpoch);
console.log("temporal epoch:", temporalEpoch);
Get a future date
The behaviour of the Date constructor can be unpredictable with different string
formats and will silently accept invalid dates. Temporal.PlainDate.from()
provides strict parsing and will throw clear errors for invalid input. In
addition, Temporal creates a date-only object when appropriate, preventing
accidental time-based calculations.
const dateFuture = new Date("2025-12-31");
const temporalFuture = Temporal.PlainDate.from("2025-12-31");
console.log("date future:", dateFuture);
console.log("temporal future:", temporalFuture);
Add 5 days to future date
This example demonstrates Temporal's superior date manipulation capabilities.
The Date API requires multiple steps and mutates the original date object using
the error-prone setDate()
method. Temporal's add()
method is immutable, more
readable and accepts an intuitive object parameter that indicates the intent to
add 5 days.
const date = new Date();
const temporal = Temporal.Now.plainDateTimeISO();
const dateAdd = new Date(date.setDate(date.getDate() + 5));
const temporalAdd = temporal.add({ days: 5 });
console.log("date add:", dateAdd); // 2025-01-10T10:52:33.781Z
console.log("temporal add:", temporalAdd); // 2025-01-10T11:52:33.794710016
Get a formatted date
The Date API's toLocaleDateString()
method provides basic formatting, but
lacks consistency across browsers. Temporal's toLocaleString()
provides more
consistent results and more control over the output format. It handles timezones
more explicitly and offers more formatting options out of the box. The example
shows how Temporal preserves more information in its default formatting.
const date = new Date();
const temporal = Temporal.Now.plainDateTimeISO();
const dateFormatted = date.toLocaleDateString("de-DE");
const temporalFormatted = temporal.toLocaleString("de-DE");
console.log("date formatted:", dateFormatted); // 5.1.2025
console.log("temporal formatted:", temporalFormatted); // 5.1.2025, 11:51:36
Immutability
A potential pitfall with Date objects
Consider the following example. We can see a pitfall when working with date objects
const processBooking = (bookingDate: Date) => {
// Attempting to create a checkout date 3 days later
// BUT this mutates the original booking date!
bookingDate.setDate(bookingDate.getDate() + 3);
return bookingDate;
};
const customerBooking = new Date("2025-01-05");
console.log("Original booking:", customerBooking); // 2025-01-05
const checkoutDate = processBooking(customerBooking);
console.log("After processing:");
console.log("Booking date:", customerBooking); // 2025-01-08 (Original date was mutated!)
console.log("Checkout date:", checkoutDate); // 2025-01-08
Here we see one of the problems with date objects. Although we have created a new date for our added date, we are changing the original date object. We have to take the step of creating new dates whenever we add days/hours etc. to it.
To make it safe in this case, we need to change our processBooking
function to
the following
const processBooking = (bookingDate: Date) => {
// Create a new date object to avoid mutating the original date
const checkoutDate = new Date(bookingDate);
checkoutDate.setDate(checkoutDate.getDate() + 3);
return checkoutDate;
};
This always creates a new Date object, not mutating the original bookingDate
object passed.
Temporal immutability
Temporal is immutable by default. This means that we never change an instance of
a date. This makes it much more predictable and not as error prone as the Date
object.
If we rewrite the previous example for the Temporal API, we get the following
const processBooking = (bookingDate: Temporal.PlainDate) => {
// Add 3 days to the booking date
return bookingDate.add({ days: 3 });
};
const customerBooking = Temporal.PlainDate.from("2025-01-05");
console.log("Original booking:", customerBooking.toString()); // 2025-01-05
const checkoutDate = processBooking(customerBooking);
console.log("After processing:");
console.log("Booking date:", customerBooking.toString()); // 2025-01-05
console.log("Checkout date:", checkoutDate.toString()); // 2025-01-08
In our processBooking
function we can see that we are working directly on the
bookingDate
and even returning it after adding the 3 days. However, when we
run it we can see that the output is perfectly fine.
Conclusion
The Temporal API is a significant improvement over JavaScript's traditional Date object, addressing many of its long-standing challenges. As we've seen through various examples, Temporal offers several advantages:
- Better Readability: The API provides more intuitive and self-documenting methods for working with dates and times.
- Immutability: All operations return new instances, preventing unexpected side effects and making code more predictable.
- Type Safety: The API is designed with strong typing in mind, reducing the likelihood of runtime errors.
- Timezone Handling: More explicit and reliable handling of timezones compared to the Date object.
- Modern Features: Built-in support for common operations that previously required external libraries.
While the Temporal API is still in the implementation phase for most browsers, its benefits are clear. The availability of a polyfill means that we can start using these improved features today, gradually moving away from the limitations and quirks of the Date object.
As the API becomes more widely supported, it is likely to become the new standard for date and time manipulation in JavaScript, potentially reducing our reliance on external date handling libraries. For developers starting new projects, considering the Temporal API (with polyfill support) could be a forward-thinking choice that leads to more maintainable and reliable code.