閉じる

Pythonのdatetimeにタイムゾーンを付与(zoneinfo)の使い方

Pythonで日付や時刻を扱う際、タイムゾーンを正しく付与すると、国や地域をまたぐデータ処理でも矛盾が起きにくくなります。

Python 3.9以降では標準ライブラリのzoneinfoが利用でき、外部ライブラリなしでIANAタイムゾーンに対応できます。

本記事では、現在時刻の取得から既存のdatetimeへの付与、変換、実務の注意点までを丁寧に解説します

zoneinfoとは(Python 3.9+)

zoneinfoは、IANA(Time Zone Database)に基づくタイムゾーン情報を標準ライブラリとして提供するモジュールです

Python 3.9以降で使え、from zoneinfo import ZoneInfoとしてインポートし、datetimetzinfoに渡して使います。

pytzのような外部ライブラリを使わずに済むため、依存関係が軽くなります。

なお、Windowsなど一部環境ではIANAデータがない場合があり、その際はpipでtzdataをインストールすると使えるようになります。

タイムゾーン付きdatetime(aware)の基本

タイムゾーン付きのdatetimeは「aware」と呼ばれ、タイムゾーンのオフセット(+09:00や+00:00など)を内部に持ちます

演算や表示時にそのオフセットが考慮され、時差を含めた正しい処理が可能です。

作成時にZoneInfoを渡すのが最も確実です。

Python
# 基本: タイムゾーン付き(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
Windows環境での注意点

IANA タイムゾーンデータベースに依存していますが、Windows 環境ではそれが標準で入っていないことが多いです。そのためエラーメッセージが出る場合は tzdata が必要になります。

一番手っ取り早いのは、PyPI の tzdata パッケージを入れることです。

Shell
pip install tzdata

インストール後は、先ほどのコードがそのまま動くはずです。

naiveなdatetimeとの違い

naiveなdatetimetzinfoNoneで、どのタイムゾーンの時刻かがわかりません

そのため、システムローカルのタイムゾーンに依存した誤差や不具合が起こりやすく、特に国際化されたアプリや分散システムで問題になります。

Python
# 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)
tzinfoNoneZoneInfoなどの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()を使うと自動付与されます。

Python
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は世界共通の基準時刻です。

サーバーサイドの保存や比較に最適です。

Python
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=...)は「変換」ではなく「ラベル付け」です。

指定したタイムゾーンとして解釈するだけで、時刻はズレません。

もともとどのタイムゾーンの時刻なのかがわかっている場合にのみ使います。

Python
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を使います

これは瞬間を保ったまま表示上の時刻とオフセットを合わせます。

Python
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に変換します。

Python
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で保存し、画面やレポートではユーザーのローカルタイムゾーンに変換して表示します。

これにより、比較や集計がシンプルになり、国またぎでも矛盾が起きにくくなります。

Python
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の切り替えに伴うオフセットの変化を自動で扱います

同一の瞬間を別タイムゾーンへ変換すると、適切なESTEDTが選ばれます。

Python
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が必要になることがあります。

Shell
pip install tzdata

この記事の最初のサンプルを動かすために導入している人は大丈夫ですが、tzdataを導入していないWindowsユーザーは入れておくべきです。

実際の開発での注意

パッケージの導入で解決しているため、venvで仮想環境を構築する場合は、パッケージの導入漏れに気をつけてください。

Python
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が利用できます。

Shell
pip install backports.zoneinfo
Python
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表記にするとより明確です。

Python
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の導入を検討してください。

これらを押さえれば、国や地域をまたぐシステムでも時刻の扱いで困ることが大幅に減ります。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!