Skip to main content

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

·PkgPulse Team

TL;DR

date-fns v4 (with TypeScript and tree-shaking) is the 2026 default for date manipulation — 13KB, functional, no mutation. The Temporal API is now in Stage 3 (polyfill available) and will eventually replace Date built-in. Day.js is still useful for its Moment.js-compatible API (minimal migration). Avoid: Moment.js (immutable values, 70KB), Luxon (overkill unless heavy timezone work).

Key Takeaways

  • date-fns v4: ~13KB tree-shaken, pure functions, TypeScript, best ecosystem
  • Temporal API: Future web standard, no mutation, proper timezone handling
  • Day.js: Moment.js drop-in, 2KB core, plugin system
  • date-fns v4 vs v3: Now supports Temporal natively, fp submodule improved
  • Bundle sizes: date-fns (13KB used) < Day.js (2KB + plugins) < Luxon (24KB) << Moment (70KB)

Downloads

PackageWeekly DownloadsTrend
date-fns~30M→ Stable
dayjs~15M→ Stable
luxon~8M↓ Declining
moment~12M↓ Declining (legacy)

date-fns v4: The Default Choice

import {
  format, formatDistance, formatRelative,
  add, sub, differenceInDays, differenceInHours,
  isAfter, isBefore, isSameDay,
  startOfDay, endOfDay, startOfWeek, startOfMonth,
  parseISO, parse,
  isValid,
} from 'date-fns';
import { toZonedTime, formatInTimeZone } from 'date-fns-tz';

// Basic formatting:
const now = new Date();
format(now, 'yyyy-MM-dd');            // "2026-03-09"
format(now, 'MMMM d, yyyy');         // "March 9, 2026"
format(now, 'h:mm a');               // "3:45 PM"
format(now, "yyyy-MM-dd'T'HH:mm:ssXXX");  // ISO 8601

// Date arithmetic (immutable — always returns new Date):
const nextWeek = add(now, { weeks: 1 });
const lastMonth = sub(now, { months: 1 });
const threeHoursLater = add(now, { hours: 3, minutes: 30 });

// Relative time:
formatDistance(new Date('2026-03-01'), now, { addSuffix: true });
// "8 days ago"

formatRelative(new Date('2026-03-09'), now);
// "today at 3:45 PM"

// Comparisons:
isAfter(nextWeek, now);   // true
isBefore(lastMonth, now); // true
isSameDay(now, new Date('2026-03-09'));  // true

// Range helpers:
const start = startOfWeek(now, { weekStartsOn: 1 });  // Monday
const end = endOfDay(now);
differenceInDays(end, start);  // 0 to 6

// Parse:
const date = parseISO('2026-03-09T15:45:00Z');  // ISO string → Date
const dateFromString = parse('09/03/2026', 'dd/MM/yyyy', new Date());
isValid(dateFromString);  // true
// Timezone handling with date-fns-tz:
import { toZonedTime, fromZonedTime, formatInTimeZone } from 'date-fns-tz';

const utcDate = new Date('2026-03-09T15:45:00Z');
const userTz = 'America/New_York';

// Convert UTC → user timezone:
const localDate = toZonedTime(utcDate, userTz);
format(localDate, 'h:mm a');  // "10:45 AM"

// Format directly in timezone:
formatInTimeZone(utcDate, userTz, 'yyyy-MM-dd HH:mm zzz');
// "2026-03-09 10:45 EST"

// Convert user input → UTC for storage:
const inputDate = parse('2026-03-09 10:45', 'yyyy-MM-dd HH:mm', new Date());
const utcForStorage = fromZonedTime(inputDate, userTz);

Temporal API (Future Standard)

// Temporal API — Stage 3, polyfill available:
// npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';

// PlainDate — no time, no timezone:
const today = Temporal.Now.plainDateISO();  // 2026-03-09
const birthday = Temporal.PlainDate.from('1990-06-15');
const yearsOld = today.since(birthday, { largestUnit: 'years' }).years;

// Temporal.ZonedDateTime — the full type:
const now = Temporal.Now.zonedDateTimeISO('America/New_York');
console.log(now.toString());  // "2026-03-09T10:45:00-05:00[America/New_York]"

// Arithmetic (immutable — returns new instances):
const nextWeek = now.add({ weeks: 1 });
const lastMonth = now.subtract({ months: 1 });

// No timezone confusion:
// Date DST + arithmetic = bugs; Temporal handles DST correctly:
const springForward = Temporal.ZonedDateTime.from('2026-03-08T01:00:00[America/New_York]');
const twoHoursLater = springForward.add({ hours: 2 });
// Correctly returns 04:00 (skips 02:00 DST gap)

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

Day.js: Moment.js Drop-In

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

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

// Moment.js-compatible API:
dayjs().format('YYYY-MM-DD');           // "2026-03-09"
dayjs().format('MMMM D, YYYY');         // "March 9, 2026"
dayjs('2026-03-01').fromNow();          // "8 days ago"
dayjs().add(1, 'week').toDate();        // Date object
dayjs().subtract(1, 'month').toDate();

// Timezone:
dayjs().tz('America/New_York').format('h:mm A');  // "10:45 AM"
dayjs.utc('2026-03-09T15:45:00Z').tz('America/New_York').format();

Benchmark Comparison

Bundle size (minified + gzipped, typical usage):
  date-fns (tree-shaken, 10 fns):  ~8KB
  Day.js (core):                    2KB + plugins
  date-fns (all imported):         ~13KB
  date-fns-tz:                     +3KB for timezone
  Luxon:                            24KB
  Moment.js:                        70KB (avoid)

Temporal polyfill:                  35KB (will be 0 when native)

Performance (1M date operations):
  date-fns:    ~85ms
  Day.js:      ~120ms (wrapper overhead)
  Temporal:    ~95ms (polyfill overhead)

Decision Guide

Use date-fns v4 if:
  → Default choice for TypeScript projects
  → Need tree-shaking (bundle size matters)
  → Want pure functions, no mutation
  → Need date-fns-tz for serious timezone handling

Use Day.js if:
  → Migrating from Moment.js (1:1 API)
  → Very small bundle budget (2KB core)
  → Plugin ecosystem covers your needs

Use Temporal API if:
  → New projects willing to use a polyfill
  → Complex timezone arithmetic where DST correctness matters
  → Building for the future (native in 2-3 years)

Avoid:
  → Moment.js: 70KB, mutation, deprecated
  → Luxon: good but 24KB when date-fns is smaller

Compare date-fns, Day.js, and Luxon download trends on PkgPulse.

Comments

Stay Updated

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