Skip to content

Commit 56b73ac

Browse files
authored
DEPR: DateOffset.apply (#44522)
1 parent 1b66adf commit 56b73ac

File tree

11 files changed

+71
-49
lines changed

11 files changed

+71
-49
lines changed

doc/source/user_guide/timeseries.rst

+11-12
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ savings time. However, all :class:`DateOffset` subclasses that are an hour or sm
852852

853853
The basic :class:`DateOffset` acts similar to ``dateutil.relativedelta`` (`relativedelta documentation`_)
854854
that shifts a date time by the corresponding calendar duration specified. The
855-
arithmetic operator (``+``) or the ``apply`` method can be used to perform the shift.
855+
arithmetic operator (``+``) can be used to perform the shift.
856856

857857
.. ipython:: python
858858
@@ -866,7 +866,6 @@ arithmetic operator (``+``) or the ``apply`` method can be used to perform the s
866866
friday.day_name()
867867
# Add 2 business days (Friday --> Tuesday)
868868
two_business_days = 2 * pd.offsets.BDay()
869-
two_business_days.apply(friday)
870869
friday + two_business_days
871870
(friday + two_business_days).day_name()
872871
@@ -938,14 +937,14 @@ in the operation).
938937
939938
ts = pd.Timestamp("2014-01-01 09:00")
940939
day = pd.offsets.Day()
941-
day.apply(ts)
942-
day.apply(ts).normalize()
940+
day + ts
941+
(day + ts).normalize()
943942
944943
ts = pd.Timestamp("2014-01-01 22:00")
945944
hour = pd.offsets.Hour()
946-
hour.apply(ts)
947-
hour.apply(ts).normalize()
948-
hour.apply(pd.Timestamp("2014-01-01 23:30")).normalize()
945+
hour + ts
946+
(hour + ts).normalize()
947+
(hour + pd.Timestamp("2014-01-01 23:30")).normalize()
949948
950949
.. _relativedelta documentation: https://dateutil.readthedocs.io/en/stable/relativedelta.html
951950

@@ -1185,16 +1184,16 @@ under the default business hours (9:00 - 17:00), there is no gap (0 minutes) bet
11851184
pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
11861185
pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
11871186
1188-
# It is the same as BusinessHour().apply(pd.Timestamp('2014-08-01 17:00')).
1189-
# And it is the same as BusinessHour().apply(pd.Timestamp('2014-08-04 09:00'))
1190-
pd.offsets.BusinessHour().apply(pd.Timestamp("2014-08-02 15:00"))
1187+
# It is the same as BusinessHour() + pd.Timestamp('2014-08-01 17:00').
1188+
# And it is the same as BusinessHour() + pd.Timestamp('2014-08-04 09:00')
1189+
pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02 15:00")
11911190
11921191
# BusinessDay results (for reference)
11931192
pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
11941193
1195-
# It is the same as BusinessDay().apply(pd.Timestamp('2014-08-01'))
1194+
# It is the same as BusinessDay() + pd.Timestamp('2014-08-01')
11961195
# The result is the same as rollworward because BusinessDay never overlap.
1197-
pd.offsets.BusinessHour().apply(pd.Timestamp("2014-08-02"))
1196+
pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02")
11981197
11991198
``BusinessHour`` regards Saturday and Sunday as holidays. To use arbitrary
12001199
holidays, you can use ``CustomBusinessHour`` offset, as explained in the

doc/source/whatsnew/v1.4.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ Other Deprecations
464464
- Deprecated casting behavior when passing an item with mismatched-timezone to :meth:`DatetimeIndex.insert`, :meth:`DatetimeIndex.putmask`, :meth:`DatetimeIndex.where` :meth:`DatetimeIndex.fillna`, :meth:`Series.mask`, :meth:`Series.where`, :meth:`Series.fillna`, :meth:`Series.shift`, :meth:`Series.replace`, :meth:`Series.reindex` (and :class:`DataFrame` column analogues). In the past this has cast to object dtype. In a future version, these will cast the passed item to the index or series's timezone (:issue:`37605`)
465465
- Deprecated the 'errors' keyword argument in :meth:`Series.where`, :meth:`DataFrame.where`, :meth:`Series.mask`, and meth:`DataFrame.mask`; in a future version the argument will be removed (:issue:`44294`)
466466
- Deprecated :meth:`PeriodIndex.astype` to ``datetime64[ns]`` or ``DatetimeTZDtype``, use ``obj.to_timestamp(how).tz_localize(dtype.tz)`` instead (:issue:`44398`)
467+
- Deprecated :meth:`DateOffset.apply`, use ``offset + other`` instead (:issue:`44522`)
467468
-
468469

469470
.. ---------------------------------------------------------------------------

pandas/_libs/tslibs/offsets.pyx

+28-18
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ cdef class BaseOffset:
442442
return np.array([self + x for x in other])
443443

444444
try:
445-
return self.apply(other)
445+
return self._apply(other)
446446
except ApplyTypeError:
447447
return NotImplemented
448448

@@ -466,7 +466,17 @@ cdef class BaseOffset:
466466
FutureWarning,
467467
stacklevel=1,
468468
)
469-
return self.apply(other)
469+
return self._apply(other)
470+
471+
def apply(self, other):
472+
# GH#44522
473+
warnings.warn(
474+
f"{type(self).__name__}.apply is deprecated and will be removed "
475+
"in a future version. Use `offset + other` instead",
476+
FutureWarning,
477+
stacklevel=2,
478+
)
479+
return self._apply(other)
470480

471481
def __mul__(self, other):
472482
if util.is_array(other):
@@ -895,7 +905,7 @@ cdef class Tick(SingleConstructorOffset):
895905
else:
896906
return delta_to_tick(self.delta + other.delta)
897907
try:
898-
return self.apply(other)
908+
return self._apply(other)
899909
except ApplyTypeError:
900910
# Includes pd.Period
901911
return NotImplemented
@@ -904,7 +914,7 @@ cdef class Tick(SingleConstructorOffset):
904914
f"the add operation between {self} and {other} will overflow"
905915
) from err
906916

907-
def apply(self, other):
917+
def _apply(self, other):
908918
# Timestamp can handle tz and nano sec, thus no need to use apply_wraps
909919
if isinstance(other, _Timestamp):
910920
# GH#15126
@@ -1047,7 +1057,7 @@ cdef class RelativeDeltaOffset(BaseOffset):
10471057
self.__dict__.update(state)
10481058

10491059
@apply_wraps
1050-
def apply(self, other: datetime) -> datetime:
1060+
def _apply(self, other: datetime) -> datetime:
10511061
if self._use_relativedelta:
10521062
other = _as_datetime(other)
10531063

@@ -1377,7 +1387,7 @@ cdef class BusinessDay(BusinessMixin):
13771387
return "+" + repr(self.offset)
13781388

13791389
@apply_wraps
1380-
def apply(self, other):
1390+
def _apply(self, other):
13811391
if PyDateTime_Check(other):
13821392
n = self.n
13831393
wday = other.weekday()
@@ -1690,7 +1700,7 @@ cdef class BusinessHour(BusinessMixin):
16901700
return dt
16911701

16921702
@apply_wraps
1693-
def apply(self, other: datetime) -> datetime:
1703+
def _apply(self, other: datetime) -> datetime:
16941704
# used for detecting edge condition
16951705
nanosecond = getattr(other, "nanosecond", 0)
16961706
# reset timezone and nanosecond
@@ -1839,7 +1849,7 @@ cdef class WeekOfMonthMixin(SingleConstructorOffset):
18391849
raise ValueError(f"Day must be 0<=day<=6, got {weekday}")
18401850

18411851
@apply_wraps
1842-
def apply(self, other: datetime) -> datetime:
1852+
def _apply(self, other: datetime) -> datetime:
18431853
compare_day = self._get_offset_day(other)
18441854

18451855
months = self.n
@@ -1919,7 +1929,7 @@ cdef class YearOffset(SingleConstructorOffset):
19191929
return get_day_of_month(&dts, self._day_opt)
19201930

19211931
@apply_wraps
1922-
def apply(self, other: datetime) -> datetime:
1932+
def _apply(self, other: datetime) -> datetime:
19231933
years = roll_qtrday(other, self.n, self.month, self._day_opt, modby=12)
19241934
months = years * 12 + (self.month - other.month)
19251935
return shift_month(other, months, self._day_opt)
@@ -2068,7 +2078,7 @@ cdef class QuarterOffset(SingleConstructorOffset):
20682078
return mod_month == 0 and dt.day == self._get_offset_day(dt)
20692079

20702080
@apply_wraps
2071-
def apply(self, other: datetime) -> datetime:
2081+
def _apply(self, other: datetime) -> datetime:
20722082
# months_since: find the calendar quarter containing other.month,
20732083
# e.g. if other.month == 8, the calendar quarter is [Jul, Aug, Sep].
20742084
# Then find the month in that quarter containing an is_on_offset date for
@@ -2195,7 +2205,7 @@ cdef class MonthOffset(SingleConstructorOffset):
21952205
return dt.day == self._get_offset_day(dt)
21962206

21972207
@apply_wraps
2198-
def apply(self, other: datetime) -> datetime:
2208+
def _apply(self, other: datetime) -> datetime:
21992209
compare_day = self._get_offset_day(other)
22002210
n = roll_convention(other.day, self.n, compare_day)
22012211
return shift_month(other, n, self._day_opt)
@@ -2313,7 +2323,7 @@ cdef class SemiMonthOffset(SingleConstructorOffset):
23132323
return self._prefix + suffix
23142324

23152325
@apply_wraps
2316-
def apply(self, other: datetime) -> datetime:
2326+
def _apply(self, other: datetime) -> datetime:
23172327
is_start = isinstance(self, SemiMonthBegin)
23182328

23192329
# shift `other` to self.day_of_month, incrementing `n` if necessary
@@ -2488,7 +2498,7 @@ cdef class Week(SingleConstructorOffset):
24882498
return self.n == 1 and self.weekday is not None
24892499

24902500
@apply_wraps
2491-
def apply(self, other):
2501+
def _apply(self, other):
24922502
if self.weekday is None:
24932503
return other + self.n * self._inc
24942504

@@ -2839,7 +2849,7 @@ cdef class FY5253(FY5253Mixin):
28392849
return year_end == dt
28402850

28412851
@apply_wraps
2842-
def apply(self, other: datetime) -> datetime:
2852+
def _apply(self, other: datetime) -> datetime:
28432853
norm = Timestamp(other).normalize()
28442854

28452855
n = self.n
@@ -3088,7 +3098,7 @@ cdef class FY5253Quarter(FY5253Mixin):
30883098
return start, num_qtrs, tdelta
30893099

30903100
@apply_wraps
3091-
def apply(self, other: datetime) -> datetime:
3101+
def _apply(self, other: datetime) -> datetime:
30923102
# Note: self.n == 0 is not allowed.
30933103

30943104
n = self.n
@@ -3179,7 +3189,7 @@ cdef class Easter(SingleConstructorOffset):
31793189
self.normalize = state.pop("normalize")
31803190

31813191
@apply_wraps
3182-
def apply(self, other: datetime) -> datetime:
3192+
def _apply(self, other: datetime) -> datetime:
31833193
current_easter = easter(other.year)
31843194
current_easter = datetime(
31853195
current_easter.year, current_easter.month, current_easter.day
@@ -3258,7 +3268,7 @@ cdef class CustomBusinessDay(BusinessDay):
32583268
BusinessDay.__setstate__(self, state)
32593269

32603270
@apply_wraps
3261-
def apply(self, other):
3271+
def _apply(self, other):
32623272
if self.n <= 0:
32633273
roll = "forward"
32643274
else:
@@ -3421,7 +3431,7 @@ cdef class _CustomBusinessMonth(BusinessMixin):
34213431
return roll_func
34223432

34233433
@apply_wraps
3424-
def apply(self, other: datetime) -> datetime:
3434+
def _apply(self, other: datetime) -> datetime:
34253435
# First move to month offset
34263436
cur_month_offset_date = self.month_roll(other)
34273437

pandas/core/arrays/datetimes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,7 @@ def generate_range(start=None, end=None, periods=None, offset=BDay()):
25662566
break
25672567

25682568
# faster than cur + offset
2569-
next_date = offset.apply(cur)
2569+
next_date = offset._apply(cur)
25702570
if next_date <= cur:
25712571
raise ValueError(f"Offset {offset} did not increment date")
25722572
cur = next_date
@@ -2580,7 +2580,7 @@ def generate_range(start=None, end=None, periods=None, offset=BDay()):
25802580
break
25812581

25822582
# faster than cur + offset
2583-
next_date = offset.apply(cur)
2583+
next_date = offset._apply(cur)
25842584
if next_date >= cur:
25852585
raise ValueError(f"Offset {offset} did not decrement date")
25862586
cur = next_date

pandas/tests/tseries/offsets/common.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
def assert_offset_equal(offset, base, expected):
2929
actual = offset + base
3030
actual_swapped = base + offset
31-
actual_apply = offset.apply(base)
31+
actual_apply = offset._apply(base)
3232
try:
3333
assert actual == expected
3434
assert actual_swapped == expected
@@ -155,7 +155,7 @@ def test_rsub(self):
155155
# i.e. skip for TestCommon and YQM subclasses that do not have
156156
# offset2 attr
157157
return
158-
assert self.d - self.offset2 == (-self.offset2).apply(self.d)
158+
assert self.d - self.offset2 == (-self.offset2)._apply(self.d)
159159

160160
def test_radd(self):
161161
if self._offset is None or not hasattr(self, "offset2"):

pandas/tests/tseries/offsets/test_business_day.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,4 @@ def test_apply_corner(self):
232232
"with datetime, datetime64 or timedelta"
233233
)
234234
with pytest.raises(ApplyTypeError, match=msg):
235-
self._offset().apply(BMonthEnd())
235+
self._offset()._apply(BMonthEnd())

pandas/tests/tseries/offsets/test_business_hour.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def test_roll_date_object(self):
318318
def test_normalize(self, case):
319319
offset, cases = case
320320
for dt, expected in cases.items():
321-
assert offset.apply(dt) == expected
321+
assert offset._apply(dt) == expected
322322

323323
on_offset_cases = []
324324
on_offset_cases.append(

pandas/tests/tseries/offsets/test_custom_business_hour.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def test_roll_date_object(self):
192192
def test_normalize(self, norm_cases):
193193
offset, cases = norm_cases
194194
for dt, expected in cases.items():
195-
assert offset.apply(dt) == expected
195+
assert offset._apply(dt) == expected
196196

197197
def test_is_on_offset(self):
198198
tests = [

pandas/tests/tseries/offsets/test_fiscal.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -643,18 +643,18 @@ def test_bunched_yearends():
643643
fy = FY5253(n=1, weekday=5, startingMonth=12, variation="nearest")
644644
dt = Timestamp("2004-01-01")
645645
assert fy.rollback(dt) == Timestamp("2002-12-28")
646-
assert (-fy).apply(dt) == Timestamp("2002-12-28")
646+
assert (-fy)._apply(dt) == Timestamp("2002-12-28")
647647
assert dt - fy == Timestamp("2002-12-28")
648648

649649
assert fy.rollforward(dt) == Timestamp("2004-01-03")
650-
assert fy.apply(dt) == Timestamp("2004-01-03")
650+
assert fy._apply(dt) == Timestamp("2004-01-03")
651651
assert fy + dt == Timestamp("2004-01-03")
652652
assert dt + fy == Timestamp("2004-01-03")
653653

654654
# Same thing, but starting from a Timestamp in the previous year.
655655
dt = Timestamp("2003-12-31")
656656
assert fy.rollback(dt) == Timestamp("2002-12-28")
657-
assert (-fy).apply(dt) == Timestamp("2002-12-28")
657+
assert (-fy)._apply(dt) == Timestamp("2002-12-28")
658658
assert dt - fy == Timestamp("2002-12-28")
659659

660660

pandas/tests/tseries/offsets/test_offsets.py

+19-7
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def test_return_type(self, offset_types):
125125
assert offset + NaT is NaT
126126

127127
assert NaT - offset is NaT
128-
assert (-offset).apply(NaT) is NaT
128+
assert (-offset)._apply(NaT) is NaT
129129

130130
def test_offset_n(self, offset_types):
131131
offset = self._get_offset(offset_types)
@@ -188,14 +188,25 @@ def _check_offsetfunc_works(self, offset, funcname, dt, expected, normalize=Fals
188188

189189
if (
190190
type(offset_s).__name__ == "DateOffset"
191-
and (funcname == "apply" or normalize)
191+
and (funcname in ["apply", "_apply"] or normalize)
192192
and ts.nanosecond > 0
193193
):
194194
exp_warning = UserWarning
195195

196196
# test nanosecond is preserved
197197
with tm.assert_produces_warning(exp_warning):
198198
result = func(ts)
199+
200+
if exp_warning is None and funcname == "_apply":
201+
# GH#44522
202+
# Check in this particular case to avoid headaches with
203+
# testing for multiple warnings produced by the same call.
204+
with tm.assert_produces_warning(FutureWarning, match="apply is deprecated"):
205+
res2 = offset_s.apply(ts)
206+
207+
assert type(res2) is type(result)
208+
assert res2 == result
209+
199210
assert isinstance(result, Timestamp)
200211
if normalize is False:
201212
assert result == expected + Nano(5)
@@ -225,7 +236,7 @@ def _check_offsetfunc_works(self, offset, funcname, dt, expected, normalize=Fals
225236

226237
if (
227238
type(offset_s).__name__ == "DateOffset"
228-
and (funcname == "apply" or normalize)
239+
and (funcname in ["apply", "_apply"] or normalize)
229240
and ts.nanosecond > 0
230241
):
231242
exp_warning = UserWarning
@@ -243,13 +254,14 @@ def test_apply(self, offset_types):
243254
sdt = datetime(2011, 1, 1, 9, 0)
244255
ndt = np_datetime64_compat("2011-01-01 09:00Z")
245256

257+
expected = self.expecteds[offset_types.__name__]
258+
expected_norm = Timestamp(expected.date())
259+
246260
for dt in [sdt, ndt]:
247-
expected = self.expecteds[offset_types.__name__]
248-
self._check_offsetfunc_works(offset_types, "apply", dt, expected)
261+
self._check_offsetfunc_works(offset_types, "_apply", dt, expected)
249262

250-
expected = Timestamp(expected.date())
251263
self._check_offsetfunc_works(
252-
offset_types, "apply", dt, expected, normalize=True
264+
offset_types, "_apply", dt, expected_norm, normalize=True
253265
)
254266

255267
def test_rollforward(self, offset_types):

0 commit comments

Comments
 (0)