Pythonのdatetimeとposixtimeを行き来するシンプルな方法

time-image
Image from pixabay

今回はPythonのdatetimeとUNIX時間 (POSIXとも呼ばれる) を行き来する間違いのないやり方を扱います。

datetime型は通常私たちが使用している、2018年9月15日何時何分何秒と行った情報を保持するデータ型、UNIX/POSIXは、1970年1月1日0時0分0秒から経過した秒数です。

やり方は一つではないと思いますが、自分はこれなら間違えない、という方法を紹介したいと思います。

メインコード

解説は後回しにして、先にコードを確認してしまいましょう。
分からなさそうであれば、先に解説を読んでみてください。

今回は日本時間の2018年9月15日10時00分00秒とUTCを行き来します。

# ライブラリのimport
import datetime
import calendar
import pytz

# timezone
tz_tokyo = pytz.timezone("Asia/Tokyo")
tz_utc = pytz.utc

# naiveのdatetime作成
datetime_naive = datetime.datetime(2018, 9, 15, 10, 0, 0)

# datetime(naive) -> datetime(Asia/Tokyo)
datetime_tokyo = tz_tokyo.localize(datetime_naive)

# datetime(Asia/Tokyo) -> datetime(utc)
datetime_utc = datetime_tokyo.astimezone(tz_utc)

# datetime(utc) -> unix_time
unixtime_from_utc = calendar.timegm(datetime_utc.timetuple())

# unix_time -> datetime(naive)
naive_datetime_from_utc_unixtime = datetime.datetime.fromtimestamp(unixtime_from_utc)

# datetime(naive) -> datetime(utc)とdatetime(Asia/Tokyo)
# この二つは同じタイミングで作成可能
utc_datetime_from_utc_unixtime = naive_datetime_from_utc_unixtime.astimezone(tz_utc)
tokyo_datetime_from_utc_unixtime = naive_datetime_from_utc_unixtime.astimezone(tz_tokyo)

print("utc_datetime_from_utc_unixtime: {}".format(utc_datetime_from_utc_unixtime))
print("tokyo_datetime_from_utc_unixtime: {}".format(tokyo_datetime_from_utc_unixtime))

この方法で、最終的に求めたい出力を得ることができているはずです。

解説

順を追って一つずつ解説をしていきます。

pytzについて

今回はtimezoneを扱う際、基本的にpytzを頼っています。
詳しい使い方はドキュメントを参考にしていただければと思います。
ここで把握しておくこととしては、timezoneに対応するオブジェクトを作成する、という部分でしょうか。

tz_tokyo = pytz.timezone("Asia/Tokyo")
tz_utc = pytz.utc

timezoneは、

from pytz import timezone

としてimportする場合もあります。今回はpytzを使用していることを明示するために、シンプルな方法を使っています。

timezone情報を持つdatetimeの作成

今回の方法では、はじめにnaive、つまりtimezone情報を持たないdatetimeを作成した後、pytzを使用してtimezone情報を持つdatetimeに変換しました。
(ちなみに、timezone情報を持つdatetime型をawareなdatetimeと呼びます。)

直感的に一番分かりやすいのは、datetime.datetime()の引数にtz_infoを渡す方法ですが、この方法は使うことができません。
実際に使うと下記のような出力になります。

# ライブラリのimportなどは省略
tz_tokyo = pytz.timezone("Asia/Tokyo")
datetime_tokyo = datetime.datetime(2018, 9, 15, 10, 0, 0, tzinfo=tz_tokyo)
print(datetime_tokyo)
# 出力結果
2018-09-15 10:00:00+09:19

ご覧のように、”謎の19分”がtimezone情報に現れます。
pytzのドキュメントに書かれている通りですが、pytzがサポートしているtimezone情報を持つデータの作成方法は2種類です。

  1. timezone情報を持たないnaiveなdatetimeにlocalize()メソッドを使用する方法。
  2. すでに存在しているlocalize済みのデータに対して、astimezone()メソッドを使用する方法。

今回は1の方法を使います。
先ほどのコードにprint文を足して実行してみます。

tz_tokyo = pytz.timezone("Asia/Tokyo")
tz_utc = pytz.utc

# naiveのdatetime作成
datetime_naive = datetime.datetime(2018, 9, 15, 10, 0, 0)

# datetime(naive) -> datetime(Asia/Tokyo)
datetime_tokyo = tz_tokyo.localize(datetime_naive)
print(datetime_tokyo)

出力結果

2018-09-15 10:00:00+09:00

期待通り正しくtimezone情報を持たせることができました。

Asia/Tokyoからutcへの変換

ここはシンプルに、astimezone()を使用してutcに変換しています。

後述しますが、間違いなくデータを扱うために、unixtimeに変換する前に、utcへ変換しておくことがポイントになります。

datetime_utc = datetime_tokyo.astimezone(tz_utc)
print(datetime_utc)
2018-09-15 01:00:00+00:00

期待通りの結果が得られています。

unixtimeへの変換

unixtimeへの変換にはcalendarのtimegmを使用します。

unixtime_from_utc = calendar.timegm(datetime_utc.timetuple())

ポイントとしては、datetime型をtimetuple()で変換する必要がある、ということぐらいでしょうか。

ここで、先に述べていた、Asia/Tokyoからutcに変換する必要がある理由を説明します。

# utcに変換した場合のunixtime
datetime_utc = datetime_tokyo.astimezone(tz_utc)
unixtime_from_utc = calendar.timegm(datetime_utc.timetuple())

# Asia/Tokyoから直接変換した場合のunixtime
unixtime_from_tokyo = calendar.timegm(datetime_tokyo.timetuple())

print("unixtime_from_utc: {}".format(unixtime_from_utc))
print("unixtime_from_tokyo: {}".format(unixtime_from_tokyo))
unixtime_from_utc: 1536973200
unixtime_from_tokyo: 1537005600

この通り、出力結果が異なっています。

日本の午前10時とUTCの午前1時は、同じunixtimeを指していなければなりません。
そのため、結果が異なることは大問題ですよね。

これを避けるために、unixtimeに変換する前に、一度UTCに変換する必要が発生する、というわけです。

unixtimeからnaiveのdatetimeへの変換

これも1行ですが、少し注意が必要なので、出力もみておきます。

naive_datetime_from_utc_unixtime = datetime.datetime.fromtimestamp(unixtime_from_utc)
print(naive_datetime_from_utc_unixtime)
2018-09-15 10:00:00

これは、naiveのdatetimeです。
ぱっと見はAsia/Tokyoのように見えてしまいますが、timezone情報は持っていないです。

確認もしておきましょう。

print(naive_datetime_from_utc_unixtime.tzinfo)
None

当然ではあるのですが、Noneが返ってきます。

naiveの情報を扱うと、場所によって結果が変わる、というような事態を引き起こしかねないので、可能であればawareで扱いましょう。

naiveのdatetimeからtimezone付きのdatetime(awareのdatetime)への変換

これはUTCもAsia/Tokyoも同時に作成することができます。

utc_datetime_from_utc_unixtime = naive_datetime_from_utc_unixtime.astimezone(tz_utc)
tokyo_datetime_from_utc_unixtime = naive_datetime_from_utc_unixtime.astimezone(tz_tokyo)
print("utc_datetime_from_utc_unixtime: {}".format(utc_datetime_from_utc_unixtime))
print("tokyo_datetime_from_utc_unixtime: {}".format(tokyo_datetime_from_utc_unixtime))
utc_datetime_from_utc_unixtime: 2018-09-15 01:00:00+00:00
tokyo_datetime_from_utc_unixtime: 2018-09-15 10:00:00+09:00

期待通りの結果が得られていますね。

一度naiveを経由することを明示するために分けて書きましたが、

utc_datetime_from_utc_unixtime = datetime.datetime.fromtimestamp(unixtime_from_utc).astimezone(tz_utc)
tokyo_datetime_from_utc_unixtime = datetime.datetime.fromtimestamp(unixtime_from_utc).astimezone(tz_tokyo)

のように、unixtimeから一気に変換してしまう方が分かりやすいかもしれません。

まとめ

pythonのdatetimeとunixtimeの行き来をする、間違いが少なそうな方法を解説しました。

まずは間違えないことが第一、という立場で分かりやすい方法を取ったつもりです。
夏時間なども一度utcを経由してくれれば大丈夫、だと思っていますが、未検証なのでその辺りはご了承ください。

参考文献

pythonドキュメント 8.1. datetime
pytzドキュメント
Nekoya Press