日付時刻操作ライブラリをmomentからdayjsへ乗り換えた

この記事は「[DeNAその2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/dena2)」16日目の記事です。

## 背景
JavaScriptの日付時刻操作は罠が多く、コードのメンテナンスも行いにくいため、
GitHub Star数が4万に迫っていて、安定感のあるmomentをこれまで採用してきました。
参考: https://qiita.com/labocho/items/5fbaa0491b67221419b4

しかし、momentはファイルサイズが大きく、jsのバンドルサイズが肥大化する原因にもなっていました。

そんな時、[Vue Fes Japan 2018](https://vuefes.jp/)の公演の中だったり、有識者の記事の中だったりで、momentではない他の日付時刻操作ライブラリを採用しているケースを知り、今回調べてみることにしました。

以下は検討するきっかけとなった資料です。

- [noteをNuxt.jsで再構築した話](https://speakerdeck.com/fukuiretu/notewonuxt-dot-jsdezai-gou-zhu-sitahua?slide=86)
- [Netlify+Node.js+MongoDBでJAMstackな草を生やすサービスをOSSで公開しました!](https://qiita.com/kt3k/items/10df41c74a752a4c6c9c#date-fns)

## 前提
調べるにあたって重視したのは以下の2点です。

> 1. momentよりもファイルサイズが軽量のもの
> 2. momentで行なっている日付時刻操作を代替できるもの

2について補足します。
私たちのサービスにおいて、momentが行なっていた処理は以下の3つです。
サンプルコードの通り、momentがtimestampを受け取って日付時刻を操作しています。

❶. 日付時刻をフォーマットする

```js
import moment from 'moment'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
moment(timestamp).format('YYYY/MM/DD HH:mm:ss')
> 2018/12/16 07:00:00
```

❷. 日付の加算と減算を計算する

```js
import moment from 'moment'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
moment(timestamp).add(3, 'days').format('YYYY/MM/DD')
> 2018/12/19
moment(timestamp).add(-3, 'days').format('YYYY/MM/DD')
> 2018/12/13
```

❸. 2つの日付の日数を計算する

```js
import moment from 'moment'

const timestamp1 = 1544911200 * 1000 // 2018/12/16 07:00:00
const timestamp2 = 1549663200 * 1000 // 2019/02/09 07:00:00
moment(timestamp1).diff(timestamp2, 'days')
> -55
moment(timestamp2).diff(timestamp1, 'days')
> 55
```

## 検討対象とその情報について
検討対象にしたライブラリは、「moment」と「dayjs」「date-fns」「luxon」です。GitHub Starが多く、メンテナンスが継続的に行われているライブラリをピックアップしてみました。
luxonについては、momentと同じOrganizationで開発されており、興味もあって検討対象に入れてみました。

### npmのダウンロード数から見る
npmのダウンロード数を比較できる[npmtrends](https://www.npmtrends.com/)によると、momentはまだまだ圧倒的に支持されており、次いでdate-fnsが徐々にシェアを伸ばしている様子。
dayjsとluxon頑張れ！というような状態です。
<img width="400" alt="npmtrends.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/f12fb42a-5b30-d09c-f7d5-0d5b9f9d2854.png">
画像元はこちらです。
https://www.npmtrends.com/date-fns-vs-dayjs-vs-luxon-vs-moment

### GitHubやDocumentから見る
上述のnpmのダウンロード数では、dayjsよりもdate-fnsが上回っていましたが、GitHub Starはdayjsの方が上回っています。
dayjsは今年の4月に生まれたばかりなので、これからさらに伸びていくことに期待です。

Documentについては、どのライブラリもよくまとめられていました。
その中でも、date-fnsは、欲しい情報がすぐに見つけられて好感を持ちました。
例えば、日付を加算するメソッドを探そうと`add`と入力したら一発で欲しい情報に辿り着けたのには感謝感激。
<img width="240" alt="doc-date-fns.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/18c66f21-5e81-cfef-31a7-f19be4fdb55b.png">
URL: https://date-fns.org/docs/Getting-Started

ライブラリごとの情報は以下の通りです。
(2018/12/10 00:03時点)

#### moment
<img width="240" alt="logo-moment.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/b7851f84-8e44-90b7-0612-c2d01d7fa952.png">
GitHub: https://github.com/moment/moment
Document: https://momentjs.com
GitHub Star: 39,459 / Latest commit: Oct 31, 2018 / created: Mar 1, 2011

#### dayjs
<img width="240" alt="logo-dayjs.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/e6fe701d-0dc2-b1ed-0aca-2cb01075a32d.png">
GitHub: https://github.com/iamkun/dayjs
Document: https://github.com/iamkun/dayjs/blob/master/docs/ja/README-ja.md
GitHub Star: 16,821 / Latest commit: Nov 20, 2018 / created: Apr 10, 2018

#### date-fns
<img width="240" alt="logo-date-fns.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/e53ead36-9d19-2d3d-b31b-88f93ffd059c.png">
GitHub: https://github.com/date-fns/date-fns
Document: https://date-fns.org
GitHub Star: 15,312 / Latest commit: Nov 14, 2018 / created: Oct 6, 2014

#### luxon
<img width="240" alt="logo-luxon.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/e030b076-c97b-2cfc-e0ed-88ddb56a1245.png">
GitHub: https://github.com/moment/luxon
Document: https://moment.github.io/luxon
GitHub Star: 7290 / Latest commit: Nov 30, 2018 / created: Nov 30, 2015

## サンプルコード
各ライブラリのサンプルコードを記載していきます。
処理は[前提](#前提)で記載した以下3つの処理を各ライブラリごとに記載しています。

> 1. 日付時刻をフォーマットする
> 2. 日付の加算と減算を計算する
> 3. 2つの日付の日数を計算する

### momentのサンプル
momentは、[前提](#前提)に記載しているので省略します。

### dayjsのサンプル
>  Moment.js の API との広い互換性を持ちます。
> 参照: https://github.com/iamkun/dayjs/blob/master/docs/ja/README-ja.md

上記の通り、dayjsはmomentと互換性があり、[前提](#前提)に記載したサンプルコードの`moment`を`dayjs`に置換しただけで作業は終了しました。
moment経験者は学習要らずかもしれません。

❶. 日付時刻をフォーマットする

```js
import dayjs from 'dayjs'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
dayjs(timestamp).format('YYYY/MM/DD HH:mm:ss')
> 2018/12/16 07:00:00
```

❷. 日付の加算と減算を計算する

```js
import dayjs from 'dayjs'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
dayjs(timestamp).add(3, 'days').format('YYYY/MM/DD')
> 2018/12/19
dayjs(timestamp).add(-3, 'days').format('YYYY/MM/DD')
> 2018/12/13
```

❸. 2つの日付の日数を計算する

```js
import dayjs from 'dayjs'

const timestamp1 = 1544911200 * 1000 // 2018/12/16 07:00:00
const timestamp2 = 1549663200 * 1000 // 2019/02/09 07:00:00
dayjs(timestamp1).diff(timestamp2, 'days')
> -55
dayjs(timestamp2).diff(timestamp1, 'days')
> 55
```

### date-fnsのサンプル
他のライブラリは、メソッドチェーンで頑張っていくのに対して、date-fnsはコードがシンプルに書けて使いやすかったです。

❶. 日付時刻をフォーマットする

```js
import format from 'date-fns/format'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
format(timestamp, 'YYYY/MM/DD HH:mm:ss')
> 2018/12/16 07:00:00
```

❷. 日付の加算と減算を計算する

```js
import format from 'date-fns/format'
import addDays from 'date-fns/add_days'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
format(addDays(timestamp, 3), 'YYYY/MM/DD')
> 2018/12/19
format(addDays(timestamp, -3), 'YYYY/MM/DD')
> 2018/12/13
```

❸. 2つの日付の日数を計算する

```js
import differenceInDays from 'date-fns/difference_in_days'

const timestamp1 = 1544911200 * 1000 // 2018/12/16 07:00:00
const timestamp2 = 1549663200 * 1000 // 2019/02/09 07:00:00
differenceInDays(timestamp1, timestamp2)
> -55
differenceInDays(timestamp2, timestamp1)
> 55
```

### luxonのサンプル
明示的なAPIがluxonの1つの特徴です。moment, dayjs, date-fnsは、「timestamp」「ISO8601フォーマットの文字列」「Dateオブジェクト」等をAPIに渡せば日付時刻操作を行うことができます。

```js
// momentの場合
moment(1544911200 * 1000).format('YYYY/MM/DD HH:mm:ss')
moment('2018-12-16T07:00:00+09:00').format('YYYY/MM/DD HH:mm:ss')
moment(new Date('2018-12-16 07:00:00')).format('YYYY/MM/DD HH:mm:ss')
```

一方luxonは、日付時刻の形式ごとにAPIが分けられているので、日付時刻のデータを厳密に管理したい人にとっては良いのかもしれません。

```js
// luxonの場合
DateTime.fromMillis(1544911200 * 1000).toFormat('yyyy/MM/dd HH:mm:ss')
DateTime.fromISO('2018-12-16T07:00:00+09:00').toFormat('yyyy/MM/dd HH:mm:ss')
DateTime.fromJSDate(new Date('2018-12-16 07:00')).toFormat('yyyy/MM/dd HH:mm:ss')
```

参考: https://qiita.com/jnst/items/545d9a7a5a95e190fbf5#-なぜ-luxon-がつくられたか

それでは、サンプルコードです。

❶. 日付時刻をフォーマットする

```js
import { DateTime } from 'luxon'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
DateTime.fromMillis(timestamp).toFormat('yyyy/MM/dd HH:mm:ss')
> 2018/12/16 07:00:00
```

❷. 日付の加算と減算を計算する

```js
import { DateTime } from 'luxon'

const timestamp = 1544911200 * 1000 // 2018/12/16 07:00:00
DateTime.fromMillis(timestamp).plus({ days: 3 }).toFormat('yyyy/MM/dd')
> 2018/12/19
DateTime.fromMillis(timestamp).plus({ days: -3 }).toFormat('yyyy/MM/dd')
> 2018/12/13
```

❸. 2つの日付の日数を計算する

```js
import { DateTime } from 'luxon'

const timestamp1 = 1544911200 * 1000 // 2018/12/16 07:00:00
const timestamp2 = 1549663200 * 1000 // 2019/02/09 07:00:00
const dt1 = DateTime.fromMillis(timestamp1)
const dt2 = DateTime.fromMillis(timestamp2)
Math.floor(dt1.diff(dt2, 'days').days)
> -55
Math.floor(dt2.diff(dt1, 'days').days)
> 55
```

## ファイルサイズ
上記サンプルコードをNuxt.js上に書き、それをanalyzeしてファイルサイズを確認しました。
以下にリポジトリを公開しています。
GitHub: https://github.com/yagisuke/jikan-libraries

### プロジェクト全体
<img width="800" alt="size.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/2b74bd81-cafd-af2b-480e-8e350a363948.png">

### ライブラリごと
ファイルサイズが軽量なものから並べています。
dayjsとdate-fnsはmomentよりもだいぶ軽量だったので好印象でした。
luxonはmomentよりも重いようです。

| ライブラリ名 | バンドルサイズ | 補足 |
|:-----------|:--------------|:-------|
| dayjs | <img width="225" alt="dayjs.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/e48656f1-61b7-5a1f-f22c-fa8bf41804d1.png"> | |
| date-fns | <img width="225" alt="date-fns.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/e8e5f085-b0e3-0871-1436-c47e5b917208.png">| サンプルコードで必要な format, addDays, differenceInDays のみに絞りました |
| moment | <img width="225" alt="moment.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/fbba3bfa-3f90-241a-f798-e77faf47f986.png"> | localeファイルはjaに絞りました |
| luxon |<img width="225" alt="luxon.png" src="https://qiita-image-store.s3.amazonaws.com/0/191671/88231e9c-12a3-9953-0649-47cb29502807.png">|  |


## まとめ
「moment」「dayjs」「date-fns」「luxon」をそれぞれみてきました。
比較するのって楽しいですね:relaxed:

そして、比較した結果、今回は `dayjs` を採用することにしました:tada::tada:
採用した理由としては、以下の通りです。

- GitHub Starやメンテナンス頻度を確認して安心感があったこと
- momentで行なっていた処理を代替できること
- momentに比べて大幅にファイルサイズを削減できること
- momentの利用経験者は学習コストがかからないこと
- momentと互換性があり、乗り換えまでに時間がかからないこと

`date-fns`と悩みましたが、決め手になったのは `momentと互換性があり、乗り換えまでに時間がかからないこと` でした。
momentユーザーの方は、この機会にぜひ`dayjs`を使ってみてはいかがでしょうか。

以上、お付き合いいただきありがとうございました。