Temporal API

The JavaScript Temporal API: A Modern Replacement for Date

JavaScript Date has been broken in well-known ways for decades. TC39 Temporal — Stage 4 since early 2025, shipping in modern browsers and Node.js — cleanly separates instants, wall-clock dates, and zoned date-times. This guide explains the new types, how they map to Unix timestamps, and how to migrate.

Temporal vs Date at a glance

JavaScript's built-in Date has been a known source of bugs for decades: it is mutable, it only understands the local timezone and UTC, its months are zero-indexed, and its parsing is loosely specified. Temporal is the standards-track replacement — Stage 4 and part of ECMAScript 2026 — and fixes each of those problems with purpose-built, immutable types. The table below is the high-level contrast; the rest of this guide covers the types and how they map to Unix timestamps.

AspectDate (legacy)Temporal
MutabilityMutableImmutable
PrecisionMillisecondsNanoseconds
TimezonesLocal + UTC onlyAny IANA zone (ZonedDateTime)
Month indexing0-based (0 = January)1-based (1 = January)
ParsingLenient, implementation-definedStrict ISO 8601 / RFC 9557
Instant vs wall-clockConflated in one typeSeparate types

Why Temporal exists

JavaScript Date conflates several distinct concepts: an exact instant, a wall-clock time, and a zoned date-time. Date also has well-known parsing quirks, a single timezone (the user’s), and millisecond precision. Temporal exposes the underlying concepts as separate types and supports nanosecond precision, arbitrary IANA timezones, and explicit calendar systems.

  • Stage 4 since early 2025; first shipped in Firefox 139 by default
  • Now available in Chrome 144+, Firefox 139+, and Node.js 26+
  • Polyfills (@js-temporal/polyfill) work in any modern environment
  • Designed to coexist with Date — interop methods exist in both directions

Core Temporal types

A small set of types covers the date-time problem cleanly. Each has a canonical string form (ISO 8601, extended by RFC 9557 for the zoned type):

TypeRepresentsExample string
Temporal.InstantAn exact instant, like a Unix timestamp (ns precision)2026-01-01T00:00:00Z
Temporal.ZonedDateTimeInstant + IANA timezone + calendar2026-01-01T09:00:00+09:00[Asia/Tokyo]
Temporal.PlainDateTimeDate + time, no timezone2026-01-01T09:00:00
Temporal.PlainDateA calendar date (e.g. a birthday)2026-01-01
Temporal.PlainTimeA wall-clock time09:00:00
Temporal.DurationA length of time, unit-awareP3DT4H10M

Working with Unix timestamps

Temporal.Instant is the Unix-timestamp type. The shipped API exposes only millisecond and nanosecond epoch conversions — the earlier fromEpochSeconds and fromEpochMicroseconds methods were removed before Stage 4 — so convert Unix seconds by multiplying by 1000. Use Instant for storage and transport, and ZonedDateTime when you need a wall-clock view in a specific timezone.

  • Temporal.Now.instant() — the current exact moment
  • Temporal.Instant.fromEpochMilliseconds(Date.now()) — from a JS millisecond timestamp
  • Temporal.Instant.fromEpochMilliseconds(1700000000 * 1000) — from Unix seconds (× 1000)
  • Temporal.Instant.fromEpochNanoseconds(1700000000000000000n) — from nanoseconds (BigInt)
  • instant.epochMilliseconds and instant.epochNanoseconds — read the epoch value back
  • instant.toZonedDateTimeISO("America/New_York") — a wall-clock view in a zone

Timezones and DST handled correctly

ZonedDateTime carries an IANA timezone, so DST transitions, gap times, and overlap times are all handled explicitly. The disambiguation option lets you pick a policy for ambiguous local times, and the canonical string format is RFC 9557 (an ISO 8601 timestamp with a bracketed zone).

  • zdt.timeZoneId — the IANA name (e.g. America/Los_Angeles)
  • zdt.add({ hours: 1 }) — DST-aware arithmetic in wall-clock terms
  • zdt.until(zdt2) — distance between two zoned date-times
  • Disambiguation options: "earlier", "later", "compatible" (default — later for gap, earlier for overlap), "reject"
  • Round-trip: instant ↔ zoned date-time is loss-free

Migration patterns from Date

The simplest migration strategy is to introduce Temporal at the boundaries first (parsing input, formatting output) and gradually move internal logic over.

  • Date → Instant: Temporal.Instant.fromEpochMilliseconds(date.getTime())
  • Instant → Date: new Date(instant.epochMilliseconds)
  • For new code, prefer Temporal everywhere and convert to Date only at API edges
  • date-fns and Luxon both have Temporal-aware modes / adapters
  • JSON: Temporal.Instant.prototype.toJSON returns an ISO 8601 string

Browser and runtime support

Temporal shipped in 2025 and native support is now broad, but not yet universal. The polyfill is the safest path while older browsers and Safari stable are still in service.

  • Firefox 139+: first to ship, enabled by default (May 2025)
  • Chrome 144+: enabled by default (January 2026)
  • Node.js 26+: enabled by default (May 2026)
  • Safari: available in Technology Preview, not yet in a stable release
  • Polyfill: @js-temporal/polyfill or temporal-polyfill — same API, any modern environment

Related references

FAQ

Is Temporal a replacement for Date?
Yes — Temporal is designed to fully replace Date for new code. Date remains in the language for backward compatibility, and the two interoperate via epoch milliseconds.
How do I convert a Unix timestamp to Temporal?
Use Temporal.Instant.fromEpochMilliseconds(). For Unix seconds, multiply by 1000: Temporal.Instant.fromEpochMilliseconds(seconds * 1000). The shipped API only has fromEpochMilliseconds and fromEpochNanoseconds — the older fromEpochSeconds method was removed before Temporal reached Stage 4.
Does Temporal support nanoseconds?
Yes. Temporal.Instant has nanosecond precision internally and exposes epochNanoseconds as a BigInt.
Can I use Temporal in Node.js today?
In Node.js 26+ Temporal is enabled by default. In older Node versions, install @js-temporal/polyfill — the API surface is identical.
Which browsers support Temporal?
Firefox 139+ (May 2025), Chrome 144+ (January 2026), and Node.js 26+ (May 2026) ship Temporal by default. Safari has it in Technology Preview. For anything older, use the @js-temporal/polyfill.
How does Temporal handle the DST gap?
ZonedDateTime constructors and arithmetic accept a disambiguation option. "compatible" (the default) picks the later instant for gaps and the earlier for overlaps; "reject" throws an error if the local time is ambiguous.