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.