Pythonのdatetime.timedeltaで秒を扱う際に注意すべきたったひとつのこと
こんにちは。サービスグループの武田です。
みなさんプログラムで日時の扱いは得意でしょうか?基本的には言語の標準ライブラリを使うことが多いのではないでしょうか。
今回はPythonのお話です。Pythonは標準でdatetimeモジュールが提供され、日付や時間、タイムゾーンなどを扱えます。その中にtimedelta
オブジェクトというものが提供されているのですが、これは 時刻間の差 を表すという、他の言語ではあまり見ないユニークな機能を提供します。
使い方の例を次に示します。+
演算子などでdatetime
オブジェクトと演算でき、直感的に日時の操作が可能です。
from datetime import datetime, timedelta d = datetime.fromisoformat("2004-07-07T00:00:00") d #=> datetime.datetime(2004, 7, 7, 0, 0) d + timedelta(seconds=30) #=> datetime.datetime(2004, 7, 7, 0, 0, 30) d + timedelta(hours=8) #=> datetime.datetime(2004, 7, 7, 8, 0)
さてこのtimedelta
オブジェクトですが、seconds
という読み出し用の属性を持っています。名前から分かるとおり、表している時刻間の差を秒で取得できます。
timedelta(seconds=30).seconds #=> 30 timedelta(minutes=5).seconds #=> 300 timedelta(hours=8).seconds #=> 28800 (timedelta(hours=8) * 4).seconds #=? ???
なるほど、便利ですね。最後の結果は伏せたのですが、結果を予想できるでしょうか?
8時間は28800秒ですね。その前の結果からわかります。それを4倍しているのだから 115200だ! と考えたあなた!間違いです!
結果は 28800 になります。
はて、どういうことなのか。
詳細はドキュメントを読んでもらうとして重要な点だけ抜粋します。
days, seconds, microseconds だけが内部的に保持されます。
さらに、値が一意に表されるように days, seconds, microseconds が以下のように正規化されます
0 <= microseconds < 1000000
0 <= seconds < 3600*24 (一日中の秒数)
-999999999 <= days <= 999999999
datetime --- 基本的な日付型および時間型 — Python 3.10.0b2 ドキュメント
そうなんです。24時間までは秒で保持されるのですが、超えると内部的には秒ではなく日として保存されます。さきほどのseconds
属性はあくまで保持している秒属性を返すだけなんですね。事実(timedelta(hours=8) * 4).days
の結果は1
を返します。
そんなわけでこのエントリの言いたい たったひとつのこと とは、timedeltaが保持している時間差を正しく取得したいならtotal_seconds()
を使ってください!ということです。
(timedelta(hours=8) * 4).total_seconds() #=> 115200.0
ただしこのメソッドはmicrosecondsも含むためfloat型を返します。秒だけでいいのであればint()
などで小数部分は切り捨てましょう。
まとめ
24時間を超えない限りはseconds
で秒を取得していても問題ありません。しかし24時間を超えた値を扱った時にバグとして表面化します。仕様として超える可能性があるのであればtotal_seconds()
を使ってバグを作り込まないように気をつけてください。