diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 7e6e382c17cc6..4100f3d90e817 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -359,6 +359,8 @@ cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR = { "b": "B", "c": "C", "MIN": "min", + "US": "us", + "NS": "ns", } cdef str INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}" diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index db35cc0c93237..ff24c2942cb76 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4697,13 +4697,9 @@ _lite_rule_alias = { "BYS": "BYS-JAN", # BYearBegin(month=1), "Min": "min", - "min": "min", - "ms": "ms", - "us": "us", - "ns": "ns", } -_dont_uppercase = {"h", "bh", "cbh", "MS", "ms", "s"} +_dont_uppercase = {"min", "h", "bh", "cbh", "s", "ms", "us", "ns"} INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}" @@ -4713,6 +4709,37 @@ INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}" _offset_map = {} +def _warn_about_deprecated_aliases(name: str, is_period: bool) -> str: + if name in _lite_rule_alias: + return name + if name in c_PERIOD_AND_OFFSET_DEPR_FREQSTR: + warnings.warn( + f"\'{name}\' is deprecated and will be removed " + f"in a future version, please use " + f"\'{c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)}\' " + f" instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + return c_PERIOD_AND_OFFSET_DEPR_FREQSTR[name] + + for _name in (name.lower(), name.upper()): + if name == _name: + continue + if _name in c_PERIOD_AND_OFFSET_DEPR_FREQSTR.values(): + warnings.warn( + f"\'{name}\' is deprecated and will be removed " + f"in a future version, please use " + f"\'{_name}\' " + f" instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + return _name + + return name + + def _validate_to_offset_alias(alias: str, is_period: bool) -> None: if not is_period: if alias.upper() in c_OFFSET_RENAMED_FREQSTR: @@ -4750,35 +4777,6 @@ def _get_offset(name: str) -> BaseOffset: -------- _get_offset('EOM') --> BMonthEnd(1) """ - if ( - name not in _lite_rule_alias - and (name.upper() in _lite_rule_alias) - and name != "ms" - ): - warnings.warn( - f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use \'{name.upper()}\' instead.", - FutureWarning, - stacklevel=find_stack_level(), - ) - elif ( - name not in _lite_rule_alias - and (name.lower() in _lite_rule_alias) - and name != "MS" - ): - warnings.warn( - f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use \'{name.lower()}\' instead.", - FutureWarning, - stacklevel=find_stack_level(), - ) - if name not in _dont_uppercase: - name = name.upper() - name = _lite_rule_alias.get(name, name) - name = _lite_rule_alias.get(name.lower(), name) - else: - name = _lite_rule_alias.get(name, name) - if name not in _offset_map: try: split = name.split("-") @@ -4880,6 +4878,7 @@ cpdef to_offset(freq, bint is_period=False): tups = zip(split[0::4], split[1::4], split[2::4]) for n, (sep, stride, name) in enumerate(tups): + name = _warn_about_deprecated_aliases(name, is_period) _validate_to_offset_alias(name, is_period) if is_period: if name.upper() in c_PERIOD_TO_OFFSET_FREQSTR: @@ -4888,31 +4887,21 @@ cpdef to_offset(freq, bint is_period=False): f"\'{name}\' is no longer supported, " f"please use \'{name.upper()}\' instead.", ) - name = c_PERIOD_TO_OFFSET_FREQSTR.get(name.upper()) - - if name in c_PERIOD_AND_OFFSET_DEPR_FREQSTR: - warnings.warn( - f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use " - f"\'{c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)}\' " - f"instead.", - FutureWarning, - stacklevel=find_stack_level(), - ) - name = c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name) + name = c_PERIOD_TO_OFFSET_FREQSTR[name.upper()] + name = _lite_rule_alias.get(name, name) + if sep != "" and not sep.isspace(): raise ValueError("separator must be spaces") - prefix = _lite_rule_alias.get(name) or name if stride_sign is None: stride_sign = -1 if stride.startswith("-") else 1 if not stride: stride = 1 - if prefix in {"D", "h", "min", "s", "ms", "us", "ns"}: + if name in {"D", "h", "min", "s", "ms", "us", "ns"}: # For these prefixes, we have something like "3h" or # "2.5min", so we can construct a Timedelta with the # matching unit and get our offset from delta_to_tick - td = Timedelta(1, unit=prefix) + td = Timedelta(1, unit=name) off = delta_to_tick(td) offset = off * float(stride) if n != 0: @@ -4921,7 +4910,7 @@ cpdef to_offset(freq, bint is_period=False): offset *= stride_sign else: stride = int(stride) - offset = _get_offset(prefix) + offset = _get_offset(name) offset = offset * int(np.fabs(stride) * stride_sign) if result is None: @@ -4931,7 +4920,7 @@ cpdef to_offset(freq, bint is_period=False): except (ValueError, TypeError) as err: raise ValueError(INVALID_FREQ_ERR_MSG.format( f"{freq}, failed to parse with error message: {repr(err)}") - ) + ) from err else: result = None diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index de189b7e2f724..8e348805de978 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -760,7 +760,7 @@ def test_date_range_frequency_M_Q_Y_raises(self, freq): with pytest.raises(ValueError, match=msg): pd.date_range("1/1/2000", periods=4, freq=freq) - @pytest.mark.parametrize("freq_depr", ["2MIN", "2mS", "2Us"]) + @pytest.mark.parametrize("freq_depr", ["2MIN", "2nS", "2Us"]) def test_date_range_uppercase_frequency_deprecated(self, freq_depr): # GH#9586, GH#54939 depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a " diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 1e5bfa6033216..d19717e87c7d2 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -788,9 +788,7 @@ def test_get_offset(): pairs = [ ("B", BDay()), - ("b", BDay()), - ("bme", BMonthEnd()), - ("Bme", BMonthEnd()), + ("BME", BMonthEnd()), ("W-MON", Week(weekday=0)), ("W-TUE", Week(weekday=1)), ("W-WED", Week(weekday=2)),