npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

temporal-gregorian

v0.2.0

Published

Node Temporal compatibility layer for libraries supporting Node 18-26: native Temporal on Node 26+, polyfilled before it, CJS + ESM, with implementation-agnostic type and runtime guards.

Readme

temporal-gregorian

A Node Temporal compatibility layer for libraries supporting Node 18–26 — native Temporal on Node 26+, polyfilled before it, with CJS and ESM support and implementation-agnostic type and runtime guards.

npm license

Status: early release (v0.x). API mirrors TC39 Temporal.

The problem this solves

Node 26 ships native Temporal. Node 24 LTS does not, and it is supported until April 2028. So for the next couple of years a published library can receive Temporal values that were built by:

  • native Node 26 Temporal,
  • temporal-polyfill,
  • @js-temporal/polyfill,
  • or a second bundled copy of any of those.

Those values are spec-identical, but they are not interchangeable:

// A dependency built this with its own polyfill copy.
const zdt = fromDependency; // Temporal.ZonedDateTime

Temporal.ZonedDateTime.compare(zdt, mine);
// TypeError: Missing timeZone

zdt instanceof Temporal.ZonedDateTime;
// false — even though it is a perfectly valid ZonedDateTime

Types have the same split. Importing your public signatures from one implementation's package means a native Node 26 value may not satisfy them — the conflict Fedify hit on TypeScript 6.

This package is the compatibility layer for that gap.

What it gives you

  • One import, any Node 18–26. Native Temporal when the runtime has it, temporal-polyfill when it does not. No code change on upgrade.
  • CJS and ESM both work, down to Node 18. temporal-polyfill publishes no require condition, so require()-ing it directly throws ERR_REQUIRE_ESM on Node 18 and Node 20 before 20.19. This package bundles it into its own .cjs build so require("temporal-gregorian") just works.
  • Implementation-agnostic public types. Types come from temporal-spec, which is derived from TypeScript's own esnext.intl.d.ts — the same declarations the built-in lib uses for native Temporal. A native value, a polyfill value, and a value from this package all satisfy the same signature.
  • normalizeTemporal() — rebuild a foreign Temporal value with the active implementation, losslessly, so it is safe to use. See below.
  • reflect utilitiesgetTemporalType(value), isTemporal(value), TEMPORAL_CTORS — runtime introspection that never uses instanceof.
  • compare free functions — tree-shakeable forms of the static comparators.

Install

npm add temporal-gregorian
# or: pnpm add temporal-gregorian / yarn add temporal-gregorian
import { PlainDate, Now } from "temporal-gregorian";

const today = Now.plainDateISO();
const due = today.add({ days: 30 });

Crossing implementations safely

import { normalizeTemporal, isNormalized, getTemporalType } from "temporal-gregorian";

export function schedule(when: unknown) {
  // Works on values from ANY Temporal implementation — no instanceof.
  if (getTemporalType(when) !== "ZonedDateTime") {
    throw new TypeError("schedule() needs a Temporal.ZonedDateTime");
  }
  // Rebuild it with the active implementation. Returns it unchanged when it
  // already belongs to the active one, so the common path costs nothing.
  const zdt = normalizeTemporal(when);
  return zdt.add({ hours: 1 }); // safe
}

normalizeTemporal is lossless. Exact time is carried by epochNanoseconds (a BigInt primitive, so it crosses implementations unchanged); everything else is rebuilt from the value's canonical ISO string, which keeps full nanosecond precision plus the calendar and time-zone annotations. ZonedDateTime is rebuilt from (epochNanoseconds, timeZoneId, calendarId), so a tzdata difference between the two implementations cannot shift the instant.

It throws TypeError for anything that is not a Temporal value, and RangeError for calendars outside this package's ISO8601/Gregorian scope.

Per-type helpers are available when you want the stricter check: normalizeInstant, normalizePlainDate, normalizePlainTime, normalizePlainDateTime, normalizePlainYearMonth, normalizePlainMonthDay, normalizeZonedDateTime, normalizeDuration. Each throws TypeError if handed a different Temporal type.

isNormalized(value) tells you whether a value already belongs to the active implementation, without throwing on non-Temporal input.

How it compares

| | temporal-gregorian | temporal-polyfill | @js-temporal/polyfill | native only | | --- | --- | --- | --- | --- | | Uses native Temporal on Node 26+ | yes | yes | no | yes | | Works on Node 18–24 | yes | yes | yes | no | | require() works on Node 18 / 20.0–20.18 | yes (bundled .cjs) | no (ERR_REQUIRE_ESM) | yes | n/a | | Public types independent of any one implementation | yes (temporal-spec) | yes (temporal-spec) | no (own classes) | yes | | Rebuild a foreign value (normalizeTemporal) | yes | no | no | no | | Type/tag introspection without instanceof | yes | no | no | no | | Calendars | ISO8601 + Gregorian | ISO + Gregorian (/full adds more) | all | all | | Min+gzip on non-native Node | ~19.7 KB (ESM) / ~21.4 KB (CJS) | ~19.7 KB | ~46.9 KB | 0 |

Measured with esbuild --bundle --minify + gzip -9 on 2026-08-19.

Use temporal-polyfill directly if you are an application on Node 20.19+ and only need Temporal itself — it is the implementation underneath this package and you do not need a layer.

Use temporal-gregorian if you publish a library that must run on Node 18–26, must be require()-able, and may receive Temporal values built by someone else's implementation.

Migrating

See Migrating a Node library from polyfilled Temporal to Node 26 native Temporal — a step-by-step guide that keeps Node 24, CJS, and TypeScript 6/7 working. A runnable demo lives in examples/mixed-runtime/.

Compatibility, tested

CI runs on every push:

| Axis | Covered | | --- | --- | | Node | 18, 20, 22, 24, 26 | | Module format | ESM and CJS, every published subpath | | Runtime | native Temporal and polyfilled Temporal | | TypeScript | 5.9, 6.0, 7.0 | | Module resolution | NodeNext and Bundler | | Interop | values from a different implementation crossing the API boundary |

npm run test:matrix runs the runtime half locally; npm run check:types-matrix runs the TypeScript half. Both install the packed tarball into a clean directory, so they test what consumers actually receive.

On Node 18–24 the "native" runtime is simulated by installing globalThis.Temporal before the package loads, which is exactly what Node 26 does.

Scope

ISO8601/Gregorian calendars only. A value on another calendar is rejected with a RangeError rather than silently mishandled.

Recurrence, intervals, relative-time, business-day helpers and duration formatting are out of scope — they are already well served by rrule-temporal, temporal-interval, Intl.RelativeTimeFormat and Intl.DurationFormat. This package stays a compatibility layer.

Requirements

Node 18 or newer. Timezone data comes from the host ICU, same as native Temporal; small-icu Node builds may lack full tzdata, so install the full-icu package if your environment does not guarantee it.

Ecosystem

  • temporal-sql — Postgres ⇄ Temporal codecs for pg, postgres.js, Drizzle & Prisma, built on this package's reflect primitives. Correct intervalDuration and microsecond-precision safety, no JS Date.

License

MIT