Chapter 1 of ?
js 10 min read

JavaScript Mastery — Chapter 20: Modern JS Temporal API — Date, Time & Math

Module 4: Modern JS Temporal API Chapter 20 27 min read

Modern JS Temporal API — Date, Time & Math

Master wall-clock calendar primitives (PlainDate, PlainTime, PlainDateTime), handle ISO durations with Temporal.Duration, perform pure mathematical arithmetic with .add() and .subtract(), measure exact intervals using .since() and .until(), and leverage the complete legacy-to-Temporal migration guide.

Temporal Arithmetic & Duration Pipeline

Unlike legacy JavaScript where date math relies on messy millisecond conversions, Temporal performs calendar-aware arithmetic that handles leap years, month length variations, and DST changes natively.

Temporal.PlainDate "2026-01-31" • Year: 2026 • Month: 1 (Jan) • Day: 31 (End of month) Immutable reference + Temporal.Duration { months: 1 } • ISO: "P1M" • Calendar-aware shift • Automatic day clamping Constrains to Feb 28! = New PlainDate Result "2026-02-28" • Non-overflowing month • Zero silent mutations • Original date untouched No March overflow trap!

1. Wall-Clock Calendar Primitives

Real-world applications frequently handle dates that have no meaningful timezone or clock hour attached (like birth dates, wedding anniversaries, or invoice due dates). Temporal provides dedicated primitive objects:

Temporal.PlainDate
A calendar date without hours or timezone.
2026-12-25
Ideal for: Birthdays, holidays, sprint deadlines.
Temporal.PlainTime
A wall-clock time without calendar date.
09:30:00.000
Ideal for: Store opening hours, daily recurring alarms.
Temporal.PlainDateTime
Calendar date + wall clock without timezone.
2026-05-15T14:30:00
Ideal for: Local appointments before venue timezone is known.

Instantiating Plain Primitives

// 1. PlainDate creation (1-indexed month!):
const birthday = Temporal.PlainDate.from('1998-04-12');
console.log(`Born on day ${birthday.day}, month ${birthday.month} (${birthday.dayOfWeek})`);

// 2. PlainTime creation:
const storeOpens = Temporal.PlainTime.from({ hour: 8, minute: 30 });
console.log('Store opens at:', storeOpens.toString()); // "08:30:00"

// 3. Merging Date and Time into a PlainDateTime:
const examDateTime = birthday.toPlainDateTime(storeOpens);
console.log('Exam scheduled:', examDateTime.toString());
Try it in Playground

2. Temporal.Duration & Pure Date Arithmetic

A Duration represents a physical length of time (e.g. 3 weeks, 2 days, and 45 minutes). Temporal provides robust math without millisecond calculations:

Adding & Subtracting Durations

const startDate = Temporal.PlainDate.from('2026-01-15');

// Adding 45 days and 2 months:
const futureDate = startDate.add({ months: 2, days: 45 });
console.log('Future Date:', futureDate.toString());

// Subtracting 1 week:
const oneWeekAgo = startDate.subtract({ weeks: 1 });
console.log('One week prior:', oneWeekAgo.toString());

// Leap-Year and month overflow handling:
const endOfJan = Temporal.PlainDate.from('2026-01-31');
const nextMonth = endOfJan.add({ months: 1 });
// Safely clamps to Feb 28 (2026 is not a leap year)!
console.log('Safe clamped date:', nextMonth.toString()); // 2026-02-28
Try it in Playground

Measuring Intervals: .since() and .until()

To compute the exact elapsed duration between two dates, use date1.until(date2) or date2.since(date1):

Calculating Elapsed Time Between Dates

const today = Temporal.PlainDate.from('2026-05-15');
const launch = Temporal.PlainDate.from('2026-12-25');

// How much time until launch?
const countdown = today.until(launch, { largestUnit: 'month' });
console.log(`Months: ${countdown.months}, Days: ${countdown.days}`);
// Output: Months: 7, Days: 10

// As total days:
const totalDays = today.until(launch, { largestUnit: 'day' }).days;
console.log(`Total days remaining: ${totalDays}`); // 224 days
Try it in Playground

Interactive Mini-Lab: Temporal Math & Interval Engine

Select a starting date, apply a duration shift, and watch the calendar engine calculate safe, non-overflowing results in real time:

Start Date & Operations
Computed Temporal Output
Generated Temporal Code:
startDate.add({ months: 1 })
Resulting PlainDate:
2026-02-28
Note: Clamped automatically from Jan 31 to Feb 28 to avoid month overflow.

3. Complete Legacy Date → Temporal Migration Guide

Here is the definitive reference for refactoring legacy Date patterns into modern Temporal in existing production projects:

Goal Legacy Date (Deprecated / Bug-Prone) Modern Temporal (Recommended)
Current Moment new Date() Temporal.Now.instant()
Current Date Only new Date() (stripping hours manually) Temporal.Now.plainDateISO()
Add 7 Days d.setDate(d.getDate() + 7) (Mutates in place!) date.add({ days: 7 }) (Pure immutable copy)
Time Difference (d2.getTime() - d1.getTime()) / 86400000 d1.until(d2, { largestUnit: 'day' }).days
Month Property d.getMonth() (0 = January! 11 = December!) d.month (1 = January, 12 = December)
Format in Timezone d.toLocaleString(..., { timeZone: 'Asia/Tokyo' }) instant.toZonedDateTimeISO('Asia/Tokyo').toLocaleString()

Hands-on Challenge: SaaS Subscription Renewal & Grace Period Engine

Coding Challenge

You are implementing the billing subscription logic for an enterprise SaaS platform. Given an initial signup date, calculate renewal deadlines, leap-safe anniversary dates, and whether an account is currently in its 14-day grace period:

Billing Engine Specs
  1. Accept an initial signup date string (e.g. "2024-02-29" leap day).
  2. Calculate the Next Annual Renewal Date using .add({ years: 1 }).
  3. Calculate the Grace Period Expiration by adding 14 days to the renewal date.
  4. Calculate total remaining days until expiration from a given check date using .until().
Production Subscription Engine Implementation
/**
 * Calculates SaaS renewal and grace period deadlines with Temporal precision.
 */
function calculateSubscriptionStatus(signupDateStr, checkDateStr) {
  const signupDate = Temporal.PlainDate.from(signupDateStr);
  const checkDate = Temporal.PlainDate.from(checkDateStr);

  // 1. Calculate next annual renewal (Handles leap year 2024-02-29 -> 2025-02-28 safely)
  const renewalDate = signupDate.add({ years: 1 });

  // 2. Grace period ends 14 days after renewal date
  const gracePeriodEnd = renewalDate.add({ days: 14 });

  // 3. Days until expiration from today
  const daysUntilGraceEnd = checkDate.until(gracePeriodEnd, { largestUnit: 'day' }).days;

  const isExpired = Temporal.PlainDate.compare(checkDate, gracePeriodEnd) > 0;
  const inGracePeriod = Temporal.PlainDate.compare(checkDate, renewalDate) >= 0 && !isExpired;

  return {
    signupDate: signupDate.toString(),
    renewalDate: renewalDate.toString(),
    gracePeriodEnd: gracePeriodEnd.toString(),
    daysRemaining: Math.max(0, daysUntilGraceEnd),
    inGracePeriod,
    isExpired
  };
}

// Verification on Leap Day Signup:
const result = calculateSubscriptionStatus('2024-02-29', '2025-03-05');
console.log(result);
// Output shows safe renewal on 2025-02-28 and grace period expiring 2025-03-14!
Key Benefit: In legacy Date, adding 1 year to February 29th notoriously spilled into March 1st. Temporal handles leap day clamping cleanly and deterministically.

Chapter 20 Knowledge Check

Validate your mastery of Temporal arithmetic, durations, and plain calendar primitives.

1. Which Temporal class is specifically designed to store a person's birthday (e.g. "1995-11-20") without hours or timezone?
Temporal.Instant
Temporal.PlainDate
Temporal.ZonedDateTime
Temporal.PlainTime
2. What happens if you add 1 month to January 31 on a non-leap year (e.g. PlainDate.from('2026-01-31').add({ months: 1 }))?
It overflows into March 3rd (like legacy Date).
It safely clamps the day to the last valid day of February (February 28, 2026).
It throws a RangeError exception.
It returns NaN.
3. Which method computes the duration elapsed from a starting date up to an ending date?
startDate.delta(endDate)
startDate.until(endDate)
startDate.minus(endDate)
Temporal.diff(startDate, endDate)
4. How do you sort an array of Temporal.PlainDate objects in ascending chronological order?
dates.toSorted(Temporal.PlainDate.compare)
dates.sort((a, b) => a - b)
dates.sortByDate()
Temporal.sort(dates)
5. What object represents a physical length of time (e.g. 5 days, 3 hours) in the Temporal API?
Temporal.Interval
Temporal.TimeSpan
Temporal.Duration
Temporal.Period
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up
—

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *
★ ★ ★ ★ ★