diff --git a/pandas/_libs/tslibs/ccalendar.pyx b/pandas/_libs/tslibs/ccalendar.pyx index a68ecbd2e8629..d7edae865911a 100644 --- a/pandas/_libs/tslibs/ccalendar.pyx +++ b/pandas/_libs/tslibs/ccalendar.pyx @@ -33,6 +33,17 @@ 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'] +MONTH_NUMBERS = {name: num for num, name in enumerate(MONTHS)} +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'] +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} + # ---------------------------------------------------------------------- diff --git a/pandas/_libs/tslibs/fields.pyx b/pandas/_libs/tslibs/fields.pyx index 950677b3b53db..18101c834c737 100644 --- a/pandas/_libs/tslibs/fields.pyx +++ b/pandas/_libs/tslibs/fields.pyx @@ -139,8 +139,8 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, int mo_off, dom, doy, dow, ldom _month_offset = np.array( - [[ 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 ]], + [[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]], dtype=np.int32) count = len(dtindex) @@ -380,8 +380,8 @@ def get_date_field(ndarray[int64_t] dtindex, object field): int mo_off, doy, dow _month_offset = np.array( - [[ 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 ]], + [[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]], dtype=np.int32 ) count = len(dtindex) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 29e14103dfe20..933e7ed64b837 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -17,6 +17,7 @@ np.import_array() from util cimport is_string_object, is_integer_object +from ccalendar import MONTHS, DAYS from conversion cimport tz_convert_single, pydt_to_i8 from frequencies cimport get_freq_code from nattype cimport NPY_NAT @@ -27,14 +28,9 @@ from np_datetime cimport (pandas_datetimestruct, # --------------------------------------------------------------------- # Constants -# Duplicated in tslib -_MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', - 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] -_int_to_month = {(k + 1): v for k, v in enumerate(_MONTHS)} -_month_to_int = {v: k for k, v in _int_to_month.items()} - class WeekDay(object): + # TODO: Remove: This is not used outside of tests MON = 0 TUE = 1 WED = 2 @@ -44,18 +40,6 @@ class WeekDay(object): SUN = 6 -_int_to_weekday = { - WeekDay.MON: 'MON', - WeekDay.TUE: 'TUE', - WeekDay.WED: 'WED', - WeekDay.THU: 'THU', - WeekDay.FRI: 'FRI', - WeekDay.SAT: 'SAT', - WeekDay.SUN: 'SUN'} - -_weekday_to_int = {_int_to_weekday[key]: key for key in _int_to_weekday} - - _offset_to_period_map = { 'WEEKDAY': 'D', 'EOM': 'M', @@ -88,17 +72,16 @@ _offset_to_period_map = { need_suffix = ['QS', 'BQ', 'BQS', 'YS', 'AS', 'BY', 'BA', 'BYS', 'BAS'] for __prefix in need_suffix: - for _m in _MONTHS: + for _m in MONTHS: key = '%s-%s' % (__prefix, _m) _offset_to_period_map[key] = _offset_to_period_map[__prefix] for __prefix in ['A', 'Q']: - for _m in _MONTHS: + for _m in MONTHS: _alias = '%s-%s' % (__prefix, _m) _offset_to_period_map[_alias] = _alias -_days = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] -for _d in _days: +for _d in DAYS: _offset_to_period_map['W-%s' % _d] = 'W-%s' % _d diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 8ce1d9cdf2158..a9a5500cd7447 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -41,6 +41,9 @@ from dateutil.relativedelta import relativedelta from dateutil.parser import DEFAULTPARSER from dateutil.parser import parse as du_parse +from ccalendar import MONTH_NUMBERS +from nattype import nat_strings + # ---------------------------------------------------------------------- # Constants @@ -49,14 +52,8 @@ class DateParseError(ValueError): pass -_nat_strings = set(['NaT', 'nat', 'NAT', 'nan', 'NaN', 'NAN']) - _DEFAULT_DATETIME = datetime(1, 1, 1).replace(hour=0, minute=0, second=0, microsecond=0) -_MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', - 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] -_MONTH_NUMBERS = {k: i for i, k in enumerate(_MONTHS)} -_MONTH_ALIASES = {(k + 1): v for k, v in enumerate(_MONTHS)} cdef object _TIMEPAT = re.compile(r'^([01]?[0-9]|2[0-3]):([0-5][0-9])') @@ -213,7 +210,7 @@ cdef inline object _parse_dateabbr_string(object date_string, object default, # len(date_string) == 0 # should be NaT??? - if date_string in _nat_strings: + if date_string in nat_strings: return NAT_SENTINEL, NAT_SENTINEL, '' date_string = date_string.upper() @@ -267,7 +264,7 @@ cdef inline object _parse_dateabbr_string(object date_string, object default, if freq is not None: # hack attack, #1228 try: - mnum = _MONTH_NUMBERS[_get_rule_month(freq)] + 1 + mnum = MONTH_NUMBERS[_get_rule_month(freq)] + 1 except (KeyError, ValueError): msg = ('Unable to retrieve month information from given ' 'freq: {0}').format(freq) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index cf73257caf227..42570e355e2bf 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -32,8 +32,9 @@ from timestamps import Timestamp from timezones cimport is_utc, is_tzlocal, get_utcoffset, get_dst_info from timedeltas cimport delta_to_nanoseconds +from ccalendar import MONTH_NUMBERS from parsing import (parse_time_string, NAT_SENTINEL, - _get_rule_month, _MONTH_NUMBERS) + _get_rule_month) from frequencies cimport get_freq_code from resolution import resolution, Resolution from nattype import nat_strings, NaT, iNaT @@ -1148,7 +1149,7 @@ def _quarter_to_myear(year, quarter, freq): if quarter <= 0 or quarter > 4: raise ValueError('Quarter must be 1 <= q <= 4') - mnum = _MONTH_NUMBERS[_get_rule_month(freq)] + 1 + mnum = MONTH_NUMBERS[_get_rule_month(freq)] + 1 month = (mnum + (quarter - 1) * 3) % 12 + 1 if month > mnum: year -= 1 diff --git a/pandas/_libs/tslibs/resolution.pyx b/pandas/_libs/tslibs/resolution.pyx index d2b518c74a1e3..9cb2c450524fb 100644 --- a/pandas/_libs/tslibs/resolution.pyx +++ b/pandas/_libs/tslibs/resolution.pyx @@ -17,13 +17,13 @@ from pandas._libs.khash cimport (khiter_t, from cpython.datetime cimport datetime -from np_datetime cimport (pandas_datetimestruct, - dtstruct_to_dt64, dt64_to_dtstruct) +from np_datetime cimport pandas_datetimestruct, dt64_to_dtstruct from frequencies cimport get_freq_code from timezones cimport (is_utc, is_tzlocal, maybe_get_tz, get_dst_info, get_utcoffset) from fields import build_field_sarray from conversion import tz_convert +from ccalendar import DAYS, MONTH_ALIASES, int_to_weekday from pandas._libs.properties import cache_readonly from pandas._libs.tslib import Timestamp @@ -50,13 +50,6 @@ _ONE_MINUTE = 60 * _ONE_SECOND _ONE_HOUR = 60 * _ONE_MINUTE _ONE_DAY = 24 * _ONE_HOUR -DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] -_weekday_rule_aliases = {k: v for k, v in enumerate(DAYS)} - -_MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', - 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] -_MONTH_ALIASES = {(k + 1): v for k, v in enumerate(_MONTHS)} - # ---------------------------------------------------------------------- cpdef resolution(ndarray[int64_t] stamps, tz=None): @@ -354,7 +347,7 @@ class Resolution(object): # Frequency Inference -# TODO: this is non performiant logic here (and duplicative) and this +# TODO: this is non performant logic here (and duplicative) and this # simply should call unique_1d directly # plus no reason to depend on khash directly cdef unique_deltas(ndarray[int64_t] arr): @@ -537,7 +530,7 @@ class _FrequencyInferer(object): annual_rule = self._get_annual_rule() if annual_rule: nyears = self.ydiffs[0] - month = _MONTH_ALIASES[self.rep_stamp.month] + month = MONTH_ALIASES[self.rep_stamp.month] alias = '{prefix}-{month}'.format(prefix=annual_rule, month=month) return _maybe_add_count(alias, nyears) @@ -545,7 +538,7 @@ class _FrequencyInferer(object): if quarterly_rule: nquarters = self.mdiffs[0] / 3 mod_dict = {0: 12, 2: 11, 1: 10} - month = _MONTH_ALIASES[mod_dict[self.rep_stamp.month % 3]] + month = MONTH_ALIASES[mod_dict[self.rep_stamp.month % 3]] alias = '{prefix}-{month}'.format(prefix=quarterly_rule, month=month) return _maybe_add_count(alias, nquarters) @@ -558,7 +551,7 @@ class _FrequencyInferer(object): days = self.deltas[0] / _ONE_DAY if days % 7 == 0: # Weekly - day = _weekday_rule_aliases[self.rep_stamp.weekday()] + day = int_to_weekday[self.rep_stamp.weekday()] return _maybe_add_count('W-{day}'.format(day=day), days / 7) else: return _maybe_add_count('D', days) @@ -630,7 +623,7 @@ class _FrequencyInferer(object): # get which week week = week_of_months[0] + 1 - wd = _weekday_rule_aliases[weekdays[0]] + wd = int_to_weekday[weekdays[0]] return 'WOM-{week}{weekday}'.format(week=week, weekday=wd) @@ -642,7 +635,7 @@ class _TimedeltaFrequencyInferer(_FrequencyInferer): days = self.deltas[0] / _ONE_DAY if days % 7 == 0: # Weekly - wd = _weekday_rule_aliases[self.rep_stamp.weekday()] + wd = int_to_weekday[self.rep_stamp.weekday()] alias = 'W-{weekday}'.format(weekday=wd) return _maybe_add_count(alias, days / 7) else: diff --git a/pandas/tests/indexes/period/test_tools.py b/pandas/tests/indexes/period/test_tools.py index 9cbcfa4f46008..9df23948ae627 100644 --- a/pandas/tests/indexes/period/test_tools.py +++ b/pandas/tests/indexes/period/test_tools.py @@ -6,7 +6,8 @@ import pandas.core.indexes.period as period from pandas.compat import lrange from pandas.tseries.frequencies import get_freq -from pandas._libs.tslibs.resolution import _MONTHS as MONTHS + +from pandas._libs.tslibs.ccalendar import MONTHS from pandas._libs.tslibs.period import period_ordinal, period_asfreq from pandas import (PeriodIndex, Period, DatetimeIndex, Timestamp, Series, date_range, to_datetime, period_range) diff --git a/pandas/tests/scalar/test_period.py b/pandas/tests/scalar/test_period.py index eb6363689cca0..792eb0d49077f 100644 --- a/pandas/tests/scalar/test_period.py +++ b/pandas/tests/scalar/test_period.py @@ -12,9 +12,9 @@ from pandas._libs import tslib from pandas._libs.tslibs import period as libperiod +from pandas._libs.tslibs.ccalendar import DAYS, MONTHS from pandas._libs.tslibs.parsing import DateParseError from pandas import Period, Timestamp, offsets -from pandas._libs.tslibs.resolution import DAYS, _MONTHS as MONTHS class TestPeriodProperties(object): diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index 1fd6befd64f57..f00fa07d868a1 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -22,7 +22,7 @@ from pandas.core.base import SpecificationError, AbstractMethodError from pandas.errors import UnsupportedFunctionCall from pandas.core.groupby import DataError -from pandas._libs.tslibs.resolution import DAYS, _MONTHS as MONTHS + from pandas.tseries.frequencies import to_offset from pandas.core.indexes.datetimes import date_range from pandas.tseries.offsets import Minute, BDay @@ -33,6 +33,7 @@ from pandas.util.testing import (assert_series_equal, assert_almost_equal, assert_frame_equal, assert_index_equal) from pandas._libs.tslibs.period import IncompatibleFrequency +from pandas._libs.tslibs.ccalendar import DAYS, MONTHS bday = BDay() diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index 460ad3f5591fc..f6e3d1f271036 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -25,7 +25,8 @@ from pandas._libs.tslibs.resolution import (Resolution, _FrequencyInferer, _TimedeltaFrequencyInferer) -from pandas._libs.tslibs.parsing import _get_rule_month, _MONTH_NUMBERS +from pandas._libs.tslibs.parsing import _get_rule_month +from pandas._libs.tslibs.ccalendar import MONTH_NUMBERS from pytz import AmbiguousTimeError @@ -496,8 +497,8 @@ def _is_annual(rule): def _quarter_months_conform(source, target): - snum = _MONTH_NUMBERS[source] - tnum = _MONTH_NUMBERS[target] + snum = MONTH_NUMBERS[source] + tnum = MONTH_NUMBERS[target] return snum % 3 == tnum % 3 diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index dd5f01a36a43e..8b12b2f3ad2ce 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -16,13 +16,13 @@ from pandas._libs import tslib, Timestamp, OutOfBoundsDatetime, Timedelta from pandas.util._decorators import cache_readonly +from pandas._libs.tslibs import ccalendar from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds import pandas._libs.tslibs.offsets as liboffsets from pandas._libs.tslibs.offsets import ( ApplyTypeError, as_datetime, _is_normalized, _get_calendar, _to_dt64, _validate_business_time, - _int_to_weekday, _weekday_to_int, _determine_offset, apply_index_wraps, roll_yearday, @@ -933,7 +933,7 @@ def name(self): if self.isAnchored: return self.rule_code else: - month = liboffsets._int_to_month[self.n] + month = ccalendar.MONTH_ALIASES[self.n] return "{code}-{month}".format(code=self.rule_code, month=month) @@ -1348,7 +1348,8 @@ def onOffset(self, dt): def rule_code(self): suffix = '' if self.weekday is not None: - suffix = '-{weekday}'.format(weekday=_int_to_weekday[self.weekday]) + weekday = ccalendar.int_to_weekday[self.weekday] + suffix = '-{weekday}'.format(weekday=weekday) return self._prefix + suffix @classmethod @@ -1356,7 +1357,7 @@ def _from_name(cls, suffix=None): if not suffix: weekday = None else: - weekday = _weekday_to_int[suffix] + weekday = ccalendar.weekday_to_int[suffix] return cls(weekday=weekday) @@ -1430,7 +1431,7 @@ def onOffset(self, dt): @property def rule_code(self): - weekday = _int_to_weekday.get(self.weekday, '') + weekday = ccalendar.int_to_weekday.get(self.weekday, '') return '{prefix}-{week}{weekday}'.format(prefix=self._prefix, week=self.week + 1, weekday=weekday) @@ -1443,7 +1444,7 @@ def _from_name(cls, suffix=None): # TODO: handle n here... # only one digit weeks (1 --> week 0, 2 --> week 1, etc.) week = int(suffix[0]) - 1 - weekday = _weekday_to_int[suffix[1:]] + weekday = ccalendar.weekday_to_int[suffix[1:]] return cls(week=week, weekday=weekday) @@ -1509,7 +1510,7 @@ def onOffset(self, dt): @property def rule_code(self): - weekday = _int_to_weekday.get(self.weekday, '') + weekday = ccalendar.int_to_weekday.get(self.weekday, '') return '{prefix}-{weekday}'.format(prefix=self._prefix, weekday=weekday) @@ -1519,7 +1520,7 @@ def _from_name(cls, suffix=None): raise ValueError("Prefix {prefix!r} requires a suffix." .format(prefix=cls._prefix)) # TODO: handle n here... - weekday = _weekday_to_int[suffix] + weekday = ccalendar.weekday_to_int[suffix] return cls(weekday=weekday) # --------------------------------------------------------------------- @@ -1550,7 +1551,7 @@ def isAnchored(self): def _from_name(cls, suffix=None): kwargs = {} if suffix: - kwargs['startingMonth'] = liboffsets._month_to_int[suffix] + kwargs['startingMonth'] = ccalendar.MONTH_TO_CAL_NUM[suffix] else: if cls._from_name_startingMonth is not None: kwargs['startingMonth'] = cls._from_name_startingMonth @@ -1558,7 +1559,7 @@ def _from_name(cls, suffix=None): @property def rule_code(self): - month = liboffsets._int_to_month[self.startingMonth] + month = ccalendar.MONTH_ALIASES[self.startingMonth] return '{prefix}-{month}'.format(prefix=self._prefix, month=month) @apply_wraps @@ -1681,12 +1682,12 @@ def __init__(self, n=1, normalize=False, month=None): def _from_name(cls, suffix=None): kwargs = {} if suffix: - kwargs['month'] = liboffsets._month_to_int[suffix] + kwargs['month'] = ccalendar.MONTH_TO_CAL_NUM[suffix] return cls(**kwargs) @property def rule_code(self): - month = liboffsets._int_to_month[self.month] + month = ccalendar.MONTH_ALIASES[self.month] return '{prefix}-{month}'.format(prefix=self._prefix, month=month) @@ -1906,8 +1907,8 @@ def _get_suffix_prefix(self): def get_rule_code_suffix(self): prefix = self._get_suffix_prefix() - month = liboffsets._int_to_month[self.startingMonth] - weekday = _int_to_weekday[self.weekday] + month = ccalendar.MONTH_ALIASES[self.startingMonth] + weekday = ccalendar.int_to_weekday[self.weekday] return '{prefix}-{month}-{weekday}'.format(prefix=prefix, month=month, weekday=weekday) @@ -1921,8 +1922,8 @@ def _parse_suffix(cls, varion_code, startingMonth_code, weekday_code): raise ValueError("Unable to parse varion_code: " "{code}".format(code=varion_code)) - startingMonth = liboffsets._month_to_int[startingMonth_code] - weekday = _weekday_to_int[weekday_code] + startingMonth = ccalendar.MONTH_TO_CAL_NUM[startingMonth_code] + weekday = ccalendar.weekday_to_int[weekday_code] return {"weekday": weekday, "startingMonth": startingMonth,