From d93dd6da24eea6b48f33f3331ed177e8765ad8e2 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 27 Sep 2022 16:03:01 -0700 Subject: [PATCH 1/3] BUG: DTI-datetime_scalar preserve freq --- doc/source/whatsnew/v1.6.0.rst | 1 + pandas/core/arrays/datetimelike.py | 11 ++++++++++- pandas/tests/indexes/datetimes/test_datetime.py | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index 566e3d7c66b79..754bcd813a4d2 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -165,6 +165,7 @@ Datetimelike ^^^^^^^^^^^^ - Bug in :func:`pandas.infer_freq`, raising ``TypeError`` when inferred on :class:`RangeIndex` (:issue:`47084`) - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) +- Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`?`) Timedelta ^^^^^^^^^ diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 5a92cc3c8509c..1482547369e88 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1186,7 +1186,16 @@ def _sub_datetimelike_scalar(self, other: datetime | np.datetime64): i8 = self.asi8 result = checked_add_with_arr(i8, -other.value, arr_mask=self._isnan) - return result.view("timedelta64[ns]") + res_m8 = result.view(f"timedelta64[{self._unit}]") + + new_freq = None + if isinstance(self.freq, Tick): + # adding a scalar preserves freq + new_freq = self.freq + + from pandas.core.arrays import TimedeltaArray + + return TimedeltaArray._simple_new(res_m8, dtype=res_m8.dtype, freq=new_freq) @final def _sub_datetime_arraylike(self, other): diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index 5c85221c5a753..2a3b1071d4824 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -17,6 +17,14 @@ class TestDatetimeIndex: + def test_sub_datetime_preserves_reso(self, tz_naive_fixture): + dti = date_range("2016-01-01", periods=12, tz=tz_naive_fixture) + + res = dti - dti[0] + expected = pd.timedelta_range("0 Days", "11 Days") + tm.assert_index_equal(res, expected) + assert res.freq == expected.freq + def test_time_overflow_for_32bit_machines(self): # GH8943. On some machines NumPy defaults to np.int32 (for example, # 32-bit Linux machines). In the function _generate_regular_range From f4d532887e5b11aeee3b883561a2f009f4bf0bd2 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 27 Sep 2022 16:03:47 -0700 Subject: [PATCH 2/3] GH ref --- doc/source/whatsnew/v1.6.0.rst | 2 +- pandas/tests/indexes/datetimes/test_datetime.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index 754bcd813a4d2..9f793532e5e6b 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -165,7 +165,7 @@ Datetimelike ^^^^^^^^^^^^ - Bug in :func:`pandas.infer_freq`, raising ``TypeError`` when inferred on :class:`RangeIndex` (:issue:`47084`) - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) -- Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`?`) +- Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`) Timedelta ^^^^^^^^^ diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index 2a3b1071d4824..bdef2a4774de9 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -18,6 +18,7 @@ class TestDatetimeIndex: def test_sub_datetime_preserves_reso(self, tz_naive_fixture): + # GH#48818 dti = date_range("2016-01-01", periods=12, tz=tz_naive_fixture) res = dti - dti[0] From 4339bd5e46c1cb9221c58dcbf7a179ef3e788520 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 28 Sep 2022 12:40:46 -0700 Subject: [PATCH 3/3] add xfail test across DST boundary --- .../tests/indexes/datetimes/test_datetime.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index bdef2a4774de9..e770aa9fe5beb 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -17,7 +17,7 @@ class TestDatetimeIndex: - def test_sub_datetime_preserves_reso(self, tz_naive_fixture): + def test_sub_datetime_preserves_freq(self, tz_naive_fixture): # GH#48818 dti = date_range("2016-01-01", periods=12, tz=tz_naive_fixture) @@ -26,6 +26,27 @@ def test_sub_datetime_preserves_reso(self, tz_naive_fixture): tm.assert_index_equal(res, expected) assert res.freq == expected.freq + @pytest.mark.xfail( + reason="The inherited freq is incorrect bc dti.freq is incorrect " + "https://github.com/pandas-dev/pandas/pull/48818/files#r982793461" + ) + def test_sub_datetime_preserves_freq_across_dst(self): + # GH#48818 + ts = Timestamp("2016-03-11", tz="US/Pacific") + dti = date_range(ts, periods=4) + + res = dti - dti[0] + expected = pd.TimedeltaIndex( + [ + pd.Timedelta(days=0), + pd.Timedelta(days=1), + pd.Timedelta(days=2), + pd.Timedelta(days=2, hours=23), + ] + ) + tm.assert_index_equal(res, expected) + assert res.freq == expected.freq + def test_time_overflow_for_32bit_machines(self): # GH8943. On some machines NumPy defaults to np.int32 (for example, # 32-bit Linux machines). In the function _generate_regular_range