-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3d32100
commit b38ccb9
Showing
1 changed file
with
122 additions
and
0 deletions.
There are no files selected for viewing
122 changes: 122 additions & 0 deletions
122
src/content/blog/2024-09-16-v4.0-with-time-zone-support.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
--- | ||
title: "v4.0 is out with first-class time zones support!" | ||
description: "date-fns v4.0 is out with first-class time zone support and no major breaking changes." | ||
pubDate: "Sep 16 2024" | ||
--- | ||
|
||
This is [Sasha @kossnocorp] here. | ||
|
||
I'm happy to announce the release of v4.0, which includes first-class time zone support and no major breaking changes. | ||
|
||
For TL;DR, [see the time zones doc](https://date-fns.org/v4.0.0/docs/Time-Zones) and [the change log entry](https://date-fns.org/v4.0.0/docs/Change-Log#v4.0.0-2024-09-16). | ||
|
||
## Time zones support | ||
|
||
It's one of the most long-awaited features. There's a fantastic third-party [`date-fns-tz`](https://www.npmjs.com/package/date-fns-tz) by [Marnus Weststrate](https://github.com/marnusw) that I have been recommending for years. I believe it started as a date-fns PR, which I never got to merge. | ||
|
||
Truth be told, I didn't have much experience working with time zones and had no vision for good API, and everything I saw proposed didn't feel right. I knew just 20% of devs needed it, and the `date-fns-tz` to `date-fns` downloads ratio (3.2M/21.7M~15%) shows that I was right. I feared that if I were to introduce it, these 20% would generate 80% of bugs and support requests and distract from the core functionality. On top of that, before Intl API became widespread, it required a heavyweight IANA database. I also didn't want to copy `date-fns-tz` and nullify Marnus' work. So, it was a conscious decision not to support them natively. | ||
|
||
In 2022, I came up with [`@date-fns/utc`] that immediately felt right. You get an option to make all the calculations in UTC and remove the local time zone from the equation without adding any extra weight to the core library. From then on, I knew I wanted to have something like that for all time zones, but it took me another two years to figure out how to approach it. | ||
|
||
Now I'm finally happy to say it's here. Just like with the UTC support, there's an external package [`@date-fns/tz`] that provides `TZDate` (as well as a few helper functions) that works with all date-fns functions: | ||
|
||
```ts | ||
import { TZDate } from "@date-fns/tz"; | ||
import { addHours } from "date-fns"; | ||
|
||
// Given that the system time zone is America/Los_Angeles | ||
// where DST happens on Sunday, 13 March 2022, 02:00:00 | ||
|
||
// Using the system time zone will produce 03:00 instead of 02:00 because of DST: | ||
const date = new Date(2022, 2, 13); | ||
addHours(date, 2).toString(); | ||
//=> 'Sun Mar 13 2022 03:00:00 GMT-0700 (Pacific Daylight Time)' | ||
|
||
// Using Asia/Singapore will provide the expected 02:00: | ||
const tzDate = new TZDate(2022, 2, 13, "Asia/Singapore"); | ||
addHours(tzDate, 2).toString(); | ||
//=> 'Sun Mar 13 2022 02:00:00 GMT+0800 (Singapore Standard Time)' | ||
``` | ||
|
||
Before, in v3, you couldn't mix `UTCDate` and `Date` in arguments without risking getting an unexpected result. Now, with v4, all functions normalize both arguments and the result to the first object argument (`Date` or a `Date` extension instance). | ||
|
||
It allows you to mix and match different types of time zones without risking your calculations being wrong. | ||
|
||
However, the results still might differ depending on the arguments' order: | ||
|
||
```ts | ||
import { TZDate } from "@date-fns/tz"; | ||
import { differenceInBusinessDays } from "date-fns"; | ||
|
||
const laterDate = new TZDate(2025, 0, 1, "Asia/Singapore"); | ||
const earlierDate = new TZDate(2024, 0, 1, "America/New_York"); | ||
|
||
// Will calculate in Asia/Singapore | ||
differenceInBusinessDays(laterDate, earlierDate); | ||
//=> 262 | ||
|
||
// Will calculate in America/New_York | ||
differenceInBusinessDays(earlierDate, laterDate); | ||
//=> -261 | ||
``` | ||
|
||
So, to work around the issue, most of the date-fns functions (all where time zones might affect the result) accept the context option `in` that allows to specify what time zone to use explicitly: | ||
|
||
```ts | ||
import { tz } from "@date-fns/tz"; | ||
|
||
// Will calculate in Asia/Singapore | ||
differenceInBusinessDays(laterDate, earlierDate); | ||
//=> 262 | ||
|
||
// Will normalize to America/Los_Angeles | ||
differenceInBusinessDays(laterDate, earlierDate, { | ||
in: tz("America/Los_Angeles"), | ||
}); | ||
//=> 261 | ||
``` | ||
|
||
I'm very happy with the result, both from an implementation and API point of view. It gives all the power of time zone calculations without adding much overhead to the core library. | ||
|
||
In addition, you can use both [`@date-fns/tz`] and [`@date-fns/utc`] without date-fns and still get value from them. | ||
|
||
This is the date-fns approach I was looking for. | ||
|
||
## Bundle footprint | ||
|
||
As I mentioned, the time zones don't add much overhead to the core library, and most of the functionality is handled by external and optional [`@date-fns/tz`]. [The Bundlephobia says](https://bundlephobia.com/package/[email protected]) that the total package size grew from `17.2 kB` to `17.5 kB`. That is a very irrelevant number by itself as it includes every single function and locale, but it is also illustrative. I had to update almost all of the 250 functions, which only amounted to a `300 B` increase, which I think is very good. | ||
|
||
As for `TZDate` itself, its minimal version, `TZDateMini`, is just `761 B`, and the full version that adds strings formatting is `1 kB`. That is half the size of what competitors offer. | ||
|
||
`UTCDate` is even smaller, with `UTCDateMini` just `239 B` and the full version `504 B`, nearly five times smaller than the competition. | ||
|
||
## Breaking changes | ||
|
||
In [the v3.0 announcement](https://blog.date-fns.org/v3-is-out/), I suggested that I want to get rid of CommonJS support. Some of you'll be happy to learn that date-fns v4.0 is still a dual ESM/CommonJS package. With v4.0 coming less than a year since v3.0, while the previous iteration took 4 years, and the overall negative impact of the ESM transition on the ecosystem, I decided to wait a little longer. The package is, however, now ESM-first and has `"type": "module"` in `package.json`, and instead of `.mjs`, it has `.cjs`. This should not affect anyone, except those doing something funny with the internals. | ||
|
||
I also had to change some types, mostly function generics. Again, this should not affect normal usage but rare edge cases. I won't be able to list all the possible ways TypeScript might complain after upgrading to v4.0, but if it does, I'm sure the fixes will be trivial. | ||
|
||
This version breaks the tradition of packing major releases with many breaking changes, and I intend to keep it this way. | ||
|
||
## v5.0 and beyond | ||
|
||
Another tradition that I violated is to have many years between releases. I'm happy about it and intend to have major versions thematic, ship more often, and with minimal breaking changes (or even without them), just like v4.0. | ||
|
||
Now, after I finally tackle time zones, I will have more space for the missing features like durations, global locales, or any other blind spot of date-fns that remains, and I will make date-fns a feature-complete date-time library. | ||
|
||
One of my biggest, more abstract goals is to finish the transition to the international API era and prepare for the soon-coming Temporal API. | ||
|
||
With `format` still being the most used and the heaviest function at the same time, it would be beneficial to nudge developers to adopt a more lightweight and robust Intl API. I plan to extract custom locales and locale-dependant functions to separate packages like `@date-fns/es` and make the Intl-based format the default way to render dates. | ||
|
||
I also have [Interim API](https://github.com/date-fns/interim) and [experiment in work](https://x.com/kossnocorp/status/1778923173792379274), a lightweight Temporal API subset, to help developers start using it now and when it's time to migrate—simply find-and-replace class names or imports. | ||
|
||
Of course, the final game with Temporal API is to have a date-fns version that works with it while still maintaining the `Date` version of the library for years and maybe decades to come. | ||
|
||
You can see that I don't have concrete plans for v5.0. It is clear that it is a lot of work, so if you want to support date-fns or me personally, [consider sponsoring me at GitHub](https://github.com/sponsors/kossnocorp), [subscribing to my newsletter](https://kossnocorp.ck.page/), or [sending me a nice DM](https://twitter.com/kossnocorp). | ||
|
||
Cheers, | ||
[Sasha @kossnocorp] | ||
|
||
[`@date-fns/utc`]: https://github.com/date-fns/utc | ||
[`@date-fns/tz`]: https://github.com/date-fns/tz | ||
[Sasha @kossnocorp]: https://twitter.com/kossnocorp |