Python’s zoneinfo module (Python 3.9+) provides access to the IANA timezone database for creating fully-aware datetime objects. from zoneinfo import ZoneInfo. Create: tz = ZoneInfo("America/New_York") — wraps the IANA key and implements the tzinfo interface. Attach: dt = datetime(2024, 6, 15, 9, 0, tzinfo=ZoneInfo("Europe/London")). Convert: dt.astimezone(ZoneInfo("Asia/Tokyo")). List all zones: zoneinfo.available_timezones() → set[str] (system-dependent). UTC shortcut: ZoneInfo("UTC"). Cache control: ZoneInfo.no_cache("America/Los_Angeles") → bypasses the module cache; ZoneInfo.clear_cache() — flush all cached instances. Error: ZoneInfoNotFoundError (subclass of KeyError) raised when the timezone key doesn’t exist. Fallback data: install tzdata (PyPI) to bundle timezone data on systems without /usr/share/zoneinfo (e.g. Windows, some Docker images). Claude Code generates timezone-aware scheduling systems, UTC converters, local time helpers, DST-safe arithmetic, and multi-region calendar tools.
CLAUDE.md for zoneinfo
## zoneinfo Stack
- Stdlib: from zoneinfo import ZoneInfo, available_timezones, ZoneInfoNotFoundError
- Create: tz = ZoneInfo("America/New_York")
- Aware: dt = datetime(2024, 6, 15, 9, 0, tzinfo=ZoneInfo("UTC"))
- Convert: dt_local = dt.astimezone(ZoneInfo("Europe/Berlin"))
- List: available_timezones() # set of IANA keys
- Fallback: pip install tzdata # for Windows / minimal containers
- Note: Python 3.9+; backports.zoneinfo for 3.6–3.8
zoneinfo Timezone Pipeline
# app/zoneinfoutil.py — conversion, schedule, DST guard, multi-region, summary
from __future__ import annotations
import datetime
from dataclasses import dataclass, field
from typing import Any
try:
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones
except ImportError: # Python < 3.9
from backports.zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones # type: ignore
# ─────────────────────────────────────────────────────────────────────────────
# 1. Timezone lookup helpers
# ─────────────────────────────────────────────────────────────────────────────
def get_tz(key: str) -> "ZoneInfo | None":
"""
Return a ZoneInfo for key, or None if the key is not found.
Example:
tz = get_tz("America/Chicago")
tz_bad = get_tz("Not/AZone") # None
"""
try:
return ZoneInfo(key)
except (ZoneInfoNotFoundError, KeyError):
return None
def tz_or_utc(key: "str | None") -> ZoneInfo:
"""
Return ZoneInfo for key, falling back to UTC on error or None.
Example:
tz = tz_or_utc("Europe/Paris")
tz = tz_or_utc(None) # ZoneInfo("UTC")
"""
if key:
tz = get_tz(key)
if tz is not None:
return tz
return ZoneInfo("UTC")
def search_timezones(substring: str) -> list[str]:
"""
Return IANA timezone keys that contain substring (case-insensitive).
Example:
search_timezones("london") # ["Europe/London"]
search_timezones("us/") # ["US/Eastern", "US/Pacific", ...]
"""
lower = substring.lower()
return sorted(k for k in available_timezones() if lower in k.lower())
def list_region_zones(region: str) -> list[str]:
"""
Return all IANA keys for a top-level region prefix (e.g. "America", "Europe").
Example:
list_region_zones("Pacific") # ["Pacific/Auckland", ...]
"""
prefix = region.rstrip("/") + "/"
return sorted(k for k in available_timezones() if k.startswith(prefix))
# ─────────────────────────────────────────────────────────────────────────────
# 2. Datetime conversion utilities
# ─────────────────────────────────────────────────────────────────────────────
def now_in(tz_key: str) -> datetime.datetime:
"""
Return the current time in the given timezone.
Example:
print(now_in("Asia/Tokyo"))
print(now_in("UTC"))
"""
return datetime.datetime.now(tz=ZoneInfo(tz_key))
def utc_now() -> datetime.datetime:
"""Return the current UTC time as a timezone-aware datetime."""
return datetime.datetime.now(tz=ZoneInfo("UTC"))
def to_utc(dt: datetime.datetime) -> datetime.datetime:
"""
Convert a timezone-aware datetime to UTC.
Naive datetimes are assumed to be in UTC.
Example:
local = datetime.datetime(2024, 6, 1, 12, 0, tzinfo=ZoneInfo("US/Eastern"))
print(to_utc(local)) # 2024-06-01 16:00:00+00:00
"""
if dt.tzinfo is None:
return dt.replace(tzinfo=ZoneInfo("UTC"))
return dt.astimezone(ZoneInfo("UTC"))
def convert_tz(dt: datetime.datetime, target_key: str) -> datetime.datetime:
"""
Convert a timezone-aware datetime to a different timezone.
Naive datetimes are treated as UTC.
Example:
utc_dt = datetime.datetime(2024, 1, 15, 18, 0, tzinfo=ZoneInfo("UTC"))
ny_dt = convert_tz(utc_dt, "America/New_York")
print(ny_dt) # 2024-01-15 13:00:00-05:00
"""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=ZoneInfo("UTC"))
return dt.astimezone(ZoneInfo(target_key))
def localize_naive(dt: datetime.datetime, tz_key: str) -> datetime.datetime:
"""
Attach a timezone to a naive datetime (replaces tzinfo without conversion).
Use this when you know the datetime represents local time in tz_key.
Example:
naive = datetime.datetime(2024, 3, 10, 2, 30) # ambiguous in US/Eastern DST
aware = localize_naive(naive, "America/New_York")
"""
return dt.replace(tzinfo=ZoneInfo(tz_key))
# ─────────────────────────────────────────────────────────────────────────────
# 3. DST / UTC-offset inspection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class TzStatus:
"""
Snapshot of timezone state at a given moment.
Example:
s = tz_status_at(datetime.datetime(2024, 7, 4, tzinfo=ZoneInfo("UTC")), "America/New_York")
print(s.abbr, s.utcoffset, s.is_dst)
"""
key: str
utcoffset: datetime.timedelta
abbr: str
is_dst: bool
def offset_hours(self) -> float:
return self.utcoffset.total_seconds() / 3600
def tz_status_at(dt: datetime.datetime, tz_key: str) -> TzStatus:
"""
Return timezone status (offset, abbreviation, DST flag) at a given UTC moment.
Example:
summer = datetime.datetime(2024, 7, 1, 12, tzinfo=ZoneInfo("UTC"))
winter = datetime.datetime(2024, 1, 1, 12, tzinfo=ZoneInfo("UTC"))
print(tz_status_at(summer, "America/Los_Angeles").abbr) # PDT
print(tz_status_at(winter, "America/Los_Angeles").abbr) # PST
"""
tz = ZoneInfo(tz_key)
local = dt.astimezone(tz)
offset = local.utcoffset() or datetime.timedelta(0)
dst = local.dst() or datetime.timedelta(0)
abbr = local.strftime("%Z")
return TzStatus(key=tz_key, utcoffset=offset, abbr=abbr, is_dst=bool(dst))
def utc_offset_hours(tz_key: str, at: "datetime.datetime | None" = None) -> float:
"""
Return the UTC offset in hours for tz_key at a given moment (default: now).
Example:
print(utc_offset_hours("Asia/Kolkata")) # 5.5
print(utc_offset_hours("America/New_York")) # -5.0 or -4.0 (DST)
"""
ref = (at or utc_now()).astimezone(ZoneInfo(tz_key))
offset = ref.utcoffset() or datetime.timedelta(0)
return offset.total_seconds() / 3600
# ─────────────────────────────────────────────────────────────────────────────
# 4. Multi-region time display
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class RegionTime:
"""One row in a multi-region display."""
zone_key: str
local_time: str
utc_offset: str
abbr: str
def world_clock(
tz_keys: "list[str]",
at: "datetime.datetime | None" = None,
fmt: str = "%Y-%m-%d %H:%M:%S",
) -> list[RegionTime]:
"""
Return current local time for each timezone key.
Example:
for row in world_clock(["UTC", "America/New_York", "Asia/Tokyo", "Europe/Paris"]):
print(f"{row.zone_key:30s} {row.local_time} {row.utc_offset} {row.abbr}")
"""
ref = at or utc_now()
rows = []
for key in tz_keys:
tz = get_tz(key)
if tz is None:
continue
local = ref.astimezone(tz)
offset = local.utcoffset() or datetime.timedelta(0)
h, rem = divmod(int(offset.total_seconds()), 3600)
m = abs(rem) // 60
offset_str = f"UTC{h:+03d}:{m:02d}"
rows.append(RegionTime(
zone_key=key,
local_time=local.strftime(fmt),
utc_offset=offset_str,
abbr=local.strftime("%Z"),
))
return rows
def schedule_utc(
naive_local: datetime.datetime,
tz_key: str,
) -> datetime.datetime:
"""
Given a naive local datetime in tz_key, return the equivalent UTC moment.
Useful for scheduling events entered in local time.
Example:
# User says "9 AM tomorrow in Berlin"
import datetime
local = datetime.datetime(2024, 6, 15, 9, 0)
utc = schedule_utc(local, "Europe/Berlin")
print(utc) # 2024-06-15 07:00:00+00:00
"""
aware = naive_local.replace(tzinfo=ZoneInfo(tz_key))
return aware.astimezone(ZoneInfo("UTC"))
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== zoneinfo demo ===")
# ── search_timezones ──────────────────────────────────────────────────────
print("\n--- search_timezones ---")
for term in ["london", "tokyo", "kolkata"]:
results = search_timezones(term)
print(f" {term!r}: {results}")
# ── now_in / utc_now ──────────────────────────────────────────────────────
print("\n--- now_in ---")
for key in ["UTC", "America/New_York", "Europe/Berlin", "Asia/Tokyo"]:
t = now_in(key)
print(f" {key:25s} {t.isoformat()}")
# ── convert_tz ────────────────────────────────────────────────────────────
print("\n--- convert_tz ---")
utc_dt = datetime.datetime(2024, 7, 4, 18, 0, tzinfo=ZoneInfo("UTC"))
for target in ["America/Los_Angeles", "Europe/London", "Asia/Kolkata"]:
local = convert_tz(utc_dt, target)
print(f" {utc_dt.isoformat()} → {local.isoformat()} ({target})")
# ── tz_status_at ──────────────────────────────────────────────────────────
print("\n--- tz_status_at ---")
summer = datetime.datetime(2024, 7, 1, 12, tzinfo=ZoneInfo("UTC"))
winter = datetime.datetime(2024, 1, 1, 12, tzinfo=ZoneInfo("UTC"))
for dt, label in [(summer, "summer"), (winter, "winter")]:
s = tz_status_at(dt, "America/New_York")
print(f" {label}: {s.abbr} UTC{s.offset_hours():+.1f}h DST={s.is_dst}")
# ── world_clock ───────────────────────────────────────────────────────────
print("\n--- world_clock ---")
ref = datetime.datetime(2024, 6, 15, 12, 0, tzinfo=ZoneInfo("UTC"))
for row in world_clock(
["UTC", "America/New_York", "Europe/Paris", "Asia/Kolkata", "Asia/Tokyo"],
at=ref,
):
print(f" {row.zone_key:25s} {row.local_time} {row.utc_offset} {row.abbr}")
# ── schedule_utc ───────────────────────────────────────────────────────────
print("\n--- schedule_utc ---")
local_event = datetime.datetime(2024, 9, 20, 9, 30)
utc_event = schedule_utc(local_event, "Europe/Berlin")
print(f" Local (Berlin): {local_event} → UTC: {utc_event}")
# ── utc_offset_hours ──────────────────────────────────────────────────────
print("\n--- utc_offset_hours ---")
ref2 = datetime.datetime(2024, 1, 15, 12, tzinfo=ZoneInfo("UTC"))
for key in ["Asia/Kolkata", "Australia/Adelaide", "Pacific/Chatham"]:
h = utc_offset_hours(key, at=ref2)
print(f" {key:25s} UTC{h:+.2f}h")
print("\n=== done ===")
For the pytz (PyPI) alternative — pytz pre-dates zoneinfo and uses pytz.timezone("America/New_York").localize(naive_dt) for safe localization and dt.astimezone(tz) for conversion; it bundles its own timezone data independently of the OS — prefer zoneinfo (stdlib, Python 3.9+) for all new code; use pytz only when supporting Python < 3.9 or when a library dependency requires it. For the dateutil (PyPI) tz module alternative — dateutil.tz.gettz("America/New_York") also returns a tzinfo object using the OS timezone database and adds tzoffset, tzlocal, tzutc convenience classes — use dateutil.tz when you need its parser integration (dateutil.parser.parse) or robust fuzzy timezone string resolution; use zoneinfo when you want a stdlib-only solution with the same IANA database. The Claude Skills 360 bundle includes zoneinfo skill sets covering get_tz()/tz_or_utc()/search_timezones()/list_region_zones() lookup helpers, now_in()/utc_now()/to_utc()/convert_tz()/localize_naive() conversion utilities, TzStatus dataclass + tz_status_at()/utc_offset_hours() DST inspection, and RegionTime/world_clock()/schedule_utc() multi-region display. Start with the free tier to try timezone patterns and zoneinfo pipeline code generation.