pythonで7-digitsなマイクロ秒を含んだ文字列をstriptimeでdatetimeオブジェクトにしようとしてハマった話

時刻を文字列表現にするときにマイクロ秒を含んだ表現の場合がある。例えばgithub actionsのログは以下のようなもの。

lint Set up job  2021-05-25T20:31:24.8848799Z Current runner version: '2.278.0'

2021-05-25T20:31:24.8848799Z 部分が時刻の表現。実はマイクロ秒部分が7-digitsなのだけれど、これが意図しない表現でハマってしまったと言う話。

%f Microsecond as a decimal number, zero-padded on the left. 000000, 000001, …, 999999 (5)

ここを参考に以下のようにすればdatetimeオブジェクトが手に入ると思ったがValueErrorになる。辛い。

>>> from datetime import datetime
>>> s = "2021-05-25T20:31:24.8848799Z"
>>> datetime.strptime(s, "%Y-%m-%dT%H:%M:%S.%f%z")
...
ValueError: time data '2021-05-25T20:31:24.8848799Z' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'

詳細

まずその前にマイクロ秒部分を除いた表現で試してみる。

>>> datetime.strptime("2021-05-25T20:31:24", "%Y-%m-%dT%H:%M:%S")
datetime.datetime(2021, 5, 25, 20, 31, 24)

これは上手く行く。

しかし、マイクロ秒部分を追加したらValueErrorになった。辛い。実は先程引用したドキュメントにそのあたりの説明が書かれてはいる。

When used with the strptime() method, the %f directive accepts from one to six digits and zero pads on the right. %f is an extension to the set of format characters in the C standard (but implemented separately in datetime objects, and therefore always available).

試しに6-digitsでやってみる。

>>> s[:26]
'2021-05-25T20:31:24.884879'
>>> datetime.strptime(s[:26]+s[27:], "%Y-%m-%dT%H:%M:%S.%f%z")
datetime.datetime(2021, 5, 25, 20, 31, 24, 884879, tzinfo=datetime.timezone.utc)

アドホックなコードになってしまったがたしかに取れた。しかしだるい。

秒数部分で区切っても良いが、時刻部分は切り貼りせずそのまま使いたい。

>>> s.split(".")[0]
'2021-05-25T20:31:24'
>>> len(s.split(".")[0])
19
>>> datetime.strptime(s[:19], "%Y-%m-%dT%H:%M:%S")
datetime.datetime(2021, 5, 25, 20, 31, 24)

標準ライブラリでこの辺サポートしてくれると助かるのだけれど。

pendulum

pendulum.eustace.io

pendulumを使っている場合はその辺をよしなにやってくれる1

>>> import pendulum
>>> pendulum.parse(s)
DateTime(2021, 5, 25, 20, 31, 24, 884879, tzinfo=Timezone('UTC'))

が、依存を増やしたくない場合もあったりはする。

例えばアプリケーションコードの場合はbundleしたりdeployすれば良いだけなのであまり困らないのだけれど、色んな所に配布したいようなコードの場合に依存を増やさず1ファイルで収まると都合が良い場合が多い。これはgoでワンバイナリが手軽というのと同じような話かもしれない(書き手側の苦労でこの恩恵を得るための要求を満たそうとしている)。

そういう意味ではユースケースに応じてコードの書き方も変わるよなーというようなことを最近良く思っている2

追記

pythonソースコード自体を書き換えれば結構手軽に対応できるようだ。 datetime.datetime.strptimeは_strptimeモジュールをimportして使っているだけ。

https://github.com/python/cpython/blob/3e7ee02327db13e4337374597cdc4458ecb9e3ad/Modules/_datetimemodule.c#L5159-L5166

テキトーに変更してあげれば動く。

https://gist.github.com/podhmo/1cb1807f0207112735ea9d7dd8ca8836/revisions


  1. 内部的には多分 dateutil.parser.parse() あたりを使っているとは思う。

  2. jupyter notebookフレンドリーなコードと通常のアプリケーションコードは別だしライブラリのコードも別。書捨てのスクリプトも別だしインフラ用のコードも別。