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
!
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));
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"
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 withDate
.
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.
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).
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:
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.
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!
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
);
}
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;
}
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);
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 (10)
Some comparison of time-libs:
So be carefull
Intl
is very very slow.My mistake.
Intl
API is fast when cached: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
I've already meet it with Angular, which likes to recalculate template expressions frequently to detect changes.
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?
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.
Even GitHub Copilot did not suggest Intl.DateTimeFormat as a very easy solution <3
I need the formatting to a selectable language.. nothing more
Thank you very much.
I miss the illumination of the testability of the individual libraries and solutions. That is the most important point for me.
Thank you Dmitriy, now I choose my date lib more consciously π€
came for the info but stayed for the drama.
one problem i face with dayjs is that it does not return timezone abbriviation like we get in moment-tz.
Anybody knows how we can get timezone abbriviation using dayjs?