プログラミングでの UTC 変換 - タイムゾーン処理の正しい実装パターン
ソフトウェア開発における UTC 変換の正しい実装方法を解説。保存は UTC、表示はローカルの原則から、JavaScript/Python での具体的な実装パターン、よくあるバグの原因まで網羅します。
JavaScript の組み込み Date オブジェクトは 1995 年の言語誕生時から大きな変更がなく、日時操作 API として多くの欠点を抱えています。月が 0 始まり、ローカルタイム前提の挙動、ミュータブルでバグを生みやすい設計、タイムゾーン操作のサポート不足など、本格的なアプリケーションでは Date のままでは保守性に問題が出ます。多くのプロジェクトが第三者ライブラリに頼ってきた理由はここにあります。
ライブラリ選定では、バンドルサイズ・API 設計・タイムゾーン対応・国際化サポート・更新の活発さの 5 軸を評価する必要があります。フロントエンドではバンドルサイズが直接ユーザー体験に響き、サーバーサイドでは正確性と可読性が重視されます。同じプロジェクトでもクライアントとサーバーで別ライブラリを使う構成が珍しくありません。
Moment.js は 2011 年の登場以来、JavaScript の日時操作のデファクトでした。豊富な API、わかりやすいメソッドチェーン、強力な国際化機能で世界中の開発者に支持されました。しかし 2020 年に開発元自身が「レガシープロジェクト」と公式に宣言し、新規プロジェクトでの採用を非推奨としました。理由はミュータブルな設計、ツリーシェイキングが効かない単一ファイル構造、バンドルサイズが約 290KB と肥大していることです。
既存の Moment.js コードを抱えるプロジェクトでも、新規追加機能では別ライブラリを選ぶ判断が一般的です。ただし全面的な置き換えは API の差異が大きくコストが高いため、段階的な移行戦略 (機能単位で新規ライブラリに切り替えていく) が現実的です。Moment.js は今もセキュリティパッチは提供されていますが、新機能の追加は止まっています。
Day.js は 2018 年に登場した Moment.js の事実上の後継です。API を意図的に Moment.js と互換に設計しており、コアのバンドルサイズは約 7KB と Moment.js の 1/40 程度に圧縮されています。タイムゾーン処理や relativeTime などの追加機能はプラグイン方式で取り込む設計のため、必要な機能だけを足し込めばバンドルサイズを最小化できます。
Day.js のメリットは Moment.js からの移行コストの低さです。dayjs("2026-05-18").add(1, "day").format("YYYY-MM-DD") のような典型的な操作はほぼコード変更なしで動きます。一方で、Moment.js の全機能を完全網羅しているわけではないため、特定の高度な API に依存している場合は事前に互換性を確認する必要があります。タイムゾーン対応は dayjs/plugin/timezone を読み込む必要があり、内部で Intl.DateTimeFormat を使う実装のため、Node.js の小さな ICU バンドルでは完全に動かないケースもあります。
date-fns は 2014 年から開発されているライブラリで、不変データと純粋関数を組み合わせた関数型の API が特徴です。ライブラリ独自のクラスを作らず、ネイティブの Date を入出力に取るため、他のコードとの相互運用性が高いのが利点です。ツリーシェイキングが完全に効くため、format しか使わなければバンドルサイズが数 KB に収まります。
API スタイルは Moment.js と大きく異なり、format(date, "yyyy-MM-dd") のように date を引数として渡す形式です。チェーン形式に慣れた開発者には最初違和感があるものの、TypeScript との相性が極めて良く、引数の型推論が完全に効きます。タイムゾーン操作は date-fns-tz という別パッケージで提供され、IANA タイムゾーン名で aware な変換が可能です。新規プロジェクトでは date-fns を選ぶケースが増えています。
Luxon は Moment.js のメンテナーが新規開発したライブラリで、不変オブジェクトと豊富なタイムゾーン機能を備えています。DateTime.fromISO("2026-05-18T09:00", { zone: "Asia/Tokyo" }) のように、生成時点で IANA タイムゾーン名を指定できます。サマータイム境界やフォルトトレラントな日時パースなど、現場で必要な機能が標準で揃っているのが強みです。
ただし Luxon は Intl.DateTimeFormat と Intl.NumberFormat に深く依存しているため、ブラウザや Node.js が完全な ICU を持っていることを前提とします。古いブラウザ向けにポリフィルを足す必要があり、最新のモバイルブラウザだけを対象とするプロジェクトに向いています。バンドルサイズは date-fns より大きいですが、タイムゾーン処理が中心のアプリケーションでは API の使いやすさが勝ります。
Temporal は ECMAScript の Stage 3 提案として進行中の新しい日時 API です。Date の問題を根本から解決するために、PlainDate、PlainTime、PlainDateTime、ZonedDateTime、Instant など用途別の不変クラスを提供します。タイムゾーン aware な計算、暦法の切り替え (グレゴリオ暦・和暦・ヘブライ暦など)、サマータイム境界の安全な処理が言語標準として実装されます。
2026 年 5 月時点では一部のブラウザで実装が始まったばかりで、本番投入には @js-temporal/polyfill の併用が必要です。すべてのブラウザがネイティブ実装を備えれば、第三者ライブラリへの依存が原理的に不要になり、サーバーとクライアントで完全に同じ API が使える理想的な状態が訪れます。長期的には Day.js や Luxon すら不要になる可能性があり、新規プロジェクトでは Temporal を中核に据えてポリフィルで補う設計を検討する価値があります。
日時操作が単純な書式整形と簡単な算術だけなら、追加ライブラリを使わず標準 Date と Intl.DateTimeFormat で十分です。バンドルサイズが厳しいフロントエンドでは date-fns または Day.js の最小構成が選ばれます。タイムゾーン操作が頻繁なバックエンドや、複数の暦法を扱う必要がある国際的なアプリケーションでは Luxon が有力です。新規プロジェクトを始められる立場であれば、Temporal + ポリフィルで未来を見据えるのも合理的な選択です。
判断を誤りやすいのは「Moment.js を使い続ける」という選択です。動いているから変えないという理由で放置すると、依存パッケージの脆弱性報告に対応する手間が増え続けます。少なくとも新規機能では別ライブラリを採用し、既存コードは段階的に置き換える計画を立てるのが現実解です。日時関連のコードは比較的局所化しやすいため、3 ヶ月単位の移行計画でも十分回せるケースが多いでしょう。
この記事は役に立ちましたか?
ソフトウェア開発における UTC 変換の正しい実装方法を解説。保存は UTC、表示はローカルの原則から、JavaScript/Python での具体的な実装パターン、よくあるバグの原因まで網羅します。
データベースにおける日時データの設計判断を体系的に解説。DATE/TIME/TIMESTAMP の使い分け、タイムゾーン付きカラムの挙動、予約システムやイベント管理での実践的な設計パターンを紹介します。
主要プログラミング言語の日時処理 API を比較。JavaScript の Temporal、Python の datetime/zoneinfo、Java の java.time の設計思想、機能差、移行パスを実務的な観点から解説します。