date-fns v4 vs Temporal API vs Day.js 2026
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/dayjshistorically) - Time zone support requires the
dayjs-plugin-timezonewhich depends on the browser'sIntl.DateTimeFormatAPI
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
| Feature | date-fns v4 | Day.js | Temporal API |
|---|---|---|---|
| Weekly downloads | 24M | 15M | (native) |
| Bundle size | Tree-shakeable (~2-5KB used) | 2.6KB + plugins | 20KB (polyfill) |
| Time zone support | @date-fns/tz | Plugin | Native (built-in) |
| TypeScript | Excellent | Good | Excellent |
| Immutability | Yes (pure functions) | Yes | Yes |
| DST handling | With @date-fns/tz | With timezone plugin | Native, correct |
| Calendar systems | No | No | Yes (15+ calendars) |
| API style | Functional | Chained (OOP) | OOP |
| Browser native | No | No | Chrome 144+, Firefox 139+ |
Ecosystem and Community
date-fns has one of the largest communities in the JavaScript date library space. With 35,000+ GitHub stars and 24M weekly downloads, it's the clear leader by adoption. The library's philosophy of pure functions makes it highly testable — most date-fns questions have immediate Stack Overflow answers because the functional API is predictable. The @date-fns/tz package's integration with the core API was designed to address the most common criticism of date-fns v3 (that timezone handling required a separate library with a different API pattern). Contributions are active, the changelog is detailed, and the TypeScript types are generated from source, ensuring they never drift.
Day.js's community is shaped by its primary use case: Moment.js migration. Teams that search "moment.js alternative" in 2026 consistently find Day.js as the first recommendation due to its nearly identical API. The 70+ community plugins cover edge cases (fiscal quarters, Islamic calendar, Twitter-style relative dates), and the plugin architecture means the core library stays small. The main community resource is the Day.js GitHub repository and npm, with questions often answered by the maintainer directly in issues.
The Temporal API community is a TC39 working group, browser engineering teams, and early adopters. The proposal champions (Maggie Pint, Philipp Dunkel, Brian Terlson) have written extensively about the design decisions, and the polyfill repository has active issues tracking compatibility edge cases. As browser support matures, the Temporal community on Stack Overflow and the MDN Web Docs pages will become the primary resources for developers encountering the API for the first time.
Real-World Adoption
date-fns v4 is the default choice in TypeScript-first Node.js applications. API servers that need to format dates for API responses, calculate durations, work with ISO 8601 strings, and handle time zone conversions all reach for date-fns. The tree-shaking story means that a Next.js application using format, parseISO, and addDays adds only ~6KB to the bundle. Enterprise applications with complex business date logic — fiscal year calculations, business day calculations, recurring event generation — use date-fns for its comprehensive function library. For packages competing in similar utility niches, see 20 fastest growing npm packages 2026.
Day.js is the primary destination for teams migrating off Moment.js. Since Moment.js entered maintenance-only mode in 2020, the JavaScript ecosystem has been quietly migrating, and Day.js is the path of least resistance for teams that need a working replacement quickly. E-commerce sites, marketing platforms, and legacy applications being modernized frequently use Day.js because the chained API matches what developers remember from Moment.js without requiring a mental model shift. Testing date-dependent code thoroughly is critical — see best JavaScript testing frameworks 2026 for frameworks that support mocking system time with vi.useFakeTimers or Jest's timer utilities.
The Temporal API is in early production adoption. Developers at startups and at companies with forward-looking technical cultures are adding the polyfill to new projects, accepting the 20KB overhead in exchange for using the standard API that will eventually ship natively everywhere. The most compelling use case is internationalization — apps that need to support multiple calendar systems (Japanese imperial calendar, Hebrew calendar, Islamic calendar) find that Temporal's first-class calendar support eliminates entire categories of custom conversion code.
Developer Experience Deep Dive
date-fns's TypeScript experience is excellent for most use cases. The function signatures are precise, the return types are accurate, and editor autocomplete works correctly. The one friction point is that importing many functions individually creates verbose import statements — teams often create a utils/dates.ts file that re-exports commonly used functions. The @date-fns/tz package's TZDate class integrates transparently with all existing date-fns functions, so upgrading from date-fns-tz (v3) to @date-fns/tz (v4) is a clean migration.
Day.js's TypeScript experience has improved significantly with v1.11. The types are now bundled with the package (no separate @types/dayjs needed), and the plugin type augmentations work correctly when you call dayjs.extend(). The main limitation is that the chained API can lose type information in complex chains — TypeScript doesn't always know which plugins you've loaded, which can lead to runtime errors that TypeScript didn't catch.
The Temporal API's TypeScript types are part of the @js-temporal/polyfill package and the temporal-spec types package. They're well-designed, reflecting the rich type system of the API (PlainDate, ZonedDateTime, Instant, Duration are all distinct types rather than overloaded representations). The distinct types are actually a TypeScript advantage — passing a PlainDate where a ZonedDateTime is expected is a type error, preventing an entire class of bugs that occur with JavaScript's untyped Date object.
Migration from Moment.js
Moment.js remains installed in millions of JavaScript projects despite being deprecated. If your project still uses Moment.js, the migration path in 2026 is clear:
For projects that primarily use moment().format(), moment().add(), moment().diff(), and moment().from(), Day.js provides a nearly identical API and the migration is largely find-and-replace. The dayjs/plugin/relativeTime and dayjs/plugin/duration plugins cover most of Moment's utility surface area.
For projects that need tree-shaking, TypeScript-first development, or functional programming patterns, date-fns is the better destination. The API is more verbose (function calls rather than method chains) but the bundle size benefit is significant for browser applications.
For completely new projects without legacy Moment.js code, evaluate whether the Temporal polyfill (20KB) is acceptable — using the standard API today means zero migration work when browsers achieve full support in 2027-2028.
Final Verdict 2026
date-fns v4 remains the practical default for production JavaScript and TypeScript applications. Its combination of 24M weekly downloads (meaning abundant community resources), tree-shaking, and the new @date-fns/tz integration for time zone handling covers the needs of the vast majority of applications. TypeScript developers in particular benefit from date-fns's precise function signatures.
Day.js is the right choice for teams migrating from Moment.js and for browser applications where bundle size is paramount and functional programming isn't required. Its 2.6KB core is genuinely the smallest viable date library with solid timezone and locale support via plugins.
The Temporal API is the correct long-term bet. Starting to use it with the polyfill today in new browser projects builds familiarity with an API that will eventually be the platform standard. The polyfill's 20KB cost is the investment; the return is never needing to migrate again.
Compare date library downloads on PkgPulse.
Interoperability: Converting Between Libraries
A practical question for projects adopting Temporal alongside existing date-fns code is how to convert between the two systems. The Temporal polyfill provides conversion methods:
The Temporal.Instant.fromEpochMilliseconds(date.getTime()) pattern converts a JavaScript Date to a Temporal.Instant, and temporalInstant.toZonedDateTimeISO('UTC').toPlainDateTime() converts back. For date-fns users, the most common conversion is parseISO() to get a JavaScript Date from an ISO string, then wrapping it in Temporal for timezone-aware operations. This interoperability means teams can adopt Temporal incrementally — use Temporal for new timezone-heavy code while keeping date-fns for existing utility functions.
Day.js and date-fns can be used together in the same codebase without any interoperability issues — both ultimately work with JavaScript Date objects, so a date-fns format() call can format a Day.js .toDate() result. Teams that inherited Day.js code and are adopting date-fns for new code don't need to convert one to the other; the two libraries complement each other.
Internationalization and Calendar Support
The Temporal API's calendar support is genuinely unique and addresses a real gap in existing libraries. date-fns supports locale-based date formatting (translating month names, day names, and ordinals into different languages) but it doesn't support non-Gregorian calendar systems for date arithmetic. If you need to calculate "30 days from now in the Hebrew calendar" or "next month in the Islamic calendar," Temporal is the only JavaScript option without writing custom calendar conversion code.
Day.js similarly only handles the Gregorian calendar natively. The dayjs-plugin-localeData and locale files handle formatting in other languages but not calendar arithmetic for non-Gregorian systems. There's a third-party dayjs-jalali plugin for the Persian (Jalali) calendar, but it's community-maintained and not part of the official Day.js plugin ecosystem.
For most applications, Gregorian calendar arithmetic with date-fns or Day.js is entirely sufficient. The Temporal calendar feature matters for applications serving users in regions where official dates use non-Gregorian systems — Hebrew dates in Israeli government applications, Hijri dates in Islamic financial institutions, Japanese imperial dates in Japanese government software, or Thai solar calendar dates in Thai applications.
Related: Best JavaScript Date Libraries 2026, Best JavaScript Testing Frameworks 2026, Best Monorepo Tools 2026
Compare Date-fns-v4, Temporal-api, and Day.js package health on PkgPulse.