Python で Datetime を扱う際に気をつけること

※ Python 3.9 からタイムゾーンの標準ライブラリが追加されたので、3.9 移行を使う場合はこの記事の内容は適してないかもしれません

参考: Python 3.9 の新機能 - python.jp #zoneinfo モジュール


python で日本時間を扱うときに気をつけること。

準備

$ pip install pytz
from datetime import datetime, timedelta
from pytz import timezone, utc

jst = timezone('Asia/Tokyo')

aware と naive

datetime object は、タイムゾーンの有無で 2 種類にわけられる

  • タイムゾーン情報を持つものを naive と呼び
  • タイムゾーン情報を持たないものを aware と呼ぶ

コンストラクタや、datetime 型を取得するメソッドでタイムゾーン情報を指定しない場合は aware が、指定した場合は naive が返される

now_aware = datetime.now()        # datetime.datetime(2020, 5, 28, 22, 2, 42, 634478)
now_naive = datetime.now(tz=utc)  # datetime.datetime(2020, 5, 28, 13, 3, 0, 220156, tzinfo=<UTC>)

LMT 問題

本来 datetime の仕様としては、コンストラクタにタイムゾーンを渡すのだが、pytz における JST を渡すと意図した挙動にならない

datetime(year=2020, month=1, day=1, tzinfo=jst)  # datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>)

見ての通り、19 分ずれている(本来の時差は+9:00)

天文学辞典 » 地方時

によると、地方時には

  • Local Mean Time(国で統一化されてない)
  • Local Standard Time(国で統一化されている)

の 2 種類があり、どうやら、datetime コンストラクタや一部のメソッドでは LMT が採用されてしまうとのこと

てことで、めんどいけど LST(日本なら JST)として扱えるように用いるメソッド等を選ぶ必要がある

仕様チェック

用途ごとに、LMT が採用されるのか LST が採用されるのか確認していく

現在時刻の取得

datetime.now(tz=jst).tzinfo          # <DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD> OK
jst.localize(datetime.now()).tzinfo  # <DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD> OK

どちらでも OK

特定の時刻の取得

params = {'year': 2020, 'month': 1, 'day': 1}

datetime(**params, tzinfo=jst).tzinfo    # <DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>
jst.localize(datetime(**params)).tzinfo  # <DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>

コンストラクタは NG、特定の時刻の naive オブジェクトを取得するときは、タイムゾーン.localize(aware) を使う

naive => naive のタイムゾーン変換

すでにタイムゾーンを持つオブジェクトを別のタイムゾーンへ置換する

utc_time = datetime(**params, tzinfo=utc)  # datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>)

utc_time.replace(tzinfo=jst).tzinfo  # <DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>
utc_time.astimezone(jst).tzinfo      # <DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>

replace メソッドは NG、astimezone メソッドは OK.

まとめ

まとめると、LST を用いたい場合は,

用途メソッド
現在時刻の取得datetime.now(tz=jst) or jst.localize(datetime.now())
特定の時刻の取得jst.localize(datetime(**params))
タイムゾーン変換utc_time.astimezone(jst)

を使えば良い

比較について

datetime 型は、標準の比較演算子を用いて、比較ができる

aware vs aware

datetime.now() < datetime.now()              # True

当然できる.

aware vs native

datetime.now(tz=jst) < datetime.now()      # TypeError: can't compare offset-naive and offset-aware datetimes

できない.

naive vs naive (同タイムゾーン)

datetime.now(tz=jst) < datetime.now(tz=jst)  # True

当然できる.

naive vs naive (別タイムゾーン)

datetime.now(tz=jst) < datetime.now(tz=utc)  # True

タイムゾーンをまたいでも問題なく比較できる