# The Temporal API: JavaScript dates, finally fixed

> Temporal replaces JavaScript's broken Date object. Learn the key types, practical examples, browser support, and how to polyfill for production.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-08-01 | Topics: [JavaScript](https://flaviocopes.com/tags/js/) | Canonical: https://flaviocopes.com/temporal-api/

If you've worked with [JavaScript dates](https://flaviocopes.com/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](https://flaviocopes.com/how-to-format-date-javascript/), getting [timestamps](https://flaviocopes.com/how-to-get-timestamp-javascript/), 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:

```js
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:

```js
const launch = Temporal.PlainDate.from('2026-08-01')
launch.month // 8
launch.day // 1
```

### PlainTime

A wall-clock time with no date attached:

```js
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:

```js
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:

```js
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:

```js
const now = Temporal.Instant.from('2026-08-01T12:00:00Z')
now.epochMilliseconds // 1785585600000
```

### Duration

A span of time. Not tied to any calendar date:

```js
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:

```js
const today = Temporal.Now.plainDateISO()
```

Add three days without mutating anything:

```js
const deadline = today.add({ days: 3 })
```

The original `today` is unchanged. `add()` returns a new value.

Find the difference between two dates:

```js
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:

```js
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:

```js
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:

```js
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/polyfill` from the proposal champions
- `temporal-polyfill` from FullCalendar (smaller bundle)

Install and import it before your app code runs:

```js
import '@js-temporal/polyfill'
```

After that, `Temporal` works everywhere. Feature-detect if you prefer:

```js
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:

```js
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](https://tc39.es/proposal-temporal/docs/).
