Tackling Temporal Troubles
- Interactive slides (Pitch)
- Static slides (PDF, 2 MB)
- Recording (YouTube)
The following text was originally published on the Factorial website.
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
- Limited time zone support:
UTC and the user’s system time zone. Should be enough, right? - Mutability:
Don’t forget to clone your date object or face the consequences. - Few high-level API methods:
Want to do calculations with dates? Good luck! - Design inconsistencies:
Zero-based months and the infamousgetYear
, 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.