mrkeyoor.com_
Sat 19 Sept 08:56 UTC
npmUtilsupdated 19 Sept 2026

date-fns review

date-fns 4.4.0 is a large catalog of standalone functions for native JavaScript Date objects. It covers strict parsing, display formatting, comparisons, date arithmetic, intervals, relative labels, and locale-aware calendar rules without introducing its own everyday date class. Version 4 can preserve a date extension such as TZDate from the separate @date-fns/tz package, which makes named-zone calculations possible while keeping the familiar function calls. Version 4.4 moves browser CDN files into @date-fns/cdn and deprecates the CDN scripts inside the main package. Our whole-package browser test measured 71.7 KB minified and 17.9 KB gzipped, so selective imports still matter.

89.1Mdownloads / wk
Verdict

date-fns 4.4.0 installed in 2.8 seconds with 0 dependencies and 0 audit findings, but the full package occupied 28 MB and bundled to 17.9 KB gzipped in our sandbox. Install it for native-Date applications that need many explicit calendar operations; use Intl alone for display-only work and Luxon when zone identity belongs on every value.

We installed it

Lab card: what happened when we installed date-fnsScreenshot of date-fns documentation
Install✓ · 2.8s1 package on disk · 28 MB
ImportESM import works · require() works · ESM package with exports map
Browser17.9 KBgzipped (71.7 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does date-fns install cleanly?

Yes. In a fresh container with an empty cache, npm install date-fns finished in 3 seconds, leaving 1 package and 28 MB on disk. npm audit reported no known vulnerabilities.

How much does date-fns add to a browser bundle?

17.9 KB gzipped (71.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does date-fns work with both ESM and CommonJS?

Yes. Both import 'date-fns' and require('date-fns') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does date-fns include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

date-fns or dayjs: which should you use?

dayjs: Choose it when a small chainable API and Moment-like plugins suit the team's existing habits. date-fns 4.4.0 installed in 2.8 seconds with 0 dependencies and 0 audit findings, but the full package occupied 28 MB and bundled to 17.9 KB gzipped in our sandbox.

When should you not use date-fns?

Every timestamp must retain its IANA zone through the application. Luxon's DateTime stores that identity directly, while a plain Date passed to date-fns does not.

API stability4/5The public function-per-operation design has stayed recognizable across major releases, and version 4 extended those calls to date subclasses rather than replacing them. Migration risk lives around packaging and semantics: deep internal import paths are outside the public exports map, v4.4 deprecates bundled CDN scripts, and formatting tokens still differ from Moment. Named public exports and documented Unicode tokens are the stable boundary.
Docs4/5The official reference gives signatures, option types, return values, examples, and boundary descriptions for more than 200 functions. That detail is enough to settle whether an interval includes its endpoints or which week rule applies. Finding the right function can take longer because elapsed, calendar, ISO-week, and business-day variants sit close together, and named-zone guidance also requires reading the companion @date-fns/tz documentation.
Maintenance4/5Release 4.4.0 was published on May 29, 2026, and the repository was pushed on August 10, 2026. GitHub reports 999 open issues and pull requests, which is a sizable queue, but the project is unarchived and current work includes packaging changes plus the version 4 time-zone extension. The active release line is clear; response time for obscure locale or boundary reports is less predictable.
Ecosystem5/5npm counted 101,668,048 downloads in the latest completed week and GitHub reports 36,647 stars. Date pickers and component libraries commonly provide date-fns adapters, the package ships its own TypeScript declarations, and both CommonJS require and ESM import worked in our Node 22 check. The separate @date-fns/tz and @date-fns/cdn packages cover named zones and script-tag delivery without adding core dependencies.

Discussed on

  1. hnDate-fns v2 is out9 points
  2. hnDate-fns 4.08 points
  3. hnDate-fns: Modern JavaScript date utility library8 points
  4. hnHow date-fns came about4 points
  5. hnDate-fns v2 beta is out4 points

Use it if

  • Your data layer already returns native Date values and you need explicit functions for arithmetic, comparison, formatting, or intervals.
  • Frontend code can import only the functions and locale data it uses instead of shipping the full catalog.
  • Calendar rules such as week starts, month boundaries, business-day offsets, and inclusive intervals need readable named operations.
  • You need occasional IANA-zone calculations and are willing to add @date-fns/tz rather than move every date into a wrapper type.
Skip it if

Setup reality

Our install of date-fns 4.4.0 finished in 2.8 seconds inside a fresh Node 22 Bookworm container. It left one package using 28 MB, declared no direct or peer dependencies, and npm audit reported 0 known vulnerabilities. The package is ESM with an exports map, yet both require() and ESM import worked in our checks. TypeScript declarations are bundled. A browser build that imported the entire public package produced 71.7 KB minified and 17.9 KB gzipped.

Use named public exports and import each locale from date-fns/locale. Locale choice is normally passed in an options object, as is weekStartsOn when the product rule must stay fixed. Version 4.4 deprecates the main package's CDN scripts; projects using script tags should move to @date-fns/cdn before version 5 removes them. The release also removes CDN source maps from date-fns itself.

The formatter follows Unicode field tokens. yyyy means calendar year and dd means day of month; Moment-style uppercase tokens do not mean the same thing. parse() requires a reference date to fill fields absent from the input and can produce Invalid Date, so pair it with isValid(). These details create bugs that look correct during most of the year and fail near a boundary.

Plain Date values still display in the host zone. Add @date-fns/tz and use TZDate when an operation must follow an IANA zone across daylight-saving changes. The interval helpers include endpoints unless a specific function says otherwise, while differenceInDays and differenceInCalendarDays answer different questions. Read the chosen function's boundary rule before using it for billing or expiry logic.

Patterns

Format with Unicode date tokens format-date

import { format } from 'date-fns';

const label = format(new Date(2026, 7, 22), 'yyyy-MM-dd');
// '2026-08-22'

In date-fns 4, lowercase `yyyy` is the calendar year and `dd` is the day of month. Moment's uppercase tokens carry different meanings.

Parse a supported ISO value parse-iso

import { parseISO, isValid } from 'date-fns';

const value = parseISO('2026-08-22T14:30:00Z');
if (!isValid(value)) throw new Error('Invalid timestamp');

parseISO avoids the runtime differences associated with passing partial date strings directly to the Date constructor.

Parse a fixed human date format parse-known-format

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

const value = parse('22/08/2026', 'dd/MM/yyyy', new Date());
if (!isValid(value)) throw new Error('Expected DD/MM/YYYY');

The third argument fills fields missing from the input. parse can return Invalid Date, so validate its result before storing it.

Add calendar days without mutation add-calendar-days

import { addDays } from 'date-fns';

const createdAt = new Date('2026-08-22T10:00:00Z');
const expiresAt = addDays(createdAt, 14);

addDays returns a new Date. The input object remains unchanged.

Sort dates from oldest to newest compare-dates

import { compareAsc } from 'date-fns';

const ordered = [...dates].sort(compareAsc);

Copy the array first when callers must retain its original order; Array.sort mutates the array even though date-fns does not mutate Date inputs.

Count crossed calendar dates measure-calendar-days

import { differenceInCalendarDays } from 'date-fns';

const days = differenceInCalendarDays(checkout, checkin);

differenceInCalendarDays removes time components before comparing dates. Use differenceInDays when complete local-day periods are the rule.

Enumerate an inclusive date interval build-date-range

import { eachDayOfInterval } from 'date-fns';

const days = eachDayOfInterval({
  start: new Date(2026, 7, 1),
  end: new Date(2026, 7, 7),
});

The returned array includes both August 1 and August 7. A long interval allocates one Date for every day.

Start weeks on Monday set-week-boundary

import { startOfWeek } from 'date-fns';

const monday = startOfWeek(new Date(), { weekStartsOn: 1 });

Pass weekStartsOn when the product has a fixed rule. Otherwise locale defaults can change the result.

Format with one imported locale format-locale

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

const label = format(new Date(2026, 7, 22), 'd MMMM yyyy', { locale: fr });

Importing a single locale keeps unrelated locale data out of a tree-shaken browser build.

Render a relative timestamp show-relative-time

import { formatDistanceToNow } from 'date-fns';

const label = formatDistanceToNow(message.createdAt, { addSuffix: true });

The output is human text such as `3 days ago`; do not persist it because it changes as the clock moves.

Check whether intervals overlap test-overlap

import { areIntervalsOverlapping } from 'date-fns';

const conflicts = areIntervalsOverlapping(first, second, { inclusive: true });

With inclusive true, intervals that meet at one endpoint count as overlapping. Omit it if adjacent bookings are allowed.

Run calendar math in an IANA zone calculate-in-time-zone

import { addDays, startOfDay } from 'date-fns';
import { TZDate } from '@date-fns/tz';

const berlin = new TZDate(2026, 2, 28, 12, 0, 0, 'Europe/Berlin');
const nextStart = startOfDay(addDays(berlin, 1));

TZDate comes from the separate @date-fns/tz package. A plain Date does not retain the Europe/Berlin identity.

Alternatives

PackageRegistryPick it when
dayjsnpmChoose it when a small chainable API and Moment-like plugins suit the team's existing habits.
luxonnpmChoose it when zones, durations, and intervals should be represented by dedicated objects.
momentnpmKeep it for an established codebase that already relies on Moment's mutable API and plugin behavior.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.