Temporal starts by asking what kind of time you have
JavaScript's Date stores one millisecond timestamp but exposes it through a mixture of UTC and host-local methods. That design works well for simple instants and notoriously badly for calendar dates, named time zones and daylight saving transitions.
Temporal breaks those concepts into separate immutable types. The first question is not "What method do I call?" but "What does this value mean?"
| Requirement | Temporal type |
|---|---|
| exact point on the timeline | Temporal.Instant |
| date and time in an IANA timezone | Temporal.ZonedDateTime |
| calendar date and clock time without a zone | Temporal.PlainDateTime |
| calendar date such as a birthday | Temporal.PlainDate |
| wall-clock time such as store opening time | Temporal.PlainTime |
| amount of calendar or clock time | Temporal.Duration |
The TC39 specification is a Stage 4 draft dated July 2026. Firefox and Chrome have shipped the API, and Node.js 26 enables it by default, but MDN still marks it as limited availability. Feature detection remains part of a responsible rollout.
Instant is the Unix-timestamp type
Use Temporal.Instant when the value answers when did this happen? It represents a point on the UTC timeline with nanosecond resolution.
const fromMilliseconds = Temporal.Instant.fromEpochMilliseconds(
1_700_000_000_000,
);
const fromNanoseconds = Temporal.Instant.fromEpochNanoseconds(
1_700_000_000_000_000_000n,
);
console.log(fromMilliseconds.toString());
// "2023-11-14T22:13:20Z"
The final n makes the nanosecond value a BigInt. A present-day epoch-nanosecond value cannot be represented exactly by JavaScript Number.
The finalized constructor surface has millisecond and nanosecond methods:
Temporal.Instant.fromEpochMilliseconds(epochMilliseconds);
Temporal.Instant.fromEpochNanoseconds(epochNanoseconds);
For Unix seconds, convert explicitly:
const epochSeconds = 1_700_000_000;
const instant = Temporal.Instant.fromEpochMilliseconds(epochSeconds * 1_000);
The name of the unit at the border is still important. Temporal cannot know whether an unlabeled integer was in seconds, milliseconds, or some non-Unix epoch.
Convert an instant into a wall-clock view
An instant has no inherent city or civil-time rule. Apply an IANA timezone when you need a local view:
const instant = Temporal.Instant.from("2026-11-01T05:30:00Z");
const newYork = instant.toZonedDateTimeISO("America/New_York");
console.log(newYork.toString());
// "2026-11-01T01:30:00-04:00[America/New_York]"
Temporal.ZonedDateTime combines an instant, an IANA timezone, and a calendar. Its string includes both the current offset and the zone annotation. That extra information is what makes calendar arithmetic possible following timezone transitions.
The bracketed form is based on RFC 9557's Internet Extended Date/Time Format. An offset describes the UTC relationship at a particular instant. A named zone provides rules for other instants.
Make DST ambiguity a policy, not an accident
Some local times do not exist, and others occur twice. Temporal.ZonedDateTime.from() accepts a disambiguation option for that reverse mapping:
const repeatedTime = {
timeZone: "America/New_York",
year: 2026,
month: 11,
day: 1,
hour: 1,
minute: 30,
};
const earlier = Temporal.ZonedDateTime.from(repeatedTime, {
disambiguation: "earlier",
});
const later = Temporal.ZonedDateTime.from(repeatedTime, {
disambiguation: "later",
});
console.log(earlier.toString());
console.log(later.toString());
The choices are:
| Option | Overlap | Gap |
|---|---|---|
compatible |
earlier occurrence | shifts forward |
earlier |
earlier occurrence | shifts backward across the gap |
later |
later occurrence | shifts forward across the gap |
reject |
throws | throws |
Use reject at a user-input boundary when silently moving an appointment would be surprising. Use another policy only when the product requirement defines it.
Calendar arithmetic and elapsed time are deliberately different
“Tomorrow at the same local time” and “24 hours later” can produce different answers across a DST transition.
const start = Temporal.ZonedDateTime.from(
"2026-03-07T12:00:00-05:00[America/New_York]",
);
const sameLocalTimeTomorrow = start.add({ days: 1 });
const exactlyTwentyFourHoursLater = start
.toInstant()
.add({ hours: 24 })
.toZonedDateTimeISO("America/New_York");
console.log(sameLocalTimeTomorrow.toString());
// "2026-03-08T12:00:00-04:00[America/New_York]"
console.log(exactlyTwentyFourHoursLater.toString());
// "2026-03-08T13:00:00-04:00[America/New_York]"
Adding one calendar day preserves the local clock and spans 23 elapsed hours in this example. Adding 24 hours to the instant preserves elapsed time and changes the displayed clock.
This is not a Temporal quirk. You can see this is the real difference between calendar arithmetic and timeline arithmetic in the type and operation that you choose.
Plain types intentionally have no timezone
Temporal.PlainDate, PlainTime, and PlainDateTime represent calendar or wall-clock values without claiming an instant.
const birthday = Temporal.PlainDate.from("1990-08-12");
const openingTime = Temporal.PlainTime.from("09:00");
const localAppointment = Temporal.PlainDateTime.from("2026-11-01T01:30");
This is useful for birthdays, repeated opening hours and form input . This is dangerous if the value is a real event timestamp. A plain date-time needs a time zone (and around transitions a disambiguation policy) to become an instant.
Interoperate with legacy Date at boundaries
Move between the APIs with epoch milliseconds:
const legacyDate = new Date("2024-03-15T14:30:00Z");
const instant = Temporal.Instant.fromEpochMilliseconds(
legacyDate.getTime(),
);
const backToDate = new Date(instant.epochMilliseconds);
That conversion loses any sub-millisecond precision because Date stores milliseconds. It also does not transfer an IANA timezone; a Date contains only the instant value.
For JSON, Temporal objects serialize to strings through their toJSON() methods. Define the field's expected Temporal type in the schema. A string representing an Instant is not interchangeable with a zone-free PlainDateTime merely because both use ISO-style syntax.
Roll out with feature detection
Current confirmed native milestones include Firefox 139, Chrome 144, and Node.js 26. Browser support is still not universal.
if (typeof globalThis.Temporal === "undefined") {
throw new Error("Temporal is not available in this runtime");
}
Production choices are straightforward:
- use native Temporal only when your runtime matrix guarantees it
- load a maintained polyfill for unsupported targets
- keep
Datefor simple timestamp work when a polyfill's size is not justified - isolate date-time operations behind tested application boundaries so the implementation can change later
Don’t assume that a polyfill that works in browsers and every native implementation are byte-for-byte identical. Pin polyfill versions, test the runtime matrix you ship, rerun timezone-sensitive tests when the runtime or timezone database changes.
Know when Date is still enough
Temporal is most useful when the application models calendar dates, named timezones, recurring local schedules, or DST ambiguity. not all timestamps have to be.
Keep Date when all of these are true:
- the value is an instant already expressed as Unix milliseconds or an ISO string with an offset
- the code only compares, stores, or formats that instant
- millisecond precision is sufficient
- the deployment matrix does not justify a Temporal polyfill
Choose Temporal when the business rule says “9 AM in New York,” “the same local time next month,” or “reject a repeated clock time.” Those requirements need a calendar, a named timezone, or an explicit disambiguation policy. They are where Date-only code usually becomes a collection of offsets and assumptions.
This boundary keeps the migration practical: use Instant and ZonedDateTime where their semantics prevent bugs, but do not replace stable timestamp-only code merely to use a newer API.
A useful audit question is whether the code ever reconstructs a local time from offsets, adds calendar units with millisecond constants, or carries a timezone beside a Date in a separate variable. Those patterns indicate that the data has outgrown Date. A simple createdAt value that is stored as an instant and formatted once at the UI boundary usually has not.
A migration sequence that limits risk
- Label existing integer units and string formats at system boundaries.
- Replace ambiguous parsing with explicit
Instant, plain, or zoned parsing. - Convert
Datevalues toInstantwhere the code models an event. - Introduce
ZonedDateTimeonly where named-zone rules are required. - Replace millisecond arithmetic with explicit elapsed or calendar operations.
- Add tests for DST gaps, overlaps, month ends, leap years, and precision loss.
- Keep
Dateadapters at legacy API edges until those consumers migrate.
The big win with Temporal is not nanoseconds, or nicer method names. The type informs future readers whether a value is an instant, a calendar value, or a wall clock value. This puts the hard choices on the table before the production does it for you.
Related reading
Frequent questions:
- Q: Is Temporal a replacement for JavaScript Date?
- A: Temporal is designed as the modern API for new date, time, calendar, and timezone code. Date remains available for compatibility and simple millisecond timestamp work, and the two can interoperate through epoch milliseconds.
- Q: How do I convert Unix seconds to Temporal?
- A: Multiply Unix seconds by 1000 and pass the result to Temporal.Instant.fromEpochMilliseconds(). The Stage 4 API provides fromEpochMilliseconds() and fromEpochNanoseconds(); it does not provide fromEpochSeconds().
- Q: Does Temporal support nanoseconds?
- A: Yes. Temporal.Instant represents epoch nanoseconds and exposes epochNanoseconds as a BigInt. Actual clock accuracy and input precision may be much coarser than the representation.
- Q: Can I use Temporal in Node.js?
- A: Node.js 26 enables Temporal by default. For older supported Node releases, use feature detection and a maintained Temporal polyfill if the application needs the API.
- Q: Which browsers support Temporal?
- A: Firefox added Temporal in version 139 and Chrome in version 144. MDN still marks Temporal as limited availability because some widely used browsers do not support it, so production sites should feature-detect and provide a fallback where required.
- Q: How does Temporal handle a DST gap or overlap?
- A: ZonedDateTime conversion accepts a disambiguation option: compatible, earlier, later, or reject. compatible is the default; reject throws when a local time is skipped or repeated.