Skip to content

Commit 6a7e775

Browse files
authored
REF: re-use get_firstbday, get_lastbday in fields.pyx (#35199)
1 parent 4274b11 commit 6a7e775

File tree

5 files changed

+78
-108
lines changed

5 files changed

+78
-108
lines changed

pandas/_libs/tslibs/ccalendar.pxd

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ cpdef int32_t get_days_in_month(int year, Py_ssize_t month) nogil
1010
cpdef int32_t get_week_of_year(int year, int month, int day) nogil
1111
cpdef iso_calendar_t get_iso_calendar(int year, int month, int day) nogil
1212
cpdef int32_t get_day_of_year(int year, int month, int day) nogil
13+
cpdef int get_lastbday(int year, int month) nogil
14+
cpdef int get_firstbday(int year, int month) nogil
1315

1416
cdef int64_t DAY_NANOS
1517
cdef int64_t HOUR_NANOS

pandas/_libs/tslibs/ccalendar.pyx

+49
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,52 @@ cpdef int32_t get_day_of_year(int year, int month, int day) nogil:
241241

242242
day_of_year = mo_off + day
243243
return day_of_year
244+
245+
246+
# ---------------------------------------------------------------------
247+
# Business Helpers
248+
249+
cpdef int get_lastbday(int year, int month) nogil:
250+
"""
251+
Find the last day of the month that is a business day.
252+
253+
Parameters
254+
----------
255+
year : int
256+
month : int
257+
258+
Returns
259+
-------
260+
last_bday : int
261+
"""
262+
cdef:
263+
int wkday, days_in_month
264+
265+
wkday = dayofweek(year, month, 1)
266+
days_in_month = get_days_in_month(year, month)
267+
return days_in_month - max(((wkday + days_in_month - 1) % 7) - 4, 0)
268+
269+
270+
cpdef int get_firstbday(int year, int month) nogil:
271+
"""
272+
Find the first day of the month that is a business day.
273+
274+
Parameters
275+
----------
276+
year : int
277+
month : int
278+
279+
Returns
280+
-------
281+
first_bday : int
282+
"""
283+
cdef:
284+
int first, wkday
285+
286+
wkday = dayofweek(year, month, 1)
287+
first = 1
288+
if wkday == 5: # on Saturday
289+
first = 3
290+
elif wkday == 6: # on Sunday
291+
first = 2
292+
return first

pandas/_libs/tslibs/fields.pyx

+17-60
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ from pandas._libs.tslibs.ccalendar cimport (
1919
get_days_in_month, is_leapyear, dayofweek, get_week_of_year,
2020
get_day_of_year, get_iso_calendar, iso_calendar_t,
2121
month_offset,
22+
get_firstbday,
23+
get_lastbday,
2224
)
2325
from pandas._libs.tslibs.np_datetime cimport (
2426
npy_datetimestruct, pandas_timedeltastruct, dt64_to_dtstruct,
@@ -137,9 +139,7 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
137139
int end_month = 12
138140
int start_month = 1
139141
ndarray[int8_t] out
140-
bint isleap
141142
npy_datetimestruct dts
142-
int mo_off, dom, doy, dow, ldom
143143

144144
out = np.zeros(count, dtype='int8')
145145

@@ -172,10 +172,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
172172
continue
173173

174174
dt64_to_dtstruct(dtindex[i], &dts)
175-
dom = dts.day
176-
dow = dayofweek(dts.year, dts.month, dts.day)
177175

178-
if (dom == 1 and dow < 5) or (dom <= 3 and dow == 0):
176+
if dts.day == get_firstbday(dts.year, dts.month):
179177
out[i] = 1
180178

181179
else:
@@ -185,9 +183,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
185183
continue
186184

187185
dt64_to_dtstruct(dtindex[i], &dts)
188-
dom = dts.day
189186

190-
if dom == 1:
187+
if dts.day == 1:
191188
out[i] = 1
192189

193190
elif field == 'is_month_end':
@@ -198,15 +195,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
198195
continue
199196

200197
dt64_to_dtstruct(dtindex[i], &dts)
201-
isleap = is_leapyear(dts.year)
202-
mo_off = month_offset[isleap * 13 + dts.month - 1]
203-
dom = dts.day
204-
doy = mo_off + dom
205-
ldom = month_offset[isleap * 13 + dts.month]
206-
dow = dayofweek(dts.year, dts.month, dts.day)
207-
208-
if (ldom == doy and dow < 5) or (
209-
dow == 4 and (ldom - doy <= 2)):
198+
199+
if dts.day == get_lastbday(dts.year, dts.month):
210200
out[i] = 1
211201

212202
else:
@@ -216,13 +206,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
216206
continue
217207

218208
dt64_to_dtstruct(dtindex[i], &dts)
219-
isleap = is_leapyear(dts.year)
220-
mo_off = month_offset[isleap * 13 + dts.month - 1]
221-
dom = dts.day
222-
doy = mo_off + dom
223-
ldom = month_offset[isleap * 13 + dts.month]
224209

225-
if ldom == doy:
210+
if dts.day == get_days_in_month(dts.year, dts.month):
226211
out[i] = 1
227212

228213
elif field == 'is_quarter_start':
@@ -233,11 +218,9 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
233218
continue
234219

235220
dt64_to_dtstruct(dtindex[i], &dts)
236-
dom = dts.day
237-
dow = dayofweek(dts.year, dts.month, dts.day)
238221

239222
if ((dts.month - start_month) % 3 == 0) and (
240-
(dom == 1 and dow < 5) or (dom <= 3 and dow == 0)):
223+
dts.day == get_firstbday(dts.year, dts.month)):
241224
out[i] = 1
242225

243226
else:
@@ -247,9 +230,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
247230
continue
248231

249232
dt64_to_dtstruct(dtindex[i], &dts)
250-
dom = dts.day
251233

252-
if ((dts.month - start_month) % 3 == 0) and dom == 1:
234+
if ((dts.month - start_month) % 3 == 0) and dts.day == 1:
253235
out[i] = 1
254236

255237
elif field == 'is_quarter_end':
@@ -260,16 +242,9 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
260242
continue
261243

262244
dt64_to_dtstruct(dtindex[i], &dts)
263-
isleap = is_leapyear(dts.year)
264-
mo_off = month_offset[isleap * 13 + dts.month - 1]
265-
dom = dts.day
266-
doy = mo_off + dom
267-
ldom = month_offset[isleap * 13 + dts.month]
268-
dow = dayofweek(dts.year, dts.month, dts.day)
269245

270246
if ((dts.month - end_month) % 3 == 0) and (
271-
(ldom == doy and dow < 5) or (
272-
dow == 4 and (ldom - doy <= 2))):
247+
dts.day == get_lastbday(dts.year, dts.month)):
273248
out[i] = 1
274249

275250
else:
@@ -279,13 +254,9 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
279254
continue
280255

281256
dt64_to_dtstruct(dtindex[i], &dts)
282-
isleap = is_leapyear(dts.year)
283-
mo_off = month_offset[isleap * 13 + dts.month - 1]
284-
dom = dts.day
285-
doy = mo_off + dom
286-
ldom = month_offset[isleap * 13 + dts.month]
287257

288-
if ((dts.month - end_month) % 3 == 0) and (ldom == doy):
258+
if ((dts.month - end_month) % 3 == 0) and (
259+
dts.day == get_days_in_month(dts.year, dts.month)):
289260
out[i] = 1
290261

291262
elif field == 'is_year_start':
@@ -296,11 +267,9 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
296267
continue
297268

298269
dt64_to_dtstruct(dtindex[i], &dts)
299-
dom = dts.day
300-
dow = dayofweek(dts.year, dts.month, dts.day)
301270

302271
if (dts.month == start_month) and (
303-
(dom == 1 and dow < 5) or (dom <= 3 and dow == 0)):
272+
dts.day == get_firstbday(dts.year, dts.month)):
304273
out[i] = 1
305274

306275
else:
@@ -310,9 +279,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
310279
continue
311280

312281
dt64_to_dtstruct(dtindex[i], &dts)
313-
dom = dts.day
314282

315-
if (dts.month == start_month) and dom == 1:
283+
if (dts.month == start_month) and dts.day == 1:
316284
out[i] = 1
317285

318286
elif field == 'is_year_end':
@@ -323,16 +291,9 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
323291
continue
324292

325293
dt64_to_dtstruct(dtindex[i], &dts)
326-
isleap = is_leapyear(dts.year)
327-
dom = dts.day
328-
mo_off = month_offset[isleap * 13 + dts.month - 1]
329-
doy = mo_off + dom
330-
dow = dayofweek(dts.year, dts.month, dts.day)
331-
ldom = month_offset[isleap * 13 + dts.month]
332294

333295
if (dts.month == end_month) and (
334-
(ldom == doy and dow < 5) or (
335-
dow == 4 and (ldom - doy <= 2))):
296+
dts.day == get_lastbday(dts.year, dts.month)):
336297
out[i] = 1
337298

338299
else:
@@ -342,13 +303,9 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
342303
continue
343304

344305
dt64_to_dtstruct(dtindex[i], &dts)
345-
isleap = is_leapyear(dts.year)
346-
mo_off = month_offset[isleap * 13 + dts.month - 1]
347-
dom = dts.day
348-
doy = mo_off + dom
349-
ldom = month_offset[isleap * 13 + dts.month]
350306

351-
if (dts.month == end_month) and (ldom == doy):
307+
if (dts.month == end_month) and (
308+
dts.day == get_days_in_month(dts.year, dts.month)):
352309
out[i] = 1
353310

354311
else:

pandas/_libs/tslibs/offsets.pyx

+7-46
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ from pandas._libs.tslibs.util cimport (
3434
from pandas._libs.tslibs.ccalendar import (
3535
MONTH_ALIASES, MONTH_TO_CAL_NUM, weekday_to_int, int_to_weekday,
3636
)
37-
from pandas._libs.tslibs.ccalendar cimport DAY_NANOS, get_days_in_month, dayofweek
37+
from pandas._libs.tslibs.ccalendar cimport (
38+
DAY_NANOS,
39+
dayofweek,
40+
get_days_in_month,
41+
get_firstbday,
42+
get_lastbday,
43+
)
3844
from pandas._libs.tslibs.conversion cimport (
3945
convert_datetime_to_tsobject,
4046
localize_pydatetime,
@@ -177,51 +183,6 @@ cdef _wrap_timedelta_result(result):
177183
# ---------------------------------------------------------------------
178184
# Business Helpers
179185

180-
cpdef int get_lastbday(int year, int month) nogil:
181-
"""
182-
Find the last day of the month that is a business day.
183-
184-
Parameters
185-
----------
186-
year : int
187-
month : int
188-
189-
Returns
190-
-------
191-
last_bday : int
192-
"""
193-
cdef:
194-
int wkday, days_in_month
195-
196-
wkday = dayofweek(year, month, 1)
197-
days_in_month = get_days_in_month(year, month)
198-
return days_in_month - max(((wkday + days_in_month - 1) % 7) - 4, 0)
199-
200-
201-
cpdef int get_firstbday(int year, int month) nogil:
202-
"""
203-
Find the first day of the month that is a business day.
204-
205-
Parameters
206-
----------
207-
year : int
208-
month : int
209-
210-
Returns
211-
-------
212-
first_bday : int
213-
"""
214-
cdef:
215-
int first, wkday
216-
217-
wkday = dayofweek(year, month, 1)
218-
first = 1
219-
if wkday == 5: # on Saturday
220-
first = 3
221-
elif wkday == 6: # on Sunday
222-
first = 2
223-
return first
224-
225186

226187
cdef _get_calendar(weekmask, holidays, calendar):
227188
"""Generate busdaycalendar"""

pandas/tests/tslibs/test_liboffsets.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77

8+
from pandas._libs.tslibs.ccalendar import get_firstbday, get_lastbday
89
import pandas._libs.tslibs.offsets as liboffsets
910
from pandas._libs.tslibs.offsets import roll_qtrday
1011

@@ -25,7 +26,7 @@ def day_opt(request):
2526
)
2627
def test_get_last_bday(dt, exp_week_day, exp_last_day):
2728
assert dt.weekday() == exp_week_day
28-
assert liboffsets.get_lastbday(dt.year, dt.month) == exp_last_day
29+
assert get_lastbday(dt.year, dt.month) == exp_last_day
2930

3031

3132
@pytest.mark.parametrize(
@@ -37,7 +38,7 @@ def test_get_last_bday(dt, exp_week_day, exp_last_day):
3738
)
3839
def test_get_first_bday(dt, exp_week_day, exp_first_day):
3940
assert dt.weekday() == exp_week_day
40-
assert liboffsets.get_firstbday(dt.year, dt.month) == exp_first_day
41+
assert get_firstbday(dt.year, dt.month) == exp_first_day
4142

4243

4344
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)