Suggest an editImprove this articleRefine the answer for “Temporal API in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**The Temporal API is a new built-in API for dates and times in JavaScript, designed to replace the broken `Date`.** It provides immutable objects and a clear separation of concepts: `Temporal.Instant` (a point on the UTC timeline), `PlainDate` / `PlainTime` / `PlainDateTime` (local values with no zone), `ZonedDateTime` (date + time + an IANA time zone), `Duration` (a length of time) and `Temporal.Now` (access to the current time). Arithmetic never mutates the original object, the time zone is stored as part of the value, and an invalid date raises a `RangeError` instead of silently rolling over into the next month. ```javascript const today = Temporal.PlainDate.from('2025-10-16'); const tomorrow = today.add({ days: 1 }); // a brand new object console.log(today.toString(), tomorrow.toString()); // 2025-10-16 2025-10-17 ``` **Key point:** `Date` mutates, conflates local time with UTC and silently fixes mistakes; Temporal is immutable, explicit about time zones and strict about invalid values.Shown above the full answer for quick recall.Answer (EN)Image**The Temporal API is a new standard set of date and time types for JavaScript, meant to replace `Date`.** It is a TC39 proposal that reached Stage 3, is already implemented in some engines and is available everywhere through the official polyfill. The core idea: immutable objects, a clear separation of the concepts "instant", "date", "time" and "time zone", plus accurate handling of the IANA time zone database. ## Theory ### TL;DR - `Temporal` is a new built-in date and time API; it does not extend `Date`, it replaces it. - Every object is **immutable**: `add()`, `subtract()`, `with()` and `round()` return a new value. - Separate types for separate jobs: `Instant`, `PlainDate`, `PlainTime`, `PlainDateTime`, `ZonedDateTime`, `Duration`, `Now`. - The time zone is stored as part of the value (`[Europe/Kyiv]`), not just as a `+03:00` offset, so daylight saving transitions are handled correctly. - An invalid date raises a `RangeError` (with `overflow: 'reject'`) or is clamped explicitly, instead of silently rolling over as `new Date(2025, 1, 30)` does. - Months are numbered from **1**, not from 0 as in `Date`. - Formatting is delegated to `Intl.DateTimeFormat`; older environments pull in `@js-temporal/polyfill`. ### Quick example ```javascript // A date with no time const date = Temporal.PlainDate.from('2025-10-16'); console.log(date.year, date.month, date.day); // 2025 10 16 // A time with no date const time = Temporal.PlainTime.from('13:45:30'); console.log(time.hour); // 13 // A date and time with no time zone const dt = Temporal.PlainDateTime.from('2025-10-16T13:45:30'); console.log(dt.toString()); // 2025-10-16T13:45:30 ``` ### Why `Date` is not enough `Date` is one of the oldest and most problematic parts of the language: its API was copied from Java back in 1995 and has barely changed since. | Problem | Example | | --- | --- | | Implicit time zones | `new Date('2024-01-01')` is parsed as UTC, while `new Date('2024-01-01T00:00')` is parsed as local time | | Unpredictable arithmetic | `new Date(2024, 1, 30)` gives 1 March, because 30 February does not exist | | Mutating methods | `setMonth()`, `setHours()` change the original object instead of returning a new one | | Clumsy UTC handling | you juggle `toISOString()` and the `getHours()` / `getUTCHours()` pairs by hand | | Zero based numbering | months are 0..11 while days of the month are 1..31 | | No types for calendars, durations or zones | every small task pulled in a third party library: Moment, date-fns, Luxon | That is why Temporal does not patch `Date`: it introduces a separate, consistent model of time. ### The Temporal classes | Class | What it represents | Example | | --- | --- | --- | | `Temporal.Instant` | a moment on the absolute timeline (a point in UTC) | `"2025-10-16T21:00:00Z"` | | `Temporal.PlainDate` | a date with no time | `"2025-10-16"` | | `Temporal.PlainTime` | a time with no date | `"21:00:00"` | | `Temporal.PlainDateTime` | a date and time with no time zone | `"2025-10-16T21:00:00"` | | `Temporal.ZonedDateTime` | date + time + time zone | `"2025-10-16T21:00:00+03:00[Europe/Kyiv]"` | | `Temporal.Duration` | a length of time, the difference between two moments | `"P3DT5H"` (3 days 5 hours) | | `Temporal.Now` | a helper for reading the current time | `Temporal.Now.instant()` | The difference between `PlainDateTime` and `ZonedDateTime` is the key interview point. `PlainDateTime` is wall clock time with no place attached: "16 October at 21:00" means different instants in different countries. `ZonedDateTime` carries the zone, so it points at one specific instant and knows about DST transitions. When you need an actual instant (an event timestamp, a log record), use `Instant` or `ZonedDateTime`; when you need a birthday or an office opening time, use `PlainDate` / `PlainTime`. ### Time zones and daylight saving time ```javascript const zdt = Temporal.ZonedDateTime.from({ timeZone: 'Europe/Kyiv', year: 2025, month: 10, day: 16, hour: 21, }); console.log(zdt.toString()); // 2025-10-16T21:00:00+03:00[Europe/Kyiv] console.log(zdt.toInstant().toString()); // 2025-10-16T18:00:00Z (the same moment in UTC) ``` Temporal keeps the zone identifier (`[Europe/Kyiv]`) as part of the data, not just the `+03:00` offset. Because of that, arithmetic respects daylight saving transitions: ```javascript const beforeDST = Temporal.ZonedDateTime.from('2025-03-30T01:30:00+01:00[Europe/Berlin]'); const afterDST = beforeDST.add({ hours: 1 }); console.log(afterDST.toString()); // 2025-03-30T03:30:00+02:00[Europe/Berlin] ``` The clock jumped from 01:30 to 03:30 because the clocks moved forward at 02:00: one real hour was added and the offset changed from `+01:00` to `+02:00`. The current time comes from `Temporal.Now`: ```javascript const now = Temporal.Now.instant(); console.log(now.toString()); // the current time in UTC const kyiv = Temporal.Now.zonedDateTimeISO('Europe/Kyiv'); console.log(kyiv.toString()); // for example: 2025-10-16T23:50:00+03:00[Europe/Kyiv] ``` ### Arithmetic, Duration and strict validation Every operation returns a new value and leaves the original untouched: ```javascript const today = Temporal.PlainDate.from('2025-10-16'); const tomorrow = today.add({ days: 1 }); const lastWeek = today.subtract({ weeks: 1 }); console.log(tomorrow.toString()); // 2025-10-17 console.log(lastWeek.toString()); // 2025-10-09 console.log(today.toString()); // 2025-10-16, unchanged ``` The difference between two moments is a `Temporal.Duration` in ISO 8601 form: ```javascript const start = Temporal.PlainDateTime.from('2025-10-16T10:00'); const end = Temporal.PlainDateTime.from('2025-10-18T15:30'); const duration = end.since(start); console.log(duration.toString()); // P2DT5H30M (2 days 5 hours 30 minutes) console.log(duration.days, duration.hours, duration.minutes); // 2 5 30 ``` Invalid values are never fixed up behind your back. A string holding a date that does not exist throws right away, and for an object the `overflow` option decides: ```javascript // The old Date silently rolls the date over new Date(2025, 1, 30); // 2025-03-02 (month 1 is February, and 30 February does not exist) // Temporal: a string with a non existent date is an immediate error Temporal.PlainDate.from('2025-02-30'); // RangeError // For an object the default is overflow: 'constrain' (clamped to the end of the month) Temporal.PlainDate.from({ year: 2025, month: 2, day: 30 }); // 2025-02-28 // Strict mode Temporal.PlainDate.from({ year: 2025, month: 2, day: 30 }, { overflow: 'reject' }); // RangeError ``` Note that in Temporal `month: 2` is February, because months are numbered from 1. In `Date` the same position would mean March. ### Benefits, formatting and support | Benefit | What it gives you | | --- | --- | | Immutability | safe operations with no mutation and no accidental side effects | | A clear model | the concepts "instant", "date", "time" and "zone" are kept apart | | Accurate time zones | the IANA database, correct DST, the zone held inside the value | | No `Date` magic | errors instead of implicit corrections | | Works with `Intl` | user facing formatting without hand built strings | | ISO 8601 and `Duration` | simple work with differences and intervals | Formatting is delegated to `Intl.DateTimeFormat`, so localisation looks familiar: ```javascript const zdt = Temporal.ZonedDateTime.from('2025-10-16T21:00:00+03:00[Europe/Kyiv]'); const fmt = new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long' }); console.log(fmt.format(zdt)); // Thursday 16 October 2025 at 21:00:00 Eastern European Summer Time ``` Where you can already use it: - The newest engines ship `Temporal` natively (Firefox was first with a complete implementation), the rest are working on it. - In Node.js and older browsers you pull in the official polyfill. - Before shipping to production check support on MDN or guard with `typeof Temporal !== 'undefined'`. ```bash npm i @js-temporal/polyfill ``` ```javascript import { Temporal } from '@js-temporal/polyfill'; const date = Temporal.PlainDate.from('2025-10-16'); console.log(date.toString()); // 2025-10-16 ``` ### Common mistakes - **Expecting mutation.** `dt.add({ days: 1 })` changes nothing in `dt`; you have to assign the result. This is the most common slip for anyone used to `setDate()`. - **Confusing `PlainDateTime` with `ZonedDateTime`.** `PlainDateTime` is not a moment on the timeline: without a zone it cannot be compared to an `Instant` or stored as an event timestamp. - **Storing only the offset.** `+03:00` is not a time zone: a week after the switch back to winter time the same place is at `+02:00`. Store the IANA identifier (`Europe/Kyiv`). - **Zero based months.** Porting code from `Date` it is easy to leave `month: 0` in place; in Temporal that is a `RangeError`, because January is `1`. - **Counting on a `RangeError` where `constrain` applies.** By default `from({...})` clamps the value to the end of the month; pass `{ overflow: 'reject' }` if you want an error. - **Mixing `Date` and `Temporal` in calculations.** Convert explicitly with `date.toTemporalInstant()` and `instant.epochMilliseconds`. - **Pulling in a heavy library just for `Temporal`.** The polyfill is not small either, so for a single formatting call plain `Date` plus `Intl.DateTimeFormat` may be enough.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.