The History of Date in JavaScript
From the Beginning to Temporal in Node.js 26
A 31-year journey from a 10-day hack to a standards-grade date/time API

Contents
- 1995 — Date is Born in a Rush
- 1997 — ECMAScript 1 Standardizes the Mess
- 2000s — Learning to Live With the Pain
- 2011 — Moment.js Saves the Day
- 2014–2018 — The Age of Alternatives
- 2017 — The Temporal Proposal is Born
- 2017–2021 — Design and Stage 1 to Stage 2
- 2021–2025 — Stage 3: Implementations and Years of Polishing
- 2022 — Even Date Libraries Became a Security Risk
- March 2026 — Stage 4: Temporal Enters the Standard
- May 2026 — Node.js 26: Temporal On by Default
- How to Upgrade to Node.js 26 for Free
- Date vs. Temporal: Before and After
- What Happens to Moment.js and Friends?
- References
1 — 1995: Date is Born in a Rush
When Brendan Eich created JavaScript in just 10 days in 1995 for Netscape Navigator, the Date implementation was borrowed directly from Java — and Java had in turn taken it from java.util.Date, which already had its own serious problems.
The result was an API that shipped to the world with factory-installed defects.
| Problem | Example |
|---|---|
| Months are 0-indexed (0 to 11) | new Date(2026, 0, 1) is January |
| Weekdays are 0-indexed (0 = Sunday) | Guaranteed confusion |
getYear() returns year minus 1900 | new Date().getYear() returns 126 in 2026 |
| Objects are mutable by default | Any function can silently alter your date |
| No real time-zone support | Only UTC and the system's local zone |
| String parsing is non-deterministic | new Date("2/3/2026") — February 3 or March 2? |
// The classic 0-indexed month bug
const christmas = new Date(2026, 11, 25); // 11 = December, not an error
console.log(christmas.getMonth()); // 11
// Silent mutability: a nightmare
const date = new Date();
function doSomething(d) {
d.setFullYear(1900); // modifies the original!
}
doSomething(date);
console.log(date.getFullYear()); // 1900
2 — 1997: ECMAScript 1 Standardizes the Mess
With the first official ECMAScript specification (ES1), Date was formally standardized with all its quirks intact.
The foundational principle of “don’t break the web” that guides TC39 meant those bugs would become practically eternal. Any attempt to fix them retroactively would break billions of existing web pages.
The web was growing explosively and JavaScript was everywhere. There was neither the time nor the appetite for a breaking redesign.
The industry collectively decided to live with it.
3 — 2000s: Learning to Live With the Pain
The community developed a set of defensive patterns to work safely with dates:
// Comparing dates: only via .getTime()
if (date1.getTime() === date2.getTime()) {
/* ... */
}
// Cloning to avoid silent mutation
const copy = new Date(original.getTime());
// The most popular "safe" parsing pattern
const parts = "2026-05-22".split("-");
const date = new Date(parts[0], parts[1] - 1, parts[2]); // -1 required !!
// "Safe" formatting by hand (no built-in formatter until Intl)
function formatDate(d) {
return d.getFullYear() + "-"
+ String(d.getMonth() + 1).padStart(2, "0") + "-" // +1 !!
+ String(d.getDate()).padStart(2, "0");
}
These workarounds were repeated millions of times across codebases worldwide.
Entire coding conventions emerged just to avoid the traps baked into Date.
4 — 2011: Moment.js Saves the Day
Moment.js became one of the most downloaded libraries in the JavaScript ecosystem.
Its fluent, human-readable API was a breath of fresh air:
// So comfortable it felt like cheating...
moment("2026-05-22").add(7, "days").format("DD/MM/YYYY"); // "29/05/2026"
moment("2026-05-22").fromNow(); // "3 hours ago"
moment.tz("2026-05-22", "America/New_York").utcOffset(); // -240
Moment.js proved beyond any doubt that a decent date API was possible in JavaScript.
It became a fixture in virtually every serious web project.
But it had its own Achilles heel: mutable objects, a massive bundle size (~67 KB minified), no tree-shaking support, and performance problems at scale.
It demonstrated what was possible, but at a cost.
5 — 2014–2018: The Age of Alternatives
The community began searching for lighter, more modern alternatives.
| Library | Year | Approach |
|---|---|---|
date-fns | 2015 | Pure functions, fully immutable, fully tree-shakeable |
Luxon | 2017 | Built by the Moment.js team; leverages the native Intl API for time zones and formatting |
Day.js | 2018 | Moment-compatible API in only ~2 KB |
// date-fns: the functional style — pure, predictable, tree-shakeable
import { addDays, format } from "date-fns";
format(addDays(new Date(), 7), "dd/MM/yyyy");
// Luxon: built on Intl, real timezone support
import { DateTime } from "luxon";
DateTime.now().setZone("America/Chicago").toISO();
// Day.js: Moment's API, one-seventh of its size
dayjs("2026-05-22").add(7, "day").format("DD/MM/YYYY");
Each library solved real problems, but they all shared one fundamental limitation: they were wrappers or replacements built on top of — or entirely separate from — the native runtime.
Bundling date logic remained a cost that every JavaScript application had to pay.
6 — 2017: The Temporal Proposal is Born at TC39
Developers from Bloomberg, Igalia, and the broader community formally opened the Temporal proposal at TC39.
The motivation was unambiguous: replace Date with a correct-by-design API, not a patch.
The founding principles of Temporal were:
- Complete immutability — all operations return new objects
- Real time zones, not just offsets
- Separate types for each use case (
dateonly,timeonly,instant,zoned, etc.) - Deterministic parsing via ISO 8601 exclusively
- Full non-Gregorian calendar support
- Explicit and unambiguous API surface — no implicit coercions
“The goal of Temporal is not to be a replacement for Moment.js. It is to be the correct foundation that JavaScript should have had from the start.”
— TC39 Temporal Champions
7 — 2017–2021: Design and Stage 1 to Stage 2
For years, the proposal champions — Philipp Dunkel, Maggie Johnson-Pint, Matt Johnson-Pint, Shane F. Carr, and others — iterated over the design.
It was an enormous undertaking. Replacing Date required handling:
- The world's calendar systems: Chinese lunisolar, Islamic, Hebrew, Japanese imperial era, Ethiopian, and more
- The IANA TZDB (the global database of time zones), including DST transitions
- Duration arithmetic with variable-length months and years
- Deep integration with the existing
IntlAPI (ECMA-402) - Edge cases in wall-clock time arithmetic at DST transitions
Timeline
- 2017 — Stage 1 — Exploratory proposal accepted by TC39
- 2019 — Stage 2 — Draft specification published
- 2021 — Stage 3 — Specification complete; implementations begin
8 — 2021–2025: Stage 3, Implementations and Years of Polishing
In Stage 3, browsers and runtimes began experimental implementations.
Users could opt in behind flags:
// Available behind experimental flags in recent Node.js versions
// Node.js (v18+ with V8 experimental support):
node --harmony-temporal app.js
// Chrome:
chrome://flags "Experimental JavaScript"
const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // "2026-05-22"
These years exposed unanticipated edge cases: bugs in duration arithmetic across DST boundaries, unexpected behavior in exotic calendars, ambiguous wall-clock times during clock changes. Each one required revisions to the spec. The champions chose thoroughness over speed — a decision that paid off in the correctness of the final standard.
Browser support rolled out gradually during this period: Chrome and V8-based engines shipped incremental support starting in 2024, with broader availability through 2025.
9 — 2022: Even Date Libraries Became a Security Risk
By the early 2020s, third-party date libraries had become deeply embedded across the JavaScript ecosystem.
Applications relied on them for:
- formatting
- time zones
- parsing
- scheduling
- localization
- duration calculations
But this dependency came with growing operational and security concerns.
In 2022, Moment.js was affected by a widely discussed path traversal vulnerability (CVE-2022-31129), reminding developers that even foundational utility libraries can become part of an application's attack surface.
The issue was fixed, but it reinforced a broader realization across the ecosystem:
critical platform capabilities should ideally exist natively in the runtime itself — not exclusively through third-party dependencies.
That realization helped strengthen support for Temporal and the push toward a modern built-in date/time API for JavaScript.
10 — March 11, 2026: Stage 4, Temporal Enters the Standard
After 9 years of work, at the TC39 meeting of March 2026, Temporal officially reached Stage 4, making it part of ECMAScript 2026.
"Temporal is now Stage 4 at TC39. Thanks to all the other champions of JavaScript's new date-time API. It has been a wild ride."
— TC39 Temporal Champions, March 11, 2026
Alongside Stage 4, browser support solidified:
- Chrome and Edge (V8) — enabled by default from January 2026 (v144+)
- TypeScript 6.0 — full type definitions shipped in February 2026
- Safari — partial support in Technology Preview
- Firefox — implementation in progress
11 — May 5, 2026: Node.js 26, Temporal On by Default
Node.js 26, released on May 5, 2026 with V8 14.6 and Undici 8, enabled Temporal without any flags or experimental settings.
For the first time in JavaScript's history, developers have a first-class date/time API built directly into the runtime.
// No import. No npm install. No flags. Just modern JavaScript.
// Specialized types for every use case
const date = Temporal.PlainDate.from("2026-05-22");
const time = Temporal.PlainTime.from("14:30:00");
const dateTime = Temporal.PlainDateTime.from("2026-05-22T14:30:00");
const instant = Temporal.Now.instant();
const zoned = Temporal.Now.zonedDateTimeISO("America/Chicago");
// True immutability — methods return new objects
const tomorrow = date.add({ days: 1 });
console.log(date.toString());
// "2026-05-22" (unchanged)
console.log(tomorrow.toString());
// "2026-05-23"
// Arithmetic without off-by-one month bugs
const start = Temporal.PlainDate.from("2026-01-31");
const end = start.add({ months: 1 });
console.log(end.toString());
// "2026-02-28" (not "2026-03-03")
// Real time zones with automatic DST handling
const event = Temporal.ZonedDateTime.from(
"2026-03-08T01:30:00[America/New_York]"
);
const oneHourLater = event.add({ hours: 1 });
// Correctly accounts for the clock jumping forward
// Unambiguous comparisons
const a = Temporal.PlainDate.from("2026-05-22");
const b = Temporal.PlainDate.from("2026-06-01");
console.log(Temporal.PlainDate.compare(a, b));
// -1 (a is before b)
// Expressive durations
const duration = Temporal.Duration.from({
years: 1,
months: 6,
days: 3
});
console.log(duration.toString());
// "P1Y6M3D" (ISO 8601)
// 'until' and 'since': difference between dates
const today = Temporal.Now.plainDateISO();
const target = Temporal.PlainDate.from("2026-12-31");
const diff = today.until(target);
console.log(`${diff.days} days until end of year`);
12 — How to Upgrade to Node.js 26 for Free
If your infrastructure or production servers are still running an older version of Node.js and you want to take advantage of Temporal, built-in time zones, and all the other improvements in Node.js 26, you can upgrade at no cost.
Free Upgrade Program — NodeSource and OpenJS Foundation
NodeSource, in partnership with the OpenJS Foundation, has created a free upgrade program to help developers and organizations move to Node.js 26 safely and with support guidance.
Get started at:
https://nodesource.com/upgrade
The upgrade path from any currently supported or end-of-life version of Node.js is straightforward.
The program covers:
- Step-by-step migration guidance for common Node.js versions
- Compatibility checklists for breaking API removals in Node.js 26
- Resources from the OpenJS Foundation for long-term support planning
- Access to NodeSource's enterprise distribution channels
With Temporal now part of ECMAScript 2026 and active by default in Node.js 26, there has never been a better time to upgrade.
No third-party date libraries, no polyfills, no flags — just a modern runtime with a correct date/time API built in.
13 — Date vs. Temporal: Before and After
The old world
// Months start at 0 — always a trap
new Date(2026, 0, 1); // January (why?!)
// Silent mutability
const d = new Date();
d.setMonth(d.getMonth() + 1); // mutates the original
// Time zone ambiguity and implicit UTC conversion
const meeting = new Date("2026-05-22T10:00:00");
console.log(meeting.toString());
// Local machine timezone
console.log(meeting.toISOString());
// Converted to UTC automatically
// Non-deterministic string parsing
new Date("2/3/2026");
The new world
// Months start at 1 — exactly as expected
Temporal.PlainDate.from({
year: 2026,
month: 1,
day: 1
});
// Complete immutability
const t = Temporal.Now.plainDateISO();
const next = t.add({ months: 1 });
// Explicit timezone-aware date/time
Temporal.ZonedDateTime.from(
"2026-05-22T10:00:00[America/New_York]"
);
// Strict ISO 8601 parsing — always unambiguous
Temporal.PlainDate.from("2026-03-02");
| Feature | Date | Temporal |
|---|---|---|
| Immutability | No — mutable by default | Yes — all operations return new objects |
| Month indexing | 0–11 | 1–12 |
| Time zone support | UTC + system local only | Full IANA TZDB, DST-aware |
| Calendar support | Gregorian only | Multiple calendars built in |
| String parsing | Non-deterministic, locale-dependent | ISO 8601, strict and deterministic |
| Separate date/time types | No — one object for everything | Yes — PlainDate, PlainTime, ZonedDateTime, Instant, etc. |
| Duration type | No | Yes — Temporal.Duration |
| Native in Node.js 26 | Since 1995 | Yes, enabled by default |
14 — What Happens to Moment.js and Friends?
The Moment.js team had already declared the project in maintenance mode in 2020, recommending against using it in new projects.
With Temporal now in the ECMAScript standard and active in Node.js 26, the picture is clear:
| Library | Status with Temporal |
|---|---|
| Moment.js | Maintenance mode since 2020 — migrate to Temporal for new projects |
| date-fns | Gradually replaceable — Temporal covers the same functional style natively |
| Day.js | Gradually replaceable — Temporal's API is more complete and correct |
| Luxon | Gradually replaceable — Temporal integrates deeply with Intl natively |
For projects already using these libraries, migration is incremental.
Temporal does not remove Date — it coexists with it.
The standard provides interoperability methods:
// Convert between Date and Temporal
const legacyDate = new Date();
const instant = Temporal.Instant.fromEpochMilliseconds(
legacyDate.getTime()
);
// Back to Date when needed (e.g., for legacy APIs)
const backToDate = new Date(instant.epochMilliseconds);
- 1995 —
Dateis born (copied from Java, 10 days of work) - 1997 — ECMAScript 1 standardizes it — bugs included
- 2011 — Moment.js saves developers from the suffering
- 2015 —
date-fns(functional immutability) - 2017 — Temporal proposed at TC39 (Stage 1)
- 2017 — Day.js and Luxon emerge as modern alternatives
- 2019 — Stage 2 (draft specification)
- 2021 — Stage 3 (experimental implementations begin)
- 2021 — Node.js ships it under
--harmony-temporalflag - 2024 — Chrome/Edge enable it incrementally
- 11 Mar 2026 — Stage 4 — part of ECMAScript 2026
- 05 May 2026 — Node.js 26 — Temporal on by default
It took 31 years from the day Brendan Eich wrote Date in those frantic 10 days of 1995 for JavaScript to finally have the date/time handling it always deserved.
A reminder that in the world of web standards, good things take their time.
References
- Node.js 26.0.0 Release Blog — https://nodejs.org/en/blog/release/v26.0.0
- Bloomberg Engineering Blog — “Temporal: The 9-Year Journey to Fix Time in JavaScript” (March 11, 2026) — https://bloomberg.github.io/js-blog/post/temporal/
- TC39 Temporal Proposal Repository — https://github.com/tc39/proposal-temporal
- Socket.dev — “TC39 Advances Temporal to Stage 4” (March 16, 2026) — https://socket.dev/blog/tc39-advances-temporal-to-stage-4
- NodeSource Upgrade Program (NodeSource + OpenJS Foundation) — https://nodesource.com/upgrade