<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cna</title>
    <description>The latest articles on DEV Community by Cna (@the_cna).</description>
    <link>https://dev.to/the_cna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4085032%2F793b4167-189e-4d54-8c58-03c7cb717f01.jpg</url>
      <title>DEV Community: Cna</title>
      <link>https://dev.to/the_cna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/the_cna"/>
    <language>en</language>
    <item>
      <title>Migrating a Node library from polyfilled `Temporal` to Node 26 native `Temporal`</title>
      <dc:creator>Cna</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:56:55 +0000</pubDate>
      <link>https://dev.to/the_cna/migrating-a-node-library-from-polyfilled-temporal-to-node-26-native-temporal-3jhm</link>
      <guid>https://dev.to/the_cna/migrating-a-node-library-from-polyfilled-temporal-to-node-26-native-temporal-3jhm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Without breaking Node 24, CJS, or TypeScript 6/7.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Node 26 enabled native &lt;code&gt;Temporal&lt;/code&gt; by default on 2026-05-05. Node 24 LTS is&lt;br&gt;
supported until April 2028. If you publish a library, both are your users at the&lt;br&gt;
same time, and they will be for years.&lt;/p&gt;

&lt;p&gt;This guide is for &lt;strong&gt;library authors&lt;/strong&gt;. If you ship an application and control&lt;br&gt;
your own Node version, your migration is one line — drop the polyfill when you&lt;br&gt;
move to Node 26 — and you can stop reading.&lt;/p&gt;


&lt;h2&gt;
  
  
  The three things that actually break
&lt;/h2&gt;

&lt;p&gt;Most migration advice says "the polyfill and native &lt;code&gt;Temporal&lt;/code&gt; are&lt;br&gt;
spec-compatible, so just swap them". That is true about behaviour and false about&lt;br&gt;
identity. Three concrete failures:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. &lt;code&gt;instanceof&lt;/code&gt; stops working
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;foreignZdt&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The value is a perfectly valid &lt;code&gt;ZonedDateTime&lt;/code&gt;. It just came from a different&lt;br&gt;
implementation, so it has a different prototype chain. Any guard, any router, any&lt;br&gt;
&lt;code&gt;switch&lt;/code&gt; built on &lt;code&gt;instanceof&lt;/code&gt; silently takes the wrong branch.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Some values are rejected outright
&lt;/h3&gt;

&lt;p&gt;Passing a foreign value into a Temporal API falls back to reading it as a&lt;br&gt;
property bag. For most types that quietly works. For &lt;code&gt;ZonedDateTime&lt;/code&gt; it does not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foreignZdt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// TypeError: Missing timeZone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@js-temporal/polyfill&lt;/code&gt; exposes &lt;code&gt;timeZoneId&lt;/code&gt;, not &lt;code&gt;timeZone&lt;/code&gt;, so the property-bag&lt;br&gt;
path finds nothing to read. This is a hard failure at runtime, in production, on&lt;br&gt;
a mixed-version fleet — and it is exactly what&lt;br&gt;
&lt;a href="https://github.com/fedify-dev/fedify/issues/767" rel="noopener noreferrer"&gt;Fedify hit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Borrowed methods fail too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foreignDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// TypeError: Invalid calling context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Your public types are tied to one implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@js-temporal/polyfill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That signature describes &lt;em&gt;that polyfill's classes&lt;/em&gt;. A caller on Node 26 holding a&lt;br&gt;
native &lt;code&gt;ZonedDateTime&lt;/code&gt; may not satisfy it, and on TypeScript 6 the two&lt;br&gt;
declarations&lt;br&gt;
&lt;a href="https://github.com/fedify-dev/fedify/issues/823" rel="noopener noreferrer"&gt;conflict outright&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is a fourth trap that only bites CJS users — covered in step 4.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1 — Stop importing types from an implementation
&lt;/h2&gt;

&lt;p&gt;Take your public types from a &lt;strong&gt;types-only&lt;/strong&gt; package that describes the spec&lt;br&gt;
rather than a class hierarchy.&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/temporal-spec" rel="noopener noreferrer"&gt;&lt;code&gt;temporal-spec&lt;/code&gt;&lt;/a&gt; is derived from&lt;br&gt;
TypeScript's own &lt;code&gt;esnext.intl.d.ts&lt;/code&gt;, so it is the same shape the built-in lib&lt;br&gt;
uses for native &lt;code&gt;Temporal&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- import type { Temporal } from "@js-temporal/polyfill";
&lt;/span&gt;&lt;span class="gi"&gt;+ import type { Temporal } from "temporal-spec";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  export function schedule(when: Temporal.ZonedDateTime): void;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a native value, a &lt;code&gt;temporal-polyfill&lt;/code&gt; value and a &lt;code&gt;@js-temporal/polyfill&lt;/code&gt;&lt;br&gt;
value all satisfy the signature.&lt;/p&gt;

&lt;p&gt;If you use &lt;code&gt;temporal-gregorian&lt;/code&gt;, re-export it from there instead so your&lt;br&gt;
consumers need no extra dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporal-gregorian/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify it.&lt;/strong&gt; Write a fixture that pushes values both directions across the&lt;br&gt;
line and type-check it under every TypeScript version you support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeZdt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// what Node 26 hands you&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;takesNative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;takesNative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;   &lt;span class="c1"&gt;// ours -&amp;gt; native-typed slot&lt;/span&gt;
&lt;span class="nx"&gt;myLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nativeZdt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// native -&amp;gt; ours&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it under &lt;code&gt;moduleResolution: NodeNext&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;Bundler&lt;/code&gt;. They resolve&lt;br&gt;
declarations differently and a package can pass one while failing the other.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2 — Delete every &lt;code&gt;instanceof&lt;/code&gt; check
&lt;/h2&gt;

&lt;p&gt;Replace them with a check on &lt;code&gt;Symbol.toStringTag&lt;/code&gt;, which every Temporal value&lt;br&gt;
carries and every implementation sets identically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- if (value instanceof Temporal.ZonedDateTime) { ... }
&lt;/span&gt;&lt;span class="gi"&gt;+ if (getTemporalType(value) === "ZonedDateTime") { ... }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rolling your own is ~6 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTemporalType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toStringTag&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Temporal.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Temporal.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the caveat: &lt;code&gt;Symbol.toStringTag&lt;/code&gt; is an ordinary forgeable property. It is&lt;br&gt;
the right tool for telling apart values you already trust, not for validating&lt;br&gt;
untrusted input. Step 3 closes that gap.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3 — Normalize values at your API boundary
&lt;/h2&gt;

&lt;p&gt;Detecting a foreign value is not enough — you still have to use it. Rebuild it&lt;br&gt;
with whichever implementation is active in your process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;normalizeTemporal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getTemporalType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporal-gregorian&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getTemporalType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schedule() needs a Temporal.ZonedDateTime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeTemporal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;when&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// returns `when` unchanged if already ours&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;zdt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;        &lt;span class="c1"&gt;// safe&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing it by hand, the two rules that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exact time crosses as a BigInt, not a string.&lt;/strong&gt; &lt;code&gt;epochNanoseconds&lt;/code&gt; is a
primitive, so it is implementation-independent and cannot lose precision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuild &lt;code&gt;ZonedDateTime&lt;/code&gt; from &lt;code&gt;(epochNanoseconds, timeZoneId, calendarId)&lt;/code&gt;,
not from its offset string.&lt;/strong&gt; If the two implementations carry different tzdata
versions, re-parsing an offset can move the instant or throw. Pinning the epoch
value cannot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else round-trips losslessly through &lt;code&gt;toString()&lt;/code&gt;, which carries full&lt;br&gt;
nanosecond precision plus the calendar and time-zone annotations.&lt;/p&gt;

&lt;p&gt;Normalize &lt;strong&gt;once, at the boundary&lt;/strong&gt; — not on every internal call. Inside your&lt;br&gt;
library the values are already yours.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4 — Do not break CJS
&lt;/h2&gt;

&lt;p&gt;This is the step most migrations miss.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;temporal-polyfill&lt;/code&gt; publishes no &lt;code&gt;require&lt;/code&gt; condition in its &lt;code&gt;exports&lt;/code&gt;. So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporal-polyfill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Node 18, Node 20.0-20.18:&lt;/span&gt;
&lt;span class="c1"&gt;// Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works on Node 20.19+, 22.12+, 24 and 26 (which support &lt;code&gt;require(esm)&lt;/code&gt;), and&lt;br&gt;
fails below that. If your library is dual-published and any consumer is on Node&lt;br&gt;
18, a plain dependency on &lt;code&gt;temporal-polyfill&lt;/code&gt; breaks them.&lt;/p&gt;

&lt;p&gt;Three ways out, in order of preference:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bundle the polyfill into your CJS build only.&lt;/strong&gt; ESM keeps it external so
consumers share one copy; CJS inlines it so &lt;code&gt;require()&lt;/code&gt; works everywhere.
This is what &lt;code&gt;temporal-gregorian&lt;/code&gt; does — see its
&lt;a href="//../tsup.config.ts"&gt;&lt;code&gt;tsup.config.ts&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raise your &lt;code&gt;engines&lt;/code&gt; floor&lt;/strong&gt; to &lt;code&gt;&amp;gt;=20.19&lt;/code&gt; and keep the dependency external.
Simpler, but it is a breaking change for your Node 18 users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay on &lt;code&gt;@js-temporal/polyfill&lt;/code&gt;&lt;/strong&gt;, which ships a real CJS build — at roughly
2.4× the bundle size (~46.9 KB vs ~19.7 KB min+gzip) and with the type problem
from step 1 still unsolved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bundling has one cost, and you should know it: a CJS consumer who also installs&lt;br&gt;
the polyfill directly ends up with two copies, which is precisely the foreign-value&lt;br&gt;
situation from step 3. That is why step 3 comes first.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5 — Prove it with a matrix, not with a smoke test
&lt;/h2&gt;

&lt;p&gt;Importing your package once on your laptop proves nothing here. The failures are&lt;br&gt;
combinational. Test the packed tarball — not your source tree — across:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Values&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;18, 20, 22, 24, 26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module format&lt;/td&gt;
&lt;td&gt;ESM, CJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;native &lt;code&gt;Temporal&lt;/code&gt;, polyfilled &lt;code&gt;Temporal&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;5.9, 6.0, 7.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module resolution&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NodeNext&lt;/code&gt;, &lt;code&gt;Bundler&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interop&lt;/td&gt;
&lt;td&gt;a value from another implementation crossing your API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You do &lt;strong&gt;not&lt;/strong&gt; need Node 26 runners to test the native path. Install&lt;br&gt;
&lt;code&gt;globalThis.Temporal&lt;/code&gt; before your package loads and any Node 18+ process behaves&lt;br&gt;
like a native one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// simulate-native.mjs — load with: node --import ./simulate-native.mjs app.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporal-polyfill/implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In ESM this must live in its &lt;strong&gt;own module&lt;/strong&gt;, imported first. Every import's body&lt;br&gt;
runs before the importing module's own statements, so an inline assignment lands&lt;br&gt;
after your package has already read the global.&lt;/p&gt;

&lt;p&gt;Working implementations of both matrices:&lt;br&gt;
&lt;a href="//../scripts/compat-matrix.mjs"&gt;&lt;code&gt;scripts/compat-matrix.mjs&lt;/code&gt;&lt;/a&gt; and&lt;br&gt;
&lt;a href="//../scripts/check-type-resolution.mjs"&gt;&lt;code&gt;scripts/check-type-resolution.mjs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6 — Let the runtime pick
&lt;/h2&gt;

&lt;p&gt;Once the above is in place, the actual "migration" is nothing. Feature-detect at&lt;br&gt;
load and pass through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Temporal&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;polyfillTemporal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;temporal-polyfill&lt;/code&gt; already does this internally, and so does&lt;br&gt;
&lt;code&gt;temporal-gregorian&lt;/code&gt;. Your Node 26 users get native &lt;code&gt;Temporal&lt;/code&gt; at zero overhead&lt;br&gt;
the day they upgrade; your Node 24 users notice nothing; and neither can hand the&lt;br&gt;
other a value your library chokes on.&lt;/p&gt;




&lt;h2&gt;
  
  
  See it fail, then see it work
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sina-heidariaan/temporal-gregorian
&lt;span class="nb"&gt;cd &lt;/span&gt;temporal-gregorian
npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build

npm run demo          &lt;span class="c"&gt;# polyfilled runtime&lt;/span&gt;
npm run demo:native   &lt;span class="c"&gt;# simulated Node 26 native runtime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="//../examples/mixed-runtime/demo.mjs"&gt;&lt;code&gt;examples/mixed-runtime/demo.mjs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Public types come from &lt;code&gt;temporal-spec&lt;/code&gt; (or &lt;code&gt;temporal-gregorian/types&lt;/code&gt;), not from a polyfill package.&lt;/li&gt;
&lt;li&gt;[ ] Zero &lt;code&gt;instanceof&lt;/code&gt; checks against Temporal classes.&lt;/li&gt;
&lt;li&gt;[ ] Foreign values are normalized once, at the API boundary.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;ZonedDateTime&lt;/code&gt; is rebuilt from &lt;code&gt;epochNanoseconds&lt;/code&gt;, not from an offset string.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;require()&lt;/code&gt; works on your lowest supported Node.&lt;/li&gt;
&lt;li&gt;[ ] CI covers Node × format × runtime × TypeScript × module resolution.&lt;/li&gt;
&lt;li&gt;[ ] The native path is tested, even without a Node 26 runner.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Found a case this guide misses, or a combination that still breaks? Please open an issue at &lt;a href="https://github.com/sina-heidariaan/temporal-gregorian/issues" rel="noopener noreferrer"&gt;temporal-gregorian/issues&lt;/a&gt;&lt;br&gt;
— real-world interop reports are what this package is for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>softwaredevelopment</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
