🧊
freezegun で timezone を設定すると時刻がずれる問題と回避策
次で報告済の内容の解説です:
正しい Python の仕様
本来は datetime.now()
で生成した時刻は、引数: tz
が違っても同じ時刻になります:
from datetime import datetime, timedelta, timezone
from freezegun import freeze_time
from dateutil.tz import tzlocal
def test_freezegun1() -> None:
timestamp_utc_fixed = int(datetime.now(tz=timezone(timedelta(hours=0))).timestamp() * 1000)
timestamp_tzlocal = int(datetime.now(tz=tzlocal()).timestamp() * 1000)
timestamp_local_timezone = int(datetime.now(tz=datetime.now(timezone.utc).astimezone().tzinfo).timestamp() * 1000)
timestamp_no_tz = int(datetime.now().timestamp() * 1000)
assert timestamp_utc_fixed == timestamp_no_tz
assert timestamp_tzlocal == timestamp_no_tz
assert timestamp_local_timezone == timestamp_no_tz
参考: datetime.datetime.timestamp()
の仕様:
datetime --- 基本的な日付と時間の型 — Python 3.12.4 ドキュメント
freezegun の問題の詳細
freeze_time()
の引数 tz_offset
を与えると、
datetime.now()
に引数 tz
を与えて生成した日付は、
datetime.now()
に引数 tz
を与えずに生成した日付に比べ、
tz_offset
時間のズレが発生します:
from datetime import datetime, timedelta, timezone
from freezegun import freeze_time
from dateutil.tz import tzlocal
DIFFERENCE_TIMESTAMP_JST = 9 * 60 * 60 * 1000
@freeze_time("2022-08-09 11:26:00.000", tz_offset=-9)
def test_freezegun2() -> None:
timestamp_utc_fixed = int(datetime.now(tz=timezone(timedelta(hours=0))).timestamp() * 1000)
timestamp_tzlocal = int(datetime.now(tz=tzlocal()).timestamp() * 1000)
timestamp_local_timezone = int(datetime.now(tz=datetime.now(timezone.utc).astimezone().tzinfo).timestamp() * 1000)
timestamp_no_tz = int(datetime.now().timestamp() * 1000)
assert timestamp_utc_fixed + DIFFERENCE_TIMESTAMP_JST == timestamp_no_tz # Wrong!
assert timestamp_tzlocal + DIFFERENCE_TIMESTAMP_JST == timestamp_no_tz # Wrong!
assert timestamp_local_timezone + DIFFERENCE_TIMESTAMP_JST == timestamp_no_tz # Wrong!
freeze_time() の引数 tz_offset を与えない場合は何も起きません:
from datetime import datetime, timedelta, timezone
from freezegun import freeze_time
from dateutil.tz import tzlocal
@freeze_time("2022-08-09 11:26:00.000")
def test_freezegun3() -> None:
timestamp_utc_fixed = int(datetime.now(tz=timezone(timedelta(hours=0))).timestamp() * 1000)
timestamp_tzlocal = int(datetime.now(tz=tzlocal()).timestamp() * 1000)
timestamp_local_timezone = int(datetime.now(tz=datetime.now(timezone.utc).astimezone().tzinfo).timestamp() * 1000)
timestamp_no_tz = int(datetime.now().timestamp() * 1000)
assert timestamp_utc_fixed == timestamp_no_tz
assert timestamp_tzlocal == timestamp_no_tz
assert timestamp_local_timezone == timestamp_no_tz
これらのテストは、どのローカルタイムゾーンでも成功するようです
回避策
上記のコードのように、テストコード上で時間のズレを定数定義します
(上記のコードでは DIFFERENCE_TIMESTAMP_JST
)
DIFFERENCE_TIMESTAMP_JST = 9 * 60 * 60 * 1000
そして assert
などで期待値と実測値の比較をするときに加算または減算します:
@freeze_time("2022-08-09 11:26:00.000", tz_offset=-9)
def test_freezegun4() -> None:
timestamp_utc_fixed = int(datetime.now(tz=timezone(timedelta(hours=0))).timestamp() * 1000)
timestamp_no_tz = int(datetime.now().timestamp() * 1000)
assert timestamp_utc_fixed + DIFFERENCE_TIMESTAMP_JST == timestamp_no_tz
動作確認バージョン
Python: 3.12.4
freezegun: 1.5.1
Discussion