diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index dacd433f112a5..6f4d589213be2 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -285,7 +285,7 @@ Timedelta - Bug in :func:`TimedeltaIndex.intersection` where for non-monotonic indices in some cases an empty ``Index`` was returned when in fact an intersection existed (:issue:`25913`) - Bug with comparisons between :class:`Timedelta` and ``NaT`` raising ``TypeError`` (:issue:`26039`) -- +- Bug when adding or subtracting a :class:`BusinessHour` to a :class:`Timestamp` with the resulting time landing in a following or prior day respectively (:issue:`26381`) Timezones ^^^^^^^^^ diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index c46cc1de7aa97..8c8a2f75c4a47 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -1287,6 +1287,23 @@ def test_opening_time(self, case): datetime(2014, 7, 7, 19, 30): datetime(2014, 7, 5, 4, 30), datetime(2014, 7, 7, 19, 30, 30): datetime(2014, 7, 5, 4, 30, 30)})) + # long business hours (see gh-26381) + apply_cases.append((BusinessHour(n=4, start='00:00', end='23:00'), { + datetime(2014, 7, 3, 22): datetime(2014, 7, 4, 3), + datetime(2014, 7, 4, 22): datetime(2014, 7, 7, 3), + datetime(2014, 7, 3, 22, 30): datetime(2014, 7, 4, 3, 30), + datetime(2014, 7, 3, 22, 20): datetime(2014, 7, 4, 3, 20), + datetime(2014, 7, 4, 22, 30, 30): datetime(2014, 7, 7, 3, 30, 30), + datetime(2014, 7, 4, 22, 30, 20): datetime(2014, 7, 7, 3, 30, 20)})) + + apply_cases.append((BusinessHour(n=-4, start='00:00', end='23:00'), { + datetime(2014, 7, 4, 3): datetime(2014, 7, 3, 22), + datetime(2014, 7, 7, 3): datetime(2014, 7, 4, 22), + datetime(2014, 7, 4, 3, 30): datetime(2014, 7, 3, 22, 30), + datetime(2014, 7, 4, 3, 20): datetime(2014, 7, 3, 22, 20), + datetime(2014, 7, 7, 3, 30, 30): datetime(2014, 7, 4, 22, 30, 30), + datetime(2014, 7, 7, 3, 30, 20): datetime(2014, 7, 4, 22, 30, 20)})) + @pytest.mark.parametrize('case', apply_cases) def test_apply(self, case): offset, cases = case diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 29fee48f85015..c1764b3845fce 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -689,7 +689,6 @@ def rollforward(self, dt): @apply_wraps def apply(self, other): - daytime = self._get_daytime_flag businesshours = self._get_business_hours_by_sec bhdelta = timedelta(seconds=businesshours) @@ -731,21 +730,19 @@ def apply(self, other): result = other + timedelta(hours=hours, minutes=minutes) # because of previous adjustment, time will be larger than start - if ((daytime and (result.time() < self.start or - self.end < result.time())) or - not daytime and (self.end < result.time() < self.start)): - if n >= 0: - bday_edge = self._prev_opening_time(other) - bday_edge = bday_edge + bhdelta - # calculate remainder + if n >= 0: + bday_edge = self._prev_opening_time(other) + bhdelta + if bday_edge < result: bday_remain = result - bday_edge result = self._next_opening_time(other) result += bday_remain - else: - bday_edge = self._next_opening_time(other) + else: + bday_edge = self._next_opening_time(other) + if bday_edge > result: bday_remain = result - bday_edge result = self._next_opening_time(result) + bhdelta result += bday_remain + # edge handling if n >= 0: if result.time() == self.end: