JavaScript Finally Separates Time from Dates, Ten Years After Java Did
Source: hackernews
Bloomberg Engineering’s retrospective on the Temporal proposal is worth reading for anyone who has spent time fighting JavaScript’s Date object. The story it tells is partly a technical history and partly a case study in how complex web standards actually get made. What it does not spend much time on is the wider context: JavaScript is the last major general-purpose language to address this problem, and the solution it landed on matches what every other language converged to independently.
The Same Design Keeps Appearing
Java deprecated most of java.util.Date in 1997, two years after JavaScript copied it. The replacement was java.util.Calendar, which fixed some things and was bad in its own ways. In 2014, Java 8 shipped java.time (formalized as JSR-310), a complete redesign built from Joda-Time, Stephen Colebourne’s third-party library that had filled the gap since 2002. The java.time type hierarchy: Instant for a point on the timeline, LocalDate for a calendar date, LocalDateTime for date and time without timezone, ZonedDateTime for the full picture.
Those names are nearly identical to Temporal’s. That is not coincidence or borrowing; the TC39 proposal explicitly referenced java.time and Noda Time, Jon Skeet’s C# library, as prior art. Noda Time has Instant, LocalDate, LocalDateTime, ZonedDateTime. Python 3.9 (2020) added zoneinfo to the standard library for proper IANA timezone handling. Rust’s chrono crate gives you NaiveDate, NaiveDateTime, DateTime<Tz>, where the Naive prefix explicitly signals that no timezone information is encoded.
The same decomposition appears across ecosystems because the underlying structure of the problem makes it unavoidable. A point in time and a point on a calendar are different things. “9 AM” and “1:00:00.000000000 UTC on 2026-03-11” are different things. Mixing them into a single type causes ambiguity that no amount of clever method naming can eliminate.
Why Third-Party Libraries Could Not Fix This
JavaScript’s date library ecosystem is extensive. Moment.js, Luxon, date-fns, Day.js. Each generation improved on the previous one’s API design. None of them could close the most important gap.
Arbitrary IANA timezone support requires a timezone database. Governments change DST rules, sometimes with weeks of notice. A library that bundles its own copy of the IANA database ships a snapshot that becomes stale without a package update. Users of moment-timezone needed to update their dependency when Brazil abolished DST in 2019. This is a real production maintenance burden, not a hypothetical.
Browsers already maintain a current timezone database for Intl.DateTimeFormat, the built-in internationalization API. Intl.DateTimeFormat can render a date in any IANA timezone. But it could only render. There was no way to perform arithmetic in an arbitrary timezone using only the standard library. Temporal’s ZonedDateTime uses the host’s timezone data for arithmetic because it is part of the language. The database stays current through browser and runtime updates, not npm updates.
// Get the next weekday at 9 AM in New York, DST-correct
function nextWeekdayAt9(timeZone) {
let date = Temporal.Now.plainDateISO();
// dayOfWeek is 1-7, 6=Saturday, 7=Sunday
while (date.dayOfWeek >= 6) {
date = date.add({ days: 1 });
}
return date.toZonedDateTime({
timeZone,
plainTime: Temporal.PlainTime.from('09:00')
});
}
// If this runs the day before a DST spring-forward,
// the result's epochMilliseconds accounts for the shorter day automatically.
The Polyfill Changed the Spec
One detail in Bloomberg’s retrospective deserves particular attention: the @js-temporal/polyfill was developed in parallel with the formal specification, not after it. Developers used it in real code during Stage 3. That feedback loop changed the design in meaningful ways.
Early Temporal iterations were closer to Joda-Time: class-based, with mutable TimeZone and Calendar objects that could be subclassed. Custom calendar support worked through inheritance. This proved problematic for JavaScript specifically. Subclassing built-in objects creates implementation complexity across engines, and in a browser security context, mutable objects that can be extended by arbitrary code create observable side-channel risks. The design moved to a protocol-based approach, where custom calendars and timezones satisfy an interface rather than inherit from a base class.
Duration arithmetic with calendar units went through a related change. The problem: “how many days is one month?” has no single answer. One month from March 1 is 31 days; one month from February 1 is 28 or 29. An earlier API version allowed duration rounding that would silently pick the wrong answer when calendar unit boundaries were involved. The fix added a relativeTo parameter.
const d = Temporal.Duration.from({ months: 1 });
// This throws: calendar units need a reference point
d.total({ unit: 'days' });
// TypeError: a relativeTo option is required for this operation
// These produce different answers, and both are correct
d.total({ unit: 'days', relativeTo: Temporal.PlainDate.from('2026-03-01') });
// 31
d.total({ unit: 'days', relativeTo: Temporal.PlainDate.from('2026-02-01') });
// 28
The error message initially frustrated users who wanted the API to just work. The alternative was silently wrong answers locked into browser implementations forever. Discovering this through polyfill feedback before Stage 4 was exactly the point of the polyfill-first model.
How Bloomberg Ended Up Writing the Spec
Bloomberg funded Igalia, a web standards consulting firm, to write the formal specification and the reference polyfill. Philip Chimento and Ujjwal Sharma at Igalia produced the bulk of the specification text. The Temporal spec is approximately 200 pages of formal ECMA-262; for context, the entire ES2015 specification was around 600 pages. This is not something a volunteer produces in spare time.
Bloomberg’s motivation was direct. The Bloomberg Terminal and related financial data products run constant date arithmetic on time series data. Operations like “9:30 AM New York time on every trading day through the next DST transition, across twenty global markets with their respective holiday calendars” are production requirements. Bloomberg needed non-Gregorian calendar support because global financial markets use Hebrew, Islamic, Persian, and other calendar systems. Getting this fixed in the language was worth a multi-year engineering investment.
The corporate-sponsor-plus-standards-consulting model is increasingly how complex TC39 proposals get written. Several WebAssembly features, parts of the CSS specification, and other major browser additions have followed similar paths. Standards work at this scale requires full-time engineers with deep specialization.
Getting Started Today
The polyfill is production-ready. It is large (around 380KB minified, because it includes full timezone and calendar computation tables), but that weight disappears once native implementations ship.
npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';
// Converting from legacy Date at integration boundaries
const legacy = new Date();
const instant = Temporal.Instant.fromEpochMilliseconds(legacy.getTime());
const zdt = instant.toZonedDateTimeISO('America/New_York');
// Choosing the right type:
// Logging, comparison, database storage → Temporal.Instant
// Calendar arithmetic, month boundaries → Temporal.PlainDate
// DST-correct scheduling → Temporal.ZonedDateTime
// Recurring time of day, market opens → Temporal.PlainTime
Firefox 139 ships Temporal natively. V8 has it available in Node.js behind --harmony-temporal. The TC39 proposal is at Stage 3, with two complete implementations on the path to Stage 4.
The nine years were necessary. The specification covers more ground than any previous single addition to JavaScript’s standard library: 10+ types, 20 calendar systems, nanosecond precision, DST disambiguation modes, and custom calendar and timezone protocols. Every place Date made a silent choice, Temporal surfaces the decision and requires you to state your intent. Other languages got here faster. JavaScript got here with a design that holds up.