JavaScript Temporal API: The Modern, Precise, and Safe Way to Handle Dates and Times in JavaScript
📑 Table of Contents
- Background: Why a New Date System?
- Topic 1 — Temporal Introduction & Overview
- Topic 2 — Temporal vs Date: Side-by-Side
- Topic 3 — Temporal.Now
- Topic 4 — Temporal.Instant
- Topic 5 — Plain Types (PlainDate, PlainTime, PlainYearMonth, PlainMonthDay)
- Topic 6 — Temporal.PlainDateTime
- Topic 7 — Temporal.ZonedDateTime
- Topic 8 — Temporal.Duration
- Topic 9 — Temporal Arithmetic
- Topic 10 — Complete Temporal Reference
- Applied Exercises
- Mini Project — Global Meeting Scheduler
- Completion Checklist
⚠️ IMPORTANT — Browser Support Notice The Temporal API is a TC39 Stage 3 proposal — it is officially approved for standardisation but not yet natively built into all browsers. To use it today, install the polyfill:
npm install @js-temporal/polyfillimport { Temporal } from "@js-temporal/polyfill";Native support is arriving in modern browsers. Always check caniuse.com for the latest status. All code examples in this tutorial use the standard Temporal API as specified.
1. Background: Why a New Date System?
You already know that JavaScript has a Date object. So why build an entirely new system called Temporal?
Because the old Date object has deep, unfixable problems that have caused bugs in production software for over 30 years. The Temporal API was designed from scratch to fix every one of them.
The 7 Problems with the Old Date Object
| Problem | What Happens |
|---|---|
| 1. Mutable | setDate(), setMonth() change the object permanently — easy to accidentally modify a shared date |
| 2. 0-indexed months | January = 0, December = 11 — confusing and error-prone |
| 3. No timezone support | Date stores UTC but displays in local time — constantly confusing |
| 4. Millisecond precision only | Cannot represent microseconds or nanoseconds |
| 5. Inconsistent string parsing | new Date("01/15/2024") behaves differently in Chrome vs Firefox |
| 6. No calendar system support | Cannot work with Hebrew, Japanese, Islamic, or other calendars |
| 7. One type for everything | You cannot distinguish “just a date” from “a date with a time” from “a timestamp” |
What Temporal Fixes
Temporal introduces a family of separate types, each designed for a specific use case:
Temporal.Instant → A precise moment in time (like a UTC timestamp)
Temporal.PlainDate → A date with no time, no timezone ("2024-03-20")
Temporal.PlainTime → A time with no date, no timezone ("09:30:00")
Temporal.PlainDateTime → A date + time, no timezone ("2024-03-20T09:30:00")
Temporal.ZonedDateTime → A date + time + timezone ("2024-03-20T09:30:00[Europe/London]")
Temporal.Duration → A length of time ("2 years, 3 months, 5 days")
Temporal.Now → Static methods to get the current moment in any form
Temporal.PlainYearMonth → Just a year and month ("2024-03")
Temporal.PlainMonthDay → Just a month and day ("--03-20") — for recurring dates
💡 TIP: Think of it like specialised tools in a toolkit. A carpenter does not use a screwdriver to hammer a nail. Similarly, you should not use
Temporal.Instant(a precise UTC point) when you only needTemporal.PlainDate(a date on a calendar).
🏢 REAL WORLD: Major companies building calendars, booking systems, financial platforms, and global apps lobbied for the Temporal API for years — because the old
Dateobject was causing real data loss and billing errors in production systems.
2. Topic 1 — Temporal Introduction & Overview
Phase 1 — Conceptual Understanding
Key Design Principles of Temporal
Before looking at any code, understand these five principles that govern how Temporal works:
Principle 1 — Immutable (No Setters!)
Every Temporal object is immutable — it cannot be changed after creation. When you “modify” a Temporal value, you always get a new object back. The original is untouched.
const date = Temporal.PlainDate.from("2024-03-20");
// ❌ There is no date.setDate(21) — no setters exist!
// ✅ Instead — create a new object:
const newDate = date.with({ day: 21 });
console.log(date.toString()); // 2024-03-20 ← unchanged
console.log(newDate.toString()); // 2024-03-21 ← new object
▶ Expected Output:
2024-03-20
2024-03-21
🏢 REAL WORLD: Immutability prevents a whole class of bugs where a shared date object is accidentally modified by one part of the code, breaking another. This is especially common in React and other UI frameworks.
Principle 2 — Months Are 1-Indexed ✅
Unlike the old Date object, Temporal uses human-friendly month numbers. January = 1, December = 12.
// Old Date — CONFUSING ❌
const old = new Date(2024, 0, 15); // month 0 = January?!
// Temporal — CLEAR ✅
const modern = Temporal.PlainDate.from({ year: 2024, month: 1, day: 15 }); // month 1 = January
console.log(modern.month); // 1
console.log(modern.monthCode); // "M01"
Principle 3 — Explicit Timezone Handling
Temporal separates “plain” dates/times (no timezone) from “zoned” dates/times (with timezone). You always know exactly what you’re working with.
Principle 4 — Nanosecond Precision
Temporal stores time with nanosecond precision — 1,000,000× more precise than the old Date object’s milliseconds. Critical for scientific, financial, and performance-sensitive applications.
const instant = Temporal.Instant.fromEpochNanoseconds(1705276800000000500n);
// Note the "n" suffix — JavaScript BigInt for very large numbers
console.log(instant.epochNanoseconds); // 1705276800000000500n
Principle 5 — ISO 8601 Strings for Everything
Every Temporal type produces and accepts clean, unambiguous ISO 8601 strings.
const date = Temporal.PlainDate.from("2024-03-20");
console.log(date.toString()); // "2024-03-20"
const time = Temporal.PlainTime.from("14:30:00");
console.log(time.toString()); // "14:30:00"
const zdt = Temporal.ZonedDateTime.from("2024-03-20T14:30:00[Europe/London]");
console.log(zdt.toString()); // "2024-03-20T14:30:00+00:00[Europe/London]"
The Temporal Type Family — When to Use What
| Use Case | Temporal Type |
|---|---|
| “Right now” in universal time | Temporal.Now.instant() |
| “Today’s date” (no time) | Temporal.Now.plainDateISO() |
| A calendar date with no time (e.g. a birthday) | Temporal.PlainDate |
| A time of day with no date (e.g. “office opens at 9 AM”) | Temporal.PlainTime |
| A date and time with no timezone (e.g. a log entry) | Temporal.PlainDateTime |
| A meeting with a specific timezone | Temporal.ZonedDateTime |
| An exact point in history (database timestamp) | Temporal.Instant |
| “How long” between two events | Temporal.Duration |
| A month in a year (no day — e.g. credit card expiry) | Temporal.PlainYearMonth |
| An annual event (month + day, no year — e.g. birthday) | Temporal.PlainMonthDay |
3. Topic 2 — Temporal vs Date: Side-by-Side
Phase 1 — Conceptual Understanding
Let’s compare the same tasks done the old way (Date) vs the new way (Temporal).
Comparison 1 — Create “March 20, 2024”
// Old Date — month is 0-indexed (bug waiting to happen)
const old = new Date(2024, 2, 20); // month 2 = March ← confusing!
// Temporal — month 3 = March (human-friendly!)
const modern = Temporal.PlainDate.from({ year: 2024, month: 3, day: 20 });
// or from a string:
const modern2 = Temporal.PlainDate.from("2024-03-20");
console.log(old.toDateString()); // Wed Mar 20 2024
console.log(modern.toString()); // 2024-03-20
Comparison 2 — Add 10 Days
// Old Date — mutates the original!
const old = new Date(2024, 2, 20);
old.setDate(old.getDate() + 10); // ← mutates old directly!
console.log(old.toDateString()); // Sat Mar 30 2024
// Temporal — always returns a new object, original unchanged
const original = Temporal.PlainDate.from("2024-03-20");
const added = original.add({ days: 10 });
console.log(original.toString()); // 2024-03-20 ← untouched!
console.log(added.toString()); // 2024-03-30 ← new object
▶ Expected Output:
2024-03-20
2024-03-30
Comparison 3 — Difference Between Two Dates
// Old Date — manual math, error-prone
const d1 = new Date(2024, 0, 1);
const d2 = new Date(2024, 11, 31);
const diffDays = Math.round((d2 - d1) / 86400000); // 365
// Temporal — clean and explicit
const t1 = Temporal.PlainDate.from("2024-01-01");
const t2 = Temporal.PlainDate.from("2024-12-31");
const diff = t1.until(t2);
console.log(diff.days); // 365
console.log(diff.toString()); // "P365D"
Comparison 4 — Timezone Conversion
// Old Date — no built-in timezone support at all!
// You need external libraries like Moment.js or date-fns/tz
// Temporal — built-in, explicit, clear
const meeting = Temporal.ZonedDateTime.from("2024-03-20T14:00:00[Europe/London]");
const inNY = meeting.withTimeZone("America/New_York");
const inTokyo = meeting.withTimeZone("Asia/Tokyo");
console.log(inNY.toString()); // 2024-03-20T10:00:00-04:00[America/New_York]
console.log(inTokyo.toString()); // 2024-03-20T23:00:00+09:00[Asia/Tokyo]
Full Comparison Table
| Feature | Old Date |
Temporal |
|---|---|---|
| Months | 0-indexed (Jan=0) ❌ | 1-indexed (Jan=1) ✅ |
| Mutable | Yes (setters) ❌ | No (immutable) ✅ |
| Precision | Milliseconds | Nanoseconds ✅ |
| Timezone support | None (workarounds needed) ❌ | Built-in, explicit ✅ |
| String parsing | Inconsistent ❌ | ISO 8601, consistent ✅ |
| Date-only type | No (Date always has time) ❌ | Yes (PlainDate) ✅ |
| Time-only type | No ❌ | Yes (PlainTime) ✅ |
| Duration type | No (manual math) ❌ | Yes (Duration) ✅ |
| Calendar systems | No ❌ | Yes (ISO, Hebrew, etc.) ✅ |
4. Topic 3 — Temporal.Now
Phase 1 — Conceptual Understanding
Temporal.Now is a namespace (a collection of static methods) that gives you the current date and time in various forms. You do NOT create a Temporal.Now object — you call its methods directly.
Think of Temporal.Now like a dashboard with different dials — each method gives you the “current moment” in a different format.
Temporal.Now.instant() — Current Moment as a UTC Instant
Returns the current moment as a Temporal.Instant — a precise point in universal time, stored in nanoseconds.
const now = Temporal.Now.instant();
console.log(now.toString());
// "2024-03-20T14:30:00.123456789Z"
// ↑ Full UTC timestamp with nanoseconds!
console.log(now.epochMilliseconds);
// 1710944200123 (milliseconds since epoch — for compatibility)
▶ Expected Output (sample):
2024-03-20T14:30:00.123456789Z
1710944200123
🏢 REAL WORLD: Use this to timestamp database records, log entries, and event streams. It gives the most precise, unambiguous timestamp possible.
Temporal.Now.zonedDateTimeISO() — Current Date+Time With Timezone
Returns the current date and time in your local timezone as a Temporal.ZonedDateTime.
const zdt = Temporal.Now.zonedDateTimeISO();
console.log(zdt.toString());
// "2024-03-20T14:30:00.123456789+00:00[Europe/London]"
// ↑ Full date, time, UTC offset, AND timezone name!
// You can also specify a timezone:
const inNY = Temporal.Now.zonedDateTimeISO("America/New_York");
console.log(inNY.toString());
// "2024-03-20T10:30:00.123456789-04:00[America/New_York]"
Temporal.Now.plainDateISO() — Today’s Date Only
Returns today’s date as a Temporal.PlainDate — no time, no timezone.
const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // "2024-03-20"
console.log(today.year); // 2024
console.log(today.month); // 3 (March — 1-indexed!)
console.log(today.day); // 20
▶ Expected Output (sample):
2024-03-20
2024
3
20
💡 TIP: You can also specify a timezone to get “today” in a specific part of the world:
const todayInTokyo = Temporal.Now.plainDateISO("Asia/Tokyo"); // Tokyo is ahead of UTC — their "today" might be ahead of yours
Temporal.Now.plainTimeISO() — Current Time Only
Returns the current time of day as a Temporal.PlainTime — no date, no timezone.
const time = Temporal.Now.plainTimeISO();
console.log(time.toString()); // "14:30:00.123456789"
console.log(time.hour); // 14
console.log(time.minute); // 30
console.log(time.second); // 0
Temporal.Now.plainDateTimeISO() — Current Date and Time (No Timezone)
Returns current date + time as a Temporal.PlainDateTime — useful for local timestamps where you don’t need timezone info.
const dt = Temporal.Now.plainDateTimeISO();
console.log(dt.toString()); // "2024-03-20T14:30:00.123456789"
Temporal.Now.timeZoneId() — Your Current Timezone Name
Returns the IANA timezone identifier (the official name) of your system’s current timezone.
const tz = Temporal.Now.timeZoneId();
console.log(tz); // "Europe/London" or "America/New_York" or "Asia/Tokyo" etc.
🏢 REAL WORLD: Use this when you need to store a user’s timezone preference automatically on first visit to your app.
Summary: All Temporal.Now Methods
| Method | Returns | Best For |
|---|---|---|
Temporal.Now.instant() |
Temporal.Instant |
Precise UTC timestamps |
Temporal.Now.zonedDateTimeISO(tz?) |
Temporal.ZonedDateTime |
Full local date+time+timezone |
Temporal.Now.plainDateISO(tz?) |
Temporal.PlainDate |
Today’s date only |
Temporal.Now.plainTimeISO(tz?) |
Temporal.PlainTime |
Current time of day only |
Temporal.Now.plainDateTimeISO(tz?) |
Temporal.PlainDateTime |
Local date+time, no TZ needed |
Temporal.Now.timeZoneId() |
String | Get the user’s timezone name |
5. Topic 4 — Temporal.Instant
Phase 1 — Conceptual Understanding
A Temporal.Instant represents a specific, precise point in universal time — like a pin stuck in the global timeline. It is always in UTC. It has no timezone, no calendar, no year/month/day on its own — just a single precise moment measured in nanoseconds from the Unix Epoch (January 1, 1970 UTC).
Think of it like a universal timestamp on a package — it says exactly when something happened, without any cultural interpretation of “day” or “month”.
Creating a Temporal.Instant
From an ISO 8601 string (must end with Z for UTC):
const i1 = Temporal.Instant.from("2024-03-20T14:30:00Z");
console.log(i1.toString()); // "2024-03-20T14:30:00Z"
const i2 = Temporal.Instant.from("2024-03-20T14:30:00.123456789Z");
console.log(i2.toString()); // "2024-03-20T14:30:00.123456789Z"
From epoch milliseconds (for compatibility with old Date):
const i = Temporal.Instant.fromEpochMilliseconds(1710944200000);
console.log(i.toString()); // "2024-03-20T14:36:40Z"
From epoch nanoseconds (BigInt — note the n):
const i = Temporal.Instant.fromEpochNanoseconds(1710944200000000000n);
console.log(i.toString()); // "2024-03-20T14:36:40Z"
Temporal.Instant.fromEpochSeconds():
const i = Temporal.Instant.fromEpochSeconds(1710944200);
console.log(i.toString()); // "2024-03-20T14:36:40Z"
Reading Instant Properties
const i = Temporal.Instant.from("2024-03-20T14:30:00.123Z");
console.log(i.epochSeconds); // 1710944200 (Number)
console.log(i.epochMilliseconds); // 1710944200123 (Number)
console.log(i.epochMicroseconds); // 1710944200123000n (BigInt)
console.log(i.epochNanoseconds); // 1710944200123000000n (BigInt)
▶ Expected Output (sample):
1710944200
1710944200123
1710944200123000n
1710944200123000000n
💡 TIP:
epochMillisecondsis the most compatible property — it is the same value you’d get from the oldDateobject’sgetTime()method. Use it when passing timestamps to APIs or databases that expect milliseconds.
🤔 THINK ABOUT IT: Why are
epochMicrosecondsandepochNanosecondsBigInt values (withn) whileepochSecondsandepochMillisecondsare regular Numbers? Because nanosecond timestamps are too large to fit accurately in JavaScript’s regular 64-bit floating-point number format!
Comparing Two Instants
const event1 = Temporal.Instant.from("2024-03-20T10:00:00Z");
const event2 = Temporal.Instant.from("2024-03-20T14:30:00Z");
// Compare: returns -1, 0, or 1
const result = Temporal.Instant.compare(event1, event2);
console.log(result); // -1 (event1 is before event2)
// Check order
if (Temporal.Instant.compare(event1, event2) < 0) {
console.log("event1 happened first");
}
▶ Expected Output:
-1
event1 happened first
Arithmetic with Instant
const start = Temporal.Instant.from("2024-03-20T10:00:00Z");
// Add a duration (only hours, minutes, seconds, ms, µs, ns — NOT days/months!)
const later = start.add({ hours: 4, minutes: 30 });
console.log(later.toString()); // "2024-03-20T14:30:00Z"
// Subtract
const earlier = start.subtract({ minutes: 90 });
console.log(earlier.toString()); // "2024-03-20T08:30:00Z"
⚠️ WATCH OUT:
Temporal.Instantdoes NOT support addingdays,months, oryears— because “1 day” is ambiguous without a timezone (daylight saving time can make days 23 or 25 hours long). UseTemporal.ZonedDateTimefor calendar-aware arithmetic.
Converting Instant to ZonedDateTime
To read the year, month, day, hour etc. from an Instant, you must first attach a timezone:
const i = Temporal.Instant.from("2024-03-20T14:30:00Z");
const inLondon = i.toZonedDateTimeISO("Europe/London");
const inNewYork = i.toZonedDateTimeISO("America/New_York");
const inTokyo = i.toZonedDateTimeISO("Asia/Tokyo");
console.log(inLondon.toString()); // "2024-03-20T14:30:00+00:00[Europe/London]"
console.log(inNewYork.toString()); // "2024-03-20T10:30:00-04:00[America/New_York]"
console.log(inTokyo.toString()); // "2024-03-20T23:30:00+09:00[Asia/Tokyo]"
▶ Expected Output:
2024-03-20T14:30:00+00:00[Europe/London]
2024-03-20T10:30:00-04:00[America/New_York]
2024-03-20T23:30:00+09:00[Asia/Tokyo]
🏢 REAL WORLD: Store all timestamps in your database as
Temporal.Instant(or itsepochMilliseconds). When displaying to a user, convert to their timezone usingtoZonedDateTimeISO(userTimezone). This is the professional pattern used by all global apps.
until() and since() — Duration Between Instants
const start = Temporal.Instant.from("2024-03-20T10:00:00Z");
const end = Temporal.Instant.from("2024-03-20T14:30:00Z");
const duration = start.until(end);
console.log(duration.hours); // 4
console.log(duration.minutes); // 30
console.log(duration.toString()); // "PT4H30M"
// since() is the reverse
const ago = end.since(start);
console.log(ago.toString()); // "PT4H30M"
6. Topic 5 — Plain Types
Phase 1 — Conceptual Understanding
“Plain” types in Temporal are date/time objects with no timezone attached. They represent calendar values as they appear on a wall calendar or clock — without any UTC conversion or timezone ambiguity.
There are four Plain types. Each covers a different slice of date/time information:
| Type | Stores | Example |
|---|---|---|
Temporal.PlainDate |
Year, Month, Day | “2024-03-20” |
Temporal.PlainTime |
Hour, Minute, Second, subseconds | “14:30:00” |
Temporal.PlainYearMonth |
Year + Month (no day) | “2024-03” |
Temporal.PlainMonthDay |
Month + Day (no year) | ”–03-20” |
6A — Temporal.PlainDate
A date on a calendar with no time and no timezone. Perfect for birthdays, holidays, deadlines, or any date where the specific hour doesn’t matter.
Creating a PlainDate:
// From an ISO string
const d1 = Temporal.PlainDate.from("2024-03-20");
console.log(d1.toString()); // "2024-03-20"
// From an object (month is 1-indexed!)
const d2 = Temporal.PlainDate.from({ year: 2024, month: 3, day: 20 });
console.log(d2.toString()); // "2024-03-20"
// Constructor
const d3 = new Temporal.PlainDate(2024, 3, 20);
console.log(d3.toString()); // "2024-03-20"
Reading PlainDate Properties:
const d = Temporal.PlainDate.from("2024-03-20");
console.log(d.year); // 2024
console.log(d.month); // 3 ← March (1-indexed!)
console.log(d.monthCode); // "M03"
console.log(d.day); // 20
console.log(d.dayOfWeek); // 3 ← Wednesday (1=Mon, 7=Sun — ISO standard)
console.log(d.dayOfYear); // 80 ← 80th day of 2024
console.log(d.weekOfYear); // 12 ← 12th week of 2024
console.log(d.daysInMonth); // 31 ← March has 31 days
console.log(d.daysInYear); // 366 ← 2024 is a leap year
console.log(d.inLeapYear); // true
console.log(d.calendarId); // "iso8601"
▶ Expected Output:
2024
3
M03
20
3
80
12
31
366
true
iso8601
💡 TIP:
dayOfWeekin Temporal follows the ISO 8601 standard — Monday=1, Tuesday=2, …, Sunday=7. This is different from the oldDateobject where Sunday=0.
🐛 COMMON MISTAKE: Do not confuse
day(day of the month: 1–31) withdayOfWeek(day of the week: 1–7 in ISO). And notedayOfWeek: 3means Wednesday, NOT Thursday!
Modifying a PlainDate with .with():
const d = Temporal.PlainDate.from("2024-03-20");
const nextMonth = d.with({ month: 4 }); // Change only the month
console.log(nextMonth.toString()); // "2024-04-20"
const endOfYear = d.with({ month: 12, day: 31 });
console.log(endOfYear.toString()); // "2024-12-31"
Adding and Subtracting:
const d = Temporal.PlainDate.from("2024-03-20");
const plus10 = d.add({ days: 10 });
console.log(plus10.toString()); // "2024-03-30"
const plus3m = d.add({ months: 3 });
console.log(plus3m.toString()); // "2024-06-20"
const minus1y = d.subtract({ years: 1 });
console.log(minus1y.toString()); // "2023-03-20"
▶ Expected Output:
2024-03-30
2024-06-20
2023-03-20
Comparing Two Dates:
const d1 = Temporal.PlainDate.from("2024-03-20");
const d2 = Temporal.PlainDate.from("2024-06-15");
console.log(Temporal.PlainDate.compare(d1, d2)); // -1 (d1 is before d2)
console.log(d1.equals(d2)); // false
console.log(d1.equals("2024-03-20")); // true (string auto-converted)
until() and since():
const start = Temporal.PlainDate.from("2024-01-01");
const end = Temporal.PlainDate.from("2024-12-31");
const diff = start.until(end);
console.log(diff.days); // 365
// With explicit units
const diff2 = start.until(end, { largestUnit: "month" });
console.log(diff2.months); // 11
console.log(diff2.days); // 30
6B — Temporal.PlainTime
A time of day with no date and no timezone. Perfect for “office opens at 9:00 AM”, “alarm at 06:30”, or any recurring daily time.
Creating a PlainTime:
const t1 = Temporal.PlainTime.from("14:30:00");
console.log(t1.toString()); // "14:30:00"
const t2 = Temporal.PlainTime.from({ hour: 14, minute: 30, second: 0 });
console.log(t2.toString()); // "14:30:00"
const t3 = new Temporal.PlainTime(14, 30, 0, 0, 0, 0); // h, m, s, ms, µs, ns
console.log(t3.toString()); // "14:30:00"
Reading PlainTime Properties:
const t = Temporal.PlainTime.from("14:30:45.123456789");
console.log(t.hour); // 14
console.log(t.minute); // 30
console.log(t.second); // 45
console.log(t.millisecond); // 123
console.log(t.microsecond); // 456
console.log(t.nanosecond); // 789
▶ Expected Output:
14
30
45
123
456
789
Arithmetic on PlainTime:
const t = Temporal.PlainTime.from("22:00:00");
// Adding time wraps around midnight automatically!
const plus3h = t.add({ hours: 3 });
console.log(plus3h.toString()); // "01:00:00" ← wrapped past midnight!
const minus30m = t.subtract({ minutes: 30 });
console.log(minus30m.toString()); // "21:30:00"
▶ Expected Output:
01:00:00
21:30:00
Until/Since with PlainTime:
const open = Temporal.PlainTime.from("09:00:00");
const close = Temporal.PlainTime.from("17:30:00");
const workDay = open.until(close);
console.log(workDay.hours); // 8
console.log(workDay.minutes); // 30
▶ Expected Output:
8
30
6C — Temporal.PlainYearMonth
Represents a specific year and month with no day. Perfect for credit card expiry dates, monthly reporting periods, or subscription months.
const ym1 = Temporal.PlainYearMonth.from("2024-03");
console.log(ym1.year); // 2024
console.log(ym1.month); // 3
console.log(ym1.daysInMonth); // 31
// Add months
const next = ym1.add({ months: 3 });
console.log(next.toString()); // "2024-06"
// Compare
console.log(Temporal.PlainYearMonth.compare(ym1, next)); // -1
▶ Expected Output:
2024
3
31
2024-06
-1
🏢 REAL WORLD: A credit card expiry of “03/2027” is a
PlainYearMonth— it represents the entire month of March 2027, not a specific day.
6D — Temporal.PlainMonthDay
Represents a month and day with no year. Perfect for recurring annual events — birthdays, anniversaries, national holidays — where the year changes but the month/day stays the same.
// Note the "--" prefix in ISO format for PlainMonthDay (no year!)
const birthday = Temporal.PlainMonthDay.from("--03-20");
console.log(birthday.month); // 3
console.log(birthday.day); // 20
// Or from an object
const holiday = Temporal.PlainMonthDay.from({ month: 12, day: 25 });
console.log(holiday.toString()); // "--12-25"
// Convert to a PlainDate for a specific year
const thisYear = birthday.toPlainDate({ year: 2024 });
console.log(thisYear.toString()); // "2024-03-20"
const nextYear = birthday.toPlainDate({ year: 2025 });
console.log(nextYear.toString()); // "2025-03-20"
▶ Expected Output:
3
20
--12-25
2024-03-20
2025-03-20
🏢 REAL WORLD: In a birthday reminder app, store each person’s birthday as a
PlainMonthDay. Each year, call.toPlainDate({ year: currentYear })to get this year’s birthday.
7. Topic 6 — Temporal.PlainDateTime
Phase 1 — Conceptual Understanding
Temporal.PlainDateTime represents a date and a time of day — but with no timezone. It is like a calendar entry written on paper: “March 20, 2024 at 2:30 PM” — it describes a moment in time on a local clock, but says nothing about which timezone that clock is in.
Use PlainDateTime when:
- You’re recording events that happened on a specific machine (logs)
- The timezone is already understood by both parties (e.g. “always UTC”)
- You want to combine date + time before attaching a timezone
Creating a PlainDateTime
// From ISO string
const dt1 = Temporal.PlainDateTime.from("2024-03-20T14:30:00");
console.log(dt1.toString()); // "2024-03-20T14:30:00"
// From an object
const dt2 = Temporal.PlainDateTime.from({
year: 2024, month: 3, day: 20,
hour: 14, minute: 30, second: 0
});
console.log(dt2.toString()); // "2024-03-20T14:30:00"
// Constructor
const dt3 = new Temporal.PlainDateTime(2024, 3, 20, 14, 30, 0);
console.log(dt3.toString()); // "2024-03-20T14:30:00"
Reading All Properties
PlainDateTime has ALL the properties of both PlainDate and PlainTime:
const dt = Temporal.PlainDateTime.from("2024-03-20T14:30:45.123456789");
// Date parts
console.log(dt.year); // 2024
console.log(dt.month); // 3
console.log(dt.day); // 20
console.log(dt.dayOfWeek); // 3 (Wednesday in ISO: Mon=1, Sun=7)
console.log(dt.dayOfYear); // 80
console.log(dt.weekOfYear); // 12
console.log(dt.daysInMonth); // 31
console.log(dt.inLeapYear); // true
// Time parts
console.log(dt.hour); // 14
console.log(dt.minute); // 30
console.log(dt.second); // 45
console.log(dt.millisecond); // 123
console.log(dt.microsecond); // 456
console.log(dt.nanosecond); // 789
Modifying with .with()
const dt = Temporal.PlainDateTime.from("2024-03-20T14:30:00");
// Change just the time
const morning = dt.with({ hour: 9, minute: 0, second: 0 });
console.log(morning.toString()); // "2024-03-20T09:00:00"
// Change just the date
const nextWeek = dt.with({ day: 27 });
console.log(nextWeek.toString()); // "2024-03-27T14:30:00"
▶ Expected Output:
2024-03-20T09:00:00
2024-03-27T14:30:00
Arithmetic with PlainDateTime
const dt = Temporal.PlainDateTime.from("2024-03-20T14:30:00");
const plus2weeks = dt.add({ weeks: 2 });
console.log(plus2weeks.toString()); // "2024-04-03T14:30:00"
const minus1y6m = dt.subtract({ years: 1, months: 6 });
console.log(minus1y6m.toString()); // "2022-09-20T14:30:00"
▶ Expected Output:
2024-04-03T14:30:00
2022-09-20T14:30:00
Converting PlainDateTime to Other Types
const dt = Temporal.PlainDateTime.from("2024-03-20T14:30:00");
// Extract just the date
const date = dt.toPlainDate();
console.log(date.toString()); // "2024-03-20"
// Extract just the time
const time = dt.toPlainTime();
console.log(time.toString()); // "14:30:00"
// Attach a timezone to make a ZonedDateTime
const zdt = dt.toZonedDateTime("Europe/London");
console.log(zdt.toString()); // "2024-03-20T14:30:00+00:00[Europe/London]"
8. Topic 7 — Temporal.ZonedDateTime
Phase 1 — Conceptual Understanding
Temporal.ZonedDateTime is the most complete Temporal type. It stores a date, a time, AND a timezone. It knows exactly what moment in universal time something is, AND what it looks like on a calendar in a specific place.
Think of it like a meeting invitation: “March 20, 2024 at 2:30 PM London time” — this is fully unambiguous. Anyone anywhere can calculate what time this is in their own timezone.
Use ZonedDateTime when:
- Scheduling meetings across timezones
- Recording events that users in different countries need to see in their own local time
- Handling daylight saving time correctly
Creating a ZonedDateTime
The key feature of a ZonedDateTime string is the timezone identifier in square brackets [...]:
// From ISO string with timezone in brackets
const zdt1 = Temporal.ZonedDateTime.from("2024-03-20T14:30:00[Europe/London]");
console.log(zdt1.toString());
// "2024-03-20T14:30:00+00:00[Europe/London]"
// With UTC offset and timezone
const zdt2 = Temporal.ZonedDateTime.from("2024-03-20T10:30:00-04:00[America/New_York]");
console.log(zdt2.toString());
// "2024-03-20T10:30:00-04:00[America/New_York]"
// From an object
const zdt3 = Temporal.ZonedDateTime.from({
year: 2024, month: 3, day: 20,
hour: 14, minute: 30, second: 0,
timeZone: "Asia/Tokyo"
});
console.log(zdt3.toString());
// "2024-03-20T14:30:00+09:00[Asia/Tokyo]"
Reading ZonedDateTime Properties
ZonedDateTime has all the properties of PlainDateTime, PLUS timezone-specific ones:
const zdt = Temporal.ZonedDateTime.from("2024-03-20T14:30:00+00:00[Europe/London]");
// Standard date/time properties
console.log(zdt.year); // 2024
console.log(zdt.month); // 3
console.log(zdt.day); // 20
console.log(zdt.hour); // 14
console.log(zdt.minute); // 30
// Timezone-specific properties
console.log(zdt.timeZoneId); // "Europe/London"
console.log(zdt.offset); // "+00:00"
console.log(zdt.offsetNanoseconds); // 0
console.log(zdt.hoursInDay); // 24 (or 23/25 on DST change days!)
Converting Timezones with .withTimeZone()
This is one of the most powerful features — converting the same moment into any timezone:
const london = Temporal.ZonedDateTime.from("2024-03-20T14:00:00[Europe/London]");
// Convert to different timezones — same moment, different local time display
const newYork = london.withTimeZone("America/New_York");
const tokyo = london.withTimeZone("Asia/Tokyo");
const dubai = london.withTimeZone("Asia/Dubai");
const sydney = london.withTimeZone("Australia/Sydney");
console.log("London :", london.hour + ":" + String(london.minute).padStart(2,"0"));
console.log("New York:", newYork.hour + ":" + String(newYork.minute).padStart(2,"0"));
console.log("Tokyo :", tokyo.hour + ":" + String(tokyo.minute).padStart(2,"0"));
console.log("Dubai :", dubai.hour + ":" + String(dubai.minute).padStart(2,"0"));
console.log("Sydney :", sydney.hour + ":" + String(sydney.minute).padStart(2,"0"));
▶ Expected Output (for 14:00 London time in summer):
London : 14:00
New York: 10:00
Tokyo : 23:00
Dubai : 18:00
Sydney : 00:00
🏢 REAL WORLD: Google Calendar shows “2:00 PM London time — click to see your local time”. That conversion is exactly
withTimeZone(userTimezone).
Daylight Saving Time (DST) — ZonedDateTime Handles It Automatically
One of the most complex problems in date handling is Daylight Saving Time. Clocks spring forward in spring and fall back in autumn. ZonedDateTime handles this correctly and transparently.
// The day clocks spring forward in London (March 31, 2024)
const beforeDST = Temporal.ZonedDateTime.from("2024-03-31T00:00:00[Europe/London]");
const afterDST = beforeDST.add({ hours: 3 });
// Even though we added 3 hours, the clock shows 4 hours later
// because the clock jumped forward 1 hour at 1 AM!
console.log(beforeDST.hour); // 0
console.log(afterDST.hour); // 4 ← 3 elapsed hours = 4 clock hours because of DST!
console.log(beforeDST.hoursInDay); // 23 ← this day only has 23 hours!
🤔 THINK ABOUT IT: If you tried to do this with the old
Dateobject, you’d have to manually check whether DST was in effect. ZonedDateTime does this for you automatically using IANA timezone data.
ZonedDateTime Arithmetic
const zdt = Temporal.ZonedDateTime.from("2024-03-20T14:00:00[America/New_York]");
// Calendar-aware addition (respects DST!)
const plus3months = zdt.add({ months: 3 });
console.log(plus3months.toString());
// "2024-06-20T14:00:00-04:00[America/New_York]"
// Note: offset changed from -05:00 to -04:00 because DST kicked in!
Converting ZonedDateTime to Other Types
const zdt = Temporal.ZonedDateTime.from("2024-03-20T14:30:00[Europe/London]");
const instant = zdt.toInstant();
console.log(instant.toString()); // "2024-03-20T14:30:00Z"
const plainDate = zdt.toPlainDate();
console.log(plainDate.toString()); // "2024-03-20"
const plainTime = zdt.toPlainTime();
console.log(plainTime.toString()); // "14:30:00"
const plainDT = zdt.toPlainDateTime();
console.log(plainDT.toString()); // "2024-03-20T14:30:00"
9. Topic 8 — Temporal.Duration
Phase 1 — Conceptual Understanding
A Temporal.Duration represents a length of time — not a point in time, but the span between two points. Think of it like the difference between “2:30 PM” (a point in time) and “2 hours and 30 minutes” (a duration).
Durations can include: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds — all in one object.
Creating a Duration
// From an object
const d1 = Temporal.Duration.from({ years: 1, months: 6, days: 15 });
console.log(d1.toString()); // "P1Y6M15D" (ISO 8601 duration format)
// From an ISO duration string
// P = "Period" marker, T = time separator
const d2 = Temporal.Duration.from("P1Y6M15DT2H30M");
console.log(d2.years); // 1
console.log(d2.months); // 6
console.log(d2.days); // 15
console.log(d2.hours); // 2
console.log(d2.minutes); // 30
// Using the constructor
const d3 = new Temporal.Duration(1, 6, 0, 15, 2, 30);
// (years, months, weeks, days, hours, minutes, seconds, ms, µs, ns)
console.log(d3.toString()); // "P1Y6M15DT2H30M"
▶ Expected Output:
P1Y6M15D
1
6
15
2
30
P1Y6M15DT2H30M
Understanding ISO Duration Format
The ISO 8601 duration string format uses letters as unit markers:
P1Y6M15DT2H30M45S
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
│ │ │ │ │ │ │ └── Seconds
│ │ │ │ │ │ └───── Minutes
│ │ │ │ │ └──────── Hours
│ │ │ │ └────────── "T" separates date part from time part
│ │ │ └──────────── Days
│ │ └─────────────── Months
│ └───────────────── Years
└─────────────────── "P" = Period (required start marker)
console.log(Temporal.Duration.from("P1Y").toString()); // "P1Y"
console.log(Temporal.Duration.from("P6M").toString()); // "P6M"
console.log(Temporal.Duration.from("P15D").toString()); // "P15D"
console.log(Temporal.Duration.from("PT2H30M").toString()); // "PT2H30M"
console.log(Temporal.Duration.from("PT45S").toString()); // "PT45S"
console.log(Temporal.Duration.from("P1Y6M15DT2H30M45S").toString()); // full
Reading Duration Properties
const d = Temporal.Duration.from({ years: 2, months: 3, weeks: 1, days: 5, hours: 6, minutes: 30, seconds: 15 });
console.log(d.years); // 2
console.log(d.months); // 3
console.log(d.weeks); // 1
console.log(d.days); // 5
console.log(d.hours); // 6
console.log(d.minutes); // 30
console.log(d.seconds); // 15
console.log(d.sign); // 1 (positive duration)
Negative Durations
A Duration can be negative (representing going back in time). The sign property tells you the direction.
const negative = Temporal.Duration.from({ days: -10 });
console.log(negative.sign); // -1
console.log(negative.toString()); // "-P10D"
// Negate a duration
const positive = Temporal.Duration.from({ days: 10 });
const flipped = positive.negated();
console.log(flipped.sign); // -1
console.log(flipped.toString()); // "-P10D"
// Absolute value (always positive)
const abs = negative.abs();
console.log(abs.toString()); // "P10D"
Adding and Subtracting Durations
const d1 = Temporal.Duration.from({ months: 3, days: 10 });
const d2 = Temporal.Duration.from({ months: 1, days: 5 });
// Duration + Duration
// Note: requires a relativeTo date for month calculations
const sum = d1.add(d2, { relativeTo: Temporal.PlainDate.from("2024-01-01") });
console.log(sum.months); // 4
console.log(sum.days); // 15
total() — Convert Duration to a Single Unit
// How many total days is 2 months and 15 days?
const d = Temporal.Duration.from({ months: 2, days: 15 });
const totalDays = d.total({
unit: "day",
relativeTo: Temporal.PlainDate.from("2024-01-01")
});
console.log(totalDays); // 76 (Jan=31, Feb=29 in 2024 leap year, +15 = 75... check exact)
💡 TIP: Many Duration operations require a
relativeTodate. This is because months have different numbers of days (28–31), and years have 365 or 366 days. Without knowing which months you’re measuring, JavaScript can’t convert accurately.
10. Topic 9 — Temporal Arithmetic
Phase 1 — Conceptual Understanding
Date arithmetic — adding days, subtracting months, calculating differences — is one of the most common (and error-prone) tasks in date handling. Temporal makes it clean, safe, and explicit.
The core rule: All Temporal types are immutable. Every arithmetic operation returns a new object. The original is never changed.
Adding a Duration to a Date
const startDate = Temporal.PlainDate.from("2024-03-20");
// Add individual components
const plus30days = startDate.add({ days: 30 });
const plus2months = startDate.add({ months: 2 });
const plus1year = startDate.add({ years: 1 });
const combined = startDate.add({ years: 1, months: 2, days: 15 });
console.log(plus30days.toString()); // "2024-04-19"
console.log(plus2months.toString()); // "2024-05-20"
console.log(plus1year.toString()); // "2025-03-20"
console.log(combined.toString()); // "2025-06-04"
▶ Expected Output:
2024-04-19
2024-05-20
2025-03-20
2025-06-04
Subtracting a Duration from a Date
const d = Temporal.PlainDate.from("2024-03-20");
const minus10days = d.subtract({ days: 10 });
const minus1month = d.subtract({ months: 1 });
const minus2years = d.subtract({ years: 2 });
console.log(minus10days.toString()); // "2024-03-10"
console.log(minus1month.toString()); // "2024-02-20"
console.log(minus2years.toString()); // "2022-03-20"
▶ Expected Output:
2024-03-10
2024-02-20
2022-03-20
Calculating Difference: until() and since()
until() and since() tell you the Duration between two dates.
a.until(b)→ “how long from a to b?” (forward)a.since(b)→ “how long since b until a?” (also forward, just written differently)
const start = Temporal.PlainDate.from("2024-01-15");
const end = Temporal.PlainDate.from("2024-12-31");
// Default largest unit = "day"
const diff1 = start.until(end);
console.log(diff1.days); // 351
// Specify largest unit to get months and days
const diff2 = start.until(end, { largestUnit: "month" });
console.log(diff2.months); // 11
console.log(diff2.days); // 16
// Specify smallest unit to round
const diff3 = start.until(end, { largestUnit: "year", smallestUnit: "month" });
console.log(diff3.years); // 0
console.log(diff3.months); // 11
▶ Expected Output:
351
11
16
0
11
Overflow Control: overflow Option
When adding months, you can land on a day that doesn’t exist in the target month. For example, adding 1 month to January 31 would give February 31 — which doesn’t exist. Temporal handles this with the overflow option:
const jan31 = Temporal.PlainDate.from("2024-01-31");
// Default overflow = "constrain" — clamps to last valid day
const feb = jan31.add({ months: 1 }); // Feb 31 → constrain to Feb 29 (2024 leap year)
console.log(feb.toString()); // "2024-02-29"
// overflow: "reject" — throws an error instead
try {
const invalid = jan31.add({ months: 1 }, { overflow: "reject" });
} catch (e) {
console.log(e.message); // RangeError — Feb 31 does not exist
}
▶ Expected Output:
2024-02-29
RangeError ...
Rounding Durations: round()
const start = Temporal.PlainDateTime.from("2024-03-20T10:23:45");
const end = Temporal.PlainDateTime.from("2024-03-20T14:37:12");
const diff = start.until(end);
console.log(diff.toString()); // "PT4H13M27S"
// Round to the nearest hour
const rounded = diff.round({ smallestUnit: "hour", relativeTo: start });
console.log(rounded.hours); // 4
Using a Stored Duration Object
// Store a duration and reuse it
const subscription = Temporal.Duration.from({ months: 1 });
const signupDate = Temporal.PlainDate.from("2024-03-15");
const renewalDate = signupDate.add(subscription);
const renewal2 = renewalDate.add(subscription);
const renewal3 = renewal2.add(subscription);
console.log(renewalDate.toString()); // "2024-04-15"
console.log(renewal2.toString()); // "2024-05-15"
console.log(renewal3.toString()); // "2024-06-15"
▶ Expected Output:
2024-04-15
2024-05-15
2024-06-15
Comparing Dates and Times
const d1 = Temporal.PlainDate.from("2024-03-20");
const d2 = Temporal.PlainDate.from("2024-06-15");
const d3 = Temporal.PlainDate.from("2024-03-20");
// Static compare() method: -1 (before), 0 (equal), 1 (after)
console.log(Temporal.PlainDate.compare(d1, d2)); // -1 (d1 before d2)
console.log(Temporal.PlainDate.compare(d2, d1)); // 1 (d2 after d1)
console.log(Temporal.PlainDate.compare(d1, d3)); // 0 (equal)
// .equals() for equality check
console.log(d1.equals(d3)); // true
console.log(d1.equals(d2)); // false
// Sorting an array by date
const dates = [
Temporal.PlainDate.from("2024-06-15"),
Temporal.PlainDate.from("2024-01-01"),
Temporal.PlainDate.from("2024-03-20"),
];
dates.sort(Temporal.PlainDate.compare);
console.log(dates.map(d => d.toString()));
// ["2024-01-01", "2024-03-20", "2024-06-15"]
▶ Expected Output:
-1
1
0
true
false
["2024-01-01", "2024-03-20", "2024-06-15"]
Arithmetic with ZonedDateTime (DST-Aware)
const meeting = Temporal.ZonedDateTime.from("2024-03-20T09:00:00[America/New_York]");
// Add 6 months — result respects DST!
const followUp = meeting.add({ months: 6 });
console.log(followUp.toString());
// "2024-09-20T09:00:00-04:00[America/New_York]"
// Notice: offset changed from -05:00 to -04:00 because summer DST is active!
// But the *clock time* (9:00 AM) stays the same — exactly what a human would expect!
11. Topic 10 — Complete Temporal Reference
Your one-stop reference for every Temporal type and its methods.
Temporal.Now — All Methods
| Method | Returns | Description |
|---|---|---|
Temporal.Now.instant() |
Temporal.Instant |
Current UTC instant |
Temporal.Now.zonedDateTimeISO(tz?) |
Temporal.ZonedDateTime |
Current date+time+timezone |
Temporal.Now.plainDateISO(tz?) |
Temporal.PlainDate |
Today’s date |
Temporal.Now.plainTimeISO(tz?) |
Temporal.PlainTime |
Current time of day |
Temporal.Now.plainDateTimeISO(tz?) |
Temporal.PlainDateTime |
Current date+time (no TZ) |
Temporal.Now.timeZoneId() |
String | IANA timezone name of system |
Temporal.Instant — Key Methods and Properties
| Member | Description |
|---|---|
Temporal.Instant.from(string) |
Create from ISO string ending in Z |
Temporal.Instant.fromEpochSeconds(n) |
Create from epoch seconds |
Temporal.Instant.fromEpochMilliseconds(n) |
Create from epoch ms |
Temporal.Instant.fromEpochMicroseconds(n) |
Create from epoch µs (BigInt) |
Temporal.Instant.fromEpochNanoseconds(n) |
Create from epoch ns (BigInt) |
Temporal.Instant.compare(a, b) |
Compare two instants (-1/0/1) |
.epochSeconds |
Number of seconds since epoch |
.epochMilliseconds |
Number of ms since epoch |
.epochMicroseconds |
Number of µs since epoch (BigInt) |
.epochNanoseconds |
Number of ns since epoch (BigInt) |
.add(duration) |
Add time-only duration → new Instant |
.subtract(duration) |
Subtract time-only duration → new Instant |
.until(other, options?) |
Duration from this to other |
.since(other, options?) |
Duration since other to this |
.round(options) |
Round to given unit |
.equals(other) |
Equality check |
.toZonedDateTimeISO(timeZone) |
Convert to ZonedDateTime |
.toString() |
ISO string with Z suffix |
Temporal.PlainDate — Key Methods and Properties
| Member | Description |
|---|---|
Temporal.PlainDate.from(item) |
Create from string or object |
Temporal.PlainDate.compare(a, b) |
Compare two dates |
.year |
4-digit year |
.month |
Month number (1–12) |
.monthCode |
Month code e.g. “M03” |
.day |
Day of month (1–31) |
.dayOfWeek |
ISO day of week (1=Mon, 7=Sun) |
.dayOfYear |
Day number in year (1–366) |
.weekOfYear |
ISO week number |
.daysInMonth |
Days in current month |
.daysInYear |
Days in current year |
.inLeapYear |
true if leap year |
.calendarId |
Calendar system name |
.add(duration, options?) |
Add duration → new PlainDate |
.subtract(duration, options?) |
Subtract → new PlainDate |
.until(other, options?) |
Duration to another date |
.since(other, options?) |
Duration since another date |
.with(fields) |
Change specific fields → new date |
.equals(other) |
Equality check |
.toPlainDateTime(time?) |
Combine with a time |
.toZonedDateTime(tz) |
Attach timezone |
.toPlainYearMonth() |
Drop the day |
.toPlainMonthDay() |
Drop the year |
.toString() |
“YYYY-MM-DD” |
Temporal.PlainTime — Key Properties and Methods
| Member | Description |
|---|---|
.hour |
0–23 |
.minute |
0–59 |
.second |
0–59 |
.millisecond |
0–999 |
.microsecond |
0–999 |
.nanosecond |
0–999 |
.add(duration) |
Add duration → new PlainTime (wraps midnight) |
.subtract(duration) |
Subtract → new PlainTime |
.until(other) |
Duration to other time |
.since(other) |
Duration since other time |
.with(fields) |
Change specific fields |
.equals(other) |
Equality check |
.toPlainDateTime(date) |
Combine with a date |
.toString() |
“HH:MM:SS.sss…” |
Temporal.PlainDateTime — Key Properties
Has ALL properties of PlainDate + PlainTime, plus:
| Method | Description |
|---|---|
.with(fields) |
Change date or time fields |
.withPlainDate(date) |
Replace the date part |
.withPlainTime(time) |
Replace the time part |
.toPlainDate() |
Extract date part |
.toPlainTime() |
Extract time part |
.toZonedDateTime(tz) |
Attach timezone |
.toInstant(tz) |
Convert to Instant (needs TZ) |
Temporal.ZonedDateTime — Key Properties
Has ALL properties of PlainDateTime, plus:
| Property / Method | Description |
|---|---|
.timeZoneId |
IANA timezone name |
.offset |
UTC offset string e.g. “+05:30” |
.offsetNanoseconds |
UTC offset in nanoseconds |
.hoursInDay |
Hours in this day (23/24/25 for DST) |
.withTimeZone(tz) |
Convert to different timezone |
.withCalendar(cal) |
Convert to different calendar |
.toInstant() |
Convert to Temporal.Instant |
.toPlainDate() |
Drop time and timezone |
.toPlainTime() |
Drop date and timezone |
.toPlainDateTime() |
Drop timezone |
Temporal.Duration — Key Properties and Methods
| Member | Description |
|---|---|
.years |
Years component |
.months |
Months component |
.weeks |
Weeks component |
.days |
Days component |
.hours |
Hours component |
.minutes |
Minutes component |
.seconds |
Seconds component |
.milliseconds |
Milliseconds component |
.microseconds |
Microseconds component |
.nanoseconds |
Nanoseconds component |
.sign |
1 (positive), -1 (negative), 0 (zero) |
.blank |
true if all components are 0 |
.add(other, {relativeTo}) |
Add two durations |
.subtract(other, {relativeTo}) |
Subtract durations |
.with(fields) |
Replace specific components |
.negated() |
Flip sign |
.abs() |
Always positive |
.round(options) |
Round to a unit |
.total(options) |
Total in a single unit |
.toString() |
ISO 8601 duration string (P…) |
Complete Arithmetic Options Reference
When calling .add(), .subtract(), .until(), .since(), .round():
| Option | Values | Description |
|---|---|---|
largestUnit |
"year", "month", "week", "day", "hour", "minute", "second", etc. |
Largest unit in result |
smallestUnit |
Same list | Smallest unit (rounds below this) |
roundingMode |
"trunc", "ceil", "floor", "halfExpand", "halfEven" |
How to round |
overflow |
"constrain", "reject" |
Behaviour when day doesn’t exist |
relativeTo |
PlainDate or ZonedDateTime | Reference date for month/year conversions |
12. Applied Exercises
Phase 2 — Applied Exercises
Exercise 1 — Temporal Detective 🔍
Objective: Practice creating and reading every Temporal plain type.
Scenario: You are building a school records system using Temporal to store student data. All dates must be timezone-free (plain types).
Warm-up Micro-Demo:
const dob = Temporal.PlainDate.from("2005-08-12");
console.log("Year :", dob.year); // 2005
console.log("Month :", dob.month); // 8 (August — 1-indexed!)
console.log("Day :", dob.day); // 12
console.log("Day of wk :", dob.dayOfWeek); // 5 (Friday in ISO: Mon=1, Sun=7)
console.log("Leap year :", dob.inLeapYear); // false
▶ Expected Output:
Year : 2005
Month : 8
Day : 12
Day of wk : 5
Leap year : false
Task A — Build a Student Record
const student = {
name: "Amara Diallo",
dob: Temporal.PlainDate.from("2005-08-12"),
enrollmentDate: Temporal.PlainDate.from("2020-09-01"),
graduationDate: Temporal.PlainDate.from("2025-06-30"),
classStartTime: Temporal.PlainTime.from("08:30:00"),
lunchTime: Temporal.PlainTime.from("12:00:00"),
};
// Calculate:
// 1. Student age today
const today = Temporal.Now.plainDateISO();
let age = today.year - student.dob.year;
const birthdayThisYear = student.dob.with({ year: today.year });
if (Temporal.PlainDate.compare(today, birthdayThisYear) < 0) age--;
console.log("Age:", age);
// 2. Days until graduation
const daysLeft = today.until(student.graduationDate, { largestUnit: "day" });
console.log("Days until graduation:", daysLeft.days);
// 3. Total enrollment duration in years and months
const enrollmentDuration = student.enrollmentDate.until(student.graduationDate, {
largestUnit: "year"
});
console.log("Enrollment:", enrollmentDuration.years, "years,", enrollmentDuration.months, "months");
// 4. Hours between class start and lunch
const morningLength = student.classStartTime.until(student.lunchTime);
console.log("Morning session:", morningLength.hours, "hours", morningLength.minutes, "min");
Expected Output (sample based on current date ~March 2026):
Age: 20
Days until graduation: 121 (approximate — depends on run date)
Enrollment: 4 years, 9 months
Morning session: 3 hours 30 min
Self-check questions:
- Why does the age calculation need a birthday-this-year check?
- Why use
PlainDateinstead ofZonedDateTimefor school dates? - What does
dayOfWeek: 5mean in Temporal’s ISO convention?
Exercise 2 — Global Meeting Coordinator 🌍
Objective: Practice ZonedDateTime, withTimeZone(), and Temporal.Now.
Scenario: You work at a company with teams in London, New York, Lagos, and Tokyo. Schedule a 2 PM London meeting and show each team what time that is locally.
Warm-up Micro-Demo:
const londonMeeting = Temporal.ZonedDateTime.from("2024-06-15T14:00:00[Europe/London]");
const nyTime = londonMeeting.withTimeZone("America/New_York");
console.log(`London: ${londonMeeting.hour}:00 → New York: ${nyTime.hour}:00`);
▶ Expected Output: London: 14:00 → New York: 10:00
Task A — Convert Meeting to All Timezones
const meetingLondon = Temporal.ZonedDateTime.from("2024-06-20T14:00:00[Europe/London]");
const timezones = [
{ city: "London", tz: "Europe/London" },
{ city: "New York", tz: "America/New_York" },
{ city: "Lagos", tz: "Africa/Lagos" },
{ city: "Tokyo", tz: "Asia/Tokyo" },
{ city: "Sydney", tz: "Australia/Sydney" },
];
console.log("Meeting Scheduler — June 20, 2024\n");
timezones.forEach(({ city, tz }) => {
const local = meetingLondon.withTimeZone(tz);
const h = String(local.hour).padStart(2, "0");
const m = String(local.minute).padStart(2, "0");
console.log(`${city.padEnd(10)} : ${h}:${m} (UTC${local.offset})`);
});
▶ Expected Output:
Meeting Scheduler — June 20, 2024
London : 14:00 (UTC+01:00)
New York : 09:00 (UTC-04:00)
Lagos : 15:00 (UTC+01:00)
Tokyo : 22:00 (UTC+09:00)
Sydney : 23:00 (UTC+10:00)
Task B — Find Best Meeting Time
// Write a function that checks if a proposed London time is within
// working hours (8 AM - 6 PM) for all given cities.
function isWorkingHour(zdt) {
return zdt.hour >= 8 && zdt.hour < 18;
}
function findBestMeetingTime(cities) {
// Try each hour from 8 AM to 5 PM London time
for (let h = 8; h <= 17; h++) {
const londonTime = Temporal.ZonedDateTime.from({
year: 2024, month: 6, day: 20,
hour: h, minute: 0, second: 0,
timeZone: "Europe/London"
});
const allWorking = cities.every(({ tz }) =>
isWorkingHour(londonTime.withTimeZone(tz))
);
if (allWorking) {
console.log(`Best time: ${h}:00 London (all cities in working hours)`);
return h;
}
}
console.log("No single hour works for all cities!");
return null;
}
const cities = [
{ city: "London", tz: "Europe/London" },
{ city: "New York", tz: "America/New_York" },
{ city: "Lagos", tz: "Africa/Lagos" },
];
findBestMeetingTime(cities);
Self-check questions:
- Why might
hoursInDayreturn 23 on some days in London? - What is the difference between an offset (
+01:00) and a timezone ID (Europe/London)? - If you store a meeting time in a database, should you store it as a
ZonedDateTimestring or anInstantepoch? Why?
Exercise 3 — Duration Calculator ⏱️
Objective: Practice creating, reading, and using Temporal.Duration objects.
Scenario: You are building a subscription management system that tracks trial periods, billing cycles, and contract lengths.
Warm-up Micro-Demo:
const trial = Temporal.Duration.from({ days: 14 });
const start = Temporal.PlainDate.from("2024-03-15");
const end = start.add(trial);
console.log("Trial ends:", end.toString()); // "2024-03-29"
▶ Expected Output: Trial ends: 2024-03-29
Task A — Build a Subscription Timeline
const durations = {
trial: Temporal.Duration.from({ days: 14 }),
monthly: Temporal.Duration.from({ months: 1 }),
quarterly: Temporal.Duration.from({ months: 3 }),
annual: Temporal.Duration.from({ years: 1 }),
noticePeriod: Temporal.Duration.from({ days: 30 }),
};
const signupDate = Temporal.PlainDate.from("2024-03-15");
const trialEnd = signupDate.add(durations.trial);
const firstBill = trialEnd.add({ days: 1 }); // billing starts day after trial
const secondBill = firstBill.add(durations.monthly);
const contractEnd = signupDate.add(durations.annual);
const cancelBy = contractEnd.subtract(durations.noticePeriod);
// How long from signup to contract end?
const totalLength = signupDate.until(contractEnd, { largestUnit: "month" });
console.log("Signup :", signupDate.toString());
console.log("Trial ends :", trialEnd.toString());
console.log("First bill :", firstBill.toString());
console.log("Second bill :", secondBill.toString());
console.log("Cancel by :", cancelBy.toString());
console.log("Contract end:", contractEnd.toString());
console.log(`Contract length: ${totalLength.months} months`);
▶ Expected Output:
Signup : 2024-03-15
Trial ends : 2024-03-29
First bill : 2024-03-30
Second bill : 2024-04-30
Cancel by : 2025-02-13
Contract end: 2025-03-15
Contract length: 12 months
Self-check questions:
- Why is Duration immutable? What benefit does that give to the
durationsobject? - What does
duration.signreturn forTemporal.Duration.from({ days: -5 })? - Why does
total()require arelativeTooption for months?
13. Mini Project — Global Meeting Scheduler
Phase 3 — Project Simulation
Real-world scenario: You are a developer at a multinational consulting firm. Build a global meeting scheduler that:
- Stores meetings as
ZonedDateTime(with full timezone support) - Shows each meeting in multiple timezones
- Calculates countdown until each meeting
- Uses
Durationto compute meeting lengths - Finds and highlights the next upcoming meeting
- Generates a formatted agenda report
🔵 Stage 1 — Setup: Store Meetings and Display Basics
Goal: Create ZonedDateTime objects for each meeting and display their basic info.
Simple stage preview:
const meeting = Temporal.ZonedDateTime.from("2026-04-10T14:00:00[Europe/London]");
console.log(`Meeting: ${meeting.year}-${String(meeting.month).padStart(2,"0")}-${String(meeting.day).padStart(2,"0")} at ${meeting.hour}:00 (${meeting.timeZoneId})`);
▶ Expected Output: Meeting: 2026-04-10 at 14:00 (Europe/London)
Stage 1 Full Code:
"use strict";
// Days and months (ISO week: Mon=1, Sun=7)
const ISO_DAYS = ["", "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"];
const MONTH_NAMES = ["","January","February","March","April","May","June",
"July","August","September","October","November","December"];
// All meetings stored as ZonedDateTime in the host city's timezone
const meetings = [
{
title: "Q2 Strategy Kickoff",
zdt: Temporal.ZonedDateTime.from("2026-04-10T09:00:00[America/New_York]"),
duration: Temporal.Duration.from({ hours: 2 }),
host: "New York"
},
{
title: "Product Roadmap Review",
zdt: Temporal.ZonedDateTime.from("2026-05-15T14:00:00[Europe/London]"),
duration: Temporal.Duration.from({ hours: 1, minutes: 30 }),
host: "London"
},
{
title: "APAC Partnership Summit",
zdt: Temporal.ZonedDateTime.from("2026-06-03T10:00:00[Asia/Tokyo]"),
duration: Temporal.Duration.from({ hours: 3 }),
host: "Tokyo"
},
{
title: "Annual Finance Review",
zdt: Temporal.ZonedDateTime.from("2026-07-20T08:00:00[Africa/Lagos]"),
duration: Temporal.Duration.from({ hours: 4 }),
host: "Lagos"
},
];
function displayMeetingBasic(meeting) {
const z = meeting.zdt;
const day = ISO_DAYS[z.dayOfWeek];
const mon = MONTH_NAMES[z.month];
const hh = String(z.hour).padStart(2, "0");
const mm = String(z.minute).padStart(2, "0");
const end = z.add(meeting.duration);
const endH = String(end.hour).padStart(2, "0");
const endM = String(end.minute).padStart(2, "0");
console.log(`\n📅 ${meeting.title}`);
console.log(` Host city : ${meeting.host}`);
console.log(` When : ${day}, ${mon} ${z.day}, ${z.year}`);
console.log(` Time : ${hh}:${mm} – ${endH}:${endM} (${z.timeZoneId})`);
console.log(` Duration : ${meeting.duration.hours}h ${meeting.duration.minutes || 0}m`);
}
console.log("=".repeat(50));
console.log(" GLOBAL MEETINGS — STAGE 1 OVERVIEW");
console.log("=".repeat(50));
meetings.forEach(displayMeetingBasic);
▶ Expected Output:
==================================================
GLOBAL MEETINGS — STAGE 1 OVERVIEW
==================================================
📅 Q2 Strategy Kickoff
Host city : New York
When : Friday, April 10, 2026
Time : 09:00 – 11:00 (America/New_York)
Duration : 2h 0m
📅 Product Roadmap Review
Host city : London
When : Friday, May 15, 2026
Time : 14:00 – 15:30 (Europe/London)
Duration : 1h 30m
...
Reflection: Why do we store meetings in the host city’s timezone rather than converting everything to UTC first?
🟢 Stage 2 — Multi-Timezone Display and Countdown
Goal: Show each meeting in 4 timezones simultaneously, and calculate a live countdown.
Simple stage preview:
const m = Temporal.ZonedDateTime.from("2026-04-10T09:00:00[America/New_York]");
const london = m.withTimeZone("Europe/London");
console.log(`New York: ${m.hour}:00 → London: ${london.hour}:00`);
▶ Expected Output: New York: 9:00 → London: 14:00
Stage 2 Full Code:
const displayTimezones = [
{ label: "New York", tz: "America/New_York" },
{ label: "London", tz: "Europe/London" },
{ label: "Lagos", tz: "Africa/Lagos" },
{ label: "Tokyo", tz: "Asia/Tokyo" },
];
function getCountdown(zdt) {
const now = Temporal.Now.zonedDateTimeISO(zdt.timeZoneId);
const nowInst = now.toInstant();
const evtInst = zdt.toInstant();
const cmp = Temporal.Instant.compare(nowInst, evtInst);
if (cmp > 0) return "✅ Already happened";
if (cmp === 0) return "🎉 HAPPENING NOW!";
const diff = nowInst.until(evtInst, { largestUnit: "hour" });
const days = Math.floor(diff.hours / 24);
const hours = diff.hours % 24;
const mins = diff.minutes;
if (days > 0) return `📆 In ${days} day(s) ${hours}h ${mins}m`;
if (hours > 0) return `⚡ In ${hours}h ${mins}m`;
return `🔔 In ${mins} minute(s)!`;
}
function displayMeetingFull(meeting) {
const z = meeting.zdt;
console.log("\n" + "-".repeat(52));
console.log(` 🌐 ${meeting.title.toUpperCase()}`);
console.log(` Countdown : ${getCountdown(z)}`);
console.log(" Local times:");
displayTimezones.forEach(({ label, tz }) => {
const local = z.withTimeZone(tz);
const hh = String(local.hour).padStart(2, "0");
const mm = String(local.minute).padStart(2, "0");
const offset = local.offset;
const marker = tz === z.timeZoneId ? " ← host" : "";
console.log(` ${label.padEnd(10)} : ${hh}:${mm} (UTC${offset})${marker}`);
});
}
console.log("\n" + "=".repeat(52));
console.log(" GLOBAL MEETING SCHEDULE — FULL VIEW");
console.log("=".repeat(52));
meetings.forEach(displayMeetingFull);
▶ Expected Output (sample):
====================================================
GLOBAL MEETING SCHEDULE — FULL VIEW
====================================================
----------------------------------------------------
🌐 Q2 STRATEGY KICKOFF
Countdown : 📆 In 30 day(s) 3h 15m
Local times:
New York : 09:00 (UTC-04:00) ← host
London : 14:00 (UTC+01:00)
Lagos : 14:00 (UTC+01:00)
Tokyo : 22:00 (UTC+09:00)
----------------------------------------------------
🌐 PRODUCT ROADMAP REVIEW
Countdown : 📆 In 65 day(s) 6h 45m
...
Reflection: Why did we convert both times to Instant before comparing them for the countdown? What would go wrong if we compared ZonedDateTimes directly?
🟠 Stage 3 — Full Agenda Report
Goal: Generate a polished agenda with meeting durations, timezone conversions, and a “next meeting” highlight.
Stage 3 Full Code:
function findNextMeeting(meetings) {
const nowInst = Temporal.Now.instant();
return meetings
.filter(m => Temporal.Instant.compare(nowInst, m.zdt.toInstant()) < 0)
.sort((a, b) => Temporal.Instant.compare(a.zdt.toInstant(), b.zdt.toInstant()))[0];
}
function printFullReport(meetings) {
const now = Temporal.Now.zonedDateTimeISO();
const genMonth = MONTH_NAMES[now.month];
const next = findNextMeeting(meetings);
console.log("\n" + "=".repeat(58));
console.log(" GLOBAL MEETING MASTER AGENDA");
console.log(` Generated: ${ISO_DAYS[now.dayOfWeek]}, ${genMonth} ${now.day}, ${now.year}`);
console.log("=".repeat(58));
if (next) {
console.log(`\n ⭐ NEXT MEETING: ${next.title}`);
const untilNext = Temporal.Now.instant().until(next.zdt.toInstant(), { largestUnit: "hour" });
const d = Math.floor(untilNext.hours / 24);
const h = untilNext.hours % 24;
console.log(` Starts in: ${d} day(s) and ${h} hour(s)\n`);
}
meetings.forEach(meeting => {
const z = meeting.zdt;
const endZdt = z.add(meeting.duration);
const weekday = ISO_DAYS[z.dayOfWeek];
const month = MONTH_NAMES[z.month];
const isNext = next && next.title === meeting.title;
console.log("\n" + (isNext ? "★" : "─").repeat(58));
console.log(` ${isNext ? "⭐" : "📌"} ${meeting.title}`);
console.log(` Date : ${weekday}, ${month} ${z.day}, ${z.year}`);
console.log(` Duration : ${meeting.duration.hours}h ${meeting.duration.minutes || 0}m`);
console.log(" Times :");
displayTimezones.forEach(({ label, tz }) => {
const local = z.withTimeZone(tz);
const endLoc = endZdt.withTimeZone(tz);
const start = `${String(local.hour).padStart(2,"0")}:${String(local.minute).padStart(2,"0")}`;
const end = `${String(endLoc.hour).padStart(2,"0")}:${String(endLoc.minute).padStart(2,"0")}`;
const host = tz === z.timeZoneId ? " ← host city" : "";
console.log(` ${label.padEnd(10)} : ${start}–${end}${host}`);
});
console.log(` Countdown: ${getCountdown(z)}`);
});
console.log("\n" + "=".repeat(58));
const upcoming = meetings.filter(m => Temporal.Instant.compare(Temporal.Now.instant(), m.zdt.toInstant()) < 0);
console.log(` Total: ${meetings.length} | Upcoming: ${upcoming.length} | Past: ${meetings.length - upcoming.length}`);
console.log("=".repeat(58) + "\n");
}
printFullReport(meetings);
▶ Expected Output (sample — dates depend on today):
==========================================================
GLOBAL MEETING MASTER AGENDA
Generated: Wednesday, March 11, 2026
==========================================================
⭐ NEXT MEETING: Q2 Strategy Kickoff
Starts in: 30 day(s) and 3 hour(s)
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
⭐ Q2 Strategy Kickoff
Date : Friday, April 10, 2026
Duration : 2h 0m
Times :
New York : 09:00–11:00 ← host city
London : 14:00–16:00
Lagos : 14:00–16:00
Tokyo : 22:00–00:00
Countdown: 📆 In 30 day(s) 3h 0m
──────────────────────────────────────────────────────────
📌 Annual Finance Review
Date : Monday, July 20, 2026
...
==========================================================
Total: 4 | Upcoming: 4 | Past: 0
==========================================================
Reflection questions:
- Why can we use
Instant.compare()for sorting meetings but notZonedDateTime.compare()directly? - What would
hoursInDayreturn for London on the day clocks spring forward? How would this affect a meeting scheduled across that boundary? - How would you change this system to support meetings with an uncertain duration (e.g., “approximately 2 hours”)?
- How would you persist these meetings to a database and reload them?
Optional advanced features:
- Add a
PlainMonthDayrecurring annual meeting (e.g. Annual General Meeting always on the 1st Monday of March) - Add conflict detection — find two meetings that overlap for a given city
- Show a participant list per meeting and verify each person’s local time is within their working hours
- Add
Duration.round()to display approximate meeting durations (“about 2 hours”)
14. Completion Checklist
- ✅ I understand why Temporal was created — the 7 problems with the old
Dateobject. - ✅ I know the five design principles of Temporal: immutable, 1-indexed months, explicit timezones, nanosecond precision, ISO 8601 strings.
- ✅ I can explain when to use each Temporal type:
Instant,PlainDate,PlainTime,PlainDateTime,ZonedDateTime,Duration,PlainYearMonth,PlainMonthDay. - ✅ I can use all six
Temporal.Nowmethods to get the current moment in any format. - ✅ I can create and read a
Temporal.Instantand convert it to aZonedDateTimewithtoZonedDateTimeISO(). - ✅ I know that
epochMicrosecondsandepochNanosecondsareBigIntvalues (withnsuffix). - ✅ I can create, read, and modify
Temporal.PlainDateusing.with(),.add(),.subtract(), and.until(). - ✅ I know that
dayOfWeekin Temporal follows ISO 8601 (Monday=1, Sunday=7) — NOT the oldgetDay()convention. - ✅ I can use
PlainTimeand understand that adding time past midnight wraps around correctly. - ✅ I understand
PlainYearMonth(credit card expiry, billing months) andPlainMonthDay(recurring annual events). - ✅ I can create and convert
Temporal.PlainDateTimeand understand when to use it vsZonedDateTime. - ✅ I can create a
Temporal.ZonedDateTimefrom a string with a timezone in brackets[Timezone/Name]. - ✅ I can convert a meeting across timezones using
.withTimeZone(). - ✅ I understand that
hoursInDaycan return 23 or 25 on DST transition days, and thatZonedDateTimehandles DST automatically. - ✅ I can create and use
Temporal.Durationobjects, read their properties, and understand the ISO duration string format (P1Y6M15DT2H30M). - ✅ I understand that all arithmetic operations (
add,subtract,with) return new objects — Temporal is immutable. - ✅ I know why many Duration operations require a
relativeTodate (because months have different lengths). - ✅ I can use
compare()for sorting andequals()for equality checks on all Temporal types. - ✅ I completed all three exercises and the full three-stage mini-project.
📌 One-Sentence Summary of Each Topic
Why Temporal: The old Date object has 7 unfixable problems — mutable state, 0-indexed months, no timezone support, millisecond-only precision, inconsistent parsing, no calendar systems, and no type specialisation — and Temporal was designed from scratch to fix all of them.
Overview: Temporal is a family of immutable types (Instant, PlainDate, PlainTime, PlainDateTime, ZonedDateTime, Duration, PlainYearMonth, PlainMonthDay), each designed for a specific date/time use case, all using 1-indexed months and ISO 8601 strings.
Temporal vs Date: Temporal is immutable (no setters), uses human-friendly 1-indexed months, has nanosecond precision, built-in timezone handling, consistent string parsing, and separate types for different use cases — solving every major pain point of the old Date object.
Temporal.Now: Temporal.Now is a namespace of static methods that returns the current moment in any form — instant() for UTC timestamps, zonedDateTimeISO() for local date+time+timezone, plainDateISO() for today’s date only, plainTimeISO() for the time of day, and timeZoneId() for the system timezone name.
Temporal.Instant: A Temporal.Instant is a precise UTC point in time stored in nanoseconds since the Unix Epoch — with no timezone, no calendar, just a raw universal timestamp convertible to any timezone using toZonedDateTimeISO().
Plain Types: PlainDate, PlainTime, PlainYearMonth, and PlainMonthDay represent calendar values without any timezone — use them when the time and place are either implicit, irrelevant, or when you’re working with recurring dates like birthdays and credit card expiries.
PlainDateTime: Temporal.PlainDateTime combines a date and time without a timezone — use it for local timestamps where timezone is already understood or irrelevant, and call .toZonedDateTime(timezone) to make it globally unambiguous.
ZonedDateTime: Temporal.ZonedDateTime is the most complete type — storing a date, time, AND timezone name — enabling correct timezone conversions with withTimeZone() and automatically handling daylight saving time transitions.
Duration: Temporal.Duration represents a length of time (not a point) using up to 10 components from years to nanoseconds, expressed in ISO 8601 format (P1Y6M15DT2H30M), and requires a relativeTo date for any operation involving months or years.
Arithmetic: All Temporal arithmetic (.add(), .subtract(), .until(), .since()) returns new immutable objects, supports overflow control with "constrain" or "reject" options, and handles DST automatically in ZonedDateTime — making it safe, correct, and predictable.
Reference: The complete Temporal API provides 9 types, 6 Temporal.Now methods, dozens of immutable read methods per type, and consistent arithmetic and comparison methods across all types — with both ISO calendar and alternative calendar system support.
📘 Built from W3Schools.com —
js_temporal|js_temporal_intro|js_temporal_vs_date|js_temporal_now|js_temporal_instant|js_temporal_plain|js_temporal_plaindatetime|js_temporal_zoneddatetime|js_temporal_duration|js_temporal_arithmetic|js_temporal_referenceFramework: Understand → Practice → Create