Temporal: the modern way to handle dates in Node.js
JavaScript's Date object has frustrated developers for thirty years: it's mutable, it counts months from zero, it has no real time-zone support, and parsing behaviour that varies between engines. For most of that time the fix was to reach for a library like Moment, Day.js, or date-fns. Not anymore — Temporal is here, and it's built into the platform.
Temporal reached Stage 4 (it's part of ECMAScript 2026) and is enabled by default in Node.js 26, released in May 2026. It's a complete, modern date and time API — and it finally makes correct date handling the easy path.
One object was the problem; a family of types is the fix
Instead of cramming everything into a single Date, Temporal gives you a set of small, immutable types, each for a specific job:
Temporal.Instant— an exact point in time (like a timestamp), independent of any calendar or zone.Temporal.ZonedDateTime— a date and time in a specific time zone; the full, unambiguous type.Temporal.PlainDate— a calendar date with no time and no zone (e.g. a birthday).Temporal.PlainTime— a wall-clock time with no date (e.g. 09:00).Temporal.PlainDateTime— a date and time with no zone.Temporal.Duration— a length of time (e.g. "2 hours, 30 minutes"), used for arithmetic.
Every operation returns a new object, so you never accidentally mutate a date that's shared elsewhere.
Getting the current date and time
// An exact moment in time
Temporal.Now.instant(); // 2026-07-05T13:00:00.123456789Z
// The current date + time in the system time zone
Temporal.Now.zonedDateTimeISO(); // 2026-07-05T15:00:00+02:00[Europe/Vienna]
// Just today's date — no time, no zone
Temporal.Now.plainDateISO(); // 2026-07-05
// The system time zone identifier
Temporal.Now.timeZoneId(); // "Europe/Vienna"Plain dates: no time zone, no surprises
When you only care about a calendar date, PlainDate keeps things simple. Note the readable, one-based fields — month is 7 for July, not 6.
const date = Temporal.PlainDate.from('2026-07-05');
// or build it from parts:
const date2 = Temporal.PlainDate.from({ year: 2026, month: 7, day: 5 });
date.year; // 2026
date.month; // 7 (one-based!)
date.day; // 5
date.dayOfWeek; // 7 (Sunday)
date.daysInMonth; // 31
// Immutable: .with() returns a NEW date, the original is untouched
const firstOfMonth = date.with({ day: 1 }); // 2026-07-01
date.toString(); // still "2026-07-05"Date math that actually makes sense
Arithmetic takes a plain object of units. Differences between two dates come back as a Temporal.Duration, which you can read field-by-field or reduce to a single unit with .total().
const date = Temporal.PlainDate.from('2026-07-05');
const later = date.add({ months: 1, days: 10 }); // 2026-08-15
const earlier = date.subtract({ weeks: 2 }); // 2026-06-21
// Difference between two dates -> a Temporal.Duration
const start = Temporal.PlainDate.from('2026-01-01');
const end = Temporal.PlainDate.from('2026-07-05');
const diff = start.until(end, { largestUnit: 'day' });
diff.days; // 185
// A Duration reduces to a single unit with .total()
const meeting = Temporal.Duration.from({ hours: 2, minutes: 30 });
meeting.total({ unit: 'minutes' }); // 150Time zones and DST, handled for you
This is where Date falls apart and Temporal shines. A ZonedDateTime carries its zone in the string itself — the [Area/City] suffix — and arithmetic is daylight-saving aware.
// A specific wall-clock time in a specific zone
const meeting = Temporal.ZonedDateTime.from('2026-07-05T10:00[America/New_York]');
meeting.timeZoneId; // "America/New_York"
meeting.hour; // 10
// Convert to another zone — same instant, different wall clock
const vienna = meeting.withTimeZone('Europe/Vienna');
vienna.hour; // 16
// Adding "1 day" respects clock changes across a DST boundary
const nextDay = meeting.add({ days: 1 });Comparing and sorting dates
const a = Temporal.PlainDate.from('2026-07-05');
const b = Temporal.PlainDate.from('2026-12-25');
Temporal.PlainDate.compare(a, b); // -1 (a is before b)
a.equals(b); // false
// compare() is a ready-made sort comparator
const dates = [b, a];
dates.sort(Temporal.PlainDate.compare); // [a, b]Formatting for humans
Temporal objects work directly with Intl.DateTimeFormat and have a convenient toLocaleString().
const zdt = Temporal.Now.zonedDateTimeISO();
zdt.toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' });
// "Sunday, July 5, 2026 at 3:00 PM"
new Intl.DateTimeFormat('de-DE', { dateStyle: 'long' }).format(zdt.toPlainDate());
// "5. Juli 2026"Working with existing Date values
You rarely start from scratch — you have Date objects from databases, APIs, and older libraries. Temporal bridges both ways.
// Legacy Date -> Temporal
const instant = new Date().toTemporalInstant();
const zdt = instant.toZonedDateTimeISO('Europe/Vienna');
const today = zdt.toPlainDate();
// Temporal -> legacy Date (for an API that still expects one)
const back = new Date(instant.epochMilliseconds);Proof: it really runs on Node.js 26
Every example above was run on a stock node:26-slim Docker image — no flags, no polyfill. Here's an actual session (with TZ=Europe/Vienna so the wall-clock output is deterministic):
$ docker run --rm -e TZ=Europe/Vienna node:26-slim node -v
v26.4.0
$ node demo.mjs
month (one-based): 7
add 1mo 10d: 2026-08-15
days until Jul 5: 185
NY 10:00 in Vienna: 16:00
formatted: Sunday, July 5, 2026 at 3:00 PMCan I use it today?
Node.js 26+ ships Temporal enabled by default, so on the server you can use it right now (that's exactly what the run above shows). In browsers it's still limited availability — not yet Baseline, and not shipped in every engine at the time of writing — so for the browser, or for older Node versions, use the official polyfill:
npm i @js-temporal/polyfillimport { Temporal, Intl, toTemporalInstant } from '@js-temporal/polyfill';
// Also enable Date.prototype.toTemporalInstant()
Date.prototype.toTemporalInstant = toTemporalInstant;
const today = Temporal.Now.plainDateISO();Goodbye, date libraries
Immutable types, first-class time zones, sane arithmetic, and correct comparisons — the exact reasons teams pulled in Moment, Day.js, or date-fns are now covered by the platform itself. For new code, prefer Temporal and drop the dependency; for existing code, migrate gradually using toTemporalInstant() at the boundaries. It's the biggest improvement to dates in JavaScript's history, and it's finally built in.