Skip to content
SysTutorials
  • SysTutorialsExpand
    • Linux & Systems Administration Academy
    • Web3 & Crypto Academy
    • Programming Academy
    • Systems & Architecture Academy
  • Subscribe
  • Linux Manuals
  • Search
SysTutorials
Languages & Frameworks

Choosing Between Python’s zoneinfo and pytz

ByQ A Posted onSep 19, 2018Apr 12, 2026 Updated onApr 12, 2026

Python’s standard library includes zoneinfo, which handles timezones correctly without external dependencies. Use it for all new code.

from datetime import datetime
from zoneinfo import ZoneInfo

utc_time = datetime.now(ZoneInfo("UTC"))
ny_time = utc_time.astimezone(ZoneInfo("America/New_York"))
print(ny_time)

This automatically manages Daylight Saving Time transitions. The IANA timezone database (via the tzdata package on Windows; built-in on Unix-like systems) backs the lookups, so timezone rules stay current as DST rules change.

Creating Timezone-Aware Datetimes

You’ll often need to create a datetime in a specific timezone rather than convert from UTC:

from datetime import datetime
from zoneinfo import ZoneInfo

# Create a datetime in Tokyo timezone
tokyo_tz = ZoneInfo("Asia/Tokyo")
meeting_time = datetime(2026, 6, 15, 14, 30, tzinfo=tokyo_tz)
print(meeting_time)
# 2026-06-15 14:30:00+09:00

# Convert to your local timezone
local_tz = ZoneInfo("America/Chicago")
local_meeting = meeting_time.astimezone(local_tz)
print(local_meeting)
# 2026-06-15 00:30:00-05:00

The key difference: passing tzinfo directly to the constructor creates a datetime in that timezone, while astimezone() converts an existing datetime.

Converting Between Timezones

When working with multiple timezones, always use astimezone() to convert:

from datetime import datetime
from zoneinfo import ZoneInfo

# Start with a UTC time
utc_time = datetime(2026, 3, 10, 12, 0, tzinfo=ZoneInfo("UTC"))

# Convert to different timezones
london = utc_time.astimezone(ZoneInfo("Europe/London"))
sydney = utc_time.astimezone(ZoneInfo("Australia/Sydney"))
mumbai = utc_time.astimezone(ZoneInfo("Asia/Kolkata"))

print(f"UTC: {utc_time}")
print(f"London: {london}")
print(f"Sydney: {sydney}")
print(f"Mumbai: {mumbai}")

All conversions reference the same instant in time — only the local representation changes.

Handling DST Transitions

The zoneinfo library handles DST automatically, but you need to be aware of ambiguous times during transitions. When clocks “fall back,” a time exists twice:

from datetime import datetime
from zoneinfo import ZoneInfo

# During DST transition, 1:30 AM occurs twice
# zoneinfo defaults to the first occurrence (fold=0)
eastern = ZoneInfo("America/New_York")

# First occurrence (EDT, UTC-4)
time1 = datetime(2026, 11, 1, 1, 30, fold=0, tzinfo=eastern)
print(time1)  # 2026-11-01 01:30:00-04:00

# Second occurrence (EST, UTC-5)
time2 = datetime(2026, 11, 1, 1, 30, fold=1, tzinfo=eastern)
print(time2)  # 2026-11-01 01:30:00-05:00

Use fold=0 for the first occurrence and fold=1 for the second. If users specify an ambiguous time, validate and handle it explicitly rather than silently picking the wrong one. You can check if a time is ambiguous:

from datetime import datetime
from zoneinfo import ZoneInfo

def is_ambiguous(dt):
    """Check if a naive datetime is ambiguous in the given timezone."""
    tz = dt.tzinfo
    # If fold=0 and fold=1 produce different offsets, the time is ambiguous
    dt_fold0 = dt.replace(fold=0)
    dt_fold1 = dt.replace(fold=1)
    return dt_fold0.utcoffset() != dt_fold1.utcoffset()

eastern = ZoneInfo("America/New_York")
ambig_time = datetime(2026, 11, 1, 1, 30, tzinfo=eastern)
print(is_ambiguous(ambig_time))  # True

Database Best Practices

Store all timestamps in UTC in your database. Convert to the user’s local timezone only when displaying:

from datetime import datetime
from zoneinfo import ZoneInfo

# Save to database as UTC
def save_event(event_name, local_datetime_str, user_timezone_str):
    user_tz = ZoneInfo(user_timezone_str)
    local_dt = datetime.fromisoformat(local_datetime_str).replace(tzinfo=user_tz)
    utc_dt = local_dt.astimezone(ZoneInfo("UTC"))

    # Store utc_dt.isoformat() in your database
    return utc_dt.isoformat()

# Retrieve from database and convert to user timezone
def display_event(utc_datetime_str, user_timezone_str):
    utc_dt = datetime.fromisoformat(utc_datetime_str)
    user_tz = ZoneInfo(user_timezone_str)
    user_dt = utc_dt.astimezone(user_tz)

    return user_dt.strftime("%Y-%m-%d %H:%M:%S %Z")

# Example usage
event_utc = save_event("Team standup", "2026-06-15T09:00:00", "America/Denver")
display_event(event_utc, "Europe/Berlin")
# 2026-06-15 17:00:00 CEST

This approach ensures consistency across your system. Avoid storing timezone information with datetimes in the database — the timezone offset can change due to DST rule updates, making historical data unreliable.

Getting the Current Time in a Specific Timezone

For real-time queries without conversion:

from datetime import datetime
from zoneinfo import ZoneInfo

# Current time in Tokyo (right now, not converted)
tokyo_now = datetime.now(ZoneInfo("Asia/Tokyo"))
print(tokyo_now)

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

Python 3.8 and Earlier: Using pytz

If you’re stuck on Python 3.8, use the pytz library:

from datetime import datetime
import pytz

ny_tz = pytz.timezone("America/New_York")
ny_time = datetime.now(ny_tz)
print(ny_time)

However, pytz requires explicit localization and is more error-prone with DST:

import pytz
from datetime import datetime

eastern = pytz.timezone("America/New_York")

# Wrong way (naive datetime won't handle DST correctly)
wrong = datetime(2026, 6, 15, 14, 30, tzinfo=eastern)

# Right way (use localize)
correct = eastern.localize(datetime(2026, 6, 15, 14, 30))

Upgrade to Python 3.9+ if possible — zoneinfo is simpler and doesn’t require workarounds.

Avoiding Common Pitfalls

Never use naive datetimes in production code:

# DON'T DO THIS
naive_time = datetime.now()  # No timezone info — ambiguous

# DO THIS
aware_time = datetime.now(ZoneInfo("UTC"))  # Always timezone-aware

Naive datetimes cause subtle bugs when code runs in different environments or processes events from multiple timezones. If you receive a naive datetime from an external source, always attach timezone info before using it:

from datetime import datetime
from zoneinfo import ZoneInfo

# Data from an API with no timezone info
api_time_str = "2026-06-15T14:30:00"
naive_dt = datetime.fromisoformat(api_time_str)

# Assume it's in the user's timezone (don't assume UTC unless documented)
user_tz = ZoneInfo("America/Los_Angeles")
aware_dt = naive_dt.replace(tzinfo=user_tz)

# Now safe to use
utc_dt = aware_dt.astimezone(ZoneInfo("UTC"))
Read more:
  • Choosing Stream Processing Tools Based on Latency Needs
  • Choosing the Right Timer: clock_gettime vs TSC in Linux
  • Tuning MapReduce: Choosing Mapper and Reducer Counts
  • Convert Line Endings Between DOS and Unix in Emacs
  • Importing Functions Between Node.js Modules
  • Converting Between File Descriptors and FILE Pointers in C
  • Alt-Tab Between Windows Only in GNOME
  • Convert Between Simplified and Traditional Chinese on Linux Using OpenCC
Post Tags: #convert#database#How to#Library#Programming#Python#System#systems#time#timezone#Tutorial#Unix#Windows#www

Post navigation

Previous Previous
GNOME Shell Themes in Ubuntu: Setup and Management
NextContinue
Renaming Git Branches Locally and Remotely

Tutorials

  • Systems & Architecture Academy
    • Advanced Systems Path
    • Security & Cryptography Path
  • Linux & Systems Administration Academy
    • Linux Essentials Path
    • Linux System Administration Path
  • Programming Academy
  • Web3 & Crypto Academy
  • AI Engineering Hub

Categories

  • AI Engineering (4)
  • Algorithms & Data Structures (14)
  • Code Optimization (7)
  • Databases & Storage (11)
  • Design Patterns (4)
  • Design Patterns & Architecture (18)
  • Development Best Practices (104)
  • Functional Programming (4)
  • Languages & Frameworks (97)
  • Linux & Systems Administration (727)
  • Linux System Configuration (32)
  • Object-Oriented Programming (4)
  • Programming Languages (131)
  • Scripting & Utilities (65)
  • Security & Encryption (16)
  • Software Architecture (3)
  • System Administration & Cloud (33)
  • Systems & Architecture (46)
  • Testing & DevOps (33)
  • Web Development (25)
  • Web3 & Crypto (1)

SysTutorials, Terms, Privacy

  • SysTutorials
    • Linux & Systems Administration Academy
    • Web3 & Crypto Academy
    • Programming Academy
    • Systems & Architecture Academy
  • Subscribe
  • Linux Manuals
  • Search