back

by dmit·6y ago·view on hn ↗
Please pardon this drive-by assessment of JS calendar libraries by a casual user.

moment.js: Mutable. Thank you, next.

Luxon: Takes the effort to implement `Interval`, which would be `Range<DateTime>` in any proper language, but somehow avoids providing separate `Date` and `Time` objects.

Day.js: When you kinda like moment.js, but your bundler says it's too fat.

date-fns: The finest of pure, curry-able functions over the minefield that is Javascript's `Date` object.

js-joda: If Javascript didn't have classes already, this project would probably port the entire Java runtime to JS just to replicate them. Likely the most correct handling of date/time stuff available for the browser, but damn, at what cost?

------------

Edit: this turned out way too negative, my bad. All I wanted was a library that offered:

0) Type definitions.

1) Immutable classes of `Date` (year, month, day), `Time` (hour, minute, etc), `DateTime` (the prior two combined), `Instant` (for a certain moment on the global timeline), and `Duration`.

1b) `DateTime` should probably be split into two separate things, one of which is aware of time zones.

2) All the obvious date arithmetic functions - duration between dates, adding/subtracting durations, etc etc.

7 comments
Hi. You seem to be asking for Temporal. It's coming, we hope! https://tc39.es/proposal-temporal/docs/index.html
Yes, thank you! I've seen this before and forgot. Looks like it's at Stage 2 right now, so still a ways to go until shipping, but should resolve all my gripes when it does.
Can you explain why you find mutability to be a showstopper? Is this a front end developer thing?

I like my consts and all where relevant, but I don't see why mutability is such a problem. Javascript is an imperative language where almost everything more complicated than a number is mutable. I see the lack of creating copies of objects with every operation as a benefit because of the RAM and CPU cycles it saves.

Is it because the API returns a reference to an object as well as updating said object? That's the API I'd expect, personally; if I want to do an addition of 1 to i, I'd write i += 1 and expect i to have incremented. I don't see why moment.add() should behave differently?

Basically, unexpected behavior. One of the "footguns" is that it's easy to write to a Moment object when you mean to be reading from it. And by the way, you can mutate objects declared with `const` in JS - you just can't reassign them.
I don't view the mutability of moment as a complete show stopper, though. If you limit the surface area of the mutability by hiding the underlying moment object within an abstraction, then you can at the very least keep the chances of misusing moment to a minimum.
I also think it's not a showstopper for Node.js apps. For bundling in a front-end app it's waaaay too big.
The moment locales webpack plugin goes a very long way toward dealing with the size downside of the library.

That said, we wouldn't be in this position if more companies used a shared CDN for libraries instead of always bundling them.

> shared CDN

That helps with transfer but clients still have to parse and execute the javascript which can be significant, especially on mobile devices.

It's very unpractical and error prone.

You call a function that requires a date (moment) object, and after the call you want to do something else with that date. Was it modified? Who knows. You will have to clone it before calling the aforementioned function. Otherwise, bugs.

You write a function that expects a date (moment) object. You want a different date (say "the day after"). If you call a method on the object, will the parent be smart enough not to touch anymore the date? You can't know that, so you will have to (remember to) start by cloning the date. Otherwise, bugs

Treating it as a dealbreaker seems a bit strong, but immutability has become very popular among JS devs recently. JS has pretty respectable functional-programming features, libraries like Immutable.js have cropped up to add memory-saving immutable data structures, and major frameworks like React and Redux play much more naturally with immutable data than mutable. The overall goal is scalability through reduction of side-effects. Whether the trade off is worth it or not, it's absolutely the fashion right now.
Others have already covered it, but it's mostly about limiting things that can go wrong. The single-threaded nature of JavaScript means data races are not an issue, but you can still get situations where a local variable (whether `const` or not) is mutated without it being obvious.

I see your concerns about performance, but 1) temporal objects are so small that there are plenty of optimization opportunities for the JS engines to eliminate or greatly reduce allocations.

2) The larger and older your project, and the more people are working on it (including authors of third-party dependencies), the murkier data ownership becomes. With that you are more likely to slip into defensive programming practices. "I have a `Date` object that is someone's birthday, but I need to pass it to multiple functions and then serialize it to storage. But I can't tell for sure what those functions are doing with that value. Should I serialize the date right away and persist that value later? Should I just create copies of the object to pass to the functions?"

But birthdays don't mutate, so now you're worrying about a thing that shouldn't be an issue! In the unlikely case that someone's birthday date is corrected, May 9th doesn't suddenly turn into July 15th. In the real world you don't drag the red circle drawn in marker on a paper calendar with your finger from one cell to another, or white out the day number and write in another one. You cross the old circle off and draw a new one in the right place.

My background is in backend services, hundreds of threads running on dozens of cores. The peace of mind that comes with being able to pass values to async functions and thread pool executors, and knowing that they won't turn into a pumpkin at midnight is a very real thing. There's a performance price to pay, sure, but it's so tiny compared to the wins in development and debugging times. And in the particular case of date/time objects, most things are around 8-16 bytes anyway so it ends up not mattering much.

Tangent: this is the fear that Rust talks about in its value proposition btw. Technically, everything is mutable in Rust. You can flip a bit in an integer that is passed to a function from inside that function if you want. But the difference is that you have control over who gets to do that, and you have visual cues in the code (the `mut` keyword), and you have assurances from the compiler that the rules are followed. One owner at a time. Many can look, but not while a value is modified. Best of both worlds. The end result is that it never bothered me that `Date` is mutable in Rust. Either I'm the owner, or I borrowed the value to look at it, or I borrowed it for mutation and have the guarantees that 1) nobody else is doing the same; and 2) nobody will see in-progress modifications until I'm done.

"moment.js: Mutable. Thank you, next."

Mutable is not bad, buddy. You live in mutable world.

False comparison. We're not creating a copy of the mutable world, we're writing software. Pretty different. Good luck programming in DNA or whatever and knowing that your code does what you want it to do.
Mutability, in this case, can be pretty unexpected.

Just like you don't expect the expression a + 5 to change the value of a but to return a new number, most people don't expect myDate.add(5, 'days') to change the value of myDate.

I know that moment values are mutable, yet I often forget it while writing code and make dangerous mistakes because of how unnatural it is to see an arithmetic expression that is not immutable.

That's totally expected behavior to me.

I would expect to use something like Moment.addDate(date1, 5, 'days') if I wanted a new instance of a date to be returned.

Even in javascript-land, methods like that will have side-effects, yet still return this often just for the sake of method chaining. E.g. date.add(5, 'days).format("%Y-%m-%d") would mutate date, but return the same instance as a convenience.

No I don't. I live in an immutable, four-dimensional world /s

These are all just models of the real world, not the reality. The real world can be modeled equally accurately as mutable or immutable, it just depends which properties of the real world you care about modeling.

Mutable is not bad when necessary, but otherwise I'd say that immutable is generally quite good.
> 1b) `DateTime` should probably be split into two separate things, one of which is aware of time zones.

due to daylight saving timezones depend on the date, which makes it hard to separate date and time

You're right! I should have phrased that better. I didn't mean splitting `DateTime` into timezone-aware `Date` and `Time`. I meant two different `DateTime`s.

Sometimes you need "Wake me up on December 24th at 10:00, regardless of where I am that day", and sometimes you want "The match will start on May 1st at 21:00 British Standard Time, and I want to be alerted about this even if I'm in New Zealand at that moment."

What I meant is a distinction between a `LocalDateTime` (first example, timezone is not relevant) and a `ZonedDateTime` (second example). The latter is very close to `Instant`, which is a point on the UTC timescale, but the subtle difference is that if timezone definitions were to change - as they often do - the `ZonedDateTime` would correctly remap to the actual `Instant` when the event was happening.

> `DateTime` should probably be split...

https://github.com/tc39/proposal-temporal

Anyone has an idea for a solid module that supports representations of sub-millisecond precision (preferrably ns but at least us)?
> moment.js: Mutable. Thank you, next.

A petty, thoughtless dismissal that reflects more on the one saying it than the library.