The Problem
If you've ever tried to do anything nontrivial with JavaScript's Date object, you know the pain:
// What timezone is this?
const date = new Date('2026-03-12T19:00:00Z');
// Parsing is inconsistent across browsers
new Date('2026-03-12');
new Date('March 12, 2026');
// Basic operations are counterintuitive
date.setDate(date.getDate() + 5); // Mutates the original!
The Date object was designed in 1995 for a very different era. It's immutable-challenged, timezone-agnostic, and full of gotchas that cause bugs in production daily.
Enter Temporal API — now Stage 4 and part of ES2026. It's the complete overhaul JavaScript developers have been waiting for.
The Solution: Temporal API
The Temporal API provides modern, immutable, and fully timezone-aware date/time handling.
Core Types
| Type | Use Case |
|---|---|
Temporal.Instant |
A point in time (like a Unix timestamp) |
Temporal.PlainDate |
A date without time |
Temporal.PlainTime |
A time without date |
Temporal.PlainDateTime |
A date and time without timezone |
Temporal.ZonedDateTime |
A date/time in a specific timezone |
Temporal.Duration |
A length of time |
Code Examples
1. Creating Dates
// OLD: Date object
const birthday = new Date('1990-05-15');
// NEW: Temporal PlainDate
const birthday = Temporal.PlainDate.from('1990-05-15');
console.log(birthday.year); // 1990
console.log(birthday.month); // 5
console.log(birthday.day); // 15
console.log(birthday.dayOfWeek); // 3 (Tuesday)
2. Timezones Made Simple
// ZonedDateTime - explicit and clear
const nowInBerlin = Temporal.Now.zonedDateTimeISO('Europe/Berlin');
console.log(nowInBerlin.toString());
// 2026-03-12T19:00:00+01:00[Europe/Berlin]
const nowInTokyo = Temporal.Now.zonedDateTimeISO('Asia/Tokyo');
// Automatically converts
3. Immutable Operations
// OLD: Date mutates!
const date = new Date('2026-03-12');
date.setDate(date.getDate() + 7); // Original is lost!
// NEW: Temporal returns new objects
const date = Temporal.PlainDate.from('2026-03-12');
const nextWeek = date.add({ days: 7 });
console.log(date.day); // 12 (unchanged!)
console.log(nextWeek.day); // 19
4. Duration and Arithmetic
const start = Temporal.PlainDateTime.from('2026-03-01T09:00:00');
const end = Temporal.PlainDateTime.from('2026-03-12T17:30:00');
const duration = start.until(end);
console.log(duration.toString()); // P11DT8H30M
console.log(duration.days); // 11
console.log(duration.total({ unit: 'hour' })); // 275.5
5. Parsing and Formatting
const parsed = Temporal.PlainDateTime.from('2026-03-12 19:00:00');
// Formatting
const date = Temporal.PlainDateTime.from('2026-03-12T14:30:00');
console.log(date.toLocaleString('en-US', { dateStyle: 'full' }));
// "Thursday, March 12, 2026"
console.log(date.toLocaleString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric'
}));
// "12. März 2026"
6. Business Logic Examples
// Get next Friday at 3pm Berlin time
function getNextFriday3pm() {
const now = Temporal.Now.zonedDateTimeISO('Europe/Berlin');
const daysUntilFriday = (5 - now.dayOfWeek + 7) % 7 || 7;
return now
.add({ days: daysUntilFriday })
.with({ hour: 15, minute: 0, second: 0, millisecond: 0 });
}
// Check if a date is a weekend
function isWeekend(date) {
const d = Temporal.PlainDate.from(date);
return d.dayOfWeek === 6 || d.dayOfWeek === 7;
}
Migration Guide
Step 1: Add the Polyfill
npm install @js-temporal/polyfill
import '@js-temporal/polyfill';
Step 2: Gradual Migration
const Temporal = globalThis.Temporal || require('@js-temporal/polyfill').Temporal;
Step 3: Common Conversion Patterns
// Date → PlainDate
const plainDate = Temporal.PlainDate.from(dateObject.toISOString().split('T')[0]);
// Date → ZonedDateTime
const zoned = Temporal.ZonedDateTime.from(dateObject.toISOString());
// Get current time
const now = Temporal.Now.zonedDateTimeISO();
Browser Compatibility
As of March 2026:
- Chrome/Edge: Full support (v128+)
- Firefox: Full support (v135+)
- Safari: Full support (v18+)
- Node.js: Full support (v24+)
For older browsers, the polyfill provides complete functionality.
Gotchas and Caveats
1. Plain vs Zoned - Choose Correctly
// WRONG: Mixing types
const plain = Temporal.PlainDateTime.from('2026-03-12T15:00:00');
const zoned = Temporal.Now.zonedDateTimeISO();
plain.with(zoned); // TypeError!
// CORRECT: Keep types consistent
const asZoned = plain.toZonedDateTime('Europe/Berlin');
2. Equality Checks
const a = Temporal.PlainDate.from('2026-03-12');
const b = Temporal.PlainDate.from('2026-03-12');
console.log(a === b); // false (different objects)
console.log(a.equals(b)); // true (same value)
3. Not a Drop-in Replacement
// You CANNOT do:
const date = new Temporal.PlainDate('2026-03-12'); // TypeError!
// Must use .from():
const date = Temporal.PlainDate.from('2026-03-12'); // Works
When to Still Use Date
- Interfacing with legacy APIs that expect Date objects
- Storage in old databases that only support Date
- Simple timestamps where you just need
.now()and.toISOString() - When working with libraries that haven't updated yet
Summary
The Temporal API finally solves 30+ years of JavaScript date handling pain:
- ✅ Immutable - No more accidental mutations
- ✅ Timezone-aware - Built-in support for IANA timezones
- ✅ Type-safe - PlainDate vs PlainTime vs ZonedDateTime
- ✅ Intuitive - Method names make sense
- ✅ Standard - Part of JavaScript (ES2026), not a library
// Your new go-to for dates
const now = Temporal.Now.zonedDateTimeISO();
const date = Temporal.PlainDate.from('2026-03-12');
const future = date.add({ days: 30 });
// That's it. That's the API.
Migration is straightforward with the polyfill, and you can migrate incrementally. The old Date object isn't going anywhere, but you no longer have to live in fear of it.
Start using Temporal today — your future self will thank you.
Top comments (0)