Skip to main content

date-fns v4 vs Temporal API vs Day.js: Date Handling in JavaScript 2026

·PkgPulse Team

Chrome 144 shipped the Temporal API natively in January 2026 — the first JavaScript standard for date handling in 30 years that actually works correctly. Firefox 139 shipped it in May 2025. date-fns v4 added first-class time zone support via @date-fns/tz. Day.js remains the lightweight Moment.js drop-in with 2KB gzipped. JavaScript's date handling landscape is finally mature — the question now is whether to use a library or wait for Temporal to reach full browser coverage.

TL;DR

date-fns v4 for most Node.js and browser projects — 24M weekly downloads, tree-shakeable, excellent TypeScript, now with native time zone support via @date-fns/tz. Day.js when you need a Moment.js drop-in replacement with minimal bundle size and a familiar chained API. Temporal API for new browser-only code where you can use a polyfill — it's the correct long-term answer for JavaScript date handling and is now natively supported in Chrome and Firefox. For Node.js services, date-fns v4 is still the default. For browser apps in 2026, consider adding the Temporal polyfill now.

Key Takeaways

  • date-fns v4: 24M weekly downloads, tree-shakeable, first-class time zone support via @date-fns/tz
  • Day.js: 15M weekly downloads, 2KB gzipped, Moment.js-compatible API, plugin system
  • Temporal API: Chrome 144 (January 2026) + Firefox 139 — ES2026 standard, no library needed
  • Temporal vs Date: Immutable, explicit time zones, no DST bugs, supports calendar systems (Gregorian, Hebrew, Chinese, etc.)
  • Moment.js: Still 23M weekly downloads but deprecated — migrate to date-fns or Day.js
  • Luxon: 4M weekly downloads, Moment.js successor by the same creator, Temporal-influenced API
  • For edge runtimes: All three work in Cloudflare Workers (Web Standards environments)

The Date Problem in JavaScript

JavaScript's Date object is notoriously broken:

// Problems with JavaScript's Date:

// 1. Months are 0-indexed
new Date(2026, 0, 1)  // January 1st (month 0!)
new Date(2026, 11, 31) // December 31st (month 11!)

// 2. DST bugs — this might not be 24 hours:
const a = new Date('2024-03-10 00:00');
const b = new Date('2024-03-11 00:00');
(b - a) / 1000 / 60 / 60  // 23! DST transition

// 3. Timezone confusion — always local time unless you specify Z
new Date('2026-03-08')        // midnight UTC
new Date('2026-03-08T00:00')  // midnight LOCAL time

// 4. Mutable — methods modify in place
const date = new Date();
date.setMonth(date.getMonth() + 1);  // Mutates!

Libraries and now the Temporal API solve these problems.

date-fns v4

Package: date-fns Weekly downloads: 24M GitHub stars: 35K Creator: Sasha Koss

date-fns is the most-used JavaScript date library. Its key design principle: pure functions that take Date objects, transform them, and return new Date objects — no classes, no mutation, fully tree-shakeable.

Installation

npm install date-fns
npm install date-fns-tz  # For time zone support (v3)
# Or in v4, use @date-fns/tz for new time zone API
npm install @date-fns/tz

Basic Usage

import {
  format,
  addDays,
  differenceInDays,
  startOfWeek,
  endOfMonth,
  isWithinInterval,
  parseISO,
} from 'date-fns';

// Format
format(new Date(2026, 2, 8), 'MMMM do, yyyy');  // "March 8th, 2026"
format(new Date(), "yyyy-MM-dd'T'HH:mm:ss");     // ISO format

// Arithmetic
const nextWeek = addDays(new Date(), 7);
const daysLeft = differenceInDays(new Date('2026-12-31'), new Date());

// Ranges
const weekStart = startOfWeek(new Date(), { weekStartsOn: 1 }); // Monday
const monthEnd = endOfMonth(new Date());

// Parsing
const parsed = parseISO('2026-03-08T12:00:00Z');

// Range check
isWithinInterval(new Date(), { start: weekStart, end: monthEnd });

v4: First-Class Time Zone Support

date-fns v4's headline feature is native time zone support via @date-fns/tz:

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

// TZDate: a Date subclass that respects a specific timezone
const nyMidnight = new TZDate(2026, 2, 8, 0, 0, 0, 'America/New_York');
const laTime = new TZDate(2026, 2, 8, 0, 0, 0, 'America/Los_Angeles');

// format in the date's own timezone (not local)
format(nyMidnight, 'yyyy-MM-dd HH:mm zzz');  // "2026-03-08 00:00 EST"
format(laTime, 'yyyy-MM-dd HH:mm zzz');      // "2026-03-08 00:00 PST"

// Arithmetic respects DST
const tomorrow = addDays(nyMidnight, 1);  // Correctly handles DST transitions

// tz helper for context option (alternative syntax)
format(new Date(), 'yyyy-MM-dd HH:mm', {
  in: tz('America/New_York'),
});

Tree Shaking

date-fns's functional design means only the functions you import get bundled:

// If you only import format and addDays:
import { format } from 'date-fns';
import { addDays } from 'date-fns';

// Bundle: only those two functions (~2KB each)
// vs. Moment.js: ~72KB regardless of what you use

Locale Support

import { format, formatDistance } from 'date-fns';
import { es, ja, de } from 'date-fns/locale';

format(new Date(), 'MMMM do, yyyy', { locale: es });
// "marzo 8vo, 2026"

formatDistance(addDays(new Date(), 3), new Date(), { locale: ja });
// "3日後"

Day.js

Package: dayjs Weekly downloads: 15M GitHub stars: 48K Creator: iamkun

Day.js is the minimal Moment.js replacement. It's ~2KB gzipped and uses an almost identical API to Moment.js, making migration from Moment trivial.

Installation

npm install dayjs

Basic Usage

import dayjs from 'dayjs';

// Familiar chained API (Moment.js-style)
dayjs('2026-03-08').format('MMMM D, YYYY');  // "March 8, 2026"
dayjs().add(7, 'day').format('DD/MM/YYYY');
dayjs().startOf('month').toDate();
dayjs('2026-01-01').isBefore(dayjs());  // true
dayjs().diff(dayjs('2026-01-01'), 'day');  // Number of days since Jan 1

Plugin System

Day.js's core is immutable — features are added via plugins:

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';
import duration from 'dayjs/plugin/duration';

dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
dayjs.extend(duration);

// UTC
dayjs.utc('2026-03-08T12:00:00Z').format();

// Timezone
dayjs.tz('2026-03-08', 'America/New_York').format();
dayjs().tz('Asia/Tokyo').format('HH:mm');

// Relative
dayjs('2025-01-01').fromNow();  // "a year ago"

// Duration
dayjs.duration(90, 'minutes').humanize();  // "2 hours"

Bundle Comparison

Day.js core:        2.6 KB gzipped
day.js + utc:       ~3.5 KB gzipped
day.js + timezone:  ~4.5 KB gzipped (also loads a small tz data file)

date-fns (format + addDays):  ~5 KB gzipped
date-fns full:                ~80 KB (if you import everything)

Moment.js:          72 KB gzipped (always)

Day.js Limitations

  • Plugin system — you must remember to extend before using plugin features
  • Less TypeScript-native than date-fns (types are separate @types/dayjs historically)
  • Time zone support requires the dayjs-plugin-timezone which depends on the browser's Intl.DateTimeFormat API

Temporal API

Status: Stage 3 TC39 (ES2026) Browser support: Chrome 144+ (Jan 2026), Firefox 139+ (May 2025) Node.js: v23+ (experimental flag), polyfill available

The Temporal API is the official JavaScript standard for date and time handling — the first major update to JavaScript dates since the original broken Date object. It's been a decade in the making.

Why Temporal Fixes Date

// Temporal is immutable
const now = Temporal.Now.plainDateISO();
const tomorrow = now.add({ days: 1 });
// `now` is unchanged, `tomorrow` is a new object

// Explicit time zones — no ambiguity
const nyNow = Temporal.Now.zonedDateTimeISO('America/New_York');
const tokyoNow = Temporal.Now.zonedDateTimeISO('Asia/Tokyo');

// Correct DST handling
const dstStart = Temporal.ZonedDateTime.from('2026-03-08T01:00[America/New_York]');
const dstEnd = dstStart.add({ hours: 2 });
// Correctly handles the 2am → 3am DST transition

// Calendar systems
const hebrewDate = Temporal.PlainDate.from({
  year: 5786,
  month: 6,
  day: 8,
  calendar: 'hebrew',
});

Temporal Types

// PlainDate: date without time or timezone (2026-03-08)
const date = Temporal.PlainDate.from('2026-03-08');
date.year   // 2026
date.month  // 3
date.day    // 8

// PlainTime: time without date or timezone (14:30:00)
const time = Temporal.PlainTime.from('14:30:00');

// PlainDateTime: date + time without timezone
const dt = Temporal.PlainDateTime.from('2026-03-08T14:30:00');

// ZonedDateTime: full date + time + timezone (the most useful)
const zdt = Temporal.ZonedDateTime.from('2026-03-08T14:30:00[America/New_York]');
zdt.timeZoneId  // "America/New_York"
zdt.epochSeconds  // Unix timestamp

// Instant: a specific moment in time (like Date)
const now = Temporal.Now.instant();
now.epochSeconds  // Unix timestamp

// Duration: arithmetic type
const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
date.add(duration);

Using Temporal Today (Polyfill)

npm install temporal-polyfill  # 20KB gzipped
# or
npm install @js-temporal/polyfill  # Official TC39 polyfill
// Feature detection + polyfill
if (typeof Temporal === 'undefined') {
  const { Temporal, Intl } = await import('temporal-polyfill');
  globalThis.Temporal = Temporal;
}

// Or just import always:
import { Temporal } from 'temporal-polyfill';

const now = Temporal.Now.zonedDateTimeISO('America/New_York');
const formatted = now.toLocaleString('en-US', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});

Comparison

Featuredate-fns v4Day.jsTemporal API
Weekly downloads24M15M(native)
Bundle sizeTree-shakeable (~2-5KB used)2.6KB + plugins20KB (polyfill)
Time zone support@date-fns/tzPluginNative (built-in)
TypeScriptExcellentGoodExcellent
ImmutabilityYes (pure functions)YesYes
DST handlingWith @date-fns/tzWith timezone pluginNative, correct
Calendar systemsNoNoYes (15+ calendars)
API styleFunctionalChained (OOP)OOP
Browser nativeNoNoChrome 144+, Firefox 139+

When to Use Each

Choose date-fns v4 if:

  • Node.js server-side work
  • You want the largest community and most Stack Overflow answers
  • Tree-shaking is important and you use only a subset of functions
  • You need locale/i18n support for date formatting
  • TypeScript-first development with excellent type inference

Choose Day.js if:

  • Migrating from Moment.js (nearly identical API)
  • Bundle size is critical and you only need a few date operations
  • You prefer a chained, OOP-style API
  • Your project doesn't need advanced time zone arithmetic

Choose Temporal API if:

  • Building browser-only features (Chrome 144+ and Firefox 139+ = most users)
  • You can add a polyfill (temporal-polyfill, ~20KB)
  • Correct DST handling is critical
  • You need calendar system support (Hebrew, Chinese, Japanese, etc.)
  • You want to avoid a library dependency long-term

Compare date library downloads on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.