date-fns、dayjs、luxon、moment はすべて JavaScript における日付・時刻の操作を簡略化するためのライブラリです。これらのライブラリは、ネイティブの Date オブジェクトが抱える不変性の欠如、タイムゾーン対応の難しさ、直感的でない API といった課題を解決するために設計されています。moment は長らく業界標準として使われてきましたが、現在はメンテナンスモードに入っており、新規プロジェクトでの使用は推奨されていません。一方、date-fns は関数型アプローチと優れたツリーシェイキングを特徴とし、dayjs は moment に似たチェーン可能な API を軽量に再現し、luxon は現代的な Intl API を活用して強力なタイムゾーンサポートを提供します。
フロントエンド開発で日付を扱う際、Date オブジェクトの限界にすぐにぶつかります。そこで登場するのが日付操作ライブラリですが、選択肢は多く、それぞれ設計思想や機能が大きく異なります。特に moment は公式に非推奨となっており、新規プロジェクトでの使用は避けるべきです。ここでは、プロフェッショナルな開発者がアーキテクチャ判断を行うために必要な、技術的・実践的な観点から4つの主要ライブラリを比較します。
moment はかつてデファクトスタンダードでしたが、公式ドキュメントおよび npm ページ に明記されている通り、「メンテナンスモード」に入っており、新規プロジェクトでの使用は推奨されていません。代わりに date-fns、dayjs、luxon のいずれかを検討すべきです。
残り3つは、以下のような設計思想の違いがあります:
date-fns: 関数型スタイル。不変性を保ち、個別の関数をインポートして使う。dayjs: moment に似たチェーン可能な API を提供しつつ、軽量でモジュール化可能。luxon: 現代的な JavaScript(特に Intl API)を活用し、タイムゾーン処理に強みを持つ。date-fns は完全に不変で、関数に日付を渡すと新しい日付が返されます。元の値は一切変更されません。
// date-fns
import { addDays, format } from 'date-fns';
const today = new Date();
const tomorrow = addDays(today, 1);
console.log(format(tomorrow, 'yyyy-MM-dd')); // 新しい日付を返す
dayjs は moment と同様にチェーン可能なメソッドを持ちますが、内部的には不変です(各メソッドが新しいインスタンスを返す)。
// dayjs
import dayjs from 'dayjs';
const today = dayjs();
const tomorrow = today.add(1, 'day');
console.log(tomorrow.format('YYYY-MM-DD')); // 新しいインスタンス
luxon も同様に不変で、DateTime クラスのメソッドは常に新しいインスタンスを返します。
// luxon
import { DateTime } from 'luxon';
const today = DateTime.now();
const tomorrow = today.plus({ days: 1 });
console.log(tomorrow.toFormat('yyyy-MM-dd')); // 新しいインスタンス
moment(非推奨)は、古いバージョンでは可変でしたが、後期には不変メソッドも追加されました。しかし、設計が古く、bundle size も大きいのが問題です。
// moment(非推奨)
import moment from 'moment';
const today = moment();
const tomorrow = today.clone().add(1, 'days'); // clone 必須で安全
console.log(tomorrow.format('YYYY-MM-DD'));
💡 補足:
dayjsやluxonはmomentと似た API を持ちますが、内部は不変で安全です。一方、date-fnsは「関数に日付を渡す」という素朴なアプローチを取ります。
date-fns はコアパッケージにタイムゾーンサポートが含まれていません。date-fns-tz という別パッケージが必要です。
// date-fns + date-fns-tz
import { zonedTimeToUtc, utcToZonedTime, format } from 'date-fns-tz';
const utcDate = zonedTimeToUtc('2023-07-01 12:00:00', 'Asia/Tokyo');
const tokyoTime = utcToZonedTime(utcDate, 'Asia/Tokyo');
console.log(format(tokyoTime, 'yyyy-MM-dd HH:mm', { timeZone: 'Asia/Tokyo' }));
dayjs もコアにはタイムゾーン機能がなく、timezone プラグインを有効にする必要があります。
// dayjs + timezone plugin
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Tokyo');
const tokyoTime = dayjs.tz('2023-07-01 12:00:00', 'Asia/Tokyo');
console.log(tokyoTime.format('YYYY-MM-DD HH:mm'));
luxon はビルトインでタイムゾーン対応しており、Intl API を利用するため、追加プラグイン不要で動作します。
// luxon(タイムゾーン内蔵)
import { DateTime } from 'luxon';
const tokyoTime = DateTime.fromISO('2023-07-01T12:00:00', { zone: 'Asia/Tokyo' });
console.log(tokyoTime.toFormat('yyyy-MM-dd HH:mm')); // タイムゾーン情報保持
moment は moment-timezone という別パッケージが必要で、bundle size がさらに膨らみます(非推奨)。
// moment-timezone(非推奨)
import moment from 'moment-timezone';
const tokyoTime = moment.tz('2023-07-01 12:00:00', 'Asia/Tokyo');
console.log(tokyoTime.format('YYYY-MM-DD HH:mm'));
💡 タイムゾーンを頻繁に扱うアプリ(国際サービス、スケジューラなど)では、
luxonが最も手軽で堅牢です。
date-fns は関数単位でインポートできるため、ツリーシェイキングに最適です。使わない関数は一切バンドルされません。
// date-fns — 最小限のコード
import { format, isBefore } from 'date-fns';
dayjs もプラグイン方式で、必要な機能だけを追加できます。コアは非常に小さいです。
// dayjs — コア + 必要なプラグインのみ
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
luxon は ES モジュール対応でツリーシェイキング可能ですが、機能が密結合しているため、date-fns や dayjs ほど細かく削減できません。
// luxon — 基本的に DateTime を使う
import { DateTime } from 'luxon';
moment はツリーシェイキングが効きにくい構造で、ロケールデータも含めると bundle size が肥大化します(非推奨)。
date-fns は parse 関数でカスタムフォーマットに対応しますが、フォーマット文字列の指定が必要です。
// date-fns
import { parse, format } from 'date-fns';
const date = parse('01/07/2023', 'dd/MM/yyyy', new Date());
console.log(format(date, 'yyyy-MM-dd'));
dayjs は CustomParseFormat プラグインを使うことで、柔軟なパースが可能になります。
// dayjs + CustomParseFormat
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
const date = dayjs('01/07/2023', 'DD/MM/YYYY');
console.log(date.format('YYYY-MM-DD'));
luxon は fromFormat メソッドで直接カスタムフォーマットをパースできます。
// luxon
import { DateTime } from 'luxon';
const date = DateTime.fromFormat('01/07/2023', 'dd/MM/yyyy');
console.log(date.toFormat('yyyy-MM-dd'));
moment は柔軟なパースが可能でしたが、非推奨のため新規採用は避けてください。
date-fns は formatDistance 関数で相対時間を生成できます。
// date-fns
import { formatDistance, subMinutes } from 'date-fns';
import { ja } from 'date-fns/locale';
const past = subMinutes(new Date(), 2);
console.log(formatDistance(past, new Date(), { locale: ja })); // 「2分前」
dayjs は RelativeTime プラグインを有効にすることで、.fromNow() が使えます。
// dayjs
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/ja';
dayjs.extend(relativeTime);
dayjs.locale('ja');
const past = dayjs().subtract(2, 'minute');
console.log(past.fromNow()); // 「2分前」
luxon は toRelative() メソッドを備えており、ロケール対応も簡単です。
// luxon
import { DateTime } from 'luxon';
const past = DateTime.now().minus({ minutes: 2 });
console.log(past.toRelative({ locale: 'ja' })); // 「2分前」
すべての現代的ライブラリ(date-fns, dayjs, luxon)は不変性を保証しており、ユニットテストで副作用の心配がありません。一方、moment は過去に可変インスタンスによるバグが多発した経緯があり、それが非推奨の理由の一つでもあります。
| 特徴 | date-fns | dayjs | luxon | moment |
|---|---|---|---|---|
| 新規プロジェクト推奨 | ✅ | ✅ | ✅ | ❌(非推奨) |
| 不変性 | ✅(関数型) | ✅(チェーン) | ✅(クラス) | ⚠️(注意が必要) |
| タイムゾーン | 別パッケージ | プラグイン | ビルトイン | 別パッケージ |
| ツリーシェイキング | 最適 | 良好 | 可能だが限定的 | 劣悪 |
| 学習コスト | 中(関数型) | 低(moment に似てる) | 中(Intl 依存) | 低(だが非推奨) |
date-fnsmoment に似た API で軽量さが欲しいなら → dayjsluxonmoment を使うのは避けてください最終的には、チームの好みやプロジェクトの要件(特にタイムゾーンの有無)によって決めるのがベストです。ただし、moment は公式に非推奨なので、移行計画を立てるか、最初から他の選択肢を選ぶことを強く推奨します。
date-fns は、関数型プログラミングを好み、bundle size を最小限に抑えたいプロジェクトに最適です。各関数を個別にインポートできるため、ツリーシェイキングが非常に効果的です。ただし、タイムゾーン処理には別パッケージ(date-fns-tz)が必要で、API が moment のようなチェーン形式ではない点に注意が必要です。
dayjs は、moment に似た使いやすさを維持しつつ、軽量でモダンな代替手段を求めているチームに向いています。プラグイン方式で必要な機能だけを追加でき、bundle size も小さく抑えられます。既存の moment コードを移行する際にも学習コストが低く済みます。
moment は公式に「メンテナンスモード」に入っており、新規プロジェクトでの使用は推奨されていません。bundle size が大きく、ツリーシェイキングも効きにくく、現代的な要件(特にタイムゾーンや不変性)に対して劣っています。既存プロジェクトの保守には使えますが、新規開発では date-fns、dayjs、luxon のいずれかを検討すべきです。
luxon は、タイムゾーンや国際化(ロケール)を多用するアプリケーション、例えばグローバル展開するサービスやスケジューリングツールに最適です。Intl API を活用しているため、追加のプラグインなしで強力なタイムゾーンサポートが利用できます。ただし、ES2018 以降の環境を前提としている点に留意してください。
🔥️ 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.