Skip to content

Tackling Temporal Troubles

The Trouble with Times

Let’s take a date, for example 10 June 2025, store it in JavaScript and then print it for displaying on a website. To achieve this, we usually reach for the Date API and create a new date object like this:

new Date("2025-06-10");

We quickly run into issues though when trying to work with this object. Printing it gives us somewhat unexpected results. When we’re in Hamburg, Germany, the printed date is:

Tue Jun 10 2025 02:00:00 GMT+0200 (CEST)

In another part of the world, for example in Los Angeles, California, we get:

Mon Jun 09 2025 17:00:00 GMT-0700 (PDT)

Even if we only specify a date, the Date API always adds a time component. When printing the date, the API converts to the user’s system time zone.

We can try to work around this issue, for example by explicitly printing the original date in the ISO format:

new Date("2025-06-10").toISOString();
2025-06-10T00:00:00.000Z

This might not be the desired output format since user’s are more familiar with their locale’s format. Instead we can use the toLocaleDateString method to format the date in a specific locale:

new Date("2025-06-10").toLocaleDateString("en-UK", { dateStyle: "full" });

Which again is printed in the user’s system time zone. In Hamburg this is:

Tuesday, 10 June 2025

And in Los Angeles:

Monday, 9 June 2025

Let’s force a specific time zone, in this case UTC since we’re not interested in time:

new Date("2025-06-10").toLocaleDateString("en-UK", {
	dateStyle: "full",
	timeZone: "UTC",
});

And finally get the desired result:

Tuesday, 10 June 2025

You need to be aware of all these peculiarities of the Date API just to print a date while ignoring the time component.

Hit list of bad Date API design

  1. Limited time zone support:
    UTC and the user’s system time zone. Should be enough, right?
  2. Mutability:
    Don’t forget to clone your date object or face the consequences.
  3. Few high-level API methods:
    Want to do calculations with dates? Good luck!
  4. Design inconsistencies:
    Zero-based months and the infamous getYear, anyone?

Time for Temporal Technology

Let’s solve all these issues by switching to the Temporal API:

Temporal.PlainDate.from("2025-06-10");

This stores a date without the time component, hence the name “plain” date.

Calling the toLocaleString method immediately gives us the desired result, without having to think about time zone issues:

Temporal.PlainDate.from("2025-06-10").toLocaleString("en-UK", { dateStyle: "full" });
Tuesday, 10 June 2025

Calendar day

PlainDate is useful when storing an event that happens on a specific calendar day, for example Easter holiday:

const easter = Temporal.PlainDate.from("2026-04-05");

Wall-clock time

The equivalent for time is PlainTime, for example when storing an alarm clock that shouldn’t change with time zones:

const alarm = Temporal.PlainTime.from("07:00");

Calendar day and wall-clock time

Combine both and get PlainDateTime, a time-zone-independent representation of date and time

const newYear = Temporal.PlainDateTime.from("2026-01-01T00:00");

Calendar month and day

To store a recurring event that happens on the same day every year, PlainMonthDay comes in handy:

const mentalHealthDay = Temporal.PlainMonthDay.from("10-10");

Calendar year and month

And the remaining plain API is PlainYearMonth to store a whole month for a specific year:

const prideMonth = Temporal.PlainYearMonth.from("2025-06");

Time Zones: the final frontier

But sometimes you can’t get away without considering time zones. Let’s add a time component to our date – 10 June 2025, 19:00, Europe/Berlin – and use Temporal to store it:

Temporal.ZonedDateTime.from("2025-06-10T19:00[Europe/Berlin]");

Calling the toLocaleString method always prints the date in the originally specified time zone:

Temporal.ZonedDateTime.from("2025-06-10T19:00[Europe/Berlin]").toLocaleString("en-UK", {
	dateStyle: "full",
	timeStyle: "long",
});
Tuesday, 10 June 2025 at 19:00:00 CEST

Of course it’s possible to convert to a different time zone, for example to show the date and time for an online event in a time zone better suited for the user:

Temporal.ZonedDateTime.from("2025-06-10T19:00[Europe/Berlin]")
	.withTimeZone("America/Los_Angeles")
	.toLocaleString("en-UK", {
		dateStyle: "full",
		timeStyle: "long",
	});
Tuesday, 10 June 2025 at 10:00:00 GMT-7

One thing to note is the immutability of Temporal objects. Every time you call a method that modifies the date or time, a new Temporal object is returned while the original is left unchanged.

Definitive Durations

So far, Temporal provides a cleaner and more intuitive API replacement for Date. Something completely new to JavaScript are duration objects:

Temporal.Duration.from("PT1H30M");

We can also print these with the toLocaleString method in a more human-friendly format:

Temporal.Duration.from("PT1H30M").toLocaleString("en-UK", { style: "long" });
1 hour, 30 minutes

Durations can be converted to numbers for use in JavaScript APIs that don’t directly accept Temporal objects:

const delay = Temporal.Duration.from("PT1H30M").total("milliseconds");
setTimeout(callback, delay);

Computer, add 2 days

In conjunction with other Temporal APIs, durations can be used for date and time calculations:

Temporal.ZonedDateTime.from("2025-06-10T19:00[Europe/Berlin]")
	.add("P2D")
	.toLocaleString("en-UK", {
		dateStyle: "full",
		timeStyle: "long",
	});
Thursday, 12 June 2025 at 19:00:00 CEST

How many days until Halloween?

The other way around, Temporal plain and zoned APIs expose methods to calculate durations:

Temporal.PlainDate.from("2025-06-10")
	.until(Temporal.PlainDate.from("2025-10-31"))
	.toLocaleString("en-UK");
144 days

String Theory

Temporal APIs work with one defined ISO format for dates, times, and durations. No more weird parsing issues like with the Date API.

Date and time string

YYYY-MM-DD T HH:mm:ss.sssssssss Z/±HH:mm [time_zone] [u-ca=calendar]

Duration string

±P nY nM nW nD T nH nM nS

Depending on which API is used, not all components are required, for example PlainDate only uses the date component.

These serialized date, time, and duration representations are useful when communicating with backend APIs where a standardized format is necessary. For internal purposes in JavaScript, objects can be used instead of ISO strings.

Date and time object

Temporal.PlainDate.from({
	year: 2063,
	month: 4,
	day: 5,
});

Temporal.ZonedDateTime.from({
	year: 2233,
	month: 3,
	day: 22,
	timeZone: "America/Chicago",
});

Duration object

Temporal.Duration.from({
	hours: 10,
	minutes: 31,
});

There’s so much more

This article only scratches the surface of the new Temporal APIs. The most comprehensive documentation is available on MDN with lots of examples. The technical specification is available on the TC39 website.