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にしてから変換するとその問題は発生しないので安全。