ハバナ式継続的デリバリー

Python, Ubuntu, JavaScript 色々知らなかった!のメモ

Pythonのdatetime・utcnowとnowの違い

Pythonタイムゾーンに関する区別

UTCとかタイムゾーンの扱いで色々考えていて、この二つは何が違うのか混乱してきたので整理した。
まず、Pythonのdatetimeには「タイムゾーンを持つ・持たない」の区別がある。

そして、タイムゾーンを持つaware-datetimeの中で、UTCとそれ以外という区別があると考えると良い(以下の図)。
f:id:cultureeen:20131211160244p:plain

datetimeのutcnowとnowは何が違うのか?

Pythonのdatetimeにはutcnowとnowという二つのメソッドがある。
これは、現在日時を取得するとき、UTCを基準に日時算出するか・コンピュータに設定されているタイムゾーンから算出するかの違いを区別するため分かれている。

  • utcnow: UTCで現在日時を取得する。
  • now: コンピュータに設定されているタイムゾーンでの現在日時を取得する。

例えば、コンピュータに日本時間が設定されていれば、utcnowとnowが返す日時は9時間離れている。

>>> from datetime import datetime
>>> datetime.utcnow()
datetime.datetime(2013, 12, 11, 7, 10, 42, 37689)
>>> datetime.now()
datetime.datetime(2013, 12, 11, 16, 10, 44, 644069)
>>> datetime.utcnow() == datetime.now()
False

このように、utcnowもnowも両方naiveなdatetimeを返すのだが、時差があると日時の値は等しくない。

言い換えると、「naiveなdatetimeを見ただけではそれがUTCなのか日本時間なのか判らない」。

pytzのfromutcとlocalize

そんなときにpytzのタイムゾーンを使ってnaive-datetimeをaware-datetimeに変換する必要が生じる。

>>> import pytz
>>> from datetime import datetime
>>> UTC, JST = pytz.utc, pytz.timezone("Japan")
>>> x = UTC.fromutc(datetime(2013, 12, 11, 10, 15, 22))
>>> y = JST.localize(datetime(2013, 12, 11, 19, 15, 22))
>>> x == y
True

余談だが、Pythonのdatetimeのコンストラクタに直接tzinfoを渡すのはサマータイムを考慮する場合は避けた方が無難らしい。

Pythonのdatetimeで夏時間を扱う - nekoya press

夏時間を考慮してdatetimeオブジェクトを作る場合は、以下を基本方針とするのがいいでしょう。

コンストラクタにはtzinfoを渡さず、naiveなdatetimeオブジェクトをlocalizeする
存在しない時刻が渡る可能性がある時はnormalizeするかUTCに変換する

fromutcとlocalizeを間違えて使わないよう注意

naiveなdatetimeをawareに変換するときは、そのnaiveがUTC準拠なのかローカルタイムゾーン準拠なのかによって利用するメソッドが違う。

from datetime import datetime
 
import pytz
 
 
now1 = datetime.utcnow()
now2 = datetime.now()
 
 
utc, japan = pytz.utc, pytz.timezone("Japan")
 
template = """\
{0}
    [tz({0}).localize(utcnow)] {1}
    [tz({0}).localize(now)]    {2}
    [tz({0}).fromutc(utcnow)]  {3}
    [tz({0}).fromutc(now)]     {4}
"""
for tz in (utc, japan):
    print(template.format(tz,
                          tz.localize(now1),
                          tz.localize(now2),
                          tz.fromutc(now1),
                          tz.fromutc(now2)))

試しにこういうプログラムを実行してみると以下の結果になり、間違っているケースが存在することが判る。

UTC
    [tz(UTC).localize(utcnow)] 2013-12-10 02:56:08.186243+00:00
    [tz(UTC).localize(now)]    2013-12-10 11:56:08.186250+00:00  ←間違い
    [tz(UTC).fromutc(utcnow)]  2013-12-10 02:56:08.186243+00:00
    [tz(UTC).fromutc(now)]     2013-12-10 11:56:08.186250+00:00  ←間違い

Japan
    [tz(Japan).localize(utcnow)] 2013-12-10 02:56:08.186243+09:00  ←間違い
    [tz(Japan).localize(now)]    2013-12-10 11:56:08.186250+09:00
    [tz(Japan).fromutc(utcnow)]  2013-12-10 11:56:08.186243+09:00
    [tz(Japan).fromutc(now)]     2013-12-10 20:56:08.186250+09:00  ←間違い

間違いケース

  1. fromutcにutcnowではなくnowで取得した日時を渡す。
  2. utcのtzinfoを使う場合にlocalizeにnowで取得した日時を渡す。
  3. ローカルタイムゾーンのtzinfoを使ってlocalizeにutcnowで取得した日時を渡す。

まとめると以下の図になる。

f:id:cultureeen:20131211162906p:plain