Skip to content

Commit 99ebe28

Browse files
authored
REF: de-duplicate BusinessDay apply, apply_array (pandas-dev#51220)
1 parent 326de11 commit 99ebe28

File tree

1 file changed

+81
-94
lines changed

1 file changed

+81
-94
lines changed

pandas/_libs/tslibs/offsets.pyx

+81-94
Original file line numberDiff line numberDiff line change
@@ -1582,25 +1582,7 @@ cdef class BusinessDay(BusinessMixin):
15821582

15831583
# avoid slowness below by operating on weeks first
15841584
weeks = n // 5
1585-
if n <= 0 and wday > 4:
1586-
# roll forward
1587-
n += 1
1588-
1589-
n -= 5 * weeks
1590-
1591-
# n is always >= 0 at this point
1592-
if n == 0 and wday > 4:
1593-
# roll back
1594-
days = 4 - wday
1595-
elif wday > 4:
1596-
# roll forward
1597-
days = (7 - wday) + (n - 1)
1598-
elif wday + n <= 4:
1599-
# shift by n days without leaving the current week
1600-
days = n
1601-
else:
1602-
# shift by n days plus 2 to get past the weekend
1603-
days = n + 2
1585+
days = self._adjust_ndays(wday, weeks)
16041586

16051587
result = other + timedelta(days=7 * weeks + days)
16061588
if self.offset:
@@ -1617,11 +1599,90 @@ cdef class BusinessDay(BusinessMixin):
16171599
"Only know how to combine business day with datetime or timedelta."
16181600
)
16191601

1602+
@cython.wraparound(False)
1603+
@cython.boundscheck(False)
1604+
cdef ndarray _shift_bdays(
1605+
self,
1606+
ndarray i8other,
1607+
NPY_DATETIMEUNIT reso=NPY_DATETIMEUNIT.NPY_FR_ns,
1608+
):
1609+
"""
1610+
Implementation of BusinessDay.apply_offset.
1611+
1612+
Parameters
1613+
----------
1614+
i8other : const int64_t[:]
1615+
reso : NPY_DATETIMEUNIT, default NPY_FR_ns
1616+
1617+
Returns
1618+
-------
1619+
ndarray[int64_t]
1620+
"""
1621+
cdef:
1622+
int periods = self.n
1623+
Py_ssize_t i, n = i8other.size
1624+
ndarray result = cnp.PyArray_EMPTY(
1625+
i8other.ndim, i8other.shape, cnp.NPY_INT64, 0
1626+
)
1627+
int64_t val, res_val
1628+
int wday, days
1629+
npy_datetimestruct dts
1630+
int64_t DAY_PERIODS = periods_per_day(reso)
1631+
cnp.broadcast mi = cnp.PyArray_MultiIterNew2(result, i8other)
1632+
1633+
for i in range(n):
1634+
# Analogous to: val = i8other[i]
1635+
val = (<int64_t*>cnp.PyArray_MultiIter_DATA(mi, 1))[0]
1636+
1637+
if val == NPY_NAT:
1638+
res_val = NPY_NAT
1639+
else:
1640+
# The rest of this is effectively a copy of BusinessDay.apply
1641+
weeks = periods // 5
1642+
pandas_datetime_to_datetimestruct(val, reso, &dts)
1643+
wday = dayofweek(dts.year, dts.month, dts.day)
1644+
1645+
days = self._adjust_ndays(wday, weeks)
1646+
res_val = val + (7 * weeks + days) * DAY_PERIODS
1647+
1648+
# Analogous to: out[i] = res_val
1649+
(<int64_t*>cnp.PyArray_MultiIter_DATA(mi, 0))[0] = res_val
1650+
1651+
cnp.PyArray_MultiIter_NEXT(mi)
1652+
1653+
return result
1654+
1655+
cdef int _adjust_ndays(self, int wday, int weeks):
1656+
cdef:
1657+
int n = self.n
1658+
int days
1659+
1660+
if n <= 0 and wday > 4:
1661+
# roll forward
1662+
n += 1
1663+
1664+
n -= 5 * weeks
1665+
1666+
# n is always >= 0 at this point
1667+
if n == 0 and wday > 4:
1668+
# roll back
1669+
days = 4 - wday
1670+
elif wday > 4:
1671+
# roll forward
1672+
days = (7 - wday) + (n - 1)
1673+
elif wday + n <= 4:
1674+
# shift by n days without leaving the current week
1675+
days = n
1676+
else:
1677+
# shift by n days plus 2 to get past the weekend
1678+
days = n + 2
1679+
return days
1680+
16201681
@apply_array_wraps
16211682
def _apply_array(self, dtarr):
16221683
i8other = dtarr.view("i8")
16231684
reso = get_unit_from_dtype(dtarr.dtype)
1624-
res = _shift_bdays(i8other, self.n, reso=reso)
1685+
res = self._shift_bdays(i8other, reso=reso)
16251686
if self.offset:
16261687
res = res.view(dtarr.dtype) + Timedelta(self.offset)
16271688
res = res.view("i8")
@@ -4328,80 +4389,6 @@ def shift_months(
43284389
return out
43294390

43304391

4331-
@cython.wraparound(False)
4332-
@cython.boundscheck(False)
4333-
cdef ndarray _shift_bdays(
4334-
ndarray i8other,
4335-
int periods,
4336-
NPY_DATETIMEUNIT reso=NPY_DATETIMEUNIT.NPY_FR_ns,
4337-
):
4338-
"""
4339-
Implementation of BusinessDay.apply_offset.
4340-
4341-
Parameters
4342-
----------
4343-
i8other : const int64_t[:]
4344-
periods : int
4345-
reso : NPY_DATETIMEUNIT, default NPY_FR_ns
4346-
4347-
Returns
4348-
-------
4349-
ndarray[int64_t]
4350-
"""
4351-
cdef:
4352-
Py_ssize_t i, n = i8other.size
4353-
ndarray result = cnp.PyArray_EMPTY(
4354-
i8other.ndim, i8other.shape, cnp.NPY_INT64, 0
4355-
)
4356-
int64_t val, res_val
4357-
int wday, nadj, days
4358-
npy_datetimestruct dts
4359-
int64_t DAY_PERIODS = periods_per_day(reso)
4360-
cnp.broadcast mi = cnp.PyArray_MultiIterNew2(result, i8other)
4361-
4362-
for i in range(n):
4363-
# Analogous to: val = i8other[i]
4364-
val = (<int64_t*>cnp.PyArray_MultiIter_DATA(mi, 1))[0]
4365-
4366-
if val == NPY_NAT:
4367-
res_val = NPY_NAT
4368-
else:
4369-
# The rest of this is effectively a copy of BusinessDay.apply
4370-
nadj = periods
4371-
weeks = nadj // 5
4372-
pandas_datetime_to_datetimestruct(val, reso, &dts)
4373-
wday = dayofweek(dts.year, dts.month, dts.day)
4374-
4375-
if nadj <= 0 and wday > 4:
4376-
# roll forward
4377-
nadj += 1
4378-
4379-
nadj -= 5 * weeks
4380-
4381-
# nadj is always >= 0 at this point
4382-
if nadj == 0 and wday > 4:
4383-
# roll back
4384-
days = 4 - wday
4385-
elif wday > 4:
4386-
# roll forward
4387-
days = (7 - wday) + (nadj - 1)
4388-
elif wday + nadj <= 4:
4389-
# shift by n days without leaving the current week
4390-
days = nadj
4391-
else:
4392-
# shift by nadj days plus 2 to get past the weekend
4393-
days = nadj + 2
4394-
4395-
res_val = val + (7 * weeks + days) * DAY_PERIODS
4396-
4397-
# Analogous to: out[i] = res_val
4398-
(<int64_t*>cnp.PyArray_MultiIter_DATA(mi, 0))[0] = res_val
4399-
4400-
cnp.PyArray_MultiIter_NEXT(mi)
4401-
4402-
return result
4403-
4404-
44054392
def shift_month(stamp: datetime, months: int, day_opt: object = None) -> datetime:
44064393
"""
44074394
Given a datetime (or Timestamp) `stamp`, an integer `months` and an

0 commit comments

Comments
 (0)