datetime モジュールは Python の標準ライブラリの中でも、使用頻度が高い割に罠が多かったり使い方が難しかったりする、あまりイケてないモジュールだと個人的に思っています。
そんな datetime モジュールですが、 Python 2 のプロジェクトを Python 3 に移行した時に大分コードを整理できてちょっと感動したので紹介しておきます。
unixtime との相互変換
unixtime から datetime.datetime
への変換は、 ローカルタイムなら.fromtimestamp()
で、 UTC なら .utcfromtimestamp()
関数で行います。
>>> import time
>>> from datetime import datetime
>>> now = time.time()
>>> now
1415542873.099776
>>> loc = datetime.fromtimestamp(now)
>>> loc
datetime.datetime(2014, 11, 9, 23, 21, 13, 99776)
>>> utc = datetime.utcfromtimestamp(now)
>>> utc
datetime.datetime(2014, 11, 9, 14, 21, 13, 99776)
しかし、逆に datetime
から unixtime への変換は直接できず、一旦 .timetuple()
メソッドで time.struct_time
型に変換する必要があります。
しかも、そこから unixtime に戻すとき、ローカルタイムなら time.mktime()
で行けますが、UTC の場合は calendar.timegm()
が必要でした。このためだけに calendar モジュールを import するのが少し残念です。
>>> time.mktime(loc.timetuple())
1415542873.0
>>> import calendar
>>> calendar.timegm(utc.timetuple())
1415542873
time モジュールはC言語の time.h
の機能にOSごとの追加のタイマーを提供するだけで、 datetime は逆に Python 用に設計されているので、組み合わせて使おうとするとこのようなミスマッチが起きていました。
ですが、 Python 3.3 では datetime オブジェクトに直接 unixtime を生成する .timestamp()
メソッドが追加されました。
>>> from datetime import datetime
>>> now = 1415542873.099776
>>> loc = datetime.fromtimestamp(now)
>>> loc.timestamp()
1415542873.099776
ただし、 .timestamp()
は datetime 自体が持っている tzinfo を利用し、 tzinfo を持たない datetime (naive と呼ぶ) の場合は勝手にローカルタイムだとして計算します。
UTC の datetime を unixtime に変換したいときは、ちゃんと tz=utc を設定する必要があります。コレについては次のセクションで。
timezone
datetime モジュールの datetime 型などで、タイムゾーン情報を持つものを aware, 持たないものを naive と呼びます。
naive な datetime は MySQL などほかの naive なソフトウェアとやりとりするときにおせっかいなタイムゾーン変換が入ってハマったりしない純粋な型として魅力があるのですが、 UTC と JST 両方を扱うプログラムを書くときなどは間違えて +9 を2回実行してしまったりとバグの原因になります。
世界のタイムゾーンについては IANA がデータベースを公開しているのですが、 Python のバージョンアップとタイミングが同期させられないので、標準ライブラリは抽象クラスだけを提供し、それを継承した実際のタイムゾーンクラスは pytz という非標準ライブラリで提供されていました。
ですが、夏時間が今のところ発生しない JST と UTC だけ使えれば十分なアプリケーションで、わざわざ世界中のタイムゾーン・データベースを利用するのはちょっと面倒です。
マルチバイト環境のことを考えたくないASCII圏の人の気持ちが分かる気がします。
Python 3 では、 datetime.timezone
という、 UTC からの時差が固定で夏時間が存在しないタイムゾーンを扱うクラスが標準ライブラリで提供されていて、さらに UTC については datetime.timezone.utc
が最初から提供されています。
これを使って、前のセクションでやった unixtime と datetime の変換を、今度は aware な datetime でやってみます。
>>> from datetime import datetime, timezone, timedelta
>>> now = 1415542873.099776
>>> JST = timezone(timedelta(hours=+9), 'JST')
>>> loc = datetime.fromtimestamp(now, JST)
>>> utc = datetime.fromtimestamp(now, timezone.utc)
>>> print(loc)
2014-11-09 23:21:13.099776+09:00
>>> print(utc)
2014-11-09 14:21:13.099776+00:00
>>> loc.timestamp()
1415542873.099776
>>> utc.timestamp()
1415542873.099776
calendar モジュールもいらないし、 Python 2 のときよりシンプルかつバグを起こしにくいコードが書けるようになりました。
なお、 naive な datetime にタイムゾーン情報を付与するには、 .replace()
メソッドを利用します。
>>> naiveutc = datetime.utcfromtimestamp(now)
>>> naiveutc
datetime.datetime(2014, 11, 9, 14, 21, 13, 99776)
>>> naiveutc.replace(tzinfo=timezone.utc).timestamp()
1415542873.099776