Tutorial

Python でのタイムゾーンの操作: 完全ガイド

はじめに

Python でのタイムゾーンの処理は難しい場合がありますが、異なるタイムゾーンにまたがって動作する堅牢なアプリケーションを構築するには不可欠です。このチュートリアルでは、基本概念から高度なテクニックまで、Python でのタイムゾーンの操作について知っておく必要があるすべてを説明します。

タイムゾーンの処理が重要な理由

# ❌ BAD: Naive datetime (no timezone info)
from datetime import datetime
now = datetime.now()  # Which timezone is this?

# ✅ GOOD: Timezone-aware datetime
from datetime import datetime, timezone
now = datetime.now(timezone.utc)  # Clear: UTC time

単純な日時に関する一般的な問題:

  • DST 移行中のあいまいな時間
  • タイムゾーンをまたいだ不正確な計算
  • 分散システムでのデータ破損
  • デバッグが難しいタイムゾーンのバグ

Python タイムゾーン ライブラリの概要

1. 日時 (組み込み)

Python 3.2 以降には、基本的なタイムゾーンのサポートが含まれています。

from datetime import datetime, timezone, timedelta

# UTC timezone
utc_now = datetime.now(timezone.utc)
print(utc_now)  # 2025-01-15 10:30:00+00:00

# Fixed offset timezone
est = timezone(timedelta(hours=-5))
est_now = datetime.now(est)
print(est_now)  # 2025-01-15 05:30:00-05:00

長所:

  • 内蔵されており、インストールは必要ありません
  • UTC と固定オフセットのシンプルな

短所:

  • 名前付きタイムゾーンはサポートされません (例: "America/New_York")
  • DST を自動的に処理できない
  • 制限された機能

2. zoneinfo (組み込み、Python 3.9 以降)

Python 3.9 以降を推奨

from datetime import datetime
from zoneinfo import ZoneInfo

# Named timezone support
ny_time = datetime.now(ZoneInfo("America/New_York"))
tokyo_time = datetime.now(ZoneInfo("Asia/Tokyo"))

print(f"New York: {ny_time}")
print(f"Tokyo: {tokyo_time}")

長所:

  • 組み込み (Python 3.9 以降)
  • IANA タイムゾーン データベースのサポート
  • 自動 DST 処理
  • タイプセーフでモダン

短所:

  • Python 3.9以降でのみ利用可能
  • システムのタイムゾーン データ (または tzdata パッケージ) が必要です

3. pytz (サードパーティ)

Python 3.9 未満または最大の互換性に最適

import pytz
from datetime import datetime

# Create timezone-aware datetime
utc = pytz.UTC
eastern = pytz.timezone('US/Eastern')

# Current time in timezone
ny_time = datetime.now(eastern)
print(ny_time)

長所:

  • Python 2.7以降で動作します
  • 包括的なタイムゾーンデータベース
  • 十分にテストされており安定しています

短所:

  • インストールが必要です (「pip install pytz」)
  • もう少し複雑な API -zoneinfo に取って代わられる

4. dateutil (サードパーティ)

from dateutil import tz
from datetime import datetime

# Get timezone
eastern = tz.gettz('America/New_York')
utc = tz.UTC

# Create datetime
dt = datetime.now(eastern)

長所:

  • 強力な日付解析
  • 簡単なタイムゾーン変換
  • ローカルタイムゾーンで動作します

短所:

  • より大きな依存関係
  • 単純なタイムゾーン作業にはやりすぎ

タイムゾーン対応の日付時刻の作成

ゾーン情報の使用 (Python 3.9 以降)

from datetime import datetime
from zoneinfo import ZoneInfo

# Method 1: Create with timezone
dt = datetime(2025, 1, 15, 14, 30, tzinfo=ZoneInfo("America/New_York"))

# Method 2: Get current time with timezone
now = datetime.now(ZoneInfo("America/New_York"))

# Method 3: Replace timezone on naive datetime
naive_dt = datetime(2025, 1, 15, 14, 30)
aware_dt = naive_dt.replace(tzinfo=ZoneInfo("America/New_York"))

pytz の使用

import pytz
from datetime import datetime

# Method 1: Use localize() for naive datetimes
eastern = pytz.timezone('US/Eastern')
naive_dt = datetime(2025, 1, 15, 14, 30)
aware_dt = eastern.localize(naive_dt)

# Method 2: Get current time (use UTC, then convert)
utc_now = datetime.now(pytz.UTC)
eastern_now = utc_now.astimezone(eastern)

# ❌ WRONG with pytz!
wrong_dt = datetime(2025, 1, 15, 14, 30, tzinfo=eastern)
# This bypasses DST handling!

pytz の重要な注意点: tzinfo を直接渡すのではなく、常に localize() を使用してください。

タイムゾーン間の変換

基本的な変換

from datetime import datetime
from zoneinfo import ZoneInfo

# Create datetime in one timezone
ny_time = datetime(2025, 1, 15, 14, 30, tzinfo=ZoneInfo("America/New_York"))

# Convert to another timezone
tokyo_time = ny_time.astimezone(ZoneInfo("Asia/Tokyo"))
london_time = ny_time.astimezone(ZoneInfo("Europe/London"))
utc_time = ny_time.astimezone(ZoneInfo("UTC"))

print(f"New York:  {ny_time}")      # 2025-01-15 14:30:00-05:00
print(f"Tokyo:     {tokyo_time}")   # 2025-01-16 04:30:00+09:00
print(f"London:    {london_time}")  # 2025-01-15 19:30:00+00:00
print(f"UTC:       {utc_time}")     # 2025-01-15 19:30:00+00:00

変換ヘルパー関数

from datetime import datetime
from zoneinfo import ZoneInfo

def convert_timezone(dt, from_tz, to_tz):
    """
    Convert datetime between timezones

    Args:
        dt: datetime object (naive or aware)
        from_tz: Source timezone string (e.g., 'America/New_York')
        to_tz: Target timezone string (e.g., 'Asia/Tokyo')

    Returns:
        Timezone-aware datetime in target timezone
    """
    # If datetime is naive, localize it first
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=ZoneInfo(from_tz))

    # Convert to target timezone
    return dt.astimezone(ZoneInfo(to_tz))

# Usage
naive_dt = datetime(2025, 6, 15, 14, 30)
tokyo_time = convert_timezone(naive_dt, "America/New_York", "Asia/Tokyo")
print(tokyo_time)  # 2025-06-16 03:30:00+09:00

DST 移行の処理

夏時間 (DST) は、曖昧で存在しない時間を作成します。

あいまいな時間 (フォールバック)

クロックが「フォールバック」する (DST が終了する) と、1 つのクロック時間が 2 回発生します。

from datetime import datetime
from zoneinfo import ZoneInfo
import pytz

# November 5, 2023, 01:30 AM happens TWICE in US/Eastern
# Once in EDT (UTC-4), once in EST (UTC-5)

# With zoneinfo (Python 3.9+)
tz = ZoneInfo("America/New_York")

# Create the ambiguous time
dt = datetime(2023, 11, 5, 1, 30, tzinfo=tz)
print(dt)  # Uses the first occurrence (DST)

# With pytz - explicit control
eastern = pytz.timezone('US/Eastern')

# First occurrence (DST, UTC-4)
dt_dst = eastern.localize(datetime(2023, 11, 5, 1, 30), is_dst=True)
print(f"DST:      {dt_dst}")  # 2023-11-05 01:30:00-04:00

# Second occurrence (Standard, UTC-5)
dt_std = eastern.localize(datetime(2023, 11, 5, 1, 30), is_dst=False)
print(f"Standard: {dt_std}")  # 2023-11-05 01:30:00-05:00

存在しない時代 (スプリングフォワード)

時計が「早送り」される (DST が始まる) と、いくつかの時間が存在しません。

from datetime import datetime
from zoneinfo import ZoneInfo
import pytz

# March 10, 2024, 02:30 AM doesn't exist in US/Eastern
# Clocks jump from 02:00 AM to 03:00 AM

# With zoneinfo - automatically adjusts forward
tz = ZoneInfo("America/New_York")
dt = datetime(2024, 3, 10, 2, 30, tzinfo=tz)
print(dt)  # Automatically becomes 03:30

# With pytz - raises error by default
eastern = pytz.timezone('US/Eastern')

try:
    dt = eastern.localize(datetime(2024, 3, 10, 2, 30))
except pytz.exceptions.NonExistentTimeError:
    print("This time doesn't exist!")

# Handle non-existent time explicitly
dt = eastern.localize(datetime(2024, 3, 10, 2, 30), is_dst=None)
# Returns the next valid time

ベストプラクティス

1. タイムスタンプを常に UTC で保存する

from datetime import datetime, timezone

# ✅ GOOD: Store in UTC
def save_event(event_time):
    utc_time = event_time.astimezone(timezone.utc)
    database.save(utc_time)
    return utc_time

# ✅ GOOD: Display in user's timezone
def display_event(utc_time, user_timezone):
    local_time = utc_time.astimezone(ZoneInfo(user_timezone))
    return local_time.strftime("%Y-%m-%d %H:%M:%S %Z")

2. シリアル化には ISO 8601 形式を使用する

from datetime import datetime
from zoneinfo import ZoneInfo

dt = datetime.now(ZoneInfo("America/New_York"))

# ✅ GOOD: ISO 8601 with timezone
iso_string = dt.isoformat()
print(iso_string)  # 2025-01-15T14:30:00-05:00

# Parse back
parsed_dt = datetime.fromisoformat(iso_string)

3. 本番環境では単純な日時を決して使用しないでください

# ❌ BAD: Naive datetime
naive = datetime.now()

# ✅ GOOD: Always timezone-aware
aware = datetime.now(timezone.utc)

4. 計算には UTC を使用します

from datetime import datetime, timedelta, timezone

# ✅ GOOD: Calculate in UTC
start_utc = datetime.now(timezone.utc)
end_utc = start_utc + timedelta(hours=24)

# Then convert to local timezone for display
local_end = end_utc.astimezone(ZoneInfo("America/New_York"))

一般的なエラーと解決策

エラー 1: 単純な日付時刻と認識された日付時刻を使用した算術演算

# ❌ ERROR: Can't mix naive and aware
naive = datetime.now()
aware = datetime.now(timezone.utc)
# difference = aware - naive  # TypeError!

# ✅ SOLUTION: Make both timezone-aware
naive_aware = naive.replace(tzinfo=timezone.utc)
difference = aware - naive_aware

エラー 2: pytz の使用法が正しくありません

import pytz

# ❌ WRONG
eastern = pytz.timezone('US/Eastern')
dt = datetime(2025, 1, 15, 14, 30, tzinfo=eastern)

# ✅ CORRECT
dt = eastern.localize(datetime(2025, 1, 15, 14, 30))

エラー 3: ローカル タイムゾーンを想定している

# ❌ BAD: Assumes server timezone
dt = datetime.now()  # Which timezone?

# ✅ GOOD: Explicit timezone
dt = datetime.now(timezone.utc)

実用的な例: 会議スケジューラ

from datetime import datetime
from zoneinfo import ZoneInfo

class MeetingScheduler:
    """Schedule meetings across timezones"""

    def __init__(self):
        self.meetings = []

    def schedule_meeting(self, date_str, time_str, timezone_str, duration_hours):
        """
        Schedule a meeting in a specific timezone

        Args:
            date_str: Date as 'YYYY-MM-DD'
            time_str: Time as 'HH:MM'
            timezone_str: IANA timezone (e.g., 'America/New_York')
            duration_hours: Meeting duration in hours
        """
        # Parse date and time
        year, month, day = map(int, date_str.split('-'))
        hour, minute = map(int, time_str.split(':'))

        # Create timezone-aware datetime
        tz = ZoneInfo(timezone_str)
        meeting_time = datetime(year, month, day, hour, minute, tzinfo=tz)

        # Convert to UTC for storage
        meeting_utc = meeting_time.astimezone(ZoneInfo("UTC"))

        meeting = {
            'start_utc': meeting_utc,
            'timezone': timezone_str,
            'duration': duration_hours
        }

        self.meetings.append(meeting)
        return meeting

    def get_meeting_time(self, meeting, display_timezone):
        """Get meeting time in any timezone"""
        tz = ZoneInfo(display_timezone)
        local_time = meeting['start_utc'].astimezone(tz)

        return {
            'time': local_time.strftime("%Y-%m-%d %H:%M %Z"),
            'timezone': display_timezone
        }

# Usage example
scheduler = MeetingScheduler()

# Schedule meeting in New York
meeting = scheduler.schedule_meeting(
    '2025-02-15', '14:00', 'America/New_York', 1
)

# Display for different participants
print("Meeting times:")
print(f"  New York: {scheduler.get_meeting_time(meeting, 'America/New_York')['time']}")
print(f"  London:   {scheduler.get_meeting_time(meeting, 'Europe/London')['time']}")
print(f"  Tokyo:    {scheduler.get_meeting_time(meeting, 'Asia/Tokyo')['time']}")

出力:

Meeting times:
  New York: 2025-02-15 14:00 EST
  London:   2025-02-15 19:00 GMT
  Tokyo:    2025-02-16 04:00 JST

タイムゾーンコードのテスト

import unittest
from datetime import datetime
from zoneinfo import ZoneInfo

class TestTimezoneConversion(unittest.TestCase):

    def test_utc_to_eastern(self):
        """Test UTC to Eastern conversion"""
        utc_time = datetime(2025, 1, 15, 19, 30, tzinfo=ZoneInfo("UTC"))
        eastern_time = utc_time.astimezone(ZoneInfo("America/New_York"))

        # In January, Eastern is UTC-5 (EST)
        self.assertEqual(eastern_time.hour, 14)
        self.assertEqual(eastern_time.minute, 30)

    def test_dst_transition(self):
        """Test DST transition handling"""
        # Before DST (March 10, 2024, 1:00 AM)
        before_dst = datetime(2024, 3, 10, 1, 0, tzinfo=ZoneInfo("America/New_York"))

        # After DST (March 10, 2024, 3:00 AM - 2:00 AM doesn't exist)
        after_dst = datetime(2024, 3, 10, 3, 0, tzinfo=ZoneInfo("America/New_York"))

        # Difference should be 1 hour in local time, 2 hours in absolute time
        diff = after_dst - before_dst
        self.assertEqual(diff.total_seconds(), 3600)  # 1 hour

if __name__ == '__main__':
    unittest.main()

関連ツールとリソース

タイムゾーンを操作するには、無料のタイムスタンプ ツールを使用します。

概要

重要なポイント:

  1. 運用コードでは常にタイムゾーン対応の日時を使用する
  2. タイムスタンプを UTC で保存、表示用にローカルに変換します
  3. zoneinfo (Python 3.9+) または古いバージョンの場合は pytz を使用します
  4. 必要に応じて DST 移行を明示的に処理
  5. タイムゾーン コードを徹底的にテストします (特に DST 付近)。
  6. 現地のタイムゾーンを想定しないでください - 常に明示的にしてください

クイックリファレンス:

# Modern Python (3.9+)
from datetime import datetime
from zoneinfo import ZoneInfo

# Current UTC time
utc_now = datetime.now(ZoneInfo("UTC"))

# Current local time
local_now = datetime.now(ZoneInfo("America/New_York"))

# Convert between timezones
tokyo_time = local_now.astimezone(ZoneInfo("Asia/Tokyo"))

# ISO 8601 format (with timezone)
iso_string = tokyo_time.isoformat()

これらのテクニックを使用すると、Python アプリケーションで自信を持ってタイムゾーンを処理できるようになります。


最終更新日: 2025 年 1 月