@@ -42,7 +42,11 @@ from pandas._libs.tslibs.conversion cimport (
42
42
)
43
43
from pandas._libs.tslibs.nattype cimport NPY_NAT, c_NaT as NaT
44
44
from pandas._libs.tslibs.np_datetime cimport (
45
- npy_datetimestruct, dtstruct_to_dt64, dt64_to_dtstruct)
45
+ npy_datetimestruct,
46
+ dtstruct_to_dt64,
47
+ dt64_to_dtstruct,
48
+ pydate_to_dtstruct,
49
+ )
46
50
from pandas._libs.tslibs.timezones cimport utc_pytz as UTC
47
51
from pandas._libs.tslibs.tzconversion cimport tz_convert_single
48
52
@@ -607,7 +611,10 @@ cdef class BaseOffset:
607
611
def _get_offset_day (self , datetime other ):
608
612
# subclass must implement `_day_opt`; calling from the base class
609
613
# will raise NotImplementedError.
610
- return get_day_of_month(other, self ._day_opt)
614
+ cdef:
615
+ npy_datetimestruct dts
616
+ pydate_to_dtstruct(other, & dts)
617
+ return get_day_of_month(& dts, self ._day_opt)
611
618
612
619
def is_on_offset (self , dt ) -> bool:
613
620
if self.normalize and not _is_normalized(dt ):
@@ -1864,10 +1871,11 @@ cdef class YearOffset(SingleConstructorOffset):
1864
1871
1865
1872
def _get_offset_day (self , other ) -> int:
1866
1873
# override BaseOffset method to use self.month instead of other.month
1867
- # TODO: there may be a more performant way to do this
1868
- return get_day_of_month(
1869
- other.replace(month = self .month), self._day_opt
1870
- )
1874
+ cdef:
1875
+ npy_datetimestruct dts
1876
+ pydate_to_dtstruct(other , &dts )
1877
+ dts.month = self .month
1878
+ return get_day_of_month(&dts , self._day_opt )
1871
1879
1872
1880
@apply_wraps
1873
1881
def apply(self , other ):
@@ -4052,14 +4060,14 @@ def shift_month(stamp: datetime, months: int,
4052
4060
return stamp.replace(year = year, month = month, day = day)
4053
4061
4054
4062
4055
- cdef int get_day_of_month(datetime other , day_opt) except ? - 1 :
4063
+ cdef int get_day_of_month(npy_datetimestruct * dts , day_opt) nogil except ? - 1 :
4056
4064
"""
4057
4065
Find the day in `other`'s month that satisfies a DateOffset's is_on_offset
4058
4066
policy, as described by the `day_opt` argument.
4059
4067
4060
4068
Parameters
4061
4069
----------
4062
- other : datetime or Timestamp
4070
+ dts : npy_datetimestruct*
4063
4071
day_opt : {'start', 'end', 'business_start', 'business_end'}
4064
4072
'start': returns 1
4065
4073
'end': returns last day of the month
@@ -4085,20 +4093,20 @@ cdef int get_day_of_month(datetime other, day_opt) except? -1:
4085
4093
if day_opt == ' start' :
4086
4094
return 1
4087
4095
elif day_opt == ' end' :
4088
- days_in_month = get_days_in_month(other .year, other .month)
4096
+ days_in_month = get_days_in_month(dts .year, dts .month)
4089
4097
return days_in_month
4090
4098
elif day_opt == ' business_start' :
4091
4099
# first business day of month
4092
- return get_firstbday(other .year, other .month)
4100
+ return get_firstbday(dts .year, dts .month)
4093
4101
elif day_opt == ' business_end' :
4094
4102
# last business day of month
4095
- return get_lastbday(other.year, other.month)
4103
+ return get_lastbday(dts.year, dts.month)
4104
+ elif day_opt is not None :
4105
+ raise ValueError (day_opt)
4096
4106
elif day_opt is None :
4097
4107
# Note: unlike `shift_month`, get_day_of_month does not
4098
4108
# allow day_opt = None
4099
4109
raise NotImplementedError
4100
- else :
4101
- raise ValueError (day_opt)
4102
4110
4103
4111
4104
4112
cpdef int roll_convention(int other, int n, int compare) nogil:
@@ -4151,21 +4159,24 @@ def roll_qtrday(other: datetime, n: int, month: int,
4151
4159
"""
4152
4160
cdef:
4153
4161
int months_since
4162
+ npy_datetimestruct dts
4163
+ pydate_to_dtstruct(other , &dts )
4164
+
4154
4165
# TODO: Merge this with roll_yearday by setting modby = 12 there?
4155
4166
# code de-duplication versus perf hit?
4156
4167
# TODO: with small adjustments this could be used in shift_quarters
4157
4168
months_since = other.month % modby - month % modby
4158
4169
4159
4170
if n > 0:
4160
4171
if months_since < 0 or (months_since == 0 and
4161
- other.day < get_day_of_month(other ,
4172
+ other.day < get_day_of_month(& dts ,
4162
4173
day_opt )):
4163
4174
# pretend to roll back if on same month but
4164
4175
# before compare_day
4165
4176
n -= 1
4166
4177
else :
4167
4178
if months_since > 0 or (months_since == 0 and
4168
- other.day > get_day_of_month(other ,
4179
+ other.day > get_day_of_month(& dts ,
4169
4180
day_opt)):
4170
4181
# make sure to roll forward, so negate
4171
4182
n += 1
@@ -4232,18 +4243,22 @@ def roll_yearday(other: datetime, n: int, month: int, day_opt: object) -> int:
4232
4243
-6
4233
4244
4234
4245
"""
4246
+ cdef:
4247
+ npy_datetimestruct dts
4248
+ pydate_to_dtstruct(other , &dts )
4249
+
4235
4250
# Note: The other.day < ... condition will never hold when day_opt=='start'
4236
4251
# and the other.day > ... condition will never hold when day_opt=='end'.
4237
4252
# At some point these extra checks may need to be optimized away.
4238
4253
# But that point isn't today.
4239
4254
if n > 0:
4240
4255
if other.month < month or (other.month == month and
4241
- other.day < get_day_of_month(other ,
4256
+ other.day < get_day_of_month(& dts ,
4242
4257
day_opt )):
4243
4258
n -= 1
4244
4259
else :
4245
4260
if other.month > month or (other.month == month and
4246
- other.day > get_day_of_month(other ,
4261
+ other.day > get_day_of_month(& dts ,
4247
4262
day_opt)):
4248
4263
n += 1
4249
4264
return n
0 commit comments