Pythonで日付や時刻を扱う際、タイムゾーンを正しく付与すると、国や地域をまたぐデータ処理でも矛盾が起きにくくなります。
Python 3.9以降では標準ライブラリのzoneinfo
が利用でき、外部ライブラリなしでIANAタイムゾーンに対応できます。
本記事では、現在時刻の取得から既存のdatetime
への付与、変換、実務の注意点までを丁寧に解説します。
zoneinfoとは(Python 3.9+)
zoneinfoは、IANA(Time Zone Database)に基づくタイムゾーン情報を標準ライブラリとして提供するモジュールです。
Python 3.9以降で使え、from zoneinfo import ZoneInfo
としてインポートし、datetime
のtzinfo
に渡して使います。
pytzのような外部ライブラリを使わずに済むため、依存関係が軽くなります。
なお、Windowsなど一部環境ではIANAデータがない場合があり、その際はpipでtzdata
をインストールすると使えるようになります。
タイムゾーン付きdatetime(aware)の基本
タイムゾーン付きのdatetime
は「aware」と呼ばれ、タイムゾーンのオフセット(+09:00や+00:00など)を内部に持ちます。
演算や表示時にそのオフセットが考慮され、時差を含めた正しい処理が可能です。
作成時にZoneInfo
を渡すのが最も確実です。
# 基本: タイムゾーン付き(aware)なdatetimeを作る
from datetime import datetime
from zoneinfo import ZoneInfo
# Asia/Tokyo(日本時間)で 2023-05-01 09:30 を表す aware datetime
dt_aware = datetime(2023, 5, 1, 9, 30, tzinfo=ZoneInfo("Asia/Tokyo"))
print(dt_aware) # 文字列表現にオフセット(+09:00)が含まれる
print(dt_aware.tzname()) # タイムゾーン名(JST)などが取得できる
2023-05-01 09:30:00+09:00
JST
IANA タイムゾーンデータベースに依存していますが、Windows 環境ではそれが標準で入っていないことが多いです。そのためエラーメッセージが出る場合は tzdata
が必要になります。
一番手っ取り早いのは、PyPI の tzdata
パッケージを入れることです。
pip install tzdata
インストール後は、先ほどのコードがそのまま動くはずです。
naiveなdatetimeとの違い
naiveなdatetime
はtzinfo
がNone
で、どのタイムゾーンの時刻かがわかりません。
そのため、システムローカルのタイムゾーンに依存した誤差や不具合が起こりやすく、特に国際化されたアプリや分散システムで問題になります。
# naive(タイムゾーンなし)とaware(タイムゾーンあり)の違い
from datetime import datetime
from zoneinfo import ZoneInfo
dt_naive = datetime(2023, 5, 1, 9, 30) # tzinfo=None
dt_aware = datetime(2023, 5, 1, 9, 30, tzinfo=ZoneInfo("Asia/Tokyo"))
print(dt_naive, dt_naive.tzinfo)
print(dt_aware, dt_aware.tzinfo)
2023-05-01 09:30:00 None
2023-05-01 09:30:00+09:00 Asia/Tokyo
以下の表は違いの要点です。
項目 | naive(datetime) | aware(datetime) |
---|---|---|
tzinfo | None | ZoneInfoなどのtzinfoを保持 |
時差考慮 | なし | あり |
表示のオフセット | 付かない | 付く(+09:00など) |
時刻の一意性 | 不明確 | 一意に特定可能 |
利用推奨 | 一時的/内部用途 | 実運用・保管・通信 |
実務では可能な限りawareを使い、必要に応じて変換するのが安全です。
使うタイムゾーン名の例(Asia/Tokyo, UTC)
IANAのタイムゾーン名は地域/都市
の形式で指定します。
よく使う例を挙げます。
名称 | 説明 | 典型のオフセット |
---|---|---|
UTC | 協定世界時 | +00:00 |
Asia/Tokyo | 日本標準時(JST) | +09:00 |
文字列は厳密一致が必要です。
例えば"Asia/Tokyo"
は正しいですが、“Asia/Tokyoo”のような綴りミスはエラーになります。
現在時刻にタイムゾーンを付与する
現在時刻は、用途に応じて「ローカルの現在時刻」か「UTCの現在時刻」を作り分けます。
サーバー間連携や保存にはUTC、画面表示にはユーザーのローカルが一般的です。
datetime.now(ZoneInfo)でローカルタイムを得る
地域を明示できる場合はZoneInfo("Asia/Tokyo")
などを指定します。
システムのローカルタイムゾーンに合わせたい場合は.astimezone()
を使うと自動付与されます。
from datetime import datetime
from zoneinfo import ZoneInfo
# 1) 地域を明示する(例: 東京)
tokyo_now = datetime.now(ZoneInfo("Asia/Tokyo"))
# 2) システムのローカルタイムゾーンを自動付与
local_now = datetime.now().astimezone() # tz未指定で現在時刻→ローカルTZ付与
print("Tokyo:", tokyo_now.isoformat())
print("Local:", local_now.isoformat(), local_now.tzinfo)
Tokyo: 2024-08-15T10:23:45.678901+09:00
Local: 2024-08-15T10:23:45.680000+09:00 Asia/Tokyo
ローカルタイムは環境依存のため、ログや保存用途ではなるべくUTCに正規化して扱うのが安全です。
UTCの現在時刻を作る(ZoneInfo(‘UTC’))
UTCは世界共通の基準時刻です。
サーバーサイドの保存や比較に最適です。
from datetime import datetime
from zoneinfo import ZoneInfo
utc_now = datetime.now(ZoneInfo("UTC"))
print(utc_now.isoformat(), utc_now.tzname())
2024-08-15T01:23:45.678901+00:00 UTC
既存のdatetimeにタイムゾーンを付与(zoneinfo)
すでにあるdatetime
にタイムゾーンを「貼る」のか、「別のタイムゾーンに変換」するのかで使い方が異なります。
混同しないことが重要です。
replace(tzinfo=…)でラベル付け(変換なし)
replace(tzinfo=...)
は「変換」ではなく「ラベル付け」です。
指定したタイムゾーンとして解釈するだけで、時刻はズレません。
もともとどのタイムゾーンの時刻なのかがわかっている場合にのみ使います。
from datetime import datetime
from zoneinfo import ZoneInfo
# もともと「東京の 2023-03-01 09:00」を表していたnaive時刻だとわかっている想定
dt_naive = datetime(2023, 3, 1, 9, 0, 0)
# 東京時間としてラベル付け(時刻は変わらない)
dt_tokyo = dt_naive.replace(tzinfo=ZoneInfo("Asia/Tokyo"))
# UTCへ「変換」して確認
dt_utc = dt_tokyo.astimezone(ZoneInfo("UTC"))
print("naive: ", dt_naive) # tzなし
print("tokyo: ", dt_tokyo) # +09:00が付くが、時刻は 09:00 のまま
print("to UTC:", dt_utc) # 00:00+00:00 に変換される
naive: 2023-03-01 09:00:00
tokyo: 2023-03-01 09:00:00+09:00
to UTC: 2023-03-01 00:00:00+00:00
元の時刻がどのタイムゾーンかわからない状態でreplace
を使うと、意味の異なる時刻を作ってしまう可能性があります。
astimezone(…)で別タイムゾーンへ変換
別の地域の同一瞬間を表す時刻にしたい場合はastimezone
を使います。
これは瞬間を保ったまま表示上の時刻とオフセットを合わせます。
from datetime import datetime
from zoneinfo import ZoneInfo
dt_tokyo = datetime(2023, 12, 1, 12, 0, tzinfo=ZoneInfo("Asia/Tokyo"))
dt_ny = dt_tokyo.astimezone(ZoneInfo("America/New_York"))
print("Tokyo:", dt_tokyo.isoformat())
print("NY: ", dt_ny.isoformat(), dt_ny.tzname())
Tokyo: 2023-12-01T12:00:00+09:00
NY: 2023-11-30T22:00:00-05:00 EST
ローカルタイムからUTCへ変換する
ローカルのawareなdatetime
からUTC
へはastimezone(ZoneInfo("UTC"))
で変換します。
もしnaiveなら、まずローカルタイムゾーンを付与してからUTCに変換します。
from datetime import datetime
from zoneinfo import ZoneInfo
# 1) すでにローカルTZが付いた現在時刻 → UTCへ
local_now = datetime.now().astimezone()
to_utc = local_now.astimezone(ZoneInfo("UTC"))
print("local_now:", local_now.isoformat())
print("to_utc :", to_utc.isoformat())
# 2) naiveな「東京の時刻」とわかっているケース → 東京を付与 → UTCへ
dt_naive_tokyo = datetime(2023, 7, 1, 12, 0, 0) # これが東京の時刻だと分かっている前提
dt_tokyo = dt_naive_tokyo.replace(tzinfo=ZoneInfo("Asia/Tokyo"))
dt_utc = dt_tokyo.astimezone(ZoneInfo("UTC"))
print("tokyo :", dt_tokyo.isoformat())
print("utc :", dt_utc.isoformat())
local_now: 2025-09-15T15:24:05.077697+09:00
to_utc : 2025-09-15T06:24:05.077697+00:00
tokyo : 2023-07-01T12:00:00+09:00
utc : 2023-07-01T03:00:00+00:00
実務でのコツと注意点
実務では「保存はUTC」「表示はローカル」という原則を守ると、時刻の一意性とユーザー体験の両立がしやすくなります。
加えて、夏時間や無効なタイムゾーン名などの例外処理も意識します。
保存はUTC、表示はローカルタイム
APIやデータベースにはUTCで保存し、画面やレポートではユーザーのローカルタイムゾーンに変換して表示します。
これにより、比較や集計がシンプルになり、国またぎでも矛盾が起きにくくなります。
from datetime import datetime
from zoneinfo import ZoneInfo
# 受信: クライアントのローカル時刻(例: Asia/Tokyo) → UTCで保存
received_local = datetime(2024, 10, 1, 9, 0, tzinfo=ZoneInfo("Asia/Tokyo"))
store_utc = received_local.astimezone(ZoneInfo("UTC"))
print("save(UTC):", store_utc.isoformat())
# 表示: 保存していたUTC → ユーザーがNYならNY時間に変換して表示
display_for_ny = store_utc.astimezone(ZoneInfo("America/New_York"))
print("display (NY):", display_for_ny.isoformat(), display_for_ny.tzname())
save(UTC): 2024-10-01T00:00:00+00:00
display (NY): 2024-09-30T20:00:00-04:00 EDT
夏時間(DST)はzoneinfoが自動対応
zoneinfoはDSTの切り替えに伴うオフセットの変化を自動で扱います。
同一の瞬間を別タイムゾーンへ変換すると、適切なEST
やEDT
が選ばれます。
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
# 2023-03-12はNYで夏時間開始(02:00がスキップ)
dt_utc1 = datetime(2023, 3, 12, 6, 30, tzinfo=ZoneInfo("UTC")) # 切替前
dt_utc2 = dt_utc1 + timedelta(hours=2) # 2時間後(切替後)
ny1 = dt_utc1.astimezone(ZoneInfo("America/New_York"))
ny2 = dt_utc2.astimezone(ZoneInfo("America/New_York"))
print("UTC1:", dt_utc1.isoformat(), "-> NY:", ny1.isoformat(), ny1.tzname())
print("UTC2:", dt_utc2.isoformat(), "-> NY:", ny2.isoformat(), ny2.tzname())
UTC1: 2023-03-12T06:30:00+00:00 -> NY: 2023-03-12T01:30:00-05:00 EST
UTC2: 2023-03-12T08:30:00+00:00 -> NY: 2023-03-12T04:30:00-04:00 EDT
非存在時刻(スキップ)や曖昧時刻(1日内で同じローカル時刻が2回現れる)も、UTC基準で扱えば安全です。
内部処理はUTC、最後に表示用に変換が鉄則です。
タイムゾーン名が不正な場合の対処法
存在しないタイムゾーン名を指定するとZoneInfoNotFoundError
が発生します。
入力値が外部由来なら例外処理でフォールバックしましょう。
また、WindowsなどでIANAデータがない場合はpip install tzdataが必要になることがあります。
pip install tzdata
この記事の最初のサンプルを動かすために導入している人は大丈夫ですが、tzdata
を導入していないWindowsユーザーは入れておくべきです。
パッケージの導入で解決しているため、venvで仮想環境を構築する場合は、パッケージの導入漏れに気をつけてください。
from datetime import datetime
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
def safe_zone(name: str, fallback="UTC"):
try:
return ZoneInfo(name)
except ZoneInfoNotFoundError:
print(f"[warn] timezone '{name}' not found. fallback to {fallback}")
return ZoneInfo(fallback)
dt = datetime(2024, 1, 1, 12, 0, 0, tzinfo=safe_zone("Asia/Tokyoo")) # 綴りミス
print(dt.isoformat(), dt.tzinfo)
[warn] timezone 'Asia/Tokyoo' not found. fallback to UTC
2024-01-01T12:00:00+00:00 UTC
Python 3.8以下ではbackports.zoneinfo
を使うことで似たAPIが利用できます。
pip install backports.zoneinfo
from datetime import datetime
try:
# Python 3.9以降
from zoneinfo import ZoneInfo
except ImportError:
# Python 3.8以下
from backports.zoneinfo import ZoneInfo
# 東京時間の現在時刻を取得
dt_tokyo = datetime.now(ZoneInfo("Asia/Tokyo"))
print("Tokyo:", dt_tokyo.isoformat())
# UTC時間の現在時刻を取得
dt_utc = datetime.now(ZoneInfo("UTC"))
print("UTC:", dt_utc.isoformat())
# 任意の日付を特定のタイムゾーンで作成
dt_ny = datetime(2024, 1, 1, 12, 0, 0, tzinfo=ZoneInfo("America/New_York"))
print("New York:", dt_ny.isoformat())
backports.zoneinfo
を導入すれば、標準のZoneInfo
と同じように使えるようになります。
ISO形式で文字列化(isoformat)
ISO 8601形式は相互運用性が高く、通信や保存に適しています。
datetime.isoformat()
で生成できます。
UTCはZ
表記にするとより明確です。
from datetime import datetime
from zoneinfo import ZoneInfo
dt_tokyo = datetime(2024, 4, 5, 14, 23, 45, 123456, tzinfo=ZoneInfo("Asia/Tokyo"))
print("Tokyo:", dt_tokyo.isoformat())
dt_utc = dt_tokyo.astimezone(ZoneInfo("UTC"))
print("UTC :", dt_utc.isoformat())
# Z表記にしたい場合(マイクロ秒を落としつつ)
dt_utc_z = dt_utc.replace(microsecond=0).isoformat().replace("+00:00", "Z")
print("UTC(Z):", dt_utc_z)
# 小数部桁数を制御したい場合(timespec)
print("seconds:", dt_tokyo.isoformat(timespec="seconds"))
Tokyo: 2024-04-05T14:23:45.123456+09:00
UTC : 2024-04-05T05:23:45.123456+00:00
UTC(Z): 2024-04-05T05:23:45Z
seconds: 2024-04-05T14:23:45+09:00
ISO 8601ではタイムゾーンはオフセット(+09:00等)で表すのが一般的で、UTCはZ
を使うケースも多いです。
まとめ
Python 3.9+のzoneinfo
を使えば、標準ライブラリだけで正確なタイムゾーン処理が可能になります。
ポイントは次の通りです。
まず、現在時刻は用途に応じてローカルかUTCかを選択し、既存のdatetime
に対するreplace(tzinfo=...)
はラベル付け、astimezone
は変換だと理解します。
実務では保存はUTC・表示はローカルを徹底し、DSTや不正なタイムゾーン名へのフォールバック、ISO 8601での文字列化を組み合わせると安全です。
最後に、Windowsでエラーが出る場合はtzdata
の導入を検討してください。
これらを押さえれば、国や地域をまたぐシステムでも時刻の扱いで困ることが大幅に減ります。