123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- # cython: boundscheck=False
- """
- Cython implementations of functions resembling the stdlib calendar module
- """
- cimport cython
- from numpy cimport (
- int32_t,
- int64_t,
- )
- # ----------------------------------------------------------------------
- # Constants
- # Slightly more performant cython lookups than a 2D table
- # The first 12 entries correspond to month lengths for non-leap years.
- # The remaining 12 entries give month lengths for leap years
- cdef int32_t* days_per_month_array = [
- 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
- 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- cdef int* em = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
- # The first 13 entries give the month days elapsed as of the first of month N
- # (or the total number of days in the year for N=13) in non-leap years.
- # The remaining 13 entries give the days elapsed in leap years.
- cdef int32_t* month_offset = [
- 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365,
- 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
- # Canonical location for other modules to find name constants
- MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL",
- "AUG", "SEP", "OCT", "NOV", "DEC"]
- # The first blank line is consistent with calendar.month_name in the calendar
- # standard library
- MONTHS_FULL = ["", "January", "February", "March", "April", "May", "June",
- "July", "August", "September", "October", "November",
- "December"]
- MONTH_NUMBERS = {name: num for num, name in enumerate(MONTHS)}
- cdef dict c_MONTH_NUMBERS = MONTH_NUMBERS
- MONTH_ALIASES = {(num + 1): name for num, name in enumerate(MONTHS)}
- MONTH_TO_CAL_NUM = {name: num + 1 for num, name in enumerate(MONTHS)}
- DAYS = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
- DAYS_FULL = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
- "Saturday", "Sunday"]
- int_to_weekday = {num: name for num, name in enumerate(DAYS)}
- weekday_to_int = {int_to_weekday[key]: key for key in int_to_weekday}
- # ----------------------------------------------------------------------
- @cython.wraparound(False)
- @cython.boundscheck(False)
- cpdef int32_t get_days_in_month(int year, Py_ssize_t month) nogil:
- """
- Return the number of days in the given month of the given year.
- Parameters
- ----------
- year : int
- month : int
- Returns
- -------
- days_in_month : int
- Notes
- -----
- Assumes that the arguments are valid. Passing a month not between 1 and 12
- risks a segfault.
- """
- return days_per_month_array[12 * is_leapyear(year) + month - 1]
- @cython.wraparound(False)
- @cython.boundscheck(False)
- @cython.cdivision(True)
- cdef long quot(long a , long b) noexcept nogil:
- cdef long x
- x = a/b
- if (a < 0):
- x -= (a % b != 0)
- return x
- @cython.wraparound(False)
- @cython.boundscheck(False)
- @cython.cdivision(True)
- cdef int dayofweek(int y, int m, int d) noexcept nogil:
- """
- Find the day of week for the date described by the Y/M/D triple y, m, d
- using Gauss' method, from wikipedia.
- 0 represents Monday. See [1]_.
- Parameters
- ----------
- y : int
- m : int
- d : int
- Returns
- -------
- weekday : int
- Notes
- -----
- Assumes that y, m, d, represents a valid date.
- See Also
- --------
- [1] https://docs.python.org/3/library/calendar.html#calendar.weekday
- [2] https://en.wikipedia.org/wiki/\
- Determination_of_the_day_of_the_week#Gauss's_algorithm
- """
- # Note: this particular implementation comes from
- # http://berndt-schwerdtfeger.de/wp-content/uploads/pdf/cal.pdf
- cdef:
- long c
- int g
- int f
- int e
- if (m < 3):
- y -= 1
- c = quot(y, 100)
- g = y - c * 100
- f = 5 * (c - quot(c, 4) * 4)
- e = em[m]
- if (m > 2):
- e -= 1
- return (-1 + d + e + f + g + g/4) % 7
- cdef bint is_leapyear(int64_t year) nogil:
- """
- Returns 1 if the given year is a leap year, 0 otherwise.
- Parameters
- ----------
- year : int
- Returns
- -------
- is_leap : bool
- """
- return ((year & 0x3) == 0 and # year % 4 == 0
- ((year % 100) != 0 or (year % 400) == 0))
- @cython.wraparound(False)
- @cython.boundscheck(False)
- cpdef int32_t get_week_of_year(int year, int month, int day) nogil:
- """
- Return the ordinal week-of-year for the given day.
- Parameters
- ----------
- year : int
- month : int
- day : int
- Returns
- -------
- week_of_year : int32_t
- Notes
- -----
- Assumes the inputs describe a valid date.
- """
- return get_iso_calendar(year, month, day)[1]
- @cython.wraparound(False)
- @cython.boundscheck(False)
- cpdef iso_calendar_t get_iso_calendar(int year, int month, int day) nogil:
- """
- Return the year, week, and day of year corresponding to ISO 8601
- Parameters
- ----------
- year : int
- month : int
- day : int
- Returns
- -------
- year : int32_t
- week : int32_t
- day : int32_t
- Notes
- -----
- Assumes the inputs describe a valid date.
- """
- cdef:
- int32_t doy, dow
- int32_t iso_year, iso_week
- doy = get_day_of_year(year, month, day)
- dow = dayofweek(year, month, day)
- # estimate
- iso_week = (doy - 1) - dow + 3
- if iso_week >= 0:
- iso_week = iso_week // 7 + 1
- # verify
- if iso_week < 0:
- if (iso_week > -2) or (iso_week == -2 and is_leapyear(year - 1)):
- iso_week = 53
- else:
- iso_week = 52
- elif iso_week == 53:
- if 31 - day + dow < 3:
- iso_week = 1
- iso_year = year
- if iso_week == 1 and month == 12:
- iso_year += 1
- elif iso_week >= 52 and month == 1:
- iso_year -= 1
- return iso_year, iso_week, dow + 1
- @cython.wraparound(False)
- @cython.boundscheck(False)
- cpdef int32_t get_day_of_year(int year, int month, int day) nogil:
- """
- Return the ordinal day-of-year for the given day.
- Parameters
- ----------
- year : int
- month : int
- day : int
- Returns
- -------
- day_of_year : int32_t
- Notes
- -----
- Assumes the inputs describe a valid date.
- """
- cdef:
- bint isleap
- int32_t mo_off
- int day_of_year
- isleap = is_leapyear(year)
- mo_off = month_offset[isleap * 13 + month - 1]
- 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
|