web-utilities-date-fns について
date-fns で日付を扱う開発者向けのパターンガイドで、書式設定、解析、演算、比較、ローカライズ、タイムゾーンを扱います。純粋関数、名前付きインポート、解析結果の検証を重視し、v3 と v4 のタイムゾーン方式を区別しています。
主な機能
- 01Unicode TR35 トークンによる書式設定
- 02ISO・独自形式の日付解析と検証
- 03不変の日付演算と期間境界
- 04日付比較と期間処理
- 05ロケール対応表示とバージョン別タイムゾーン方式
活用シーン
- 01多言語インターフェースの日付表示
- 02ユーザー入力や API の日付を検証
- 03請求期間、カレンダー範囲、経過時間を計算
- 04タイムゾーンを含む日付を処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-utilities-date-fnsインストール後、Claude Code で/web-utilities-date-fnsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/comparison.mdcore.mdi18n.mdrelative.mdtimezone.md詳細
date-fns の日付ユーティリティのパターン
クイックガイド: date-fns はツリーシェイキングに対応した純粋関数のセットです。各関数は新しい
Dateを返し、何も変更しません。ISO 文字列にはparseISO、カスタム形式にはparseで解析し、v4 では v3 のように例外がスローされず Invalid Date が返されるため、isValidで確認してください。書式トークンは Unicode TR35(yyyy、dd、EEEE)に準拠しており、Moment のものとは異なります。タイムゾーンサポートは、メジャーバージョンによって名前が異なる関連パッケージで提供されます。
詳細なリソース:
- examples/core.md — 書式設定、検証付きの解析、算術演算、境界、プリセット範囲
- examples/timezone.md — TZDate、
inオプション、transpose、DST の検出、UTC クラス - examples/i18n.md — ロケールマップ、ローカライズされた月名と曜日名、週の開始曜日
- examples/relative.md — 距離、期間、相対表示と絶対表示
- examples/comparison.md — 比較、区間の生成、重複の検出、検証
- reference.md — 書式トークン表、関数一覧、v4 の破壊的変更、Moment からの移行表
該当するパス
- v4 で、日付にタイムゾーンが含まれる場合 —
@date-fns/tzはTZDateとtz()を、@date-fns/utcはUTCDateを提供し、transposeはコアパッケージに含まれています。examples/timezone.md を参照してください。 - v3.x で、日付にタイムゾーンが含まれる場合 —
date-fns-tzはformatInTimeZone、toZonedTime、fromZonedTimeを提供します。同じファイルの後半を参照してください。 - タイムゾーンが重要でない場合 — ISO UTC 形式で保存および送信し、
parseISOで読み込み、日付のみの値はstartOfDayで正規化し、タイムゾーンのファイルは参照しないでください。
<critical_requirements>
date-fns のコードを書く前に
ISO 8601 文字列の解析には parseISO を使ってください。 new Date(string) による解析はエンジンに依存するため、new Date("01/15/2026") は実行環境によって成功したり、失敗したり、別の日を意味したりします。
解析した値には isValid() を確認してください。 v4 では、v3 で例外が発生していた箇所が Invalid Date や NaN を返すため、解析結果を確認しないまま使うと、後続の最初の format 呼び出しまで問題が表面化しません。
関数は名前付きで個別にインポートしてください。 import * as dateFns はツリーシェイキングを妨げ、ライブラリ全体を取り込みます。名前付きの format インポートなら、およそ 2 KB です。
新しい日付を作るときは、Date のセッターではなく add*/sub*/startOf* を使ってください。
date.setDate(...) は、その参照を共有するすべての箇所から見えるオブジェクトを変更します。一方、date-fns の関数はすべて新しい日付を返します。
</critical_requirements>
自動検出: date-fns, format, parseISO, addDays, subMonths, differenceInDays, formatDistance, formatDistanceToNow, isAfter, isBefore, eachDayOfInterval, startOfWeek, weekStartsOn, date-fns-tz, @date-fns/tz, @date-fns/utc, TZDate, TZDateMini, UTCDate, UTCDateMini, tz(), transpose, tzName, tzScan, tzOffset, withTimeZone
対象:
- ロケールの有無を問わない、表示用の日付の書式設定
- 日付文字列の解析と、解析結果の検証
- 日、月、年、営業日の加算と減算
- 期間の境界、およびカレンダー用の日付範囲の生成
- 比較、期間への包含、および重複の検出
- 相対表現や期間の表現(「2 時間前」、「5 か月 14 日」)
対象外:
- 算術処理を伴わない表示のみの書式設定 — プラットフォーム標準の
Intl.DateTimeFormatでバンドルサイズを増やさずに対応できます。また、Temporal標準を備えた実行環境では、このライブラリの大部分が不要になります - 繰り返しルール — 繰り返しイベントを発生時刻に展開する処理は別の課題です。date-fns は、展開によって得られた時点を扱います
- tz データベース自体 — 関連パッケージは、独自のデータをバンドルするのではなく、実行環境の IANA データを読み取ります
メソッドではなく関数を使います。チェーン可能なラッパーオブジェクトはありません。format(date, "PP") は Date を受け取り、文字列を返します。addDays(date, 7) は Date を受け取り、新しい Date を返します。この設計によってライブラリはツリーシェイキング可能になり、バンドラーはインポートしていない 200 個の関数を除外できます。また、個々の操作をそれぞれ単独でテストできます。
覚えておきたい点が 2 つあります。合成した処理は内側から外側へ読みます(format(startOfMonth(addDays(d, 7)), F))。また、暗黙の設定はないため、ロケールやタイムゾーンを使うには、オプションとして呼び出しに渡す必要があります。
基本パターン
パターン 1:書式トークンは Unicode TR35 に準拠
トークンは Unicode Technical Standard #35 に準拠しており、Moment とは異なります。YYYY ではなく yyyy、DD ではなく dd、dddd ではなく EEEE を使います。Moment のトークンを使ってもエラーにはならず、別の値が生成されます。
import { format } from "date-fns";
const date = new Date(2026, 0, 15, 14, 30, 0);
format(date, "yyyy-MM-dd"); // "2026-01-15"
format(date, "MMMM d, yyyy 'at' h:mm a"); // "January 15, 2026 at 2:30 PM"
format(date, "EEEE"); // "Thursday"
トークン一覧: reference.md。コード全体: examples/core.md
パターン 2:ロケールに応じた書式設定のショートカット
P、PP、PPP、PPPP(時刻には p、pp)を使うと、各ロケール固有の慣習に沿って表示されるため、同じ呼び出しでもどの地域でも適切に読めます。MM/dd/yyyy のように固定した書式は米国専用です。
import { format } from "date-fns";
import { enUS, de, ja } from "date-fns/locale";
format(date, "P", { locale: enUS }); // "01/15/2026"
format(date, "P", { locale: de }); // "15.01.2026"
format(date, "P", { locale: ja }); // "2026/01/15"
format(date, "PPPP", { locale: de }); // "Donnerstag, 15. Januar 2026"
コード全体: examples/i18n.md
パターン 3:解析してから検証する
ISO 8601 には parseISO を使い、独自の書式には第 3 引数として基準日を指定して parse を使います。どちらも例外をスローしないため、使う前に検証してください。さらに format で往復変換すると、2 月 30 日のように、解析はできても実在しない日付を除外できます。
import { parseISO, parse, isValid, format } from "date-fns";
const fromApi = parseISO("2026-01-15T14:30:00Z");
const fromUser = parse("15/01/2026", "dd/MM/yyyy", new Date());
if (!isValid(fromUser)) return null;
if (format(fromUser, "dd/MM/yyyy") !== "15/01/2026") return null; // Feb 30 etc.
コード全体: examples/core.md
パターン 4:算術処理は新しい日付を返す
すべての add* 関数と sub* 関数は入力を変更しないため、状態に保持した日付やプロパティとして渡された日付を安全に使って計算できます。
import { addDays, addMonths, subDays } from "date-fns";
const trialEnd = addDays(signupDate, 14);
const nextBilling = addMonths(signupDate, 1);
const warnAt = subDays(trialEnd, 7);
// signupDate is unchanged
コード全体: examples/core.md
パターン 5:期間の境界
startOf*/endOf* は月の日数、うるう年、週の開始日の規則を処理し、>= start AND <= end フィルターに必要な両端を含む範囲を返します。endOfDay は深夜ではなく、23:59:59.999 です。
import {
startOfDay,
endOfDay,
startOfWeek,
startOfMonth,
endOfMonth,
} from "date-fns";
const today = { start: startOfDay(date), end: endOfDay(date) };
const thisMonth = { start: startOfMonth(date), end: endOfMonth(date) };
const isoWeekStart = startOfWeek(date, { weekStartsOn: 1 }); // Monday
全コード:examples/core.md
パターン 6:タイムスタンプ計算ではなく比較関数を使う
isSameDay、isWithinInterval などは意図を明確に表現し、手動での処理では誤りやすい単位の切り捨ても適切に行います。
import { isAfter, isSameDay, isWithinInterval, isWeekend } from "date-fns";
isAfter(end, start);
isSameDay(a, b); // ignores time-of-day
isWithinInterval(candidate, { start, end }); // endpoints sorted, so order is not checked
isWeekend(date);
<red_flags>
注意すべき点
実行時にエラーが発生します:
- 検証していない解析結果をフォーマットすること — Invalid Date に対する
formatは例外をスローします。また、v4 では解析時に警告も表示されません。isValidで判定してください。 - 2 つの引数で
parseを呼び出すこと — 基準日が必須であり、その年と月はフォーマット文字列で省略された情報を補います。 UTCDateに対してformat(date, "z")を使うこと —zトークンにはネイティブのDateが必要です。
予想外の動作:
startがendより後にある区間も拒否されません — v3 以降、isWithinIntervalとareIntervalsOverlappingは端点を並べ替え、eachDayOfIntervalは日付を逆順に返します。そのため、ユーザーが順序を逆にした範囲でもエラーにならず、一見妥当な結果が返されます。区間の順序は自分で確認してください。- Moment のフォーマット文字列を貼り付けると、エラーではなく誤った値が返されます — 移行後のコードベースで最もよくある不具合です。
new Date("2026-01-15")は UTC の午前 0 時になります。日付のみの ISO 文字列は UTC と定められているためです。それ以外の文字列形式は実装依存なので、同じ入力でも実行環境によって丸一日ずれることがあります。addMonths(jan31, 1)は 2 月 28 日になり、addYears(feb29, 1)も 2 月 28 日になります — 日付は対象月の日数に合わせて切り詰められます。differenceIn*は切り捨てます。45 日の場合、differenceInMonthsは 1 であり、1.5 ではありません。v4 は v3 の切り下げとは異なり、0 に向かって切り捨てるため、負の結果が変わります。- 週の開始日は地域によって異なり、デフォルトは日曜日です — ユーザー向けに使う場合は
weekStartsOnまたはロケールを指定してください。 daysInYearなどの定数は、パッケージのルートではなくdate-fns/constantsからインポートします。
</red_flags>