Migration guide

    Moment.js → date-fns, step by step.

    Moment.js has been in maintenance mode for years, and its 60kB is hard to justify when date-fns tree-shakes down to just the functions you import. Three things change how you write date code: Moment mutates and date-fns doesn't, the format tokens are different, and locales become an explicit import. Here's every step.

    Refactyl doesn't migrate Moment.js to date-fns yet. This guide covers the manual path; the waitlist notifies you when the verified engine ships.

    Why this migration is hard to get right

    Moment is mutable; date-fns is pure

    Moment objects mutate in-place: moment().add(1, 'day').subtract(2, 'hours') chains mutate the same object. date-fns functions are pure. Each returns a new Date, leaving the original unchanged. Code that stores a moment, mutates it, and reads it later must be refactored to capture the return value of each operation.

    Format string syntax differs

    Moment uses uppercase tokens: YYYY for 4-digit year, DD for day, hh for 12-hour. date-fns uses Unicode Technical Standard #35 (like Intl.DateTimeFormat): yyyy, dd, hh. The difference is subtle and breaks silently: format(date, 'YYYY-MM-DD') in date-fns produces wrong output because YYYY means 'local week year', not calendar year. Every format string needs mechanical translation.

    Locale handling is completely different

    Moment has a global locale (moment.locale('fr')) that affects all instances. date-fns requires explicit locale import and pass-through: format(date, 'PPP', { locale: fr }). Code that relied on moment's global locale switch, especially i18n-heavy apps, must thread the locale through every date-formatting call.

    How to migrate manually

      1

      Install date-fns, audit moment usage

      Install date-fns. Run a codebase search for `import moment` and `require('moment')` to enumerate all call sites. Count the unique moment methods you use. The full API is large but most projects use 10–15 functions.

      2

      Replace moment() constructors

      moment() → new Date() or Date.now(). moment('2024-01-01') → parseISO('2024-01-01') from date-fns. moment(timestamp) → new Date(timestamp). moment.utc() → use date-fns-tz and toZonedTime. moment.isMoment(x) → x instanceof Date.

      3

      Translate format strings

      YYYY → yyyy, MM → MM, DD → dd, HH → HH (24h), hh → hh (12h), mm → mm, ss → ss, dddd → EEEE, ddd → EEE. Run a search-replace for the format string content (not the function calls, just the string arguments). Verify a sample set with actual dates: a wrong token often produces a plausible but wrong result.

      4

      Replace date operations

      moment(x).add(n, 'days') → addDays(x, n). subtract → subDays/subMonths/subYears. .diff(y, 'days') → differenceInDays(x, y). .isBefore(y) → isBefore(x, y). .isSame(y, 'day') → isSameDay(x, y). .startOf('month') → startOfMonth(x). Each is a named date-fns function; import only what you use.

      5

      Fix locale handling

      Import locales: `import { fr } from 'date-fns/locale'`. Pass as the third argument to format(), formatRelative(), formatDistance(). If you had moment.locale() calls in an i18n setup, centralize the locale object in your i18n provider and pass it down to date-formatting utilities.

    What Refactyl's verified version will do

    Roadmap

    Refactyl doesn't run this migration yet. When it ships, the bar will be the same as every other Refactyl path: every file gated by a real tsc --noEmit (date-fns is fully typed) run, repaired until clean, or clearly flagged with the reason. No unverified files ship.

    The public benchmark methodology is at refactyl.com/benchmark . The same dual-metric approach will apply here.

    Manual / DIY vs Refactyl (roadmap)

    This table describes what Refactyl's Moment.jsdate-fns engine will do. It isn't live yet.

    ConcernManual / DIYRefactyl (roadmap)
    Bundle sizeMoment: ~60kB gzipped (always the full library)date-fns: tree-shaken to only the functions you import, typically 5–15kB
    Format string translationManual YYYY/DD token audit, easy to miss the YYYY vs yyyy distinctionWill detect and translate every format string, including template literal constructions
    Mutation to pure refactorEach chained mutation must be manually identified and splitWill detect mutation chains and refactor them to explicit intermediate variables
    TypeScript typesMoment has types; date-fns types are stricter, so new errors surfaceType errors from the migration will be repaired in the same pass (uses the tsc gate)

    FAQ

    Moment.js → date-fns migration: common questions

    Real questions engineers ask before migrating Moment.js to date-fns, answered honestly.

    On the roadmap

    Want Refactyl to do this for you?

    Join the waitlist and we'll notify you when the Moment.jsdate-fns engine ships. The bar is the same as every other Refactyl path: every file gated by a real tsc --noEmit (date-fns is fully typed) run before handoff.

    Join the waitlist →

    Refactyl doesn't migrate Moment.js to date-fns yet. No misleading trial, no fake demo.