From 951a93f4bd825c663882989466682f0e7fdff99b Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 31 Oct 2025 14:23:10 -0700 Subject: [PATCH 1/2] DEPR: inconsistent string parsing in DatetimeIndex.indexer_at_time --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/indexes/datetimes.py | 32 +++++++++++++++++++++- pandas/tests/frame/methods/test_at_time.py | 19 +++++++++++++ pandas/tests/generic/test_finalize.py | 5 ++-- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index b1dc78bbf8020..e55bdca65475f 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -735,6 +735,7 @@ Other Deprecations - Deprecated using ``epoch`` date format in :meth:`DataFrame.to_json` and :meth:`Series.to_json`, use ``iso`` instead. (:issue:`57063`) - Deprecated allowing ``fill_value`` that cannot be held in the original dtype (excepting NA values for integer and bool dtypes) in :meth:`Series.unstack` and :meth:`DataFrame.unstack` (:issue:`12189`, :issue:`53868`) - Deprecated allowing ``fill_value`` that cannot be held in the original dtype (excepting NA values for integer and bool dtypes) in :meth:`Series.shift` and :meth:`DataFrame.shift` (:issue:`53802`) +- Deprecated allowing strings representing full dates in :meth:`DataFrame.at_time` and :meth:`Series.at_time` (:issue:`50839`) - Deprecated backward-compatibility behavior for :meth:`DataFrame.select_dtypes` matching "str" dtype when ``np.object_`` is specified (:issue:`61916`) - Deprecated option "future.no_silent_downcasting", as it is no longer used. In a future version accessing this option will raise (:issue:`59502`) - Deprecated silent casting of non-datetime 'other' to datetime in :meth:`Series.combine_first` (:issue:`62931`) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 6451e55f7fc4d..adec82944813c 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -767,7 +767,37 @@ def indexer_at_time(self, time, asof: bool = False) -> npt.NDArray[np.intp]: if isinstance(time, str): from dateutil.parser import parse - time = parse(time).time() + orig = time + try: + alt = to_time(time) + except ValueError: + warnings.warn( + # GH#50839 + f"The string '{orig}' cannot be parsed using pd.core.tools.to_time " + f"and in a future version will raise. " + "Use an unambiguous time string format or explicitly cast to " + "`datetime.time` before calling.", + Pandas4Warning, + stacklevel=find_stack_level(), + ) + time = parse(time).time() + else: + try: + time = parse(time).time() + except ValueError: + # e.g. '23550' raises dateutil.parser._parser.ParserError + time = alt + if alt != time: + warnings.warn( + # GH#50839 + f"The string '{orig}' is currently parsed as {time} " + f"but in a future version will be parsed as {alt}, consistent" + "with `between_time` behavior. To avoid this warning, " + "use an unambiguous string format or explicitly cast to " + "`datetime.time` before calling.", + Pandas4Warning, + stacklevel=find_stack_level(), + ) if time.tzinfo: if self.tz is None: diff --git a/pandas/tests/frame/methods/test_at_time.py b/pandas/tests/frame/methods/test_at_time.py index b69db80dee446..0a888ec935ff9 100644 --- a/pandas/tests/frame/methods/test_at_time.py +++ b/pandas/tests/frame/methods/test_at_time.py @@ -8,6 +8,7 @@ import pytest from pandas._libs.tslibs import timezones +from pandas.errors import Pandas4Warning from pandas import ( DataFrame, @@ -132,3 +133,21 @@ def test_at_time_datetimeindex(self): tm.assert_frame_equal(result, expected) tm.assert_frame_equal(result, expected2) assert len(result) == 4 + + def test_at_time_ambiguous_format_deprecation(self): + # GH#50839 + rng = date_range("1/1/2000", "1/5/2000", freq="5min") + ts = DataFrame(list(range(len(rng))), index=rng) + + msg1 = "The string '.*' cannot be parsed" + with tm.assert_produces_warning(Pandas4Warning, match=msg1): + ts.at_time("2022-12-12 00:00:00") + with tm.assert_produces_warning(Pandas4Warning, match=msg1): + ts.at_time("2022-12-12 00:00:00 +09:00") + with tm.assert_produces_warning(Pandas4Warning, match=msg1): + ts.at_time("2022-12-12 00:00:00.000000") + + # The dateutil parser raises on these, so we can give the future behavior + # immediately using pd.core.tools.to_time + ts.at_time("235500") + ts.at_time("115500PM") diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py index 641d9518adb9a..de972f2f2f9c7 100644 --- a/pandas/tests/generic/test_finalize.py +++ b/pandas/tests/generic/test_finalize.py @@ -3,6 +3,7 @@ """ from copy import deepcopy +from datetime import time import operator import re @@ -280,12 +281,12 @@ ( pd.Series, (1, pd.date_range("2000", periods=4)), - operator.methodcaller("at_time", "12:00"), + operator.methodcaller("at_time", time(12)), ), ( pd.DataFrame, ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)), - operator.methodcaller("at_time", "12:00"), + operator.methodcaller("at_time", time(12)), ), ( pd.Series, From 78f747fd11704956f257e13e7e658140db3e3a8a Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 4 Nov 2025 08:05:58 -0800 Subject: [PATCH 2/2] Smaller test-data --- pandas/tests/frame/methods/test_at_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/frame/methods/test_at_time.py b/pandas/tests/frame/methods/test_at_time.py index 0a888ec935ff9..bd63d5bffceeb 100644 --- a/pandas/tests/frame/methods/test_at_time.py +++ b/pandas/tests/frame/methods/test_at_time.py @@ -136,7 +136,7 @@ def test_at_time_datetimeindex(self): def test_at_time_ambiguous_format_deprecation(self): # GH#50839 - rng = date_range("1/1/2000", "1/5/2000", freq="5min") + rng = date_range("1/1/2000", "1/5/2000", freq="125min") ts = DataFrame(list(range(len(rng))), index=rng) msg1 = "The string '.*' cannot be parsed"