pytz

raw JSON →
2026.1.post1 verified Tue May 12 auth: no python install: verified quickstart: verified maintenance

pytz brings the IANA/Olson timezone database into Python, enabling accurate cross-platform timezone calculations and daylight-saving-time-aware datetime handling. Current version is 2026.1.post1, released March 2026; versions track IANA tz database releases (typically several times per year). The pytz maintainer explicitly states that projects on Python 3.9+ should prefer the stdlib zoneinfo module (PEP 615) with the tzdata package instead — pytz is now in maintenance mode for backwards compatibility only.

pip install pytz
breaking Do NOT pass a pytz timezone directly to the datetime constructor via tzinfo=. For non-UTC zones this silently attaches the zone's historical LMT (Local Mean Time) offset, not the correct current offset, producing wrong results with a 50% chance of being off by an hour around DST transitions.
fix Always use tz.localize(naive_dt) to attach a pytz timezone to a naive datetime. Only pytz.utc is safe to pass directly to the constructor.
breaking After any timedelta arithmetic on a pytz-aware local datetime, the UTC offset can become stale if the operation crossed a DST boundary. The offset is not automatically recalculated.
fix Wrap every arithmetic result with tz.normalize(result) to correct the offset. Or work exclusively in UTC and convert only for display.
deprecated The pytz maintainer and PyPI page explicitly state that projects on Python 3.9+ should use stdlib zoneinfo (PEP 615) + tzdata instead. pytz receives no new features; only IANA DB updates are applied.
fix New code: use 'from zoneinfo import ZoneInfo' with 'pip install tzdata'. For gradual migration use the pytz-deprecation-shim package.
gotcha pytz.utc / pytz.UTC is NOT the same object as pytz.timezone('GMT'), pytz.timezone('Greenwich'), or datetime.timezone.utc. Identity/equality checks between these will return False.
fix Standardise on pytz.utc throughout pytz code, or use datetime.timezone.utc when mixing with stdlib. Never assume UTC aliases are interchangeable.
gotcha localize() raises ValueError if the datetime already has tzinfo set. Calling localize() on an already-aware datetime is a common mistake when re-processing data.
fix Check dt.tzinfo is None before calling localize(). For already-aware datetimes use astimezone() to convert zones.
gotcha DST disambiguation via is_dst=None raises pytz.exceptions.AmbiguousTimeError or NonExistentTimeError instead of silently picking one, but only if you opt in. The default (is_dst=False) silently chooses standard time, which may surprise callers.
fix Pass is_dst=None to localize() in production scheduling code so ambiguous or non-existent wall-clock times raise an exception rather than silently producing a wrong result.
gotcha pytz timezone objects are not compatible with the standard Python tzinfo interface expected by PEP 495 consumers (fold attribute). Passing pytz zones to libraries expecting stdlib-compatible tzinfo (e.g. Django 4.0+ defaults, zoneinfo consumers) can produce incorrect offsets.
fix When interfacing with PEP 495-aware code, convert: use zoneinfo.ZoneInfo(str(pytz_zone)) to obtain an equivalent stdlib zone object.
pip install tzdata
python os / libc variant status wheel install import disk
3.10 alpine (musl) pytz - - 0.01s 20.6M
3.10 alpine (musl) tzdata - - - -
3.10 slim (glibc) pytz - - 0.01s 21M
3.10 slim (glibc) tzdata - - - -
3.11 alpine (musl) pytz - - 0.01s 22.4M
3.11 alpine (musl) tzdata - - - -
3.11 slim (glibc) pytz - - 0.01s 23M
3.11 slim (glibc) tzdata - - - -
3.12 alpine (musl) pytz - - 0.01s 14.3M
3.12 alpine (musl) tzdata - - - -
3.12 slim (glibc) pytz - - 0.01s 15M
3.12 slim (glibc) tzdata - - - -
3.13 alpine (musl) pytz - - 0.01s 13.9M
3.13 alpine (musl) tzdata - - - -
3.13 slim (glibc) pytz - - 0.01s 14M
3.13 slim (glibc) tzdata - - - -
3.9 alpine (musl) pytz - - 0.01s 20.1M
3.9 alpine (musl) tzdata - - - -
3.9 slim (glibc) pytz - - 0.01s 21M
3.9 slim (glibc) tzdata - - - -

Canonical pytz usage: always store in UTC, convert to local only for display. Use localize() to attach a tz to a naive datetime; use normalize() after any arithmetic on a local datetime to fix DST offsets.

from datetime import datetime, timedelta
import pytz

# --- Correct: build a UTC-aware datetime ---
utc = pytz.utc
utc_dt = datetime(2024, 3, 10, 7, 0, 0, tzinfo=utc)
print("UTC:", utc_dt)

# --- Correct: convert UTC -> local via astimezone ---
eastern = pytz.timezone('US/Eastern')
loc_dt = utc_dt.astimezone(eastern)
print("Eastern:", loc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'))

# --- Correct: localize a naive datetime ---
naive = datetime(2024, 11, 3, 1, 30)          # ambiguous wall-clock time
aware = eastern.localize(naive, is_dst=True)   # explicitly choose EDT side
print("Localized (EDT):", aware)

# --- Correct: arithmetic on local times MUST be followed by normalize ---
before_dst_end = eastern.localize(datetime(2024, 11, 3, 1, 50))
result = eastern.normalize(before_dst_end + timedelta(minutes=20))
print("After normalize:", result.strftime('%Y-%m-%d %H:%M:%S %Z%z'))

# --- WRONG (do not do this) ---
# wrong = datetime(2024, 11, 3, 1, 30, tzinfo=eastern)  # returns LMT, not EST/EDT!

# --- Preferred modern alternative (Python 3.9+) ---
# from zoneinfo import ZoneInfo
# aware_modern = datetime(2024, 11, 3, 1, 30, tzinfo=ZoneInfo('US/Eastern'))