# Python標準ライブラリで時間を扱う:`datetime`完全攻略 > **シリーズ 03 – 日付/時間演算、タイムゾーン、フォーマット変換まで一挙に** 時間は単なる「文字列」ではありません。日が変わり、月が変わり、サマータイムが入り、地域ごとに基準が異なります。 そのため、日付/時間を扱う際は「表示用文字列」と「計算可能な時間値」を区別し、タイムゾーンまで含めて扱う習慣が重要です。 ![時計職人の魔法の作業室](/media/editor_temp/6/5843a4cb-f3a6-4d8d-9a13-89c1cbb0ef6c.png) この記事では `datetime` モジュールを中心に次を整理します。 * 現在時刻と日付の作成 * 期間計算(`timedelta`) * 文字列フォーマット/パース(`strftime`, `strptime`) * タイムゾーン処理(`timezone`, `zoneinfo`) * よく直面する **注意点** と安定したパターン --- ## 1. `datetime` は何を提供するか? {#sec-5f3cbdfb3d40} `datetime` には似ているようで役割が異なる型があります。 * `date` : 年-月-日だけが必要なとき * `time` : 時:分:秒だけが必要なとき * `datetime` : 日付+時間(最も頻繁に使用) * `timedelta` : 時間の「差」(期間) * `timezone` : 固定オフセットタイムゾーン(例:UTC+9のようにオフセットが変わらない場合) そして Python 3.9+ では標準ライブラリに **`zoneinfo`** が追加され、地域タイムゾーン(例:Asia/Tokyo)を扱いやすくなりました。 --- ## 2. 「今」を得る方法:naive vs aware から整理 {#sec-80466bd4f739} ### 2.1 naive / aware とは? {#sec-893b0793d467} `datetime` オブジェクトは大きく二つのカテゴリに分かれます。 * **naive datetime**: タイムゾーン情報がない * **aware datetime**: タイムゾーン情報(`tzinfo`)がある これらを混在させて演算/比較するとエラーが出ることもありますし、エラーが出なくても結果が意図と異なることがあります。 ### 2.2 推奨されるデフォルト:UTC aware で始める {#sec-f876fdec8f2b} 保存/計算基準を UTC に統一すると、コードがきれいになるケースが多いです。 ```python from datetime import datetime, timezone utc_now = datetime.now(timezone.utc) # aware (UTC) print(utc_now) ``` ローカル時刻が必要な場合は「表示段階」で変換します。 ```python from datetime import datetime, timezone from zoneinfo import ZoneInfo utc_now = datetime.now(timezone.utc) tokyo_now = utc_now.astimezone(ZoneInfo("Asia/Tokyo")) print(utc_now) print(tokyo_now) ``` --- ## 3. 日付/時間計算:`timedelta` が中心 {#sec-5497d843fd3e} ### 3.1 加算/減算 {#sec-0288780aa297} ```python from datetime import datetime, timedelta, timezone now = datetime.now(timezone.utc) print(now + timedelta(days=3)) print(now - timedelta(hours=2)) ``` ### 3.2 二つの時刻の差 {#sec-c781f333910b} ```python from datetime import datetime, timezone a = datetime(2026, 1, 1, tzinfo=timezone.utc) b = datetime(2026, 1, 10, tzinfo=timezone.utc) delta = b - a print(delta.days) # 9 print(delta.total_seconds()) # 777600.0 ``` ### 3.3 `timedelta` で「期間」を表現する感覚 {#sec-c81173d2a4f5} `timedelta` は **日/秒/マイクロ秒** 単位で動作します。 「一か月後」のように月の長さが変わる表現は `timedelta(days=30)` で大まかに合わせることはできますが、カレンダー基準の「次の月同じ日」は別のアプローチが必要です(この部分は標準ライブラリの範囲外で `dateutil` などがよく使われます)。 --- ## 4. フォーマットとパース:`strftime` / `strptime` {#sec-a17a8e259a48} ### 4.1 datetime → 文字列(`strftime`) {#sec-8d6f34025b2e} ```python from datetime import datetime, timezone dt = datetime(2026, 1, 30, 14, 5, 0, tzinfo=timezone.utc) print(dt.strftime("%Y-%m-%d %H:%M:%S %z")) ``` よく使うフォーマットパターン: * `%Y-%m-%d` : 2026-01-30 * `%H:%M:%S` : 14:05:00 * `%z` : +0000 (UTCオフセット) ### 4.2 文字列 → datetime(`strptime`) {#sec-f2bbc1672485} ```python from datetime import datetime s = "2026-01-30 14:05:00" dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S") print(dt) ``` ここで生成された `dt` は **naive** です。タイムゾーンを明確にしたい場合は `tzinfo` を付ける必要があります。 ```python from datetime import datetime, timezone s = "2026-01-30 14:05:00" dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) print(dt) ``` > `replace(tzinfo=...)` は「時間値を変換」するのではなく **その時刻にタイムゾーンラベルを付ける** 動作です。 > 変換が必要なら `astimezone()` を使用します(次節参照)。 --- ## 5. タイムゾーン:`timezone` と `zoneinfo` を分けて使う {#sec-05b1d0f7ef82} ### 5.1 固定オフセットなら `timezone` {#sec-67fec65074ab} 例:UTC、UTC+9 のようにオフセットが固定の場合 ```python from datetime import datetime, timezone, timedelta kst_fixed = timezone(timedelta(hours=9)) dt = datetime(2026, 1, 30, 12, 0, tzinfo=kst_fixed) print(dt) ``` ### 5.2 地域タイムゾーンなら `zoneinfo` {#sec-834b663bce83} サマータイムのようにオフセットが変わる地域は `zoneinfo` が適しています。 ```python from datetime import datetime, timezone from zoneinfo import ZoneInfo utc_now = datetime.now(timezone.utc) ny_now = utc_now.astimezone(ZoneInfo("America/New_York")) print(ny_now) ``` ### 5.3 `replace(tzinfo=...)` vs `astimezone(...)` の違い {#sec-35bf0fbaa9df} * `replace(tzinfo=...)`: **時刻値はそのまま**、タイムゾーンだけ「ラベリング」 * `astimezone(...)`: **同じ瞬間**を別のタイムゾーンの時刻に「変換」 この違いを知っておくと、タイムゾーン関連のバグを大幅に減らせます。 --- ## 6. よく直面する注意点まとめ {#sec-552ca3933648} ### 6.1 naive/aware 混用 {#sec-21a4c96b9022} * 内部計算用時刻は **aware(UTC)** に合わせると安定します。 * 外部入力は受け取った瞬間にタイムゾーンを決定し、統一するのが良いです。 ### 6.2 「ローカル時間」は環境によって変わることがある {#sec-e2bd08b413c3} `datetime.now()` は実行環境のローカル設定を参照します。コンテナ/サーバ環境ではローカルが UTC である場合もありますし、そうでない場合もあります。 環境差を減らすには `datetime.now(timezone.utc)` のような方法が望ましいです。 ### 6.3 文字列パースのフォーマット不一致 {#sec-e52b753858ac} `strptime` はフォーマットが 1 文字でも違うと失敗します。入力フォーマットが複数ある場合は「前処理」やフォーマット候補を段階的に試すコードが必要です(可能なら入力フォーマットを一つに限定する方が良いです)。 --- ## 7. よく使うパターン 3 つ {#sec-b244af674a6a} ### 7.1 ISO 8601 形式で保存する {#sec-4b1979b29466} 標準的な文字列表現が必要なときは `isoformat()` が便利です。 ```python from datetime import datetime, timezone dt = datetime.now(timezone.utc) print(dt.isoformat()) # 例: 2026-01-30T05:12:34.567890+00:00 ``` ### 7.2 「今日の日付」でファイル名を作る {#sec-ae13a5dac423} ```python from datetime import datetime stamp = datetime.now().strftime("%Y%m%d") filename = f"report_{stamp}.json" print(filename) ``` ### 7.3 特定時刻まで残り時間を計算する {#sec-b509d1188ff7} ```python from datetime import datetime, timezone target = datetime(2026, 2, 1, 0, 0, tzinfo=timezone.utc) now = datetime.now(timezone.utc) remaining = target - now print(remaining) print(remaining.total_seconds()) ``` --- ## 8. まとめ {#sec-9fa53305f95a} `datetime` は「日付文字列を作る」だけでなく、時間を **演算可能な値** として扱えるようにする核心ツールです。 タイムゾーン(`timezone`, `zoneinfo`) を含めることで、実行環境が変わっても結果が揺らぐことのないコードを作りやすくなります。 次回は `random` を別に取り上げ、乱数生成/サンプリング/シャッフル/シード、そしてセキュリティ用乱数(`secrets`)までまとめて解説します。 --- **関連記事 :** - [Djangoでdatetimeとtimezoneを正しく使う](/ko/whitedec/2025/11/10/django-datetime-timezone/) - [Djangoの時間管理マジック - `django.utils.timezone` 完全ガイド](/ko/whitedec/2025/11/14/django-utils-timezone-guide/) --- **前回シリーズを見る** - [[Python標準ライブラリ - 0] Python標準ライブラリとは?初心者向けガイド](/ko/whitedec/2026/1/29/python-standard-library/) - [[Python標準ライブラリ -1] ファイルシステム & OS環境マスター: pathlib vs os](/ko/whitedec/2026/1/29/file-system-os-environment-master-pathlib-vs-os/) - [[Python標準ライブラリ - 2] データ保存 & シリアライズ: json, pickle, csv](/ko/whitedec/2026/1/30/python-json-pickle-csv/)