時差を付けても、1時間が0秒になる。
Pythonの予約ツールで経過時間を扱うなら、自分はUTCへそろえてから引き算する。tzinfo付きなら安心、と済ませると夏時間の終了日に詰まるんだよね。
今日、1時間離れた日時をニューヨーク時刻へ変換して引いたら、0.0だった。オフセットを見直したけど、どちらも正しい。通常日と東京も足して、3ケースで比べた。
datetimeの引き算を3ケースで比べる
実行環境はCPython 3.14.6、macOS/arm64。標準ライブラリとOSのタイムゾーンデータを使った。ZoneInfoNotFoundErrorが出る環境では、IANAのタイムゾーンデータが別途必要だ。
入力はUTCの05:30と06:30。ニューヨークの夏時間が終わった2024年11月3日と、その前日を比べる。東京も対照に置いた。
次をdst_elapsed.pyとして保存し、python3 dst_elapsed.pyで実行する。
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
cases = [
("NY通常日", "2024-11-02", "America/New_York", 3600),
("NY終了日", "2024-11-03", "America/New_York", 0),
("東京", "2024-11-03", "Asia/Tokyo", 3600),
]
for label, day, zone, expected in cases:
tz = ZoneInfo(zone)
start, end = [
datetime.fromisoformat(f"{day}T{hour}:30:00+00:00")
.astimezone(tz)
for hour in ("05", "06")
]
direct = int((end - start).total_seconds())
elapsed = int((
end.astimezone(timezone.utc)
- start.astimezone(timezone.utc)
).total_seconds())
assert direct == expected
assert elapsed == 3600
print(label, f"fold={start.fold}/{end.fold}", direct, elapsed)
実行結果。末尾の2列は秒数。
| ケース | fold(開始/終了) | そのまま引く | UTCへ変換して引く |
|---|---|---|---|
| NY通常日 | 0/0 | 3600 | 3600 |
| NY終了日 | 0/1 | 0 | 3600 |
| 東京 | 0/0 | 3600 | 3600 |
assertもすべて通った。
foldが付いていても、引き算では使われない
終了日の2時刻をISO形式で見ると、開始は2024-11-03T01:30:00-04:00、終了は2024-11-03T01:30:00-05:00になる。時計の針が戻り、01:30が2回来る。UTCでは05:30と06:30なので、実際には1時間離れている。
foldは、重なる時刻の前側を0、後ろ側を1で区別する属性だ。UTCからの変換で付く。自分で1を指定したわけでもない。
それでも差は0秒。
同梱のPython実装、_pydatetime.pyのdatetime.__sub__も読んだ。同じtzinfoオブジェクトを持つ日時同士では、年月日と時計上の時刻から計算した差をそのまま返す。UTCオフセットの差を差し引く処理へ進まない。今回は同じtzを渡しているので、この条件に当たる。
01:30から01:30を引けば0秒になる。fold=1が見えていても、経過時間の計算には反映されない。ログにはオフセットまで残したい。01:30だけを眺めていると、目でも見落とす。
UTCに変換する箇所を決める
上のelapsedでは、両方をastimezone(timezone.utc)で変換して引く。05:30から06:30までの3600秒を得られる。
ここでreplace(tzinfo=timezone.utc)を使うと失敗する。01:30という時計上の値を保ったままUTC扱いにするので、両方が01:30 UTCになってしまう。終了日の2時刻で試すと、差はやはり0秒だった。
用途は分けておきたい。「毎朝9時」の予約には現地の日時とタイムゾーンが要る。今回直したいのは「開始から何秒たったか」という計算だ。LLMから予約ツールを呼ぶ設計でも、地域名を含む日時の受け取りと、経過秒の計算を別に扱えば、この挙動を小さなテストで押さえられる。
予約ツールに残すテスト
自分なら、UTCで1時間離れた2入力を夏時間終了日の現地時刻に変換するケースを残す。表示用の日時は現地時刻で持ち、経過時間を求める直前にUTCへそろえる。東京だけで通っていたテストも、海外の予約を扱い始めるとこの差が出る。地域を増やす前に、この3ケースを回す。
