Table of Contents
- What is the Temporal API?
- Core Temporal Objects
- Temporal.Instant: A Global Moment
- Temporal.ZonedDateTime: Time in a Time Zone
- Temporal.DateTime: Date and Time (No Time Zone)
- Temporal.Date: Date-Only Values
- Temporal.Time: Time-Only Values
- Temporal.Duration: Lengths of Time
- Temporal.TimeZone: Time Zone Logic
- Temporal.Calendar: Non-Gregorian Calendars
- Parsing and Formatting
- Manipulating Dates and Times
- Comparing Temporal Objects
- Migrating from
Dateto Temporal - Advanced Use Cases
- Browser Support and Polyfills
- Reference
What is the Temporal API?
The Temporal API is a TC39 proposal (currently at Stage 3) aimed at overhauling JavaScript’s date-time handling. Its design goals include:
- Immutability: All Temporal objects are immutable, preventing accidental side effects.
- Clarity: Distinct types for different time concepts (e.g.,
ZonedDateTimefor time-zone-aware times,Datefor date-only values). - Precision: Nanosecond-level accuracy (vs.
Date’s millisecond limit). - Robustness: Consistent parsing, native time zone support, and non-Gregorian calendar compatibility.
Core Temporal Objects
Temporal introduces a suite of specialized objects to model different aspects of time. Let’s explore the most commonly used ones.
Temporal.Instant: A Global Moment
An Instant represents a specific moment in time (e.g., “July 20, 1969, 20:17:40 UTC”), independent of time zones or calendars. It’s analogous to a Unix timestamp but with nanosecond precision.
Key Features:
- Immutable.
- Represents time as the number of nanoseconds since the Unix epoch (January 1, 1970, UTC).
Creating an Instant
// From an ISO 8601 string (UTC)
const moonLanding = Temporal.Instant.from('1969-07-20T20:17:40Z');
console.log(moonLanding.toString()); // "1969-07-20T20:17:40Z"
// From epoch milliseconds (e.g., Date.now())
const nowMs = Date.now(); // 1695000000000
const nowInstant = Temporal.Instant.fromEpochMilliseconds(nowMs);
// From epoch nanoseconds (for precision)
const preciseInstant = Temporal.Instant.fromEpochNanoseconds(1695000000000000000n);
Common Operations
Convert an Instant to a time-zone-specific ZonedDateTime:
const nycTimeZone = Temporal.TimeZone.from('America/New_York');
const moonLandingInNYC = moonLanding.toZonedDateTimeISO(nycTimeZone);
console.log(moonLandingInNYC.toString()); // "1969-07-20T16:17:40-04:00[America/New_York]"
Temporal.ZonedDateTime: Time in a Time Zone
A ZonedDateTime combines an Instant, a TimeZone, and a Calendar to represent a date and time in a specific time zone. Use it when you need to work with wall-clock time in a geographic region (e.g., “3:00 PM in Tokyo”).
Creating a ZonedDateTime
// From an ISO string with time zone
const meeting = Temporal.ZonedDateTime.from('2023-10-05T15:00:00+09:00[Asia/Tokyo]');
// From components (year, month, day, etc.)
const birthday = Temporal.ZonedDateTime.from({
year: 1990,
month: 5,
day: 15,
hour: 9,
minute: 30,
timeZone: 'Europe/Paris', // IANA time zone identifier
calendar: 'iso8601' // Default: Gregorian calendar
});
console.log(birthday.toString()); // "1990-05-15T09:30:00+02:00[Europe/Paris]"
Manipulating ZonedDateTime
Add/subtract time using add() or subtract() (returns a new ZonedDateTime):
const nextMeeting = meeting.add({ weeks: 1, hours: 2 });
console.log(nextMeeting.toString()); // "2023-10-12T17:00:00+09:00[Asia/Tokyo]"
const prevMeeting = meeting.subtract(Temporal.Duration.from({ days: 3 }));
Temporal.DateTime: Date and Time (No Time Zone)
A DateTime represents a date and time without a time zone (e.g., “2023-12-25T09:00:00”). Use it for abstract “wall-clock” times (e.g., “9 AM on Christmas”) that aren’t tied to a specific geographic region.
Example
// From ISO string
const holiday = Temporal.DateTime.from('2023-12-25T09:00:00');
// From components
const deadline = Temporal.DateTime.from({
year: 2023,
month: 12,
day: 31,
hour: 23,
minute: 59
});
// Convert to ZonedDateTime by adding a time zone
const deadlineInLA = deadline.toZonedDateTimeISO('America/Los_Angeles');
Temporal.Date: Date-Only Values
A Date represents a calendar date (year, month, day) without time or time zone (e.g., “2023-10-05”). Use it for date-specific logic like “birthdays” or “deadlines” where time is irrelevant.
Example
const today = Temporal.Now.date(); // Current date (local time zone)
const eventDate = Temporal.Date.from('2024-02-29'); // Leap day
console.log(eventDate.isValid); // true (2024 is a leap year)
// Add 1 month (handles edge cases like January 31 → February 29/28)
const nextMonth = eventDate.add({ months: 1 });
console.log(nextMonth.toString()); // "2024-03-29" (not March 31!)
Temporal.Time: Time-Only Values
A Time represents a time of day (hours, minutes, seconds, nanoseconds) without a date or time zone (e.g., “08:30:00”). Use it for recurring events like “daily at 8:30 AM”.
Example
const alarm = Temporal.Time.from('08:30:00');
const openingTime = Temporal.Time.from({ hour: 9, minute: 0 });
// Check if a time is before another
console.log(alarm.isBefore(openingTime)); // true (8:30 AM < 9:00 AM)
Temporal.Duration: Lengths of Time
A Duration represents a length of time (e.g., “2 days and 3 hours”) rather than a specific moment. Use it to add/subtract time from temporal objects or calculate differences between dates.
Creating and Using Duration
// Create a duration
const tripLength = Temporal.Duration.from({ days: 5, hours: 6 });
// Add to a ZonedDateTime
const departure = Temporal.ZonedDateTime.from('2023-10-01T08:00:00[America/Chicago]');
const arrival = departure.add(tripLength);
console.log(arrival.toString()); // "2023-10-06T14:00:00-05:00[America/Chicago]"
// Calculate difference between two dates
const start = Temporal.Date.from('2023-01-01');
const end = Temporal.Date.from('2023-12-31');
const yearDuration = end.since(start);
console.log(yearDuration.toString()); // "P364D" (364 days in 2023, a non-leap year)
Temporal.TimeZone: Time Zone Logic
A TimeZone object provides tools to work with time zones, including converting between Instant and local time, handling daylight saving time (DST), and fetching time zone offsets.
Example
const berlinTZ = Temporal.TimeZone.from('Europe/Berlin');
// Get the current offset from UTC (in minutes)
const now = Temporal.Now.instant();
const offset = berlinTZ.getOffsetNanosecondsFor(now);
console.log(offset / 3.6e12); // 3600 (seconds → 1 hour ahead of UTC in DST)
// List all IANA time zones (for validation)
console.log(Temporal.TimeZone.from('invalid').toString()); // Throws error!
Temporal.Calendar: Non-Gregorian Calendars
Temporal natively supports non-Gregorian calendars (e.g., Islamic, Hebrew, Japanese) via the Calendar object. Use it to work with dates in cultural or regional contexts.
Example
// Japanese calendar ( Reiwa era)
const japaneseCalendar = Temporal.Calendar.from('japanese');
const todayInJapan = Temporal.Now.date({ calendar: japaneseCalendar });
console.log(todayInJapan.toString()); // "R05-10-05" (Reiwa 5, October 5th)
// Hebrew calendar (Jewish)
const hebrewDate = Temporal.Date.from({
year: 5784,
month: 1,
day: 1,
calendar: 'hebrew'
});
console.log(hebrewDate.toString()); // "5784-01-01[hebrew]"
Parsing and Formatting
Temporal eliminates Date’s inconsistent parsing by strictly adhering to ISO 8601 and supporting explicit format options.
Parsing Strings
Temporal parses ISO 8601 strings reliably and rejects ambiguous formats:
// Valid: ISO 8601 with time zone
Temporal.ZonedDateTime.from('2023-10-05T15:00:00+02:00[Europe/Berlin]');
// Valid: Date-only
Temporal.Date.from('2023-13-01'); // Throws! (Month 13 is invalid)
// Invalid: Ambiguous non-ISO format (rejected by default)
Temporal.Date.from('10/05/2023'); // Throws! Use options for custom formats.
For non-ISO formats, use Temporal.PlainDate.from() with a parser (via libraries like @js-temporal/parse for now, as native custom parsing is planned).
Formatting
Format Temporal objects using toLocaleString() for human-readable output:
const concert = Temporal.ZonedDateTime.from('2024-02-14T20:00:00[America/Mexico_City]');
// Format for Spanish (Mexico)
console.log(concert.toLocaleString('es-MX', {
dateStyle: 'full',
timeStyle: 'long'
}));
// "miércoles, 14 de febrero de 2024 20:00:00 hora estándar central"
Manipulating Dates and Times
Temporal makes adding/subtracting time intuitive and error-free, even with edge cases like leap years or DST transitions.
Adding/Subtracting Time
const event = Temporal.ZonedDateTime.from('2023-03-12T02:30:00[America/New_York]');
// Add 1 hour during DST transition (clocks spring forward 1 hour)
const adjustedEvent = event.add({ hours: 1 });
console.log(adjustedEvent.toString()); // "2023-03-12T04:30:00-04:00[America/New_York]"
// (3:30 AM is skipped; Temporal adjusts to 4:30 AM)
Comparing Temporal Objects
Temporal objects support direct comparison with compare(), isBefore(), isAfter(), and equals().
Example
const start = Temporal.DateTime.from('2023-10-05T09:00:00');
const end = Temporal.DateTime.from('2023-10-05T17:00:00');
const now = Temporal.Now.dateTime();
if (now.isBetween(start, end)) {
console.log("Business hours!");
}
Migrating from Date to Temporal
Replace common Date patterns with Temporal equivalents:
| Task | Date Approach | Temporal Approach |
|---|---|---|
| Current time (UTC) | new Date().toISOString() | Temporal.Now.instant().toString() |
| Current local date | new Date().toLocaleDateString() | Temporal.Now.date().toString() |
| Add 1 day | const d = new Date(); d.setDate(d.getDate() + 1); | Temporal.Now.date().add({ days: 1 }) |
| Parse ISO string | new Date('2023-10-05') | Temporal.Date.from('2023-10-05') |
Advanced Use Cases
Calculating Overlapping Time Intervals
Check if two events overlap:
const meeting1 = {
start: Temporal.ZonedDateTime.from('2023-10-05T10:00:00[UTC]'),
end: Temporal.ZonedDateTime.from('2023-10-05T11:00:00[UTC]')
};
const meeting2 = {
start: Temporal.ZonedDateTime.from('2023-10-05T10:30:00[UTC]'),
end: Temporal.ZonedDateTime.from('2023-10-05T12:00:00[UTC]')
};
const overlap = meeting1.start.isBefore(meeting2.end) &&
meeting2.start.isBefore(meeting1.end);
console.log(overlap); // true (meetings overlap)
Billing Cycles with Durations
Calculate the end of a 30-day billing cycle:
const billingStart = Temporal.Now.zonedDateTimeISO();
const billingEnd = billingStart.add(Temporal.Duration.from({ days: 30 }));
console.log(`Billing ends: ${billingEnd.toLocaleString()}`);
Browser Support and Polyfills
As of 2023, Temporal is a Stage 3 proposal and not yet implemented in browsers. To use it today:
- Polyfill: Install
@js-temporal/polyfill(maintained by the Temporal team):npm install @js-temporal/polyfill - Browser Support: Track progress on Can I Use.
Reference
- Official Temporal Proposal (TC39)
- Temporal Polyfill
- IANA Time Zone Database
- MDN Temporal Docs (upcoming)
With Temporal, JavaScript finally has a date-time API that prioritizes clarity, reliability, and developer experience. Say goodbye to Date-induced headaches and hello to precise, intuitive time handling! 🕒