DF[dot]DEV

An introduction to the upcoming Temporal API in JavaScript

The Temporal API is an upcoming JavaScript feature that aims to address the difficulties that developers have with the current Date object. It’s not a replacement for Date, but rather new functionality to make working with timezones and date math far easier than it currently is.

The largest difference between Temporal and Date, is that Temporal has the ability to be both timezone and calendar aware depending on the instance type used. Unlike Date, which is only aware of the system time, these instances of Temporal have complete support for all timezones and can be created in whichever timezone is needed, regardless of the system time setting.

Temporal instances are also immutable by design. Unlike the Date object, Temporal instances cannot be modified once created and actions taken on Temporal objects return new Temporal instances. In addition to the reliability this provides, it also allows for many of the Temporal methods to be chained together, as you’ll see below.

A breakdown of the different Temporal instance types

The Temporal API offers us developers seven new objects to express dates and times in different ways, with each supporting different components of date and time. Here is a chart borrowed from the official proposal showing the object relationship and hierarchy with the new types that Temporal introduces. It also details their support for exact (UTC) time and local (system) time, which is an important distinction to understand when working with Temporal.

test

Let’s take a look at how each new type can be constructed:

Temporal.Instant

A Temporal.Instant instance represents an exact point in time and contains only UTC (“exact”) components. No local time components are included.

// Using `Temporal.Now`
Temporal.Now.instant(); // 2025-01-17T18:39:28.334304Z
// From a specific time
Temporal.Instant.from('2025-01-17T05:23:00Z'); // 2025-01-17T05:23:00Z
// From an existing Date object
const currentDate = new Date();
currentDate.toTemporalInstant(); // 2025-01-17T18:39:28.334Z

Temporal.PlainDateTime

A Temporal.PlainDateTime instance represents local time with year, month, day, and time components. This is the most complete type and is comparable to the existing Date object in JavaScript.

// Using `Temporal.Now`
Temporal.Now.plainDateTimeISO(); // 2025-01-17T05:23:00.055700992
// From a specific time
Temporal.PlainDateTime.from('2025-01-17T05:23:00'); // 2025-01-17T05:23:00

Temporal.PlainDate

A Temporal.PlainDate instance represents local time with year, month, and day components.

// Using `Temporal.Now`
Temporal.Now.plainDateISO(); // 2025-01-17
// From a specific time
Temporal.PlainDate.from('2025-01-17T05:23:00Z'); // 2025-01-17

Temporal.PlainMonthDay

A Temporal.PlainMonthDay instance represents local time with month and day components.

// NOTE: Temporal.Now does not contain a method to construct a new Temporal.PlainMonthDay instance
// From a specific time
Temporal.PlainMonthDay.from('2025-01-17T05:23:00'); // 01-17

Temporal.PlainYearMonth

A Temporal.PlainYearMonth instance represents local time with year and month components.

// NOTE: Temporal.Now does not contain a method to construct a new Temporal.PlainYearMonth instance
// From a specific time
Temporal.PlainYearMonth.from('2025-01-17T05:23:00'); // 2025-01

Temporal.PlainTime

A Temporal.PlainTime instance represents local time with only a time component.

// Using `Temporal.Now`
Temporal.Now.plainTimeISO(); // 05:23:34.435644928
// From a specific time
Temporal.PlainTime.from('2025-01-17T05:23:00Z'); // 05:23:00

Temporal.ZonedDateTime

A Temporal.ZonedDateTime instance represents an exact time in a specific timezone. If no timezone is specified, the system timezone will be used.

// Using `Temporal.Now`
Temporal.Now.zonedDateTimeISO(); // 2025-01-17T11:26:02.197758976-08:00[America/Los_Angeles]
Temporal.Now.zonedDateTimeISO('America/New_York'); // 2025-01-17T14:26:02.197758976-05:00[America/New_York]
// From a specific local time
Temporal.ZonedDateTime.from('2025-01-17T09:30:00[America/Los_Angeles]'); // 2025-01-17T09:30:00-08:00[America/Los_Angeles]
Temporal.ZonedDateTime.from('2025-01-17T09:30:00[America/New_York]'); // 2025-01-17T09:30:00-05:00[America/New_York]

A note on constructing Temporal instances with ISO 8601 style strings

When using the from static method on any instance type that does not support exact (UTC) time (PlainDateTime, PlainDate, PlainMonthDay, PlainYearMonth, and PlainTime), if an ISO 8601 string is used as the argument it must not contain timezone information. For example, 2025-01-17T05:23:00 will be interpreted as local time and work as expected, but 2025-01-17T05:23:00Z will be interpreted as UTC time and an error will be thrown by the constructor.

What can we do with these Temporal instances?

Now that we’re familiar with how to construct these new types, let’s take a look at some examples of actions that can be performed with them.

Performing date math with Temporal instances

The Temporal API allows you to perform basic math operations on dates and times using methods such as add, subtract, and round:

const initialDate = Temporal.PlainDateTime.from('2025-01-17T05:23:00'); // 2025-01-17T05:23:00
initialDate.add({ years: 1, months: 5, weeks: 2, hours: 9, seconds: 25 }); // 2026-07-01T14:23:25
initialDate.subtract({ weeks: 1, days: 2, hours: 4, minutes: 47 }); // 2025-01-08T00:36:00
initialDate.round('hour'); // 2025-01-17T05:00:00
// 'round' can also accept an object for more control over the result
initialDate.round({
smallestUnit: 'day',
roundingMode: 'floor'
}); // 2025-01-17T00:00:00

Calculating durations between two Temporal instances

Determining the duration between two dates is incredibly simple with the Temporal API by using the until and since methods. Additionally, the calculations work correctly with dates in different timezones.

const firstDate = Temporal.ZonedDateTime.from(
'2025-01-17T05:23:00[America/Los_Angeles]'
);
const secondDate = Temporal.ZonedDateTime.from(
'2025-01-17T10:10:00[America/New_York]'
);
firstDate.until(secondDate); // PT1H47M
secondDate.since(firstDate); // PT1H47M

The PT1H47M value returned by these methods above is a string representation of the Temporal.Duration object. Since the Temporal API is immutable and returns a new instance for each operation, it becomes easily chainable. Let’s take advantage of that to round the calculated duration to the nearest hour.

// ...continuing from the code above
secondDate.since(firstDate).round('hour'); // PT2H

Update individual components

The with method can be used to update individual components of a Temporal instance. Since it takes an object as a parameter, you can think of this like a grouped version of the Date set methods (setDate, setMinutes, etc…).

const originalDate = Temporal.PlainDateTime.from('2025-01-17T05:23:00');
originalDate.with({ day: 13, hour: 12, minute: 46 }); // 2025-01-13T12:46:00

Use cases

Now that we’re familiar with the types of Temporal instances available to us and some of the functionality they provide, let’s take a look at a few use case examples.

Retrieving a UNIX timestamp for an exact time in a specific timezone

Without the Temporal API, constructing a Date object at a local time in a specific timezone is extremely complex given the nuances of how local time is represented around the world. You can construct a date object using a UTC offset (Ex: new Date('2025-01-17T09:30:00-05:00')), but that offset value will change throughout the year under systems like daylight savings time in the United States.

I had a situation where given a date in YYYY-MM-DD format, I needed to determine the UNIX timestamp value for the NYSE daily market open time of 9:30 AM in the America/New_York timezone. The Temporal API makes this incredibly easy and completely durable in that I don’t need to worry about the server timezone or think about the current UTC offset:

function getMarketOpenTimestamp(ymdStr) {
return Temporal.ZonedDateTime.from(`${ymdStr}T09:30:00[America/New_York]`)
.epochMilliseconds;
}
console.log(getMarketOpenTimestamp('2025-01-17')); // 1737124200000
console.log(getMarketOpenTimestamp('2025-05-13')); // 1747143000000
console.log(getMarketOpenTimestamp('2025-13-01')); // RangeError is thrown for invalid inputs

Calculating recurring events

Keeping with the financial market examples, there is an event referred to as triple witching that occurs every third month on the third Friday. Let’s see how easy this would be to calculate using the Temporal API:

function getTripleWitchingDates(year) {
const tripleWitchingDates = [];
// start at March (3) and loop three months at a time until we've reached December (12)
for (let month = 3; month <= 12; month += 3) {
// Temporal.PlainDate is ideal since we don't need the time component
const startOfMonth = Temporal.PlainDate.from({ year, month, day: 1 });
// Calculate the number of days that we need to add to the first day of
// the month to get us to the first Friday
const numDaysUntilFriday =
startOfMonth.dayOfWeek <= 5
? 5 - startOfMonth.dayOfWeek
: 7 - startOfMonth.dayOfWeek + 5;
// Let's add the number of days to bring us to the first Friday in the month
// and then add two additional weeks.
//
// NOTE: 'add' can be chained since it returns a new Temporal instance
const thirdFriday = startOfMonth
.add({ days: numDaysUntilFriday })
.add({ weeks: 2 });
tripleWitchingDates.push(thirdFriday);
}
return tripleWitchingDates;
}
getTripleWitchingDates(2024); // [ 2024-03-15, 2024-06-21, 2024-09-20, 2024-12-20 ]
getTripleWitchingDates(2025); // [ 2025-03-21, 2025-06-20, 2025-09-19, 2025-12-19 ]
getTripleWitchingDates(2026); // [ 2026-03-20, 2026-06-19, 2026-09-18, 2026-12-18 ]

How to use the Temporal API today

At the time of this post (January 17, 2025), the Temporal API is in stage 3 of the TC39 process and is close to being finalized into the ECMAScript standard.

While no browsers support the API at this point, hopefully it will be moved to stage 4 this year and we’ll begin to see browser support following that. The only server-side runtime that currently has support for it is Deno, where it can be enabled with the --unstable-temporal flag.

In the meantime there are polyfill libraries such as temporal-polyfill that closely match the current status of the specification.

When to use the Temporal API?

Most of the time, the traditional Date object will be fine for a task. Intl.DateTimeFormat can also be used to display a previously generated Date object in a local format. Where the Temporal API will be the most beneficial is when you need to instantiate a date with an exact time in a specific timezone. Additionally, if you find yourself leveraging a library to perform math operations on dates, the Temporal API may allow you to achieve the same results with standardized functionality.

Additional information

Official Temporal API docs

Temporal API cookbook

Deno implementation reference

Back to posts