Skip to content

Commit 1043a46

Browse files
jbrockmendeljreback
authored andcommitted
move monthrange inside get_first/last_bday, allows nogil (pandas-dev#18512)
1 parent f745e52 commit 1043a46

File tree

2 files changed

+67
-74
lines changed

2 files changed

+67
-74
lines changed

pandas/_libs/tslibs/offsets.pyx

+63-65
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ from frequencies cimport get_freq_code
2424
from nattype cimport NPY_NAT
2525
from np_datetime cimport (pandas_datetimestruct,
2626
dtstruct_to_dt64, dt64_to_dtstruct,
27-
is_leapyear, days_per_month_table)
27+
is_leapyear, days_per_month_table, dayofweek)
2828

2929
# ---------------------------------------------------------------------
3030
# Constants
@@ -145,45 +145,44 @@ def apply_index_wraps(func):
145145
# ---------------------------------------------------------------------
146146
# Business Helpers
147147

148-
cpdef int get_lastbday(int wkday, int days_in_month):
148+
cpdef int get_lastbday(int year, int month) nogil:
149149
"""
150150
Find the last day of the month that is a business day.
151151
152-
(wkday, days_in_month) is the output from monthrange(year, month)
153-
154152
Parameters
155153
----------
156-
wkday : int
157-
days_in_month : int
154+
year : int
155+
month : int
158156
159157
Returns
160158
-------
161159
last_bday : int
162160
"""
161+
cdef:
162+
int wkday, days_in_month
163+
164+
wkday = dayofweek(year, month, 1)
165+
days_in_month = get_days_in_month(year, month)
163166
return days_in_month - max(((wkday + days_in_month - 1) % 7) - 4, 0)
164167

165168

166-
cpdef int get_firstbday(int wkday, int days_in_month=0):
169+
cpdef int get_firstbday(int year, int month) nogil:
167170
"""
168171
Find the first day of the month that is a business day.
169172
170-
(wkday, days_in_month) is the output from monthrange(year, month)
171-
172173
Parameters
173174
----------
174-
wkday : int
175-
days_in_month : int, default 0
175+
year : int
176+
month : int
176177
177178
Returns
178179
-------
179180
first_bday : int
180-
181-
Notes
182-
-----
183-
`days_in_month` arg is a dummy so that this has the same signature as
184-
`get_lastbday`.
185181
"""
186-
cdef int first
182+
cdef:
183+
int first, wkday
184+
185+
wkday = dayofweek(year, month, 1)
187186
first = 1
188187
if wkday == 5: # on Saturday
189188
first = 3
@@ -556,52 +555,50 @@ def shift_months(int64_t[:] dtindex, int months, object day=None):
556555
out[i] = dtstruct_to_dt64(&dts)
557556

558557
elif day == 'business_start':
559-
for i in range(count):
560-
if dtindex[i] == NPY_NAT:
561-
out[i] = NPY_NAT
562-
continue
558+
with nogil:
559+
for i in range(count):
560+
if dtindex[i] == NPY_NAT:
561+
out[i] = NPY_NAT
562+
continue
563563

564-
dt64_to_dtstruct(dtindex[i], &dts)
565-
months_to_roll = months
566-
wkday, days_in_month = monthrange(dts.year, dts.month)
567-
compare_day = get_firstbday(wkday, days_in_month)
564+
dt64_to_dtstruct(dtindex[i], &dts)
565+
months_to_roll = months
566+
compare_day = get_firstbday(dts.year, dts.month)
568567

569-
if months_to_roll > 0 and dts.day < compare_day:
570-
months_to_roll -= 1
571-
elif months_to_roll <= 0 and dts.day > compare_day:
572-
# as if rolled forward already
573-
months_to_roll += 1
568+
if months_to_roll > 0 and dts.day < compare_day:
569+
months_to_roll -= 1
570+
elif months_to_roll <= 0 and dts.day > compare_day:
571+
# as if rolled forward already
572+
months_to_roll += 1
574573

575-
dts.year = year_add_months(dts, months_to_roll)
576-
dts.month = month_add_months(dts, months_to_roll)
574+
dts.year = year_add_months(dts, months_to_roll)
575+
dts.month = month_add_months(dts, months_to_roll)
577576

578-
wkday, days_in_month = monthrange(dts.year, dts.month)
579-
dts.day = get_firstbday(wkday, days_in_month)
580-
out[i] = dtstruct_to_dt64(&dts)
577+
dts.day = get_firstbday(dts.year, dts.month)
578+
out[i] = dtstruct_to_dt64(&dts)
581579

582580
elif day == 'business_end':
583-
for i in range(count):
584-
if dtindex[i] == NPY_NAT:
585-
out[i] = NPY_NAT
586-
continue
581+
with nogil:
582+
for i in range(count):
583+
if dtindex[i] == NPY_NAT:
584+
out[i] = NPY_NAT
585+
continue
587586

588-
dt64_to_dtstruct(dtindex[i], &dts)
589-
months_to_roll = months
590-
wkday, days_in_month = monthrange(dts.year, dts.month)
591-
compare_day = get_lastbday(wkday, days_in_month)
587+
dt64_to_dtstruct(dtindex[i], &dts)
588+
months_to_roll = months
589+
compare_day = get_lastbday(dts.year, dts.month)
592590

593-
if months_to_roll > 0 and dts.day < compare_day:
594-
months_to_roll -= 1
595-
elif months_to_roll <= 0 and dts.day > compare_day:
596-
# as if rolled forward already
597-
months_to_roll += 1
591+
if months_to_roll > 0 and dts.day < compare_day:
592+
months_to_roll -= 1
593+
elif months_to_roll <= 0 and dts.day > compare_day:
594+
# as if rolled forward already
595+
months_to_roll += 1
598596

599-
dts.year = year_add_months(dts, months_to_roll)
600-
dts.month = month_add_months(dts, months_to_roll)
597+
dts.year = year_add_months(dts, months_to_roll)
598+
dts.month = month_add_months(dts, months_to_roll)
601599

602-
wkday, days_in_month = monthrange(dts.year, dts.month)
603-
dts.day = get_lastbday(wkday, days_in_month)
604-
out[i] = dtstruct_to_dt64(&dts)
600+
dts.day = get_lastbday(dts.year, dts.month)
601+
out[i] = dtstruct_to_dt64(&dts)
605602

606603
else:
607604
raise ValueError("day must be None, 'start', 'end', "
@@ -635,7 +632,7 @@ cpdef datetime shift_month(datetime stamp, int months, object day_opt=None):
635632
"""
636633
cdef:
637634
int year, month, day
638-
int wkday, days_in_month, dy
635+
int days_in_month, dy
639636

640637
dy = (stamp.month + months) // 12
641638
month = (stamp.month + months) % 12
@@ -645,20 +642,21 @@ cpdef datetime shift_month(datetime stamp, int months, object day_opt=None):
645642
dy -= 1
646643
year = stamp.year + dy
647644

648-
wkday, days_in_month = monthrange(year, month)
649645
if day_opt is None:
646+
days_in_month = get_days_in_month(year, month)
650647
day = min(stamp.day, days_in_month)
651648
elif day_opt == 'start':
652649
day = 1
653650
elif day_opt == 'end':
654-
day = days_in_month
651+
day = get_days_in_month(year, month)
655652
elif day_opt == 'business_start':
656653
# first business day of month
657-
day = get_firstbday(wkday, days_in_month)
654+
day = get_firstbday(year, month)
658655
elif day_opt == 'business_end':
659656
# last business day of month
660-
day = get_lastbday(wkday, days_in_month)
657+
day = get_lastbday(year, month)
661658
elif is_integer_object(day_opt):
659+
days_in_month = get_days_in_month(year, month)
662660
day = min(day_opt, days_in_month)
663661
else:
664662
raise ValueError(day_opt)
@@ -691,22 +689,22 @@ cpdef int get_day_of_month(datetime other, day_opt) except? -1:
691689
692690
"""
693691
cdef:
694-
int wkday, days_in_month
692+
int days_in_month
695693

696694
if day_opt == 'start':
697695
return 1
698-
699-
wkday, days_in_month = monthrange(other.year, other.month)
700-
if day_opt == 'end':
696+
elif day_opt == 'end':
697+
days_in_month = get_days_in_month(other.year, other.month)
701698
return days_in_month
702699
elif day_opt == 'business_start':
703700
# first business day of month
704-
return get_firstbday(wkday, days_in_month)
701+
return get_firstbday(other.year, other.month)
705702
elif day_opt == 'business_end':
706703
# last business day of month
707-
return get_lastbday(wkday, days_in_month)
704+
return get_lastbday(other.year, other.month)
708705
elif is_integer_object(day_opt):
709-
day = min(day_opt, days_in_month)
706+
days_in_month = get_days_in_month(other.year, other.month)
707+
return min(day_opt, days_in_month)
710708
elif day_opt is None:
711709
# Note: unlike `shift_month`, get_day_of_month does not
712710
# allow day_opt = None

pandas/tests/tseries/offsets/test_liboffsets.py

+4-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import pytest
88

9-
from pandas._libs import tslib
109
from pandas import Timestamp
1110

1211
import pandas._libs.tslibs.offsets as liboffsets
@@ -15,25 +14,21 @@
1514
def test_get_lastbday():
1615
dt = datetime(2017, 11, 30)
1716
assert dt.weekday() == 3 # i.e. this is a business day
18-
wkday, days_in_month = tslib.monthrange(dt.year, dt.month)
19-
assert liboffsets.get_lastbday(wkday, days_in_month) == 30
17+
assert liboffsets.get_lastbday(dt.year, dt.month) == 30
2018

2119
dt = datetime(1993, 10, 31)
2220
assert dt.weekday() == 6 # i.e. this is not a business day
23-
wkday, days_in_month = tslib.monthrange(dt.year, dt.month)
24-
assert liboffsets.get_lastbday(wkday, days_in_month) == 29
21+
assert liboffsets.get_lastbday(dt.year, dt.month) == 29
2522

2623

2724
def test_get_firstbday():
2825
dt = datetime(2017, 4, 1)
2926
assert dt.weekday() == 5 # i.e. not a weekday
30-
wkday, days_in_month = tslib.monthrange(dt.year, dt.month)
31-
assert liboffsets.get_firstbday(wkday, days_in_month) == 3
27+
assert liboffsets.get_firstbday(dt.year, dt.month) == 3
3228

3329
dt = datetime(1993, 10, 1)
3430
assert dt.weekday() == 4 # i.e. a business day
35-
wkday, days_in_month = tslib.monthrange(dt.year, dt.month)
36-
assert liboffsets.get_firstbday(wkday, days_in_month) == 1
31+
assert liboffsets.get_firstbday(dt.year, dt.month) == 1
3732

3833

3934
def test_shift_month():

0 commit comments

Comments
 (0)