Skip to content
Codeloom
JavaScript

The Temporal API: A Modern Replacement for JavaScript Dates

Explore the Temporal API for JavaScript -- a modern, immutable, and time-zone-aware replacement for the legacy Date object.

·6 min read · By Codeloom
Intermediate 12 min read

What you'll learn

  • Why Temporal replaces the legacy Date object
  • How to work with PlainDate, PlainTime, ZonedDateTime, and Duration
  • Practical patterns for date math, time zones, and formatting

Prerequisites

  • Basic JavaScript
  • Experience with Date and its quirks

The JavaScript Date object has been a source of developer frustration since 1995. It is mutable, has no time zone support beyond UTC, months are zero-indexed, and parsing is inconsistent across browsers. The Temporal API fixes all of these problems with a modern, immutable, and comprehensive date/time library built into the language.

Why Temporal?

The legacy Date has well-known problems:

// Month is zero-indexed (0 = January)
const date = new Date(2026, 6, 1); // July 1, not June 1

// Date is mutable
const d = new Date("2026-07-01");
d.setMonth(12); // Silently wraps to next year

// No timezone support beyond the system timezone
// No duration type
// Parsing is unreliable across browsers

Temporal provides distinct types for different use cases, all immutable and unambiguous.

The Temporal type system

TypeRepresentsExample
Temporal.PlainDateCalendar date without time2026-07-01
Temporal.PlainTimeWall-clock time without date14:30:00
Temporal.PlainDateTimeDate and time without time zone2026-07-01T14:30:00
Temporal.ZonedDateTimeExact date/time in a time zone2026-07-01T14:30:00[America/New_York]
Temporal.InstantExact point on the timeline2026-07-01T18:30:00Z
Temporal.DurationA length of timeP1Y2M3DT4H5M
Temporal.PlainYearMonthYear and month2026-07
Temporal.PlainMonthDayMonth and day (recurring)07-01

PlainDate: working with calendar dates

When you only care about the date (birthdays, holidays, deadlines):

const date = Temporal.PlainDate.from("2026-07-01");

console.log(date.year);      // 2026
console.log(date.month);     // 7 (1-indexed!)
console.log(date.day);       // 1
console.log(date.dayOfWeek); // 3 (Wednesday, 1 = Monday)
console.log(date.daysInMonth); // 31

// Arithmetic -- returns a new object (immutable)
const nextWeek = date.add({ days: 7 });
console.log(nextWeek.toString()); // "2026-07-08"

const lastMonth = date.subtract({ months: 1 });
console.log(lastMonth.toString()); // "2026-06-01"

No more getMonth() + 1. Months are 1-indexed in Temporal.

PlainTime: wall-clock time

const time = Temporal.PlainTime.from("14:30:00");

console.log(time.hour);   // 14
console.log(time.minute); // 30

const later = time.add({ hours: 2, minutes: 15 });
console.log(later.toString()); // "16:45:00"

// Comparison
const meetingTime = Temporal.PlainTime.from("09:00");
console.log(Temporal.PlainTime.compare(time, meetingTime)); // 1 (time is after)

ZonedDateTime: the full picture

When timezones matter (meeting scheduling, flight times):

const meeting = Temporal.ZonedDateTime.from({
  year: 2026,
  month: 7,
  day: 1,
  hour: 14,
  minute: 30,
  timeZone: "America/New_York",
});

console.log(meeting.toString());
// "2026-07-01T14:30:00-04:00[America/New_York]"

// Convert to another timezone
const tokyo = meeting.withTimeZone("Asia/Tokyo");
console.log(tokyo.toString());
// "2026-07-02T03:30:00+09:00[Asia/Tokyo]"

// Same instant, different wall-clock time
console.log(meeting.toInstant().equals(tokyo.toInstant())); // true

Instant: an exact point in time

Temporal.Instant represents an exact moment, similar to a Unix timestamp.

const now = Temporal.Now.instant();
console.log(now.toString()); // "2026-07-01T18:30:00.000Z"

// From epoch
const epoch = Temporal.Instant.fromEpochMilliseconds(1751391000000);

// Convert to zoned
const zoned = epoch.toZonedDateTimeISO("Europe/London");
console.log(zoned.hour); // local hour in London

Duration: measuring and expressing time spans

const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
console.log(duration.total("minutes")); // 150

// From ISO 8601 duration string
const iso = Temporal.Duration.from("P1Y2M3DT4H5M");
console.log(iso.years);   // 1
console.log(iso.months);  // 2
console.log(iso.days);    // 3
console.log(iso.hours);   // 4
console.log(iso.minutes); // 5

Duration between dates

const start = Temporal.PlainDate.from("2026-01-15");
const end = Temporal.PlainDate.from("2026-07-01");

const diff = start.until(end, { largestUnit: "month" });
console.log(diff.toString()); // "P5M16D" -- 5 months and 16 days

// Or in total days
const totalDays = start.until(end, { largestUnit: "day" });
console.log(totalDays.days); // 167

Comparison and sorting

All Temporal types have a static compare method for sorting.

const dates = [
  Temporal.PlainDate.from("2026-12-25"),
  Temporal.PlainDate.from("2026-01-01"),
  Temporal.PlainDate.from("2026-07-04"),
];

dates.sort(Temporal.PlainDate.compare);
console.log(dates.map((d) => d.toString()));
// ["2026-01-01", "2026-07-04", "2026-12-25"]

// Equality
const a = Temporal.PlainDate.from("2026-07-01");
const b = Temporal.PlainDate.from("2026-07-01");
console.log(a.equals(b)); // true

Practical example: age calculator

function calculateAge(birthDate) {
  const birth = Temporal.PlainDate.from(birthDate);
  const today = Temporal.Now.plainDateISO();
  const age = birth.until(today, { largestUnit: "year" });
  return {
    years: age.years,
    months: age.months,
    days: age.days,
    description: `${age.years} years, ${age.months} months, ${age.days} days`,
  };
}

console.log(calculateAge("1995-03-15"));
// { years: 31, months: 3, days: 16, description: "31 years, 3 months, 16 days" }

Practical example: business days calculator

function addBusinessDays(startDate, numDays) {
  let date = Temporal.PlainDate.from(startDate);
  let added = 0;

  while (added < numDays) {
    date = date.add({ days: 1 });
    // Skip weekends (6 = Saturday, 7 = Sunday)
    if (date.dayOfWeek <= 5) {
      added++;
    }
  }
  return date;
}

const deadline = addBusinessDays("2026-07-01", 10);
console.log(deadline.toString()); // "2026-07-15" (skips weekends)

Practical example: meeting scheduler

function scheduleMeeting(dateTime, timeZone, attendeeZones) {
  const meeting = Temporal.ZonedDateTime.from({
    ...dateTime,
    timeZone,
  });

  return attendeeZones.map((zone) => {
    const local = meeting.withTimeZone(zone);
    return {
      timeZone: zone,
      localTime: local.toPlainTime().toString({ smallestUnit: "minute" }),
      date: local.toPlainDate().toString(),
    };
  });
}

const agenda = scheduleMeeting(
  { year: 2026, month: 7, day: 2, hour: 15, minute: 0 },
  "America/New_York",
  ["America/Los_Angeles", "Europe/London", "Asia/Tokyo"]
);

console.log(agenda);
// [
//   { timeZone: "America/Los_Angeles", localTime: "12:00", date: "2026-07-02" },
//   { timeZone: "Europe/London", localTime: "20:00", date: "2026-07-02" },
//   { timeZone: "Asia/Tokyo", localTime: "04:00", date: "2026-07-03" },
// ]

Converting between types

// PlainDate + PlainTime = PlainDateTime
const date = Temporal.PlainDate.from("2026-07-01");
const time = Temporal.PlainTime.from("14:30");
const dateTime = date.toPlainDateTime(time);
console.log(dateTime.toString()); // "2026-07-01T14:30:00"

// PlainDateTime + TimeZone = ZonedDateTime
const zoned = dateTime.toZonedDateTime("America/New_York");
console.log(zoned.toString());
// "2026-07-01T14:30:00-04:00[America/New_York]"

// ZonedDateTime -> Instant (exact point in time)
const instant = zoned.toInstant();
console.log(instant.toString()); // "2026-07-01T18:30:00Z"

Using Temporal today with a polyfill

Since Temporal is not yet in all browsers, use the polyfill:

// Install: npm install @js-temporal/polyfill
import { Temporal } from "@js-temporal/polyfill";

const now = Temporal.Now.plainDateISO();
console.log(now.toString()); // "2026-07-01"

The polyfill API matches the spec, so your code will work without changes when browsers ship native support.

Summary

The Temporal API provides a complete, modern solution for date and time handling in JavaScript:

  • Immutable — all operations return new objects.
  • Unambiguous — separate types for dates, times, zoned moments, and durations.
  • Time-zone-aware — first-class support for IANA time zones.
  • Precise — nanosecond precision and correct calendar arithmetic.
  • No more zero-indexed months — January is 1, December is 12.

While Temporal is still progressing through the TC39 standards process, you can start using it today with the @js-temporal/polyfill package. It is the future of date handling in JavaScript.