JavaScript’s Temporal API — a complete replacement for the broken Date object — reached TC39 Stage 4 and is now shipping in production browsers. Chrome 144 shipped it in January 2026, Firefox 139 in May 2025. Safari support remains in progress. After nine years of proposals, debates, and false starts, JavaScript finally has a date and time system that works correctly.
Why JavaScript’s Date Object Was Always Broken
The Date object was created in ten days in 1995. Brendan Eich, under orders to “make it like Java,” copied the API directly from java.util.Date.1 Within two years, Java had deprecated most of that class’s own methods — in version 1.1, released 1997 — because the design was already considered a mistake. JavaScript developers have been living with that mistake ever since.
The problems aren’t cosmetic. They cause real bugs:
Month numbering starts at zero. new Date(2026, 0, 1) is January 1st. new Date(2026, 12, 1) silently overflows into the next year. This trips up experienced developers constantly.
No timezone support beyond local and UTC. If you need to work with time in New York while your server runs in UTC and your user is in Tokyo, Date forces you to do the math yourself — manually, with no DST awareness.
DST arithmetic is unpredictable. Adding 24 hours to a date near a daylight saving transition doesn’t always land where you expect. The Date API has no concept of “the next calendar day at the same wall-clock time.”
Mutation causes subtle bugs. Date objects are mutable. Pass one to a function and that function can silently modify it. Every defensive JavaScript codebase has date-copying boilerplate for exactly this reason.
Parsing behavior is implementation-defined. The spec left date string parsing intentionally vague. new Date("2026-03-27") can return different results across browsers and environments depending on how the string is interpreted.
Millisecond precision only. High-resolution timing use cases — benchmarking, animation, scientific computing — needed performance.now() as a workaround.
These aren’t edge cases. They’re the everyday reality of JavaScript date handling, which explains why date manipulation libraries collectively represent hundreds of millions of npm downloads per week.
How the TC39 Process Works (and Why It Took Nine Years)
Understanding the timeline requires understanding TC39, the committee that governs ECMAScript standardization. Every language feature progresses through five numbered stages:
| Stage | Name | Meaning |
|---|---|---|
| 0 | Strawperson | Idea submitted for discussion |
| 1 | Proposal | Problem statement, initial solution, champions assigned |
| 2 | Draft | Precise syntax and semantics described |
| 3 | Candidate | Spec complete, awaiting implementation feedback |
| 4 | Finished | Two shipping implementations, merged into spec |
The Temporal proposal was formally introduced with a GitHub repository on March 12, 2017, championed initially by Philipp Dunkel and Maggie Johnson-Pint.2 The core problem statement was clear from day one. The design work took years longer.
Stage 3 (the implementation phase) proved especially long. The proposal’s own documentation contained an unusual warning that held browser engineers back: “implementers of this proposal MUST NOT ship unflagged Temporal implementations” until the IETF had standardized timezone and calendar string serialization formats. That wasn’t just a polite note — it was a hard dependency on a separate standards body moving on its own schedule.
The nine-year gap between “everyone knows this is broken” and “it’s shipping in Chrome” reflects how standards actually evolve. It’s not one proposal stalling — it’s a cascade of dependencies: language design, spec precision, IETF standardization, and finally two independent browser implementations. Temporal hit Stage 4 in late 2024 and browsers began shipping immediately after.
What Temporal Actually Looks Like
Temporal is not a class — it’s a namespace, like Math or Intl. You never write new Temporal(). Instead, you use specific classes within the namespace, each designed for a particular purpose.
// The old way — ambiguous and mutation-prone const now = new Date(); const inTwoHours = new Date(now.getTime() + 2 * 60 * 60 * 1000);
// The Temporal way — explicit and immutable const now = Temporal.Now.instant(); const inTwoHours = now.add({ hours: 2 });
The key classes map directly to real concepts:
| Class | Use Case | Example |
|---|---|---|
Temporal.Instant | A precise moment in time (UTC) | Timestamps, logs, event ordering |
Temporal.ZonedDateTime | A moment with timezone context | Scheduling meetings, alarms |
Temporal.PlainDate | A calendar date with no time | Birthdays, holidays, deadlines |
Temporal.PlainTime | A wall-clock time with no date | Business hours, recurring daily times |
Temporal.PlainDateTime | Date + time, no timezone | Local form inputs, database storage |
Temporal.Duration | A span of time | ”3 days 2 hours from now” |
Temporal.PlainYearMonth | Month + year only | Monthly billing periods |
Temporal.PlainMonthDay | Month + day only | Anniversaries, recurring yearly dates |
DST-Safe Arithmetic
The most practically important improvement is timezone-aware arithmetic:
// Schedule “9am New York” the day after DST transition const meetingDay = Temporal.PlainDate.from(‘2026-03-08’); const nextDay = meetingDay.add({ days: 1 });
const meeting = nextDay.toZonedDateTime({ timeZone: ‘America/New_York’, plainTime: Temporal.PlainTime.from(‘09
’) });// Always 9
AM New York time — regardless of DST console.log(meeting.toString()); // 2026-03-09T09:00:00-04With Date, this calculation would silently produce 10:00 AM or 8:00 AM depending on DST state unless you wrote careful manual correction code.
Month Numbers That Make Sense
// Date: months are 0-indexed const march2026Date = new Date(2026, 2, 27); // month 2 = March
// Temporal: months are 1-indexed const march2026 = Temporal.PlainDate.from({ year: 2026, month: 3, day: 27 });
Nanosecond Precision
const start = Temporal.Now.instant(); // … some operation … const end = Temporal.Now.instant(); const elapsed = end.since(start);
console.log(elapsed.nanoseconds); // Precise to nanoseconds
Immutability by Design
const date = Temporal.PlainDate.from(‘2026-03-27’); const nextWeek = date.add({ weeks: 1 });
// date is unchanged — nextWeek is a new object console.log(date.toString()); // 2026-03-27 console.log(nextWeek.toString()); // 2026-04-03
Calendar System Support
One underappreciated capability: Temporal natively supports non-Gregorian calendars. This matters for international applications targeting markets where Islamic, Hebrew, Japanese, or Chinese calendars have legal or cultural significance.
// Hebrew calendar date const hebrewDate = Temporal.PlainDate.from({ year: 5786, month: 7, day: 1, calendar: ‘hebrew’ });
// Convert to ISO for display alongside Gregorian dates const isoEquivalent = hebrewDate.withCalendar(‘iso8601’);
Previously this required specialized libraries with inconsistent APIs. Temporal builds it into the language.
Browser Support Status (As of March 2026)
The rollout is substantial but not yet universal:
| Environment | Support | Version |
|---|---|---|
| Chrome | Native | 144+ (January 2026) |
| Edge | Native | 144+ |
| Firefox | Native | 139+ (May 2025) |
| Safari | In development | No timeline confirmed |
| Node.js | Tracking | In progress |
| GraalJS | Planned | 25.1.0 |
Global coverage from caniuse stands at approximately 64% as of early 2026.3 That’s enough for applications with Chrome/Firefox-dominant audiences, but not a safe baseline for production without polyfilling.
Using Temporal in Production Today
The safest path for immediate adoption:
npm install @js-temporal/polyfill
// Load only when native Temporal is unavailable if (typeof Temporal === ‘undefined’) { const { Temporal, Intl, toTemporalInstant } = await import(‘@js-temporal/polyfill’); globalThis.Temporal = Temporal; globalThis.Intl = Intl; Date.prototype.toTemporalInstant = toTemporalInstant; }
The official polyfill, maintained by proposal champions Philipp Chimento and Justin Grant, tracks the final spec. Code written against it today will work natively when Safari ships and the polyfill becomes dead weight you can drop without changing application code.
A second option, temporal-polyfill by FullCalendar, offers a smaller bundle size with a tree-shakeable API — worth evaluating for bandwidth-sensitive applications.
What This Means for the Ecosystem
The broader implication is that JavaScript’s date library ecosystem is entering a consolidation phase. Moment.js already declared itself in maintenance mode in 2020, explicitly recommending users migrate away.4 Luxon (created by a Moment.js author) and date-fns have positioned themselves as modern alternatives, but both remain wrappers around Date.
Temporal doesn’t just replace Moment — it eliminates the category of problem that made Moment necessary. Date arithmetic, timezone handling, and calendar operations are now a solved problem at the language level.
Library authors will need to decide: wrap Temporal for convenience APIs, or advise users to use Temporal directly. For most calendar and scheduling use cases, the native API is already expressive enough that wrappers add overhead without value.
The Lesson in How Standards Evolve
Temporal’s nine-year arc is worth understanding as a model for how web standards actually advance. The JavaScript community recognized the Date problem in 1997. Popular solutions (moment.js launched in 2011) emerged and captured millions of users. TC39 began formal work in 2017. Browser implementations followed years later. Safari will presumably ship in 2026 or 2027.
Each delay had a specific cause: design debates about API shape, the external dependency on IETF’s RFC 9557, implementation complexity in browser engines. The nine years weren’t bureaucratic inertia — they were the time required to design a correct solution, not just a better one.
The result is an API with no obvious design debt. Where Date borrowed mistakes from Java, Temporal was designed by people who spent years thinking about what JavaScript date handling should look like if you started from scratch. The immutability, the specialized classes, the DST-safety, the calendar support — these aren’t features bolted on. They’re the foundation.
Frequently Asked Questions
Q: Can I use Temporal in production today?
A: Yes, with a polyfill. Install @js-temporal/polyfill from the proposal champions or temporal-polyfill from FullCalendar for a smaller bundle. Both implement the final spec, so code written now will work natively once Safari ships with no changes required.
Q: Will Temporal replace Moment.js and date-fns? A: For new code, yes. Moment.js is already in maintenance-only mode. Once Temporal achieves full browser coverage (likely when Safari ships), there’s no remaining justification for adding a date library for standard use cases. Temporal provides timezone arithmetic, immutability, calendar support, and nanosecond precision natively.
Q: What TC39 stage is Temporal at? A: Stage 4 — the final stage, meaning the proposal is complete and merged into the ECMAScript specification. Chrome 144 (January 2026) and Firefox 139 (May 2025) have shipped native implementations.
Q: Does Temporal work in Node.js?
A: Native Node.js support is tracked but not yet shipped as of March 2026. The @js-temporal/polyfill package works in Node.js today and is the recommended approach for server-side JavaScript until native support lands.
Q: What’s the difference between Temporal.Instant and Temporal.ZonedDateTime?
A: Instant represents a single point in universal time — like a Unix timestamp, with no timezone context. ZonedDateTime is that same instant interpreted in a specific timezone, giving you wall-clock time, DST-aware arithmetic, and local date/time components. Use Instant for timestamps and ordering; use ZonedDateTime for scheduling and display.
Footnotes
-
Maggie Pint, Brian Terlson, Matt Johnson. “Fixing JavaScript Date: Getting Started.” Blog post, April 9, 2017. ↩
-
TC39 Proposal-Temporal GitHub Repository, created March 12, 2017. https://github.com/tc39/proposal-temporal ↩
-
Can I Use: Temporal API. https://caniuse.com/temporal (accessed March 2026) ↩
-
Moment.js Project Status. https://momentjs.com/docs/#/-project-status/ ↩