The Temporal API: JavaScript dates, finally fixed
By Flavio Copes
Temporal replaces JavaScript's broken Date object. Learn the key types, practical examples, browser support, and how to polyfill for production.
If you’ve worked with JavaScript dates, you know the pain.
Months are zero-indexed. January is 0. The object is mutable, so one function can silently change a date you passed in. Time zones are a mess. Parsing is inconsistent across browsers.
I’ve written dozens of posts working around these quirks. Formatting dates, getting timestamps, adding days without mutating the original. Every time, we’re fighting the API.
Temporal is the fix. It’s a new built-in global that replaces Date with immutable, explicit types. Each type tells you exactly what it holds: a calendar date, a wall-clock time, a moment in UTC, or a span of time.
Every operation returns a new value. You never mutate a date in place.
Temporal reached TC39 Stage 4 in 2025. It’s being merged into ECMA-262.
The key types
Temporal splits dates and times into separate types. No more guessing what a value represents.
Temporal.Now
Temporal.Now gives you the current time in different forms:
Temporal.Now.plainDateISO() // '2026-08-01'
Temporal.Now.instant() // '2026-08-01T05:00:00Z'
PlainDate
A calendar date with no time and no time zone. Perfect for birthdays and deadlines:
const launch = Temporal.PlainDate.from('2026-08-01')
launch.month // 8
launch.day // 1
PlainTime
A wall-clock time with no date attached:
const meeting = Temporal.PlainTime.from('14:30:00')
meeting.hour // 14
meeting.minute // 30
PlainDateTime
A date and time together, still with no time zone:
const event = Temporal.PlainDateTime.from('2026-08-01T14:30:00')
ZonedDateTime
This is where time zones work properly. A date, a time, and a specific IANA time zone:
const rome = Temporal.ZonedDateTime.from('2026-08-01T14:30:00+02:00[Europe/Rome]')
rome.timeZoneId // 'Europe/Rome'
No more getTimezoneOffset() hacks. The time zone is part of the value.
Instant
A point on the UTC timeline. Use it when you need an absolute moment, like a server timestamp:
const now = Temporal.Instant.from('2026-08-01T12:00:00Z')
now.epochMilliseconds // 1785585600000
Duration
A span of time. Not tied to any calendar date:
const week = Temporal.Duration.from({ days: 7 })
week.days // 7
Practical examples
Let’s walk through the things you do every day.
Get today’s date:
const today = Temporal.Now.plainDateISO()
Add three days without mutating anything:
const deadline = today.add({ days: 3 })
The original today is unchanged. add() returns a new value.
Find the difference between two dates:
const start = Temporal.PlainDate.from('2026-08-01')
const end = Temporal.PlainDate.from('2026-08-15')
const gap = start.until(end)
gap.days // 14
You can also use .since() to flip the direction.
Compare two dates:
const a = Temporal.PlainDate.from('2026-08-01')
const b = Temporal.PlainDate.from('2026-08-15')
Temporal.PlainDate.compare(a, b) // -1
A negative result means a comes before b.
Format for display:
const date = Temporal.PlainDate.from('2026-08-01')
date.toString() // '2026-08-01'
date.toLocaleString('en-US', { month: 'long', day: 'numeric' }) // 'August 1'
Convert between time zones:
const ny = Temporal.ZonedDateTime.from('2026-08-01T09:00:00-04:00[America/New_York]')
const tokyo = ny.withTimeZone('Asia/Tokyo')
tokyo.hour // 22
That’s it. No library. No manual offset math.
Browser and runtime support
As of mid-2026, Temporal ships natively in Firefox 139+, Chrome 144+, Node.js 26+, and Deno.
Safari is the holdout. It’s available in Safari Technology Preview behind a flag, but not in stable Safari yet. That blocks Baseline status for now.
For production, use a polyfill until Safari catches up. Two good options:
@js-temporal/polyfillfrom the proposal championstemporal-polyfillfrom FullCalendar (smaller bundle)
Install and import it before your app code runs:
import '@js-temporal/polyfill'
After that, Temporal works everywhere. Feature-detect if you prefer:
if (!globalThis.Temporal) {
await import('@js-temporal/polyfill')
}
Start using it
My advice: reach for Temporal in all new code. The API is cleaner, safer, and harder to misuse.
Keep Date around for legacy code and third-party libraries that still expect it. You can convert between them when needed:
const plain = Temporal.PlainDate.from('2026-08-01')
const legacy = new Date(plain.year, plain.month - 1, plain.day)
But don’t write new date logic with Date if you can avoid it. We’ve spent years patching around a broken API. Temporal is the replacement we’ve been waiting for.
The full spec lives at tc39.es/proposal-temporal.
Related posts about js: