datetime の UTC / JSTの変換についてまとめ

datetime.datetimeのオブジェクトをUTCやJSTに変換するさいのパターンを網羅してみます。

datetime型には、timezone情報がついているかどうかの区別がある。timezone情報があるもの=aware。ないもの=native。

タイムゾーンの準備

timezoneを扱う場合はdateutilを使用するのが今日では一般的のよう。

  • メジャーな日付操作ライブラリとしてはdateutilかpytzがある
  • 標準ライブラリのtimezoneにtimedeltaで生成するのも+-9時間のようなシンプルなものなら便利
  • pytzは厳密すぎて+09:19のようなハマりがあるので避けたほうが良い
  • Python3.9以降限定ならば標準ライブラリのzoneinfoを使うのも良い

ひとまずここでは、Python3.9以前のことも考慮してdateutilを使用します。

pip install python-dateutil
from datetime import datetime
from dateutil import tz
JST = tz.gettz('Asia/Tokyo')
UTC = tz.gettz("UTC")

各種パターンのdatetimeを生成する

2021/04/01 11:22:33(UTC)を例に生成するパターン

タイムゾーンあり(aware) タイムゾーンなし (native)
UTC A) 2021/04/01 11:22:33 UTC B) 2021/04/01 11:22:33
JST C) 2021/04/01 20:22:33 JST D) 2021/04/01 20:22:33

A) awareでUTCのdatetime

UTCのタイムゾーンをもつawareなdatetimeを作成するには、tzinfoにタイムゾーンを渡す。

>>> A = datetime(2021, 4, 1, 11, 22, 33, tzinfo=UTC)
>>> A.isoformat()
'2021-04-01T11:22:33+00:00'

※(pytzを使う場合)ちなみにtzinfoを渡す方法をpytzで行うと、問題が発生するのでpytzのときにはこの方法は使わず、localizeメソッドを使うこと。

>>> datetime(2021, 4, 1, 11, 22, 33, tzinfo=pytz.timezone("Asia/Tokyo")) # これは間違い
datetime.datetime(2021, 4, 1, 11, 22, 33, tzinfo=<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>)
                                                                              ^^
>>> pytz.timezone("Asia/Tokyo").localize(datetime(2021, 4, 1, 11, 22, 33))
datetime.datetime(2021, 4, 1, 11, 22, 33, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)

B) nativeなdatetime (UTC)

普通にdatetimeのインスタンスをタイムゾーン情報なしに作成する。
タイムゾーンは持たないので、それがUTCの時刻かどうかは明示できない。

>>> B = datetime(2021, 4, 1, 11, 22, 33)
>>> B.isoformat()
'2021-04-01T11:22:33'

C) awareでJSTのdatetime

Aと同様。

>>> C = datetime(2021, 4, 1, 20, 22, 33, tzinfo=JST)
>>> C.isoformat()
'2021-04-01T11:22:33+09:00'

D) nativeなdatetime (日本時間)

設定する時刻が違うだけで、B)と同様。

>>> D = datetime(2021, 4, 1, 20, 22, 33)
>>> D
datetime.datetime(2021, 4, 1, 20, 22, 33)

タイムゾーンの変更 A→C / C→A

UTCタイムゾーンのAをJSTタイムゾーンに変換するときはと astimezoneメソッドを使用。
これはCに一致する。UTCにおける11:22:33から+9時間されて20:22:33になる。逆も同様。

>>> A.astimezone(JST)
datetime.datetime(2021, 4, 1, 20, 22, 33, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
>>> A.astimezone(JST) == C
True
>>> C.astimezone(UTC)
datetime.datetime(2021, 4, 1, 11, 22, 33, tzinfo=<UTC>)
>>> C.astimezone(UTC) == A
True

タイムゾーンの除去 A→B / C→D

awareなdatetimeからnativeなdatetimeにするのは、あまりないかもしれないが、replaceでtzinfoをNoneに設定すればできる。

ただしこれは本当にタイムゾーンを除去するだけで、時刻が変換されるわけではないので注意。

あるタイムゾーンからあるタイムゾーンへ置き換えるときはreplaceで置き換えてはいけない。前述のastimezoneを使うこと。

>>> A.replace(tzinfo=None)
datetime.datetime(2021, 4, 1, 11, 22, 33)

nativeなdatetimeでタイムゾーン変更の時間シフト B→D / D→B

nativeなdatetimeではタイムゾーンは持たないが、それを別のタイムゾーンのもの時間へシフトしたい場合。
直接変更するのではなく、replaceでaware化してからastimezoneで変換、再びreplaceでnative化する。

# UTCとみなしてあとに、JSTへ変換し、再びnative化
>>> B.replace(tzinfo=UTC).astimezone(JST).replace(tzinfo=None)
datetime.datetime(2021, 4, 1, 20, 22, 33)

2つのタイムゾーンのoffsetの差をtimedeltaで加算することでも可能だが、その場合はそのタイムゾーンのオフセットが日時によって変わることを考慮する必要になる。たとえば夏時間など。上記の方法で awareなdatetimeにしてから変換するとその問題は発生しないので安全。

関連記事:

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)