世界を動かす技術を、日本語で。

自分で日付解析ライブラリを書くな

2025年7月26日原文(zachleat.com)

概要

  • Eleventyの 日付パース ライブラリ選定の経緯と課題
  • Luxon依存 からの脱却とバンドルサイズ削減
  • RFC 9557対応の 新ライブラリ @11ty/parse-date-strings導入
  • 他の主要な日付ライブラリとの 比較検証
  • 今後の Temporal API 対応を見据えた設計

Eleventyの日付パースライブラリ刷新の経緯

  • 2018年に Luxon をEleventyの日付パース用ライブラリとして採用
  • Luxonは Node.js専用要件 には適していたが、クライアントサイド対応やバンドルサイズが課題
  • Eleventyの依存モジュール21.3MBのうち、Luxonが 4.7MB(22%) を占有
  • 新しい@11ty/clientバンドルでもLuxonは 229kB/806kB(28%) と大きな割合
  • EleventyでのLuxon利用範囲は DateTime.fromISO によるISO 8601パースのみ

代替ライブラリの検討

  • バンドルサイズ・精度を重視し dayjs などを検証
    • dayjsは小型だが テスト228件中80件で不合格、精度に難あり
  • 他の候補(moment, date-fns)も総合的に不採用
  • Dual publishing (ESM/CJS両対応)の必要性にも疑問

パース仕様の見直しとRFC 9557対応

  • 多様な日付形式対応による 曖昧さ がトラブルの元
  • ISO 8601標準は サブフォーマットが多く複雑
    • 例:"200"は年200ではなく、2000-2010年のディケードを指す
  • 今後は 厳格なパース仕様 に変更(設定でLuxon利用も可)
  • 新しい指針として RFC 9557 (ISO 8601/RFC 3339拡張)を採用
    • Temporal API(次世代JavaScript日付API)とも互換

主要日付フォーマット対応状況一覧

  • RFC 9557, Luxon, ISO 8601, Date.parseの 各フォーマット対応状況
    • 例:YYYY-MM-DDTHH:II:SS.SSS、ミリ秒・マイクロ秒・ナノ秒精度など
    • RFC 9557が最も 幅広い形式 に対応

新ライブラリ@11ty/parse-date-stringsの導入

  • RFC 9557互換の パース専用小型ライブラリ
  • ESM形式、6.69kB(バンドル2.3kB) と超軽量
  • @11ty/clientバンドルで約 230kB削減、@11ty/eleventyのnode_modulesも 21.3MB→16.6MB に減量
  • 今後の Temporal API移行 もスムーズに

他の注目日付ライブラリ・Temporalポリフィル

  • @js-temporal/polyfill:2.98MB(186.5kB min)
  • temporal-polyfill:551kB(56.3kB min)
  • @formkit/tempo:501kB(17.3kB min)
    • それぞれ特徴と用途に応じて選択肢

まとめ

  • Eleventyでは 日付パースの精度・軽量化・将来性 を重視し、独自ライブラリへ移行
  • RFC 9557および Temporal API時代 を見据えた設計
  • 他プロジェクトでも 日付パース要件 の見直しやライブラリ選定の参考事例

Hackerたちの意見

最近、医療の現場で誕生日を保存するために日付の扱いについて悩んだんだ。最終的には、誕生日って物理的な時間じゃなくて、ただの文字列だって考えることにした。ユーザーには「02/18/1993」みたいに先頭のゼロも含めて指定されたフォーマットで入力させることができるし、文字列の等価性以外の操作は無効にするつもり。これが実際にどうなるかはわからないけど、時間の点や区間として扱うよりはずっと良くなってる。タイムゾーンを移動したときに誕生日が変わる問題もないしね。

人々の誕生日がタイムゾーンを移動すると変わるのは、開発者が単一の日付を保存するのに日時(タイムスタンプ)を使っているからだよ。適当な基準日(例えば、Excelで使われている1900年1月1日とか、僕のお気に入りの1600年1月1日。1600は400の倍数だから、うるう年の計算がさらに簡単になる)を選んで、そこから経過した日数を保存すればいい。うるう年に関するルールは、タイムゾーンやタイムゾーンデータベースに関するルールよりずっとシンプルだし。この表現から年月日への変換は、コードで約50行あればできるよ。もちろん、日付の計算をしないなら、年、月、日をそれぞれ3つの数字で保存すればいいだけ。

さらに良いのは、ユーザーにテキストフィールドの代わりにカレンダー日付ピッカーを使わせることだね。これなら入力を完全にコントロールできる。

「02/18/1993」のフォーマットは、DD/MM/YYYYなのかMM/DD/YYYYなのか?18から判断すると後者だと思うけど、その規則は普遍的じゃないよね。あまりあいまいじゃないフォーマットとしてYYYY/MM/DDをおすすめするけど、完璧な答えはないな。

あなたの場合、日付の計算や大小比較はやらずに、等価性のテストだけをしてるんだよね?つまり、複合キーの一部ってことだ。僕も似たような問題に直面したことがあって、ユーザーが日付を提出するフォームがあったんだけど、タイムゾーンについてはあまり意識してなかったみたい。だから、「02/28/1993」を選んで、みんなが常に「02/28/1993」を見る限りはそれが正しいと考えたし、もし違うのを見たらそれは間違いだと思った。だから、システム全体でタイムゾーンを意識しない日付を使ったんだ。

Temporal[0]がこれをやるのが好きだな。君が扱ってたのはTemporal.PlainDate[1]、つまりカレンダーはあるけど時間やタイムゾーンはない日付だね(暗黙のうちに含まれてるかもしれないけど、誕生日みたいに関係ないこともある)。Temporalには他にも面白いタイプがあって、それぞれ異なる意味があるんだ:- Instant: カレンダーや場所に関係なく固定された時点。例えば「ユーザーがX日付と時間にログインした」みたいな感じで、どのタイムゾーンやカレンダーシステムでも有効。これが通常「Unix UTCタイムスタンプ」で使われるもの。- ZonedDateTime: Instantに似てるけど、特定のカレンダーと場所に関連付けられてる。Instantをカレンダーシステムとタイムゾーンに「リアル」に変換した感じで、ユーザーが意味のある時間を見れるようになってる。- PlainDate: さっき話したやつ。例えば誕生日とか。- PlainTime: 「毎日6:30pmにタスクを実行する」みたいな感じ。- PlainDateTime: Instantに似てるけどカレンダーシステムに関連付けられてて、タイムゾーンはない。ユーザーが日付ピッカーに入れるようなもので、タイムゾーンは暗黙的に含まれてる。- PlainYearMonth: 例えば「2025年10月にレポートを実行する」みたいな感じ。- PlainMonthDay: 例えば「私の誕生日は6月13日」みたいな。- Duration: 例えば「タスクは3時間30分実行された」みたいな。重要な概念も見てみてね[2]。[0] https://tc39.es/proposal-temporal/docs/ [1] https://tc39.es/proposal-temporal/docs/#Temporal-PlainDate [2] https://tc39.es/proposal-temporal/docs/timezone.html

誕生日を扱う環境ってどんなとこなの?タイムゾーンを意識した日付や時間、間隔があるのに、すでに存在するナイーブ/プレーン/ローカルな日付タイプがなくて、文字列を使わざるを得ないって?その問題に対する合理的な解決策があるみたいだけど、そんな問題が最初にあるってのが意外だね。

通常の「法的」な日付はタイムスタンプやタイムゾーンに関連してないし、この事実は人間がソフトウェアを書く限り永遠に再発見されるだろうね。

医療がなぜ重要なのかを理解しているなら、これは本当に洞察に満ちてるね。私の知る限り、医療業界は基本的に誕生日をキーとして使ってる。これによって(a)他のプライマリーキー(名前や住所など)を持つ2人の患者を区別できたり、(b)患者にその値を言わせて、簡単に認知能力をチェックしたりできるんだ。

ただの文字列として保存すると、年齢に依存するロジックとか、フィールドのユーザーに解析ロジックを渡すことができなくなるよ。個人的には、FHIRは日付(生年月日を含む)に関してかなりいいシステムを持ってると思う:YYYY、YYYY-MM、またはYYYY-MM-DD。(正確な誕生日を知らないのは、国によってはよくあることだしね)。 https://build.fhir.org/datatypes.html#date

誰かがTwitterに投稿したジョークみたいだね。「宇宙探査には賛成だったけど、日付/時間ライブラリに何を意味するかを理解してからは考えが変わった。」

時計の数字が特定の数になったら寝なきゃいけない理由を考えるのって面白いよね。現代の技術なら、必要に応じて調整できるようにプログラムできるのに。火星人が今日の9:00amに寝て、明日の9:40amに起きることに技術的な理由はないし。農家がサマータイムを気にするのが滑稽だと思うのも同じで、知ってる農家は「夜明け」や「日没」の時間を基準にしてるからね。

Hacker Newsで議論の続きを見る