dayjs, js-joda, luxon, and moment are JavaScript libraries designed to simplify working with dates, times, time zones, and internationalization. They address the limitations of JavaScript’s native Date object—such as mutability, poor time zone support, and lack of formatting utilities—by offering immutable APIs, robust parsing, formatting, manipulation, and localization features. While all aim to make temporal logic more predictable and developer-friendly, they differ significantly in architecture, performance characteristics, API design, and long-term viability.
Working with dates in JavaScript has long been a pain point. The native Date object is mutable, lacks time zone awareness, and offers minimal formatting control. Over the years, libraries like moment, dayjs, luxon, and js-joda emerged to fill this gap—but they take fundamentally different approaches. Let’s break down how they handle real-world scenarios.
moment uses a mutable model. Calling .add() or .subtract() changes the original object—a frequent source of bugs.
// moment: mutable by default
const now = moment();
const later = now.add(1, 'hour');
console.log(now === later); // true — same object!
In contrast, dayjs, luxon, and js-joda are immutable. Every operation returns a new instance, making code more predictable and easier to debug.
// dayjs: immutable
const now = dayjs();
const later = now.add(1, 'hour');
console.log(now === later); // false — safe!
This shift reflects modern JavaScript best practices, where immutability reduces side effects and simplifies state management in frameworks like React.
luxon leans entirely on the browser’s Intl API for time zones. This means no extra data bundles—but also limited control.
// luxon: uses system time zone data
DateTime.now().setZone('America/New_York').toLocaleString();
It works well if your users have up-to-date browsers, but fails silently in older environments or when precise historical DST rules are needed.
dayjs handles time zones via an optional plugin (timezone) that bundles IANA zone data. You get consistent behavior across environments—at the cost of added bytes.
// dayjs with timezone plugin
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(timezone);
dayjs.tz('2023-01-01', 'America/Los_Angeles');
js-joda takes a third path: it ships with its own time zone engine (via @js-joda/timezone). This gives you Java-level precision for recurring events or historical calculations—but increases complexity and bundle size.
moment requires moment-timezone, which bundles the entire tz database. It works but bloats your app.
How should "01/02/03" be interpreted? American (Jan 2), European (Feb 1), or ISO (2003)?
moment guesses based on locale settings—leading to inconsistent results.
// moment: ambiguous parsing
moment('01/02/03', 'MM/DD/YY'); // assumes US format unless told otherwise
luxon refuses to guess. Without an explicit format, it only accepts ISO 8601 strings.
// luxon: strict by default
DateTime.fromISO('2023-01-02'); // ✅
DateTime.fromFormat('01/02/03', 'MM/dd/yy'); // explicit required
dayjs behaves like moment by default but supports strict parsing via plugins.
js-joda is the strictest: it won’t parse ambiguous strings at all. You must provide a formatter.
// js-joda: always explicit
LocalDateTime.parse('2023-01-02T10:00', DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'));
For applications handling user input from multiple regions, explicit parsing prevents subtle data corruption.
luxon shines here. Since it uses Intl, formatting adapts automatically to the user’s locale:
// luxon: locale-aware by default
DateTime.now().setLocale('fr').toLocaleString(DateTime.DATE_FULL);
// → "lundi 2 janvier 2023"
No extra locale files needed—just leverage the browser.
dayjs requires manual locale imports:
import 'dayjs/locale/fr';
dayjs.locale('fr');
js-joda needs companion packages like @js-joda/locale for non-English output.
moment also bundles locales globally, increasing memory footprint.
If your app serves a global audience, luxon’s zero-config i18n is a major advantage.
All modern alternatives beat moment in size and speed, but trade-offs remain:
dayjs: ~2KB core. Plugins add only what you use. Ideal for code-splitting.luxon: ~7KB. Depends on modern browser features; polyfills may be needed.js-joda: ~15KB+ for core, plus time zone data (~50KB+). Heavy but precise.moment: ~70KB with locales—unacceptable for most new projects.In a React SPA where every kilobyte counts, dayjs often wins. In a dashboard app needing bulletproof date math, js-joda justifies its weight.
Despite differences, these libraries converge on key modern practices:
All except moment enforce immutability, aligning with functional programming trends and reducing bugs in reactive UIs.
// Common pattern: chain operations safely
const nextWeek = library.today().plus({ days: 7 }).startOf('day');
Method chaining improves readability for complex date logic:
// Example across libraries
const deadline = now
.plus({ months: 1 })
.minus({ days: 2 })
.endOf('month');
All offer ways to represent spans of time (not just points):
// luxon
const duration = Duration.fromObject({ hours: 2, minutes: 30 });
// js-joda
const period = Period.ofMonths(1).plusDays(5);
Modern bundlers can eliminate unused code in dayjs, luxon, and js-joda—critical for performance.
| Feature | dayjs | js-joda | luxon | moment |
|---|---|---|---|---|
| Mutability | Immutable | Immutable | Immutable | Mutable |
| Time Zones | Plugin + bundled data | Full IANA support via addon | Browser Intl API | Plugin + full tz bundle |
| Parsing Strictness | Lenient by default | Strict (explicit formats only) | Strict (ISO or explicit) | Lenient (guesses) |
| i18n | Manual locale loading | Addon required | Automatic via browser | Global locale bundles |
| Bundle Size | Tiny core, modular | Large (but precise) | Moderate (modern browsers only) | Very large |
| New Projects? | ✅ Yes | ✅ For high-precision needs | ✅ For global apps | ❌ Deprecated |
luxon if you’re building a modern web app targeting recent browsers and need strong i18n with minimal setup.dayjs if you’re migrating from moment or need maximum control over bundle size in a performance-critical app.js-joda only when you require mathematical rigor—like calculating business days across time zones or handling fiscal calendars.moment entirely in new codebases. Its era has passed.The right choice depends less on features and more on your constraints: browser support, performance budget, team familiarity, and correctness requirements. But one thing is clear: the future of JavaScript date handling is immutable, explicit, and lean.
Choose js-joda when correctness, immutability, and strict adherence to ISO standards are non-negotiable—such as in financial, scientific, or scheduling systems where date math must be deterministic. It’s a faithful port of Java’s battle-tested ThreeTen API, offering rich temporal types like LocalDateTime and ZonedDateTime. Be prepared for a steeper learning curve and larger bundle size, and note that browser time zone support requires additional configuration.
Choose dayjs when you need a lightweight, fast, and familiar Moment.js-like API without the legacy baggage. Its plugin architecture lets you include only the functionality you need (e.g., time zones, custom parsing), making it ideal for performance-sensitive applications like SPAs or mobile web experiences where bundle size matters. However, avoid it if you require full ISO 8601 compliance or advanced time zone handling beyond what its plugins provide.
Choose luxon when you want modern, chainable APIs built on top of the browser’s Intl API for seamless internationalization and time zone support without external dependencies. Its immutable design and clean syntax make it excellent for applications requiring locale-aware formatting (e.g., global SaaS products). Avoid it if you must support older browsers that lack Intl.DateTimeFormat options or if you need to parse ambiguous date strings without explicit formats.
Do not use moment in new projects. It is officially deprecated as of September 2020, with maintainers advising against its adoption due to its mutable API, large bundle size, and outdated design. While it remains functional and widely understood, its technical debt (global locales, side-effectful methods) makes it unsuitable for modern, tree-shakeable, or performance-critical applications. Migrate existing codebases to alternatives like dayjs or luxon when feasible.
js-joda is an immutable date and time library for JavaScript. It provides a simple, domain-driven and clean API based on the ISO8601 calendar.
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 supports ECMAScript 5 browsers down to IE9.
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
Then require it to any module
var LocalDate = require('js-joda').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>
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
int and long values makes no sense with JavaScript)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.
Joda-Time is the base for JSR-310 that became part of Java SE 8 in the java.time package. JSR-310 is a new implementation with an API 'inspired by Joda-Time' but improves on some design flaws (see http://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html) Joda-Time is under Apache 2.0 licence.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.
js-joda is released under the BSD 3-clause license.
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.