123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074 |
- """
- Tests of pandas.tseries.offsets
- """
- from __future__ import annotations
- from datetime import (
- datetime,
- timedelta,
- )
- from typing import (
- Dict,
- List,
- Tuple,
- )
- import numpy as np
- import pytest
- from pandas._libs.tslibs import (
- NaT,
- Timedelta,
- Timestamp,
- conversion,
- timezones,
- )
- import pandas._libs.tslibs.offsets as liboffsets
- from pandas._libs.tslibs.offsets import (
- _get_offset,
- _offset_map,
- )
- from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
- from pandas.errors import PerformanceWarning
- from pandas import (
- DatetimeIndex,
- Series,
- date_range,
- )
- import pandas._testing as tm
- from pandas.tests.tseries.offsets.common import WeekDay
- from pandas.tseries import offsets
- from pandas.tseries.offsets import (
- FY5253,
- BaseOffset,
- BDay,
- BMonthEnd,
- BusinessHour,
- CustomBusinessDay,
- CustomBusinessHour,
- CustomBusinessMonthBegin,
- CustomBusinessMonthEnd,
- DateOffset,
- Easter,
- FY5253Quarter,
- LastWeekOfMonth,
- MonthBegin,
- Nano,
- Tick,
- Week,
- WeekOfMonth,
- )
- _ApplyCases = List[Tuple[BaseOffset, Dict[datetime, datetime]]]
- _ARITHMETIC_DATE_OFFSET = [
- "years",
- "months",
- "weeks",
- "days",
- "hours",
- "minutes",
- "seconds",
- "milliseconds",
- "microseconds",
- ]
- def _create_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
- def dt():
- return Timestamp(datetime(2008, 1, 2))
- @pytest.fixture
- def expecteds():
- # executed value created by _create_offset
- # are applied to 2011/01/01 09:00 (Saturday)
- # used for .apply and .rollforward
- return {
- "Day": Timestamp("2011-01-02 09:00:00"),
- "DateOffset": Timestamp("2011-01-02 09:00:00"),
- "BusinessDay": Timestamp("2011-01-03 09:00:00"),
- "CustomBusinessDay": Timestamp("2011-01-03 09:00:00"),
- "CustomBusinessMonthEnd": Timestamp("2011-01-31 09:00:00"),
- "CustomBusinessMonthBegin": Timestamp("2011-01-03 09:00:00"),
- "MonthBegin": Timestamp("2011-02-01 09:00:00"),
- "BusinessMonthBegin": Timestamp("2011-01-03 09:00:00"),
- "MonthEnd": Timestamp("2011-01-31 09:00:00"),
- "SemiMonthEnd": Timestamp("2011-01-15 09:00:00"),
- "SemiMonthBegin": Timestamp("2011-01-15 09:00:00"),
- "BusinessMonthEnd": Timestamp("2011-01-31 09:00:00"),
- "YearBegin": Timestamp("2012-01-01 09:00:00"),
- "BYearBegin": Timestamp("2011-01-03 09:00:00"),
- "YearEnd": Timestamp("2011-12-31 09:00:00"),
- "BYearEnd": Timestamp("2011-12-30 09:00:00"),
- "QuarterBegin": Timestamp("2011-03-01 09:00:00"),
- "BQuarterBegin": Timestamp("2011-03-01 09:00:00"),
- "QuarterEnd": Timestamp("2011-03-31 09:00:00"),
- "BQuarterEnd": Timestamp("2011-03-31 09:00:00"),
- "BusinessHour": Timestamp("2011-01-03 10:00:00"),
- "CustomBusinessHour": Timestamp("2011-01-03 10:00:00"),
- "WeekOfMonth": Timestamp("2011-01-08 09:00:00"),
- "LastWeekOfMonth": Timestamp("2011-01-29 09:00:00"),
- "FY5253Quarter": Timestamp("2011-01-25 09:00:00"),
- "FY5253": Timestamp("2011-01-25 09:00:00"),
- "Week": Timestamp("2011-01-08 09:00:00"),
- "Easter": Timestamp("2011-04-24 09:00:00"),
- "Hour": Timestamp("2011-01-01 10:00:00"),
- "Minute": Timestamp("2011-01-01 09:01:00"),
- "Second": Timestamp("2011-01-01 09:00:01"),
- "Milli": Timestamp("2011-01-01 09:00:00.001000"),
- "Micro": Timestamp("2011-01-01 09:00:00.000001"),
- "Nano": Timestamp("2011-01-01T09:00:00.000000001"),
- }
- class TestCommon:
- def test_immutable(self, offset_types):
- # GH#21341 check that __setattr__ raises
- offset = _create_offset(offset_types)
- msg = "objects is not writable|DateOffset objects are immutable"
- with pytest.raises(AttributeError, match=msg):
- offset.normalize = True
- with pytest.raises(AttributeError, match=msg):
- offset.n = 91
- def test_return_type(self, offset_types):
- offset = _create_offset(offset_types)
- # make sure that we are returning a Timestamp
- result = Timestamp("20080101") + offset
- assert isinstance(result, Timestamp)
- # make sure that we are returning NaT
- assert NaT + offset is NaT
- assert offset + NaT is NaT
- assert NaT - offset is NaT
- assert (-offset)._apply(NaT) is NaT
- def test_offset_n(self, offset_types):
- offset = _create_offset(offset_types)
- assert offset.n == 1
- neg_offset = offset * -1
- assert neg_offset.n == -1
- mul_offset = offset * 3
- assert mul_offset.n == 3
- def test_offset_timedelta64_arg(self, offset_types):
- # check that offset._validate_n raises TypeError on a timedelt64
- # object
- off = _create_offset(offset_types)
- td64 = np.timedelta64(4567, "s")
- with pytest.raises(TypeError, match="argument must be an integer"):
- type(off)(n=td64, **off.kwds)
- def test_offset_mul_ndarray(self, offset_types):
- off = _create_offset(offset_types)
- expected = np.array([[off, off * 2], [off * 3, off * 4]])
- result = np.array([[1, 2], [3, 4]]) * off
- tm.assert_numpy_array_equal(result, expected)
- result = off * np.array([[1, 2], [3, 4]])
- tm.assert_numpy_array_equal(result, expected)
- def test_offset_freqstr(self, offset_types):
- offset = _create_offset(offset_types)
- freqstr = offset.freqstr
- if freqstr not in ("<Easter>", "<DateOffset: days=1>", "LWOM-SAT"):
- code = _get_offset(freqstr)
- assert offset.rule_code == code
- def _check_offsetfunc_works(self, offset, funcname, dt, expected, normalize=False):
- if normalize and issubclass(offset, Tick):
- # normalize=True disallowed for Tick subclasses GH#21427
- return
- offset_s = _create_offset(offset, normalize=normalize)
- func = getattr(offset_s, funcname)
- result = func(dt)
- assert isinstance(result, Timestamp)
- assert result == expected
- result = func(Timestamp(dt))
- assert isinstance(result, Timestamp)
- assert result == expected
- # see gh-14101
- exp_warning = None
- ts = Timestamp(dt) + Nano(5)
- if (
- type(offset_s).__name__ == "DateOffset"
- and (funcname in ["apply", "_apply"] or normalize)
- and ts.nanosecond > 0
- ):
- exp_warning = UserWarning
- # test nanosecond is preserved
- with tm.assert_produces_warning(exp_warning):
- result = func(ts)
- assert isinstance(result, Timestamp)
- if normalize is False:
- assert result == expected + Nano(5)
- else:
- assert result == expected
- if isinstance(dt, np.datetime64):
- # test tz when input is datetime or Timestamp
- return
- 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)
- result = func(dt_tz)
- assert isinstance(result, Timestamp)
- assert result == expected_localize
- result = func(Timestamp(dt, tz=tz))
- assert isinstance(result, Timestamp)
- assert result == expected_localize
- # see gh-14101
- exp_warning = None
- ts = Timestamp(dt, tz=tz) + Nano(5)
- if (
- type(offset_s).__name__ == "DateOffset"
- and (funcname in ["apply", "_apply"] or normalize)
- and ts.nanosecond > 0
- ):
- exp_warning = UserWarning
- # test nanosecond is preserved
- with tm.assert_produces_warning(exp_warning):
- result = func(ts)
- assert isinstance(result, Timestamp)
- if normalize is False:
- assert result == expected_localize + Nano(5)
- else:
- assert result == expected_localize
- def test_apply(self, offset_types, expecteds):
- sdt = datetime(2011, 1, 1, 9, 0)
- ndt = np.datetime64("2011-01-01 09:00")
- expected = expecteds[offset_types.__name__]
- expected_norm = Timestamp(expected.date())
- for dt in [sdt, ndt]:
- self._check_offsetfunc_works(offset_types, "_apply", dt, expected)
- self._check_offsetfunc_works(
- offset_types, "_apply", dt, expected_norm, normalize=True
- )
- def test_rollforward(self, offset_types, expecteds):
- expecteds = expecteds.copy()
- # result will not be changed if the target is on the offset
- no_changes = [
- "Day",
- "MonthBegin",
- "SemiMonthBegin",
- "YearBegin",
- "Week",
- "Hour",
- "Minute",
- "Second",
- "Milli",
- "Micro",
- "Nano",
- "DateOffset",
- ]
- for n in no_changes:
- expecteds[n] = Timestamp("2011/01/01 09:00")
- expecteds["BusinessHour"] = Timestamp("2011-01-03 09:00:00")
- expecteds["CustomBusinessHour"] = Timestamp("2011-01-03 09:00:00")
- # but be changed when normalize=True
- norm_expected = expecteds.copy()
- for k in norm_expected:
- norm_expected[k] = Timestamp(norm_expected[k].date())
- normalized = {
- "Day": Timestamp("2011-01-02 00:00:00"),
- "DateOffset": Timestamp("2011-01-02 00:00:00"),
- "MonthBegin": Timestamp("2011-02-01 00:00:00"),
- "SemiMonthBegin": Timestamp("2011-01-15 00:00:00"),
- "YearBegin": Timestamp("2012-01-01 00:00:00"),
- "Week": Timestamp("2011-01-08 00:00:00"),
- "Hour": Timestamp("2011-01-01 00:00:00"),
- "Minute": Timestamp("2011-01-01 00:00:00"),
- "Second": Timestamp("2011-01-01 00:00:00"),
- "Milli": Timestamp("2011-01-01 00:00:00"),
- "Micro": Timestamp("2011-01-01 00:00:00"),
- }
- norm_expected.update(normalized)
- sdt = datetime(2011, 1, 1, 9, 0)
- ndt = np.datetime64("2011-01-01 09:00")
- for dt in [sdt, ndt]:
- expected = expecteds[offset_types.__name__]
- self._check_offsetfunc_works(offset_types, "rollforward", dt, expected)
- expected = norm_expected[offset_types.__name__]
- self._check_offsetfunc_works(
- offset_types, "rollforward", dt, expected, normalize=True
- )
- def test_rollback(self, offset_types):
- expecteds = {
- "BusinessDay": Timestamp("2010-12-31 09:00:00"),
- "CustomBusinessDay": Timestamp("2010-12-31 09:00:00"),
- "CustomBusinessMonthEnd": Timestamp("2010-12-31 09:00:00"),
- "CustomBusinessMonthBegin": Timestamp("2010-12-01 09:00:00"),
- "BusinessMonthBegin": Timestamp("2010-12-01 09:00:00"),
- "MonthEnd": Timestamp("2010-12-31 09:00:00"),
- "SemiMonthEnd": Timestamp("2010-12-31 09:00:00"),
- "BusinessMonthEnd": Timestamp("2010-12-31 09:00:00"),
- "BYearBegin": Timestamp("2010-01-01 09:00:00"),
- "YearEnd": Timestamp("2010-12-31 09:00:00"),
- "BYearEnd": Timestamp("2010-12-31 09:00:00"),
- "QuarterBegin": Timestamp("2010-12-01 09:00:00"),
- "BQuarterBegin": Timestamp("2010-12-01 09:00:00"),
- "QuarterEnd": Timestamp("2010-12-31 09:00:00"),
- "BQuarterEnd": Timestamp("2010-12-31 09:00:00"),
- "BusinessHour": Timestamp("2010-12-31 17:00:00"),
- "CustomBusinessHour": Timestamp("2010-12-31 17:00:00"),
- "WeekOfMonth": Timestamp("2010-12-11 09:00:00"),
- "LastWeekOfMonth": Timestamp("2010-12-25 09:00:00"),
- "FY5253Quarter": Timestamp("2010-10-26 09:00:00"),
- "FY5253": Timestamp("2010-01-26 09:00:00"),
- "Easter": Timestamp("2010-04-04 09:00:00"),
- }
- # result will not be changed if the target is on the offset
- for n in [
- "Day",
- "MonthBegin",
- "SemiMonthBegin",
- "YearBegin",
- "Week",
- "Hour",
- "Minute",
- "Second",
- "Milli",
- "Micro",
- "Nano",
- "DateOffset",
- ]:
- expecteds[n] = Timestamp("2011/01/01 09:00")
- # but be changed when normalize=True
- norm_expected = expecteds.copy()
- for k in norm_expected:
- norm_expected[k] = Timestamp(norm_expected[k].date())
- normalized = {
- "Day": Timestamp("2010-12-31 00:00:00"),
- "DateOffset": Timestamp("2010-12-31 00:00:00"),
- "MonthBegin": Timestamp("2010-12-01 00:00:00"),
- "SemiMonthBegin": Timestamp("2010-12-15 00:00:00"),
- "YearBegin": Timestamp("2010-01-01 00:00:00"),
- "Week": Timestamp("2010-12-25 00:00:00"),
- "Hour": Timestamp("2011-01-01 00:00:00"),
- "Minute": Timestamp("2011-01-01 00:00:00"),
- "Second": Timestamp("2011-01-01 00:00:00"),
- "Milli": Timestamp("2011-01-01 00:00:00"),
- "Micro": Timestamp("2011-01-01 00:00:00"),
- }
- norm_expected.update(normalized)
- sdt = datetime(2011, 1, 1, 9, 0)
- ndt = np.datetime64("2011-01-01 09:00")
- for dt in [sdt, ndt]:
- expected = expecteds[offset_types.__name__]
- self._check_offsetfunc_works(offset_types, "rollback", dt, expected)
- expected = norm_expected[offset_types.__name__]
- self._check_offsetfunc_works(
- offset_types, "rollback", dt, expected, normalize=True
- )
- def test_is_on_offset(self, offset_types, expecteds):
- dt = expecteds[offset_types.__name__]
- offset_s = _create_offset(offset_types)
- assert offset_s.is_on_offset(dt)
- # when normalize=True, is_on_offset checks time is 00:00:00
- if issubclass(offset_types, Tick):
- # normalize=True disallowed for Tick subclasses GH#21427
- return
- offset_n = _create_offset(offset_types, normalize=True)
- assert not offset_n.is_on_offset(dt)
- if offset_types in (BusinessHour, CustomBusinessHour):
- # In default BusinessHour (9:00-17:00), normalized time
- # cannot be in business hour range
- return
- date = datetime(dt.year, dt.month, dt.day)
- assert offset_n.is_on_offset(date)
- def test_add(self, offset_types, tz_naive_fixture, expecteds):
- tz = tz_naive_fixture
- dt = datetime(2011, 1, 1, 9, 0)
- offset_s = _create_offset(offset_types)
- expected = expecteds[offset_types.__name__]
- result_dt = dt + offset_s
- result_ts = Timestamp(dt) + offset_s
- for result in [result_dt, result_ts]:
- assert isinstance(result, Timestamp)
- assert result == expected
- expected_localize = expected.tz_localize(tz)
- result = Timestamp(dt, tz=tz) + offset_s
- assert isinstance(result, Timestamp)
- assert result == expected_localize
- # normalize=True, disallowed for Tick subclasses GH#21427
- if issubclass(offset_types, Tick):
- return
- offset_s = _create_offset(offset_types, normalize=True)
- expected = Timestamp(expected.date())
- result_dt = dt + offset_s
- result_ts = Timestamp(dt) + offset_s
- for result in [result_dt, result_ts]:
- assert isinstance(result, Timestamp)
- assert result == expected
- expected_localize = expected.tz_localize(tz)
- result = Timestamp(dt, tz=tz) + offset_s
- assert isinstance(result, Timestamp)
- assert result == expected_localize
- def test_add_empty_datetimeindex(self, offset_types, tz_naive_fixture):
- # GH#12724, GH#30336
- offset_s = _create_offset(offset_types)
- dti = DatetimeIndex([], tz=tz_naive_fixture)
- warn = None
- if isinstance(
- offset_s,
- (
- Easter,
- WeekOfMonth,
- LastWeekOfMonth,
- CustomBusinessDay,
- BusinessHour,
- CustomBusinessHour,
- CustomBusinessMonthBegin,
- CustomBusinessMonthEnd,
- FY5253,
- FY5253Quarter,
- ),
- ):
- # We don't have an optimized apply_index
- warn = PerformanceWarning
- with tm.assert_produces_warning(warn):
- result = dti + offset_s
- tm.assert_index_equal(result, dti)
- with tm.assert_produces_warning(warn):
- result = offset_s + dti
- tm.assert_index_equal(result, dti)
- dta = dti._data
- with tm.assert_produces_warning(warn):
- result = dta + offset_s
- tm.assert_equal(result, dta)
- with tm.assert_produces_warning(warn):
- result = offset_s + dta
- tm.assert_equal(result, dta)
- def test_pickle_roundtrip(self, offset_types):
- off = _create_offset(offset_types)
- res = tm.round_trip_pickle(off)
- assert off == res
- if type(off) is not DateOffset:
- for attr in off._attributes:
- if attr == "calendar":
- # np.busdaycalendar __eq__ will return False;
- # we check holidays and weekmask attrs so are OK
- continue
- # Make sure nothings got lost from _params (which __eq__) is based on
- assert getattr(off, attr) == getattr(res, attr)
- def test_pickle_dateoffset_odd_inputs(self):
- # GH#34511
- off = DateOffset(months=12)
- res = tm.round_trip_pickle(off)
- assert off == res
- base_dt = datetime(2020, 1, 1)
- assert base_dt + off == base_dt + res
- def test_offsets_hashable(self, offset_types):
- # GH: 37267
- off = _create_offset(offset_types)
- assert hash(off) is not None
- @pytest.mark.filterwarnings(
- "ignore:Non-vectorized DateOffset being applied to Series or DatetimeIndex"
- )
- @pytest.mark.parametrize("unit", ["s", "ms", "us"])
- def test_add_dt64_ndarray_non_nano(self, offset_types, unit, request):
- # check that the result with non-nano matches nano
- off = _create_offset(offset_types)
- dti = date_range("2016-01-01", periods=35, freq="D")
- arr = dti._data._ndarray.astype(f"M8[{unit}]")
- dta = type(dti._data)._simple_new(arr, dtype=arr.dtype)
- expected = dti._data + off
- result = dta + off
- exp_unit = unit
- if isinstance(off, Tick) and off._creso > dta._creso:
- # cast to higher reso like we would with Timedelta scalar
- exp_unit = Timedelta(off).unit
- expected = expected.as_unit(exp_unit)
- tm.assert_numpy_array_equal(result._ndarray, expected._ndarray)
- class TestDateOffset:
- def setup_method(self):
- _offset_map.clear()
- def test_repr(self):
- repr(DateOffset())
- repr(DateOffset(2))
- repr(2 * DateOffset())
- repr(2 * DateOffset(months=2))
- def test_mul(self):
- assert DateOffset(2) == 2 * DateOffset(1)
- assert DateOffset(2) == DateOffset(1) * 2
- @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
- def test_constructor(self, kwd, request):
- if kwd == "millisecond":
- request.node.add_marker(
- pytest.mark.xfail(
- raises=NotImplementedError,
- reason="Constructing DateOffset object with `millisecond` is not "
- "yet supported.",
- )
- )
- offset = DateOffset(**{kwd: 2})
- assert offset.kwds == {kwd: 2}
- assert getattr(offset, kwd) == 2
- def test_default_constructor(self, dt):
- assert (dt + DateOffset(2)) == datetime(2008, 1, 4)
- def test_is_anchored(self):
- assert not DateOffset(2).is_anchored()
- assert DateOffset(1).is_anchored()
- def test_copy(self):
- assert DateOffset(months=2).copy() == DateOffset(months=2)
- assert DateOffset(milliseconds=1).copy() == DateOffset(milliseconds=1)
- @pytest.mark.parametrize(
- "arithmatic_offset_type, expected",
- zip(
- _ARITHMETIC_DATE_OFFSET,
- [
- "2009-01-02",
- "2008-02-02",
- "2008-01-09",
- "2008-01-03",
- "2008-01-02 01:00:00",
- "2008-01-02 00:01:00",
- "2008-01-02 00:00:01",
- "2008-01-02 00:00:00.001000000",
- "2008-01-02 00:00:00.000001000",
- ],
- ),
- )
- def test_add(self, arithmatic_offset_type, expected, dt):
- assert DateOffset(**{arithmatic_offset_type: 1}) + dt == Timestamp(expected)
- assert dt + DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
- @pytest.mark.parametrize(
- "arithmatic_offset_type, expected",
- zip(
- _ARITHMETIC_DATE_OFFSET,
- [
- "2007-01-02",
- "2007-12-02",
- "2007-12-26",
- "2008-01-01",
- "2008-01-01 23:00:00",
- "2008-01-01 23:59:00",
- "2008-01-01 23:59:59",
- "2008-01-01 23:59:59.999000000",
- "2008-01-01 23:59:59.999999000",
- ],
- ),
- )
- def test_sub(self, arithmatic_offset_type, expected, dt):
- assert dt - DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
- with pytest.raises(TypeError, match="Cannot subtract datetime from offset"):
- DateOffset(**{arithmatic_offset_type: 1}) - dt
- @pytest.mark.parametrize(
- "arithmatic_offset_type, n, expected",
- zip(
- _ARITHMETIC_DATE_OFFSET,
- range(1, 10),
- [
- "2009-01-02",
- "2008-03-02",
- "2008-01-23",
- "2008-01-06",
- "2008-01-02 05:00:00",
- "2008-01-02 00:06:00",
- "2008-01-02 00:00:07",
- "2008-01-02 00:00:00.008000000",
- "2008-01-02 00:00:00.000009000",
- ],
- ),
- )
- def test_mul_add(self, arithmatic_offset_type, n, expected, dt):
- assert DateOffset(**{arithmatic_offset_type: 1}) * n + dt == Timestamp(expected)
- assert n * DateOffset(**{arithmatic_offset_type: 1}) + dt == Timestamp(expected)
- assert dt + DateOffset(**{arithmatic_offset_type: 1}) * n == Timestamp(expected)
- assert dt + n * DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
- @pytest.mark.parametrize(
- "arithmatic_offset_type, n, expected",
- zip(
- _ARITHMETIC_DATE_OFFSET,
- range(1, 10),
- [
- "2007-01-02",
- "2007-11-02",
- "2007-12-12",
- "2007-12-29",
- "2008-01-01 19:00:00",
- "2008-01-01 23:54:00",
- "2008-01-01 23:59:53",
- "2008-01-01 23:59:59.992000000",
- "2008-01-01 23:59:59.999991000",
- ],
- ),
- )
- def test_mul_sub(self, arithmatic_offset_type, n, expected, dt):
- assert dt - DateOffset(**{arithmatic_offset_type: 1}) * n == Timestamp(expected)
- assert dt - n * DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
- def test_leap_year(self):
- d = datetime(2008, 1, 31)
- assert (d + DateOffset(months=1)) == datetime(2008, 2, 29)
- def test_eq(self):
- offset1 = DateOffset(days=1)
- offset2 = DateOffset(days=365)
- assert offset1 != offset2
- assert DateOffset(milliseconds=3) != DateOffset(milliseconds=7)
- @pytest.mark.parametrize(
- "offset_kwargs, expected_arg",
- [
- ({"microseconds": 1, "milliseconds": 1}, "2022-01-01 00:00:00.001001"),
- ({"seconds": 1, "milliseconds": 1}, "2022-01-01 00:00:01.001"),
- ({"minutes": 1, "milliseconds": 1}, "2022-01-01 00:01:00.001"),
- ({"hours": 1, "milliseconds": 1}, "2022-01-01 01:00:00.001"),
- ({"days": 1, "milliseconds": 1}, "2022-01-02 00:00:00.001"),
- ({"weeks": 1, "milliseconds": 1}, "2022-01-08 00:00:00.001"),
- ({"months": 1, "milliseconds": 1}, "2022-02-01 00:00:00.001"),
- ({"years": 1, "milliseconds": 1}, "2023-01-01 00:00:00.001"),
- ],
- )
- def test_milliseconds_combination(self, offset_kwargs, expected_arg):
- # GH 49897
- offset = DateOffset(**offset_kwargs)
- ts = Timestamp("2022-01-01")
- result = ts + offset
- expected = Timestamp(expected_arg)
- assert result == expected
- def test_offset_invalid_arguments(self):
- msg = "^Invalid argument/s or bad combination of arguments"
- with pytest.raises(ValueError, match=msg):
- DateOffset(picoseconds=1)
- class TestOffsetNames:
- def test_get_offset_name(self):
- assert BDay().freqstr == "B"
- assert BDay(2).freqstr == "2B"
- assert BMonthEnd().freqstr == "BM"
- assert Week(weekday=0).freqstr == "W-MON"
- assert Week(weekday=1).freqstr == "W-TUE"
- assert Week(weekday=2).freqstr == "W-WED"
- assert Week(weekday=3).freqstr == "W-THU"
- assert Week(weekday=4).freqstr == "W-FRI"
- assert LastWeekOfMonth(weekday=WeekDay.SUN).freqstr == "LWOM-SUN"
- def test_get_offset():
- with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
- _get_offset("gibberish")
- with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
- _get_offset("QS-JAN-B")
- pairs = [
- ("B", BDay()),
- ("b", BDay()),
- ("bm", BMonthEnd()),
- ("Bm", BMonthEnd()),
- ("W-MON", Week(weekday=0)),
- ("W-TUE", Week(weekday=1)),
- ("W-WED", Week(weekday=2)),
- ("W-THU", Week(weekday=3)),
- ("W-FRI", Week(weekday=4)),
- ]
- for name, expected in pairs:
- offset = _get_offset(name)
- assert offset == expected, (
- f"Expected {repr(name)} to yield {repr(expected)} "
- f"(actual: {repr(offset)})"
- )
- def test_get_offset_legacy():
- pairs = [("w@Sat", Week(weekday=5))]
- for name, expected in pairs:
- with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
- _get_offset(name)
- class TestOffsetAliases:
- def setup_method(self):
- _offset_map.clear()
- def test_alias_equality(self):
- for k, v in _offset_map.items():
- if v is None:
- continue
- assert k == v.copy()
- def test_rule_code(self):
- lst = ["M", "MS", "BM", "BMS", "D", "B", "H", "T", "S", "L", "U"]
- for k in lst:
- assert k == _get_offset(k).rule_code
- # should be cached - this is kind of an internals test...
- assert k in _offset_map
- assert k == (_get_offset(k) * 3).rule_code
- suffix_lst = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
- base = "W"
- for v in suffix_lst:
- alias = "-".join([base, v])
- assert alias == _get_offset(alias).rule_code
- assert alias == (_get_offset(alias) * 5).rule_code
- suffix_lst = [
- "JAN",
- "FEB",
- "MAR",
- "APR",
- "MAY",
- "JUN",
- "JUL",
- "AUG",
- "SEP",
- "OCT",
- "NOV",
- "DEC",
- ]
- base_lst = ["A", "AS", "BA", "BAS", "Q", "QS", "BQ", "BQS"]
- for base in base_lst:
- for v in suffix_lst:
- alias = "-".join([base, v])
- assert alias == _get_offset(alias).rule_code
- assert alias == (_get_offset(alias) * 5).rule_code
- def test_freq_offsets():
- off = BDay(1, offset=timedelta(0, 1800))
- assert off.freqstr == "B+30Min"
- off = BDay(1, offset=timedelta(0, -1800))
- assert off.freqstr == "B-30Min"
- class TestReprNames:
- def test_str_for_named_is_name(self):
- # look at all the amazing combinations!
- month_prefixes = ["A", "AS", "BA", "BAS", "Q", "BQ", "BQS", "QS"]
- names = [
- prefix + "-" + month
- for prefix in month_prefixes
- for month in [
- "JAN",
- "FEB",
- "MAR",
- "APR",
- "MAY",
- "JUN",
- "JUL",
- "AUG",
- "SEP",
- "OCT",
- "NOV",
- "DEC",
- ]
- ]
- days = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
- names += ["W-" + day for day in days]
- names += ["WOM-" + week + day for week in ("1", "2", "3", "4") for day in days]
- _offset_map.clear()
- for name in names:
- offset = _get_offset(name)
- assert offset.freqstr == name
- # ---------------------------------------------------------------------
- def test_valid_default_arguments(offset_types):
- # GH#19142 check that the calling the constructors without passing
- # any keyword arguments produce valid offsets
- cls = offset_types
- cls()
- @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
- def test_valid_month_attributes(kwd, month_classes):
- # GH#18226
- cls = month_classes
- # check that we cannot create e.g. MonthEnd(weeks=3)
- msg = rf"__init__\(\) got an unexpected keyword argument '{kwd}'"
- with pytest.raises(TypeError, match=msg):
- cls(**{kwd: 3})
- def test_month_offset_name(month_classes):
- # GH#33757 off.name with n != 1 should not raise AttributeError
- obj = month_classes(1)
- obj2 = month_classes(2)
- assert obj2.name == obj.name
- @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
- def test_valid_relativedelta_kwargs(kwd, request):
- if kwd == "millisecond":
- request.node.add_marker(
- pytest.mark.xfail(
- raises=NotImplementedError,
- reason="Constructing DateOffset object with `millisecond` is not "
- "yet supported.",
- )
- )
- # Check that all the arguments specified in liboffsets._relativedelta_kwds
- # are in fact valid relativedelta keyword args
- DateOffset(**{kwd: 1})
- @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
- def test_valid_tick_attributes(kwd, tick_classes):
- # GH#18226
- cls = tick_classes
- # check that we cannot create e.g. Hour(weeks=3)
- msg = rf"__init__\(\) got an unexpected keyword argument '{kwd}'"
- with pytest.raises(TypeError, match=msg):
- cls(**{kwd: 3})
- def test_validate_n_error():
- with pytest.raises(TypeError, match="argument must be an integer"):
- DateOffset(n="Doh!")
- with pytest.raises(TypeError, match="argument must be an integer"):
- MonthBegin(n=timedelta(1))
- with pytest.raises(TypeError, match="argument must be an integer"):
- BDay(n=np.array([1, 2], dtype=np.int64))
- def test_require_integers(offset_types):
- cls = offset_types
- with pytest.raises(ValueError, match="argument must be an integer"):
- cls(n=1.5)
- def test_tick_normalize_raises(tick_classes):
- # check that trying to create a Tick object with normalize=True raises
- # GH#21427
- cls = tick_classes
- msg = "Tick offset with `normalize=True` are not allowed."
- with pytest.raises(ValueError, match=msg):
- cls(n=3, normalize=True)
- @pytest.mark.parametrize(
- "offset_kwargs, expected_arg",
- [
- ({"nanoseconds": 1}, "1970-01-01 00:00:00.000000001"),
- ({"nanoseconds": 5}, "1970-01-01 00:00:00.000000005"),
- ({"nanoseconds": -1}, "1969-12-31 23:59:59.999999999"),
- ({"microseconds": 1}, "1970-01-01 00:00:00.000001"),
- ({"microseconds": -1}, "1969-12-31 23:59:59.999999"),
- ({"seconds": 1}, "1970-01-01 00:00:01"),
- ({"seconds": -1}, "1969-12-31 23:59:59"),
- ({"minutes": 1}, "1970-01-01 00:01:00"),
- ({"minutes": -1}, "1969-12-31 23:59:00"),
- ({"hours": 1}, "1970-01-01 01:00:00"),
- ({"hours": -1}, "1969-12-31 23:00:00"),
- ({"days": 1}, "1970-01-02 00:00:00"),
- ({"days": -1}, "1969-12-31 00:00:00"),
- ({"weeks": 1}, "1970-01-08 00:00:00"),
- ({"weeks": -1}, "1969-12-25 00:00:00"),
- ({"months": 1}, "1970-02-01 00:00:00"),
- ({"months": -1}, "1969-12-01 00:00:00"),
- ({"years": 1}, "1971-01-01 00:00:00"),
- ({"years": -1}, "1969-01-01 00:00:00"),
- ],
- )
- def test_dateoffset_add_sub(offset_kwargs, expected_arg):
- offset = DateOffset(**offset_kwargs)
- ts = Timestamp(0)
- result = ts + offset
- expected = Timestamp(expected_arg)
- assert result == expected
- result -= offset
- assert result == ts
- result = offset + ts
- assert result == expected
- def test_dateoffset_add_sub_timestamp_with_nano():
- offset = DateOffset(minutes=2, nanoseconds=9)
- ts = Timestamp(4)
- result = ts + offset
- expected = Timestamp("1970-01-01 00:02:00.000000013")
- assert result == expected
- result -= offset
- assert result == ts
- result = offset + ts
- assert result == expected
- @pytest.mark.parametrize(
- "attribute",
- [
- "hours",
- "days",
- "weeks",
- "months",
- "years",
- ],
- )
- def test_dateoffset_immutable(attribute):
- offset = DateOffset(**{attribute: 0})
- msg = "DateOffset objects are immutable"
- with pytest.raises(AttributeError, match=msg):
- setattr(offset, attribute, 5)
- def test_dateoffset_misc():
- oset = offsets.DateOffset(months=2, days=4)
- # it works
- oset.freqstr
- assert not offsets.DateOffset(months=2) == 2
- @pytest.mark.parametrize("n", [-1, 1, 3])
- def test_construct_int_arg_no_kwargs_assumed_days(n):
- # GH 45890, 45643
- offset = DateOffset(n)
- assert offset._offset == timedelta(1)
- result = Timestamp(2022, 1, 2) + offset
- expected = Timestamp(2022, 1, 2 + n)
- assert result == expected
- @pytest.mark.parametrize(
- "offset, expected",
- [
- (
- DateOffset(minutes=7, nanoseconds=18),
- Timestamp("2022-01-01 00:07:00.000000018"),
- ),
- (DateOffset(nanoseconds=3), Timestamp("2022-01-01 00:00:00.000000003")),
- ],
- )
- def test_dateoffset_add_sub_timestamp_series_with_nano(offset, expected):
- # GH 47856
- start_time = Timestamp("2022-01-01")
- teststamp = start_time
- testseries = Series([start_time])
- testseries = testseries + offset
- assert testseries[0] == expected
- testseries -= offset
- assert testseries[0] == teststamp
- testseries = offset + testseries
- assert testseries[0] == expected
|