- Python
offset-naive and offset-awareの直し方|Python
TypeError: can't compare offset-naive and offset-aware datetimes は、タイムゾーンの情報が無い日時(naive)と、ある日時(aware)を、<・> で比べたときのエラーです。 引き算では can't subtract offset-naive and offset-aware datetimes になります。
| 種類 | 例 | tzinfo |
|---|---|---|
| naive(タイムゾーンなし) | datetime.now()、datetime(2026, 10, 11, 9, 0) |
None |
| aware(タイムゾーンあり) | datetime.now(timezone.utc)、PostgreSQL の timestamptz |
UTC・Asia/Tokyo など |
直し方は、どちらかにそろえることです。システム全体を aware(タイムゾーンあり)にそろえるのが安全です。
from datetime import datetime, timezone
now = datetime.now(timezone.utc) # datetime.now() ではなく、タイムゾーン付きで
確認環境:Python 3.10・3.11・3.12、psycopg 3.3.6(PostgreSQL 16)、PyMySQL(MySQL 8.0)、pandas 3.0。OS のタイムゾーンは日本時間(確認日 2026年10月11日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「offset-naive and offset-awareの直し方|Python」
再現:比べ方によって、エラーになる・ならない
naive = datetime(2026, 10, 11, 9, 0)
aware = datetime(2026, 10, 11, 9, 0, tzinfo=ZoneInfo('Asia/Tokyo'))
| 操作 | 結果(確認環境) |
|---|---|
naive < aware |
TypeError: can't compare offset-naive and offset-aware datetimes |
aware - naive |
TypeError: can't subtract offset-naive and offset-aware datetimes |
sorted([aware, naive])・max(naive, aware) |
TypeError(中で比べるため) |
naive == aware |
False(エラーにならない) |
== だけはエラーにならず、常に False になりました(公式ドキュメントでも、naive と aware は決して等しくならないとされています)。「同じ時刻のはずなのに一致しない」という不具合は、これが原因のことがあります。
どこから naive・aware が来るか
| 作り方 | 結果 |
|---|---|
datetime.now() |
naive |
datetime.now(timezone.utc) |
aware(UTC) |
datetime.utcnow() |
naive(UTC の時刻だが、タイムゾーンは付かない) |
datetime.fromisoformat('2026-10-11T00:00:00Z') |
aware(3.11 以降。3.10 では Invalid isoformat string) |
datetime.fromisoformat('2026-10-11T00:00:00') |
naive |
strptime(..., '%Y-%m-%d %H:%M:%S%z') |
aware |
strptime(..., '%Y-%m-%d %H:%M:%S') |
naive |
datetime.utcnow() は、UTC の時刻を返しますが、タイムゾーンは付きません。 Python 3.12 では DeprecationWarning: datetime.datetime.utcnow() is deprecated の警告が出ました。公式ドキュメントでも、代わりに datetime.now(timezone.utc) を使うよう書かれています。
DB のドライバーで、返ってくる日時が違う
| DB・型 | Python に返る値(確認環境) |
|---|---|
PostgreSQL の timestamptz(psycopg) |
aware(接続の TimeZone の時刻。Etc/UTC なら UTC、Asia/Tokyo なら +09:00) |
PostgreSQL の timestamp |
naive |
MySQL の DATETIME・TIMESTAMP(PyMySQL) |
naive(どちらも) |
row_time = conn.execute("SELECT now()").fetchone()[0] # PostgreSQL:aware
row_time < datetime.now() # naive と比べる
# TypeError: can't compare offset-naive and offset-aware datetimes
PostgreSQL の timestamptz の値と datetime.now() を比べると、このエラーになりました。 MySQL から PostgreSQL に移したとき、比べる側のコードはそのままで、DB の値だけ aware に変わって出る、という形です。
MySQL では、エラーにならずに9時間ずれた
MySQL の NOW()(サーバーは UTC) : 2026-10-10 17:45:28 ← naive
Python の datetime.now()(日本時間): 2026-10-11 02:45:28 ← naive
差:9.0 時間
どちらも naive なのでエラーにはなりませんが、DB のサーバーは UTC、Python は日本時間で、同じ瞬間が9時間ずれた値として比べられました。 エラーになる PostgreSQL のほうが、まだ気づきやすいと言えます。日時が9時間ずれる問題の全体は、関連記事の「日時が9時間ずれる原因と直し方|Java・JS・MySQL・PG・Docker」で扱っています。
直し方:replace と astimezone の違い
naive を aware にするとき、replace と astimezone で結果が違います。
naive = datetime(2026, 10, 11, 9, 0) # 9時(どこの9時かは不明)
| 書き方 | 結果(確認環境) | 意味 |
|---|---|---|
naive.replace(tzinfo=timezone.utc) |
2026-10-11T09:00:00+00:00 |
時刻はそのまま、UTC の印を付ける |
naive.replace(tzinfo=ZoneInfo('Asia/Tokyo')) |
2026-10-11T09:00:00+09:00 |
時刻はそのまま、日本時間の印を付ける |
naive.astimezone(timezone.utc) |
2026-10-11T00:00:00+00:00 |
naive を「OS の時刻(日本時間)」とみなして、UTC に変換 |
astimezone は、naive の値を OS のタイムゾーンの時刻とみなします(公式ドキュメント)。同じコードでも、日本時間のパソコンと UTC のサーバーで結果が変わります。値がどのタイムゾーンの時刻か分かっているなら、replace(tzinfo=...) で印を付けます。
| 状況 | 書き方 |
|---|---|
naive の値が UTC の時刻(utcnow()・UTC のサーバーの DB) |
replace(tzinfo=timezone.utc) |
| naive の値が日本時間の時刻 | replace(tzinfo=ZoneInfo('Asia/Tokyo')) |
| aware を別のタイムゾーンで表したい | astimezone(ZoneInfo('Asia/Tokyo')) |
| aware から印を外したい(naive しか扱えない処理に渡す) | replace(tzinfo=None)(時刻はそのまま) |
PostgreSQL の接続の TimeZone
conn.execute("SET TimeZone = 'Asia/Tokyo'")
conn.execute("SELECT now()").fetchone()[0] # 2026-10-11T02:45:28+09:00
timestamptz の値は、接続の TimeZone の時刻で返ってきました(同じ瞬間を、+09:00 で表した値)。どちらで返っても aware なので、比べる・引き算する分には問題ありません。
pandas の場合
pd.Timestamp('2026-10-11 09:00') < pd.Timestamp('2026-10-11 09:00', tz='Asia/Tokyo')
TypeError: Cannot compare tz-naive and tz-aware timestamps
df[df['at'] > datetime(2026, 10, 11, 12, 0)] # at はタイムゾーン付きの列
TypeError: Invalid comparison between dtype=datetime64[us, Asia/Tokyo] and datetime
pandas では、メッセージの文が違います。 比べる値にもタイムゾーンを付けます。
df[df['at'] > pd.Timestamp('2026-10-11 12:00', tz='Asia/Tokyo')]
pd.Timestamp('2026-10-11 09:00').tz_localize('Asia/Tokyo').tz_convert('UTC') # 2026-10-11 00:00:00+00:00
pandas の tz_localize は replace(tzinfo=...)、tz_convert は astimezone に当たります。
確認の手順(チェックリスト)
| 順番 | 確認すること | 方法 |
|---|---|---|
| 1 | どちらが naive か | print(x.tzinfo, y.tzinfo)(None が naive) |
| 2 | naive はどこから来たか | datetime.now()・utcnow()・MySQL・タイムゾーンの無い文字列 |
| 3 | その naive は、どこの時刻か | UTC か日本時間かを決める |
| 4 | 印を付ける | replace(tzinfo=...)(astimezone は OS の時刻とみなす) |
| 5 | == で比べていないか |
naive と aware は常に False |
| 6 | 今後 | datetime.now(timezone.utc) で統一 |
よくある質問
Q. 全部 naive にそろえてもよいですか。 A. エラーは消えますが、上の MySQL の例のように、どこの時刻かが分からない値どうしを比べて、黙ってずれる危険があります。保存・計算は UTC の aware、画面に出すときだけ日本時間に変換、とそろえるのが安全です。
まとめ
- タイムゾーンなし(naive)とあり(aware)を、比べた・引いたエラー
==だけはエラーにならず、常に False- PostgreSQL の timestamptz は aware、MySQL の DATETIME は naive で返る
replaceは印を付けるだけ、astimezoneは naive を OS の時刻とみなして変換utcnow()は naive で、3.12 から非推奨。datetime.now(timezone.utc)に- pandas は
Cannot compare tz-naive and tz-aware。tz_localize・tz_convert
参考・出典
確認日はいずれも 2026年10月11日です。
- Python ドキュメント「datetime — Basic date and time types」:https://docs.python.org/3/library/datetime.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「offset-naive and offset-awareの直し方|Python」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。