mrkeyoor.com_
Wed 05 Aug 05:06 UTC
npmUtilsupdated 04 Aug 2026

date-fns

A collection of 200+ pure functions for working with native JavaScript Date objects: formatting, parsing, math, comparisons, and intervals. There is no wrapper class; every function takes a Date and returns a new Date, so it drops into any codebase that already passes Dates around. It is fully tree-shakeable (you pay only for the functions you import), written in TypeScript, ships dozens of opt-in locales, and since v4 has first-class time zone support through the companion @date-fns/tz package.

Verdict

The default pick for date work in modern JavaScript: native Dates, tree-shaking, and solid TypeScript types with no lock-in. Reach for Luxon instead when time zone logic dominates your domain.

API stability4/5v3 (ESM rework) and v4 (time zones) were both breaking majors, but the everyday functions like format, parse, and addDays have kept their signatures for years.
Docs4/5date-fns.org documents every function with runnable examples; the hard part is discovering which of 200+ functions you need, and time zone docs live half in the @date-fns/tz repo.
Maintenance3/5Actively maintained but release cadence is bursty; last push was June 2026, roughly two months before this review, and issues can sit between release windows.
Ecosystem5/596M+ weekly downloads and first-class adapter support in date pickers and UI kits (MUI, Mantine, react-datepicker all ship date-fns adapters).

Use it if

  • You want tree-shakeable date helpers where importing format and addDays costs a few KB instead of shipping a whole date class
  • Your code already passes native Date objects around and you do not want to convert to and from a wrapper type at every boundary
  • You need localized formatting in only a few languages and want to bundle just the locales you import
  • You need time zone aware dates but want them as an opt-in extra (@date-fns/tz) rather than a heavier all-in-one library
Skip it if

Setup reality

npm install date-fns and you are done: zero dependencies, no peer deps, no config. The annoyances are elsewhere. The package went ESM-first in v3 and import paths changed across v2 to v3 to v4, so a lot of older Stack Overflow answers show stale deep-import styles. Locales are not automatic: you import each one and pass it to every format call that needs it. Time zones live in the separate @date-fns/tz package, which is easy to miss when you search the main docs. Format tokens follow the Unicode standard (yyyy, dd), so muscle memory from Moment (YYYY, DD) produces wrong output that still looks plausible.

Patterns

Format a date as a stringformat-date

import { format } from "date-fns";

format(new Date(2026, 7, 4), "yyyy-MM-dd");
// => "2026-08-04"
format(new Date(), "EEEE, MMMM do");
// => "Tuesday, August 4th"

Tokens are Unicode style: yyyy and dd, not Moment's YYYY and DD. YYYY means week-numbering year and will silently give wrong results around New Year.

Parse an ISO 8601 stringparse-iso-string

import { parseISO } from "date-fns";

const d = parseISO("2026-08-04T10:30:00Z");

Use parseISO for ISO strings instead of new Date(); it has consistent cross-browser behavior for partial dates.

Parse a string with a known formatparse-custom-format

import { parse, isValid } from "date-fns";

const d = parse("04/08/2026", "dd/MM/yyyy", new Date());
if (!isValid(d)) throw new Error("bad date");

The third argument is a reference date for missing parts. A failed parse returns an Invalid Date object, not an error, so always check isValid.

Add or subtract days, months, hoursadd-subtract-time

import { addDays, subMonths, addHours } from "date-fns";

const due = addDays(new Date(), 14);
const lastQuarter = subMonths(new Date(), 3);
const later = addHours(new Date(), 6);

Every function returns a new Date and never mutates its input.

Difference between two datesdifference-between-dates

import { differenceInDays, differenceInMinutes } from "date-fns";

differenceInDays(new Date(2026, 7, 10), new Date(2026, 7, 4));
// => 6

Results are truncated toward zero, not rounded: 47 hours apart is 1 day, not 2.

Compare and sort datescompare-and-sort

import { compareAsc, isBefore, isAfter } from "date-fns";

dates.sort(compareAsc);
isBefore(a, b); // true if a < b

compareAsc plugs straight into Array.prototype.sort; use compareDesc for newest-first.

Human relative time (3 days ago)relative-time

import { formatDistanceToNow } from "date-fns";

formatDistanceToNow(post.createdAt, { addSuffix: true });
// => "3 days ago"

Without addSuffix you get just "3 days", which reads wrong in most UIs.

Start or end of a day, month, weekstart-end-of-period

import { startOfDay, endOfMonth, startOfWeek } from "date-fns";

const dayStart = startOfDay(new Date());
const monthEnd = endOfMonth(new Date());
const weekStart = startOfWeek(new Date(), { weekStartsOn: 1 });

Weeks start on Sunday by default; pass weekStartsOn: 1 for Monday or a locale that implies it.

Every day in a rangeiterate-date-range

import { eachDayOfInterval } from "date-fns";

const days = eachDayOfInterval({
  start: new Date(2026, 7, 1),
  end: new Date(2026, 7, 7),
});
// => array of 7 Dates

The interval is inclusive on both ends; it throws RangeError if start is after end.

Is a date inside a rangecheck-within-interval

import { isWithinInterval } from "date-fns";

isWithinInterval(new Date(), {
  start: promo.startsAt,
  end: promo.endsAt,
});

Boundaries are inclusive; for exclusive checks combine isAfter and isBefore yourself.

Format in another languagelocalized-format

import { format } from "date-fns";
import { fr } from "date-fns/locale";

format(new Date(), "PPPP", { locale: fr });
// => "mardi 4 aout 2026"

Locales are opt-in imports, so only the ones you import end up in your bundle; there is no global locale setting by default (setDefaultOptions exists if you want one).

Work in a specific time zone (v4+)timezone-aware-date

import { TZDate } from "@date-fns/tz";
import { addHours, format } from "date-fns";

const kolkata = new TZDate(2026, 7, 4, 9, 0, "Asia/Kolkata");
format(addHours(kolkata, 4), "HH:mm zzz");

TZDate lives in the separate @date-fns/tz package (npm install @date-fns/tz); all date-fns functions accept it because it extends Date.

Alternatives

PackageRegistryPick it when
dayjsnpmYou want a tiny Moment-style chainable object API instead of standalone functions
luxonnpmTime zones, durations, and ISO 8601 handling are core requirements, not an add-on
momentnpmOnly for legacy codebases already on it; the project itself says it is done evolving