0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonのdatetime、夏時間の計算を3ケースで比べる

0
Posted at

時差を付けても、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秒。

夏時間終了日のニューヨークでは01:30が2回来る。同じtzinfoのまま引くと0秒、UTCへ変換して引くと3600秒になる流れ。

同梱の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ケースを回す。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?