This post was created with AI assistance and reviewed for accuracy before publishing.
Date is one of the oldest footguns in JavaScript. If you have shipped a bug because of timezone inconsistencies, lost an hour to daylight saving arithmetic, or cursed at a month that is mysteriously zero-indexed, you are not alone. The spec inherited Date from Java, and Java later admitted it was a mistake. We have been paying for that decision since 1995.
The Temporal API is the fix. It is a Stage 3 TC39 proposal that introduces a suite of new global objects designed to handle date and time correctly. Here is what actually changes and why it matters for day-to-day JavaScript work.
What Is Broken With Date()
The problems are not subtle. new Date() always captures the current moment in the local system timezone, but behaves inconsistently when you serialize or compare it across environments. Server and client have different timezones. Your CI runner has a different timezone again. A date that looks correct in development silently shifts in production.
Month indexing is the classic trap. January is 0, December is 11. Nobody remembers this reliably. You write new Date(2026, 6, 19) expecting July 19 and get July 19. Then your colleague writes new Date(2026, 7, 19) expecting August and everything is off by one. The fix is always the same: add a comment, forget to read it, repeat.
Date arithmetic is worse. How many days between two dates? You subtract milliseconds and divide. But you have to account for daylight saving transitions. Some days are 23 hours long. Some are 25. Plain millisecond math gives you wrong answers near DST boundaries.
Parsing strings is unreliable across engines. new Date('2026-07-19') is UTC in V8, local time in some Safari versions. new Date('July 19, 2026') is implementation-defined. The spec calls much of this behavior undefined, which means "works until it doesn't."
What Temporal Brings to the Table
Temporal ships a set of distinct types, each with a clear purpose:
Temporal.Instant represents a fixed point in time, like a Unix timestamp with nanosecond precision. No timezone. No calendar. Just a moment.
Temporal.ZonedDateTime is a moment plus a timezone. This is what you usually want when storing event times. It knows about DST and handles transitions correctly.
Temporal.PlainDate, Temporal.PlainTime, and Temporal.PlainDateTime handle calendar dates and clock times without a timezone. These are right for things like a user's birthday or a business's operating hours. The timezone is irrelevant or unknown.
Temporal.Duration represents a span of time. It handles months and years correctly, which milliseconds cannot do because months have different lengths.
Before and After: Real Code Comparison
Calculating the number of days between two dates with Date:
// Before: fragile, ignores DST
const start = new Date('2026-03-07');
const end = new Date('2026-03-10');
const diffMs = end - start;
const days = Math.round(diffMs / (1000 * 60 * 60 * 24));
// Breaks near DST transitions. Math.round is a workaround for "almost 2 days".
With Temporal:
// After: explicit, correct
const start = Temporal.PlainDate.from('2026-03-07');
const end = Temporal.PlainDate.from('2026-03-10');
const diff = start.until(end, { largestUnit: 'day' });
console.log(diff.days); // 3, always
Adding one month to a date:
// Before: this is actually wrong in most implementations
const d = new Date(2026, 0, 31); // Jan 31
d.setMonth(d.getMonth() + 1);
// Result: March 3 (February doesn't have 31 days, overflows)
// After: Temporal clamps correctly
const d = Temporal.PlainDate.from('2026-01-31');
const result = d.add({ months: 1 });
// Result: 2026-02-28. Correct.
Storing an event in a specific timezone:
// Before: you handle timezone offset manually or rely on a library
const event = new Date('2026-07-19T14:00:00-04:00');
// After: timezone is part of the value
const event = Temporal.ZonedDateTime.from('2026-07-19T14:00:00-04:00[America/New_York]');
console.log(event.timeZoneId); // 'America/New_York'
console.log(event.toInstant().toString()); // ISO instant string
Using Temporal in TypeScript
Temporal's types are well-designed for TypeScript. Each object is immutable, so all mutation methods return new instances rather than modifying in place. That is a breaking departure from Date which mutates via setMonth, setFullYear and so on.
import { Temporal } from '@js-temporal/polyfill';
function daysUntilDeadline(deadline: string): number {
const today = Temporal.Now.plainDateISO();
const target = Temporal.PlainDate.from(deadline);
const diff = today.until(target, { largestUnit: 'day' });
return diff.days;
}
console.log(daysUntilDeadline('2026-12-31')); // correct regardless of server timezone
The immutability removes an entire class of mutation bugs. When you pass a Temporal.PlainDate to a function, you know the caller's value cannot be changed under it.
The Polyfill Situation
Temporal is not in any browser by default yet. Firefox shipped it behind a flag. The polyfill from the TC39 team (@js-temporal/polyfill) is production-quality and the correct way to use it today.
npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';
The polyfill is thorough. It passes the full test suite. You can ship code using Temporal today in production if you include the polyfill. When native support lands, you remove the import and nothing changes.
For Node.js specifically, native support is expected to ship once the V8 implementation stabilizes. Watch the Node.js blog and the TC39 proposal page for the Stage 4 announcement.
Should You Migrate Existing Code?
Not all at once. The right move is to use Temporal for new code paths and migrate old ones opportunistically. The biggest wins come from:
Date arithmetic where you compute durations, add months, or diff across DST boundaries. The existing code is almost certainly wrong in edge cases. Temporal fixes it with less code.
Any feature dealing with user-specified timezones. Scheduling tools, calendar apps, reminder systems. ZonedDateTime was built exactly for this.
Avoid migrating low-stakes, UI-only formatting like "show today's date in a header." The cost does not justify the churn until native support lands and you can remove the polyfill dependency.
The Library Question
date-fns, dayjs, and Luxon solved the Date problems at the library level. They will not disappear overnight. Temporal does not make them wrong for existing codebases. What it does is remove the reason to reach for them in new code.
Once Temporal hits Stage 4 and ships natively, the dependency calculus shifts. A library that adds 12 KB to parse dates becomes hard to justify when the platform handles it correctly. Start evaluating now so you are not surprised by the migration path later.
JavaScript's relationship with time has been embarrassing for thirty years. Temporal is the correction. It is close enough to production that you should understand how it works before it lands in your runtime.
Top comments (0)