diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 897d5bec053..b810be83457 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -26,10 +26,19 @@ New Features Breaking changes ~~~~~~~~~~~~~~~~ +- Following pandas, :py:meth:`infer_freq` will return ``"YE"``, instead of ``"Y"`` (formerly ``"A"``). + This is to be consistent with the deprecation of the latter frequency string in pandas 2.2. + This is a follow up to :pull:`8415` (:issue:`8612`, :pull:`8629`). + By `Mathias Hauser `_. Deprecations ~~~~~~~~~~~~ +- Following pandas, the frequency string ``"Y"`` (formerly ``"A"``) is deprecated in + favor of ``"YE"``. These strings are used, for example, in :py:func:`date_range`, + :py:func:`cftime_range`, :py:meth:`DataArray.resample`, and :py:meth:`Dataset.resample` + among others (:issue:`8612`, :pull:`8629`). + By `Mathias Hauser `_. Bug fixes ~~~~~~~~~ diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index 100f3b249d2..0f4c46bb00e 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -573,7 +573,7 @@ def rollback(self, date): class YearEnd(YearOffset): - _freq = "Y" + _freq = "YE" _day_option = "end" _default_month = 12 @@ -669,6 +669,7 @@ def _generate_anchored_offsets(base_freq, offset): "A": YearEnd, "AS": YearBegin, "Y": YearEnd, + "YE": YearEnd, "YS": YearBegin, "Q": partial(QuarterEnd, month=12), "QE": partial(QuarterEnd, month=12), @@ -691,6 +692,7 @@ def _generate_anchored_offsets(base_freq, offset): **_generate_anchored_offsets("A", YearEnd), **_generate_anchored_offsets("YS", YearBegin), **_generate_anchored_offsets("Y", YearEnd), + **_generate_anchored_offsets("YE", YearEnd), **_generate_anchored_offsets("QS", QuarterBegin), **_generate_anchored_offsets("Q", QuarterEnd), **_generate_anchored_offsets("QE", QuarterEnd), @@ -716,7 +718,8 @@ def _generate_anchored_deprecated_frequencies(deprecated, recommended): _DEPRECATED_FREQUENICES = { - "A": "Y", + "A": "YE", + "Y": "YE", "AS": "YS", "Q": "QE", "M": "ME", @@ -725,7 +728,8 @@ def _generate_anchored_deprecated_frequencies(deprecated, recommended): "S": "s", "L": "ms", "U": "us", - **_generate_anchored_deprecated_frequencies("A", "Y"), + **_generate_anchored_deprecated_frequencies("A", "YE"), + **_generate_anchored_deprecated_frequencies("Y", "YE"), **_generate_anchored_deprecated_frequencies("AS", "YS"), **_generate_anchored_deprecated_frequencies("Q", "QE"), } @@ -979,7 +983,7 @@ def cftime_range( +--------+--------------------------+ | Alias | Description | +========+==========================+ - | Y | Year-end frequency | + | YE | Year-end frequency | +--------+--------------------------+ | YS | Year-start frequency | +--------+--------------------------+ @@ -1009,29 +1013,29 @@ def cftime_range( +------------+--------------------------------------------------------------------+ | Alias | Description | +============+====================================================================+ - | Y(S)-JAN | Annual frequency, anchored at the end (or beginning) of January | + | Y(E,S)-JAN | Annual frequency, anchored at the (end, beginning) of January | +------------+--------------------------------------------------------------------+ - | Y(S)-FEB | Annual frequency, anchored at the end (or beginning) of February | + | Y(E,S)-FEB | Annual frequency, anchored at the (end, beginning) of February | +------------+--------------------------------------------------------------------+ - | Y(S)-MAR | Annual frequency, anchored at the end (or beginning) of March | + | Y(E,S)-MAR | Annual frequency, anchored at the (end, beginning) of March | +------------+--------------------------------------------------------------------+ - | Y(S)-APR | Annual frequency, anchored at the end (or beginning) of April | + | Y(E,S)-APR | Annual frequency, anchored at the (end, beginning) of April | +------------+--------------------------------------------------------------------+ - | Y(S)-MAY | Annual frequency, anchored at the end (or beginning) of May | + | Y(E,S)-MAY | Annual frequency, anchored at the (end, beginning) of May | +------------+--------------------------------------------------------------------+ - | Y(S)-JUN | Annual frequency, anchored at the end (or beginning) of June | + | Y(E,S)-JUN | Annual frequency, anchored at the (end, beginning) of June | +------------+--------------------------------------------------------------------+ - | Y(S)-JUL | Annual frequency, anchored at the end (or beginning) of July | + | Y(E,S)-JUL | Annual frequency, anchored at the (end, beginning) of July | +------------+--------------------------------------------------------------------+ - | Y(S)-AUG | Annual frequency, anchored at the end (or beginning) of August | + | Y(E,S)-AUG | Annual frequency, anchored at the (end, beginning) of August | +------------+--------------------------------------------------------------------+ - | Y(S)-SEP | Annual frequency, anchored at the end (or beginning) of September | + | Y(E,S)-SEP | Annual frequency, anchored at the (end, beginning) of September | +------------+--------------------------------------------------------------------+ - | Y(S)-OCT | Annual frequency, anchored at the end (or beginning) of October | + | Y(E,S)-OCT | Annual frequency, anchored at the (end, beginning) of October | +------------+--------------------------------------------------------------------+ - | Y(S)-NOV | Annual frequency, anchored at the end (or beginning) of November | + | Y(E,S)-NOV | Annual frequency, anchored at the (end, beginning) of November | +------------+--------------------------------------------------------------------+ - | Y(S)-DEC | Annual frequency, anchored at the end (or beginning) of December | + | Y(E,S)-DEC | Annual frequency, anchored at the (end, beginning) of December | +------------+--------------------------------------------------------------------+ | Q(E,S)-JAN | Quarter frequency, anchored at the (end, beginning) of January | +------------+--------------------------------------------------------------------+ @@ -1311,11 +1315,8 @@ def date_range_like(source, calendar, use_cftime=None): freq = freq.replace("QE", "Q") elif isinstance(freq_as_offset, YearBegin) and "YS" in freq: freq = freq.replace("YS", "AS") - elif isinstance(freq_as_offset, YearEnd) and "Y-" in freq: - # Check for and replace "Y-" instead of just "Y" to prevent - # corrupting anchored offsets that contain "Y" in the month - # abbreviation, e.g. "Y-MAY" -> "A-MAY". - freq = freq.replace("Y-", "A-") + elif isinstance(freq_as_offset, YearEnd) and "YE" in freq: + freq = freq.replace("YE", "A") use_cftime = _should_cftime_be_used(source, calendar, use_cftime) diff --git a/xarray/tests/test_cftime_offsets.py b/xarray/tests/test_cftime_offsets.py index 68231ecd143..1dd8ddb4151 100644 --- a/xarray/tests/test_cftime_offsets.py +++ b/xarray/tests/test_cftime_offsets.py @@ -157,7 +157,7 @@ def test_year_offset_constructor_invalid_month(offset, invalid_month, exception) (MonthBegin(), "MS"), (MonthEnd(), "ME"), (YearBegin(), "YS-JAN"), - (YearEnd(), "Y-DEC"), + (YearEnd(), "YE-DEC"), (QuarterBegin(), "QS-MAR"), (QuarterEnd(), "QE-MAR"), (Day(), "D"), @@ -1387,7 +1387,7 @@ def test_date_range_errors() -> None: ("2020-02-01", "ME", "noleap", "gregorian", True, "2020-02-29", True), ("2020-02-01", "QE-DEC", "noleap", "gregorian", True, "2020-03-31", True), ("2020-02-01", "YS-FEB", "noleap", "gregorian", True, "2020-02-01", True), - ("2020-02-01", "Y-FEB", "noleap", "gregorian", True, "2020-02-29", True), + ("2020-02-01", "YE-FEB", "noleap", "gregorian", True, "2020-02-29", True), ("2020-02-28", "3h", "all_leap", "gregorian", False, "2020-02-28", True), ("2020-03-30", "ME", "360_day", "gregorian", False, "2020-03-31", True), ("2020-03-31", "ME", "gregorian", "360_day", None, "2020-03-30", False), @@ -1409,8 +1409,8 @@ def test_date_range_like(start, freq, cal_src, cal_tgt, use_cftime, exp0, exp_pd elif "YS" in freq: freq = freq.replace("YS", "AS") expected_pandas_freq = freq - elif "Y-" in freq: - freq = freq.replace("Y-", "A-") + elif "YE-" in freq: + freq = freq.replace("YE-", "A-") expected_pandas_freq = freq elif "h" in freq: expected_pandas_freq = freq.replace("h", "H") @@ -1534,7 +1534,9 @@ def test_cftime_or_date_range_inclusive_None(function) -> None: np.testing.assert_equal(result_None.values, result_both.values) -@pytest.mark.parametrize("freq", ["A", "AS", "Q", "M", "H", "T", "S", "L", "U"]) +@pytest.mark.parametrize( + "freq", ["A", "AS", "Q", "M", "H", "T", "S", "L", "U", "Y", "A-MAY"] +) def test_to_offset_deprecation_warning(freq): # Test for deprecations outlined in GitHub issue #8394 with pytest.warns(FutureWarning, match="is deprecated"):