diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 0682e179a7640..8c65a7268aa92 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -458,6 +458,7 @@ Datetimelike - Bug in :class:`Timestamp` arithmetic when adding or subtracting a ``np.ndarray`` with ``timedelta64`` dtype (:issue:`33296`) - Bug in :meth:`DatetimeIndex.to_period` not infering the frequency when called with no arguments (:issue:`33358`) - Bug in :meth:`DatetimeIndex.tz_localize` incorrectly retaining ``freq`` in some cases where the original freq is no longer valid (:issue:`30511`) +- Bug in :meth:`DatetimeIndex.intersection` losing ``freq`` and timezone in some cases (:issue:`33604`) Timedelta ^^^^^^^^^ diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index b83b64c144681..53205d3b402d0 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -724,10 +724,10 @@ def intersection(self, other, sort=False): start = right[0] if end < start: - return type(self)(data=[]) + return type(self)(data=[], dtype=self.dtype, freq=self.freq) else: lslice = slice(*left.slice_locs(start, end)) - left_chunk = left.values[lslice] + left_chunk = left._values[lslice] return self._shallow_copy(left_chunk) def _can_fast_union(self, other) -> bool: diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index ff15cded19b1c..08b8e710237c5 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -75,8 +75,9 @@ def test_getitem(self): def test_dti_business_getitem(self): rng = pd.bdate_range(START, END) smaller = rng[:5] - exp = DatetimeIndex(rng.view(np.ndarray)[:5]) + exp = DatetimeIndex(rng.view(np.ndarray)[:5], freq="B") tm.assert_index_equal(smaller, exp) + assert smaller.freq == exp.freq assert smaller.freq == rng.freq @@ -102,8 +103,9 @@ def test_dti_business_getitem_matplotlib_hackaround(self): def test_dti_custom_getitem(self): rng = pd.bdate_range(START, END, freq="C") smaller = rng[:5] - exp = DatetimeIndex(rng.view(np.ndarray)[:5]) + exp = DatetimeIndex(rng.view(np.ndarray)[:5], freq="C") tm.assert_index_equal(smaller, exp) + assert smaller.freq == exp.freq assert smaller.freq == rng.freq sliced = rng[::5] diff --git a/pandas/tests/indexes/datetimes/test_setops.py b/pandas/tests/indexes/datetimes/test_setops.py index c088301097beb..7182d05b77be3 100644 --- a/pandas/tests/indexes/datetimes/test_setops.py +++ b/pandas/tests/indexes/datetimes/test_setops.py @@ -269,14 +269,34 @@ def test_intersection(self, tz, sort): assert result.freq is None assert result.tz == expected.tz - def test_intersection_empty(self): + # parametrize over both anchored and non-anchored freqs, as they + # have different code paths + @pytest.mark.parametrize("freq", ["T", "B"]) + def test_intersection_empty(self, tz_aware_fixture, freq): # empty same freq GH2129 - rng = date_range("6/1/2000", "6/15/2000", freq="T") + tz = tz_aware_fixture + rng = date_range("6/1/2000", "6/15/2000", freq=freq, tz=tz) result = rng[0:0].intersection(rng) assert len(result) == 0 + assert result.freq == rng.freq result = rng.intersection(rng[0:0]) assert len(result) == 0 + assert result.freq == rng.freq + + # no overlap GH#33604 + result = rng[:3].intersection(rng[-3:]) + tm.assert_index_equal(result, rng[:0]) + if freq != "T": + # We don't preserve freq on non-anchored offsets + assert result.freq == rng.freq + + # swapped left and right + result = rng[-3:].intersection(rng[:3]) + tm.assert_index_equal(result, rng[:0]) + if freq != "T": + # We don't preserve freq on non-anchored offsets + assert result.freq == rng.freq def test_intersection_bug_1708(self): from pandas import DateOffset @@ -450,6 +470,7 @@ def test_intersection_bug(self): b = bdate_range("12/10/2011", "12/20/2011") result = a.intersection(b) tm.assert_index_equal(result, b) + assert result.freq == b.freq def test_month_range_union_tz_pytz(self, sort): from pytz import timezone @@ -527,3 +548,4 @@ def test_intersection_bug(self): b = bdate_range("12/10/2011", "12/20/2011", freq="C") result = a.intersection(b) tm.assert_index_equal(result, b) + assert result.freq == b.freq diff --git a/pandas/tests/indexes/timedeltas/test_setops.py b/pandas/tests/indexes/timedeltas/test_setops.py index 4808950f17b52..d7576697435a0 100644 --- a/pandas/tests/indexes/timedeltas/test_setops.py +++ b/pandas/tests/indexes/timedeltas/test_setops.py @@ -106,6 +106,7 @@ def test_intersection_bug_1708(self): result = index_1 & index_2 expected = timedelta_range("1 day 01:00:00", periods=3, freq="h") tm.assert_index_equal(result, expected) + assert result.freq == expected.freq def test_intersection_equal(self, sort): # GH 24471 Test intersection outcome given the sort keyword @@ -182,7 +183,7 @@ def test_intersection(self, rng, expected, sort): TimedeltaIndex(["2 hour", "5 hour", "5 hour", "1 hour"], name="other"), TimedeltaIndex(["1 hour", "2 hour"], name=None), ), - # reveresed index + # reversed index ( TimedeltaIndex(["1 hour", "2 hour", "4 hour", "3 hour"], name="idx")[ ::-1 @@ -200,7 +201,7 @@ def test_intersection_non_monotonic(self, rng, expected, sort): tm.assert_index_equal(result, expected) assert result.name == expected.name - # if reveresed order, frequency is still the same + # if reversed order, frequency is still the same if all(base == rng[::-1]) and sort is None: assert isinstance(result.freq, Hour) else: