temporal-polyfill-codemod

August 13, 2026 · View on GitHub

Automated migrations for temporal-polyfill codebases.

The current transform, fns-to-temporal, rewrites code that uses the tree-shakeable temporal-polyfill/fns function API into idiomatic Temporal — real Temporal objects and their methods.

// Before
import * as PlainDateFns from 'temporal-polyfill/fns/PlainDate'
const date = PlainDateFns.create(2024, 5, 1)
const next = PlainDateFns.addDays(date, 3)

// After
const date = new Temporal.PlainDate(2024, 5, 1)
const next = date.add({ days: 3 })

Why migrate

The temporal-polyfill/fns API is great for bundle size — every function is independently tree-shakeable — but it operates on plain record objects, not real Temporal instances. As Temporal ships natively across browsers and runtimes, the polyfill stops being necessary and standard, spec-shaped Temporal becomes the natural way to write this code. This codemod does the mechanical work of converting an entire codebase over.

The transform targets the global Temporal object. It does not add import { Temporal } from 'temporal-polyfill' — wire up your Temporal source (global polyfill or native) however your project prefers.

Quick start

Run it against a file or directory:

npx temporal-polyfill-codemod fns-to-temporal <path>

Preview the changes without touching your files:

temporal-polyfill-codemod fns-to-temporal <path> --dry --print

Directories are walked recursively; node_modules, dist, and dot-directories are skipped. Supported extensions: js, jsx, ts, tsx, mjs, cjs, mts, cts.

The codemod preserves your formatting where it can but doesn't run a formatter — run yours afterward.

What it migrates

The transform covers the common shapes you'll have across a codebase:

Constructors and methodsfns calls become constructors and instance methods:

PlainDateFns.create(2024, 5, 1)        // → new Temporal.PlainDate(2024, 5, 1)
PlainDateFns.addDays(date, 3)          // → date.add({ days: 3 })
PlainDateFns.compare(a, b)             // → Temporal.PlainDate.compare(a, b)

Calendar records → calendar IDs — in slots where Temporal expects a calendar ID string:

PlainDateFns.create(2024, 5, 1, CalendarFns.getBuddhist())
// → new Temporal.PlainDate(2024, 5, 1, 'buddhist')

Types — record and option types are rewritten to their Temporal equivalents (falling back to temporal-utils where Temporal has no equivalent):

type DateValue = PlainDateRecord     // → Temporal.PlainDate
type CalendarValue = CalendarRecord  // → string

Type guardsisRecord checks become instanceof:

if (PlainDateFns.isRecord(value)) { /* ... */ }
// → if (value instanceof Temporal.PlainDate) { /* ... */ }

Some fns helpers have no direct Temporal equivalent and are rewritten to temporal-utils instead. When that happens the codemod prints a note so you can add the dependency — it won't edit your package.json. If the import would collide with a local name, it's aliased automatically.

When it can't migrate something

The codemod is deliberately conservative: it only rewrites code when the intended Temporal expression is unambiguous from the syntax. Anything it isn't sure about — function references passed around as values, dynamic/computed property access, namespace destructuring, roundTo* calls whose options already conflict with the implied unit — is left unchanged and reported as a diagnostic.

Because leftover fns records aren't interchangeable with real Temporal objects, these diagnostics are migration-blocking by default: the run prints every issue it found and exits with code 1 so CI catches an incomplete migration. Pass --allow-warnings to inspect partial results without the nonzero exit. A file that can't be parsed is reported individually and doesn't stop the rest of the run.

Example diagnostic:

warning: ZonedDateTime roundToHour options object already has smallestUnit; manual review needed
warning: Untransformed ZonedDateTimeFns.roundToHour usage

Work through the reported spots by hand, then re-run until the codemod is clean.

TypeScript

Types are rewritten to global Temporal types, but the codemod doesn't install or inject Temporal type declarations. If your project doesn't already provide global Temporal types, set that up separately.

Development

cd codemod
pnpm run lint
pnpm run test
pnpm run build

The full transform contract — every supported import path, every deferred shape, and the exit-code matrix — lives in ARCHITECTURE.md.