コンテンツへスキップ
DedicatedPHP 接触

PHPでロケール・通貨・タイムゾーンを正しく扱う

PHPでは言語、地域別書式、通貨、タイムゾーンを分離し、データを一貫して保存しながら、ユーザーごとに適切な形式で表示しましょう。

言語、地域別書式、通貨、タイムゾーンを分離するPHPアプリケーションの図

言語を変更すると金額が変わる、日付が誤った日に表示される、翻訳によってビジネスルールが変わる。こうした問題の多くは、アプリケーションがデータと表示上の判断を混同していることを示しています。PHPで日付や通貨を国際化するには、言語、地域設定、通貨、タイムゾーンを個別に扱い、システムがどの値を保持し、各ユーザーにどの表現を見せるかを定める必要があります。

これらを分離すれば、ビジネスルールを重複させたり、過去のデータを別の意味に解釈したりすることなく、対応地域を増やせます。また、問題の発生源の特定にも役立ちます。原因はユーザー入力、データの永続化、時刻変換、あるいは出力形式だけにあるかもしれません。

言語、locale、通貨、タイムゾーンは別々の設定

言語、locale、通貨、タイムゾーンは別々の設定 — guía visual de DedicatedPHP

言語は、ラベル、入力検証メッセージ、商品名など、画面のテキストや翻訳対象のコンテンツを決めます。locale(地域設定)は、小数点の区切り、日付の順序、月名など、表示上の慣習を定めるために使われます。localeは必ずしも言語と同じではありません。同じ言語を使う地域でも、書式が異なることがあります。

通貨は、EURやJPYのように金額を表す単位を示します。言語やlocaleから安全に推定することはできません。ユーザーがスペイン語の画面を使い、特定の地域書式を選択しながら、別の通貨で購入することもあります。一方、タイムゾーンは、現地時刻と協定世界時、さらに地域の時刻変更との関係を定めます。

こうした設定は明示的にモデル化しましょう。たとえば、アカウントには言語とタイムゾーンを設定し、セッションでは表示形式の希望を指定できます。また、注文には実際に使用した通貨を保持する必要があります。利用可能な値と既定値はプロダクト上の判断として定め、ネットワーク上の位置から暗黙に推定してはいけません。

保存するデータと表示時に計算する値

書式設定済みの文字列だけでなく、元の事実を再現するために必要なデータを保存しましょう。作成日時はある時点を表しますが、「03/04/2025」のような形式は曖昧で、標準的な表現として適切ではありません。世界共通のイベントでは、時点をUTCで保存し、特定の都市で合意した予約のように文脈上必要な場合は、関連するタイムゾーンも保持する方法が一般的です。

PHPアプリケーションでは、DateTimeImmutableを使うと、変換中の意図しない変更を防ぎやすくなります。保存済みの値を置き換えず、レスポンスを準備する際に選択されたタイムゾーンへ時点を変換します。+01:00のような固定オフセットではなく、Europe/Madridのように認識済みのタイムゾーン識別子を使いましょう。固定オフセットには、過去の規則や季節ごとの時刻変更が含まれません。

地域書式については、使用するライブラリと実行環境で有効なlocale識別子を保持し、対応していない設定を明示的に処理します。IntlDateFormatterやNumberFormatterなどのintl関数を使えば、区切り記号や月名を手作業で連結せずに地域の慣習を適用できます。アプリケーションの実行環境で拡張機能が利用可能であることも確認してください。

日付と時刻:変換と曖昧なケース

システムが記録する時点と、地域の時刻は同じものではありません。「2025-04-10T15:00:00Z」のような時点は、曖昧さのない一点を表します。一方、「Europe/Madridで2025-10-26の02:30」は、冬時間への切り替え時に曖昧になることがあります。この現地時刻が2回発生するためです。春の切り替え時には、存在しない現地時刻が生じることもあります。

将来の現地時刻にアクションを予約する場合、地域の時計に合わせる約束であれば、一度計算したtimestampだけを保存してはいけません。指定された日付と時刻、タイムゾーン、存在しない時刻や重複する時刻を解決する方針を保持します。拒否する、確認を求める、いずれかの時刻を選ぶといった判断は、プロダクトによって異なります。一方、すでに発生したイベントでは、実際の時点を記録します。

フォームやAPIから受け取る日付の解釈方法も定義しましょう。仕様化した形式を受け入れ、タイムゾーンを検証し、「04/05/2025」が4月なのか5月なのかを推測せず、曖昧な入力を拒否します。画面には読みやすい形式を表示しつつ、並べ替え、比較、監査のために標準データを保持してください。

金額:精度、通貨、書式

表示形式を、金額の正規の値として扱ってはいけません。金額計算に2進浮動小数点数を使うのは避けましょう。小数の一部は正確に表現できず、丸め誤差が生じることがあります。一般的な方法として、金額を通貨コードとともに、最小通貨単位の整数で保存します。ただし、すべての通貨が小数点以下2桁を使うわけではなく、最終的な請求額より高い中間精度が必要な計算もあります。

精度と丸めのタイミングは、明細行ごと、税額ごと、合計額に対してなど、ドメインのルールに基づいて定めます。注文、支払い、返金、計算では通貨を明示し、どの通貨に属するか分からない数値を解釈してはいけません。通貨を換算する場合、業務上必要であれば、適用した為替レートや基準時点など、換算の説明に必要なデータも保持します。

金額を表示するときは、数値と通貨コードを適切な地域書式のフォーマッターに渡します。記号、配置、区切りは書式によって異なります。曖昧な記号だけに頼らず、ユーザーが通貨を判別できるようにしてください。ローカライズされた文字列を普遍的な数値として解析してはいけません。入力を検証し、処理前に管理された数値表現へ変換します。

ビジネスルールを重複させずにコンテンツを翻訳する

テキストを翻訳し、書式を調整する一方で、価格、税、適格性、権限、状態を決めるルールは一元管理します。言語ごとにロジックを重複させると、検出しにくい動作の差が生まれます。料金の選択は市場、契約、商業方針に左右されることがありますが、画面上のラベルが変わっただけで変更されてはいけません。

翻訳カタログとドメインロジックを分離します。編集可能なローカライズ済みコンテンツでは、存在し得る各バージョン、未設定の場合の解決方法、フォールバックの動作を明確にします。翻訳された名前で安定した識別子を置き換えてはいけません。APIでは、各フィールドが標準値、ローカライズされたコンテンツ、表示用に整形済みの値のどれを含むかを文書化しましょう。

地域対応のテストと段階的な展開

各レイヤーで判断をテストします。ユニットテストでは、タイムゾーン間の変換、日付の検証、丸め、金額の書式を確認します。日付の境界、季節による時刻変更、重複または存在しない時刻、月ごとの日数の違い、精度が異なる通貨も含めてください。テストマシンの既定localeやタイムゾーンに依存せず、明示的に設定します。

結合テストでは、APIが標準データを永続化し、合意された設定に従って画面に表示することを確認します。不正な入力、未知のlocale、設定変更、必要な拡張機能がない場合もテストしてください。タイムゾーンデータベースを更新した場合は、将来のイベントを予約する処理と期待される結果を見直します。

地域対応は段階的に導入します。まずデータとルールを定め、次に書式と一連のフローをテストし、最後に対象ユーザーへ機能を有効化します。検証エラー、丸め差異、予定外の時刻に実行されたタスクを監視すれば、スクリーンショットだけでは分からない問題の検出に役立ちます。

チェックリスト

チェックリスト — guía visual de DedicatedPHP
  • 言語:localeと分離され、フォールバック方針が定められているか。
  • 日付:時点と、予約された現地時刻を区別しているか。
  • タイムゾーン:地域識別子を保存し、曖昧な時刻を解決しているか。
  • 金額:各金額に通貨が明示され、精度のルールが定められているか。
  • 表示:適切なツールで書式設定し、表示文字列を計算に使っていないか。
  • テスト:日付の境界、時刻変更、不正な入力、異なる設定を検証しているか。
  • 運用:PHPの設定を確認し、地域対応を管理された方法で展開しているか。

重要なのは、システムが理解するデータと、各ユーザーに見せる形式の境界を明確に保つことです。この境界を設ければ、PHPでの日付と通貨の国際化は、アプリケーション内に散在する例外の集合ではなく、検証可能なルールの集まりになります。

これらのアイデアをあなたのプロジェクトに活用してみませんか?あなたのPHPプラットフォームについて話し合いましょう。
関連サービスを見る