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-tz APIs with Temporal equivalents.
- Remove transformed date-fns / date-fns-tz imports 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'), and format(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-fns and date-fns-tz usages.
Still unsupported (manual migration required)
- formatDistance(date1, date2, [options]) — no direct Temporal equivalent. Use Intl.RelativeTimeFormat with manual duration calculation.
- formatRelative(date, baseDate, [options]) — locale-dependent relative formatting has no clean Temporal mapping. Build relative output manually with Temporal.PlainDate.until() and Intl.RelativeTimeFormat.
- Any date arithmetic API called with a variable/existing Date instead of new Date() (e.g., subDays(existingDate, n)) — convert the source Date to a Temporal.Instant or Temporal.ZonedDateTime first, then apply .add()/.subtract().
- Token-rich date-fns formatting/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, or POLYFILL=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, or DATE_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-tz patterns not covered by the AST transform.
- It does not rewrite already migrated Temporal code.
- 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-fns formatting/parsing beyond covered patterns requires manual migration.
- Low-confidence inputs are intentionally not rewritten deterministically (for example addDays(existingDateVar, n) or parse(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 source Date to a Temporal.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 build Temporal.PlainDate.from(...).
- For unsupported format(...) token patterns, map tokens to Intl.DateTimeFormat options or Temporal field access explicitly.
- 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.