はじめに
Pythonで日時を扱う際は、タイムゾーンやISO形式について理解していないと、思わぬ不具合や誤解につながることがあります。私自身、毎回datetimeを調べ直している気がしたので、備忘録として考え方とコード例をまとめました。
datetimeモジュールとは
まずはdatetimeモジュールとは何か、公式ドキュメントを改めて確認してみます。
https://docs.python.org/ja/3.13/library/datetime.html
datetime モジュールは、日付や時刻を操作するためのクラスを提供しています。
日付や時刻に対する算術がサポートされている一方、実装では出力のフォーマットや操作のための効率的な属性の抽出に重点を置いています。
つまり、datetimeモジュールは単に日付や時間を表現したり計算するだけでなく、日時データのフォーマットや操作を効率的に扱うための仕組みに重点が置かれたモジュールです。
ただし、仕様が広く、日常的には使わない機能も多いので、私がよく使うところだけに絞って整理しています。
datetimeモジュールを思い出す
Pythonのdatetimeは、日付と時刻の基本的な構造をそのままクラスとして持っています。日付だけならdate、時刻だけならtime、両方を一度に扱うならdatetimeといった具合です。また時間差を表すならtimedeltaを使います。
そして、いつも重要になるのがnaiveとawareの違いです。
たとえばdatetime(2025, 12, 3, 12, 0) のようにタイムゾーン情報を持たないものはnaiveな値です。これでは「どこの地域の12時か」がわかりません。
一方、ZoneInfo("UTC") のようにタイムゾーン情報を持つものはawareとなり、UTCとの対応関係が明確になります。
この2つを混ぜて扱うと大抵バグの原因になるため、私は「内部で扱う時間は必ずawareにする」ようにしています。
タイムゾーンは固定オフセットよりZoneInfoを使う
以前はtimezone(timedelta(hours=9))のような固定オフセットを使うことも多かったようですが、Python3.9以降はzoneinfoが標準で利用できます。
from datetime import datetime
from zoneinfo import ZoneInfo
dt_tokyo = datetime(2025, 12, 3, 10, 30, tzinfo=ZoneInfo("Asia/Tokyo"))
print(dt_tokyo)
print(dt_tokyo.tzname())
ZoneInfo("Asia/Tokyo")のように地域名を指定できるため、サマータイム(DST)の有無や、国・地域の過去の標準時変更なども正しく反映されます。
固定オフセットとの最大の違いは、歴史的な変更を知っているかどうかという点です。
「UTC+09:00なら東京だろう」と考えがちですが、同じオフセットの地域はいくつもありますし、日本も昔はサマータイムを使っていた時期があります。正確に扱いたい場合は、地域名ベースで記録しておく方が安全です。
参考:
https://docs.python.org/ja/3.13/library/zoneinfo.html
ISO8601の形式は揺れがあるため要注意
文字列で日時をやり取りする場合、ISO8601が一般的で、外部システムとの連携でもよく使われます。Pythonではisoformat()とfromisoformat()によってdatetimeとの相互変換が可能です。
from datetime import datetime
dt = datetime.fromisoformat("2025-12-03T12:34:56+09:00")
from datetime import datetime
from zoneinfo import ZoneInfo
dt_utc = datetime.now(ZoneInfo("UTC"))
s = dt_utc.isoformat()
しかし、ISO8601は複数の書式が存在し、Pythonがすべてを受け入れるわけではありません。
- 拡張形式(例:2025-12-03T12:30:31+09:00)
- 基本形式(例:20251203T123031+0900)
- UTCをZ表記する形式(例:2025-12-03T12:30:31Z)
さらに、Pythonのバージョンによって許容範囲が微妙に異なることもあります。
そのため、外部システムと日時文字列をやり取りする場合は、プロジェクトとして使用するフォーマットを統一しておくことが最も確実です。
参考:
https://note.nkmk.me/python-datetime-isoformat-fromisoformat/
UTCと地域タイムゾーンの使い分け
私は、内部で扱うデータやログはすべてUTCに統一するようにしています。保存も計算もUTCで行えば、不要な変換ミスを避けられます。
そのうえで、ユーザーに表示する際や通知を出す際など、人が読む場面だけzoneinfoで地域のタイムゾーンに変換しています。
カレンダー的な計算はtimedeltaだけでは足りないことがある
「1か月後」「次の月末」のようなカレンダーを意識した計算は、日数ベースのtimedeltaでは正確に扱えない場合があります。月によって日数が異なるため、「30日後=1か月後」にはなりません。
こうした処理が必要な場合、標準機能だけで頑張ろうとせず、dateutil.relativedeltaなどの専用ライブラリを使った方がよい場合もあります。
参考(公式ドキュメント):
https://github.com/dateutil/dateutil/blob/db9d018944c41ddc740015cf5f64717c2ba64a5c/docs/relativedelta.rst
「よく使う書き方」の簡易メモ
from datetime import datetime
from zoneinfo import ZoneInfo
now_utc = datetime.now(ZoneInfo("UTC"))
dt_jst = now_utc.astimezone(ZoneInfo("Asia/Tokyo"))
# UTCタイムゾーンを指定してdatetimeオブジェクトを作成
dt_aware = datetime(2025, 12, 3, 12, 0, tzinfo=ZoneInfo("UTC"))
iso_str = dt_aware.isoformat()
dt = datetime.fromisoformat("2025-11-26T21:34:56+09:00")
まとめ
今回、datetimeを改めて整理したことで、サマータイムによる時刻の重複など、本記事には書ききれなかったトピックも多くあり、学びが深まりました。必要になったら、また追記していこうと思います。
参考文献/引用元
-
Python Documentation — datetime
https://docs.python.org/ja/3.13/library/datetime.html -
Python Documentation — zoneinfo
https://docs.python.org/ja/3.13/library/zoneinfo.html -
python-dateutil — relativedelta
https://github.com/dateutil/dateutil/blob/db9d018944c41ddc740015cf5f64717c2ba64a5c/docs/relativedelta.rst -
ISO8601 と Python の datetime に関する解説(@nkmk)
https://note.nkmk.me/python-datetime-isoformat-fromisoformat/