概要
Pythonで時刻を扱う際にはdatetimeモジュールを使うのが一般的。Python3でも少し前までは、UNIXタイムを扱うのに一手間かかったが、Python3.7ではとても扱いやすくなっている。
datetimeオブジェクトに対してtimestamp()
でUNIXタイムを取得することができ、 datetime.fromtimestamp()
(classmethod)でUNIXタイムからdatetimeオブジェクトを作ることができる。
気軽にUNIXタイムを扱えるのは嬉しいが、タイムゾーンに気にしないと、意図しない結果になることがある。
環境
Pythonのバージョンは3.7を使用する。macOSで動作を確認した。
UNIXタイムとは
説明不要だと思うが、ウィキペディアから念の為引用しておく。
UNIX時間(ユニックスじかん)またはUNIX時刻(ユニックスじこく、UNIX time(ユニックスタイム)、POSIX time(ポジックスタイム))とはコンピューターシステム上での時刻表現の一種。UNIXエポック、すなわち協定世界時 (UTC) での1970年1月1日午前0時0分0秒から形式的な経過秒数(すなわち、実質的な経過秒数から、その間に挿入された閏秒を引き、削除された閏秒を加えたもの)として表される。
個人的に想定外だった挙動
>>> from datetime import datetime
>>> datetime.fromtimestamp(0)
datetime.datetime(1970, 1, 1, 9, 0)
"1970/01/01 00:00:00"を期待していたら、9時間ずれていた。お察しの通り、日本標準時(UTC+9)が関係している。
仕様です
ドキュメントには、datetime.fromtimestamp(timestamp, tz=None)
について次のように書いてある。
POSIX タイムスタンプに対応するローカルな日付と時刻を返します。オプションの引数 tz が None であるか、指定されていない場合、タイムスタンプはプラットフォームのローカルな日付および時刻に変換され、返される datetime オブジェクトは naive なものになります。
「POSIX タイムスタンプに対応するローカルな日付と時刻を返します 」ということで、ローカルな時刻(今回はJST)の時刻が返された。引用中の「naiveなもの」については、後述する。
UTCの時刻情報がほしい時は、datetime.utcfromtimestamp(timestamp)
のほうを使えばよかった。
>>> datetime.utcfromtimestamp(0)
datetime.datetime(1970, 1, 1, 0, 0)
また、fromtimestamp()
にはタイムゾーンの情報が渡せるので、そちらの方法で対応することもできる。
>>> from datetime import datetime, timezone
>>> datetime.fromtimestamp(0, timezone.utc)
datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
tzinfo
が追加されたが、同じっぽい結果が得られた。このtzinfo
が何かを知ろうと思うと、"naive" と "aware"の2種類のオブジェクトについて理解する必要がある。
"naive" と "aware"
datetimeモジュールで扱う時刻オブジェクトは、"naive"と"aware"の2種類に区別される。
簡単にいうと、"naive"はタイムゾーンに関する情報を持たず、"aware"はタイムゾーンに関する情報を持つ時刻オブジェクトを指す。
それぞれのメリット、デメリットについては公式ドキュメントを読むとよい。
オブジェクトの作成時にタイムゾーンの情報を渡せばawareなオブジェクトを得られるし、そうでなければnaiveになると覚えておけば、基本的に問題ないと思われる。
先ほどの例でいうと、datetime.fromtimestamp(0)
ではnaiveなオブジェクトが生成され、datetime.fromtimestamp(0, timezone.utc)
ではawareなオブジェクトが生成された。
awareなオブジェクトの作り方
datetimeモジュールでは、タイムゾーンを表すためにtzinfo
のサブクラスが使われる。このサブクラスを自分で実装することもできるが、dateutilもしくはpytzというライブラリがよく使われている。
例えば、dateutilを使うと次のようになる。
>>> from datetime import datetime
>>> from dateutil import tz
>>> datetime.now(tz=tz.gettz("Asia/Tokyo"))
datetime.datetime(2018, 12, 22, 23, 20, 56, 431366, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
ここでtz
を指定しないと、naiveなオブジェクトが作られる。
>>> datetime.now()
datetime.datetime(2018, 12, 22, 23, 22, 45, 910050)
余談
naiveなオブジェクトはプログラムを実行する環境に依存するので、awareな方を使った方がいいと思う。
例えば、手元の環境ではdatetime.now()
でJSTが返るが、AppEngineではUTCになっている、など。
以上です。