Key Takeaway
The JavaScript Temporal API is the modern, immutable replacement for the flawed Date object. It provides dedicated types like PlainDate, ZonedDateTime, Instant, and Duration, plus first-class time zones and DST-safe arithmetic. Temporal reached TC39 Stage 4 and now ships in Firefox and Chrome, with the js-temporal polyfill covering older engines.
Working with dates in JavaScript has always felt like fighting the language. The built-in Date object was ported almost verbatim from early Java, and it has carried the same design mistakes for three decades. Temporal is the standards-track answer — a brand-new global object built to make date and time handling predictable instead of perilous.
In this guide I walk through why Date is broken, the new Temporal types you will actually use, how immutability and first-class time zones change the way you reason about time, and exactly where Temporal stands today in browsers and in the language standard.
Why the Date object is broken
Date tries to be too many things at once. A single Date instance is simultaneously a timestamp measured in milliseconds since the Unix epoch and a bag of calendar fields, so the API is confusing and easy to misuse. Its worst traits are structural, not cosmetic:
- Mutability — setters like setMonth and setDate change the object in place, so passing a Date around means any function can silently corrupt it.
- Weak time zones — Date only understands UTC and the machine local zone. There is no way to represent a wall-clock time in an arbitrary IANA zone such as Asia/Jakarta.
- Fragile parsing — feeding a string into the Date constructor gives results that historically differed between browsers, so parsing anything but strict ISO input is unreliable.
- One calendar only — Date supports the Gregorian calendar exclusively, ignoring the Hebrew, Chinese, Japanese, and other systems billions of people use.
These are not edge cases. Time-zone confusion and accidental mutation are two of the most common sources of date bugs in production JavaScript, and no amount of wrapper libraries fully hides them.
Meet Temporal: immutable by design
Temporal replaces the single overloaded Date with a small family of purpose-built, immutable types. Every method that looks like it changes a value — add, subtract, with, round — instead returns a brand-new object and leaves the original untouched. That single rule eliminates an entire class of aliasing bugs.
import { Temporal } from '@js-temporal/polyfill';
// A calendar date — no time, no time zone attached
const releaseDay = Temporal.PlainDate.from('2026-08-01');
// Immutable: add() returns a NEW instance, the original is untouched
const nextRelease = releaseDay.add({ months: 1 });
releaseDay.toString(); // '2026-08-01' (unchanged)
nextRelease.toString(); // '2026-09-01'
// A real event carries its IANA time zone
const meeting = Temporal.ZonedDateTime.from(
'2026-03-07T12:00[America/New_York]'
);
// DST-safe arithmetic: adding one calendar day keeps 12:00 wall-clock,
// even though 8 Mar 2026 is only 23 hours long (spring-forward)
meeting.add({ days: 1 }).toString();
// '2026-03-08T12:00:00-04:00[America/New_York]'
// A timestamp on the global timeline: nanosecond precision, no calendar
const now = Temporal.Now.instant();Notice how each value in the sample above declares exactly what it is. A PlainDate has no time zone, so it cannot accidentally shift a day when a server runs in a different region. A ZonedDateTime carries its IANA zone, so arithmetic across daylight-saving boundaries stays correct. There is no ambiguity about what a value means.
Prefer the most specific type your use case allows. Store a birthday as a PlainDate, a daily alarm as a PlainTime, and a real scheduled event as a ZonedDateTime. Reaching for ZonedDateTime everywhere reintroduces the very ambiguity Temporal is designed to remove.
The Temporal types you will use
Temporal exposes a handful of types under one global namespace, each modelling one clear concept. Most application code touches only the first three or four:
- Temporal.Instant — a fixed point on the global timeline with nanosecond precision, stored as nanoseconds from the Unix epoch, with no calendar or zone.
- Temporal.ZonedDateTime — a date and time anchored to a specific IANA time zone and calendar; the type for real-world events like a meeting or a flight.
- Temporal.PlainDate — a calendar date of year, month, and day with no time and no zone; ideal for birthdays, invoices, and all-day events.
- Temporal.PlainTime — a wall-clock time of hour, minute, and second with no date or zone; think of a recurring 09:00 alarm.
- Temporal.PlainDateTime — a date plus a wall-clock time but still no zone; useful when the zone is supplied separately later.
- Temporal.PlainYearMonth and Temporal.PlainMonthDay — a year-and-month such as a billing period, and a month-and-day such as an annual anniversary.
- Temporal.Duration — a length of time expressed from years down to nanoseconds, returned by comparisons and consumed by add and subtract.
- Temporal.Now — a namespace for reading the current instant, zoned date-time, or plain date from the system clock.
First-class time zones and DST-safe math
The feature developers miss most in Date is honest time-zone support. Temporal treats IANA zones as a first-class input: you can construct a ZonedDateTime in Asia/Jakarta or America/New_York directly, convert between zones without losing information, and compare instants across the planet safely.
Arithmetic is daylight-saving aware. In the code sample, adding one calendar day to a New York meeting keeps the 12:00 wall-clock time even though the spring-forward day is only 23 hours long — the offset shifts from minus five to minus four automatically. With Date you would have had to special-case that yourself, and most code never does.
Temporal is not a drop-in rename of Date. Its methods, property names, and rounding behaviour differ deliberately. Budget time to learn the model rather than search-and-replacing Date calls, and keep Instant for machine timestamps separate from the plain types used for human-facing calendar values.
Date versus Temporal at a glance
The table below sums up why teams are migrating. Each row is a concrete pain point that Temporal removes by design rather than by convention.
| Aspect | Legacy Date | Temporal |
|---|---|---|
| Mutability | Setters mutate the object in place | Immutable — every operation returns a new value |
| Time zones | UTC or local machine only | Any IANA time zone, first-class |
| Types | One object represents everything | Distinct types for date, time, instant, and duration |
| Calendars | Gregorian calendar only | Multiple calendar systems supported |
| Parsing | Inconsistent across engines | Strict, predictable ISO 8601 parsing |
Standardization and browser status
Temporal has finished the TC39 process. It reached Stage 4 — the final stage, meaning it is an accepted addition to the ECMAScript standard — after nearly a decade of design work. The specification is being merged into ECMA-262 and ECMA-402, so the API surface is now stable.
Shipping in engines is well underway. Firefox was first, enabling Temporal by default in Firefox 139 on 27 May 2025. Chrome followed in Chrome 144 on 13 January 2026, and Node.js added it in Node 26 on 5 May 2026. Safari support is still in development, so Temporal is not yet a Baseline feature you can assume everywhere.
Should you use Temporal today?
Yes, carefully. In Firefox, Chrome, and recent Node you can call Temporal natively. For everything else, install the official js-temporal polyfill package from the proposal champions, which exposes the exact same API so your code stays forward-compatible. Write against Temporal now, keep the polyfill until Safari ships, and drop it later with zero code changes.

