Moment.js Alternatives for Date Handling in JavaScript
Working with dates and times is a fundamental aspect of many web applications. For years, Moment.js has been the go-to library for handling these operations in JavaScript projects. However, as web development practices have evolved to prioritize performance and bundle size optimization, Moment.js has started showing its limitations.
The Moment.js team themselves announced in 2020 that they consider the library to be in maintenance mode, recommending developers to consider alternatives for new projects. Their statement that "Moment is done" sparked a greater interest in other date manipulation libraries that address Moment's main drawbacks: its large bundle size, lack of tree-shaking support, and mutable API design.
In this article, we'll explore five powerful alternatives to Moment.js for handling date and time operations in modern JavaScript applications, with a particular focus on internationalization capabilities. Each option has its own strengths and ideal use cases, so you'll be equipped to choose the best fit for your project needs.
Why move away from Moment.js?
Before diving into alternatives, let's understand why developers are moving away from Moment.js:
Large bundle size: At around 300KB (or 70KB minified + gzipped), Moment.js significantly increases application load times.
No tree-shaking support: Due to its architecture, you can't easily exclude unused parts of the library.
Mutable API: Moment objects mutate when modified, which can lead to unexpected side effects and bugs.
Performance concerns: Some operations in Moment.js are less efficient compared to modern alternatives.
Maintenance mode: The library is no longer actively developed with new features.
Now, let's explore the alternatives that address these limitations.
The JavaScript Internationalization API
The most native approach to date internationalization is using JavaScript's built-in Internationalization API (Intl). Since it's part of the JavaScript language itself, using it adds zero additional bytes to your bundle size.
The Intl object provides constructors for language-sensitive date, time and number formatting:
// Create formatters with different locales
const usDateFormatter = new Intl.DateTimeFormat('en-US');
const germanDateFormatter = new Intl.DateTimeFormat('de-DE');
const japaneseFormatter = new Intl.DateTimeFormat('ja-JP');
// Format the same date for different locales
const date = new Date(2025, 3, 28);
console.log(usDateFormatter.format(date)); // 4/28/2025
console.log(germanDateFormatter.format(date)); // 28.4.2025
console.log(japaneseFormatter.format(date)); // 2025/4/28
You can customize formatting with various options:
const date = new Date(2025, 3, 28, 15, 30, 45);
// Using date and time style presets
const formatter = new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'long'
});
console.log(formatter.format(date)); // Monday, 28 April 2025 at 15:30:45 GMT+1
// Customizing individual components
const customFormatter = new Intl.DateTimeFormat('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
console.log(customFormatter.format(date)); // lundi 28 avril 2025, 15:30
Relative time formatting
The Intl.RelativeTimeFormat
constructor is particularly useful for creating
human-readable relative times:
const rtf = new Intl.RelativeTimeFormat('en', { style: 'long' });
// Format relative times
console.log(rtf.format(-1, 'day')); // 1 day ago
console.log(rtf.format(2, 'month')); // in 2 months
console.log(rtf.format(-5, 'year')); // 5 years ago
// Using 'auto' numeric option for more natural language
const naturalRtf = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
console.log(naturalRtf.format(-1, 'day')); // yesterday
console.log(naturalRtf.format(0, 'day')); // today
console.log(naturalRtf.format(1, 'day')); // tomorrow
The Intl API provides excellent browser support for modern environments. The main drawback is that you'll need to calculate time differences manually, as the API does not provide helper functions for date manipulation or comparison.
The Temporal API: The future of dates in JavaScript
The Temporal API is an exciting upcoming addition to JavaScript that aims to completely resolve date and time handling issues. It's currently a Stage 3 proposal in the TC39 process, meaning it's well on its way to becoming part of the language.
While not yet natively available in browsers, you can start using Temporal via a polyfill:
npm install @js-temporal/polyfill
// Then in your code
import { Temporal } from '@js-temporal/polyfill';
Working with plain dates and times
Temporal provides separate types for different use cases:
// Creating a date (year, month, day)
const date = Temporal.PlainDate.from({ year: 2025, month: 4, day: 28 });
console.log(date.toString()); // 2025-04-28
// Creating a time
const time = Temporal.PlainTime.from({ hour: 15, minute: 30 });
console.log(time.toString()); // 15:30:00
// Combining date and time
const dateTime = date.toPlainDateTime(time);
console.log(dateTime.toString()); // 2025-04-28T15:30:00
Time zones and current time
Temporal has built-in support for time zones:
// Getting the current date and time in the local time zone
const now = Temporal.Now.plainDateTimeISO();
console.log(now.toString()); // e.g., 2025-04-28T16:25:30.123456789
// Working with time zones
const timeZone = Temporal.TimeZone.from('Europe/Paris');
console.log(timeZone.toString()); // Europe/Paris
// Converting a plain datetime to a specific time zone
const zonedDateTime = dateTime.toZonedDateTime(timeZone);
console.log(zonedDateTime.toString()); // 2025-04-28T15:30:00+02:00[Europe/Paris]
Date arithmetic and durations
One of Temporal's most powerful features is its clean API for working with durations and date math:
const date = Temporal.PlainDate.from('2025-04-28');
// Adding time units
const futureDate = date.add({ days: 5, months: 1 });
console.log(futureDate.toString()); // 2025-06-02
// Subtracting time units
const pastDate = date.subtract({ weeks: 2 });
console.log(pastDate.toString()); // 2025-04-14
// Creating and using durations
const duration = Temporal.Duration.from({ days: 10, hours: 5 });
const newDate = date.add(duration);
console.log(newDate.toString()); // 2025-05-08T05:00:00
// Finding the difference between dates
const start = Temporal.PlainDate.from('2025-01-01');
const end = Temporal.PlainDate.from('2025-04-28');
const diff = start.until(end);
console.log(diff.toString()); // P3M27D (ISO 8601 duration format)
console.log(`${diff.months} months and ${diff.days} days`); // 3 months and 27 days
Internationalization with Temporal
Temporal integrates beautifully with the Intl API:
const date = Temporal.PlainDate.from('2025-04-28');
// Format with Intl.DateTimeFormat
const formatter = new Intl.DateTimeFormat('fr', { dateStyle: 'full' });
console.log(formatter.format(date)); // lundi 28 avril 2025
// Working with different calendar systems
const hebrewDate = Temporal.PlainDate.from({
year: 5785,
month: 7,
day: 15,
calendar: 'hebrew'
});
console.log(hebrewDate.toString()); // 5785-07-15[u-ca=hebrew]
// Converting between calendar systems
const islamicDate = hebrewDate.withCalendar('islamic');
console.log(islamicDate.toString()); // 1447-01-12[u-ca=islamic]
The Temporal API aims to fix all the shortcomings of the JavaScript Date object with an immutable, timezone-aware design. While it's not yet standard in browsers, using the polyfill allows you to future-proof your codebase.
Luxon: Created by a Moment.js maintainer
Luxon was created by one of Moment.js's maintainers and builds on many of its concepts while addressing its limitations. It's essentially a wrapper around the JavaScript Intl API with additional functionality.
npm install luxon
// In your code
import { DateTime } from 'luxon';
Creating and formatting dates
// Create dates in multiple ways
const now = DateTime.now();
const fromObject = DateTime.fromObject({ year: 2025, month: 4, day: 28 });
const fromISO = DateTime.fromISO('2025-04-28T15:30:45');
// Format dates
console.log(now.toLocaleString(DateTime.DATETIME_FULL)); // April 28, 2025, 4:30 PM EDT
console.log(fromObject.toFormat('yyyy LLL dd')); // 2025 Apr 28
// Using different locales
console.log(fromISO.setLocale('fr').toFormat('MMMM dd, yyyy')); // avril 28, 2025
console.log(fromISO.setLocale('de').toLocaleString(DateTime.DATE_FULL)); // Montag, 28. April 2025
Date manipulation with Luxon
Luxon uses an immutable API, which means operations return new objects rather than modifying the original:
const date = DateTime.fromISO('2025-04-28');
// These operations return new DateTime objects
const nextWeek = date.plus({ days: 7 });
const lastMonth = date.minus({ months: 1 });
const startOfMonth = date.startOf('month');
const endOfYear = date.endOf('year');
console.log(date.toISODate()); // 2025-04-28 (original unchanged)
console.log(nextWeek.toISODate()); // 2025-05-05
console.log(lastMonth.toISODate()); // 2025-03-28
console.log(startOfMonth.toISODate()); // 2025-04-01
console.log(endOfYear.toISODate()); // 2025-12-31T23:59:59.999
Relative time formatting
Luxon provides easy methods for creating human-readable relative times:
const now = DateTime.now();
const tomorrow = now.plus({ days: 1 });
const lastYear = now.minus({ years: 1 });
// Relative time from now
console.log(tomorrow.toRelative()); // in 1 day
console.log(lastYear.toRelative()); // 1 year ago
// With different locales
console.log(tomorrow.setLocale('es').toRelative()); // dentro de 1 día
console.log(lastYear.setLocale('fr').toRelative()); // il y a 1 an
// Calendar-aware relative time
console.log(tomorrow.toRelativeCalendar()); // tomorrow
console.log(lastYear.toRelativeCalendar()); // Apr 28, 2024
Luxon offers a cleaner, more modern API than Moment.js while maintaining much of its functionality. It's immutable, has good timezone support, and leverages the browser's Intl APIs for localization.
date-fns: Functional programming approach
date-fns takes a different approach with its functional programming style. Instead of methods on date objects, date-fns provides pure functions that take JavaScript Date objects as arguments.
npm install date-fns
// Import just what you need
import { format, addDays, differenceInMonths } from 'date-fns';
Basic formatting and manipulation
import { format, addDays, subMonths, startOfWeek, isToday } from 'date-fns';
const date = new Date(2025, 3, 28); // April 28, 2025
// Formatting dates
console.log(format(date, 'MMMM dd, yyyy')); // April 28, 2025
console.log(format(date, 'yyyy-MM-dd')); // 2025-04-28
// Manipulating dates
const nextWeek = addDays(date, 7);
console.log(format(nextWeek, 'MM/dd/yyyy')); // 05/05/2025
const threeMonthsAgo = subMonths(date, 3);
console.log(format(threeMonthsAgo, 'MM/dd/yyyy')); // 01/28/2025
// Getting start of a time period
const weekStart = startOfWeek(date);
console.log(format(weekStart, 'MM/dd/yyyy')); // 04/27/2025 (Sunday)
// Date checking
console.log(isToday(date)); // false
console.log(isToday(new Date())); // true
Working with locales in date-fns
For internationalization, date-fns requires importing locale files:
import { format, formatDistance, formatRelative } from 'date-fns';
import { es, de, fr } from 'date-fns/locale';
const date = new Date(2025, 3, 28);
// Formatting with different locales
console.log(format(date, 'MMMM dd, yyyy', { locale: es })); // abril 28, 2025
console.log(format(date, 'MMMM dd, yyyy', { locale: de })); // April 28, 2025
console.log(format(date, 'MMMM dd, yyyy', { locale: fr })); // avril 28, 2025
// Distance formatting
const futureDate = new Date(2025, 9, 15); // October 15, 2025
console.log(formatDistance(futureDate, date, { locale: es })); // aproximadamente 6 meses
console.log(formatDistance(futureDate, date, { addSuffix: true, locale: fr })); // dans environ 6 mois
// Relative formatting
console.log(formatRelative(date, new Date(), { locale: de })); // 28.04.2025
Advanced operations with date-fns
import {
differenceInDays,
eachDayOfInterval,
isSameMonth,
getWeeksInMonth,
parse
} from 'date-fns';
const start = new Date(2025, 3, 15); // April 15, 2025
const end = new Date(2025, 4, 15); // May 15, 2025
// Calculate difference between dates
const daysDiff = differenceInDays(end, start);
console.log(`Difference: ${daysDiff} days`); // Difference: 30 days
// Generate an array of dates in an interval
const daysInInterval = eachDayOfInterval({ start, end });
console.log(`Days in interval: ${daysInInterval.length}`); // Days in interval: 31
// Check if dates are in the same month
console.log(isSameMonth(start, end)); // false
// Get number of weeks in a month
console.log(getWeeksInMonth(start)); // 5
// Parse a string into a Date
const parsedDate = parse('April 28, 2025', 'MMMM dd, yyyy', new Date());
console.log(parsedDate); // Mon Apr 28 2025...
date-fns shines in tree-shaking scenarios since you only import the functions you need. This can lead to significantly smaller bundle sizes compared to Moment.js. Its pure functional approach also makes testing easier and eliminates issues with mutation.
Day.js: Lightweight with a familiar API
If you're looking for something with minimal size but an API similar to Moment.js, Day.js is an excellent choice. It's designed to be a minimalist alternative that mimics Moment's API.
npm install dayjs
// In your code
import dayjs from 'dayjs';
Basic usage
// Create dates
const now = dayjs();
const specificDate = dayjs('2025-04-28');
const fromValues = dayjs('2025-04-28T15:30:00');
// Format dates
console.log(now.format('YYYY-MM-DD')); // 2025-04-28 (current date)
console.log(specificDate.format('MMMM D, YYYY')); // April 28, 2025
console.log(fromValues.format('HH:mm, MMM D')); // 15:30, Apr 28
Manipulating dates with Day.js
Day.js uses a chainable API similar to Moment.js:
const date = dayjs('2025-04-28');
// Adding and subtracting time
const nextWeek = date.add(1, 'week');
console.log(nextWeek.format('YYYY-MM-DD')); // 2025-05-05
const lastMonth = date.subtract(1, 'month');
console.log(lastMonth.format('YYYY-MM-DD')); // 2025-03-28
// Start/end of time periods
const startOfMonth = date.startOf('month');
console.log(startOfMonth.format('YYYY-MM-DD')); // 2025-04-01
const endOfYear = date.endOf('year');
console.log(endOfYear.format('YYYY-MM-DD')); // 2025-12-31
Using plugins and internationalization
Day.js uses plugins to extend functionality and keep the core library small:
import dayjs from 'dayjs';
// Import plugins
import relativeTime from 'dayjs/plugin/relativeTime';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import calendar from 'dayjs/plugin/calendar';
// Import locale
import 'dayjs/locale/fr';
// Load plugins
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);
dayjs.extend(calendar);
const date = dayjs('2025-04-28');
// Using localized formats
dayjs.locale('fr'); // Set globally
console.log(date.format('L')); // 28/04/2025 (French locale)
// Or set per instance
console.log(date.locale('en').format('L')); // 04/28/2025 (English locale)
// Relative time
console.log(dayjs().to(date)); // in a year (assuming current date is 2024)
console.log(date.from(dayjs())); // a year ago
// Calendar time
console.log(dayjs().add(1, 'day').calendar()); // Tomorrow at 2:30 PM
Custom calendar formatting
Day.js allows customizing calendar output for different time ranges:
import dayjs from 'dayjs';
import calendar from 'dayjs/plugin/calendar';
import 'dayjs/locale/es';
dayjs.extend(calendar);
dayjs.locale('es');
const today = dayjs();
console.log(today.calendar(null, {
sameDay: '[Hoy a las] HH:mm',
nextDay: '[Mañana a las] HH:mm',
nextWeek: 'dddd [a las] HH:mm',
lastDay: '[Ayer a las] HH:mm',
lastWeek: '[El] dddd [pasado a las] HH:mm',
sameElse: 'DD/MM/YYYY'
})); // Output depends on current time
Day.js achieves a remarkable balance between bundle size (only about 2KB minified + gzipped) and functionality. While it doesn't have all the features of larger libraries out of the box, its plugin system allows you to add just what you need.
Final thoughts
The JavaScript date landscape has evolved significantly since Moment.js first came onto the scene. Today's developers have several excellent options that offer better performance, smaller bundle sizes, and modern API designs. Whether you prefer the minimal footprint of Day.js, the functional approach of date-fns, the object-oriented style of Luxon, or are ready to embrace the future with the Temporal API, there's a solution that fits your project's needs.
When choosing an alternative, consider your specific requirements: internationalization needs, bundle size constraints, API preferences, and browser support requirements. By transitioning away from Moment.js to one of these modern alternatives, you'll not only improve your application's performance but also future-proof your codebase for years to come.
Make your mark
Join the writer's program
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for us
Build on top of Better Stack
Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github