【Python】datetime におけるタイムゾーン変更時の注意点

記事タイトルとURLをコピーする

こんにちは。 ディベロップメントサービス1課の山本です。

今回は datetime 型にて、タイムゾーンを扱う場合の注意点を説明します。
タイムゾーンの変更方法は主に 2 種類あり、それぞれ挙動が異なるため解説します。

この記事の対象者は?

  • タイムゾーンを持つ時間を扱っている方

datetime 型 のタイムゾーンについて

datetime 型 には 時刻のタイムゾーンを指定することができます。

タイムゾーンの指定有無によって、awarenaive に分類されます。

  • aware オブジェクト:タイムゾーン有り
  • naive オブジェクト:タイムゾーン無し

datetime --- 基本的な日付と時間の型 — Python 3.12.5 ドキュメント

コード

import datetime as dt
  
tz_info_utc = zoneinfo.ZoneInfo("UTC")
  
def main():
    dt_now_naive = dt.datetime.now()
    dt_now_aware_utc = dt.datetime.now(tz_info_utc)
  
    print(f"dt_now_naive: {dt_now_naive}")
    print(f"dt_now_aware_utc: {dt_now_aware_utc}")
  
  
if __name__ == '__main__':
    main()

出力結果

dt_now_naive: 2024-08-28 17:06:35.415768
dt_now_aware_utc: 2024-08-28 08:06:35.415770+00:00

末尾のタイムゾーンによる時差の有無で確認できます。

datetime.now() にて 生成した場合は
ローカルのシステム設定を参照して時刻を取得します。(今回の場合は JST )

タイムゾーンの変更方法

タイムゾーンの変更方法は主に、以下の2つがあります。

  • datetime.replace()
    • 時刻の調整は実施せずに、タイムゾーンのみを変更する
  • datetime.astimezone()
    • 時刻の調整とタイムゾーンを変更する。

少し説明が難しいので、表にして整理してみます。
ローカルタイムゾーン が UTC システム上で変換する前提です。

変換元 replace(JST) astimezone(JST)
2024-01-01 12:00:00 2024-01-01 12:00:00+09:00 2024-01-01 21:00:00+09:00 ※
2024-01-01 12:00:00+03:00 2024-01-01 12:00:00+09:00 2024-01-01 18:00:00+09:00

※ naive オブジェクトの場合は、ローカルタイムゾーン を参照して時刻を調整する。

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

まとめ

  • datetime.replace() はタイムゾーンを変更するだけ。時刻は変えない。
  • datetime.astimezone() は変更前後のタイムゾーンに応じて、時刻も変える。

余談

ユニットテストでよく利用される freezegun にて tz_offset の指定無しで時刻を固定すると、ローカルのタイムゾーンは "UTC" となります。

freezegun · PyPI

コード

import zoneinfo
import datetime as dt
import freezegun

tz_info_utc = zoneinfo.ZoneInfo("UTC")

@freezegun.freeze_time("2021-01-01")
def main():
    dt_now_naive = dt.datetime.now()
    dt_now_aware_utc = dt.datetime.now(tz_info_utc)
    print(f"now_naive: {dt_now_naive}")
    print(f"now_aware_utc: {dt_now_aware_utc}")    

if __name__ == '__main__':
    main()

出力結果

now_naive: 2021-01-01 00:00:00
now_aware_utc: 2021-01-01 00:00:00+00:00

さいごに

テスト結果が期待値と 9 時間ずれて悩んだ末、これに気づきました。。。

本ブログがどなかたのお役に立てれば幸いです。

山本 真大(執筆記事の一覧)

アプリケーションサービス部 ディベロップメントサービス1課

2023年8月入社。カピバラさんが好き。