Everything you need to know about the Temporal API with JavaScript

Discover JavaScript's Temporal API: classes, practical examples, time zone handling, and migration from Date. The ultimate guide to mastering Temporal.
•
Updated on August 14, 2026
•
14 min read
Believemy logo

The Temporal API is the new JavaScript standard designed to replace the Date object. It offers more rigorous date and time management through dedicated classes, default immutability, native time zone support, and non-Gregorian calendar support. Natively available in Chrome and Firefox, it represents the future of time manipulation in JavaScript.

In this complete guide, we will explore every aspect of the Temporal API: its fundamental classes, their concrete use cases, date arithmetic, duration management, and best practices for integrating it into your projects right now.

 

Why the Date object is problematic

Before diving into Temporal, it's essential to understand why the current Date object is no longer sufficient. It's not simply a matter of preference: it's a fundamental design problem that causes bugs in virtually every application that manipulates dates.

A poorly designed Java legacy

JavaScript's Date object is directly inspired by Java's java.util.Date class, which was itself considered so problematic that it was replaced in the early 2010s. But JavaScript, true to its backward compatibility principle, kept this object as-is.

Here are some of the most common pitfalls:

JAVASCRIPT
// Months start at 0: here we get February 1st, not January
const date = new Date(2025, 1, 1);
console.log(date.toISOString()); // 2025-02-01T00:00:00.000Z

// Invalid dates are silently corrected
const invalid = new Date(2025, 1, 30); // February 30th doesn't exist
console.log(invalid.toISOString()); // 2025-03-02T00:00:00.000Z (March 2nd!)

// getYear() returns years since 1900, not the actual year
const now = new Date();
console.log(now.getYear()); // 125 (in 2025)
console.log(now.getFullYear()); // 2025

 

The lack of explicit time zones

Internally, a Date object only stores a timestamp in milliseconds since January 1, 1970 UTC. When you display this date, the browser automatically applies the device's local time zone. You cannot create a date in an arbitrary time zone like Asia/Tokyo or America/New_York without resorting to workarounds with Intl.DateTimeFormat.

This behavior is the source of countless bugs, especially in international applications where the same event must be displayed correctly based on each user's time zone.

 

Problematic mutability

All Date setters modify the object in place, causing side effects that are difficult to track:

JAVASCRIPT
const date = new Date(2025, 0, 15);
const copy = date; // This is NOT a copy, it's a reference

copy.setMonth(5);
console.log(date.getMonth()); // 5 — the original date is modified!

 

Introduction to the Temporal API

Temporal is a global namespace, similar to Math or Intl. It is not a constructor: you cannot write new Temporal(). All its properties and methods are static, and it exposes over 200 utility methods spread across several specialized classes.

Temporal's core philosophy rests on one principle: one type per use case. Instead of a single object that tries to do everything (like Date), Temporal offers distinct classes, each tailored to a specific need.

Main Temporal classes

ClassDescriptionExample usage
Temporal.PlainDateDate without time or time zoneBirthday, public holiday
Temporal.PlainTimeTime without date or time zoneDaily alarm, opening hours
Temporal.PlainDateTimeDate + time, without time zoneLocal appointment
Temporal.ZonedDateTimeDate + time + time zoneInternational event, flight
Temporal.InstantPrecise point on the timeline (timestamp)Server log, technical timestamp
Temporal.DurationDuration between two pointsTime remaining, trip duration
Temporal.PlainYearMonthYear + month onlyMonthly billing
Temporal.PlainMonthDayMonth + day onlyRecurring anniversary

 

Browser support and polyfills

As of mid-2025, Temporal is natively supported by Chrome and Firefox. Safari is in the process of implementing it. For Node.js, you can enable it via the --harmony-temporal flag. Bun is also working on its integration.

For immediate production use, two polyfills are recommended:

BASH
# Official polyfill by proposal champions
npm install @js-temporal/polyfill

# Alternative polyfill by FullCalendar
npm install @fullcalendar/temporal-polyfill
JAVASCRIPT
// Using the polyfill
import { Temporal } from '@js-temporal/polyfill';

const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // 2025-07-11

 

Creating Temporal objects

The Temporal API provides three consistent creation methods, available on each class. This uniformity is one of its greatest advantages over Date.

Via Temporal.Now

The Temporal.Now namespace provides methods to get the current instant in various formats:

JAVASCRIPT
// Precise instant (timestamp with UTC time zone)
const instant = Temporal.Now.instant();
console.log(instant.toString()); // 2025-07-11T14:30:00.123456789Z

// Local date and time (without time zone)
const dateTime = Temporal.Now.plainDateTimeISO();
console.log(dateTime.toString()); // 2025-07-11T16:30:00.123456789

// Today's date only
const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // 2025-07-11

// Current time only
const time = Temporal.Now.plainTimeISO();
console.log(time.toString()); // 16:30:00.123456789

// Date and time with time zone
const zoned = Temporal.Now.zonedDateTimeISO();
console.log(zoned.toString()); // 2025-07-11T16:30:00.123456789+02:00[Europe/Paris]

// System time zone identifier
const tz = Temporal.Now.timeZoneId();
console.log(tz); // Europe/Paris

 

Via the constructor

Each class exposes a constructor that accepts numeric values. Unlike Date, months are indexed starting from 1:

JAVASCRIPT
// PlainDate: year, month, day
const date = new Temporal.PlainDate(2025, 1, 1);
console.log(date.toString()); // 2025-01-01 (indeed January 1st)

// PlainDateTime: year, month, day, hour, minute, second
const dateTime = new Temporal.PlainDateTime(2025, 12, 25, 18, 30, 0);
console.log(dateTime.toString()); // 2025-12-25T18:30:00

// PlainTime: hour, minute, second
const time = new Temporal.PlainTime(8, 0, 0);
console.log(time.toString()); // 08:00:00
Good to know

Unlike Date which silently corrects invalid values, Temporal throws a RangeError if you pass impossible values like February 30th.

 

Via the from() method

The static from() method is the most versatile. It accepts an ISO 8601 string or a properties object:

JAVASCRIPT
// From an ISO string
const date = Temporal.PlainDate.from('2025-08-15');
const dateTime = Temporal.PlainDateTime.from('2025-08-15T09:00:00');

// From a properties object
const dateObj = Temporal.PlainDate.from({ year: 2025, month: 8, day: 15 });

// ZonedDateTime with full time zone
const zoned = Temporal.ZonedDateTime.from(
  '2025-08-15T09:00:00+02:00[Europe/Paris]'
);
console.log(zoned.toString());
// 2025-08-15T09:00:00+02:00[Europe/Paris]

// ZonedDateTime from an object
const zonedObj = Temporal.ZonedDateTime.from({
  timeZone: 'America/New_York',
  year: 2025,
  month: 8,
  day: 15,
  hour: 9,
  minute: 0
});
console.log(zonedObj.toString());
// 2025-08-15T09:00:00-04:00[America/New_York]

 

Reading properties of a Temporal object

Each Temporal class exposes read-only properties that allow you to extract precisely the information you need. No more getMonth() and getFullYear(): you access properties directly.

JAVASCRIPT
const date = Temporal.PlainDate.from('2025-07-14');

console.log(date.year);        // 2025
console.log(date.month);       // 7
console.log(date.day);         // 14
console.log(date.dayOfWeek);   // 1 (Monday)
console.log(date.dayOfYear);   // 195
console.log(date.daysInMonth); // 31
console.log(date.daysInYear);  // 365
console.log(date.monthsInYear);// 12
console.log(date.inLeapYear);  // false
console.log(date.weekOfYear);  // 29
console.log(date.yearOfWeek);  // 2025

For a ZonedDateTime, you get additional properties related to the time zone:

JAVASCRIPT
const zdt = Temporal.ZonedDateTime.from(
  '2025-07-14T15:30:00+02:00[Europe/Paris]'
);

console.log(zdt.timeZoneId);       // Europe/Paris
console.log(zdt.offset);           // +02:00
console.log(zdt.hoursInDay);       // 24
console.log(zdt.epochMilliseconds); // timestamp in milliseconds
console.log(zdt.epochNanoseconds);  // timestamp in nanoseconds (BigInt)

 

Manipulating and modifying dates

One of Temporal's fundamental characteristics is immutability. Every modification operation returns a new object, leaving the original intact. This eliminates an entire category of bugs related to side effects.

Modifying with with()

The with() method creates a copy of the object, replacing only the specified fields:

JAVASCRIPT
const date = Temporal.PlainDateTime.from('2025-01-01T12:00:00');

// Change only the year
const date2030 = date.with({ year: 2030 });
console.log(date2030.toString()); // 2030-01-01T12:00:00

// The original is preserved
console.log(date.toString()); // 2025-01-01T12:00:00

// Change multiple fields at once
const modified = date.with({ month: 6, day: 15, hour: 18 });
console.log(modified.toString()); // 2025-06-15T18:00:00

 

Changing time zones with withTimeZone()

For ZonedDateTime objects, you can convert to another time zone. The absolute instant remains the same, only the local representation changes:

JAVASCRIPT
const paris = Temporal.ZonedDateTime.from(
  '2025-07-14T12:00:00+02:00[Europe/Paris]'
);

const tokyo = paris.withTimeZone('Asia/Tokyo');
console.log(tokyo.toString());
// 2025-07-14T19:00:00+09:00[Asia/Tokyo]

const newYork = paris.withTimeZone('America/New_York');
console.log(newYork.toString());
// 2025-07-14T06:00:00-04:00[America/New_York]

 

Additional ZonedDateTime methods

ZonedDateTime also provides withCalendar() to change the calendar system and withPlainTime() to replace the time portion:

JAVASCRIPT
const zdt = Temporal.ZonedDateTime.from(
  '2025-07-14T15:30:00+02:00[Europe/Paris]'
);

// Go back to midnight
const midnight = zdt.startOfDay();
console.log(midnight.toString());
// 2025-07-14T00:00:00+02:00[Europe/Paris]

// Replace the time with a specific value
const at9am = zdt.withPlainTime('09:00:00');
console.log(at9am.toString());
// 2025-07-14T09:00:00+02:00[Europe/Paris]

 

Date arithmetic

Time arithmetic is one of the areas where Temporal most clearly outperforms the Date object. The add() and subtract() methods accept an object describing the duration to add or subtract.

Adding and subtracting time

JAVASCRIPT
const date = Temporal.PlainDate.from('2025-01-01');

// Add 4 days
const plus4 = date.add({ days: 4 });
console.log(plus4.toString()); // 2025-01-05

// Add 40 days (automatically handles month changes)
const plus40 = date.add({ days: 40 });
console.log(plus40.toString()); // 2025-02-10

// Subtract 2 months and 15 days
const minus = date.subtract({ months: 2, days: 15 });
console.log(minus.toString()); // 2024-10-17

// Add a complex duration
const future = date.add({ years: 1, months: 6, days: 10 });
console.log(future.toString()); // 2026-07-11

 

Arithmetic with DST handling

When using ZonedDateTime, arithmetic automatically accounts for daylight saving time (DST) changes. This is a considerable advantage over Date:

JAVASCRIPT
// DST change in France occurs on the last Sunday of March
const beforeDST = Temporal.ZonedDateTime.from(
  '2025-03-29T12:00:00+01:00[Europe/Paris]'
);

const afterDST = beforeDST.add({ days: 1 });
console.log(afterDST.toString());
// 2025-03-30T12:00:00+02:00[Europe/Paris]
// The offset changed from +01:00 to +02:00 automatically

 

Calculating the difference between two dates

The until() and since() methods return a Temporal.Duration object representing the difference between two dates:

JAVASCRIPT
const start = Temporal.PlainDate.from('2025-01-01');
const end = Temporal.PlainDate.from('2025-07-21');

// By default, duration is in days
const duration = start.until(end);
console.log(duration.toString()); // P201D (201 days)

// To get months and days, specify largestUnit
const detailed = start.until(end, { largestUnit: 'months' });
console.log(detailed.toString()); // P6M20D (6 months and 20 days)

// since() is the inverse of until()
const since = end.since(start, { largestUnit: 'months' });
console.log(since.toString()); // P6M20D

 

Comparing two dates

Comparison is done via the static compare() methods and the instance equals() method:

JAVASCRIPT
const date1 = Temporal.PlainDate.from('2025-01-01');
const date2 = Temporal.PlainDate.from('2025-07-14');

// compare() returns -1, 0, or 1
console.log(Temporal.PlainDate.compare(date1, date2)); // -1 (date1 before date2)
console.log(Temporal.PlainDate.compare(date2, date1)); // 1
console.log(Temporal.PlainDate.compare(date1, date1)); // 0

// equals() to test equality
console.log(date1.equals(Temporal.PlainDate.from('2025-01-01'))); // true

// Sorting an array of dates
const dates = [
  Temporal.PlainDate.from('2025-12-25'),
  Temporal.PlainDate.from('2025-01-01'),
  Temporal.PlainDate.from('2025-07-14')
];
dates.sort(Temporal.PlainDate.compare);
console.log(dates.map(d => d.toString()));
// ['2025-01-01', '2025-07-14', '2025-12-25']
Warning

Never use === to compare two Temporal objects. Like any JavaScript object, this compares memory references, not values. Always use equals() or compare().

 

Managing durations with Temporal.Duration

Temporal.Duration represents a duration explicitly. Unlike the millisecond calculations that Date requires, you work directly with readable units.

Creating and manipulating durations

JAVASCRIPT
// Create a duration from an object
const duration = Temporal.Duration.from({
  hours: 130,
  minutes: 20
});

// Get the total in seconds
const totalSeconds = duration.total({ unit: 'second' });
console.log(totalSeconds); // 469200

// Create a duration from an ISO 8601 string
const d2 = Temporal.Duration.from('P1Y6M15DT12H30M');
console.log(d2.years);   // 1
console.log(d2.months);  // 6
console.log(d2.days);    // 15
console.log(d2.hours);   // 12
console.log(d2.minutes); // 30

 

Rounding a duration

The round() method allows you to round a duration. Note: conversions involving months or years require a relativeTo, because a month doesn't always have the same number of days:

JAVASCRIPT
const duration = Temporal.Duration.from({ months: 6, days: 20 });

// Round to days (requires relativeTo for months)
const inDays = duration.round({
  largestUnit: 'days',
  relativeTo: Temporal.PlainDate.from('2025-01-01')
});
console.log(inDays.toString()); // P201D

// For sub-day units, no relativeTo needed
const hours = Temporal.Duration.from({ minutes: 150 });
const rounded = hours.round({ largestUnit: 'hours' });
console.log(rounded.toString()); // PT2H30M

 

Formatting a duration with Intl.DurationFormat

The Intl.DurationFormat API allows you to display durations in a localized manner:

JAVASCRIPT
const duration = Temporal.Duration.from({ months: 6, days: 20, hours: 3 });

const formatter = new Intl.DurationFormat('en', { style: 'long' });
console.log(formatter.format(duration));
// "6 months, 20 days, and 3 hours"

const shortFormatter = new Intl.DurationFormat('en', { style: 'short' });
console.log(shortFormatter.format(duration));
// "6 mo, 20 days, 3 hr"

 

Conversions between Temporal classes

A major advantage of the Temporal API is the ease with which you can convert one type to another. Each class exposes methods like toPlainDate(), toPlainTime(), toZonedDateTime(), etc.

JAVASCRIPT
// From ZonedDateTime to "Plain" types
const zdt = Temporal.ZonedDateTime.from(
  '2025-07-14T15:30:00+02:00[Europe/Paris]'
);

const plainDate = zdt.toPlainDate();
console.log(plainDate.toString()); // 2025-07-14

const plainTime = zdt.toPlainTime();
console.log(plainTime.toString()); // 15:30:00

const plainDateTime = zdt.toPlainDateTime();
console.log(plainDateTime.toString()); // 2025-07-14T15:30:00

const instant = zdt.toInstant();
console.log(instant.toString()); // 2025-07-14T13:30:00Z

// From PlainDate to ZonedDateTime (requires time zone and time)
const date = Temporal.PlainDate.from('2025-07-14');
const zoned = date.toZonedDateTime({
  timeZone: 'Europe/Paris',
  plainTime: Temporal.PlainTime.from('09:00:00')
});
console.log(zoned.toString());
// 2025-07-14T09:00:00+02:00[Europe/Paris]

// From PlainDate to PlainYearMonth or PlainMonthDay
const ym = date.toPlainYearMonth();
console.log(ym.toString()); // 2025-07

const md = date.toPlainMonthDay();
console.log(md.toString()); // 07-14

 

Non-Gregorian calendar support

Temporal natively supports multiple calendar systems, which is essential for international applications. By default, all objects use the ISO 8601 calendar (based on the Gregorian), but you can work with other calendars.

JAVASCRIPT
// Convert a Gregorian date to the Hebrew calendar
const isoDate = Temporal.PlainDate.from('2025-07-14');
const hebrewDate = isoDate.withCalendar('hebrew');
console.log(hebrewDate.toString()); // date in Hebrew format
console.log(hebrewDate.year);       // year in the Hebrew calendar
console.log(hebrewDate.month);      // month in the Hebrew calendar

// Japanese calendar (imperial eras)
const jpDate = isoDate.withCalendar('japanese');
console.log(jpDate.era);     // "reiwa"
console.log(jpDate.eraYear); // 7 (Reiwa 7)

// Localized display with Intl
console.log(hebrewDate.toLocaleString('en-US', {
  calendar: 'hebrew',
  dateStyle: 'full'
}));
Good to know

The list of supported calendars can be obtained via Intl.supportedValuesOf('calendar'). Among the most common: islamic, hebrew, chinese, japanese, persian, buddhist.

 

Integration with HTML inputs

The Temporal API integrates naturally with HTML <input> elements of type date, time, and datetime-local. These fields return values in ISO format that from() accepts directly.

JAVASCRIPT
// Input of type "date"
const dateInput = document.querySelector('#date-input');
dateInput.addEventListener('change', () => {
  const date = Temporal.PlainDate.from(dateInput.value);
  console.log(date.toString());
  console.log(date.dayOfWeek); // Day of the week
});

// Input of type "time"
const timeInput = document.querySelector('#time-input');
timeInput.addEventListener('change', () => {
  const time = Temporal.PlainTime.from(timeInput.value);
  const in3Hours = time.add({ hours: 3 });
  console.log(in3Hours.toString()); // e.g.: "18:24:00"
});

// Input of type "datetime-local" converted to ZonedDateTime
const dtInput = document.querySelector('#datetime-input');
dtInput.addEventListener('change', () => {
  const plain = Temporal.PlainDateTime.from(dtInput.value);
  const zoned = plain.toZonedDateTime('Europe/Paris');
  console.log(zoned.toString());
});

 

The RFC 9557 format

All Temporal classes use the RFC 9557 format for serialization and deserialization. This format is an extension of ISO 8601 / RFC 3339 and adds the ability to specify the time zone and calendar system:

TEXT
YYYY-MM-DD T HH:mm:ss.sssssssss Z/±HH:mm [time_zone_id] [u-ca=calendar_id]

Some concrete examples:

JAVASCRIPT
// Instant (always in UTC)
'2025-07-14T13:30:00Z'

// ZonedDateTime with time zone and calendar
'2025-07-14T15:30:00+02:00[Europe/Paris][u-ca=iso8601]'

// Simple PlainDate
'2025-07-14'

// Simple PlainTime
'15:30:00'

// PlainDateTime
'2025-07-14T15:30:00'

This format allows precision up to the nanosecond (9 decimal places), compared to milliseconds for Date. This is particularly useful for scientific applications or systems requiring high temporal precision.

 

Best practices for using Temporal

Here are the essential recommendations to get the most out of the Temporal API in your projects:

1. Choose the right type for each use case. Don't use ZonedDateTime everywhere by default. A birthday is a PlainDate, a daily alarm is a PlainTime, an international event is a ZonedDateTime.

2. Don't mix year and era/eraYear. If you're using calendars with eras, choose a single representation and stay consistent.

3. Always use compare() or equals() for comparisons. The === operator compares references, not values.

4. Specify relativeTo for duration conversions involving months or years. A month doesn't always have the same number of days.

5. Never assume a week is 7 days. Use daysInWeek for robust code with non-Gregorian calendars.

6. Prefer monthCode over month for storing recurring data (like birthdays), because monthCode corresponds to the month's name regardless of leap months.

7. Use a polyfill in production until support is universal, and test on all your target environments.

 

Migration from Date to Temporal

Here's a summary of equivalences between the old Date and the new Temporal API:

OperationWith DateWith Temporal
Current datenew Date()Temporal.Now.plainDateISO()
Current timestampDate.now()Temporal.Now.instant()
Create a datenew Date(2025, 0, 1)new Temporal.PlainDate(2025, 1, 1)
Parse a stringnew Date('2025-01-01')Temporal.PlainDate.from('2025-01-01')
Add daysdate.setDate(date.getDate() + 7)date.add({ days: 7 })
Difference(date2 - date1) / 86400000date1.until(date2)
Formatdate.toLocaleDateString()date.toLocaleString()
Comparedate1.getTime() === date2.getTime()date1.equals(date2)

 

Frequently asked questions

Question

Is the Temporal API ready for production?

Temporal has been natively supported by Chrome and Firefox since 2025. For other browsers and environments, mature polyfills like @js-temporal/polyfill allow you to use it right now. For new projects, it's a wise choice. For existing projects, a progressive migration is recommended.

 

Question

Does Temporal replace libraries like Moment.js or date-fns?

Yes, in the vast majority of cases. Temporal natively covers the main use cases of these libraries: parsing, formatting, arithmetic, time zones, and calendars. The advantage is that being built into the language, it requires no external dependencies and will always be maintained with the JavaScript engine.

 

Question

What is the difference between Temporal.Instant and Temporal.ZonedDateTime?

Temporal.Instant represents an absolute point on the timeline (a timestamp in nanoseconds since the Unix epoch), with no notion of time zone or calendar. Temporal.ZonedDateTime combines an instant with a time zone and a calendar, which allows obtaining a human-readable date and time in a given region.

 

Question

How does Temporal handle daylight saving time (DST) changes?

ZonedDateTime automatically handles DST transitions. When you perform arithmetic operations, the offset is adjusted. For ambiguous moments (during the fall-back when an hour exists twice), Temporal provides ambiguity resolution options via the disambiguation parameter in conversion methods.

Category: Developers

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.

Believemy logo
Comments (0)

You need to be logged in to comment on this article: log in or sign up.

Read next

Creating a clock using JavaScript

Imagine a clock that shows the time to your users, on a travel site for example, with the time zone of the destination country? Let's create this magnificent clock together, using JavaScript!

December 9, 2024

Understanding APIs: Exploring their Meaning and Utility

Discover the fascinating world of Application Programming Interfaces (APIs) in this captivating article. Immerse yourself in the essence of APIs, the digital bridges that enable different applications to work together seamlessly. Explore how APIs simplify technological complexity, foster innovation and enhance your everyday user experience. Real-life examples will guide you through weather applications, online payments and much more, showing you how APIs have become indispensable in the modern digital landscape.

December 11, 2024

Understanding the Window object with JavaScript

Find out all about the Window object with JavaScript: URL manipulation with window.location, history with window.history, DOM with window.document, browser with window.navigator, and screen with window.screen.

December 9, 2024

OpenAI Dots: everything you need to know

A dot keeps working after you close ChatGPT, but it doesn't get to decide everything. What it does alone, what it asks you first, and why Europe waits.

October 1, 2026

What is JSON?

Find out all you need to know about JSON, the world's most widely used format for API calls, and discover why it's a must-have.

December 9, 2024

Claude Code is temporarily doubling its usage limits

Anthropic doubles Claude Code usage limits during off-peak hours. Discover how to take advantage, exact schedules, and eligible plans.

March 16, 2026

7 Claude Code mods to create when you work alone (no coding)

Tempted by mods but not sure what to ask for? Here are 7 ideas that help when you work alone, each with the sentence to paste into Claude Code.

October 13, 2026

Creating a modal window without using JavaScript

Have you often wanted to create a modal window (a window that opens inside your website) but don't know anything about JavaScript? Let's find out how!

December 9, 2024

Become the solopreneur you dream of being.

First project, career change, business launch: access our courses, practice on real-world cases, and move forward with clear guidance.