JavaScript Temporal API Reference

Temporal.PlainDate, ZonedDateTime, Duration and Calendar types with key methods

Reference for the TC39 Temporal proposal — PlainDate, PlainDateTime, ZonedDateTime, Instant, PlainTime, Duration and PlainYearMonth — with what each type represents, when to use it, and how it fixes the legacy Date pitfalls. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

Why does JavaScript need Temporal when it already has Date?

Date has well-known flaws: it is mutable, it only models a single instant yet exposes confusing local-vs-UTC accessors, months are zero-indexed, parsing is inconsistent across engines, and it has no real time-zone or calendar support. Temporal replaces it with immutable, purpose-specific, clearly-named types and reliable ISO 8601 parsing.

Temporal is the TC39 proposal that replaces the flawed Date object with a family of immutable, purpose-built date and time types. This reference lists each type, what it models, and its key methods.

Why Temporal replaces Date

The legacy Date object has been broken by design since 1995. Its most painful problems:

  • Mutability. date.setMonth(1) modifies in place — pass a Date to a function and the caller’s copy can change.
  • Month indexing. January is 0, December is 11. Off-by-one bugs are silent and common.
  • One type for everything. Date tries to model both “a calendar date” and “a UTC timestamp” using the same object, exposing confusing local-vs-UTC accessor pairs (getMonth vs getUTCMonth).
  • No time-zone support. You only get the local system zone or UTC; named time zones like "America/New_York" are not part of the API.
  • Inconsistent parsing. new Date("2026-06-06") returns midnight UTC on most engines, but results vary.

How it works

Instead of one overloaded Date, Temporal gives you a type per concept:

Temporal.Now.plainDateISO();                 // today's date, no time/zone
Temporal.PlainDate.from("2026-06-06");       // a calendar date
Temporal.ZonedDateTime.from("2026-06-06T09:00[Europe/London]");
Temporal.Duration.from({ hours: 2, minutes: 30 });

The core split is wall-clock vs exact time:

  • PlainDate, PlainTime, PlainDateTime, PlainYearMonth, PlainMonthDay carry no time zone — they are “what a clock or calendar on the wall reads”.
  • Instant is an exact point on the global timeline (a UTC timestamp), with no local interpretation.
  • ZonedDateTime ties an instant to a time zone + calendar, so it knows local time, offset and DST rules.

Duration represents a length of time and is what until, since, add and subtract produce or consume. Every type is immutableadd/with/round return new objects.

Choosing the right type

SituationUse
A birthday, invoice date, or holidayPlainDate
A meeting time with no explicit time zonePlainDateTime
A scheduled alarm in a specific cityZonedDateTime
A server log timestamp or database recordInstant
Adding or measuring elapsed timeDuration

Practical worked examples

Date arithmetic across a DST boundary

One of the most confusing things in legacy Date is adding days across a daylight-saving clock change. In Temporal the distinction is explicit:

const startNY = Temporal.ZonedDateTime.from("2026-03-07T10:00[America/New_York]");

// "Add 1 day" = same wall-clock time tomorrow (may be 23 hours of real time):
const nextDay = startNY.add({ days: 1 });
// → 2026-03-08T10:00:00-04:00[America/New_York]

// "Add 24 hours" = exactly 24 hours of real elapsed time:
const plus24h = startNY.add({ hours: 24 });
// → 2026-03-08T11:00:00-04:00[America/New_York]  (one hour later because clocks sprang forward)

For example, a reminder app that says “notify me at 10 AM tomorrow” should use { days: 1 }. A countdown timer that says “notify me in 24 hours” should use { hours: 24 }.

Calculating age in years

const birthday = Temporal.PlainDate.from("1990-09-15");
const today = Temporal.Now.plainDateISO();
const age = birthday.until(today, { largestUnit: "years" });
// age.years → the person's current age in whole years

This respects leap years correctly and never has the off-by-one month error that plagues Date-based age calculations.

Tips and notes

  • Months are 1-indexed in Temporal (January is 1), fixing the classic Date footgun where new Date(2026, 0, 1) is January 1.
  • Adding { days: 1 } to a ZonedDateTime keeps the same wall time across DST (may be 23 or 25 hours); adding { hours: 24 } always adds 24 real hours.
  • Use compare(a, b) static methods to sort; the ==/< operators do not work on objects.
  • Convert between types with methods like .toZonedDateTime(tz), .toInstant(), .toPlainDate().
  • The @js-temporal/polyfill package brings Temporal to environments that do not have it natively yet.