· 6 min read
The JavaScript Temporal API: what it is and how to use it now
The JavaScript Temporal API reached Stage 4 and ships in Chrome, Firefox and Node 26. Here are its types, the DST rules, and how to migrate from Date.

The JavaScript Temporal API is the new built-in replacement for Date. It gives you separate, immutable types for a calendar day, a wall-clock time, an exact instant and a time in a specific time zone, so you stop squeezing all of them into one object. It reached Stage 4 at TC39 in March 2026, ships in Firefox, Chrome, Edge and Node.js 26, and you can use it everywhere else today with a polyfill.
That last part is what changed this year. Temporal has been "coming soon" for a long time. In 2026 it stopped being a proposal and became part of the language.
What is the JavaScript Temporal API?
Temporal is a global namespace object, like Math or Intl, that holds a family of date and time classes. Each class represents exactly one idea, which is the main difference from Date.
Date is a single number: milliseconds since 1970 in UTC. Everything else, the local day, the hour, the time zone, is computed from that number on the fly using whatever zone the machine happens to be in. That is why a date-only string can land on the wrong day depending on where your user lives.
Temporal splits the concepts apart. The types you will use most:
| Type | What it means | Example |
|---|---|---|
Temporal.PlainDate | A calendar day, no time, no zone | a due date, a birthday |
Temporal.PlainTime | A wall-clock time, no date | "opens at 09:30" |
Temporal.PlainDateTime | Date and time, no zone | a form input before you know the zone |
Temporal.Instant | An exact moment on the global timeline | a log timestamp |
Temporal.ZonedDateTime | An instant plus a time zone | a meeting in New York |
Temporal.Duration | A length of time | "2 months and 25 days" |
A "plain" type is one with no time zone attached. It means the same thing wherever the code runs, which is exactly what you want for things like dates of birth.
Every Temporal object is immutable: methods like add return a new object and never change the original. If you have ever had a bug from calling setDate() on a Date that was shared with other code, this alone is worth the switch.
Where does Temporal work today?
As of the end of September 2026:
- Firefox shipped it in version 139 (May 2025).
- Chrome and Edge shipped it in version 144 (January 2026).
- Node.js 26 enables it by default (May 2026). Earlier Node versions do not have it without a flag.
- TypeScript 6.0 includes the type definitions. Set
"lib": ["esnext"]or add"esnext.temporal"to yourlibarray. - Safari does not ship it in a stable release yet, so it is not Baseline.
Because Safari is missing, a public website should load a polyfill for now. There are a few maintained ones listed in the proposal repository, including temporal-polyfill and @js-temporal/polyfill.
npm install temporal-polyfill
import { Temporal } from 'temporal-polyfill';
On a server you control, like a Node 26 backend, you can skip the polyfill and use the global directly.
How do you do common date tasks with Temporal?
Parsing a calendar date gives you a day, not an instant. There is no clock involved, so no time zone can shift it.
const due = Temporal.PlainDate.from('2026-09-30');
due.day; // 30, in every time zone
due.toString(); // '2026-09-30'
Date arithmetic takes an object describing the change. Month math has a defined rule for short months: by default it clamps to the last valid day. If you would rather fail loudly, pass overflow: 'reject'.
Temporal.PlainDate.from('2026-01-31').add({ months: 1 }).toString();
// '2026-02-28'
Temporal.PlainDate.from('2026-01-31').add({ months: 1 }, { overflow: 'reject' });
// throws RangeError
The difference between two dates is a Duration, and you choose the largest unit you want back:
const a = Temporal.PlainDate.from('2026-09-30');
const b = Temporal.PlainDate.from('2026-12-25');
a.until(b).toString(); // 'P86D'
a.until(b, { largestUnit: 'month' }).toString(); // 'P2M25D'
Those strings are ISO 8601 durations: P86D is 86 days, P2M25D is two months and 25 days.
Comparing uses static methods, which also work as sort callbacks:
Temporal.PlainDate.compare(a, b); // -1
dates.sort(Temporal.PlainDate.compare);
a.equals(Temporal.PlainDate.from('2026-09-30')); // true
How does Temporal handle time zones and DST?
ZonedDateTime carries a named IANA time zone, like America/New_York, rather than a fixed offset. That lets it get daylight saving time right, which a fixed offset cannot.
Converting a meeting into another zone is one call:
const meeting = Temporal.ZonedDateTime.from('2026-11-01T09:00[America/New_York]');
meeting.withTimeZone('Asia/Kolkata').toString();
// '2026-11-01T19:30:00+05:30[Asia/Kolkata]'
The more interesting case is the day clocks change. In the US, clocks jump forward on 8 March 2026. Temporal treats "one day later" and "24 hours later" as different questions, because on that date they have different answers:
const sat = Temporal.ZonedDateTime.from('2026-03-07T09:00[America/New_York]');
sat.add({ days: 1 }).toString();
// '2026-03-08T09:00:00-04:00[America/New_York]' <- same wall-clock time
sat.add({ hours: 24 }).toString();
// '2026-03-08T10:00:00-04:00[America/New_York]' <- exactly 24 hours later
A daily reminder wants the first. A token expiry wants the second. With Date you had to know this and handle it yourself; with Temporal you say which one you mean.
How do you migrate from Date?
You do not need to rewrite everything at once. Temporal and Date can live side by side, and the bridge between them is the epoch millisecond value.
const legacy = new Date();
const instant = Temporal.Instant.fromEpochMilliseconds(legacy.getTime());
// and back
const again = new Date(instant.epochMilliseconds);
A sensible order for an existing codebase:
- Use
PlainDatefor anything that is really a calendar day: due dates, birthdays, date inputs. These cause the most bugs withDate. - Use
Instantfor timestamps you store or send over the wire. - Convert to
ZonedDateTimeonly at the edge, when you display something to a person in their zone or schedule something in a specific place. - Keep
Datewhere a library still requires it, and convert at that boundary.
Temporal objects also work with Intl, so formatting stays familiar:
Temporal.PlainDate.from('2026-09-30').toLocaleString('en-IN', { dateStyle: 'long' });
// '30 September 2026'
One thing to watch: Temporal objects serialise to ISO strings with toString() and toJSON(), but JSON.parse will not turn them back into objects. Parse them explicitly with the right type's from() when data comes back in.
Key takeaways
- Temporal reached Stage 4 in March 2026 and is now part of JavaScript.
- It ships in Firefox 139+, Chrome and Edge 144+ and Node.js 26; Safari still needs a polyfill.
- Pick the type that matches the idea:
PlainDatefor days,Instantfor moments,ZonedDateTimefor a time in a place. - All Temporal objects are immutable, and DST behaviour is explicit (
daysversushours). - Migrate gradually, starting with calendar dates, and bridge to
Datewith epoch milliseconds.
FAQ
Is Temporal ready for production?
On runtimes that ship it, such as Node.js 26 or a Chrome-only internal tool, yes. For public websites, load a polyfill until Safari ships it in a stable release.
Does Temporal replace libraries like Moment.js or date-fns?
For most parsing, arithmetic, time zone and formatting work, the built-in API now covers what those libraries were used for. You may still keep a library for specific helpers, but new code can start with Temporal.
Can I use Temporal with TypeScript?
Yes. TypeScript 6.0 ships the types. Add "esnext" or "esnext.temporal" to the lib option in your tsconfig.json.