DEV Community

loading...
Cover image for The best JavaScript date libraries in 2021
Skypack

The best JavaScript date libraries in 2021

Melissa McEwen
Developer Advocate at Skypack
Originally published at skypack.dev ・15 min read

Wrangling dates and times in JavaScript can be a headache. JavaScript date libraries provide user-friendly APIs and useful utilities that alleviate some of that pain. But with so many options how do you pick the best one? In this post we delve into the world of JavaScript date libraries to help you choose. Our top pick, date-fns, delivers the best feature set combined with an elegant modular architecture.

Our picks

Best overall: date-fns

date-fns offers great documentation, functional architecture, and utilities that handle almost any task you can think of. If dates are a critical concern for your JavaScript application, use date-fns. Each feature has clear documentation written in ESM (ES Modules) for the browser. Logical and consistent function names encourage readable code. A modular architecture allows build tools to trim away unused code for a slimmer final build.

Best for time zones: Luxon

If time zones or localization are your primary concern, we recommend Luxon. Luxon leverages JavaScript's Intl for speed and slimness while providing what Intl doesn't: an immutable user-friendly API. Concise, well-written documentation eases the learning curve for getting started. 

Best minimalist option: Day.js

For basic handling of dates, Day.js is a minimalist library that offers an excellent API without much overhead. While Day.js has fewer features than Luxon or date-fns, it's much smaller in size. 

The Research

When to use a JavaScript date library

Why not just use JavaScript's built-in Date object? A good JavaScript date library provides a clear advantage over JavaScript's Date in several ways: immutability, parsing, and time zones. In addition, JavaScript date libraries offer useful utilities for date manipulation and formatting, which can save developers time and energy.

ℹ️ Maggie Pint's blog post Fixing JavaScript Date describes how Javascript's Date was based on a Java (nope, not a typo) API from the 1990s. That Java API is long gone from Java but its problems live on in JavaScript's Date.

JavaScript's Date is mutable, which can lead to unintentional bugs. Let's say you create a Date object named currentTime. You display it on top of the website. Another developer comes in and wants to create a widget that displays next weeks events. They use currentTime.setDate(currentTime.getDate() + 7) at the top to the widget to display next week's date. However it also changes the date in the header! Oops. With an immutable date library, adding a week would create a new instance so the original would not change.

While Date has a parse method, MDN documentation for Date's dateString notes that usage is "strongly discouraged." An excellent answer in the Stack Exchange question Why does Date.parse give incorrect results? describes in detail differences in formatting standards and browsers lead to inconsistent and unreliable results. If you need to parse, a date library is a must.

ℹ️ UTC is enough for everyone..right? is an incredible interactive presentation by Zach Holman on how dates work in programming

Time zones are another major frustration. Date in the browser always represents the local system date stored as milliseconds since January 1st, 1970 in UTC. A Date can rendered as a string in a different time zone using Intl, but the Date itself is always the local system time.

Date also lacks utilities for common tasks like creating human-readable relative dates. Using library utilities for such tasks can save a lot of time.

ℹ️ TC39, the major JavaScript standard's committee, is working on a new modern API called Temporal (see the proposal here) that solves all of the above issues. The Future of Date and Time in JavaScript by Christofer Eliasson is an excellent read of the history behind Temporal and what it means for JavaScript.

In the meantime, be aware of the limitations of Date and use a library when you need one.

Our previous pick: Moment.js

For a long time Moment.js was king of the JavaScript date libraries, but in 2020 Moment.js's maintainers published a Project Status announcing they consider Moment.js a legacy project. 

The maintainers argued that there was no way to refactor Moment.js to meet the demands of modern JavaScript development such as immutability and tree shaking. Lighthouse (Chrome's built-in auditing tool) warns against using Moment because of its large size (329 kb).

For these reasons, we do not include it as one of our recommended picks. If you are looking for a replacement to Moment.js that closely matches its API, try Day.js.

How we picked

We looked at data from Skypack and NPM trends to identity the most popular and trending libraries and evaluated them against these core criteria:

  • Browser-friendly: We evaluated JavaScript date libraries for use directly by the browser rather than the Node.js backend.
  • Great documentation: Users look to this to implement the library correctly. We looked at the parts the facilitate this process: organization, search, examples, and the individual pages relating to the methods we tested.
  • Modularity + Tree shaking: Modular architecture allows you to shrink your library size by "tree-shaking" out any library code you're not using. Common build tools capable of tree-shaking include Webpack, Snowpack and Rollup.
  • Features: Feature-richness was just one factor. We also considered whether a library was especially good at certain tasks.
  • Performance: Measuring performance gives us a clue at how fast each library performs common operations. This usually isn't noticeable but in large, complex apps can cause lag.
  • Size: One of the biggest problems people had with Moment.js was size. A large library can substantially increase page load times.
  • Anti-bug features: Immutability, readability, and informative error messages can help prevent bugs. When code is readable, you can learn a lot about what it does by reading it. Format, syntax, and naming conventions can make all the difference in readability.

How we tested

When evaluating each library we made a CodePen using Skypack. We attempted to pick out common use cases that illustrate differences between libraries:

  • Rendering the current date & time
  • Rendering the current date using a custom format
  • Modifying a date to add/remove time
  • Working with timezones
  • Parsing arbitrary date strings

First we implemented these cases using JavaScript's Date:

We wrote up examples for performance testing on perf.link and all of the tests and code are available in the Date/time performance examples GitHub repository. If you have a specific use-case, you can modify one of the provided examples. JavaScript date libraries contain hundreds of methods and features that we couldn't test them all. Instead, we opted to test the same methods demonstrated in our Codepens.

A code block imports all the tested libraries via Skypack, then there are four blocks showing code for adding a week to a date using each JavaScript date library. The results are 1,990 ops/s Luxon, 25,495 ops/s Day.js, 817,780 ops/s JavaScript native date, 217,296 ops/s date-fns

A chart of the performance data is also available on CodePen. The data  doesn't indicate a clear advantage for any one library. Each JavaScript date library implements different features differently, so that's not surprising.

⏱ For other performance comparisons see

The sizes listed on each review are based on loading via the Skypack CDN. If you're using a build tool to tree-shake and the library is tree-shakable (like date-fns), the size will depend on what parts you use. For evaluating libraries with tree-shaking we recommend Bundlephobia.

A caveat to our testing is we did not consider back-end Node.js use. Many JavaScript date libraries rely on browser APIs so may not work correctly on the back-end.

Other cases to test

Depending on your needs there are lots of other tests you could do. If you'd like to test other features not covered here, you can make changes to any of the examples in our embedded Codepens or CodePen date collection.

For example:

  • Does it let us know if we have an invalid date when we pass in invalid items? For example moment(undefined) behaves like moment() and doesn't output an error, which could lead to bugs.
  • How well does it handle different languages and locales?
  • Can it render relative dates (like "4 days ago")? Can it do this in different languages?
  • Does it work in older browsers?

Our picks in detail

Date-fns

Size: 58.9 kb (tree-shakable)

date-fns is a pleasure to use, offering all the benefits of a modular architecture and covering almost any date use case. Our example shows the power of good naming and syntax in conveying information. Code like add(now, {days: 7}) is straightforward to understand even if you're unfamiliar with date-fns.

To get the most out of date-fns's modular architecture, use a tree-shaking build tool (see our criteria section) and import only the features you need:

import { format, add, getHours, parse } from "https://cdn.skypack.dev/date-fns@2.16.1";

In the example above, a tree-shaking build tool would remove all code from date-fns that's not used in format, add, getHours, or parse. The final build would be much slimmer. Still at 58.9kb for the whole package, it's not giant in the first place.

date-fns doesn't package a browser build for NPM (see the related GitHub issue) and therefore only works on CDNs that transform for the browser like Skypack and ESM.run. However, documentation has examples labeled for the browser as both ESM(ES Modules) and ES2015. This ensures developers don't get confused trying to run Node.js backend code in the browser.

Limitations

Although we liked almost everything about date-fns, it is not for everyone. For starters, date-fns seems geared towards experienced developers working with a professional tool chain. Working without a build tool, the tree-shaking advantage disappears.

Note that our CodePen example starts with a const now = new Date(), which is the default JavaScript Date object. As noted in our Date section a Date is always the current system's time zone. date-fns has a separate library for working with time zones using helper functions. We found the documentation and examples for these helper functions less consistent than the main date-fns. Our resulting code is less readable than the rest of the examples. Our time-zone example for date-fns is 4 lines of code, compared to 1 for Luxon.

Another quirk we encountered was if you Google "date-fns parse" the top results take you to the v2.0.0-alpha docs for parse, which no longer work (the current version is 2.16). We found ourselves using Google because the documentation's built-in search seems to search only title and descriptions of sub-pages. The information architecture of the menu could also use improvement, as we would it overwhelmingly long. On occasion, a modal advertising "Get awesome JavaScript jobs to your email" covered the menu's bottom.

The hardest part of implementing date-fns Codepen examples was parsing, since they use different format tokens from other libraries. Furthermore, some tokens are not compatible with other tokens and the compatibility table is a Google doc that is hard to read.

Day.js

Size: 4kb

If Date fulfills almost all your needs but you don't want to deal with its downsides, Day.js is a great choice. Day.js's small size makes it ideal for build-tool free environments like CodePen and plain JS/HTML/CSS projects. If you're forced to support older browsers, Day.js 's Readme says it supports IE as far back as IE 7, though we did not test these capabilities.

The Day.js documentation claims "If you use Moment.js, you already know how to use Day.js", which would make migrating easier. You can see direct comparisons at the excellent You don't (may not) need Moment.js. The code in our example CodePen is readable with the exception of the parsing and time zone cases.

A well-organized menu helped us find what we need in the Day.js documentation. In addition the documentation search gives great results organized by type. We liked that most documentation pages have example code.

Limitations

Like date-fns, Day.js builds on top of JavaScript's Date, so time zone support requires another library. But unlike date-fns, Day.js relies on a plugin system that we did not find intuitive. You not only need to import the plugin but you also need to enable it by running dayjs.extend(plugin_name).

There is a stark contrast in our CodePen example between the time zone example and the other examples. The time zone example is much less readable and you'll also notice we commented it out. That's because it gave us incorrect results. It could be because our example time zone is an edge case or we did something wrong, but we attempted to follow the documentation. We recommend Luxon instead if you need to deal with time zones.

We encountered a few minor issues in the docs. The documentation does not contain any ESM (Es Modules) examples. This is especially apparent in the documentation for loading plugins in the browser, which recommends loading via script tag and then extending via the window global. In general for readability and browser compatibility, we prefer to avoid globals.

Unlike the other libraries you don't indicate a format with tokens when parsing. This worked fine in our CodePen but we wonder how correctly it would handle ambiguous formats. We felt the documentation could use more examples, specifically the parsing page. It took us a minute to realize parsing isn't its own method: you use dayjs() and pass in a string. Unfortunately this syntax makes the code less understandable at a glance.

Luxon

Size: 29.5kb

If time zones are high on your list of concerns, go with Luxon. Dealing with time zones is never fun, so it's nice to use a library where it just works. Most date libraries don't have a built-in time-zone support because adding a time zone database is complex and adds a lot of weight to the final size. To get around this problem, Luxon hacks into JavaScript's Intl API, which most browsers support. It's the only JavaScript date library we evaluated that does not extend Date, instead using its own DateTime class. In our experience this makes working with time zones less bug-prone.

Luxon's creator Isaac Cambron is a Moment.js maintainer. In Why does Luxon exist he says "Luxon started because I had a bunch of ideas on how to improve Moment but kept finding Moment wasn't a good codebase to explore them with."

Luxon's documentation is divided up into a Manual and Reference. Since the more technical API documentation is in the Reference, the Manual is much more readable. Most importantly the Manual is well-written and organized into clear sections, with browser-friendly install instructions for ESM (ES Modules). The resulting code in our CodePen example is remarkably readable, particularly for the time zone case.

ℹ️ "Can't we just get rid of time zones?" A common developer refrain, usually after dealing with a time zone related bug. So You Want To Abolish Time Zones is a great read on why that's not a panacea.

Limitations

We recommend date-fns for date dependent applications because compared to Luxon it has a larger feature set and is tree-shakable. If feature set is not a major concern, Day.js is a better pick because it's ~7 times smaller than Luxon (29.5kb for Luxon vs. 4kb for Day.js). Except for time zones, Luxon doesn't offer a clear advantage.

We noted above that Luxon relies on Intl and that most browsers support it, but this doesn't include older browsers. A polyfill is available, but this will increase the complexity and size of using Luxon.

A minor quibble is Luxon's documentation search doesn't seem to index the entire text.

Other JavaScript date libraries

Here are two interesting JavaScript date libraries that we didn't have time to evaluate:

Useful Resources

Discussion (1)

Collapse
ryands17 profile image
Ryan Dsouza • Edited

Great article! I mostly use date-fns and it works well for my use case :)