pytz
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.
Warnings
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
Install
-
pip install pytz -
pip install tzdata
Imports
- timezone
from pytz import timezone
- utc / UTC
import pytz; tz = pytz.utc
- all_timezones / common_timezones
from pytz import all_timezones, common_timezones
- UnknownTimeZoneError
from pytz.exceptions import UnknownTimeZoneError
Quickstart
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'))