Skip to content

REF: make get_day_of_month nogil #34764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pandas/_libs/tslibs/np_datetime.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ cdef void td64_to_tdstruct(int64_t td64, pandas_timedeltastruct* out) nogil

cdef int64_t pydatetime_to_dt64(datetime val, npy_datetimestruct *dts)
cdef int64_t pydate_to_dt64(date val, npy_datetimestruct *dts)
cdef void pydate_to_dtstruct(date val, npy_datetimestruct *dts)

cdef npy_datetime get_datetime64_value(object obj) nogil
cdef npy_timedelta get_timedelta64_value(object obj) nogil
Expand Down
6 changes: 5 additions & 1 deletion pandas/_libs/tslibs/np_datetime.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,16 @@ cdef inline int64_t pydatetime_to_dt64(datetime val,
return dtstruct_to_dt64(dts)


cdef inline int64_t pydate_to_dt64(date val, npy_datetimestruct *dts):
cdef inline void pydate_to_dtstruct(date val, npy_datetimestruct *dts):
dts.year = PyDateTime_GET_YEAR(val)
dts.month = PyDateTime_GET_MONTH(val)
dts.day = PyDateTime_GET_DAY(val)
dts.hour = dts.min = dts.sec = dts.us = 0
dts.ps = dts.as = 0
return

cdef inline int64_t pydate_to_dt64(date val, npy_datetimestruct *dts):
pydate_to_dtstruct(val, dts)
return dtstruct_to_dt64(dts)


Expand Down
49 changes: 32 additions & 17 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ from pandas._libs.tslibs.conversion cimport (
)
from pandas._libs.tslibs.nattype cimport NPY_NAT, c_NaT as NaT
from pandas._libs.tslibs.np_datetime cimport (
npy_datetimestruct, dtstruct_to_dt64, dt64_to_dtstruct)
npy_datetimestruct,
dtstruct_to_dt64,
dt64_to_dtstruct,
pydate_to_dtstruct,
)
from pandas._libs.tslibs.timezones cimport utc_pytz as UTC
from pandas._libs.tslibs.tzconversion cimport tz_convert_single

Expand Down Expand Up @@ -607,7 +611,10 @@ cdef class BaseOffset:
def _get_offset_day(self, datetime other):
# subclass must implement `_day_opt`; calling from the base class
# will raise NotImplementedError.
return get_day_of_month(other, self._day_opt)
cdef:
npy_datetimestruct dts
pydate_to_dtstruct(other, &dts)
return get_day_of_month(&dts, self._day_opt)

def is_on_offset(self, dt) -> bool:
if self.normalize and not _is_normalized(dt):
Expand Down Expand Up @@ -1864,10 +1871,11 @@ cdef class YearOffset(SingleConstructorOffset):

def _get_offset_day(self, other) -> int:
# override BaseOffset method to use self.month instead of other.month
# TODO: there may be a more performant way to do this
return get_day_of_month(
other.replace(month=self.month), self._day_opt
)
cdef:
npy_datetimestruct dts
pydate_to_dtstruct(other, &dts)
dts.month = self.month
return get_day_of_month(&dts, self._day_opt)

@apply_wraps
def apply(self, other):
Expand Down Expand Up @@ -4052,14 +4060,14 @@ def shift_month(stamp: datetime, months: int,
return stamp.replace(year=year, month=month, day=day)


cdef int get_day_of_month(datetime other, day_opt) except? -1:
cdef int get_day_of_month(npy_datetimestruct* dts, day_opt) nogil except? -1:
"""
Find the day in `other`'s month that satisfies a DateOffset's is_on_offset
policy, as described by the `day_opt` argument.

Parameters
----------
other : datetime or Timestamp
dts : npy_datetimestruct*
day_opt : {'start', 'end', 'business_start', 'business_end'}
'start': returns 1
'end': returns last day of the month
Expand All @@ -4085,20 +4093,20 @@ cdef int get_day_of_month(datetime other, day_opt) except? -1:
if day_opt == 'start':
return 1
elif day_opt == 'end':
days_in_month = get_days_in_month(other.year, other.month)
days_in_month = get_days_in_month(dts.year, dts.month)
return days_in_month
elif day_opt == 'business_start':
# first business day of month
return get_firstbday(other.year, other.month)
return get_firstbday(dts.year, dts.month)
elif day_opt == 'business_end':
# last business day of month
return get_lastbday(other.year, other.month)
return get_lastbday(dts.year, dts.month)
elif day_opt is not None:
raise ValueError(day_opt)
elif day_opt is None:
# Note: unlike `shift_month`, get_day_of_month does not
# allow day_opt = None
raise NotImplementedError
else:
raise ValueError(day_opt)


cpdef int roll_convention(int other, int n, int compare) nogil:
Expand Down Expand Up @@ -4151,21 +4159,24 @@ def roll_qtrday(other: datetime, n: int, month: int,
"""
cdef:
int months_since
npy_datetimestruct dts
pydate_to_dtstruct(other, &dts)

# TODO: Merge this with roll_yearday by setting modby=12 there?
# code de-duplication versus perf hit?
# TODO: with small adjustments this could be used in shift_quarters
months_since = other.month % modby - month % modby

if n > 0:
if months_since < 0 or (months_since == 0 and
other.day < get_day_of_month(other,
other.day < get_day_of_month(&dts,
day_opt)):
# pretend to roll back if on same month but
# before compare_day
n -= 1
else:
if months_since > 0 or (months_since == 0 and
other.day > get_day_of_month(other,
other.day > get_day_of_month(&dts,
day_opt)):
# make sure to roll forward, so negate
n += 1
Expand Down Expand Up @@ -4232,18 +4243,22 @@ def roll_yearday(other: datetime, n: int, month: int, day_opt: object) -> int:
-6

"""
cdef:
npy_datetimestruct dts
pydate_to_dtstruct(other, &dts)

# Note: The other.day < ... condition will never hold when day_opt=='start'
# and the other.day > ... condition will never hold when day_opt=='end'.
# At some point these extra checks may need to be optimized away.
# But that point isn't today.
if n > 0:
if other.month < month or (other.month == month and
other.day < get_day_of_month(other,
other.day < get_day_of_month(&dts,
day_opt)):
n -= 1
else:
if other.month > month or (other.month == month and
other.day > get_day_of_month(other,
other.day > get_day_of_month(&dts,
day_opt)):
n += 1
return n