date-fns、dayjs、moment は、JavaScript 開発において日付の解析、操作、フォーマットを行うための代表的なライブラリです。moment は長年業界標準として君臨してきましたが、現在はメンテナンスモードに入っています。date-fns は関数型アプローチとツリーシェイキングを重視し、dayjs は moment と互換性のある API を持ちながら軽量であることを特徴としています。これらはそれぞれ異なる設計思想に基づいており、プロジェクトの規模や要件に応じて適切な選択が必要です。
JavaScript で日付を扱うことは、開発者にとって常に課題でした。ネイティブの Date オブジェクトは扱いにくく、タイムゾーンやフォーマットの処理が複雑です。moment、date-fns、dayjs はこの問題を解決するために生まれましたが、それぞれのアプローチは大きく異なります。ここでは、アーキテクチャの観点からこれらを比較し、実務での選択基準を明確にします。
moment はオブジェクト指向の設計です。
// moment: チェーン操作とミュータビリティ
const date = moment();
date.add(1, 'day').subtract(1, 'month');
// date オブジェクト自体が書き換わります
console.log(date.format('YYYY-MM-DD'));
date-fns は純粋な関数型アプローチです。
// date-fns: 関数合成とイミュータビリティ
import { addDays, subMonths, format } from 'date-fns';
const date = new Date();
const newDate = subMonths(addDays(date, 1), 1);
// 元の date オブジェクトは変更されません
console.log(format(newDate, 'yyyy-MM-dd'));
dayjs は moment との互換性を最優先しています。
moment とほぼ同じ API デザインを採用しています。moment を知っていれば、学習コストなしに使い始められます。// dayjs: moment 風のチェーン操作
import dayjs from 'dayjs';
const date = dayjs();
const newDate = date.add(1, 'day').subtract(1, 'month');
// チェーンは可能ですが、内部処理は最適化されています
console.log(newDate.format('YYYY-MM-DD'));
バンドルサイズへの影響は、モジュールの切り出し方に依存します。
moment は単一の巨大なオブジェクトとしてエクスポートされます。
// moment: 全体インポートが基本
import moment from 'moment';
// 必要な機能だけを使っても、ライブラリ全体がバンドルされる可能性が高い
moment().format('LLLL');
date-fns は関数ごとにファイルが分割されています。
// date-fns: 関数ごとのインポート
import { format } from 'date-fns';
import { ja } from 'date-fns/locale';
// 使わない関数はバンドルから除外されます
format(new Date(), 'P', { locale: ja });
dayjs はコア機能が軽量で、拡張はプラグインで行います。
moment よりも軽量ですが、date-fns ほどの細かさはありません。// dayjs: コア + プラグイン
import dayjs from 'dayjs';
import 'dayjs/locale/ja';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
// 必要なプラグインだけを拡張します
dayjs().utc().format();
多言語対応における扱いも重要な選定基準です。
moment はグローバルな状態を持ちます。
moment.locale('ja') とすると、その後のすべての処理に影響します。// moment: グローバルなロケール設定
moment.locale('ja');
const d1 = moment().format('LLLL'); // 日本語
moment.locale('en');
const d2 = moment().format('LLLL'); // 英語(以降すべて英語)
date-fns は引数としてロケールを渡します。
// date-fns: 引数でロケールを指定
import { format } from 'date-fns';
import { ja, enUS } from 'date-fns/locale';
const d1 = format(new Date(), 'P', { locale: ja });
const d2 = format(new Date(), 'P', { locale: enUS });
// それぞれ独立して動作します
dayjs は moment に近いですが、改善されています。
moment よりは安全ですが、date-fns ほど明示的ではありません。// dayjs: インスタンスごとのロケール
import dayjs from 'dayjs';
import 'dayjs/locale/ja';
import 'dayjs/locale/en';
const d1 = dayjs().locale('ja').format('LLLL');
const d2 = dayjs().locale('en').format('LLLL');
// インスタンス単位で切り替え可能です
ライブラリの長期的な維持性は、アーキテクチャ決定において最も重要な要素の一つです。
moment は公式にメンテナンスモードに入っています。
// moment: 使用を避けるべき
// 公式アナウンス:プロジェクトはメンテナンスモードです
import moment from 'moment'; // 新規プロジェクトでは非推奨
date-fns は活発に開発が続いています。
// date-fns: 活発な開発中
import { addDays } from 'date-fns'; // 推奨されるアプローチ
dayjs も積極的にメンテナンスされています。
moment の代替として多くのプロジェクトで採用されています。// dayjs: 安定した開発中
import dayjs from 'dayjs'; // 軽量な代替案として推奨
これら 3 つのライブラリには、共通する基盤技術があります。
Date オブジェクトを使用しています。// すべて Date オブジェクトに変換可能
const m = moment();
const d = dateFnsDate;
const dj = dayjs();
const native1 = m.toDate();
const native2 = new Date(d);
const native3 = dj.toDate();
// 操作の組み合わせ例
// moment
moment().add(1, 'day').startOf('month');
// date-fns
startOfMonth(addDays(new Date(), 1));
// dayjs
dayjs().add(1, 'day').startOf('month');
// TypeScript での使用例(すべて対応)
import moment from 'moment';
import { format } from 'date-fns';
import dayjs from 'dayjs';
// 型推論が効き、安全に開発できます
| 特徴 | date-fns | dayjs | moment |
|---|---|---|---|
| 設計パラダイム | 関数型(イミュータブル) | オブジェクト指向(ミュータブル) | オブジェクト指向(ミュータブル) |
| モジュール構造 | 関数ごと分割(ツリーシェイキング可) | コア + プラグイン | 単一巨大オブジェクト |
| ロケール処理 | 引数で指定(安全) | インスタンス/グローバル | グローバル(副作用あり) |
| メンテナンス | 活発 | 活発 | メンテナンスモード(非推奨) |
| 学習コスト | 中(関数型に慣れが必要) | 低(moment 経験者なら即戦力) | 低(API が豊富) |
| 新規推奨度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ❌ |
date-fns は、モダンな JavaScript アプリケーションにおける標準的な選択です。
関数型の設計は、テストのしやすさや予測可能性を高め、ツリーシェイキングによるパフォーマンス最適化も魅力的です。新規プロジェクトでは、まずこれを検討すべきです。
dayjs は、moment からの移行期間や、軽量さが求められる場合に有効です。
既存の moment コード資産を活かしたい場合や、バンドルサイズを気にしつつもオブジェクト指向の書き方を維持したい場合に適しています。
moment は、新規プロジェクトでは使用すべきではありません。
メンテナンスモードであり、将来的な技術的負債になります。既存システムで使われている場合は、date-fns または dayjs への移行計画を立てるのが賢明です。
最終的なアドバイス:日付処理はアプリケーションの根幹に関わることが多いです。一時的な手軽さよりも、長期的な保守性とパフォーマンスを重視してライブラリを選定してください。
既存のモダンな関数型プログラミングスタイルを採用しているプロジェクトや、バンドルサイズを厳密に管理する必要がある場合に date-fns を選択します。各関数が独立しているため、必要な機能だけをインポートでき、ツリーシェイキングとの相性が抜群です。イミュータブルな設計により、予期せぬ副作用を避けたい大規模アプリケーションに適しています。
moment の書き慣れた API を維持しつつ、より軽量なライブラリが必要な場合に dayjs が最適です。プラグイン機構により、必要な機能だけを後から追加できるため、初期コストを抑えながら拡張性を確保できます。既存の moment コードベースからの移行コストを最小限に抑えたいチームにとって、現実的な選択肢となります。
新規プロジェクトでは moment の使用を避けるべきです。これは公式にメンテナンスモードに入っており、バグ修正は行われますが新機能の追加や積極的な改善は行われていません。既存のレガシーシステムを維持する場合を除き、date-fns や dayjs への移行を検討するのが賢明です。
🔥️ NEW: date-fns v4.0 with first-class time zone support is out!
date-fns provides the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates in a browser & Node.js
👉 Blog
It's like Lodash for dates
import { compareAsc, format } from "date-fns";
format(new Date(2014, 1, 11), "yyyy-MM-dd");
//=> '2014-02-11'
const dates = [
new Date(1995, 6, 2),
new Date(1987, 1, 11),
new Date(1989, 6, 10),
];
dates.sort(compareAsc);
//=> [
// Wed Feb 11 1987 00:00:00,
// Mon Jul 10 1989 00:00:00,
// Sun Jul 02 1995 00:00:00
// ]
The library is available as an npm package. To install the package run:
npm install date-fns --save
See date-fns.org for more details, API, and other docs.