HomeTemporal: the modern way to handle dates in Node.js

Temporal: the modern way to handle dates in Node.js

By · Node.js & JavaScript developer
Published July 5, 2026

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' }); // 150

Time 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 PM

Can 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/polyfill
import { 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.

Sources & further reading

About Code with Node.js

This is a personal blog and reference point of a Node.js developer.

I write and explain how different Node and JavaScript aspects work, as well as research popular and cool packages, and of course fail time to time.