diff --git a/pandas/_libs/tslibs/ccalendar.pxd b/pandas/_libs/tslibs/ccalendar.pxd index ca9e01ffa3dbe..3c0f7ea824396 100644 --- a/pandas/_libs/tslibs/ccalendar.pxd +++ b/pandas/_libs/tslibs/ccalendar.pxd @@ -15,6 +15,6 @@ cpdef int32_t get_day_of_year(int year, int month, int day) noexcept nogil cpdef int get_lastbday(int year, int month) noexcept nogil cpdef int get_firstbday(int year, int month) noexcept nogil -cdef dict c_MONTH_NUMBERS +cdef dict c_MONTH_NUMBERS, MONTH_TO_CAL_NUM cdef int32_t* month_offset diff --git a/pandas/_libs/tslibs/ccalendar.pyx b/pandas/_libs/tslibs/ccalendar.pyx index 1db355fa91a20..536fa19f4c5d7 100644 --- a/pandas/_libs/tslibs/ccalendar.pyx +++ b/pandas/_libs/tslibs/ccalendar.pyx @@ -38,7 +38,7 @@ MONTHS_FULL = ["", "January", "February", "March", "April", "May", "June", 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)} +cdef dict 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", diff --git a/pandas/_libs/tslibs/dtypes.pyi b/pandas/_libs/tslibs/dtypes.pyi index d8680ed2d27b4..1f35075543fde 100644 --- a/pandas/_libs/tslibs/dtypes.pyi +++ b/pandas/_libs/tslibs/dtypes.pyi @@ -1,14 +1,6 @@ from enum import Enum -from pandas._libs.tslibs.timedeltas import UnitChoices - -# These are not public API, but are exposed in the .pyi file because they -# are imported in tests. -_attrname_to_abbrevs: dict[str, str] -_period_code_map: dict[str, int] OFFSET_TO_PERIOD_FREQSTR: dict[str, str] -OFFSET_DEPR_FREQSTR: dict[str, str] -DEPR_ABBREVS: dict[str, UnitChoices] def periods_per_day(reso: int) -> int: ... def periods_per_second(reso: int) -> int: ... diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index a8dc6b12a54b5..7995668a9b431 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -5,6 +5,7 @@ import warnings from pandas.util._exceptions import find_stack_level +from pandas._libs.tslibs.ccalendar cimport c_MONTH_NUMBERS from pandas._libs.tslibs.np_datetime cimport ( NPY_DATETIMEUNIT, get_conversion_factor, @@ -46,7 +47,7 @@ cdef class PeriodDtypeBase: def _resolution_obj(self) -> "Resolution": fgc = self._freq_group_code freq_group = FreqGroup(fgc) - abbrev = _reverse_period_code_map[freq_group.value].split("-")[0] + abbrev = _period_code_to_abbrev[freq_group.value].split("-")[0] if abbrev == "B": return Resolution.RESO_DAY attrname = _abbrev_to_attrnames[abbrev] @@ -55,7 +56,7 @@ cdef class PeriodDtypeBase: @property def _freqstr(self) -> str: # Will be passed to to_offset in Period._maybe_convert_freq - out = _reverse_period_code_map.get(self._dtype_code) + out = _period_code_to_abbrev.get(self._dtype_code) if self._n == 1: return out return str(self._n) + out @@ -150,27 +151,13 @@ _period_code_map = { "ns": PeriodDtypeCode.N, # Nanosecondly } -_reverse_period_code_map = { +cdef dict _period_code_to_abbrev = { _period_code_map[key]: key for key in _period_code_map} -# Yearly aliases; careful not to put these in _reverse_period_code_map -_period_code_map.update({"Y" + key[1:]: _period_code_map[key] - for key in _period_code_map - if key.startswith("Y-")}) - -_period_code_map.update({ - "Q": 2000, # Quarterly - December year end (default quarterly) - "Y": PeriodDtypeCode.A, # Annual - "W": 4000, # Weekly - "C": 5000, # Custom Business Day -}) - -cdef set _month_names = { - x.split("-")[-1] for x in _period_code_map.keys() if x.startswith("Y-") -} +cdef set _month_names = set(c_MONTH_NUMBERS.keys()) # Map attribute-name resolutions to resolution abbreviations -_attrname_to_abbrevs = { +cdef dict attrname_to_abbrevs = { "year": "Y", "quarter": "Q", "month": "M", @@ -182,7 +169,6 @@ _attrname_to_abbrevs = { "microsecond": "us", "nanosecond": "ns", } -cdef dict attrname_to_abbrevs = _attrname_to_abbrevs cdef dict _abbrev_to_attrnames = {v: k for k, v in attrname_to_abbrevs.items()} OFFSET_TO_PERIOD_FREQSTR: dict = { @@ -234,7 +220,7 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "YS": "Y", "BYS": "Y", } -OFFSET_DEPR_FREQSTR: dict[str, str]= { +cdef dict c_OFFSET_DEPR_FREQSTR = { "M": "ME", "Q": "QE", "Q-DEC": "QE-DEC", @@ -277,8 +263,9 @@ OFFSET_DEPR_FREQSTR: dict[str, str]= { "A-NOV": "YE-NOV", } cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR -cdef dict c_OFFSET_DEPR_FREQSTR = OFFSET_DEPR_FREQSTR -cdef dict c_REVERSE_OFFSET_DEPR_FREQSTR = {v: k for k, v in OFFSET_DEPR_FREQSTR.items()} +cdef dict c_REVERSE_OFFSET_DEPR_FREQSTR = { + v: k for k, v in c_OFFSET_DEPR_FREQSTR.items() +} cpdef freq_to_period_freqstr(freq_n, freq_name): if freq_n == 1: @@ -290,7 +277,7 @@ cpdef freq_to_period_freqstr(freq_n, freq_name): return freqstr # Map deprecated resolution abbreviations to correct resolution abbreviations -DEPR_ABBREVS: dict[str, str]= { +cdef dict c_DEPR_ABBREVS = { "A": "Y", "a": "Y", "A-DEC": "Y-DEC", @@ -359,7 +346,6 @@ DEPR_ABBREVS: dict[str, str]= { "N": "ns", "n": "ns", } -cdef dict c_DEPR_ABBREVS = DEPR_ABBREVS class FreqGroup(Enum): @@ -406,7 +392,7 @@ class Resolution(Enum): @property def attr_abbrev(self) -> str: # string that we can pass to to_offset - return _attrname_to_abbrevs[self.attrname] + return attrname_to_abbrevs[self.attrname] @property def attrname(self) -> str: @@ -450,16 +436,19 @@ class Resolution(Enum): >>> Resolution.get_reso_from_freqstr('h') == Resolution.RESO_HR True """ + cdef: + str abbrev try: - if freq in DEPR_ABBREVS: + if freq in c_DEPR_ABBREVS: + abbrev = c_DEPR_ABBREVS[freq] warnings.warn( f"\'{freq}\' is deprecated and will be removed in a future " - f"version. Please use \'{DEPR_ABBREVS.get(freq)}\' " + f"version. Please use \'{abbrev}\' " "instead of \'{freq}\'.", FutureWarning, stacklevel=find_stack_level(), ) - freq = DEPR_ABBREVS[freq] + freq = abbrev attr_name = _abbrev_to_attrnames[freq] except KeyError: # For quarterly and yearly resolutions, we need to chop off @@ -470,15 +459,16 @@ class Resolution(Enum): if split_freq[1] not in _month_names: # i.e. we want e.g. "Q-DEC", not "Q-INVALID" raise - if split_freq[0] in DEPR_ABBREVS: + if split_freq[0] in c_DEPR_ABBREVS: + abbrev = c_DEPR_ABBREVS[split_freq[0]] warnings.warn( f"\'{split_freq[0]}\' is deprecated and will be removed in a " - f"future version. Please use \'{DEPR_ABBREVS.get(split_freq[0])}\' " + f"future version. Please use \'{abbrev}\' " f"instead of \'{split_freq[0]}\'.", FutureWarning, stacklevel=find_stack_level(), ) - split_freq[0] = DEPR_ABBREVS[split_freq[0]] + split_freq[0] = abbrev attr_name = _abbrev_to_attrnames[split_freq[0]] return cls.from_attrname(attr_name) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 7f3a72178a359..3d733dfed3169 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -43,13 +43,13 @@ from pandas._libs.tslibs.util cimport ( from pandas._libs.tslibs.ccalendar import ( MONTH_ALIASES, - MONTH_TO_CAL_NUM, int_to_weekday, weekday_to_int, ) from pandas.util._exceptions import find_stack_level from pandas._libs.tslibs.ccalendar cimport ( + MONTH_TO_CAL_NUM, dayofweek, get_days_in_month, get_firstbday, diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 9590d7511891f..4918de5497c4b 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -51,7 +51,7 @@ from dateutil.tz import ( from pandas._config import get_option -from pandas._libs.tslibs.ccalendar cimport c_MONTH_NUMBERS +from pandas._libs.tslibs.ccalendar cimport MONTH_TO_CAL_NUM from pandas._libs.tslibs.dtypes cimport ( attrname_to_npy_unit, npy_unit_to_attrname, @@ -623,7 +623,7 @@ cpdef quarter_to_myear(int year, int quarter, str freq): raise ValueError("Quarter must be 1 <= q <= 4") if freq is not None: - mnum = c_MONTH_NUMBERS[get_rule_month(freq)] + 1 + mnum = MONTH_TO_CAL_NUM[get_rule_month(freq)] month = (mnum + (quarter - 1) * 3) % 12 + 1 if month > mnum: year -= 1 diff --git a/pandas/tests/scalar/period/test_asfreq.py b/pandas/tests/scalar/period/test_asfreq.py index 7164de0a228d9..4489c307172d7 100644 --- a/pandas/tests/scalar/period/test_asfreq.py +++ b/pandas/tests/scalar/period/test_asfreq.py @@ -1,6 +1,5 @@ import pytest -from pandas._libs.tslibs.dtypes import _period_code_map from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG from pandas.errors import OutOfBoundsDatetime @@ -828,5 +827,3 @@ def test_asfreq_MS(self): msg = "MonthBegin is not supported as period frequency" with pytest.raises(TypeError, match=msg): Period("2013-01", "MS") - - assert _period_code_map.get("MS") is None diff --git a/pandas/tests/tseries/frequencies/test_freq_code.py b/pandas/tests/tseries/frequencies/test_freq_code.py index 96b71ef4fe662..444f49d07481c 100644 --- a/pandas/tests/tseries/frequencies/test_freq_code.py +++ b/pandas/tests/tseries/frequencies/test_freq_code.py @@ -6,7 +6,6 @@ Resolution, to_offset, ) -from pandas._libs.tslibs.dtypes import _attrname_to_abbrevs import pandas._testing as tm @@ -40,14 +39,9 @@ def test_get_to_timestamp_base(freqstr, exp_freqstr): ], ) def test_get_attrname_from_abbrev(freqstr, expected): - assert Resolution.get_reso_from_freqstr(freqstr).attrname == expected - - -@pytest.mark.parametrize("freq", ["D", "h", "min", "s", "ms", "us", "ns"]) -def test_get_freq_roundtrip2(freq): - obj = Resolution.get_reso_from_freqstr(freq) - result = _attrname_to_abbrevs[obj.attrname] - assert freq == result + reso = Resolution.get_reso_from_freqstr(freqstr) + assert reso.attr_abbrev == freqstr + assert reso.attrname == expected @pytest.mark.parametrize(