Skip to content

Commit 96d732e

Browse files
authored
DEPS: Make pytz an optional dependency (pandas-dev#59089)
* Make pytz an optional dependency * Start to address tests * Fix tests * Fix tests * Fix test, import optional pytz in conftest * Fix formatting * Change minimum * remove type ignore * another pa under 17 * Address comments * Undo file * Fix pyarrow 17 test * Test xpasses on pyarrow 18
1 parent 2f75794 commit 96d732e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+222
-186
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
/opt/python/cp311-cp311/bin/python -m venv ~/virtualenvs/pandas-dev
5757
. ~/virtualenvs/pandas-dev/bin/activate
5858
python -m pip install --no-cache-dir -U pip wheel setuptools meson-python==0.13.1 meson[ninja]==1.2.1
59-
python -m pip install --no-cache-dir versioneer[toml] cython numpy python-dateutil pytz pytest>=7.3.2 pytest-xdist>=3.4.0 hypothesis>=6.84.0
59+
python -m pip install --no-cache-dir versioneer[toml] cython numpy python-dateutil pytest>=7.3.2 pytest-xdist>=3.4.0 hypothesis>=6.84.0
6060
python -m pip install --no-cache-dir --no-build-isolation -e . --config-settings=setup-args="--werror"
6161
python -m pip list --no-cache-dir
6262
export PANDAS_CI=1

.github/workflows/unit-tests.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ jobs:
236236
. ~/virtualenvs/pandas-dev/bin/activate
237237
python -m pip install --no-cache-dir -U pip wheel setuptools meson[ninja]==1.2.1 meson-python==0.13.1
238238
python -m pip install numpy --config-settings=setup-args="-Dallow-noblas=true"
239-
python -m pip install --no-cache-dir versioneer[toml] cython python-dateutil pytz pytest>=7.3.2 pytest-xdist>=3.4.0 hypothesis>=6.84.0
239+
python -m pip install --no-cache-dir versioneer[toml] cython python-dateutil pytest>=7.3.2 pytest-xdist>=3.4.0 hypothesis>=6.84.0
240240
python -m pip install --no-cache-dir --no-build-isolation -e . --config-settings=setup-args="--werror"
241241
python -m pip list --no-cache-dir
242242
export PANDAS_CI=1
@@ -274,7 +274,7 @@ jobs:
274274
/opt/python/cp311-cp311/bin/python -m venv ~/virtualenvs/pandas-dev
275275
. ~/virtualenvs/pandas-dev/bin/activate
276276
python -m pip install --no-cache-dir -U pip wheel setuptools meson-python==0.13.1 meson[ninja]==1.2.1
277-
python -m pip install --no-cache-dir versioneer[toml] cython numpy python-dateutil pytz pytest>=7.3.2 pytest-xdist>=3.4.0 hypothesis>=6.84.0
277+
python -m pip install --no-cache-dir versioneer[toml] cython numpy python-dateutil pytest>=7.3.2 pytest-xdist>=3.4.0 hypothesis>=6.84.0
278278
python -m pip install --no-cache-dir --no-build-isolation -e . --config-settings=setup-args="--werror"
279279
python -m pip list --no-cache-dir
280280
@@ -295,7 +295,7 @@ jobs:
295295
# In general, this will remain frozen(present, but not running) until:
296296
# - The next unreleased Python version has released beta 1
297297
# - This version should be available on GitHub Actions.
298-
# - Our required build/runtime dependencies(numpy, pytz, Cython, python-dateutil)
298+
# - Our required build/runtime dependencies(numpy, Cython, python-dateutil)
299299
# support that unreleased Python version.
300300
# To unfreeze, comment out the ``if: false`` condition, and make sure you update
301301
# the name of the workflow and Python version in actions/setup-python ``python-version:``
@@ -348,7 +348,7 @@ jobs:
348348
python -m pip install --upgrade pip setuptools wheel meson[ninja]==1.2.1 meson-python==0.13.1
349349
python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy
350350
python -m pip install versioneer[toml]
351-
python -m pip install python-dateutil pytz tzdata cython hypothesis>=6.84.0 pytest>=7.3.2 pytest-xdist>=3.4.0 pytest-cov
351+
python -m pip install python-dateutil tzdata cython hypothesis>=6.84.0 pytest>=7.3.2 pytest-xdist>=3.4.0 pytest-cov
352352
python -m pip install -ve . --no-build-isolation --no-index --no-deps --config-settings=setup-args="--werror"
353353
python -m pip list
354354

ci/deps/actions-310-minimum_versions.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ dependencies:
2323
# required dependencies
2424
- python-dateutil=2.8.2
2525
- numpy=1.23.5
26-
- pytz=2020.1
2726

2827
# optional dependencies
2928
- beautifulsoup4=4.11.2
@@ -49,6 +48,7 @@ dependencies:
4948
- pyreadstat=1.2.0
5049
- pytables=3.8.0
5150
- python-calamine=0.1.7
51+
- pytz=2023.4
5252
- pyxlsb=1.0.10
5353
- s3fs=2022.11.0
5454
- scipy=1.10.0

ci/deps/actions-310.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dependencies:
2121
# required dependencies
2222
- python-dateutil
2323
- numpy
24-
- pytz
2524

2625
# optional dependencies
2726
- beautifulsoup4>=4.11.2
@@ -47,6 +46,7 @@ dependencies:
4746
- pyreadstat>=1.2.0
4847
- pytables>=3.8.0
4948
- python-calamine>=0.1.7
49+
- pytz>=2023.4
5050
- pyxlsb>=1.0.10
5151
- s3fs>=2022.11.0
5252
- scipy>=1.10.0

ci/deps/actions-311-downstream_compat.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ dependencies:
2222
# required dependencies
2323
- python-dateutil
2424
- numpy
25-
- pytz
2625

2726
# optional dependencies
2827
- beautifulsoup4>=4.11.2
@@ -48,6 +47,7 @@ dependencies:
4847
- pyreadstat>=1.2.0
4948
- pytables>=3.8.0
5049
- python-calamine>=0.1.7
50+
- pytz>=2023.4
5151
- pyxlsb>=1.0.10
5252
- s3fs>=2022.11.0
5353
- scipy>=1.10.0

ci/deps/actions-311-numpydev.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ dependencies:
1818

1919
# pandas dependencies
2020
- python-dateutil
21-
- pytz
2221
- pip
2322

2423
- pip:

ci/deps/actions-311-pyarrownightly.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ dependencies:
1919
# required dependencies
2020
- python-dateutil
2121
- numpy<2
22-
- pytz
2322
- pip
2423

2524
- pip:

ci/deps/actions-311.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dependencies:
2121
# required dependencies
2222
- python-dateutil
2323
- numpy
24-
- pytz
2524

2625
# optional dependencies
2726
- beautifulsoup4>=4.11.2
@@ -47,6 +46,7 @@ dependencies:
4746
- pyreadstat>=1.2.0
4847
- pytables>=3.8.0
4948
- python-calamine>=0.1.7
49+
- pytz>=2023.4
5050
- pyxlsb>=1.0.10
5151
- s3fs>=2022.11.0
5252
- scipy>=1.10.0

ci/deps/actions-312.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dependencies:
2121
# required dependencies
2222
- python-dateutil
2323
- numpy
24-
- pytz
2524

2625
# optional dependencies
2726
- beautifulsoup4>=4.11.2
@@ -47,6 +46,7 @@ dependencies:
4746
- pyreadstat>=1.2.0
4847
- pytables>=3.8.0
4948
- python-calamine>=0.1.7
49+
- pytz>=2023.4
5050
- pyxlsb>=1.0.10
5151
- s3fs>=2022.11.0
5252
- scipy>=1.10.0

ci/deps/actions-pypy-39.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,5 @@ dependencies:
2222
# required
2323
- numpy
2424
- python-dateutil
25-
- pytz
2625
- pip:
2726
- tzdata>=2022.7

ci/deps/circle-311-arm64.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dependencies:
2121
# required dependencies
2222
- python-dateutil
2323
- numpy
24-
- pytz
2524

2625
# optional dependencies
2726
- beautifulsoup4>=4.11.2
@@ -47,6 +46,7 @@ dependencies:
4746
- pyreadstat>=1.2.0
4847
- pytables>=3.8.0
4948
- python-calamine>=0.1.7
49+
- pytz>=2023.4
5050
- pyxlsb>=1.0.10
5151
- s3fs>=2022.11.0
5252
- scipy>=1.10.0

ci/meta.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ requirements:
3737
- numpy >=1.21.6 # [py<311]
3838
- numpy >=1.23.2 # [py>=311]
3939
- python-dateutil >=2.8.2
40-
- pytz >=2020.1
4140
- python-tzdata >=2022.7
4241

4342
test:

doc/source/getting_started/install.rst

+11-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ Package Minimum support
205205
================================================================ ==========================
206206
`NumPy <https://numpy.org>`__ 1.23.5
207207
`python-dateutil <https://dateutil.readthedocs.io/en/stable/>`__ 2.8.2
208-
`pytz <https://pypi.org/project/pytz/>`__ 2020.1
209208
`tzdata <https://pypi.org/project/tzdata/>`__ 2022.7
210209
================================================================ ==========================
211210

@@ -419,3 +418,14 @@ Dependency Minimum Version pip extra Notes
419418
========================= ================== =============== =============================================================
420419
Zstandard 0.19.0 compression Zstandard compression
421420
========================= ================== =============== =============================================================
421+
422+
Timezone
423+
^^^^^^^^
424+
425+
Installable with ``pip install "pandas[timezone]"``
426+
427+
========================= ================== =================== =============================================================
428+
Dependency Minimum Version pip extra Notes
429+
========================= ================== =================== =============================================================
430+
pytz 2023.4 timezone Alternative timezone library to ``zoneinfo``.
431+
========================= ================== =================== =============================================================

doc/source/user_guide/timeseries.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -2569,7 +2569,7 @@ Ambiguous times when localizing
25692569
because daylight savings time (DST) in a local time zone causes some times to occur
25702570
twice within one day ("clocks fall back"). The following options are available:
25712571

2572-
* ``'raise'``: Raises a ``pytz.AmbiguousTimeError`` (the default behavior)
2572+
* ``'raise'``: Raises a ``ValueError`` (the default behavior)
25732573
* ``'infer'``: Attempt to determine the correct offset base on the monotonicity of the timestamps
25742574
* ``'NaT'``: Replaces ambiguous times with ``NaT``
25752575
* ``bool``: ``True`` represents a DST time, ``False`` represents non-DST time. An array-like of ``bool`` values is supported for a sequence of times.
@@ -2604,7 +2604,7 @@ A DST transition may also shift the local time ahead by 1 hour creating nonexist
26042604
local times ("clocks spring forward"). The behavior of localizing a timeseries with nonexistent times
26052605
can be controlled by the ``nonexistent`` argument. The following options are available:
26062606

2607-
* ``'raise'``: Raises a ``pytz.NonExistentTimeError`` (the default behavior)
2607+
* ``'raise'``: Raises a ``ValueError`` (the default behavior)
26082608
* ``'NaT'``: Replaces nonexistent times with ``NaT``
26092609
* ``'shift_forward'``: Shifts nonexistent times forward to the closest real time
26102610
* ``'shift_backward'``: Shifts nonexistent times backward to the closest real time

doc/source/whatsnew/v3.0.0.rst

+33
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ Optional libraries below the lowest tested version may still work, but are not c
221221
+------------------------+---------------------+
222222
| Package | New Minimum Version |
223223
+========================+=====================+
224+
| pytz | 2023.4 |
225+
+------------------------+---------------------+
224226
| fastparquet | 2023.10.0 |
225227
+------------------------+---------------------+
226228
| adbc-driver-postgresql | 0.10.0 |
@@ -230,6 +232,37 @@ Optional libraries below the lowest tested version may still work, but are not c
230232

231233
See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for more.
232234

235+
.. _whatsnew_300.api_breaking.pytz:
236+
237+
``pytz`` now an optional dependency
238+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239+
240+
pandas now uses :py:mod:`zoneinfo` from the standard library as the default timezone implementation when passing a timezone
241+
string to various methods. (:issue:`34916`)
242+
243+
*Old behavior:*
244+
245+
.. code-block:: ipython
246+
247+
In [1]: ts = pd.Timestamp(2024, 1, 1).tz_localize("US/Pacific")
248+
In [2]: ts.tz
249+
<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
250+
251+
*New behavior:*
252+
253+
.. ipython:: python
254+
255+
ts = pd.Timestamp(2024, 1, 1).tz_localize("US/Pacific")
256+
ts.tz
257+
258+
``pytz`` timezone objects are still supported when passed directly, but they will no longer be returned by default
259+
from string inputs. Moreover, ``pytz`` is no longer a required dependency of pandas, but can be installed
260+
with the pip extra ``pip install pandas[timezone]``.
261+
262+
263+
Additionally, pandas no longer throws ``pytz`` exceptions for timezone operations leading to ambiguous or nonexistent
264+
times. These cases will now raise a ``ValueError``.
265+
233266
.. _whatsnew_300.api_breaking.other:
234267

235268
Other API changes

environment.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ dependencies:
2424
# required dependencies
2525
- python-dateutil
2626
- numpy<2
27-
- pytz
2827

2928
# optional dependencies
3029
- beautifulsoup4>=4.11.2
@@ -50,6 +49,7 @@ dependencies:
5049
- pyreadstat>=1.2.0
5150
- pytables>=3.8.0
5251
- python-calamine>=0.1.7
52+
- pytz>=2023.4
5353
- pyxlsb>=1.0.10
5454
- s3fs>=2022.11.0
5555
- scipy>=1.10.0

pandas/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
__docformat__ = "restructuredtext"
44

55
# Let users know if they're missing any of our hard dependencies
6-
_hard_dependencies = ("numpy", "pytz", "dateutil")
6+
_hard_dependencies = ("numpy", "dateutil")
77
_missing_dependencies = []
88

99
for _dependency in _hard_dependencies:

pandas/_libs/tslibs/conversion.pyx

+10-3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ from pandas._libs.tslibs.timestamps cimport _Timestamp
6969
from pandas._libs.tslibs.timezones cimport (
7070
get_utcoffset,
7171
is_utc,
72+
treat_tz_as_pytz,
7273
)
7374
from pandas._libs.tslibs.tzconversion cimport (
7475
Localizer,
@@ -747,11 +748,17 @@ cdef datetime _localize_pydatetime(datetime dt, tzinfo tz):
747748
identically, i.e. discards nanos from Timestamps.
748749
It also assumes that the `tz` input is not None.
749750
"""
750-
try:
751+
if treat_tz_as_pytz(tz):
752+
import pytz
753+
751754
# datetime.replace with pytz may be incorrect result
752755
# TODO: try to respect `fold` attribute
753-
return tz.localize(dt, is_dst=None)
754-
except AttributeError:
756+
try:
757+
return tz.localize(dt, is_dst=None)
758+
except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError) as err:
759+
# As of pandas 3.0, we raise ValueErrors instead of pytz exceptions
760+
raise ValueError(str(err)) from err
761+
else:
755762
return dt.replace(tzinfo=tz)
756763

757764

pandas/_libs/tslibs/nattype.pyx

+8-8
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,7 @@ class NaTType(_NaT):
10671067
* bool contains flags to determine if time is dst or not (note
10681068
that this flag is only applicable for ambiguous fall dst dates).
10691069
* 'NaT' will return NaT for an ambiguous time.
1070-
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
1070+
* 'raise' will raise a ValueError for an ambiguous time.
10711071
10721072
nonexistent : {'raise', 'shift_forward', 'shift_backward', 'NaT', \
10731073
timedelta}, default 'raise'
@@ -1080,7 +1080,7 @@ timedelta}, default 'raise'
10801080
closest existing time.
10811081
* 'NaT' will return NaT where there are nonexistent times.
10821082
* timedelta objects will shift nonexistent times by the timedelta.
1083-
* 'raise' will raise an NonExistentTimeError if there are
1083+
* 'raise' will raise a ValueError if there are
10841084
nonexistent times.
10851085
10861086
Returns
@@ -1168,7 +1168,7 @@ timedelta}, default 'raise'
11681168
* bool contains flags to determine if time is dst or not (note
11691169
that this flag is only applicable for ambiguous fall dst dates).
11701170
* 'NaT' will return NaT for an ambiguous time.
1171-
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
1171+
* 'raise' will raise a ValueError for an ambiguous time.
11721172
11731173
nonexistent : {'raise', 'shift_forward', 'shift_backward', 'NaT', \
11741174
timedelta}, default 'raise'
@@ -1181,7 +1181,7 @@ timedelta}, default 'raise'
11811181
closest existing time.
11821182
* 'NaT' will return NaT where there are nonexistent times.
11831183
* timedelta objects will shift nonexistent times by the timedelta.
1184-
* 'raise' will raise an NonExistentTimeError if there are
1184+
* 'raise' will raise a ValueError if there are
11851185
nonexistent times.
11861186
11871187
Raises
@@ -1263,7 +1263,7 @@ timedelta}, default 'raise'
12631263
* bool contains flags to determine if time is dst or not (note
12641264
that this flag is only applicable for ambiguous fall dst dates).
12651265
* 'NaT' will return NaT for an ambiguous time.
1266-
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
1266+
* 'raise' will raise a ValueError for an ambiguous time.
12671267
12681268
nonexistent : {'raise', 'shift_forward', 'shift_backward', 'NaT', \
12691269
timedelta}, default 'raise'
@@ -1276,7 +1276,7 @@ timedelta}, default 'raise'
12761276
closest existing time.
12771277
* 'NaT' will return NaT where there are nonexistent times.
12781278
* timedelta objects will shift nonexistent times by the timedelta.
1279-
* 'raise' will raise an NonExistentTimeError if there are
1279+
* 'raise' will raise a ValueError if there are
12801280
nonexistent times.
12811281
12821282
Raises
@@ -1427,7 +1427,7 @@ timedelta}, default 'raise'
14271427
* bool contains flags to determine if time is dst or not (note
14281428
that this flag is only applicable for ambiguous fall dst dates).
14291429
* 'NaT' will return NaT for an ambiguous time.
1430-
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
1430+
* 'raise' will raise a ValueError for an ambiguous time.
14311431
14321432
nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
14331433
default 'raise'
@@ -1442,7 +1442,7 @@ default 'raise'
14421442
closest existing time.
14431443
* 'NaT' will return NaT where there are nonexistent times.
14441444
* timedelta objects will shift nonexistent times by the timedelta.
1445-
* 'raise' will raise an NonExistentTimeError if there are
1445+
* 'raise' will raise a ValueError if there are
14461446
nonexistent times.
14471447
14481448
Returns

0 commit comments

Comments
 (0)