date-fns to Temporal
This codemod migrates common date-fns and date-fns-tz usage to Temporal equivalents. It rewrites a focused set of high-confidence patterns such as creating “now”, adding days, simple formatting, custom parsing, and timezone conversion.
It supports optional Temporal polyfill import insertion for environments that need it.
It also supports an optional AI-assisted fallback pass for unsupported date-fns / date-fns-tz patterns that remain after deterministic AST transforms.
Goal
- Replace common
date-fns/date-fns-tzAPIs with Temporal equivalents. - Remove transformed
date-fns/date-fns-tzimports where safe. - Keep transformed code explicit and readable.
- Cover practical migration paths while surfacing unsupported patterns for manual review.
Example
Replace format(new Date(), 'yyyy-MM-dd')
diff
Replace addDays(new Date(), n)
diff
Replace subDays(new Date(), n)
diff
Replace addYears(new Date(), n)
diff
Replace format(new Date(), 'PP')
diff
Replace parse(..., 'MM/dd/yyyy', new Date())
diff
Replace utcToZonedTime(new Date(ms), tz)
diff
What this codemod currently covers
format(new Date(), 'yyyy-MM-dd'),format(new Date(), 'PP'), andformat(new Date(), 'yyyy/MM/dd'), including broader call-expression contexts (not only variable assignments / inline logs).addDays(new Date(), n), including existing log replacement cases and broader call-expression contexts.subDays(new Date(), n), including declaration + log and call-expression contexts.addYears(new Date(), n),subYears(new Date(), n),addMonths(new Date(), n),subMonths(new Date(), n),addWeeks(new Date(), n),subWeeks(new Date(), n), including declaration + log and call-expression contexts.parse(value, 'MM/dd/yyyy', new Date()), including literal-string call-expression contexts.utcToZonedTime(new Date(epochMs), timezone), including broader call-expression contexts.- Import cleanup for transformed
date-fnsanddate-fns-tzusages.
Still unsupported (manual migration required)
formatDistance(date1, date2, [options])— no direct Temporal equivalent. UseIntl.RelativeTimeFormatwith manual duration calculation.formatRelative(date, baseDate, [options])— locale-dependent relative formatting has no clean Temporal mapping. Build relative output manually withTemporal.PlainDate.until()andIntl.RelativeTimeFormat.- Any date arithmetic API called with a variable/existing
Dateinstead ofnew Date()(e.g.,subDays(existingDate, n)) — convert the sourceDateto aTemporal.InstantorTemporal.ZonedDateTimefirst, then apply.add()/.subtract(). - Token-rich
date-fnsformatting/parsing beyond covered patterns requires manual migration.
Usage
Enable optional Temporal polyfill import insertion via workflow param:
- Param:
importPolyfill=true - Env (supported aliases):
IMPORT_POLYFILL=true,CODEMOD_IMPORT_POLYFILL=true, orPOLYFILL=true
When enabled, the transform attempts to inject:
- ESM:
import { Temporal } from '@js-temporal/polyfill'; - CJS:
const { Temporal } = require('@js-temporal/polyfill');
Enable optional AI-assisted fallback via workflow param:
- Param:
enableAiFallback=true - Env (supported aliases):
ENABLE_AI_FALLBACK=true,CODEMOD_ENABLE_AI_FALLBACK=true, orDATE_FNS_TEMPORAL_ENABLE_AI_FALLBACK=true
When enabled, the fallback runs after the deterministic AST step and only targets remaining unsupported date-fns / date-fns-tz usages in JS/TS files.
AI fallback runtime setup
DATE_FNS_TEMPORAL_AI_MODE=stub(recommended for CI): deterministic behavior, no network calls.DATE_FNS_TEMPORAL_AI_MODE=provider: reserved for provider-backed execution in your runtime wrapper.- Optional provider env vars for wrapper integrations:
OPENAI_API_KEY,OPENAI_MODEL.
This package ships with deterministic stub behavior by default so test/CI runs remain reproducible when a live AI runtime is not available.
AI fallback safety limits
- Fallback only processes leftover
date-fns/date-fns-tzpatterns not covered by the AST transform. - It does not rewrite already migrated
Temporalcode. - It applies only high-confidence rewrites; uncertain patterns receive
TODO(date-fns-to-temporal ai-fallback)comments instead of risky rewrites. - It is idempotent: rerunning does not keep changing the same code.
Caveats
- This codemod focuses on known fixture-backed patterns and skips unsupported call shapes.
- Token-rich
date-fnsformatting/parsing beyond covered patterns requires manual migration. - Low-confidence inputs are intentionally not rewritten deterministically (for example
addDays(existingDateVar, n)orparse(dynamicInput, 'MM/dd/yyyy', new Date())where the parsed value is not a literal string). - Manual guidance for unsupported leftovers:
- For
addDays(existingDateVar, n), convert the sourceDateto aTemporal.Instant(or zoned value) first, then apply.plus({ days: n }). - For
parse(dynamicInput, 'MM/dd/yyyy', new Date()), validate input shape, split into month/day/year parts, then buildTemporal.PlainDate.from(...). - For unsupported
format(...)token patterns, map tokens toIntl.DateTimeFormatoptions or Temporal field access explicitly.
- For
- Temporal API usage may still require project-level runtime decisions (native Temporal vs polyfill).
- AI fallback currently includes deterministic fixture-backed rewrites plus TODO annotations for unsupported shapes.
- Always review transformed code and run your app/tests after migration.