Pythonのdatetimeでnaive/aware混在をレビューでどう見抜くか
Pythonのdatetimeでnaive/aware混在をレビューでどう見抜くか
Pythonの datetime は、timezone情報を持たないnaive datetimeと、timezone情報を持つaware datetimeを扱える。
便利な一方で、実務では naiveとawareの混在 が障害の入口になりやすい。
レビューで危ないのは、次のような実装だ。
datetime.now()とdatetime.now(timezone.utc)が混在している- DB保存時だけUTCにして、比較時はローカル時刻を使っている
- API入力のtimezone有無を検証していない
replace(tzinfo=...)で変換したつもりになっている- テストがローカルtimezoneに依存している
この記事ではPythonの日時API一覧ではなく、
時刻の基準がコード上で一貫しているかをレビューで確認する観点を整理する。
まず止めたい実装
from datetime import datetime, timezone
def is_expired(expires_at: datetime) -> bool:
return expires_at < datetime.utcnow()
def create_token() -> dict[str, datetime]:
return {
"issued_at": datetime.now(timezone.utc),
"expires_at": datetime.utcnow(),
}@Reviewer: `datetime.utcnow()` はtimezone情報を持たないnaive datetimeを返します。一方で `issued_at` はaware datetimeです。比較や保存境界で混在するため、UTC awareに統一してください。このコードは、見た目にはすべてUTCを使っているように見える。
しかし datetime.utcnow() の戻り値はtimezone情報を持たない。
レビューでは、変数名に utc が入っているかではなく、
値そのものがtimezone情報を持っているかを確認する必要がある。
なぜ危ないのか
時刻の混在は、すぐに例外として出る場合もあれば、静かに誤判定になる場合もある。
起きやすい問題は次の通りである。
- naiveとawareの比較で例外になる
- ローカルtimezoneのサーバーだけ期限判定がずれる
- DBから読んだ値とAPI入力値の基準が違う
- daylight saving timeの境界でテストが不安定になる
- ログや監査時刻がAPIレスポンスと一致しない
レビューで見るべきなのは、個々の datetime 呼び出しではない。
システム境界ごとに時刻基準が決まっているかである。
レビューで見たい3つの判断線
1. 内部表現をUTC awareに統一しているか
サーバー内部では、UTC aware datetimeに統一すると境界が読みやすい。
現在時刻を作る箇所も、ヘルパーに集約するとレビューしやすい。
from datetime import datetime, timezone
def utc_now() -> datetime:
return datetime.now(timezone.utc)@Reviewer: 現在時刻の生成が複数箇所に散っています。UTC awareに統一するため、`datetime.now(timezone.utc)` を返すヘルパーへ集約してください。2. API入力でtimezone有無を検証しているか
APIから受け取る時刻文字列は、timezone付きとは限らない。
入力境界で検証せず内部に入れると、後段の比較処理で混在が露出する。
from datetime import datetime, timezone
def parse_deadline(value: str) -> datetime:
parsed = datetime.fromisoformat(value)
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise ValueError("deadline must include timezone")
return parsed.astimezone(timezone.utc)@Reviewer: API入力のdatetime文字列をそのまま内部に渡しています。timezoneなし入力を拒否するか、仕様としてどのtimezoneで解釈するかを境界で明示してください。3. replace(tzinfo=...) を変換として使っていないか
replace(tzinfo=timezone.utc) はtimezone情報を付け替えるだけで、時刻を変換しない。
既に別timezoneの時刻をUTCへ変換したいなら、astimezone(timezone.utc) を使う。
@Reviewer: `replace(tzinfo=timezone.utc)` は時刻変換ではなくtzinfoの付け替えです。入力値がローカル時刻なら誤ったUTCとして扱われるため、解釈元timezoneを明示して `astimezone` で変換してください。レビューでは、timezone付与とtimezone変換を分けて読む。
改善例
入力境界でUTC awareへ正規化し、内部比較もUTC awareに統一する。
from datetime import datetime, timedelta, timezone
def utc_now() -> datetime:
return datetime.now(timezone.utc)
def parse_api_datetime(value: str) -> datetime:
parsed = datetime.fromisoformat(value)
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise ValueError("datetime must include timezone")
return parsed.astimezone(timezone.utc)
def is_expired(expires_at: datetime) -> bool:
if expires_at.tzinfo is None or expires_at.utcoffset() is None:
raise ValueError("expires_at must be timezone-aware")
return expires_at.astimezone(timezone.utc) < utc_now()
def create_token(ttl_seconds: int) -> dict[str, datetime]:
issued_at = utc_now()
return {
"issued_at": issued_at,
"expires_at": issued_at + timedelta(seconds=ttl_seconds),
}この構造なら、レビューアーは次を確認しやすい。
- 現在時刻の生成が1箇所に集約されている
- API入力でtimezoneなしを拒否している
- 内部比較がUTC aware同士になっている
replaceで変換したつもりになる余地がない- 期限生成で基準時刻が1回だけ取得されている
DBとAPIの境界で確認したいこと
DBや外部APIは、それぞれdatetimeの扱いが違う。
レビューでは、境界ごとに変換方針を明示したい。
@Reviewer: DBから取得した `created_at` がnaive datetimeとして扱われています。DB driverがUTCとして返す契約なのか、アプリ側でtimezoneを付与する責務なのかを明示してください。@Reviewer: APIレスポンスでtimezoneなし文字列を返しています。クライアントがローカル時刻として解釈する可能性があるため、UTC offset付きのISO文字列で返す設計にしてください。時刻は内部だけで完結しない。
保存、受信、返却の境界で同じ基準を保てるかが重要である。
テストで時刻を固定しているか
datetime.now() を直接呼ぶ実装は、テストが現在時刻に依存しやすい。
時刻を注入できる形にすると、timezoneも含めて検証しやすい。
@Reviewer: テスト対象内で現在時刻を直接取得しているため、期限境界のテストが不安定になります。clock関数を注入し、UTC aware datetimeで固定できる構造にしてください。期限切れ判定、日付集計、月末処理では特に確認したい。
レビュー観点チェックリスト
datetime.utcnow()とdatetime.now()が混在していないか- 内部表現がUTC aware datetimeに統一されているか
- API入力でtimezoneなしdatetimeを拒否または明示変換しているか
replace(tzinfo=...)を時刻変換として使っていないか- DB保存・取得時のtimezone契約が説明されているか
- APIレスポンスにtimezone情報が含まれているか
- 現在時刻をテストで固定できる構造か
まとめ
Pythonのdatetimeレビューでは、datetime を使っているかではなく、時刻の基準が揃っているかを見る。
特に naiveとawareの混在、UTC変換の責務、境界での検証 は早めに確認したい。
時刻バグは、サーバー環境や日付境界で突然見える。
レビューでは、正常系の日付表示よりも、基準時刻の一貫性を優先して読む。