Python で datetime の naive と aware を比較できず TypeError になる
can't compare offset-naive and offset-aware datetimes は tzinfo の有無が違う datetime を比較・減算したときに起きる。
両方を aware(zoneinfo / timezone.utc)に揃えると解決する。
#python#datetime#timezone#naive#aware
公開:
要約
TypeError: can't compare offset-naive and offset-aware datetimes は、片方に tzinfo が付いて aware、もう片方が naive のまま比較・減算した場合に発生する。
両方を aware に揃えるのが基本方針で、Python 3.9 以降は標準モジュールの zoneinfo を使うのが推奨。
よくある原因
datetime.now()とdatetime.now(timezone.utc)の混在: 引数なしのnow()は naive、tzinfo を渡すと aware で結果の型が異なる。- 外部ライブラリは aware: SQLAlchemy / pydantic / Django ORM などは多くの場合 aware で値を返すため、自前の naive と比較すると落ちる。
- ISO 文字列のタイムゾーン欠落:
datetime.fromisoformat("2025-01-01T12:00:00")はタイムゾーン情報なしで naive、末尾に+09:00が付くと aware。 pytzとzoneinfoの混在: 古いpytz由来の tzinfo はlocalize経由でないと正しく扱えず、zoneinfoと挙動が違う場面がある。
解決策
1. 両方を aware にする
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
now = datetime.now(ZoneInfo("Asia/Tokyo"))
later = datetime.now(timezone.utc)
# どちらも aware なので比較・減算が可能
print(later > now)datetime 公式ドキュメント のとおり、tzinfo を持つかどうかで naive / aware が決まる。
2. naive を aware に昇格
from datetime import datetime
from zoneinfo import ZoneInfo
naive = datetime(2025, 1, 1, 12, 0)
aware = naive.replace(tzinfo=ZoneInfo("Asia/Tokyo"))replace(tzinfo=...) は時刻の数字をそのまま保ったまま「これは JST だ」と注釈を付ける操作。
UTC に変換したいなら astimezone(timezone.utc) を続けて呼ぶ。
3. aware を naive に揃える(推奨しない)
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
aware = datetime.now(timezone.utc)
naive = aware.astimezone(ZoneInfo("Asia/Tokyo")).replace(tzinfo=None)DB の制約で naive しか保存できない場合の最終手段。
意図しないタイムゾーン差を生むので、可能ならスキーマ側を aware に直す方が安全。
4. プロジェクト全体のルール統一
実用上は「内部表現は UTC aware、ユーザー表示で ZoneInfo 変換」が事故が少ない。ZoneInfo 自体は zoneinfo モジュール公式 のとおり標準ライブラリに含まれており、pytz は新規採用しなくてよい。