These five libraries solve the notoriously difficult problem of working with dates and times in JavaScript. While the native Date object is mutable and confusing, these tools offer immutable data models, better timezone support, and clearer APIs. moment was the long-time standard but is now in maintenance mode. dayjs offers a similar API in a smaller package. date-fns provides functional utilities that work with native dates. luxon leverages the modern Intl API for robust timezone handling. @js-joda/core brings Java's robust date-time model to JavaScript for maximum reliability.
Working with dates in JavaScript is famously tricky. The native Date object is mutable, has a confusing API, and handles timezones poorly. To solve this, the ecosystem offers several libraries, each with a different philosophy. Let's compare @js-joda/core, date-fns, dayjs, luxon, and moment to help you pick the right tool for your architecture.
Before diving into features, we must address moment. It was the industry standard for years, but it is now in maintenance mode. This means the team is fixing critical bugs but not adding features. For any new project, moment should be avoided.
// moment: Legacy, not recommended for new work
import moment from 'moment';
const now = moment(); // Works, but consider alternatives
The other four libraries are actively maintained and represent the modern way to handle time.
One of the biggest sources of bugs in date handling is accidental mutation. If you change a date object, does it change the original reference?
moment and dayjs use immutable patterns in practice (methods return new instances), but moment had some legacy mutable methods. dayjs is fully immutable.
// dayjs: Immutable chain
import dayjs from 'dayjs';
const original = dayjs('2023-01-01');
const nextDay = original.add(1, 'day');
// original remains '2023-01-01'
luxon and @js-joda/core are strictly immutable. You cannot change an instance once created.
// luxon: Immutable
import { DateTime } from 'luxon';
const original = DateTime.fromISO('2023-01-01');
const nextDay = original.plus({ days: 1 });
// original remains unchanged
date-fns works with native Date objects but functions return new instances, ensuring immutability by design.
// date-fns: Functional immutability
import { addDays } from 'date-fns';
const original = new Date('2023-01-01');
const nextDay = addDays(original, 1);
// original remains unchanged
Handling timezones is where many libraries differ significantly. Do you need to install extra plugins, or does it work out of the box?
luxon has timezone support built-in using the browser's Intl API. No extra plugins needed.
// luxon: Built-in timezone support
import { DateTime } from 'luxon';
const nyTime = DateTime.now().setZone('America/New_York');
dayjs requires a plugin for timezone support. You must extend the library before using it.
// dayjs: Requires plugin
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);
dayjs.extend(timezone);
const nyTime = dayjs().tz('America/New_York');
date-fns relies on native Date objects, so timezone handling often requires converting to UTC strings or using date-fns-tz (a separate package).
// date-fns: Often needs companion package
import { zonedTimeToUtc } from 'date-fns-tz';
const utcDate = zonedTimeToUtc(new Date(), 'America/New_York');
@js-joda/core has a separate package @js-joda/timezone for full timezone support, similar to the Java model.
// @js-joda/core: Separate timezone package
import { ZonedDateTime, ZoneId } from '@js-joda/core';
import '@js-joda/timezone';
const zdt = ZonedDateTime.now(ZoneId.of('America/New_York'));
moment requires moment-timezone plugin, which is heavy and now also in maintenance.
// moment: Requires heavy plugin
import moment from 'moment-timezone';
const nyTime = moment.tz('America/New_York');
How much code ends up in your final build? This depends on whether the library is modular.
date-fns is designed for tree-shaking. You import only the functions you use.
// date-fns: Import only what you need
import { format, addDays } from 'date-fns';
// Unused functions are excluded from bundle
dayjs is very small by default, but adding plugins increases size. It is still lighter than moment.
// dayjs: Small core, plugins add size
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
dayjs.extend(isBetween);
luxon is a class-based library. You import classes, and unused methods are typically tree-shaken by modern bundlers.
// luxon: Class-based imports
import { DateTime } from 'luxon';
// Only DateTime class is imported
@js-joda/core is modular but can be verbose to import due to its strict structure.
// @js-joda/core: Specific class imports
import { LocalDate } from '@js-joda/core';
moment is monolithic. Even if you use one feature, the whole library is often included unless complex configuration is used.
// moment: Monolithic import
import moment from 'moment';
// Entire library included by default
Your team's coding style should match the library's API design.
date-fns uses a functional style. The data (the date) is the last argument.
// date-fns: Functional style
import { isAfter } from 'date-fns';
const result = isAfter(new Date(), new Date('2020-01-01'));
luxon, dayjs, and moment use an Object-Oriented, chainable style. You call methods on the date instance.
// luxon: OO chainable style
import { DateTime } from 'luxon';
const result = DateTime.now().isAfter(DateTime.fromISO('2020-01-01'));
// dayjs: OO chainable style
import dayjs from 'dayjs';
const result = dayjs().isAfter('2020-01-01');
@js-joda/core uses a strict OO style similar to Java, focusing on value types like LocalDate rather than a generic DateTime.
// @js-joda/core: Strict value types
import { LocalDate } from '@js-joda/core';
const date = LocalDate.now();
// Focused on specific date/time representations
You need precise calculations, immutability, and no side effects. Timezones must be explicit.
@js-joda/core or luxon@js-joda/core offers the most rigorous model. luxon offers great timezone support with a more JS-native feel.// luxon example for finance
const settlement = DateTime.now().plus({ days: 2 }).toUTC();
You need to format dates nicely and keep the bundle small for performance.
date-fns// date-fns example for blog
import { format } from 'date-fns';
const dateString = format(new Date(), 'MMMM d, yyyy');
You have a large codebase full of moment() calls and need to switch quickly without rewriting everything.
dayjsmoment, making refactoring easy.// dayjs example for migration
// Replaces moment().format() directly
const formatted = dayjs().format('YYYY-MM-DD');
Users are in many timezones. You need to show local times accurately.
luxonIntl support handles timezone conversions reliably without heavy plugins.// luxon example for dashboard
const userTime = DateTime.now().setZone(userTimeZone);
| Feature | @js-joda/core | date-fns | dayjs | luxon | moment |
|---|---|---|---|---|---|
| Status | ✅ Active | ✅ Active | ✅ Active | ✅ Active | ⚠️ Maintenance |
| Immutability | ✅ Strict | ✅ Functional | ✅ Yes | ✅ Yes | ⚠️ Mixed |
| Timezones | 🧩 Plugin | 🧩 date-fns-tz | 🧩 Plugin | ✅ Built-in | 🧩 Plugin |
| API Style | OO (Java-like) | Functional | OO (Chainable) | OO (Chainable) | OO (Chainable) |
| Bundle Size | Medium | Small (Modular) | Very Small | Medium | Large |
| Native Date | ❌ Custom Type | ✅ Uses Native | ❌ Custom Type | ❌ Custom Type | ❌ Custom Type |
date-fns is like a utility belt 🛠️—perfect for developers who want lightweight, functional tools that work with standard JavaScript dates. Ideal for modern frontend apps where bundle size matters.
luxon is like a modern navigation system 🧭—built on web standards (Intl), robust for timezones, and immutable. Best for apps that deal heavily with global users and times.
dayjs is like a compact car 🚗—small, fast, and familiar if you know moment. Great for quick projects or migrating legacy code without the baggage.
@js-joda/core is like an industrial engine ⚙️—powerful, strict, and reliable. Best for complex domains where date logic is critical (like finance or logistics).
moment is like an old landline phone 📞—it worked great in the past, but it's time to upgrade to mobile. Avoid for new work.
Final Thought: For most modern frontend applications, date-fns or luxon are the strongest choices. Pick date-fns for simplicity and bundle size, or luxon if timezones are a core part of your product. Use dayjs if you need to match moment's API, and reserve @js-joda/core for high-complexity backend or financial logic.
Choose @js-joda/core if you need absolute reliability and immutability, especially for complex financial or backend logic ported from Java. It is ideal when you want to avoid the pitfalls of JavaScript's native Date entirely and prefer a strict, value-based approach over a fluent chainable API.
Choose date-fns if you prefer functional programming and want to work with native Date objects without wrapping them. It is perfect for projects that need tree-shaking to keep bundle sizes small and for teams that like composing small, single-purpose functions rather than chaining methods on an object.
Choose dayjs if you are migrating from moment and want to keep the same fluent, chainable API style without the heavy bundle size. It is suitable for frontend projects where familiarity and ease of use are priorities, provided you are willing to manage plugins for advanced features like timezones.
Choose luxon if you need robust, built-in timezone support and want to leverage the browser's native Intl API. It is the best choice for modern applications that deal with multiple timezones frequently and prefer an immutable, class-based API that feels more modern than moment.
Do NOT choose moment for new projects. It is officially in maintenance mode, meaning it will not receive new features or significant updates. Only use it if you are maintaining a legacy codebase that already depends on it, and plan to migrate to dayjs, luxon, or date-fns eventually.
js-joda is an immutable date and time library for JavaScript. It provides a simple, domain-driven and clean API based on the ISO calendar system, which is the de facto world calendar following the proleptic Gregorian rules.
js-joda has a lightweight footprint, only 43 kB minified and compressed, no third party dependencies.
js-joda is fast. It is about 2 to 10 times faster than other JavaScript date libraries.
js-joda comes with built-in parsers/ formatters for ISO 8601 as specified in RFC 3339, that can be easily customized.
js-joda supports ECMAScript 5 browsers down to IE11.
js-joda is a port of the threeten backport, which is the base for JSR-310 implementation of the Java SE 8 java.time package. Threeten is inspired by Joda-Time, having similar concepts and the same author.
js-joda is robust and stable. We ported more then 1700 test-cases with a lots of test-permutations from the threetenbp project. We run the automated karma test-suite against Firefox, Chrome, Node and phantomjs.
Popular JavaScript date libraries like moment or date-utils are wrappers around the native JavaScript Date object, providing syntactic sugar. The native Date object always consist of a date, time and a timezone part. In contrast, js-joda is a standalone date and time implementation.
The API has a domain-driven design with classes for each of the different use cases, like LocalDate, ZonedDateTime or Period. For example, LocalDate allows you to handle dates without times (like birthdays or holidays) in a clean and error-safe way, especially if these dates are persisted to an external server.
js-joda is immutable. Immutability aligns well with pure functions and with the architecture of frameworks like React and Flux.
LocalDate represents a date without a time and timezone in the ISO-8601 calendar system, such as 2007-12-24.
LocalTime represents a time without timezone in the ISO-8601 calendar system such as '11:55:00'.
LocalDateTime is a description of the date (LocalDate), as used for birthdays, combined with the local time (LocalTime) as seen on a wall clock.
ZonedDateTime is a date-time with a timezone in the ISO-8601 calendar system, such as 2007-12-24T16:15:30+01:00 UTC+01:00.
Instant is an instantaneous point on the time-line measured from the epoch of 1970-01-01T00:00:00Z in epoch-seconds and nanosecond-of-second.
Duration is a time-based amount of time, such as '34.5 seconds'.
Period is a date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
Year represents a year in the ISO-8601 calendar system, such as '2016'.
YearMonth represents a year and a month in the ISO-8601 calendar system, such as '2016-01'.
Month represents a month-of-year in the ISO-8601 calendar system, such as 'July'.
MonthDay represents a month-day in the ISO-8601 calendar system, such as '--12-03'. Could be used to represent e.g. Birthdays.
DayOfWeek represents a day-of-week in the ISO-8601 calendar system, such as 'Tuesday'.
Install joda using npm
npm install @js-joda/core
Then require it to any module
var LocalDate = require('@js-joda/core').LocalDate;
var d = LocalDate.parse('2012-12-24').atStartOfDay().plusMonths(2); // 2013-02-24T00:00:00
To use js-joda from a browser, download either dist/js-joda.min.js or dist/js-joda.js (with sourcemaps for development). Then add it as a script tag to your page
<script src="js-joda.min.js"></script>
<script>
var LocalDate = JSJoda.LocalDate;
var d = LocalDate.parse('2012-12-24').atStartOfDay().plusMonths(2); // 2013-02-24T00:00:00
</script>
js-joda consist of four packages:
| package name | description | path |
|---|---|---|
@js-joda/core | Implementation of the ThreeTen Classes and API | /packages/core |
@js-joda/timezone | Implementation of timezone calculation based on the iana Time Zone Database | /packages/timezone |
@js-joda/locale | Implementation of locale specific functionality for js-joda, especially for formatting and parsing locale specific dates | /packages/locale |
@js-joda/extra | Implementation of the ThreeTen-Extra Classes and API | /packages/extra |
The @js-joda/examples package is for testing the different build artifacts in different context, like webpack, browser node, etc.
Contributions are always welcome. Before contributing please read the code of conduct & search the issue tracker. We use GitHub issues. Your issue may have already been discussed or fixed. To contribute, fork js-joda, commit your changes, & send a pull request.
By contributing to js-joda, you agree that your contributions will be licensed under its BSD license.
Note that only pull requests and issues that match the threeten backport API will be considered. Additional requested features will be rejected.
js-joda is released under the BSD 3-clause license.
js-joda uses the ThreeTen-Backport implementation (http://www.threeten.org/threetenbp/) as a reference base for implementation. This allows us to release js-joda under the BSD License while the OpenJDK java.time implementation is under GNU GPL+linking exception. The API of the ThreeTen-Backport is mostly identical to the official Java SE 8 API from the view of our JavaScript port.
Our implementation reference base ThreeTen-Backport (http://www.threeten.org/threetenbp/) is also released under the BSD 3-clause license
OpenJDK is under GNU GPL+linking exception.
The author of Joda-Time and the lead architect of the JSR-310 is Stephen Colebourne.
The API of this project (as far as possible with JavaScript), a lot of implementation details and documentation are just copied but never equalled.
LocalDate, LocalDateTime, ZonedDateTime, Instant, Duration and Period converting to and from ISO8601.ZonedDateTime (without support for loading iana timezone databases) currently supports only fixed offsets like UTC or UTC+02:00 and the system default time zone.ZonedDateTime.see the plugin @js-joda/locale