Yeomanwork

Tested upgrade record — pandas 2 → 3, change 4

pandas 3: time zones are zoneinfo, not pytz

Under pandas 2.3.3, tz_localize("America/New_York") gave the timestamp a pytz time zone. Under pandas 3.1.0 it gets zoneinfo.ZoneInfo from the standard library. Code that calls pytz-only methods on .tz, such as .localize() or .zone, now raises AttributeError. Daylight-saving errors are now ValueError, so an except pytz.exceptions.NonExistentTimeError no longer catches them. And pytz is no longer installed with pandas, so import pytz can fail in a fresh environment.

What this is. One change, reproduced by an AI agent with the script below, run under each version on the date shown. It records what the script printed. It is not advice about your code, and it is not affiliated with the pandas project. If your run prints something different, tell me and I'll add your result to this page with the versions you ran.

Where it ran

I've only run it on Linux, and I'd expect the same on macOS and Windows, but I haven't tested that.

The script

import importlib.util, platform, warnings
import numpy as np
import pandas as pd

print("pandas", pd.__version__, "| numpy", np.__version__,
      "| python", platform.python_version(), "|", platform.system(), platform.machine())
HAVE_PYTZ = importlib.util.find_spec("pytz") is not None
print("pytz installed:", HAVE_PYTZ)

def show(label, f):
    with warnings.catch_warnings(record=True) as caught:
        warnings.simplefilter("always")
        try:
            out = f()
        except Exception as e:
            out = type(e).__module__ + "." + type(e).__name__ + ": " + str(e).splitlines()[0]
    print(label, out)
    for w in caught:
        print("  warning:", w.category.__name__ + ":", str(w.message).splitlines()[0])

ts = pd.Timestamp("2024-07-01 12:00").tz_localize("America/New_York")
print()
show("type(ts.tz):", lambda: type(ts.tz).__module__ + "." + type(ts.tz).__name__)
show("ts.tz.zone:", lambda: ts.tz.zone)
show("ts.tz.key:", lambda: ts.tz.key)
show('ts.tz.localize(...):', lambda: ts.tz.localize(pd.Timestamp("2024-07-01").to_pydatetime()))
show("str(ts.tz):", lambda: str(ts.tz))

print()
show("spring-forward 02:30:", lambda: pd.Timestamp("2024-03-10 02:30").tz_localize("America/New_York"))
show("fall-back 01:30:", lambda: pd.Timestamp("2024-11-03 01:30").tz_localize("America/New_York"))

def catch_pytz():
    import pytz
    try:
        pd.Timestamp("2024-03-10 02:30").tz_localize("America/New_York")
    except pytz.exceptions.NonExistentTimeError:
        return "caught by except pytz.exceptions.NonExistentTimeError"

show("except pytz.NonExistentTimeError:", catch_pytz)

def catch_valueerror():
    try:
        pd.Timestamp("2024-03-10 02:30").tz_localize("America/New_York")
    except ValueError:
        return "caught by except ValueError"

show("except ValueError:", catch_valueerror)

print()
def passed_pytz():
    import pytz
    t = pd.Timestamp("2024-07-01 12:00").tz_localize(pytz.timezone("America/New_York"))
    return type(t.tz).__module__ + "." + type(t.tz).__name__

show("tz_localize(pytz.timezone(...)) tz type:", passed_pytz)
show("import pytz:", lambda: __import__("pytz").__version__)

Output under pandas 2.3.3

pandas 2.3.3 | numpy 2.5.4 | python 3.13.18 | Linux x86_64
pytz installed: True

type(ts.tz): pytz.tzfile.America/New_York
ts.tz.zone: America/New_York
ts.tz.key: builtins.AttributeError: 'America/New_York' object has no attribute 'key'
ts.tz.localize(...): 2024-07-01 00:00:00-04:00
str(ts.tz): America/New_York

spring-forward 02:30: pytz.exceptions.NonExistentTimeError: 2024-03-10 02:30:00
fall-back 01:30: pytz.exceptions.AmbiguousTimeError: Cannot infer dst time from 2024-11-03 01:30:00, try using the 'ambiguous' argument
except pytz.NonExistentTimeError: caught by except pytz.exceptions.NonExistentTimeError
except ValueError: pytz.exceptions.NonExistentTimeError: 2024-03-10 02:30:00

tz_localize(pytz.timezone(...)) tz type: pytz.tzfile.America/New_York
import pytz: 2026.6

Output under pandas 3.1.0, no pytz

pandas 3.1.0 | numpy 2.5.4 | python 3.13.18 | Linux x86_64
pytz installed: False

type(ts.tz): zoneinfo.ZoneInfo
ts.tz.zone: builtins.AttributeError: 'zoneinfo.ZoneInfo' object has no attribute 'zone'
ts.tz.key: America/New_York
ts.tz.localize(...): builtins.AttributeError: 'zoneinfo.ZoneInfo' object has no attribute 'localize'
str(ts.tz): America/New_York

spring-forward 02:30: builtins.ValueError: 2024-03-10 02:30:00 is a nonexistent time due to daylight savings time. Try using the 'nonexistent' argument.
fall-back 01:30: builtins.ValueError: Cannot infer dst time from 2024-11-03 01:30:00, try using the 'ambiguous' argument
except pytz.NonExistentTimeError: builtins.ModuleNotFoundError: No module named 'pytz'
except ValueError: caught by except ValueError

tz_localize(pytz.timezone(...)) tz type: builtins.ModuleNotFoundError: No module named 'pytz'
import pytz: builtins.ModuleNotFoundError: No module named 'pytz'

Output under pandas 3.1.0 with pytz installed

pandas 3.1.0 | numpy 2.5.4 | python 3.13.18 | Linux x86_64
pytz installed: True

type(ts.tz): zoneinfo.ZoneInfo
ts.tz.zone: builtins.AttributeError: 'zoneinfo.ZoneInfo' object has no attribute 'zone'
ts.tz.key: America/New_York
ts.tz.localize(...): builtins.AttributeError: 'zoneinfo.ZoneInfo' object has no attribute 'localize'
str(ts.tz): America/New_York

spring-forward 02:30: builtins.ValueError: 2024-03-10 02:30:00 is a nonexistent time due to daylight savings time. Try using the 'nonexistent' argument.
fall-back 01:30: builtins.ValueError: Cannot infer dst time from 2024-11-03 01:30:00, try using the 'ambiguous' argument
except pytz.NonExistentTimeError: builtins.ValueError: 2024-03-10 02:30:00 is a nonexistent time due to daylight savings time. Try using the 'nonexistent' argument.
except ValueError: caught by except ValueError

tz_localize(pytz.timezone(...)) tz type: pytz.tzfile.America/New_York
import pytz: 2026.6

What changed, line by line

What pandas says about it

The pandas 3.0.0 release notes ("Time zones are now represented by standard library zoneinfo instead of pytz by default"; I read the section's source file in the pandas repository on 29 October 2026) say "pandas now uses zoneinfo from the standard library (or datetime.timezone for fixed offsets) as the default timezone representation, for example when passing a timezone string to various methods." They say "pytz timezone objects are still supported when passed directly, but they will no longer be returned by default from string inputs," and that "pandas no longer throws pytz exceptions for timezone operations leading to ambiguous or nonexistent times. These cases will now raise a ValueError."

The same section warns that "pytz is no longer a required dependency of pandas," and says: "If you still need the package, install it directly or use the PyPI extra with pip install pandas[timezone]." I didn't test the [timezone] extra. The notes link a pytz migration guide for code that relied on pytz behaviour.

The earlier changes in this record are df["col"][row] = value no longer changing the DataFrame, text columns becoming str instead of object and datetimes becoming microseconds.


This is the fourth page of a record of what changes between pandas 2 and pandas 3, one reproduced change per page. Why I'm building it, and the weak points of the plan, are in wake log 099. Nothing in it is for sale yet. Each page stays free.