Python 3.9からの新定番zoneinfoを使いこなそう(stapy version)

Python 3.9からの新定番zoneinfoを使いこなそう(stapy version)

みんなのPython勉強会#80 資料

2022/04/14 Ryuji Tsutsui

じめに

お前誰よ

  • Ryuji Tsutsui @ryu22e

  • 普段は六本木のフィンテック系企業でDjangoを使ってWebサービスを作っています

  • 関わっているコミュニティ: Python Boot Camp、Shonan.py、Python Charity Talks in Japanなど

  • PyCon APAC 2013、PyCon JP 2014はスタッフでした

今日話すこと

  1. zoneinfoの概要の説明と基本的な使い方

  2. zoneinfoのよくあるエラー

  3. zoneinfoの周辺知識

本の宣伝

技術評論社刊『Python実践レシピ』発売中!

技術評論社刊『Python実践レシピ』発売中! 定価2,970円『Pythonライブラリ厳選レシピ』を大幅に加筆、改訂!!!

1. zoneinfoの概要の説明と基本的な使い方

zoneinfoとは何か

IANAタイムゾーンデータベースを扱う機能を提供するPythonの標準モジュールです。

IANAタイムゾーンデータベースとは

  • Internet Assigned Numbers Authority(IANA)が管理している、世界各地のタイムゾーン情報を収めたデータベースです。

  • このデータベースは、ほとんどのOSに内蔵されています。

zoneinfo誕生の経緯

2019年5月15日のPaul Ganssleさんによる提案が始まりでした。 https://pyfound.blogspot.com/2019/05/paul-ganssle-time-zones-in-standard.html

Paul Ganssleさんの提案の要約

  • 「Pythonは"batteries included"な言語のはずなのに、タイムゾーンに関する機能が足りない」

  • 「IANAタイムゾーンデータベースはタイムゾーンデータの事実上の標準で、多くのOSに内蔵されている。これを参照する標準モジュールを追加してはどうか。」

PEP 615の作成

かくして、zoneinfoに関する PEP(Python Enhancement Proposals) PEP 615(https://www.python.org/dev/peps/pep-0615/)が作成されました。

「タイムゾーンを扱う」と言えば

zoneinfo登場以前には、pytzというサードパーティライブラリが定番でした。

zoneinfoとpytzの主な違い(1)

  • zoneinfo: OSに内蔵されているタイムゾーンデータベースを参照します。

  • pytz: ライブラリ自体にタイムゾーンデータベースが内蔵されているため、タイムゾーンデータベースが更新されるとライブラリのアップデートが必要です。

zoneinfoとpytzの主な違い(2)

  • zoneinfo: Python 3.6からdatetimeモジュールに追加された「fold属性」に対応しています。

  • pytz: 「fold属性」に対応していません。

基本的な使い方

  • ZoneInfoクラスの使い

  • ZoneInfoクラスをdatetimeオブジェクトと一緒に使う方法(fold属性についての説明もします)

ZoneInfoクラスの使い

>>> from zoneinfo import ZoneInfo
>>> ASIA_TOKYO = ZoneInfo('Asia/Tokyo')
>>> ASIA_TOKYO
zoneinfo.ZoneInfo(key='Asia/Tokyo')

ZoneInfoクラスの使い

>>> from zoneinfo import ZoneInfo
>>> # オブジェクトはタイムゾーン名ごとにキャッシュされる
>>> ZoneInfo('Asia/Tokyo') is ZoneInfo('Asia/Tokyo')
True
>>> ZoneInfo('America/Los_Angeles') is \
... ZoneInfo('America/Los_Angeles')
True
>>> ZoneInfo('America/Los_Angeles') is \
... ZoneInfo('Asia/Tokyo')
False

ZoneInfoクラスの使い

>>> import zoneinfo
>>> # 指定できる値はzoneinfo.available_timezones()で調べられる
>>> zoneinfo.available_timezones()  # set型の値が返ってくる
{'Asia/Seoul', (省略), 'Asia/Tokyo', (省略), 'Asia/Pyongyang'}

ZoneInfoクラスの使い

>>> from zoneinfo import ZoneInfo
>>> ZoneInfo('spam')
Traceback (most recent call last):
(省略)
zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key spam'

datetimeオブジェクトと一緒に使う場合

>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime
>>> ASIA_TOKYO = ZoneInfo('Asia/Tokyo')
>>> # 日本時刻で2021年2月23日10:00のdatetimeオブジェクトを作成
>>> dt = datetime(2021, 2, 23, 10, tzinfo=ASIA_TOKYO)
>>> dt
datetime.datetime(2021, 2, 23, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))

datetimeオブジェクトと一緒に使う場合

>>> # (前のスライドのコードの続き)
>>> # 日本時刻2021年2月23日10:00をアメリカのロサンゼルスの日時に変換
>>> dt.astimezone(ZoneInfo('America/Los_Angeles'))
datetime.datetime(2021, 2, 22, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles'))

datetimeオブジェクトと一緒に使う場合

続いて、ZoneInfoクラスとdatetimeオブジェクトのfold属性を一緒に使う方法について説明します。

…が、まず最初にfold属性が何なのかを説明します。

公式ドキュメントの説明を引用

公式ドキュメント「datetime.fold」の説明を引用します。

公式ドキュメントの説明を引用

https://docs.python.org/ja/3/library/datetime.html#datetime.datetime.fold

[0, 1] のどちらかです。 繰り返し期間中の実時間の曖昧さ除去に使われます。 (繰り返し期間は、夏時間の終わりに時計が巻き戻るときや、現在のゾーンの UTC オフセットが政治的な理由で減少するときに発生します。) 0 (1) という値は、同じ実時間で表現される 2 つの時刻のうちの早い方 (遅い方) を表します。

公式ドキュメントの説明を引用

https://docs.python.org/ja/3/library/datetime.html#datetime.datetime.fold

[0, 1] のどちらかです。 繰り返し 期間中の実時間の曖昧さ除去に使われます。 (繰り返し期間は、夏時間の終わりに時計が巻き戻るとき や、現在のゾーンの UTC オフセットが政治的な理由で減少するとき発生します。) 0 (1) という値は、同じ実時間で表現される 2 つの時刻のうちの早い方 (遅い方) を表します。

2020年 アメリカ ロサンゼルスを例にした「夏時間の終わりに時計が巻き戻るとき」の説明

2020年 アメリカ ロサンゼルスを例にした「夏時間の終わりに時計が巻き戻るとき」の説明

2020年 アメリカ ロサンゼルスを例にした「夏時間の終わりに時計が巻き戻るとき」の説明

2020年 アメリカ ロサンゼルスを例にした「夏時間の終わりに時計が巻き戻るとき」の説明

問題】これは標準時間? それとも夏時間?

>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime
>>> # アメリカ ロサンゼルスの2020-11-01 1:00
>>> dt = datetime(2020, 11, 1, 1, tzinfo=ZoneInfo('America/Los_Angeles'))
>>> print(dt)  # 標準時間(UTC+8:00)? それとも夏時間(UTC+7:00)?

fold属性はこの問題を解決してくれます。

「夏時間の終わりに時計が巻き戻るとき」とは

  • 夏時間の終わりになると、時計が1時間巻き戻ります。

  • すると、1日に同じ時刻が2度現れることになります。

「夏時間の終わりに時計が巻き戻るとき」とは

  • datetimeオブジェクトのfold属性は、2度現れる時刻を区別するために使われます。

  • 0なら2つの時刻のうちの早い方(つまり夏時間)、1なら遅い方(つまり標準時間)を表します。

先ほどの問題の答え

>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime
>>> dt = datetime(2020, 11, 1, 1, tzinfo=ZoneInfo('America/Los_Angeles'))
>>> print(dt)    # デフォルトはfold=0、つまり夏時間(UTC+7:00)
2020-11-01 01:00:00-07:00
>>> print(dt.replace(fold=1))    # fold=1の時は標準時間(UTC+8:00)
2020-11-01 01:00:00-08:00

pytzはdatetimeのfold属性に対応していない

>>> from pytz import timezone
>>> from datetime import datetime
>>> LOS_ANGELES = timezone('America/Los_Angeles')
>>> dt = LOS_ANGELES.localize(datetime(2020, 11, 1, 1))
>> # fold=0の時は遷移前のオフセット
>>> print(dt)
2020-11-01 01:00:00-08:00
>> # fold=0の時と結果が変わっていない
>>> print(dt.replace(fold=1))
2020-11-01 01:00:00-08:00

ゾーンの UTC オフセットが政治的な理由で減少するとき」とは

実は、夏時間以外の理由でタイムゾーンが変更されることがあります。

「現在のゾーンの UTC オフセットが政治的な理由で減少するとき」とは

例えば、2011年の年末にはサモア共和国でタイムゾーンが変更されました(夏時間が理由ではなく政治的な理由で)。 この変更で前述の「時計が巻き戻るとき」が発生する場合、1日に2度同じ時刻が現れます。

タイムゾーンについてもっと詳しく知りたいなら

Dai MIKURUBEさんの「タイムゾーン呪いの書 (知識編)」がお勧めです。

https://zenn.dev/dmikurube/articles/curse-of-timezones-common-ja

おまけ】米上院、夏時間恒久化法案を可決

今年の3月15日、米上院で2023年から夏時間を恒久化する法案が可決したそうです。

https://www.newsweekjapan.jp/headlines/world/2022/03/377243.php

アメリカも夏時間が嫌な人が多いんですね…… 。

2. zoneinfoのよくあるエラー

Windowsでzoneinfoを使う場合

>>> from zoneinfo import ZoneInfo
>>> ASIA_TOKYO = ZoneInfo('Asia/Tokyo')  # Asia/Tokyoは存在するはずなのに…
Traceback (most recent call last):
 (省略)
ModuleNotFoundError: No module named 'tzdata'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
 File "", line 1, in 
 File "C:\Users\****\AppData\Local\Programs\Python\Python39\lib\zoneinfo\_common.py", line 24, in load_tzdata
   raise ZoneInfoNotFoundError(f"No time zone found with key {key}")
zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key Asia/Tokyo'

Windowsでzoneinfoを使う場合

tzdataというCPythonのコア開発者がメンテナンスしているファーストパーティパッケージをインストールします。

$ pip install tzdata

なぜこんなことが起こるのか

  • zoneinfoでIANAデータベースを参照するにはTZifファイルが必要です。

  • ころが、WindowsではTZifファイルを提供していません。その代わり、IANAタイムゾーンデータベースを扱う独自のAPIがあります。

なぜこんなことが起こるのか

3. zoneinfoの周辺知識

Python 3.8以下でfold属性を扱う場合

>>> from dateutil.tz import gettz
>>> from datetime import datetime
>>> dt = datetime(2020, 11, 1, 1, tzinfo=gettz('America/Los_Angeles'))
>>> print(dt)    # fold=0の時は遷移前のオフセット
2020-11-01 01:00:00-07:00
>>> print(dt.replace(fold=1))    # fold=1の時は遷移後のオフセット
2020-11-01 01:00:00-08:00

Djangoのzoneinfo対応について

  • DjangoはUSE_TZ = Trueのときにタイムゾーンが有効になります。

  • 元々はpytzのみに依存していました。

Djangoのzoneinfo対応について

  • Django 3.2からは従来のpytzに加えて、pytz以外のタイムゾーン実装(zoneinfoを含む)もサポートするようになりました。

  • Django 4.0(2022/04/14時点での最新バージョン)ではzoneinfoがデフォルト、pytzは非推奨になります。

Djangoでzoneinfoを使うには

>>> from zoneinfo import ZoneInfo
>>> from django.utils import timezone
>>> # timezoneという引数がある関数にZoneInfoオブジェクトを渡します。
>>> timezone.localtime(timezone=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2021, 9, 7, 22, 12, 43, 952260, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))

まとめ

  • zoneinfoはOS内蔵のIANAタイムゾーンデータベースを参照してタイムゾーンを扱う標準モジュール

  • zoneinfoはdatetimeのfold属性にも対応している

まとめ

  • fold属性とは、タイムゾーン変更時の「時計が巻き戻るとき」に起こる、1日に2度同じ時刻が現れる現象に対応した機能

  • Windowsで使うときはtzdataも必要

清聴ありがとうございました

質問あればどうぞ!