タイムゾーンの指定とデータの用意は別の問題
PythonでZoneInfo("Asia/Tokyo")のようにタイムゾーンを指定するとき、確認したいのはキーの名前だけではない。そのキーに対応するタイムゾーンデータを、zoneinfoがどこから読み込むかも関係する。
公式ドキュメントによると、zoneinfoモジュール自体はタイムゾーンデータを直接提供しない。利用するのは、システムのタイムゾーンデータベース、またはPyPIで提供されるtzdataパッケージだ。システムのデータもtzdataも利用できなければ、ZoneInfoの呼び出しはZoneInfoNotFoundErrorを送出する。
したがって、コードにタイムゾーンのキーを書いたことと、そのキーを解決するデータが環境にあることは分けて考える必要がある。この記事では、既定の検索パスを使う場合と、PYTHONTZPATHを空文字列にする場合を比較する。問いは「どちらが正しいか」ではなく、「どのデータを使いたいときに、どの設定が対応するか」である。
公式が規定する探索順と空文字列の意味
ZoneInfo(key)は、まずTZPATHに指定されたディレクトリから、キーに一致するファイルを探す。そこで見つからなければ、tzdataパッケージから一致するデータを探す。この順序が、設定を読むうえでの出発点になる。
TZPATHの既定値はコンパイル時に設定できる。環境変数による指定がなければ、その既定値が使われる。公式では、既定の検索パスにタイムゾーンデータベースの一般的な配置先が含まれる一方、Windowsにはそのような既知の配置先がないと説明されている。
環境変数で検索パスを指定する仕組みがPYTHONTZPATHだ。モジュールのインポート時や、引数なしでreset_tzpath()を呼び出してTZPATHを初期化するときに参照される。
その値を空にする場合について、公式は次のように記している。
To set the system to ignore the system data and use the tzdata package
instead, set PYTHONTZPATH="" .
出典: https://docs.python.org/3/library/zoneinfo.html (取得日 2026-10-17)
素材にある設定表記は、そのままPYTHONTZPATH=""である。これは「既定の検索先に任せる」という意味ではない。システムのデータを無視して、tzdataパッケージを使うための指定だ。未設定と空文字列は、同じ扱いとして読めない。
未設定・空文字列・明示指定を比べる
環境変数の値を軸にすると、探索の違いは次のように整理できる。表は、設定が検索パスの初期化に反映された場合の規定をまとめたものだ。
PYTHONTZPATHの設定 |
検索パスの扱い | tzdataを探す条件 |
|---|---|---|
| 未設定 | コンパイル時に設定された既定のTZPATHを使う |
検索パスにキーと一致するファイルがない場合 |
空文字列 "" |
システムのデータを無視する | システムのデータの代わりに利用する |
| 絶対パスを明示 | 指定された検索パスを使う | 検索パスにキーと一致するファイルがない場合 |
明示指定では、複数のパスをos.pathsepで区切る。指定できるのは絶対パスだけで、相対パスの要素は使われない。CPythonでは、不正な相対パスの指定に対してInvalidTZPathWarningが出る。他の実装では、その要素を黙って無視するか、例外を送出することも認められている。
また、検索パスに複数の候補がある場合、TZPATHの各要素とキーを組み合わせて探し、最初に見つかったファイルが使われる。検索先を列挙する設定では、含める場所だけでなく、その順序も規定の一部になる。
どのデータを使うかで設定を選ぶ
既定の探索を利用したいなら、PYTHONTZPATHを設定せず、既定のTZPATHから探す動作を使う。この場合でも、検索パスに一致するファイルがなければtzdataが探索対象になる。未設定だからといって、必ずシステム側のデータだけを使うわけではない。
システムのデータを無視し、tzdataを使いたいなら、公式が示す指定はPYTHONTZPATH=""である。ただし、この設定はデータの探索方法を指定するものだ。tzdataが利用可能であることまで保証する設定として扱ってはいけない。対応するデータを見つけられなければ、ZoneInfoNotFoundErrorの対象になる。
公式は、タイムゾーンデータを必要とするクロスプラットフォームのプロジェクトについて、tzdataへの依存を宣言することを推奨している。システム側にIANAデータベースがない環境もあるためだ。依存を宣言する判断と、システムのデータを使うかどうかの判断は、別々に確認すると設定の意図が明確になる。
特定の配置先を検索したい場合は、絶対パスを明示する方法がある。ただし、その指定でも、一致するファイルがなかった場合のtzdataへの探索は残る。
落とし穴は検索パスとキャッシュの違い
検索パスを実行中に変える場合には、もう一つ規定がある。reset_tzpath()は検索パスを設定・リセットするが、ZoneInfoのキャッシュを無効化しない。新しい検索パスが使われるのは、通常のコンストラクターでキャッシュミスが起きた場合だけだ。
通常のZoneInfo(key)は、キャッシュが無効化されない限り、同じキーに対して同一のオブジェクトを返す。このため、検索パスを変更したことだけを根拠に、同じキーの次の呼び出しが新しい検索先を読むとは判断できない。
公式は、実行時の検索パス変更を一般には勧めていない。一方、特定のタイムゾーンパスを必要とするテストや、システムのタイムゾーンへのアクセスを無効にするテストでは、合理的な利用方法としている。
キャッシュを消すZoneInfo.clear_cache()にも、モジュールの状態を変更し、日時の意味に広い影響を及ぼす可能性があるという注意が付いている。検索先の確認とキャッシュの操作は、ひと続きの当然の手順として扱わず、それぞれの目的を確認したい。
自分の環境で確かめる手順
次は、読者が自分の環境で実行するための確認コマンドである。pythonでPythonを起動でき、先頭の環境変数指定を使えるシェルで実行する。最初の行は現在の環境設定、次の行はPYTHONTZPATHを空にした設定を確認する。
python -c 'import zoneinfo; print(zoneinfo.TZPATH); print(zoneinfo.ZoneInfo("Asia/Tokyo"))'
PYTHONTZPATH="" python -c 'import zoneinfo; print(zoneinfo.TZPATH); print(zoneinfo.ZoneInfo("Asia/Tokyo"))'
最初の行は、すでにPYTHONTZPATHが設定されていれば、その影響を受ける。未設定との比較を行う場合は、実行前に環境変数の状態を確認する必要がある。
確認するのは、表示される検索パスと、指定したキーを構築できるかどうかだ。成功した場合でも、キーの表示だけから、読み込まれたデータの版まで分かるとは公式に書かれていない。ここでは実行結果を掲載せず、自分の環境で得た検索パスと例外の有無を、公式の探索順に照らして確認する。

コメント