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.
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
| Type | Represents | Example |
|---|---|---|
Temporal.PlainDate | Calendar date without time | 2026-07-01 |
Temporal.PlainTime | Wall-clock time without date | 14:30:00 |
Temporal.PlainDateTime | Date and time without time zone | 2026-07-01T14:30:00 |
Temporal.ZonedDateTime | Exact date/time in a time zone | 2026-07-01T14:30:00[America/New_York] |
Temporal.Instant | Exact point on the timeline | 2026-07-01T18:30:00Z |
Temporal.Duration | A length of time | P1Y2M3DT4H5M |
Temporal.PlainYearMonth | Year and month | 2026-07 |
Temporal.PlainMonthDay | Month 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.
Related articles
- JavaScript JavaScript Event Loop Explained: How Async Code Really Works
Understand how the JavaScript event loop handles async operations including the call stack, microtasks, macrotasks, and execution order with practical examples.
- JavaScript JavaScript structuredClone: The Modern Deep Copy Solution
Learn how to use JavaScript structuredClone for deep copying objects, when it beats JSON.parse(JSON.stringify()), and what types it supports and cannot handle.
- JavaScript JavaScript Design Patterns Every Developer Should Know
Learn the most useful JavaScript design patterns with practical examples including Singleton, Observer, Factory, Strategy, and more for cleaner code.
- JavaScript How to Find and Fix JavaScript Memory Leaks
Learn how to detect, diagnose, and fix JavaScript memory leaks using Chrome DevTools heap snapshots, allocation timelines, and common leak patterns.