
Temporal: the new JavaScript API that (finally) replaces Date
What's wrong with the legacy Date object, how Temporal fixes it, the core types (PlainDate, ZonedDateTime, Duration...), and where support stands in 2026.
Date is one of the oldest, most-complained-about parts of JavaScript — mutable, full of parsing footguns, and unreliable across time zones. After years in TC39’s proposal pipeline, Temporal is JavaScript’s answer: a new, purpose-built API for working with dates, times, and durations, designed from scratch instead of patched onto the original 1995 Date design.
ℹ️ Note on availability
Temporal has been at TC39 Stage 3 for a while, with native support rolling out gradually across
engines rather than landing everywhere at once — check current browser/Node support before relying
on it in production, and use the official @js-temporal/polyfill where it isn’t
natively available yet. Everything below describes the API as specified, which is what the
polyfill also implements.
What’s actually wrong with Date
A quick list, because it’s worth remembering why this needed a full replacement instead of incremental fixes:
Dateis mutable.date.setMonth(5)changes the object in place — pass aDateinto a function and that function can silently mutate your original value.- Parsing is inconsistent.
new Date("2026-01-15")andnew Date("01/15/2026")can behave differently across engines, and some formats are outright ambiguous (is01/02/2026January 2nd or February 1st?). - Time zone handling is bolted on.
Dateinternally always stores a UTC timestamp; anything you do with a specific time zone requires manual offset math or a third-party library likedate-fns-tzor Luxon. - There’s no first-class “duration” or “plain date” concept. Wanting “just a calendar date with no time” (a birthday) or “just a duration” (45 minutes) means either hacking
Dateinto representing it or building it yourself.
The core Temporal types
Instead of one do-everything object, Temporal splits the domain into distinct, purpose-built types:
| Type | Represents |
|---|---|
Temporal.PlainDate |
A calendar date with no time or time zone (e.g. a birthday) |
Temporal.PlainTime |
A time of day with no date (e.g. “office opens at 09:00”) |
Temporal.PlainDateTime |
A date and time with no time zone |
Temporal.ZonedDateTime |
A specific instant, tied to a specific time zone — what you usually want for “when did this happen” |
Temporal.Instant |
A precise point on the UTC timeline, with no calendar or time zone attached |
Temporal.Duration |
A length of time (e.g. “2 hours 30 minutes”) |
// A calendar date, no time attached
const birthday = Temporal.PlainDate.from("2026-03-14");
// A specific moment, in a specific time zone
const meeting = Temporal.ZonedDateTime.from("2026-09-10T15:00:00[Europe/Rome]");
// A duration you can do arithmetic with
const break_ = Temporal.Duration.from({ minutes: 15 });
Every one of these objects is immutable — any operation returns a new instance instead of mutating the original, the same model as Date’s friendlier modern alternatives (Luxon, date-fns) already popularized.
A practical example: converting between time zones
This is the exact kind of thing that required a library before Temporal:
const meetingInRome = Temporal.ZonedDateTime.from("2026-09-10T15:00:00[Europe/Rome]");
const meetingInNewYork = meetingInRome.withTimeZone("America/New_York");
console.log(meetingInNewYork.toString());
// 2026-09-10T09:00:00-04:00[America/New_York]
No manual offset math, no risk of forgetting daylight saving time — withTimeZone handles the conversion correctly, including DST transitions, because ZonedDateTime carries the IANA time zone identifier (Europe/Rome), not just a numeric UTC offset.
Arithmetic with durations
const start = Temporal.PlainDateTime.from("2026-09-10T09:00:00");
const end = start.add({ hours: 2, minutes: 30 });
console.log(end.toString()); // 2026-09-10T11:30:00
const duration = start.until(end);
console.log(duration.toString()); // PT2H30M
add, subtract, and until are available on every Temporal type that makes sense for them, and they correctly handle edge cases Date arithmetic gets wrong by hand — month-end rollovers, leap years, and DST transitions for ZonedDateTime.
Date vs Temporal at a glance
Date |
Temporal | |
|---|---|---|
| Mutable | Yes | No |
| Plain date without time | Not natively | Temporal.PlainDate |
| Reliable time zone support | No (manual/library) | Yes, built in (ZonedDateTime) |
| Parsing | Inconsistent across engines | Strict, spec-defined format |
| Duration as a first-class type | No | Temporal.Duration |
| Arithmetic (add/subtract) | Manual, error-prone | Built-in methods |
Should you migrate existing code today?
Not wholesale, and not yet everywhere. Given the uneven native support described above, the practical path for most projects in 2026 is:
- Use the polyfill for new code where date/time correctness genuinely matters — anything dealing with multiple time zones, recurring events, or calendar math.
- Leave
Datein place where it’s just being used to log a timestamp or measure elapsed time withDate.now()— there’s no correctness win from migrating that. - Keep an eye on your target runtimes’ support tables before dropping the polyfill dependency.
FAQ
❓ Does Temporal replace Date entirely?
Date isn’t being removed from the language — it stays for backward compatibility. Temporal is
the new, recommended API for anything beyond the simplest timestamp use cases.
❓ Do I need a library like Luxon or date-fns if I use Temporal?
For most use cases, no — Temporal covers parsing, arithmetic, formatting, and time zone conversion
natively. Libraries built around the old Date object become largely unnecessary once Temporal is
available in your target environments.
❓ Can I convert an existing Date to Temporal?
Yes — Temporal.Instant.fromEpochMilliseconds(date.getTime()) gives you a Temporal.Instant from
a legacy Date, which you can then convert to a ZonedDateTime with toZonedDateTimeISO() for a
given time zone.
Conclusion
Temporal fixes the structural problems Date couldn’t outgrow: mutability, ambiguous parsing, and time zone handling that always required extra tooling. Splitting the domain into PlainDate, ZonedDateTime, Duration, and friends makes code that handles dates correctly read like it’s handling dates correctly, instead of hiding intent behind generic Date math. Check your target runtimes’ support before dropping the polyfill, but it’s worth learning the API now — this is where date handling in JavaScript is headed.
References

Co-Founder & CTO at PAPION. Senior full-stack engineer specializing in React, TypeScript, Node.js, and application security.