Light hero background

Temporal API: The Modern Replacement for JavaScript Date

March 14, 2026

On March 11, 2026, Temporal reached TC39 Stage 4 and became part of the ES2026 specification. It is now natively shipped in Chrome 144+, Firefox 139+, and Edge 144+.

Temporal is JavaScript's new datetime API — the language-level answer to thirty years of Date design problems. If you've been reaching for dayjs, date-fns, or moment.js to fill gaps that Date leaves open, Temporal is what makes those libraries unnecessary.

Temporal's Types

Temporal lives as a top-level namespace (like Math or Intl) with a family of distinct types, each purpose-built for a specific use case:

TypeWhen to use
Temporal.PlainDateBirthdays, holidays — date only, no time, no timezone
Temporal.PlainDateTimeCalendar events in a "local" context
Temporal.ZonedDateTimeMeetings, flights — explicit IANA timezone, handles DST automatically
Temporal.InstantAn exact point in time, for storage or cross-timezone comparison
Temporal.DurationThe span between two points in time
Temporal.PlainTimeTime-of-day without a date

All Temporal objects are immutable — operations always return new objects, originals are never modified. When in doubt, start with ZonedDateTime.

Here are a few concrete examples you can run directly in the browser:

The examples on this page are editable and include the @js-temporal/polyfill, so they run in any browser. If your browser natively supports Temporal (Chrome 144+, Firefox 139+, Edge 144+), the polyfill is skipped and the native implementation is used instead.

Example 1: Duration Arithmetic

With Date, calculating "how long between two times" means subtracting milliseconds, then manually dividing by 86400000 for days, taking the remainder, dividing by 3600000 for hours... Temporal gives you a Duration object with days, hours, and minutes already separated, plus .total('hours') to convert to a single unit:

Try it: Date version — duration arithmetic
JS
Try it: Temporal version — duration arithmetic
TemporalJS
正在載入 Temporal polyfill…

Example 2: Calendar Support

The Chinese lunar calendar has months of variable length (29 or 30 days), and inserts a leap month in certain years. Adding "two months" with Date adds two Gregorian months, which shifts the lunar date. Temporal supports non-Gregorian calendars natively via the [u-ca=chinese] annotation — "add two months" operates within lunar calendar rules:

Try it: Chinese lunar calendar month arithmetic
TemporalJS
正在載入 Temporal polyfill…

Example 3: Instant for Cross-Timezone Display

Temporal.Instant represents an exact point in time with no timezone or calendar semantics — just nanoseconds since the Unix epoch. Store it, transmit it, then convert for each user's timezone:

Try it: Instant cross-timezone display
TemporalJS
正在載入 Temporal polyfill…

Example 4: ZonedDateTime Handles DST Automatically

Many countries observe Daylight Saving Time (DST) — during transitions, some moments simply don't exist. Date leaves that problem to you; Temporal.ZonedDateTime handles it automatically.

On March 29, 2026, the UK clocks spring forward from 01:00 to 02:00, so 01:30 doesn't exist:

Try it: ZonedDateTime DST handling
TemporalJS
正在載入 Temporal polyfill…

What Problems Does Temporal Solve?

Mutation hidden inside functions

Date is mutable. Passing a Date into a function can silently modify the original — no warning, no error.

With Date

Date vs Temporal: mutation
TemporalJS
正在載入 Temporal polyfill…

Month arithmetic silently overflows

Date doesn't validate dates — it rolls overflow into the next month. Jan 31 + 1 month gives Mar 03 instead of Feb 28.

Temporal clamps to the last valid day of the month. Use overflow: 'reject' if you want an error instead.

Date vs Temporal: month overflow
TemporalJS
正在載入 Temporal polyfill…

String parsing is inconsistent across browsers

The same "almost-ISO" string can produce different results depending on the browser — Chrome parses as local time, Firefox may throw, Safari parses as UTC. Temporal only accepts strictly defined formats and throws on anything ambiguous.

Date vs Temporal: string parsing
TemporalJS
正在載入 Temporal polyfill…

These three problems together drove the entire datetime library ecosystem: by 2026, moment.js + dayjs + date-fns combined exceed 100 million weekly downloads.

Things Worth Knowing

1. Comparison requires compare(), not > / <

Temporal objects aren't primitives — > / < won't throw but always returns false. compare() returns -1, 0, or 1, and works directly with Array.sort.

Try it: compare() and sorting
TemporalJS
正在載入 Temporal polyfill…

2. dayOfWeek is ISO 8601 — not Date.getDay()

Date.getDay() returns 0 for Sunday and 6 for Saturday. Temporal uses ISO 8601: 1 is Monday, 7 is Sunday. Watch out when porting old code.

Try it: dayOfWeek vs Date.getDay()
TemporalJS
正在載入 Temporal polyfill…

3. Duration doesn't auto-balance

Temporal.Duration preserves the units you give it — 100 seconds stays PT100S, not PT1M40S. Call round({ largestUnit: 'hour' }) when you need normalization.

Try it: Duration balance
TemporalJS
正在載入 Temporal polyfill…

4. total() for months and years requires relativeTo

Months have 28–31 days; years have 365 or 366. "How many days is 2 months?" has no fixed answer — you need to tell Temporal which date to count from:

Try it: total() and relativeTo
TemporalJS
正在載入 Temporal polyfill…

5. since() and until() are opposites

Both calculate a time span, but the sign flips depending on which method you call. Mix them up and you get a negative duration.

a.until(b) = how far from a to b; a.since(b) = how long since b.

Try it: since() vs until()
TemporalJS
正在載入 Temporal polyfill…

6. DST gaps when converting PlainDateTime to ZonedDateTime

Converting a wall-clock time to a specific timezone can hit a DST gap. Temporal requires you to choose how to handle it: reject throws, earlier picks the time before the gap, later picks after, compatible (default) equals later.

This only applies when constructing — arithmetic on an existing ZonedDateTime handles gaps automatically.

Try it: PlainDateTime disambiguation
TemporalJS
正在載入 Temporal polyfill…

7. Instant has no year, month, or day

Temporal.Instant is just a nanosecond timestamp — no calendar, no timezone. To access date fields, convert first with .toZonedDateTimeISO(tz).

Try it: Instant has no date fields
TemporalJS
正在載入 Temporal polyfill…

8. The DST day is only 23 hours long

When clocks spring forward, the day from midnight to midnight is only 23 hours. ZonedDateTime.until() reflects that accurately — you get PT23H, not PT24H.

.add({ hours: 24 }) and .add({ days: 1 }) produce different results on that day:

Try it: 23-hour DST day
TemporalJS
正在載入 Temporal polyfill…

9. PlainMonthDay for recurring annual events

For events like holidays that repeat on the same date each year, use Temporal.PlainMonthDay and call toPlainDate({ year }) to get the actual date for any year.

Try it: PlainMonthDay for annual events
TemporalJS
正在載入 Temporal polyfill…

10. Interoperating with Date

If you're mixing Temporal with legacy Date code:

  • Date → Temporal: Temporal.Instant.fromEpochMilliseconds(legacy.getTime()) (or legacy.toTemporalInstant() in native ES2026)
  • Temporal → Date: new Date(instant.epochMilliseconds)

Precision drops from nanoseconds to milliseconds, and calendar/timezone metadata is lost.

Try it: Date ↔ Temporal interop
TemporalJS
正在載入 Temporal polyfill…

11. Browser support

Temporal became part of ES2026 in March 2026 and is natively shipped in modern browsers:

  • Chrome 144+ (since January 2026)
  • Firefox 139+ (since May 2025)
  • Edge 144+ (since January 2026)
  • Safari (partial support in Tech Preview)

If you need to support older browsers, use @js-temporal/polyfill:

npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';

Wrapping Up

Date shipped in 1995 as a rushed copy of Java's java.util.Date — an API Java itself eventually deprecated for the same reasons. For thirty years, handling anything beyond basic date display in JavaScript meant pulling in a library.

Temporal isn't a patch. It's a full redesign: immutable objects, a proper type system where Instant and PlainDate represent genuinely different concepts, strict format parsing, complete IANA timezone support, and multi-calendar arithmetic. Everything that used to require a third-party package is now built in.

The most immediate impact for frontend work: fewer date-related bugs, full stop. Silent mutation, month overflow, and cross-browser parsing differences account for a large fraction of datetime bugs in the wild. Temporal closes all three at the language level.

Chrome 144+, Firefox 139+, and Edge 144+ ship it natively. Safari is catching up. You can start today with the official polyfill and drop it once browser support is complete.


References