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

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

datetimeを別のタイムゾーンに変更する

UTCの文字列をdatetimeに変換し、JSTに変える

例えば「2013-12-31T23:55:58Z」という、ISO 8061拡張表記の文字列をJSTに変換してみる。

やりたいことを図示すると、こうなる。

f:id:cultureeen:20131211191324p:plain

※時分秒の後ろに"Z"と付けるとUTCらしい(以下引用)
ISO 8601 - Wikipedia

タイムゾーン指定子

協定世界時(UTC

時刻の後ろに Z を添えることで協定世界時(UTC)での時刻をそのまま示すことができる。

例:
2004-04-01T12:00Z (20040401T1200Z)

2004年04月01日12時00分(正午)(UTC

文字列->datetime

python-dateutilを使うとISO8061の文字列を簡単にパースできるので実際にやってみる。

>>> from dateutil.parser import parse
>>> x = parse("2013-12-31T23:55:58Z")
>>> x
datetime.datetime(2013, 12, 31, 23, 55, 58, tzinfo=tzutc())
>>> x.isoformat()
'2013-12-31T23:55:58+00:00'

datetimeのastimezoneを使ってJSTに変換

タイムゾーンを変えるときはpytzのastimezoneを使う。

さっきの処理にこのように続けてみる。

>>> import pytz
>>> JST = pytz.timezone("Japan")
>>> y = x.astimezone(JST)
>>> y
datetime.datetime(2014, 1, 1, 8, 55, 58, tzinfo=<DstTzInfo 'Japan' JST+9:00:00 STD>)
>>> y.isoformat()
'2014-01-01T08:55:58+09:00'


astimezoneはaware-datetimeからしか呼べないので注意。
このように、naive-datetimeから呼ぶとエラーになる。

>>> now = datetime.now()
>>> now.astimezone(JST)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime

時差が入っている文字列をJSTに変換

>>> z = parse("2013-12-31T23:55:48+0900")
>>> z
datetime.datetime(2013, 12, 31, 23, 55, 48, tzinfo=tzoffset(None, 32400))
>>> z.astimezone(JST)
datetime.datetime(2013, 12, 31, 23, 55, 48, tzinfo=<DstTzInfo 'Japan' JST+9:00:00 STD>)
>>> z == z.astimezone(JST)
True

サマータイムを考慮するときはnormalize

>>> NY = pytz.timezone("America/New_York")
>>> NY.normalize(NY.localize(datetime(2014, 3, 9, 2, 1, 0)))
datetime.datetime(2014, 3, 9, 3, 1, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
>>> NY.normalize(NY.localize(datetime(2014, 3, 9, 2, 1, 0))).dst()
datetime.timedelta(0, 3600)

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