Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
51dfbe7
Make pytz an optional dependency
mroeschke Jun 24, 2024
ff76e49
Start to address tests
mroeschke Jun 24, 2024
10df94e
Fix tests
mroeschke Jun 25, 2024
b76fca7
Fix tests
mroeschke Jun 25, 2024
19d424b
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jun 25, 2024
7d1b37b
Fix test, import optional pytz in conftest
mroeschke Jun 25, 2024
a6d703e
Fix formatting
mroeschke Jun 25, 2024
2153987
Change minimum
mroeschke Jun 25, 2024
ee34f5a
remove type ignore
mroeschke Jun 25, 2024
74263d3
another pa under 17
mroeschke Jun 25, 2024
3be94bf
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jun 26, 2024
6690eaa
Address comments
mroeschke Jun 26, 2024
3b7a526
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jul 1, 2024
db94b7e
Undo file
mroeschke Jul 1, 2024
f9f92af
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jul 3, 2024
dce0c35
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jul 5, 2024
b252886
Merge branch 'main' into deps/pytz/optional
mroeschke Jul 15, 2024
f55e413
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jul 22, 2024
d15f8e1
Merge branch 'deps/pytz/optional' of https://github.com/mroeschke/pan…
mroeschke Jul 22, 2024
e8b2c8c
Merge branch 'main' into deps/pytz/optional
mroeschke Jul 25, 2024
445842e
Merge remote-tracking branch 'upstream/main' into deps/pytz/optional
mroeschke Jul 26, 2024
c59f52e
Fix pyarrow 17 test
mroeschke Jul 26, 2024
62cc55e
Merge branch 'main' into deps/pytz/optional
mroeschke Aug 2, 2024
95415ff
Merge branch 'main' into deps/pytz/optional
mroeschke Aug 7, 2024
be76d11
Merge branch 'main' into deps/pytz/optional
mroeschke Aug 7, 2024
9a0b4c1
Test xpasses on pyarrow 18
mroeschke Aug 7, 2024
589ca49
Merge branch 'main' into deps/pytz/optional
mroeschke Aug 12, 2024
02b9527
Merge branch 'main' into deps/pytz/optional
mroeschke Aug 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix tests
  • Loading branch information
mroeschke committed Jun 25, 2024
commit 10df94e66236ef62f712825c15c44157d5a32143
4 changes: 2 additions & 2 deletions doc/source/user_guide/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2569,7 +2569,7 @@ Ambiguous times when localizing
because daylight savings time (DST) in a local time zone causes some times to occur
twice within one day ("clocks fall back"). The following options are available:

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

* ``'raise'``: Raises a ``pytz.NonExistentTimeError`` (the default behavior)
* ``'raise'``: Raises a ``ValueError`` (the default behavior)
* ``'NaT'``: Replaces nonexistent times with ``NaT``
* ``'shift_forward'``: Shifts nonexistent times forward to the closest real time
* ``'shift_backward'``: Shifts nonexistent times backward to the closest real time
Expand Down
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ with the pip extra ``pip install pandas[timezone]``.


Additionally, pandas no longer throws ``pytz`` exceptions for timezone operations leading to ambiguous or nonexistent
times. These operations will now yield
times. These cases will now raise a ``ValueError``.

.. _whatsnew_300.api_breaking.other:

Expand Down
8 changes: 7 additions & 1 deletion pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -744,9 +744,15 @@ cdef datetime _localize_pydatetime(datetime dt, tzinfo tz):
It also assumes that the `tz` input is not None.
"""
if treat_tz_as_pytz(tz):
import pytz

# datetime.replace with pytz may be incorrect result
# TODO: try to respect `fold` attribute
return tz.localize(dt, is_dst=None)
try:
return tz.localize(dt, is_dst=None)
except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError) as err:
# As of pandas 3.0, we raise ValueErrors instead of pytz exceptions
raise ValueError(str(err)) from err
else:
return dt.replace(tzinfo=tz)

Expand Down
16 changes: 8 additions & 8 deletions pandas/_libs/tslibs/nattype.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ class NaTType(_NaT):
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : {'raise', 'shift_forward', 'shift_backward, 'NaT', \
timedelta}, default 'raise'
Expand All @@ -1031,7 +1031,7 @@ timedelta}, default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down Expand Up @@ -1119,7 +1119,7 @@ timedelta}, default 'raise'
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : {'raise', 'shift_forward', 'shift_backward, 'NaT', \
timedelta}, default 'raise'
Expand All @@ -1132,7 +1132,7 @@ timedelta}, default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Raises
Expand Down Expand Up @@ -1214,7 +1214,7 @@ timedelta}, default 'raise'
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : {'raise', 'shift_forward', 'shift_backward, 'NaT', \
timedelta}, default 'raise'
Expand All @@ -1227,7 +1227,7 @@ timedelta}, default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Raises
Expand Down Expand Up @@ -1378,7 +1378,7 @@ timedelta}, default 'raise'
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : 'shift_forward', 'shift_backward, 'NaT', timedelta, \
default 'raise'
Expand All @@ -1393,7 +1393,7 @@ default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down
16 changes: 8 additions & 8 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2084,7 +2084,7 @@ class Timestamp(_Timestamp):
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : {'raise', 'shift_forward', 'shift_backward, 'NaT', \
timedelta}, default 'raise'
Expand All @@ -2097,7 +2097,7 @@ timedelta}, default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down Expand Up @@ -2187,7 +2187,7 @@ timedelta}, default 'raise'
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : {'raise', 'shift_forward', 'shift_backward, 'NaT', \
timedelta}, default 'raise'
Expand All @@ -2200,7 +2200,7 @@ timedelta}, default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Raises
Expand Down Expand Up @@ -2282,7 +2282,7 @@ timedelta}, default 'raise'
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : {'raise', 'shift_forward', 'shift_backward, 'NaT', \
timedelta}, default 'raise'
Expand All @@ -2295,7 +2295,7 @@ timedelta}, default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Raises
Expand Down Expand Up @@ -2410,7 +2410,7 @@ timedelta}, default 'raise'
* bool contains flags to determine if time is dst or not (note
that this flag is only applicable for ambiguous fall dst dates).
* 'NaT' will return NaT for an ambiguous time.
* 'raise' will raise an AmbiguousTimeError for an ambiguous time.
* 'raise' will raise a ValueError for an ambiguous time.

nonexistent : 'shift_forward', 'shift_backward, 'NaT', timedelta, \
default 'raise'
Expand All @@ -2425,7 +2425,7 @@ default 'raise'
closest existing time.
* 'NaT' will return NaT where there are nonexistent times.
* timedelta objects will shift nonexistent times by the timedelta.
* 'raise' will raise an NonExistentTimeError if there are
* 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -1780,7 +1780,7 @@ def strftime(self, date_format: str) -> npt.NDArray[np.object_]:
a non-DST time (note that this flag is only applicable for
ambiguous times)
- 'NaT' will return NaT where there are ambiguous times
- 'raise' will raise an AmbiguousTimeError if there are ambiguous
- 'raise' will raise a ValueError if there are ambiguous
times.

nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, default 'raise'
Expand All @@ -1793,7 +1793,7 @@ def strftime(self, date_format: str) -> npt.NDArray[np.object_]:
closest existing time
- 'NaT' will return NaT where there are nonexistent times
- timedelta objects will shift nonexistent times by the timedelta
- 'raise' will raise an NonExistentTimeError if there are
- 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ def tz_localize(
non-DST time (note that this flag is only applicable for
ambiguous times)
- 'NaT' will return NaT where there are ambiguous times
- 'raise' will raise an AmbiguousTimeError if there are ambiguous
- 'raise' will raise a ValueError if there are ambiguous
times.

nonexistent : 'shift_forward', 'shift_backward, 'NaT', timedelta, \
Expand All @@ -986,7 +986,7 @@ def tz_localize(
closest existing time
- 'NaT' will return NaT where there are nonexistent times
- timedelta objects will shift nonexistent times by the timedelta
- 'raise' will raise an NonExistentTimeError if there are
- 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10554,7 +10554,7 @@ def tz_localize(
a non-DST time (note that this flag is only applicable for
ambiguous times)
- 'NaT' will return NaT where there are ambiguous times
- 'raise' will raise an AmbiguousTimeError if there are ambiguous
- 'raise' will raise a ValueError if there are ambiguous
times.
nonexistent : str, default 'raise'
A nonexistent time does not exist in a particular timezone
Expand All @@ -10566,7 +10566,7 @@ def tz_localize(
closest existing time
- 'NaT' will return NaT where there are nonexistent times
- timedelta objects will shift nonexistent times by the timedelta
- 'raise' will raise an NonExistentTimeError if there are
- 'raise' will raise a ValueError if there are
nonexistent times.

Returns
Expand Down
5 changes: 2 additions & 3 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import warnings

import numpy as np
import pytz

from pandas._libs import (
NaT,
Expand Down Expand Up @@ -162,7 +161,7 @@ class DatetimeIndex(DatetimeTimedeltaMixin):
non-DST time (note that this flag is only applicable for ambiguous
times)
- 'NaT' will return NaT where there are ambiguous times
- 'raise' will raise an AmbiguousTimeError if there are ambiguous times.
- 'raise' will raise a ValueError if there are ambiguous times.
dayfirst : bool, default False
If True, parse dates in `data` with the day first order.
yearfirst : bool, default False
Expand Down Expand Up @@ -591,7 +590,7 @@ def get_loc(self, key):
elif isinstance(key, str):
try:
parsed, reso = self._parse_with_reso(key)
except (ValueError, pytz.NonExistentTimeError) as err:
except ValueError as err:
raise KeyError(key) from err
self._disallow_mismatched_indexing(parsed)

Expand Down
17 changes: 8 additions & 9 deletions pandas/tests/indexes/datetimes/methods/test_tz_localize.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from dateutil.tz import gettz
import numpy as np
import pytest
import pytz

from pandas import (
DatetimeIndex,
Expand Down Expand Up @@ -69,10 +68,10 @@ def test_dti_tz_localize_nonexistent_raise_coerce(self):
times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"]
index = DatetimeIndex(times)
tz = "US/Eastern"
with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)):
with pytest.raises(ValueError, match="|".join(times)):
index.tz_localize(tz=tz)

with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)):
with pytest.raises(ValueError, match="|".join(times)):
index.tz_localize(tz=tz, nonexistent="raise")

result = index.tz_localize(tz=tz, nonexistent="NaT")
Expand All @@ -85,7 +84,7 @@ def test_dti_tz_localize_ambiguous_infer(self, tz):
# November 6, 2011, fall back, repeat 2 AM hour
# With no repeated hours, we cannot infer the transition
dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour())
with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
with pytest.raises(ValueError, match="Cannot infer dst time"):
dr.tz_localize(tz)

def test_dti_tz_localize_ambiguous_infer2(self, tz, unit):
Expand Down Expand Up @@ -117,7 +116,7 @@ def test_dti_tz_localize_ambiguous_infer3(self, tz):
def test_dti_tz_localize_ambiguous_times(self, tz):
# March 13, 2011, spring forward, skip from 2 AM to 3 AM
dr = date_range(datetime(2011, 3, 13, 1, 30), periods=3, freq=offsets.Hour())
with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:30:00"):
with pytest.raises(ValueError, match="2011-03-13 02:30:00"):
dr.tz_localize(tz)

# after dst transition, it works
Expand All @@ -127,7 +126,7 @@ def test_dti_tz_localize_ambiguous_times(self, tz):

# November 6, 2011, fall back, repeat 2 AM hour
dr = date_range(datetime(2011, 11, 6, 1, 30), periods=3, freq=offsets.Hour())
with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
with pytest.raises(ValueError, match="Cannot infer dst time"):
dr.tz_localize(tz)

# UTC is OK
Expand Down Expand Up @@ -163,11 +162,11 @@ def test_dti_tz_localize(self, prefix):
tm.assert_numpy_array_equal(dti3.values, dti_utc.values)

dti = date_range(start="11/6/2011 1:59", end="11/6/2011 2:00", freq="ms")
with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
with pytest.raises(ValueError, match="Cannot infer dst time"):
dti.tz_localize(tzstr)

dti = date_range(start="3/13/2011 1:59", end="3/13/2011 2:00", freq="ms")
with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:00:00"):
with pytest.raises(ValueError, match="2011-03-13 02:00:00"):
dti.tz_localize(tzstr)

def test_dti_tz_localize_utc_conversion(self, tz):
Expand All @@ -184,7 +183,7 @@ def test_dti_tz_localize_utc_conversion(self, tz):
# DST ambiguity, this should fail
rng = date_range("3/11/2012", "3/12/2012", freq="30min")
# Is this really how it should fail??
with pytest.raises(pytz.NonExistentTimeError, match="2012-03-11 02:00:00"):
with pytest.raises(ValueError, match="2012-03-11 02:00:00"):
rng.tz_localize(tz)

def test_dti_tz_localize_roundtrip(self, tz_aware_fixture):
Expand Down
10 changes: 5 additions & 5 deletions pandas/tests/indexes/datetimes/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from dateutil.tz import gettz
import numpy as np
import pytest
import pytz

from pandas._libs.tslibs import (
astype_overflowsafe,
Expand Down Expand Up @@ -750,7 +749,7 @@ def test_disallow_setting_tz(self):
[
None,
"America/Los_Angeles",
pytz.timezone("America/Los_Angeles"),
zoneinfo.ZoneInfo("America/Los_Angeles"),
Timestamp("2000", tz="America/Los_Angeles").tz,
],
)
Expand All @@ -765,8 +764,8 @@ def test_constructor_start_end_with_tz(self, tz):
freq="D",
)
tm.assert_index_equal(result, expected)
# Especially assert that the timezone is consistent for pytz
assert pytz.timezone("America/Los_Angeles") is result.tz
# Especially assert that the timezone is consistent for zoneinfo
assert zoneinfo.ZoneInfo("America/Los_Angeles") is result.tz

@pytest.mark.parametrize("tz", ["US/Pacific", "US/Eastern", "Asia/Tokyo"])
def test_constructor_with_non_normalized_pytz(self, tz):
Expand Down Expand Up @@ -984,6 +983,7 @@ def test_dti_ambiguous_matches_timestamp(self, tz, use_str, box_cls, request):
# GH#47471 check that we get the same raising behavior in the DTI
# constructor and Timestamp constructor
if isinstance(tz, str) and tz.startswith("pytz/"):
pytz = pytest.importorskip("pytz")
tz = pytz.timezone(tz.removeprefix("pytz/"))
dtstr = "2013-11-03 01:59:59.999999"
item = dtstr
Expand All @@ -1000,7 +1000,7 @@ def test_dti_ambiguous_matches_timestamp(self, tz, use_str, box_cls, request):
mark = pytest.mark.xfail(reason="We implicitly get fold=0.")
request.applymarker(mark)

with pytest.raises(pytz.AmbiguousTimeError, match=dtstr):
with pytest.raises(ValueError, match=dtstr):
box_cls(item, tz=tz)

@pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"])
Expand Down
Loading