diff --git a/pandas/tests/tseries/offsets/common.py b/pandas/tests/tseries/offsets/common.py index c12130544e6b2..50b6907a02eaf 100644 --- a/pandas/tests/tseries/offsets/common.py +++ b/pandas/tests/tseries/offsets/common.py @@ -5,25 +5,16 @@ from datetime import datetime -from dateutil.tz.tz import tzlocal -import pytest - -from pandas._libs.tslibs import ( - OutOfBoundsDatetime, - Timestamp, -) +from pandas._libs.tslibs import Timestamp from pandas._libs.tslibs.offsets import ( FY5253, BaseOffset, - BusinessHour, - CustomBusinessHour, DateOffset, FY5253Quarter, LastWeekOfMonth, Week, WeekOfMonth, ) -from pandas.compat import IS64 def assert_offset_equal(offset, base, expected): @@ -63,15 +54,6 @@ class Base: _offset: type[BaseOffset] | None = None d = Timestamp(datetime(2008, 1, 2)) - timezones = [ - None, - "UTC", - "Asia/Tokyo", - "US/Eastern", - "dateutil/Asia/Tokyo", - "dateutil/US/Pacific", - ] - def _get_offset(self, klass, value=1, normalize=False): # create instance from offset class if klass is FY5253: @@ -102,106 +84,3 @@ def _get_offset(self, klass, value=1, normalize=False): else: klass = klass(value, normalize=normalize) return klass - - def test_apply_out_of_range(self, request, tz_naive_fixture): - tz = tz_naive_fixture - if self._offset is None: - return - - # try to create an out-of-bounds result timestamp; if we can't create - # the offset skip - try: - if self._offset in (BusinessHour, CustomBusinessHour): - # Using 10000 in BusinessHour fails in tz check because of DST - # difference - offset = self._get_offset(self._offset, value=100000) - else: - offset = self._get_offset(self._offset, value=10000) - - result = Timestamp("20080101") + offset - assert isinstance(result, datetime) - assert result.tzinfo is None - - # Check tz is preserved - t = Timestamp("20080101", tz=tz) - result = t + offset - assert isinstance(result, datetime) - - if isinstance(tz, tzlocal) and not IS64: - # If we hit OutOfBoundsDatetime on non-64 bit machines - # we'll drop out of the try clause before the next test - request.node.add_marker( - pytest.mark.xfail(reason="OverflowError inside tzlocal past 2038") - ) - assert t.tzinfo == result.tzinfo - - except OutOfBoundsDatetime: - pass - except (ValueError, KeyError): - # we are creating an invalid offset - # so ignore - pass - - def test_offsets_compare_equal(self): - # root cause of GH#456: __ne__ was not implemented - if self._offset is None: - return - offset1 = self._offset() - offset2 = self._offset() - assert not offset1 != offset2 - assert offset1 == offset2 - - def test_rsub(self): - if self._offset is None or not hasattr(self, "offset2"): - # i.e. skip for TestCommon and YQM subclasses that do not have - # offset2 attr - return - assert self.d - self.offset2 == (-self.offset2)._apply(self.d) - - def test_radd(self): - if self._offset is None or not hasattr(self, "offset2"): - # i.e. skip for TestCommon and YQM subclasses that do not have - # offset2 attr - return - assert self.d + self.offset2 == self.offset2 + self.d - - def test_sub(self): - if self._offset is None or not hasattr(self, "offset2"): - # i.e. skip for TestCommon and YQM subclasses that do not have - # offset2 attr - return - off = self.offset2 - msg = "Cannot subtract datetime from offset" - with pytest.raises(TypeError, match=msg): - off - self.d - - assert 2 * off - off == off - assert self.d - self.offset2 == self.d + self._offset(-2) - assert self.d - self.offset2 == self.d - (2 * off - off) - - def testMult1(self): - if self._offset is None or not hasattr(self, "offset1"): - # i.e. skip for TestCommon and YQM subclasses that do not have - # offset1 attr - return - assert self.d + 10 * self.offset1 == self.d + self._offset(10) - assert self.d + 5 * self.offset1 == self.d + self._offset(5) - - def testMult2(self): - if self._offset is None: - return - assert self.d + (-5 * self._offset(-10)) == self.d + self._offset(50) - assert self.d + (-3 * self._offset(-2)) == self.d + self._offset(6) - - def test_compare_str(self): - # GH#23524 - # comparing to strings that cannot be cast to DateOffsets should - # not raise for __eq__ or __ne__ - if self._offset is None: - return - off = self._get_offset(self._offset) - - assert not off == "infer" - assert off != "foo" - # Note: inequalities are only implemented for Tick subclasses; - # tests for this are in test_ticks diff --git a/pandas/tests/tseries/offsets/test_common.py b/pandas/tests/tseries/offsets/test_common.py new file mode 100644 index 0000000000000..1c140d7dda330 --- /dev/null +++ b/pandas/tests/tseries/offsets/test_common.py @@ -0,0 +1,254 @@ +from datetime import datetime + +from dateutil.tz.tz import tzlocal +import pytest + +from pandas._libs.tslibs import ( + OutOfBoundsDatetime, + Timestamp, +) +from pandas.compat import IS64 + +from pandas.tseries.offsets import ( + FY5253, + BDay, + BMonthBegin, + BMonthEnd, + BQuarterBegin, + BQuarterEnd, + BusinessHour, + BYearBegin, + BYearEnd, + CBMonthBegin, + CBMonthEnd, + CDay, + CustomBusinessHour, + DateOffset, + FY5253Quarter, + LastWeekOfMonth, + MonthBegin, + MonthEnd, + QuarterEnd, + SemiMonthBegin, + SemiMonthEnd, + Week, + WeekOfMonth, + YearBegin, + YearEnd, +) + + +def _get_offset(klass, value=1, normalize=False): + # create instance from offset class + if klass is FY5253: + klass = klass( + n=value, + startingMonth=1, + weekday=1, + variation="last", + normalize=normalize, + ) + elif klass is FY5253Quarter: + klass = klass( + n=value, + startingMonth=1, + weekday=1, + qtr_with_extra_week=1, + variation="last", + normalize=normalize, + ) + elif klass is LastWeekOfMonth: + klass = klass(n=value, weekday=5, normalize=normalize) + elif klass is WeekOfMonth: + klass = klass(n=value, week=1, weekday=5, normalize=normalize) + elif klass is Week: + klass = klass(n=value, weekday=5, normalize=normalize) + elif klass is DateOffset: + klass = klass(days=value, normalize=normalize) + else: + klass = klass(value, normalize=normalize) + return klass + + +@pytest.fixture( + params=[ + BDay, + BusinessHour, + BMonthEnd, + BMonthBegin, + BQuarterEnd, + BQuarterBegin, + BYearEnd, + BYearBegin, + CDay, + CustomBusinessHour, + CBMonthEnd, + CBMonthBegin, + MonthEnd, + MonthBegin, + SemiMonthBegin, + SemiMonthEnd, + QuarterEnd, + LastWeekOfMonth, + WeekOfMonth, + Week, + YearBegin, + YearEnd, + FY5253, + FY5253Quarter, + DateOffset, + ] +) +def _offset(request): + return request.param + + +@pytest.fixture +def d(_offset): + if _offset in (CBMonthBegin, CBMonthEnd, BDay): + return Timestamp(2008, 1, 1) + elif _offset is (CustomBusinessHour, BusinessHour): + return Timestamp(2014, 7, 1, 10, 00) + return Timestamp(2008, 1, 2) + + +def test_apply_out_of_range(request, tz_naive_fixture, _offset): + tz = tz_naive_fixture + + # try to create an out-of-bounds result timestamp; if we can't create + # the offset skip + try: + if _offset in (BusinessHour, CustomBusinessHour): + # Using 10000 in BusinessHour fails in tz check because of DST + # difference + offset = _get_offset(_offset, value=100000) + else: + offset = _get_offset(_offset, value=10000) + + result = Timestamp("20080101") + offset + assert isinstance(result, datetime) + assert result.tzinfo is None + + # Check tz is preserved + t = Timestamp("20080101", tz=tz) + result = t + offset + assert isinstance(result, datetime) + + if isinstance(tz, tzlocal) and not IS64 and _offset is not DateOffset: + # If we hit OutOfBoundsDatetime on non-64 bit machines + # we'll drop out of the try clause before the next test + request.node.add_marker( + pytest.mark.xfail(reason="OverflowError inside tzlocal past 2038") + ) + assert str(t.tzinfo) == str(result.tzinfo) + + except OutOfBoundsDatetime: + pass + except (ValueError, KeyError): + # we are creating an invalid offset + # so ignore + pass + + +def test_offsets_compare_equal(_offset): + # root cause of GH#456: __ne__ was not implemented + offset1 = _offset() + offset2 = _offset() + assert not offset1 != offset2 + assert offset1 == offset2 + + +@pytest.mark.parametrize( + "date, offset2", + [ + [Timestamp(2008, 1, 1), BDay(2)], + [Timestamp(2014, 7, 1, 10, 00), BusinessHour(n=3)], + [ + Timestamp(2014, 7, 1, 10), + CustomBusinessHour( + holidays=["2014-06-27", Timestamp(2014, 6, 30), Timestamp("2014-07-02")] + ), + ], + [Timestamp(2008, 1, 2), SemiMonthEnd(2)], + [Timestamp(2008, 1, 2), SemiMonthBegin(2)], + [Timestamp(2008, 1, 2), Week(2)], + [Timestamp(2008, 1, 2), WeekOfMonth(2)], + [Timestamp(2008, 1, 2), LastWeekOfMonth(2)], + ], +) +def test_rsub(date, offset2): + assert date - offset2 == (-offset2)._apply(date) + + +@pytest.mark.parametrize( + "date, offset2", + [ + [Timestamp(2008, 1, 1), BDay(2)], + [Timestamp(2014, 7, 1, 10, 00), BusinessHour(n=3)], + [ + Timestamp(2014, 7, 1, 10), + CustomBusinessHour( + holidays=["2014-06-27", Timestamp(2014, 6, 30), Timestamp("2014-07-02")] + ), + ], + [Timestamp(2008, 1, 2), SemiMonthEnd(2)], + [Timestamp(2008, 1, 2), SemiMonthBegin(2)], + [Timestamp(2008, 1, 2), Week(2)], + [Timestamp(2008, 1, 2), WeekOfMonth(2)], + [Timestamp(2008, 1, 2), LastWeekOfMonth(2)], + ], +) +def test_radd(date, offset2): + assert date + offset2 == offset2 + date + + +@pytest.mark.parametrize( + "date, offset_box, offset2", + [ + [Timestamp(2008, 1, 1), BDay, BDay(2)], + [Timestamp(2008, 1, 2), SemiMonthEnd, SemiMonthEnd(2)], + [Timestamp(2008, 1, 2), SemiMonthBegin, SemiMonthBegin(2)], + [Timestamp(2008, 1, 2), Week, Week(2)], + [Timestamp(2008, 1, 2), WeekOfMonth, WeekOfMonth(2)], + [Timestamp(2008, 1, 2), LastWeekOfMonth, LastWeekOfMonth(2)], + ], +) +def test_sub(date, offset_box, offset2): + off = offset2 + msg = "Cannot subtract datetime from offset" + with pytest.raises(TypeError, match=msg): + off - date + + assert 2 * off - off == off + assert date - offset2 == date + offset_box(-2) + assert date - offset2 == date - (2 * off - off) + + +@pytest.mark.parametrize( + "offset_box, offset1", + [ + [BDay, BDay()], + [LastWeekOfMonth, LastWeekOfMonth()], + [WeekOfMonth, WeekOfMonth()], + [Week, Week()], + [SemiMonthBegin, SemiMonthBegin()], + [SemiMonthEnd, SemiMonthEnd()], + [CustomBusinessHour, CustomBusinessHour(weekmask="Tue Wed Thu Fri")], + [BusinessHour, BusinessHour()], + ], +) +def test_Mult1(offset_box, offset1, d): + assert d + 10 * offset1 == d + offset_box(10) + assert d + 5 * offset1 == d + offset_box(5) + + +def test_compare_str(_offset): + # GH#23524 + # comparing to strings that cannot be cast to DateOffsets should + # not raise for __eq__ or __ne__ + off = _get_offset(_offset) + + assert not off == "infer" + assert off != "foo" + # Note: inequalities are only implemented for Tick subclasses; + # tests for this are in test_ticks diff --git a/pandas/tests/tseries/offsets/test_custom_business_hour.py b/pandas/tests/tseries/offsets/test_custom_business_hour.py index 9a2d24f1d0307..360b541ce7c90 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_hour.py +++ b/pandas/tests/tseries/offsets/test_custom_business_hour.py @@ -86,11 +86,6 @@ def test_eq(self): holidays=["2014-06-28"] ) - def test_sub(self): - # override the Base.test_sub implementation because self.offset2 is - # defined differently in this class than the test expects - pass - def test_hash(self): assert hash(self.offset1) == hash(self.offset1) assert hash(self.offset2) == hash(self.offset2) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 95906224ee39b..34266e55d9ea9 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -234,7 +234,14 @@ def _check_offsetfunc_works(self, offset, funcname, dt, expected, normalize=Fals # test tz when input is datetime or Timestamp return - for tz in self.timezones: + for tz in [ + None, + "UTC", + "Asia/Tokyo", + "US/Eastern", + "dateutil/Asia/Tokyo", + "dateutil/US/Pacific", + ]: expected_localize = expected.tz_localize(tz) tz_obj = timezones.maybe_get_tz(tz) dt_tz = conversion.localize_pydatetime(dt, tz_obj)