Skip to content

REF: re-use get_firstbday, get_lastbday in fields.pyx #35199

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 1 commit into from
Jul 9, 2020
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
2 changes: 2 additions & 0 deletions pandas/_libs/tslibs/ccalendar.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ cpdef int32_t get_days_in_month(int year, Py_ssize_t month) nogil
cpdef int32_t get_week_of_year(int year, int month, int day) nogil
cpdef iso_calendar_t get_iso_calendar(int year, int month, int day) nogil
cpdef int32_t get_day_of_year(int year, int month, int day) nogil
cpdef int get_lastbday(int year, int month) nogil
cpdef int get_firstbday(int year, int month) nogil

cdef int64_t DAY_NANOS
cdef int64_t HOUR_NANOS
Expand Down
49 changes: 49 additions & 0 deletions pandas/_libs/tslibs/ccalendar.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,52 @@ cpdef int32_t get_day_of_year(int year, int month, int day) nogil:

day_of_year = mo_off + day
return day_of_year


# ---------------------------------------------------------------------
# Business Helpers

cpdef int get_lastbday(int year, int month) nogil:
"""
Find the last day of the month that is a business day.

Parameters
----------
year : int
month : int

Returns
-------
last_bday : int
"""
cdef:
int wkday, days_in_month

wkday = dayofweek(year, month, 1)
days_in_month = get_days_in_month(year, month)
return days_in_month - max(((wkday + days_in_month - 1) % 7) - 4, 0)


cpdef int get_firstbday(int year, int month) nogil:
"""
Find the first day of the month that is a business day.

Parameters
----------
year : int
month : int

Returns
-------
first_bday : int
"""
cdef:
int first, wkday

wkday = dayofweek(year, month, 1)
first = 1
if wkday == 5: # on Saturday
first = 3
elif wkday == 6: # on Sunday
first = 2
return first
77 changes: 17 additions & 60 deletions pandas/_libs/tslibs/fields.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ from pandas._libs.tslibs.ccalendar cimport (
get_days_in_month, is_leapyear, dayofweek, get_week_of_year,
get_day_of_year, get_iso_calendar, iso_calendar_t,
month_offset,
get_firstbday,
get_lastbday,
)
from pandas._libs.tslibs.np_datetime cimport (
npy_datetimestruct, pandas_timedeltastruct, dt64_to_dtstruct,
Expand Down Expand Up @@ -137,9 +139,7 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
int end_month = 12
int start_month = 1
ndarray[int8_t] out
bint isleap
npy_datetimestruct dts
int mo_off, dom, doy, dow, ldom

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

Expand Down Expand Up @@ -172,10 +172,8 @@ def get_start_end_field(const int64_t[:] dtindex, str field,
continue

dt64_to_dtstruct(dtindex[i], &dts)
dom = dts.day
dow = dayofweek(dts.year, dts.month, dts.day)

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

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

dt64_to_dtstruct(dtindex[i], &dts)
dom = dts.day

if dom == 1:
if dts.day == 1:
out[i] = 1

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

dt64_to_dtstruct(dtindex[i], &dts)
isleap = is_leapyear(dts.year)
mo_off = month_offset[isleap * 13 + dts.month - 1]
dom = dts.day
doy = mo_off + dom
ldom = month_offset[isleap * 13 + dts.month]
dow = dayofweek(dts.year, dts.month, dts.day)

if (ldom == doy and dow < 5) or (
dow == 4 and (ldom - doy <= 2)):

if dts.day == get_lastbday(dts.year, dts.month):
out[i] = 1

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

dt64_to_dtstruct(dtindex[i], &dts)
isleap = is_leapyear(dts.year)
mo_off = month_offset[isleap * 13 + dts.month - 1]
dom = dts.day
doy = mo_off + dom
ldom = month_offset[isleap * 13 + dts.month]

if ldom == doy:
if dts.day == get_days_in_month(dts.year, dts.month):
out[i] = 1

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

dt64_to_dtstruct(dtindex[i], &dts)
dom = dts.day
dow = dayofweek(dts.year, dts.month, dts.day)

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

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

dt64_to_dtstruct(dtindex[i], &dts)
dom = dts.day

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

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

dt64_to_dtstruct(dtindex[i], &dts)
isleap = is_leapyear(dts.year)
mo_off = month_offset[isleap * 13 + dts.month - 1]
dom = dts.day
doy = mo_off + dom
ldom = month_offset[isleap * 13 + dts.month]
dow = dayofweek(dts.year, dts.month, dts.day)

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

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

dt64_to_dtstruct(dtindex[i], &dts)
isleap = is_leapyear(dts.year)
mo_off = month_offset[isleap * 13 + dts.month - 1]
dom = dts.day
doy = mo_off + dom
ldom = month_offset[isleap * 13 + dts.month]

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

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

dt64_to_dtstruct(dtindex[i], &dts)
dom = dts.day
dow = dayofweek(dts.year, dts.month, dts.day)

if (dts.month == start_month) and (
(dom == 1 and dow < 5) or (dom <= 3 and dow == 0)):
dts.day == get_firstbday(dts.year, dts.month)):
out[i] = 1

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

dt64_to_dtstruct(dtindex[i], &dts)
dom = dts.day

if (dts.month == start_month) and dom == 1:
if (dts.month == start_month) and dts.day == 1:
out[i] = 1

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

dt64_to_dtstruct(dtindex[i], &dts)
isleap = is_leapyear(dts.year)
dom = dts.day
mo_off = month_offset[isleap * 13 + dts.month - 1]
doy = mo_off + dom
dow = dayofweek(dts.year, dts.month, dts.day)
ldom = month_offset[isleap * 13 + dts.month]

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

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

dt64_to_dtstruct(dtindex[i], &dts)
isleap = is_leapyear(dts.year)
mo_off = month_offset[isleap * 13 + dts.month - 1]
dom = dts.day
doy = mo_off + dom
ldom = month_offset[isleap * 13 + dts.month]

if (dts.month == end_month) and (ldom == doy):
if (dts.month == end_month) and (
dts.day == get_days_in_month(dts.year, dts.month)):
out[i] = 1

else:
Expand Down
53 changes: 7 additions & 46 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ from pandas._libs.tslibs.util cimport (
from pandas._libs.tslibs.ccalendar import (
MONTH_ALIASES, MONTH_TO_CAL_NUM, weekday_to_int, int_to_weekday,
)
from pandas._libs.tslibs.ccalendar cimport DAY_NANOS, get_days_in_month, dayofweek
from pandas._libs.tslibs.ccalendar cimport (
DAY_NANOS,
dayofweek,
get_days_in_month,
get_firstbday,
get_lastbday,
)
from pandas._libs.tslibs.conversion cimport (
convert_datetime_to_tsobject,
localize_pydatetime,
Expand Down Expand Up @@ -177,51 +183,6 @@ cdef _wrap_timedelta_result(result):
# ---------------------------------------------------------------------
# Business Helpers

cpdef int get_lastbday(int year, int month) nogil:
"""
Find the last day of the month that is a business day.

Parameters
----------
year : int
month : int

Returns
-------
last_bday : int
"""
cdef:
int wkday, days_in_month

wkday = dayofweek(year, month, 1)
days_in_month = get_days_in_month(year, month)
return days_in_month - max(((wkday + days_in_month - 1) % 7) - 4, 0)


cpdef int get_firstbday(int year, int month) nogil:
"""
Find the first day of the month that is a business day.

Parameters
----------
year : int
month : int

Returns
-------
first_bday : int
"""
cdef:
int first, wkday

wkday = dayofweek(year, month, 1)
first = 1
if wkday == 5: # on Saturday
first = 3
elif wkday == 6: # on Sunday
first = 2
return first


cdef _get_calendar(weekmask, holidays, calendar):
"""Generate busdaycalendar"""
Expand Down
5 changes: 3 additions & 2 deletions pandas/tests/tslibs/test_liboffsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest

from pandas._libs.tslibs.ccalendar import get_firstbday, get_lastbday
import pandas._libs.tslibs.offsets as liboffsets
from pandas._libs.tslibs.offsets import roll_qtrday

Expand All @@ -25,7 +26,7 @@ def day_opt(request):
)
def test_get_last_bday(dt, exp_week_day, exp_last_day):
assert dt.weekday() == exp_week_day
assert liboffsets.get_lastbday(dt.year, dt.month) == exp_last_day
assert get_lastbday(dt.year, dt.month) == exp_last_day


@pytest.mark.parametrize(
Expand All @@ -37,7 +38,7 @@ def test_get_last_bday(dt, exp_week_day, exp_last_day):
)
def test_get_first_bday(dt, exp_week_day, exp_first_day):
assert dt.weekday() == exp_week_day
assert liboffsets.get_firstbday(dt.year, dt.month) == exp_first_day
assert get_firstbday(dt.year, dt.month) == exp_first_day


@pytest.mark.parametrize(
Expand Down