zaki work log

作業ログやら生活ログやらなんやら

[Python] datetime.strptime()を使った文字列からdatetimeオブジェクトへの変換とタイムゾーン

Pythonでは日付を表す文字列からdatetime型のオブジェクトへの変換にstrptime()を使用できる。
例えば'Thu Feb 11 17:01:34 2021'という文字列があった場合、'%a %b %d %H:%M:%S %Y'というフォーマット文字列を用意することでオブジェクトにできる。
なお、Ansibleの場合はto_datetimeフィルターでこの機能を使うこともできる。

docs.python.org

ここで、タイムゾーンの表記と処理で少しハマったので備忘録。

UTCオフセットによる表記 (例 +0900)

'Thu Feb 11 17:21:52 +0900 2021'

この表記の+0900の部分は、小文字の%zを使えば取得できる。

import datetime

date_str_tz = 'Thu Feb 11 17:21:52 +0900 2021'
format_str_tz = '%a %b %d %H:%M:%S %z %Y'
dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
print(dt_tz)
print(dt_tz.tzinfo)

このコードを実行すると、結果は以下の通りでawareなオブジェクトとなる。

2021-02-11 17:21:52+09:00
UTC+09:00

ただしこのコードは、Python 3.6.9 では動作するが、Python 2.7.18 では%zが対応しておらず以下のエラーで機能しない。

Traceback (most recent call last):
  File "strptime.py", line 18, in <module>
    dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
  File "/usr/local/lib/python2.7/_strptime.py", line 324, in _strptime
    (bad_directive, format))
ValueError: 'z' is a bad directive in format '%a %b %d %H:%M:%S %z %Y'

タイムゾーン名表記 (例 JST)

'Thu Feb 11 17:07:53 JST 2021'

この表記のJSTの部分は、大文字の%Zを使えば取得できる。

date_str_tz = 'Thu Feb 11 17:07:53 JST 2021'
format_str_tz = '%a %b %d %H:%M:%S %Z %Y'
dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
print(dt_tz)
print(dt_tz.tzinfo)  # None

実行すると以下の通り。
%zの場合と異なりtzinfoはNoneとなっている。

2021-02-11 17:07:53
None

ただしこの%Zを使ったタイムゾーン名の書式指定は、実行環境であるOSのタイムゾーン設定に依存しており、GMTUTC以外のタイムゾーンについてはOSの設定と合致していないとエラーになってしまう。
例えばpython:3Dockerイメージのように、OSのタイムゾーンが設定されておらずUTCのまま(シェル上でdate実行などで確認できる)の場合は、以下のようにエラーとなる。

Traceback (most recent call last):
  File "/tmp/strptime.py", line 12, in <module>
    dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
  File "/usr/local/lib/python3.9/_strptime.py", line 568, in _strptime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
  File "/usr/local/lib/python3.9/_strptime.py", line 349, in _strptime
    raise ValueError("time data %r does not match format %r" %
ValueError: time data 'Thu Feb 11 17:07:53 JST 2021' does not match format '%a %b %d %H:%M:%S %Z %Y'

該当ドキュメントは以下の通り。(日本語版でも訳されてないけどなぜかどの言語もJSTと日本に住んでる人に向けた例文になっているw)

  • %Z

    In strftime(), %Z is replaced by an empty string if tzname() returns None; otherwise %Z is replaced by the returned value, which must be a string. strptime() only accepts certain values for %Z:

    1. any value in time.tzname for your machine's locale
    2. the hard-coded values UTC and GMT

    So someone living in Japan may have JST, UTC, and GMT as valid values, but probably not EST. It will raise ValueError for invalid values.

"Technical Detail" / datetime — Basic date and time types — Python 3.9.1 documentation https://docs.python.org/3/library/datetime.html#technical-detail

例えばESTというタイムゾーン名(東部標準時)の場合は、OSのタイムゾーン設定もESTになっている必要がある。

$ docker run --rm -v "$PWD":/mnt -e TZ=America/New_York -it python:3 bash
root@4e1469a8fd52:/# date
Thu Feb 11 08:09:23 EST 2021
root@4e1469a8fd52:/# python
Python 3.9.1 (default, Feb  9 2021, 07:42:03) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> date_str_tz = 'Thu Feb 11 17:07:53 JST 2021'
>>> format_str_tz = '%a %b %d %H:%M:%S %Z %Y'
>>> dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.9/_strptime.py", line 568, in _strptime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
  File "/usr/local/lib/python3.9/_strptime.py", line 349, in _strptime
    raise ValueError("time data %r does not match format %r" %
ValueError: time data 'Thu Feb 11 17:07:53 JST 2021' does not match format '%a %b %d %H:%M:%S %Z %Y'
>>> print(dt_tz)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'dt_tz' is not defined

上記の通り、OS設定がESTになっていると,JSTの文字列は解析失敗している。
この状態からESTである文字列を処理すると以下の通り。

>>> date_str_tz = 'Thu Feb 11 17:07:53 EST 2021'
>>> dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
>>> print(dt_tz)
2021-02-11 17:07:53
>>> print(dt_tz.tzinfo)
None

この通り、ESTというタイムゾーン名を処理できている。

なお、タイムゾーン名がGMTあるいはUTCであれば、OSのタイムゾーンには関係せず、Python 2.7.18 でも動作確認。

$ docker run --rm -v "$PWD":/mnt -it python:2 bash
root@2bd09566c12f:/# python
Python 2.7.18 (default, Apr 20 2020, 19:27:10) 
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> date_str_tz = 'Thu Feb 11 17:07:53 UTC 2021'
>>> format_str_tz = '%a %b %d %H:%M:%S %Z %Y'
>>> dt_tz = datetime.datetime.strptime(date_str_tz, format_str_tz)
>>> dt_tz
datetime.datetime(2021, 2, 11, 17, 7, 53)
>>> dt_tz.tzinfo
>>> print(dt_tz.tzinfo)
None

Dockerのpythonコンテナでタイムゾーン設定するには

-e TZ=Asia/Tokyoを付与する。
※ これはpythonコンテナイメージ(baseがdebian)の場合。CentOS系も同様。ただしAlpineはまた別

[zaki@cloud-dev ~]$ docker run --rm -v "$PWD":/mnt -e TZ=Asia/Tokyo -it python:3 bash
root@d8719dc21019:/# date
Thu Feb 11 22:47:17 JST 2021

(おまけ) strptime()のフォーマット文字列抜粋

頻出

指定子 意味
%Y 西暦 (4桁)
%m 月 (01オリジン)
%d
%H
%M
%S

マイナー(個人の感想)な表記

指定子 意味
%a 曜日の英語表記(Mon.とかfriとか)
%b 月の英語表記(Febとかmayとか)
%f マイクロ秒(秒の小数点未満6桁)
%I 12時間表記のhour (↓とセットで使う)
%p AM/PM (↑とセットで使う)
%Z タイムゾーン名 (UTCとかGMTとかJSTとか。本記事参照)
%z UTCオフセット

ドキュメントには「ロケールの曜日名」とか書かれているが、試した限りja_JP.UTF-8設定の環境でも「」とかは認識しなかった。

参考情報

docs.python.org

qiita.com

dev.classmethod.jp

note.nkmk.me

qiita.com