2018/12/03

[Python] datetime オブジェクト、Unix time、文字列の相互変換まとめ

Python で datetime オブジェクトUnix time、文字列を相互変換する方法はいろいろあります。 中には直感に反するものもあり1、 過去に1度間違えたのに、再度間違えるということもしばしば。

そこで、2度と間違えないように、個人的にベストと思う方法をまとめてみました。

変換には様々な方法があるので、上記が唯一の方法というわけではありませんが、 なるべく、安全かつシンプルになるものを選んだつもりです。

また、変換可能でも、安全では無いと判断したものは矢印を引いていません2

以下2点は Python のバージョンによっては使用できないので、代替案を掲載しておきました。

  • datetime.timestamp() (3.3 以降で使用可)
  • datetime.fromisoformat() (3.7 以降で使用可)

なお、掲載コードは以下のように import 済みとします。

1
2
from datetime import datetime
import pytz

1. Unix time と datetime オブジェクトの相互変換

Unix time → datetime (aware)

Unix time から datetime オブジェクトを生成するには、fromtimestamp() を使用します。

この時、tz は必ず指定するようにしましょう。そうしないと、ローカルのタイムゾーンを適用した native オブジェクト が生成されてしまいます。 例外的に、datetime.now() 等でローカル時間を取得して処理する時は tz を指定しなくても良いかもしれません。

1
2
3
4
5
6
7
8
9
datetime.fromtimestamp(1541030400, tz=pytz.utc)
# 2018-11-01 00:00:00+00:00
 
datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
 
datetime.fromtimestamp(1541030400# native
# 2018-11-01 09:00:00  (+09:00 のタイムゾーン設定されているマシンで実行した場合)
# 2018-10-31 20:00:00  (-05:00 のタイムゾーン設定されているマシンで実行した場合)

Unix time → datetime (native)

Unix Time から native オブジェクトを生成したい場合は、utcfromtimestamp() が使えます。

1
2
datetime.utcfromtimestamp(1541030400)
# 2018-11-01 00:00:00

直感的には、UTC な aware オブジェクトが生成されて欲しいところ。 間違えないように気をつけましょう。

datetime (aware) → Unix time

datetime オブジェクトから Unix time に変換する場合は、timestamp() を使用します。

この時、native オブジェクトから変換すると、ローカルのタイムゾーンを適用して変換されてしまいます。 こちらも、fromtimestamp() と同様、datetime.now() 等で取得したローカル時間の native オブジェクトを処理する時は良いかもしれませんが、基本的には使わないようにした方が良いでしょう。

1
2
3
4
5
6
7
8
9
10
11
12
dt = datetime.fromtimestamp(1541030400, tz=pytz.utc)
int(dt.timestamp())
# 1541030400
 
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
int(dt.timestamp())
# 1541030400
 
dt = datetime.utcfromtimestamp(1541030400# native
int(dt.timestamp())
# 1540998000  (+09:00 のタイムゾーン設定されているマシンで実行した場合)
# 1541044800  (-05:00 のタイムゾーン設定されているマシンで実行した場合)

timestamp() が使えない時

timestamp() は Python 3.3 以降で導入されました。 それ以前のバージョンで Unix time に変換したい場合は、以下のように calendar を使うと良いようです。 この時、UTC の aware に変換しないと想定外の値に変換されてしまうので注意が必要です。

1
2
3
4
import calendar
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
calendar.timegm(dt.astimezone(pytz.utc).timetuple())
# 1541030400

参考

Pythonの日付処理とTimeZone | Nekoya press

2. aware と native の相互変換

datetime (aware) → datetime (native)

aware から native にする場合は replace(tzinfo=None) を使用します。

1
2
3
4
5
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
 
dt.replace(tzinfo=None)
# 2018-11-01 09:00:00

datetime (native) → datetime (aware)

native から aware に変換する時は replace(tzinfo=xxx)使ってはいけません。 代わりに pytz.localize() を使用します。

1
2
3
4
5
dt = datetime.utcfromtimestamp(1541030400)
# 2018-11-01 00:00:00
 
pytz.timezone('Asia/Tokyo').localize(dt)
# 2018-11-01 00:00:00+09:00

タイムゾーンは日付によってオフセットが変わるので3、このような仕様になっているのですが、 かなり紛らわしく間違いやすいので注意が必要です。

参考

3. aware のタイムゾーンを変更する

同じ Unix time を持つ別のタイムゾーンに変換するには astimezone() を使用します。

1
2
3
4
5
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
 
dt.astimezone(pytz.timezone('America/New_York'))
# 2018-10-31 20:00:00-04:00

なお、astimezone(None) は native に変換するのではなく、ローカルのタイムゾーンに変更します。 最初、メリットがわからなかったのですが、これを使ってローカルタイムゾーンを取得するという技が紹介されていました4

参考

datetime - Python: Figure out local timezone - Stack Overflow

4. 文字列と datetime オブジェクトの相互変換

書式文字列を使用して、任意の文字列と datetime を相互変換することが出来ます。

書式文字列のディレクティブ一覧は以下

strftime() and strptime() Behavior

文字列 → datetime

文字列から datetime オブジェクトに変換する場合は strptime() を使用します。

%z を含む場合は aware になり、含まない場合は native になります。 なお、%Z(大文字)は strptime() では使用できないようです。

1
2
3
4
5
datetime.strptime("2018/11/01 09:00", "%Y/%m/%d %H:%M")
# 2018-11-01 09:00:00
 
datetime.strptime("2018/11/01 09:00+0900", "%Y/%m/%d %H:%M%z")
# 2018-11-01 09:00:00+09:00

datetime → 文字列

datetime から文字列に変換するには strftime() を使用します。

native に対し %z を指定しても何も表示されません。

1
2
3
4
5
6
7
8
9
10
11
dt = datetime.utcfromtimestamp(1541030400)
# 2018-11-01 00:00:00
dt.strftime("%Y/%m/%d %H:%M%z")
# 2018/11/01 00:00
 
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
dt.strftime("%Y/%m/%d %H:%M%z")
# 2018/11/01 09:00+0900
dt.strftime("%Y/%m/%d %H:%M(%Z)")
# 2018/11/01 09:00(JST)

5. ISO フォーマット文字列と datetime オブジェクトの相互変換

ISO 8601 形式 と datetime の相互変換も提供されています。

ただ、ISO 8601 にも基本と拡張が存在し、拡張形式にしか対応していない様子。 さらに UTC を表す "Z" には対応していないなど、使い勝手はイマイチです。

個人的には strptime()strftime() を使用した方が柔軟性が高く、便利かと思います。

ISO フォーマット文字列 → datetime オブジェクト

ISO フォーマット文字列から datetime に変換するには fromisoformat() を使用します。

上述のように、拡張形式にしか対応していません5

1
2
3
iso_string = "2018-11-01T09:00:00+09:00"
datetime.fromisoformat(iso_string)
# 2018-11-01 09:00:00+09:00

fromisoformat() は Python 3.7 で導入されたので、 それ以前の場合は strptime() で代用することになります。

厄介なのは ISO フォーマットと strptime() のタイムゾーンの記述方法が異なる点です。 ISO は +09:00 なのに対し、 strptime()+0900 でコロンがありません。

あまり良い方法は提供されていないようなので、愚直に置換してやるのがよさそうです

参考

python - parsing timezone with colon - Stack Overflow

1
2
3
4
5
6
7
8
iso_string = "2018-11-01T09:00:00+09:00"
 
# タイムゾーンのフォーマットを変更
dt_string = iso_string if ":" != iso_string[-3:-2] else iso_string[:-3]+iso_string[-2:]
# 2018-11-01T09:00:00+0900
 
datetime.strptime(dt_string, "%Y-%m-%dT%H:%M:%S%z")
# 2018-11-01 09:00:00+09:00

datetime オブジェクト → ISO フォーマット文字列

datetime から IOSフォーマット文字列に変換するには isoformat() を使用します。

1
2
3
4
5
6
7
8
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
 
dt.isoformat()
# 2018-11-01T09:00:00+09:00
 
dt.isoformat(timespec='hours')
# 2018-11-01T09+09:00

timespecsep を用いて多少フォーマットを変更できますが、上述のように全ての ISO 8601 形式をカバーしているわけではないので、使用できるシーンは限られそうです。

6. コンストラクタ

コンストラクタでタイムゾーンを指定すると、native に対し replace() したのと同等になってしまいます。

コンストラクタで tzinfo を指定するのではなく、native で作成してから pytz.localize() で aware に変換するようにしましょう。

1
2
3
4
5
6
7
8
# こちらが正解
dt = datetime(2018, 9, 1# native
pytz.timezone('Asia/Tokyo').localize(dt)
# 2018-09-01 00:00:00+09:00
 
# 以下のようにコンストラクタで tzinfo を指定すると19分というずれが発生
datetime.datetime(2018, 9, 1, tzinfo=pytz.timezone('Asia/Tokyo'))
# 2018-09-01 00:00:00+09:19

参考

  1. native になりそうなのに、ローカルのタイムゾーンを適用しちゃうとか 
  2. 安全な方法をご存じの方は教えていただけると幸いです 
  3. サマータイムのある地域だと特に 
  4. かなりトリッキー。普通にローカルタイムゾーンを取得出来ればそれで良いのだが… 
  5. セパレータは "T" に限定されず、任意の1文字で良い 
?

0 件のコメント: