DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for You might not need date-fns
Dmitriy Kovalenko
Dmitriy Kovalenko

Posted on • Updated on

You might not need date-fns

Hola! Lazy dev here and today we are going to discuss date-fns. People often choose date libraries before they really need it. "How we will format the date?", "Are there any alternatives?"

But really, are there?

Am I a hater?

Sorry, this question was required. No, I'm not. Moreover, I was a super-active user and evangelist of date-fns. I am the creator of date-io and @material-ui/pickers which have been proposing to choose date-fns over the other date libraries.

But one day I said that date-fns is not a panacea
After the twitter thread, that was transformed in this blog post, date-fns' maintainer blocked me everywhere because it says that you might not need it. So probably this thread contains some useful information for people that choosing date lib – so I decided to share it in the blog as well! Hope you will have some fun reading it :)

That's it for prerequisites so let's start the discussion

You might not need a date library at all

First of all, when you need to only show date values in the user-readable format – you can avoid date libraries at all. Today all the modern browsers (even IE11) and node.js perfectly support Intl.DateTimeFormat!

can I use Intl.DateTimeFormat

This means that if your task is only to show the date/time value in a user-readable format you can do the following:

const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0))
// Ouput will depend on user locale and timezone 
console.log(new Intl.DateTimeFormat().format(date));
Enter fullscreen mode Exit fullscreen mode

It perfectly supports native IANA timezone and locale formatting. It means that you can not include locales in the bundle at all.

const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
// Results below assume UTC timezone - your results may vary

console.log(new Intl.DateTimeFormat('en-US').format(date));
// expected output: "12/20/2012"

console.log(new Intl.DateTimeFormat('fr').format(date));
// expected output: "20/12/2012"
Enter fullscreen mode Exit fullscreen mode

If you need more

But there is a problem. When you need more than formatting – for example, parsing or you are working with dates too often so the native (not really best) Date API is not enough. You probably will start looking for some helpful date-management library.

🌈 One wonderful day we will probably be able to get rid of all libraries in favor of native API. Here is the proposal for Temporal, which provides a nice functional API for working with Date.

But today you probably will pick up date-fns. Nothing personal – only statistics. Date-fns is the most popular date library as of now. What about moment.js? It is dead.

Today date-fns is much more often used for new projects. Here are downloads stats from date-io.

date-io stats

Statistically, you will pick date-fns. But do you really need it?

Let's discuss some criterias that are commonly used to choose the date library, and see does date-fns is the best one or not?

Bundlesize

Date-fns is solving only 1 problem much better than any other date library. And that's not a bundlesize. πŸŽ‰ Surprise πŸŽ‰ date-fns takes mostly 18kb gzip without locales. Dayjs takes 6 (yes six kb).

Alt Text

But if compare not gzipped, but parsed size – date-fns is the biggest one

Alt Text


Ok. Ok. The problem date-fns solving REALLY nice is tree-shaking. Because each function has its own entry point and exported as esm, unused code will be removed from the bundle, right?

Treeshaking

Let's create a more "real-world" example, that uses the hardest to implement manually functions:

  • Formatting
  • Parsing
  • Display time from X to Y
  • 3 locales

Result:

Alt Text

As you can see date-fns takes 13.88kb gzip when only importing the most important functionality. It is a lot.

Here is a pretty fun example of a react datepicker that has a peer dependency on date-fns. Date-fns takes 3 times more space than the datepicker itself and mostly 1/3 size of react. And it's only to make a single date-picker work.

react-nice dates

Also as you saw in the bundlesize stats luxon did not change its size at all. This because luxon npm package provides only commonjs output which is not tree shakeable. So maybe one day it will become smaller.

But do not forget the most wonderful thing about Luxon – it is built over native Intl – so it doesn't bundle locales at all. You can support even 50 locales without any additional bundlesize for date formatting!

P.S. All date-fns' 75 locales bundle takes 80kb gzip
Alt Text

Bundlesize conclusion

Date-fns is not lightweight library for date/time management. There are underrated alternatives – e.g. Dayjs is much smaller when using around the same functionality.

API

The next criteria for choosing a library would be the API. API must be clear, well-typed, and comprehensive. And here the most unclear for me personally – why everybody is choosing date-fns?

Date-fns design is pretty straightforward – you have a separate function for everything. And this is totally perfect, but unfortunately, not for all javascript developers. The problem is that javascript doesn't have native function composition utils.

I mean that some complex code with date-fns is completely unreadable:

function checkIsBeforeDateFns(time: Date, maxTime: Date) {
  return isBefore(
    setMilliseconds(setSeconds(setMinutes(time, 0), 0), 0),
    maxTime
  );
}
Enter fullscreen mode Exit fullscreen mode

Function executions need to be read like from the inside out. The first function call will be setMinutes and the last will be isBefore.

Let's compare the same functions in dayjs and luxon. They are using the old good chaining API. Most developers/editors/linters/static analyzers work like a charm with such APIs.

function checkIsBeforeDayjs(time: Dayjs, maxTime: Dayjs) {
  return time.minute(0).second(0).millisecond(0).isBefore(maxTime);
}

function checkIsBeforeLuxon(time: DateTime, maxTime: DateTime) {
  return time.set({ second: 0, minute: 0, millisecond: 0 }) < maxTime;
}
Enter fullscreen mode Exit fullscreen mode

Much readable right? This is actually overall a common problem in functional programming. And it can be easily fixed by using some of the function composition techniques. For example here are the same functions with date-fns/fp submodule and ReasonML (now Rescript) – native functional language compiling to javascript. And this is awesome πŸ’œ

let checkIsBeforeDateFns = (time, maxTime) =>
  time
  |> DateFns.setMilliseconds(0)
  |> DateFns.setSeconds(0.)
  |> DateFns.setMinutes(0.)
  |> DateFns.isBefore(maxTime);
Enter fullscreen mode Exit fullscreen mode

This is still just 4 function calls. But much much more readable. Beautiful!

By the way I am the maintainer of date-fns binding for ReasonML. Bring us a ⭐️

But ask yourself – do you and more important your team are ready for functional programming? And do you have all the required tools for it like pipe or compose?

If yes – take date-fns and be happy with functional programming πŸ‘¨β€πŸ’»πŸ‘©β€πŸ’».

Performance

You should not think about performance before the problem was encouraged.

Premature optimization is the root of all evil Β© Donald Knuth

The performance difference will be visible only on the thousand function calls per second. But if you still interesting in performance differences between date-fns and the other libraries:

Short results from our date-io benchmark:

  • Date-fns is the fastest for date calculations (add, subtract, before, etc)
  • Date-fns is the fastest for date parsing
  • Moment is fastest for formatting (ha-ha didn't expect moment here)

Yes, date-fns is really fast because it works directly with a native date without creating any additional wrapper. Dayjs focused on size instead of speed and Luxon is using Intl which is super slow 🐌.

So yes date-fns is the best option if you have performance issues with other libs. But do you really have?

Conclusion

Make sure that the author of this post is incompetent, subjective, stupid, awful, and lazy. So you must reach your own conclusions for your particular project and team based on many factors.

BTW here is the repo with all date-fns comparison stuff from this post, you can check it out, play with bundlesize and API.

I will really be happy if you will think about date/time libraries in javascript and the requirement of date-fns after this reading πŸ€“

In the author's humble opinion there are no reasons to choose date-fns if you are not cooking functional programming. And, unfortunately, as far I can see literally nobody using their really good functional approach 😿.

So, in conclusion: if this lazy author one day will start a new project in javascript and will need some kind of date/time manipulations he will probably do the following:

  • Try to start with native Intl formatting
  • When lib will become really needed choose dayjs – because its
    • a) ~Harder, Better, Faster, Stronger~
    • a) smaller
    • b) tree shakable by plugins
    • c) have a nice API

Thank you

For this loooong read and by the tradition:

No date-fns maintainers were harmed in the making of this article πŸ˜‰

Top comments (7)

Collapse
 
ninjin profile image
Jin

Some comparison of time-libs:

So be carefull Intl is very very slow.

Collapse
 
ninjin profile image
Jin
Collapse
 
dmtrkovalenko profile image
Dmitriy Kovalenko Author • Edited on

Yea thank you. Our benchmark is comparing libs in node.js and it is slow enough because of node-icu.

But you probably won’t meet performance problems. So do not think about them prematurely

Thread Thread
 
ninjin profile image
Jin

I've already meet it with Angular, which likes to recalculate template expressions frequently to detect changes.

Collapse
 
noal profile image
noal

b) tree shakable by plugins

Sorry if this is a dumb question, but: what plugin? how? I wasn't able to find anything about this for searches like 'DayJS tree shaking plugin'. The only thing Google was leading me to was Webpack's tree shaking. Is that what you mean?

Collapse
 
dmtrkovalenko profile image
Dmitriy Kovalenko Author • Edited on

It means that the architecture itself build over plugins day.js.org/docs/en/plugin/plugin, so basically, you can not import a specific plugin until you need it.

So if chunk A only does simple formatting – dayjs weights 2kb, but another chunk can add some functionality like timezones day.js.org/docs/en/plugin/relative... and this code will be loaded only when it's really needed.

Collapse
 
antonrusak profile image
Anton Rusak

Thank you Dmitriy, now I choose my date lib more consciously πŸ€—

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.