date-fns vs dayjs — 日付ライブラリ徹底比較【2025年版】
1. 結論
軽量さとMoment.jsからの移行しやすさを重視するなら dayjs、関数型スタイルでTree Shakingを最大限に活かしたいなら date-fns を選んでください。どちらも本番環境で十分に信頼できるライブラリであり、プロジェクトの設計思想やバンドルサイズ戦略に応じて選択するのが最適です。
2. 比較表
| 項目 | date-fns | dayjs |
|---|---|---|
| GitHubスター | ≈ 35k | ≈ 47k |
| npm 週間DL数 | ≈ 25M | ≈ 22M |
| 設計パラダイム | 関数型(純粋関数の集合) | オブジェクト指向(メソッドチェーン) |
| イミュータブル | ✅(ネイティブDateを直接変更しない) | ✅(操作ごとに新しいインスタンス) |
| バンドルサイズ(フル) | ≈ 80 KB (min+gzip 約18KB) | ≈ 7 KB (min+gzip 約2KB) |
| Tree Shaking | ✅ 非常に効果的 | ⚠️ プラグイン単位では可能だがコア分は常に含まれる |
| TypeScript対応 | ✅ 組み込み型定義 | ✅ 組み込み型定義 |
| タイムゾーン対応 | date-fns-tz パッケージ | dayjs/plugin/utc + dayjs/plugin/timezone |
| ロケール数 | 60以上 | 140以上 |
| ロケール読み込み | 個別import(Tree Shaking対応) | 個別import or require |
| Moment.js互換API | ❌ 独自API | ✅ ほぼ互換 |
| プラグインシステム | ❌(全て個別関数) | ✅(.extend() で拡張) |
| 学習コスト | 中(関数名を覚える必要あり) | 低(Moment.js経験者はほぼゼロコスト) |
| 最終更新(2025年時点) | 活発にメンテナンス | 活発にメンテナンス |
| ライセンス | MIT | MIT |
3. それぞれの強み
date-fns の強み
-
Tree Shaking との相性が抜群 関数単位でimportするため、使った関数だけがバンドルに含まれます。大規模アプリでも「使っている機能分だけ」のサイズに収まります。
-
関数型プログラミングとの親和性 純粋関数として設計されているため、
pipeやcomposeとの組み合わせが自然です。FP志向のコードベースに馴染みます。 -
FP サブモジュール(
date-fns/fp) カリー化された関数群が用意されており、ポイントフリースタイルでの記述が可能です。 -
ネイティブ Date オブジェクトをそのまま使う 独自のラッパーオブジェクトを生成しないため、既存コードとの相互運用が容易です。
dayjs の強み
-
圧倒的な軽量さ コアがわずか約2KB(min+gzip)。パフォーマンスバジェットが厳しいプロジェクトで大きなアドバンテージです。
-
Moment.js とほぼ同じAPI
.format()、.add()、.diff()など、Moment.jsユーザーが直感的に使えるAPIです。移行コストが極めて低いです。 -
メソッドチェーンの可読性
dayjs().add(1, 'day').startOf('month').format()のように、操作を連鎖的に記述できます。 -
プラグインによる段階的拡張 必要な機能だけをプラグインとして追加できるため、コアの軽量さを維持しつつ機能を拡張できます。
4. コード例で比較
インストール
npm install date-fns
npm install dayjs
4-1. 基本的なフォーマット
// ===== date-fns =====
import { format } from "date-fns";
import { ja } from "date-fns/locale";
const now = new Date();
const result1 = format(now, "yyyy年MM月dd日(EEEE)HH:mm", { locale: ja });
console.log(result1);
// => "2025年07月11日(金曜日)14:30"
// ===== dayjs =====
import dayjs from "dayjs";
import "dayjs/locale/ja";
dayjs.locale("ja");
const result2 = dayjs().format("YYYY年MM月DD日(dddd)HH:mm");
console.log(result2);
// => "2025年07月11日(金曜日)14:30"
ポイント: date-fns はロケールを関数の引数として渡します。dayjs はグローバルまたはインスタンス単位で設定します。
4-2. 日付の加算・比較
// ===== date-fns =====
import { addDays, addMonths, isBefore, differenceInDays } from "date-fns";
const today = new Date();
const nextWeek = addDays(today, 7);
const nextMonth = addMonths(today, 1);
console.log(isBefore(nextWeek, nextMonth)); // => true
console.log(differenceInDays(nextMonth, today)); // => 30 or 31
// ===== dayjs =====
import dayjs from "dayjs";
const todayDj = dayjs();
const nextWeekDj = todayDj.add(7, "day");
const nextMonthDj = todayDj.add(1, "month");
console.log(nextWeekDj.isBefore(nextMonthDj)); // => true
console.log(nextMonthDj.diff(todayDj, "day")); // => 30 or 31
ポイント: date-fns は「何をするか」が関数名で明確です。dayjs はメソッドチェーンで流れるように書けます。
4-3. タイムゾーン変換
// ===== date-fns + date-fns-tz =====
import { formatInTimeZone } from "date-fns-tz";
const utcDate = new Date("2025-07-11T05:00:00Z");
const tokyoTime = formatInTimeZone(utcDate, "Asia/Tokyo", "yyyy-MM-dd HH:mm:ss zzz");
console.log(tokyoTime);
// => "2025-07-11 14:00:00 JST"
// ===== dayjs + plugins =====
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
const tokyoTimeDj = dayjs.utc("2025-07-11T05:00:00Z").tz("Asia/Tokyo").format("YYYY-MM-DD HH:mm:ss z");
console.log(tokyoTimeDj);
// => "2025-07-11 14:00:00 JST"
4-4. 関数型パイプライン(date-fns の FP サブモジュール)
// date-fns/fp を使ったポイントフリースタイル
import { pipe } from "date-fns/fp";
import { addDays } from "date-fns/fp/addDays";
import { startOfMonth } from "date-fns/fp/startOfMonth";
import { format } from "date-fns/fp/format";
const transform = pipe(
addDays(7),
startOfMonth,
format("yyyy-MM-dd")
);
console.log(transform(new Date("2025-07-11")));
// => "2025-08-01"
// dayjs で同等の処理
import dayjs from "dayjs";
const result = dayjs("2025-07-11")
.add(7, "day")
.startOf("month")
.format("YYYY-MM-DD");
console.log(result);
// => "2025-08-01"
ポイント: 関数合成を多用するアーキテクチャでは date-fns/fp が真価を発揮します。一方、dayjs のメソッドチェーンも十分に読みやすいです。
5. どちらを選ぶべきか — ユースケース別推奨
✅ dayjs を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| Moment.js からの移行 | APIがほぼ互換のため、機械的な置換が可能 |
| バンドルサイズを極限まで削りたい | コア2KBは圧倒的に軽量 |
| 日付操作の種類が多い(フル機能を使う) | 全機能使ってもdate-fnsより小さい場合が多い |
| チームにMoment.js経験者が多い | 学習コストがほぼゼロ |
| プロトタイプ・小規模プロジェクト | 導入が手軽でセットアップが最小限 |
✅ date-fns を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| 関数型プログラミングを採用している | 純粋関数・カリー化・パイプラインとの親和性が高い |
| 使う関数が限定的(Tree Shaking重視) | 数関数だけなら dayjs コアより小さくなることもある |
| ネイティブ Date との相互運用が多い | ラッパーオブジェクトを介さず直接操作できる |
| ESModules を前提としたモダンなビルド環境 | Tree Shakingの恩恵を最大限に受けられる |
| テストの書きやすさを重視 | 純粋関数のため、モック不要でテストしやすい |
🤔 どちらでも良いケース
- Next.js / Nuxt.js などのフレームワーク上での一般的な日付表示 — どちらを選んでもパフォーマンス差は体感できません。チームの好みで決めてください。
- Node.js のバックエンド処理 — バンドルサイズの制約がないため、APIの好みで選んで問題ありません。
6. まとめ
選定フローチャート:
Moment.js から移行? ──Yes──▶ dayjs
│
No
│
関数型スタイル重視? ──Yes──▶ date-fns
│
No
│
バンドルサイズ最優先? ──Yes──▶ dayjs(コア2KB)
│ ※ただし使う関数が2〜3個なら date-fns も検討
No
│
▼
チームの好みで選んでOK 🎉
date-fns と dayjs はどちらも成熟した優れたライブラリです。「関数型 vs オブジェクト指向」という設計思想の違いが最大の選定ポイントであり、機能面での決定的な差はほとんどありません。
プロジェクトのコーディング規約やチームメンバーの経験に合わせて選択すれば、どちらを選んでも後悔することはないでしょう。迷ったら、まずは小さなユーティリティファイルで両方試してみることをおすすめします。