From b542f7d41d1a03e85db5987a573f302bd84b45ba Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Jun 2020 14:32:09 -0700 Subject: [PATCH 1/9] REF: operate on Resolution objs instead of ints --- pandas/_libs/tslibs/parsing.pyx | 2 +- pandas/_libs/tslibs/period.pyx | 1 + pandas/_libs/tslibs/resolution.pyx | 35 +++++++++++++++++++++++++ pandas/core/indexes/datetimelike.py | 8 +++--- pandas/core/indexes/datetimes.py | 17 +++++++----- pandas/core/indexes/period.py | 22 +++++++++------- pandas/tests/indexes/period/test_ops.py | 34 +++++++++++------------- 7 files changed, 80 insertions(+), 39 deletions(-) diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 3a1af9fdb1e8f..8d523f01151e9 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -386,7 +386,7 @@ cdef inline object _parse_dateabbr_string(object date_string, datetime default, assert isinstance(date_string, str) if date_string in nat_strings: - return NaT, '' + return NaT, '' # TODO: reso str here? date_string = date_string.upper() date_len = len(date_string) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index bc190825214c1..d50c4fc835714 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2404,6 +2404,7 @@ class Period(_Period): value = str(value) value = value.upper() dt, reso = parse_time_string(value, freq) + # TODO: reso = Resolution.from_attrname(reso) if dt is NaT: ordinal = NPY_NAT diff --git a/pandas/_libs/tslibs/resolution.pyx b/pandas/_libs/tslibs/resolution.pyx index 91059638cd73a..dfdd2d3f06bca 100644 --- a/pandas/_libs/tslibs/resolution.pyx +++ b/pandas/_libs/tslibs/resolution.pyx @@ -8,6 +8,7 @@ from pandas._libs.tslibs.util cimport get_nat from pandas._libs.tslibs.np_datetime cimport ( npy_datetimestruct, dt64_to_dtstruct) from pandas._libs.tslibs.frequencies cimport attrname_to_abbrevs +from pandas._libs.tslibs.frequencies import FreqGroup from pandas._libs.tslibs.timezones cimport ( is_utc, is_tzlocal, maybe_get_tz, get_dst_info) from pandas._libs.tslibs.ccalendar cimport get_days_in_month @@ -26,6 +27,9 @@ cdef: int RESO_MIN = 4 int RESO_HR = 5 int RESO_DAY = 6 + #int RESO_MTH = 7 + int RESO_QTR = 8 + int RESO_YR = 9 _abbrev_to_attrnames = {v: k for k, v in attrname_to_abbrevs.items()} @@ -37,6 +41,9 @@ _reso_str_map = { RESO_MIN: "minute", RESO_HR: "hour", RESO_DAY: "day", + #RESO_MTH: "month", + RESO_QTR: "quarter", + RESO_YR: "year", } _str_reso_map = {v: k for k, v in _reso_str_map.items()} @@ -138,6 +145,9 @@ class Resolution(Enum): RESO_MIN = 4 RESO_HR = 5 RESO_DAY = 6 + #RESO_MTH = 7 + RESO_QTR = 8 + RESO_YR = 9 def __lt__(self, other): return self.value < other.value @@ -145,6 +155,31 @@ class Resolution(Enum): def __ge__(self, other): return self.value >= other.value + @property + def freq_group(self): + # TODO: annotate as returning FreqGroup once that is an enum + if self == Resolution.RESO_NS: + return FreqGroup.FR_NS + if self == Resolution.RESO_US: + return FreqGroup.FR_US + if self == Resolution.RESO_MS: + return FreqGroup.FR_MS + if self == Resolution.RESO_SEC: + return FreqGroup.FR_SEC + if self == Resolution.RESO_MIN: + return FreqGroup.FR_MIN + if self == Resolution.RESO_HR: + return FreqGroup.FR_HR + if self == Resolution.RESO_DAY: + return FreqGroup.FR_DAY + #if self == Resolution.RESO_MTH: + # return FreqGroup.FR_MTH + if self == Resolution.RESO_QTR: + return FreqGroup.FR_QTR + if self == Resolution.RESO_YR: + return FreqGroup.FR_ANN + raise ValueError(self) + @classmethod def get_str(cls, reso: "Resolution") -> str: """ diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 21f4b3f8bb76a..210825809e58c 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -363,19 +363,19 @@ def _format_attrs(self): # -------------------------------------------------------------------- # Indexing Methods - def _validate_partial_date_slice(self, reso: str): + def _validate_partial_date_slice(self, reso: Resolution): raise NotImplementedError - def _parsed_string_to_bounds(self, reso: str, parsed: datetime): + def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): raise NotImplementedError def _partial_date_slice( - self, reso: str, parsed: datetime, use_lhs: bool = True, use_rhs: bool = True + self, reso: Resolution, parsed: datetime, use_lhs: bool = True, use_rhs: bool = True ): """ Parameters ---------- - reso : str + reso : Resolution parsed : datetime use_lhs : bool, default True use_rhs : bool, default True diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 4677faa6b7d24..2947c5b16bb32 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -7,7 +7,6 @@ from pandas._libs import NaT, Period, Timestamp, index as libindex, lib, tslib from pandas._libs.tslibs import Resolution, fields, parsing, timezones -from pandas._libs.tslibs.frequencies import get_freq_group from pandas._libs.tslibs.offsets import prefix_mapping from pandas._typing import DtypeObj, Label from pandas.util._decorators import cache_readonly @@ -472,7 +471,7 @@ def snap(self, freq="S"): dta = DatetimeArray(snapped, dtype=self.dtype) return DatetimeIndex._simple_new(dta, name=self.name) - def _parsed_string_to_bounds(self, reso: str, parsed: datetime): + def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): """ Calculate datetime bounds for parsed time string and its resolution. @@ -487,6 +486,7 @@ def _parsed_string_to_bounds(self, reso: str, parsed: datetime): ------- lower, upper: pd.Timestamp """ + assert isinstance(reso, Resolution), (type(reso), reso) valid_resos = { "year", "month", @@ -499,10 +499,10 @@ def _parsed_string_to_bounds(self, reso: str, parsed: datetime): "second", "microsecond", } - if reso not in valid_resos: + if reso.get_str(reso) not in valid_resos: raise KeyError - grp = get_freq_group(reso) + grp = reso.freq_group per = Period(parsed, freq=(grp, 1)) start, end = per.start_time, per.end_time @@ -523,11 +523,12 @@ def _parsed_string_to_bounds(self, reso: str, parsed: datetime): end = end.tz_localize(self.tz) return start, end - def _validate_partial_date_slice(self, reso: str): + def _validate_partial_date_slice(self, reso: Resolution): + assert isinstance(reso, Resolution), (type(reso), reso) if ( self.is_monotonic - and reso in ["day", "hour", "minute", "second"] - and self._resolution_obj >= Resolution.from_attrname(reso) + and reso.get_str(reso) in ["day", "hour", "minute", "second"] + and self._resolution_obj >= reso ): # These resolution/monotonicity validations came from GH3931, # GH3452 and GH2369. @@ -627,6 +628,7 @@ def _maybe_cast_slice_bound(self, label, side: str, kind): if isinstance(label, str): freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None)) parsed, reso = parsing.parse_time_string(label, freq) + reso = Resolution.from_attrname(reso) lower, upper = self._parsed_string_to_bounds(reso, parsed) # lower, upper form the half-open interval: # [parsed, parsed + 1 freq) @@ -643,6 +645,7 @@ def _maybe_cast_slice_bound(self, label, side: str, kind): def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True): freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None)) parsed, reso = parsing.parse_time_string(key, freq) + reso = Resolution.from_attrname(reso) loc = self._partial_date_slice(reso, parsed, use_lhs=use_lhs, use_rhs=use_rhs) return loc diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 14922000c9707..dedf21524a739 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -5,7 +5,7 @@ from pandas._libs import index as libindex from pandas._libs.lib import no_default -from pandas._libs.tslibs import Period +from pandas._libs.tslibs import Period, Resolution from pandas._libs.tslibs.frequencies import get_freq_group from pandas._libs.tslibs.parsing import DateParseError, parse_time_string from pandas._typing import DtypeObj, Label @@ -501,7 +501,8 @@ def get_loc(self, key, method=None, tolerance=None): # A string with invalid format raise KeyError(f"Cannot interpret '{key}' as period") from err - grp = get_freq_group(reso) + reso = Resolution.from_attrname(reso) + grp = reso.freq_group freqn = get_freq_group(self.freq) # _get_string_slice will handle cases where grp < freqn @@ -558,6 +559,7 @@ def _maybe_cast_slice_bound(self, label, side: str, kind: str): elif isinstance(label, str): try: parsed, reso = parse_time_string(label, self.freq) + reso = Resolution.from_attrname(reso) bounds = self._parsed_string_to_bounds(reso, parsed) return bounds[0 if side == "left" else 1] except ValueError as err: @@ -569,16 +571,18 @@ def _maybe_cast_slice_bound(self, label, side: str, kind: str): return label - def _parsed_string_to_bounds(self, reso: str, parsed: datetime): - if reso not in ["year", "month", "quarter", "day", "hour", "minute", "second"]: - raise KeyError(reso) + def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): + assert isinstance(reso, Resolution), (type(reso), reso) + #if reso not in ["year", "month", "quarter", "day", "hour", "minute", "second"]: + # raise KeyError(reso) - grp = get_freq_group(reso) + grp = reso.freq_group iv = Period(parsed, freq=(grp, 1)) return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end")) - def _validate_partial_date_slice(self, reso: str): - grp = get_freq_group(reso) + def _validate_partial_date_slice(self, reso: Resolution): + assert isinstance(reso, Resolution), (type(reso), reso) + grp = reso.freq_group freqn = get_freq_group(self.freq) if not grp < freqn: @@ -590,7 +594,7 @@ def _validate_partial_date_slice(self, reso: str): def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True): # TODO: Check for non-True use_lhs/use_rhs parsed, reso = parse_time_string(key, self.freq) - + reso = Resolution.from_attrname(reso) try: return self._partial_date_slice(reso, parsed, use_lhs, use_rhs) except KeyError as err: diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index fc44226f9d72f..22216f5ff0898 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -7,24 +7,22 @@ class TestPeriodIndexOps: - def test_resolution(self): - for freq, expected in zip( - ["A", "Q", "M", "D", "H", "T", "S", "L", "U"], - [ - "day", - "day", - "day", - "day", - "hour", - "minute", - "second", - "millisecond", - "microsecond", - ], - ): - - idx = pd.period_range(start="2013-04-01", periods=30, freq=freq) - assert idx.resolution == expected + @pytest.mark.parametrize("freq,expected", + [ + ("A", "day"), + ("Q", "day"), + ("M", "day"), + ("D", "day"), + ("H", "hour"), + ("T", "minute"), + ("S", "second"), + ("L", "millisecond"), + ("U", "microsecond"), + ] + ) + def test_resolution(self, freq, expected): + idx = pd.period_range(start="2013-04-01", periods=30, freq=freq) + assert idx.resolution == expected def test_value_counts_unique(self): # GH 7735 From ffe0d95bced9cfa5b3cd100a6a3807389243eec3 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Jun 2020 15:27:56 -0700 Subject: [PATCH 2/9] restore --- pandas/_libs/tslibs/resolution.pyx | 10 +++++----- pandas/core/indexes/datetimes.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/_libs/tslibs/resolution.pyx b/pandas/_libs/tslibs/resolution.pyx index 2077152c8dc8b..7ded127b1b191 100644 --- a/pandas/_libs/tslibs/resolution.pyx +++ b/pandas/_libs/tslibs/resolution.pyx @@ -27,7 +27,7 @@ cdef: int RESO_MIN = 4 int RESO_HR = 5 int RESO_DAY = 6 - #int RESO_MTH = 7 + int RESO_MTH = 7 int RESO_QTR = 8 int RESO_YR = 9 @@ -41,7 +41,7 @@ _reso_str_map = { RESO_MIN: "minute", RESO_HR: "hour", RESO_DAY: "day", - #RESO_MTH: "month", + RESO_MTH: "month", RESO_QTR: "quarter", RESO_YR: "year", } @@ -133,7 +133,7 @@ class Resolution(Enum): RESO_MIN = 4 RESO_HR = 5 RESO_DAY = 6 - #RESO_MTH = 7 + RESO_MTH = 7 RESO_QTR = 8 RESO_YR = 9 @@ -160,8 +160,8 @@ class Resolution(Enum): return FreqGroup.FR_HR if self == Resolution.RESO_DAY: return FreqGroup.FR_DAY - #if self == Resolution.RESO_MTH: - # return FreqGroup.FR_MTH + if self == Resolution.RESO_MTH: + return FreqGroup.FR_MTH if self == Resolution.RESO_QTR: return FreqGroup.FR_QTR if self == Resolution.RESO_YR: diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 2947c5b16bb32..056f7d69cedc8 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -499,7 +499,7 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): "second", "microsecond", } - if reso.get_str(reso) not in valid_resos: + if reso.attrname not in valid_resos: raise KeyError grp = reso.freq_group @@ -527,7 +527,7 @@ def _validate_partial_date_slice(self, reso: Resolution): assert isinstance(reso, Resolution), (type(reso), reso) if ( self.is_monotonic - and reso.get_str(reso) in ["day", "hour", "minute", "second"] + and reso.attrname in ["day", "hour", "minute", "second"] and self._resolution_obj >= reso ): # These resolution/monotonicity validations came from GH3931, From cf71c6bda051ad3e60e10ec16589a46f0ef993e4 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Jun 2020 17:50:12 -0700 Subject: [PATCH 3/9] ENH: Resolutions for month/qtr/year --- pandas/_libs/tslibs/resolution.pyx | 16 ++++++++++++++-- pandas/core/arrays/datetimelike.py | 5 ----- pandas/tests/indexes/period/test_ops.py | 6 +++--- .../tests/tseries/frequencies/test_freq_code.py | 10 +++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/pandas/_libs/tslibs/resolution.pyx b/pandas/_libs/tslibs/resolution.pyx index 7ded127b1b191..a33315b7ac224 100644 --- a/pandas/_libs/tslibs/resolution.pyx +++ b/pandas/_libs/tslibs/resolution.pyx @@ -11,7 +11,7 @@ from pandas._libs.tslibs.frequencies cimport attrname_to_abbrevs from pandas._libs.tslibs.frequencies import FreqGroup from pandas._libs.tslibs.timezones cimport ( is_utc, is_tzlocal, maybe_get_tz, get_dst_info) -from pandas._libs.tslibs.ccalendar cimport get_days_in_month +from pandas._libs.tslibs.ccalendar cimport get_days_in_month, c_MONTH_NUMBERS from pandas._libs.tslibs.tzconversion cimport tz_convert_utc_to_tzlocal # ---------------------------------------------------------------------- @@ -210,7 +210,19 @@ class Resolution(Enum): >>> Resolution.get_reso_from_freq('H') == Resolution.RESO_HR True """ - attr_name = _abbrev_to_attrnames[freq] + try: + attr_name = _abbrev_to_attrnames[freq] + except KeyError: + # For quarterly and yearly resolutions, we need to chop off + # a month string. + split_freq = freq.split("-") + if len(split_freq) != 2: + raise + if split_freq[1] not in c_MONTH_NUMBERS: + # i.e. we want e.g. "Q-DEC", not "Q-INVALID" + raise + attr_name = _abbrev_to_attrnames[split_freq[0]] + return cls.from_attrname(attr_name) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index e2ecb6c343b7a..8af23815b54ef 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1122,11 +1122,6 @@ def resolution(self) -> str: """ Returns day, hour, minute, second, millisecond or microsecond """ - if self._resolution_obj is None: - if is_period_dtype(self.dtype): - # somewhere in the past it was decided we default to day - return "day" - # otherwise we fall through and will raise return self._resolution_obj.attrname # type: ignore @classmethod diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index 22216f5ff0898..a9336112b2d99 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -9,9 +9,9 @@ class TestPeriodIndexOps: @pytest.mark.parametrize("freq,expected", [ - ("A", "day"), - ("Q", "day"), - ("M", "day"), + ("A", "year"), + ("Q", "quarter"), + ("M", "month"), ("D", "day"), ("H", "hour"), ("T", "minute"), diff --git a/pandas/tests/tseries/frequencies/test_freq_code.py b/pandas/tests/tseries/frequencies/test_freq_code.py index d4eb31168b20e..2318619a5c734 100644 --- a/pandas/tests/tseries/frequencies/test_freq_code.py +++ b/pandas/tests/tseries/frequencies/test_freq_code.py @@ -93,6 +93,9 @@ def test_get_to_timestamp_base(freqstr, exp_freqstr): @pytest.mark.parametrize( "freqstr,expected", [ + ("A", "year"), + ("Q", "quarter"), + ("M", "month"), ("D", "day"), ("H", "hour"), ("T", "minute"), @@ -106,13 +109,6 @@ def test_get_attrname_from_abbrev(freqstr, expected): assert _reso.get_reso_from_freq(freqstr).attrname == expected -@pytest.mark.parametrize("freq", ["A", "Q", "M"]) -def test_get_freq_unsupported_(freq): - # Lowest-frequency resolution is for Day - with pytest.raises(KeyError, match=freq.lower()): - _reso.get_reso_from_freq(freq) - - @pytest.mark.parametrize("freq", ["D", "H", "T", "S", "L", "U", "N"]) def test_get_freq_roundtrip2(freq): obj = _reso.get_reso_from_freq(freq) From a04be73ff28f13cc58bdd176aa0f8ee7dbd32fe3 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Jun 2020 17:53:05 -0700 Subject: [PATCH 4/9] revert comments --- pandas/_libs/tslibs/parsing.pyx | 2 +- pandas/_libs/tslibs/period.pyx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 8d523f01151e9..3a1af9fdb1e8f 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -386,7 +386,7 @@ cdef inline object _parse_dateabbr_string(object date_string, datetime default, assert isinstance(date_string, str) if date_string in nat_strings: - return NaT, '' # TODO: reso str here? + return NaT, '' date_string = date_string.upper() date_len = len(date_string) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 54f5e09dd7e47..e88a20bc549bd 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -2427,7 +2427,6 @@ class Period(_Period): value = str(value) value = value.upper() dt, reso = parse_time_string(value, freq) - # TODO: reso = Resolution.from_attrname(reso) if dt is NaT: ordinal = NPY_NAT From c7791b99e6eb57f9ba903057f31ee188b228bfb8 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Jun 2020 17:54:21 -0700 Subject: [PATCH 5/9] remove commented-out --- pandas/core/indexes/period.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index dedf21524a739..d1a112dece37c 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -572,10 +572,6 @@ def _maybe_cast_slice_bound(self, label, side: str, kind: str): return label def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): - assert isinstance(reso, Resolution), (type(reso), reso) - #if reso not in ["year", "month", "quarter", "day", "hour", "minute", "second"]: - # raise KeyError(reso) - grp = reso.freq_group iv = Period(parsed, freq=(grp, 1)) return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end")) From b23b11f4924ab685473374ec56127ec41b738951 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Jun 2020 18:00:53 -0700 Subject: [PATCH 6/9] blackify --- pandas/core/indexes/datetimelike.py | 6 +++++- pandas/tests/indexes/period/test_ops.py | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 210825809e58c..ca6eb45e22c69 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -370,7 +370,11 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): raise NotImplementedError def _partial_date_slice( - self, reso: Resolution, parsed: datetime, use_lhs: bool = True, use_rhs: bool = True + self, + reso: Resolution, + parsed: datetime, + use_lhs: bool = True, + use_rhs: bool = True, ): """ Parameters diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index a9336112b2d99..e7dd76584d780 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -7,7 +7,8 @@ class TestPeriodIndexOps: - @pytest.mark.parametrize("freq,expected", + @pytest.mark.parametrize( + "freq,expected", [ ("A", "year"), ("Q", "quarter"), @@ -18,7 +19,7 @@ class TestPeriodIndexOps: ("S", "second"), ("L", "millisecond"), ("U", "microsecond"), - ] + ], ) def test_resolution(self, freq, expected): idx = pd.period_range(start="2013-04-01", periods=30, freq=freq) From 6d93dbe962b88e272588d06468c45b23fe1d7f93 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 8 Jun 2020 15:36:29 -0700 Subject: [PATCH 7/9] REF: avoid get_freq_code --- pandas/_libs/tslibs/period.pyx | 24 +++++++++++++++++------- pandas/core/arrays/period.py | 14 +++++++++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index d2c9ccc8ab4ae..c06f34e37ec49 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -94,6 +94,7 @@ from pandas._libs.tslibs.offsets cimport ( is_tick_object, is_offset_object, ) +from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG from pandas._libs.tslibs.tzconversion cimport tz_convert_utc_to_tzlocal @@ -1649,7 +1650,7 @@ cdef class _Period: freq = self._maybe_convert_freq(freq) how = validate_end_alias(how) base1 = self._dtype.dtype_code - base2, _ = get_freq_code(freq) + base2 = freq_to_dtype_code(freq) # self.n can't be negative or 0 end = how == 'E' @@ -1739,10 +1740,11 @@ cdef class _Period: if freq is None: base = self._dtype.dtype_code freq = get_to_timestamp_base(base) + base = freq else: freq = self._maybe_convert_freq(freq) + base = freq._period_dtype_code - base, _ = get_freq_code(freq) val = self.asfreq(freq, how) dt64 = period_ordinal_to_dt64(val.ordinal, base) @@ -2386,8 +2388,7 @@ class Period(_Period): elif is_period_object(value): other = value - if freq is None or get_freq_code( - freq) == get_freq_code(other.freq): + if freq is None or freq._period_dtype_code == other.freq._period_dtype_code: ordinal = other.ordinal freq = other.freq else: @@ -2414,6 +2415,7 @@ class Period(_Period): except KeyError: raise ValueError(f"Invalid frequency or could not " f"infer: {reso}") + freq = to_offset(freq) elif PyDateTime_Check(value): dt = value @@ -2432,7 +2434,7 @@ class Period(_Period): raise ValueError(msg) if ordinal is None: - base, _ = get_freq_code(freq) + base = freq_to_dtype_code(freq) ordinal = period_ordinal(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, 0, base) @@ -2444,9 +2446,17 @@ cdef bint is_period_object(object obj): return isinstance(obj, _Period) +cpdef int freq_to_dtype_code(BaseOffset freq) except? -1: + try: + return freq._period_dtype_code + except AttributeError as err: + raise ValueError(INVALID_FREQ_ERR_MSG) from err + + cdef int64_t _ordinal_from_fields(int year, int month, quarter, int day, - int hour, int minute, int second, freq): - base, mult = get_freq_code(freq) + int hour, int minute, int second, + BaseOffset freq): + base = freq_to_dtype_code(freq) if quarter is not None: year, month = quarter_to_myear(year, quarter, freq) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index b16a3df003512..0d866aa7eae26 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -442,10 +442,11 @@ def to_timestamp(self, freq=None, how="start"): if freq is None: base = self.freq._period_dtype_code freq = libfrequencies.get_to_timestamp_base(base) + base = freq else: freq = Period._maybe_convert_freq(freq) + base = freq._period_dtype_code - base, _ = libfrequencies.get_freq_code(freq) new_data = self.asfreq(freq, how=how) new_data = libperiod.periodarr_to_dt64arr(new_data.asi8, base) @@ -962,7 +963,8 @@ def _get_ordinal_range(start, end, periods, freq, mult=1): ) if freq is not None: - _, mult = libfrequencies.get_freq_code(freq) + freq = to_offset(freq) + mult = freq.n if start is not None: start = Period(start, freq) @@ -1024,10 +1026,11 @@ def _range_from_fields( if quarter is not None: if freq is None: - freq = "Q" + freq = to_offset("Q") base = libfrequencies.FreqGroup.FR_QTR else: - base, mult = libfrequencies.get_freq_code(freq) + freq = to_offset(freq) + base = libperiod.freq_to_dtype_code(freq) if base != libfrequencies.FreqGroup.FR_QTR: raise AssertionError("base must equal FR_QTR") @@ -1037,7 +1040,8 @@ def _range_from_fields( val = libperiod.period_ordinal(y, m, 1, 1, 1, 1, 0, 0, base) ordinals.append(val) else: - base, mult = libfrequencies.get_freq_code(freq) + freq = to_offset(freq) + base = libperiod.freq_to_dtype_code(freq) arrays = _make_field_arrays(year, month, day, hour, minute, second) for y, mth, d, h, mn, s in zip(*arrays): ordinals.append(libperiod.period_ordinal(y, mth, d, h, mn, s, 0, 0, base)) From b525a181e181126a707ff4bf45654196bd92ab86 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 9 Jun 2020 11:23:11 -0700 Subject: [PATCH 8/9] CLN: remove get_freq_code --- pandas/_libs/tslibs/frequencies.pxd | 2 - pandas/_libs/tslibs/frequencies.pyx | 132 +----------------- pandas/_libs/tslibs/period.pyx | 2 - pandas/core/arrays/datetimes.py | 4 +- pandas/core/indexes/period.py | 4 +- pandas/plotting/_matplotlib/timeseries.py | 3 +- .../indexes/datetimes/test_scalar_compat.py | 3 +- .../tests/indexes/datetimes/test_to_period.py | 2 +- .../indexes/timedeltas/test_scalar_compat.py | 4 +- pandas/tests/scalar/period/test_asfreq.py | 3 +- pandas/tests/scalar/period/test_period.py | 3 +- .../tests/scalar/timestamp/test_unary_ops.py | 2 +- .../tseries/frequencies/test_freq_code.py | 123 +--------------- .../tseries/frequencies/test_inference.py | 2 +- pandas/tests/tseries/offsets/test_fiscal.py | 2 +- pandas/tests/tseries/offsets/test_offsets.py | 13 +- pandas/tests/tslibs/test_libfrequencies.py | 49 ------- pandas/tests/tslibs/test_period_asfreq.py | 18 ++- 18 files changed, 41 insertions(+), 330 deletions(-) diff --git a/pandas/_libs/tslibs/frequencies.pxd b/pandas/_libs/tslibs/frequencies.pxd index 098944c965df0..896eec77ef4fe 100644 --- a/pandas/_libs/tslibs/frequencies.pxd +++ b/pandas/_libs/tslibs/frequencies.pxd @@ -1,5 +1,3 @@ cdef dict attrname_to_abbrevs -cpdef get_freq_code(freqstr) cpdef int get_to_timestamp_base(int base) -cpdef str get_freq_str(base, mult=*) diff --git a/pandas/_libs/tslibs/frequencies.pyx b/pandas/_libs/tslibs/frequencies.pyx index 9ff34ef0b6f89..6e525500ec37a 100644 --- a/pandas/_libs/tslibs/frequencies.pyx +++ b/pandas/_libs/tslibs/frequencies.pyx @@ -1,18 +1,5 @@ -cimport numpy as cnp -cnp.import_array() -from pandas._libs.tslibs.util cimport is_integer_object - -from pandas._libs.tslibs.offsets cimport is_offset_object -from pandas._libs.tslibs.offsets import ( - INVALID_FREQ_ERR_MSG, - _dont_uppercase, - _lite_rule_alias, - base_and_stride, - opattern, -) - -from .dtypes import FreqGroup, _period_code_map, _reverse_period_code_map +from .dtypes import FreqGroup # --------------------------------------------------------------------- # Period codes @@ -36,131 +23,22 @@ cdef dict attrname_to_abbrevs = _attrname_to_abbrevs # ---------------------------------------------------------------------- -def get_freq_group(freq) -> int: +# TODO: this is now identical to the version in libperiod +def get_freq_group(freq: int) -> int: """ Return frequency code group of given frequency str or offset. Examples -------- - >>> get_freq_group('W-MON') + >>> get_freq_group(4001) 4000 - >>> get_freq_group('W-FRI') + >>> get_freq_group(4006) 4000 """ - if is_offset_object(freq): - freq = freq.rule_code - - if isinstance(freq, str): - freq = attrname_to_abbrevs.get(freq, freq) - base, mult = get_freq_code(freq) - freq = base - elif isinstance(freq, int): - pass - else: - raise ValueError('input must be str, offset or int') return (freq // 1000) * 1000 -cpdef get_freq_code(freqstr): - """ - Return freq str or tuple to freq code and stride (mult) - - Parameters - ---------- - freqstr : str or tuple - - Returns - ------- - return : tuple of base frequency code and stride (mult) - - Raises - ------ - TypeError : if passed a tuple witth incorrect types - - Examples - -------- - >>> get_freq_code('3D') - (6000, 3) - - >>> get_freq_code('D') - (6000, 1) - - >>> get_freq_code(('D', 3)) - (6000, 3) - """ - if is_offset_object(freqstr): - freqstr = (freqstr.rule_code, freqstr.n) - - if isinstance(freqstr, tuple): - if is_integer_object(freqstr[0]) and is_integer_object(freqstr[1]): - # e.g., freqstr = (2000, 1) - return freqstr - elif is_integer_object(freqstr[0]): - # Note: passing freqstr[1] below will raise TypeError if that - # is not a str - code = _period_str_to_code(freqstr[1]) - stride = freqstr[0] - return code, stride - else: - # e.g., freqstr = ('T', 5) - code = _period_str_to_code(freqstr[0]) - stride = freqstr[1] - return code, stride - - if is_integer_object(freqstr): - return freqstr, 1 - - base, stride = base_and_stride(freqstr) - code = _period_str_to_code(base) - - return code, stride - - -cpdef _period_str_to_code(str freqstr): - freqstr = _lite_rule_alias.get(freqstr, freqstr) - - if freqstr not in _dont_uppercase: - lower = freqstr.lower() - freqstr = _lite_rule_alias.get(lower, freqstr) - - if freqstr not in _dont_uppercase: - freqstr = freqstr.upper() - try: - return _period_code_map[freqstr] - except KeyError: - raise ValueError(INVALID_FREQ_ERR_MSG.format(freqstr)) - - -cpdef str get_freq_str(base, mult=1): - """ - Return the summary string associated with this offset code, possibly - adjusted by a multiplier. - - Parameters - ---------- - base : int (member of FreqGroup) - - Returns - ------- - freq_str : str - - Examples - -------- - >>> get_freq_str(1000) - 'A-DEC' - - >>> get_freq_str(2000, 2) - '2Q-DEC' - - >>> get_freq_str("foo") - """ - code = _reverse_period_code_map.get(base) - if mult == 1: - return code - return str(mult) + code - - cpdef int get_to_timestamp_base(int base): """ Return frequency code group used for base of to_timestamp against diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index c06f34e37ec49..47ebf139ed496 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -75,8 +75,6 @@ from pandas._libs.tslibs.dtypes cimport ( from pandas._libs.tslibs.frequencies cimport ( attrname_to_abbrevs, - get_freq_code, - get_freq_str, get_to_timestamp_base, ) from pandas._libs.tslibs.parsing cimport get_rule_month diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 8eb1bdadf9156..90513e355e732 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -11,8 +11,8 @@ Timestamp, conversion, fields, - frequencies as libfrequencies, iNaT, + offsets as liboffsets, resolution as libresolution, timezones, to_offset, @@ -1106,7 +1106,7 @@ def to_period(self, freq=None): # https://github.com/pandas-dev/pandas/issues/33358 if res is None: - base, stride = libfrequencies.base_and_stride(freq) + base, stride = liboffsets.base_and_stride(freq) res = f"{stride}{base}" freq = res diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 43dfd94b49215..49cb78340d104 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -503,7 +503,7 @@ def get_loc(self, key, method=None, tolerance=None): reso = Resolution.from_attrname(reso) grp = reso.freq_group - freqn = get_freq_group(self.freq) + freqn = get_freq_group(self.dtype.dtype_code) # _get_string_slice will handle cases where grp < freqn assert grp >= freqn @@ -579,7 +579,7 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): def _validate_partial_date_slice(self, reso: Resolution): assert isinstance(reso, Resolution), (type(reso), reso) grp = reso.freq_group - freqn = get_freq_group(self.freq) + freqn = get_freq_group(self.dtype.dtype_code) if not grp < freqn: # TODO: we used to also check for diff --git a/pandas/plotting/_matplotlib/timeseries.py b/pandas/plotting/_matplotlib/timeseries.py index a9cca32271b9f..99fc730e818c4 100644 --- a/pandas/plotting/_matplotlib/timeseries.py +++ b/pandas/plotting/_matplotlib/timeseries.py @@ -6,7 +6,8 @@ import numpy as np from pandas._libs.tslibs import Period, to_offset -from pandas._libs.tslibs.frequencies import FreqGroup, base_and_stride +from pandas._libs.tslibs.frequencies import FreqGroup +from pandas._libs.tslibs.offsets import base_and_stride from pandas._typing import FrameOrSeriesUnion from pandas.core.dtypes.generic import ( diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 23dedf6f86a09..0d39e034905d2 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -7,6 +7,7 @@ import pytest from pandas._libs.tslibs import OutOfBoundsDatetime, to_offset +from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG import pandas as pd from pandas import DatetimeIndex, Timestamp, date_range @@ -118,7 +119,7 @@ def test_round(self, tz_naive_fixture): tm.assert_index_equal(rng.round(freq="H"), expected_rng) assert elt.round(freq="H") == expected_elt - msg = pd._libs.tslibs.frequencies.INVALID_FREQ_ERR_MSG + msg = INVALID_FREQ_ERR_MSG with pytest.raises(ValueError, match=msg): rng.round(freq="foo") with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/indexes/datetimes/test_to_period.py b/pandas/tests/indexes/datetimes/test_to_period.py index d82fc1ef6743b..51cc6af2eed08 100644 --- a/pandas/tests/indexes/datetimes/test_to_period.py +++ b/pandas/tests/indexes/datetimes/test_to_period.py @@ -6,7 +6,7 @@ import pytz from pandas._libs.tslibs.ccalendar import MONTHS -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG from pandas import ( DatetimeIndex, diff --git a/pandas/tests/indexes/timedeltas/test_scalar_compat.py b/pandas/tests/indexes/timedeltas/test_scalar_compat.py index 1b86cd1df5a7a..16c19b8d00380 100644 --- a/pandas/tests/indexes/timedeltas/test_scalar_compat.py +++ b/pandas/tests/indexes/timedeltas/test_scalar_compat.py @@ -5,6 +5,8 @@ import numpy as np import pytest +from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG + import pandas as pd from pandas import Index, Series, Timedelta, TimedeltaIndex, timedelta_range import pandas._testing as tm @@ -58,7 +60,7 @@ def test_tdi_round(self): tm.assert_index_equal(td.round(freq="H"), expected_rng) assert elt.round(freq="H") == expected_elt - msg = pd._libs.tslibs.frequencies.INVALID_FREQ_ERR_MSG + msg = INVALID_FREQ_ERR_MSG with pytest.raises(ValueError, match=msg): td.round(freq="foo") with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/scalar/period/test_asfreq.py b/pandas/tests/scalar/period/test_asfreq.py index b9f637c178d53..56281521deb90 100644 --- a/pandas/tests/scalar/period/test_asfreq.py +++ b/pandas/tests/scalar/period/test_asfreq.py @@ -1,6 +1,7 @@ import pytest -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG, _period_code_map +from pandas._libs.tslibs.dtypes import _period_code_map +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG from pandas.errors import OutOfBoundsDatetime from pandas import Period, Timestamp, offsets diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 3e769b577582a..702899f163e06 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -6,9 +6,8 @@ from pandas._libs.tslibs import iNaT, period as libperiod from pandas._libs.tslibs.ccalendar import DAYS, MONTHS -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG from pandas._libs.tslibs.parsing import DateParseError -from pandas._libs.tslibs.period import IncompatibleFrequency +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG, IncompatibleFrequency from pandas._libs.tslibs.timezones import dateutil_gettz, maybe_get_tz from pandas.compat.numpy import np_datetime64_compat diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index 388ff4ea039be..8641bbd0a66f2 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -6,7 +6,7 @@ from pytz import utc from pandas._libs.tslibs import NaT, Timestamp, conversion, to_offset -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG import pandas.util._test_decorators as td import pandas._testing as tm diff --git a/pandas/tests/tseries/frequencies/test_freq_code.py b/pandas/tests/tseries/frequencies/test_freq_code.py index f0ff449d902d0..189a0cc2171ad 100644 --- a/pandas/tests/tseries/frequencies/test_freq_code.py +++ b/pandas/tests/tseries/frequencies/test_freq_code.py @@ -1,80 +1,7 @@ import pytest -from pandas._libs.tslibs import Resolution, offsets, to_offset -from pandas._libs.tslibs.frequencies import ( - FreqGroup, - _attrname_to_abbrevs, - _period_code_map, - get_freq_code, - get_freq_group, - get_to_timestamp_base, -) - - -@pytest.fixture(params=list(_period_code_map.items())) -def period_code_item(request): - return request.param - - -@pytest.mark.parametrize( - "freqstr,expected", - [ - ("A", 1000), - ("3A", 1000), - ("-1A", 1000), - ("Y", 1000), - ("3Y", 1000), - ("-1Y", 1000), - ("W", 4000), - ("W-MON", 4001), - ("W-FRI", 4005), - ], -) -def test_freq_code(freqstr, expected): - assert get_freq_code(freqstr)[0] == expected - - -def test_freq_code_match(period_code_item): - freqstr, code = period_code_item - assert get_freq_code(freqstr)[0] == code - - -@pytest.mark.parametrize( - "freqstr,expected", - [ - ("A", 1000), - ("3A", 1000), - ("-1A", 1000), - ("A-JAN", 1000), - ("A-MAY", 1000), - ("Y", 1000), - ("3Y", 1000), - ("-1Y", 1000), - ("Y-JAN", 1000), - ("Y-MAY", 1000), - (offsets.YearEnd(), 1000), - (offsets.YearEnd(month=1), 1000), - (offsets.YearEnd(month=5), 1000), - ("W", 4000), - ("W-MON", 4000), - ("W-FRI", 4000), - (offsets.Week(), 4000), - (offsets.Week(weekday=1), 4000), - (offsets.Week(weekday=5), 4000), - ("T", FreqGroup.FR_MIN), - ], -) -def test_freq_group(freqstr, expected): - assert get_freq_group(freqstr) == expected - - -def test_freq_group_match(period_code_item): - freqstr, code = period_code_item - - str_group = get_freq_group(freqstr) - code_group = get_freq_group(code) - - assert str_group == code_group == code // 1000 * 1000 +from pandas._libs.tslibs import Resolution, to_offset +from pandas._libs.tslibs.frequencies import _attrname_to_abbrevs, get_to_timestamp_base @pytest.mark.parametrize( @@ -82,9 +9,9 @@ def test_freq_group_match(period_code_item): [("D", "D"), ("W", "D"), ("M", "D"), ("S", "S"), ("T", "S"), ("H", "S")], ) def test_get_to_timestamp_base(freqstr, exp_freqstr): - tsb = get_to_timestamp_base - - assert tsb(get_freq_code(freqstr)[0]) == get_freq_code(exp_freqstr)[0] + left_code = to_offset(freqstr)._period_dtype_code + exp_code = to_offset(exp_freqstr)._period_dtype_code + assert get_to_timestamp_base(left_code) == exp_code @pytest.mark.parametrize( @@ -144,43 +71,3 @@ def test_cat(args): with pytest.raises(ValueError, match=msg): to_offset(str(args[0]) + args[1]) - - -@pytest.mark.parametrize( - "freq_input,expected", - [ - # Frequency string. - ("A", (get_freq_code("A")[0], 1)), - ("3D", (get_freq_code("D")[0], 3)), - ("-2M", (get_freq_code("M")[0], -2)), - # Tuple. - (("D", 1), (get_freq_code("D")[0], 1)), - (("A", 3), (get_freq_code("A")[0], 3)), - (("M", -2), (get_freq_code("M")[0], -2)), - ((5, "T"), (FreqGroup.FR_MIN, 5)), - # Numeric Tuple. - ((1000, 1), (1000, 1)), - # Offsets. - (offsets.Day(), (get_freq_code("D")[0], 1)), - (offsets.Day(3), (get_freq_code("D")[0], 3)), - (offsets.Day(-2), (get_freq_code("D")[0], -2)), - (offsets.MonthEnd(), (get_freq_code("M")[0], 1)), - (offsets.MonthEnd(3), (get_freq_code("M")[0], 3)), - (offsets.MonthEnd(-2), (get_freq_code("M")[0], -2)), - (offsets.Week(), (get_freq_code("W")[0], 1)), - (offsets.Week(3), (get_freq_code("W")[0], 3)), - (offsets.Week(-2), (get_freq_code("W")[0], -2)), - (offsets.Hour(), (FreqGroup.FR_HR, 1)), - # Monday is weekday=0. - (offsets.Week(weekday=1), (get_freq_code("W-TUE")[0], 1)), - (offsets.Week(3, weekday=0), (get_freq_code("W-MON")[0], 3)), - (offsets.Week(-2, weekday=4), (get_freq_code("W-FRI")[0], -2)), - ], -) -def test_get_freq_code(freq_input, expected): - assert get_freq_code(freq_input) == expected - - -def test_get_code_invalid(): - with pytest.raises(ValueError, match="Invalid frequency"): - get_freq_code((5, "baz")) diff --git a/pandas/tests/tseries/frequencies/test_inference.py b/pandas/tests/tseries/frequencies/test_inference.py index c32ad5087ab9e..95edd038dab9b 100644 --- a/pandas/tests/tseries/frequencies/test_inference.py +++ b/pandas/tests/tseries/frequencies/test_inference.py @@ -4,7 +4,7 @@ import pytest from pandas._libs.tslibs.ccalendar import DAYS, MONTHS -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG from pandas.compat import is_platform_windows from pandas import DatetimeIndex, Index, Series, Timestamp, date_range, period_range diff --git a/pandas/tests/tseries/offsets/test_fiscal.py b/pandas/tests/tseries/offsets/test_fiscal.py index f0ce104a68e29..7713be67a7e05 100644 --- a/pandas/tests/tseries/offsets/test_fiscal.py +++ b/pandas/tests/tseries/offsets/test_fiscal.py @@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta import pytest -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG from pandas import Timestamp import pandas._testing as tm diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index e3a89d9ed57a6..784c04f225630 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -11,13 +11,9 @@ conversion, timezones, ) -from pandas._libs.tslibs.frequencies import ( - INVALID_FREQ_ERR_MSG, - get_freq_code, - get_freq_str, -) import pandas._libs.tslibs.offsets as liboffsets from pandas._libs.tslibs.offsets import ApplyTypeError, _get_offset, _offset_map +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG import pandas.compat as compat from pandas.compat.numpy import np_datetime64_compat from pandas.errors import PerformanceWarning @@ -4112,13 +4108,6 @@ def test_rule_code(self): assert alias == _get_offset(alias).rule_code assert alias == (_get_offset(alias) * 5).rule_code - lst = ["M", "D", "B", "H", "T", "S", "L", "U"] - for k in lst: - code, stride = get_freq_code("3" + k) - assert isinstance(code, int) - assert stride == 3 - assert k == get_freq_str(code) - def test_dateoffset_misc(): oset = offsets.DateOffset(months=2, days=4) diff --git a/pandas/tests/tslibs/test_libfrequencies.py b/pandas/tests/tslibs/test_libfrequencies.py index feaaaf6adca6f..993f2f4c8ef10 100644 --- a/pandas/tests/tslibs/test_libfrequencies.py +++ b/pandas/tests/tslibs/test_libfrequencies.py @@ -1,6 +1,5 @@ import pytest -from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG, _period_str_to_code from pandas._libs.tslibs.parsing import get_rule_month from pandas.tseries import offsets @@ -28,51 +27,3 @@ def test_get_rule_month(obj, expected): result = get_rule_month(obj) assert result == expected - - -@pytest.mark.parametrize( - "obj,expected", - [ - ("A", 1000), - ("A-DEC", 1000), - ("A-JAN", 1001), - ("Y", 1000), - ("Y-DEC", 1000), - ("Y-JAN", 1001), - ("Q", 2000), - ("Q-DEC", 2000), - ("Q-FEB", 2002), - ("W", 4000), - ("W-SUN", 4000), - ("W-FRI", 4005), - ("Min", 8000), - ("ms", 10000), - ("US", 11000), - ("NS", 12000), - ], -) -def test_period_str_to_code(obj, expected): - assert _period_str_to_code(obj) == expected - - -@pytest.mark.parametrize( - "freq,expected,aliases", - [ - ("D", 6000, ["DAY", "DLY", "DAILY"]), - ("M", 3000, ["MTH", "MONTH", "MONTHLY"]), - ("N", 12000, ["NANOSECOND", "NANOSECONDLY"]), - ("H", 7000, ["HR", "HOUR", "HRLY", "HOURLY"]), - ("T", 8000, ["minute", "MINUTE", "MINUTELY"]), - ("L", 10000, ["MILLISECOND", "MILLISECONDLY"]), - ("U", 11000, ["MICROSECOND", "MICROSECONDLY"]), - ("S", 9000, ["sec", "SEC", "SECOND", "SECONDLY"]), - ("B", 5000, ["BUS", "BUSINESS", "BUSINESSLY", "WEEKDAY"]), - ], -) -def test_assert_aliases_deprecated(freq, expected, aliases): - assert isinstance(aliases, list) - assert _period_str_to_code(freq) == expected - - for alias in aliases: - with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG): - _period_str_to_code(alias) diff --git a/pandas/tests/tslibs/test_period_asfreq.py b/pandas/tests/tslibs/test_period_asfreq.py index 7205c3cc676cf..63298b657e341 100644 --- a/pandas/tests/tslibs/test_period_asfreq.py +++ b/pandas/tests/tslibs/test_period_asfreq.py @@ -1,9 +1,15 @@ import pytest -from pandas._libs.tslibs.frequencies import get_freq_code +from pandas._libs.tslibs import to_offset from pandas._libs.tslibs.period import period_asfreq, period_ordinal +def get_freq_code(freqstr: str) -> int: + off = to_offset(freqstr) + code = off._period_dtype_code + return code + + @pytest.mark.parametrize( "freq1,freq2,expected", [ @@ -32,8 +38,7 @@ ) def test_intra_day_conversion_factors(freq1, freq2, expected): assert ( - period_asfreq(1, get_freq_code(freq1)[0], get_freq_code(freq2)[0], False) - == expected + period_asfreq(1, get_freq_code(freq1), get_freq_code(freq2), False) == expected ) @@ -42,7 +47,7 @@ def test_intra_day_conversion_factors(freq1, freq2, expected): ) def test_period_ordinal_start_values(freq, expected): # information for Jan. 1, 1970. - assert period_ordinal(1970, 1, 1, 0, 0, 0, 0, 0, get_freq_code(freq)[0]) == expected + assert period_ordinal(1970, 1, 1, 0, 0, 0, 0, 0, get_freq_code(freq)) == expected @pytest.mark.parametrize( @@ -55,7 +60,7 @@ def test_period_ordinal_start_values(freq, expected): ], ) def test_period_ordinal_week(dt, expected): - args = dt + (get_freq_code("W")[0],) + args = dt + (get_freq_code("W"),) assert period_ordinal(*args) == expected @@ -77,5 +82,6 @@ def test_period_ordinal_week(dt, expected): ], ) def test_period_ordinal_business_day(day, expected): - args = (2013, 10, day, 0, 0, 0, 0, 0, get_freq_code("B")[0]) + # 5000 is PeriodDtypeCode for BusinessDay + args = (2013, 10, day, 0, 0, 0, 0, 0, 5000) assert period_ordinal(*args) == expected From fee5c3971eefa089b3ede4784492666a3867e397 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 9 Jun 2020 11:54:23 -0700 Subject: [PATCH 9/9] mypy fixup --- pandas/_libs/tslibs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index 6dbb4ce7bc974..6f173a4542bb0 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -18,7 +18,7 @@ "to_offset", ] -from . import dtypes # type: ignore +from . import dtypes from .conversion import localize_pydatetime from .nattype import NaT, NaTType, iNaT, is_null_datetimelike, nat_strings from .np_datetime import OutOfBoundsDatetime