diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 380915b3494a3..b96f0e5f2c2d7 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -959,6 +959,7 @@ Categorical ^^^^^^^^^^^ - Bug in :class:`Categorical` where constructing from a pandas :class:`Series` or :class:`Index` with ``dtype='object'`` did not preserve the categories' dtype as ``object``; now the ``categories.dtype`` is preserved as ``object`` for these cases, while numpy arrays and Python sequences with ``dtype='object'`` continue to infer the most specific dtype (for example, ``str`` if all elements are strings) (:issue:`61778`) - Bug in :func:`Series.apply` where ``nan`` was ignored for :class:`CategoricalDtype` (:issue:`59938`) +- Bug in :func:`bdate_range` raising ``ValueError`` with frequency ``freq="cbh"`` (:issue:`62849`) - Bug in :func:`testing.assert_index_equal` raising ``TypeError`` instead of ``AssertionError`` for incomparable ``CategoricalIndex`` when ``check_categorical=True`` and ``exact=False`` (:issue:`61935`) - Bug in :meth:`Categorical.astype` where ``copy=False`` would still trigger a copy of the codes (:issue:`62000`) - Bug in :meth:`DataFrame.pivot` and :meth:`DataFrame.set_index` raising an ``ArrowNotImplementedError`` for columns with pyarrow dictionary dtype (:issue:`53051`) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 6451e55f7fc4d..36bd5df8cf20b 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -1133,12 +1133,14 @@ def bdate_range( msg = "freq must be specified for bdate_range; use date_range instead" raise TypeError(msg) - if isinstance(freq, str) and freq.startswith("C"): + if isinstance(freq, str) and freq.upper().startswith("C"): + msg = f"invalid custom frequency string: {freq}" + if freq == "CBH": + raise ValueError(f"{msg}, did you mean cbh?") try: weekmask = weekmask or "Mon Tue Wed Thu Fri" freq = prefix_mapping[freq](holidays=holidays, weekmask=weekmask) except (KeyError, TypeError) as err: - msg = f"invalid custom frequency string: {freq}" raise ValueError(msg) from err elif holidays or weekmask: msg = ( diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index 85e2f6a8070e0..e7b70303be451 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -1216,7 +1216,7 @@ def test_cdaterange_holidays_weekmask_requires_freqstr(self): ) @pytest.mark.parametrize( - "freq", [freq for freq in prefix_mapping if freq.startswith("C")] + "freq", [freq for freq in prefix_mapping if freq.upper().startswith("C")] ) def test_all_custom_freq(self, freq): # should not raise @@ -1280,6 +1280,39 @@ def test_data_range_custombusinessday_partial_time(self, unit): ) tm.assert_index_equal(result, expected) + def test_cdaterange_cbh(self): + # GH#62849 + result = bdate_range( + "2009-03-13", + "2009-03-15", + freq="cbh", + weekmask="Mon Wed Fri", + holidays=["2009-03-14"], + ) + expected = DatetimeIndex( + [ + "2009-03-13 09:00:00", + "2009-03-13 10:00:00", + "2009-03-13 11:00:00", + "2009-03-13 12:00:00", + "2009-03-13 13:00:00", + "2009-03-13 14:00:00", + "2009-03-13 15:00:00", + "2009-03-13 16:00:00", + ], + dtype="datetime64[ns]", + freq="cbh", + ) + tm.assert_index_equal(result, expected) + + def test_cdaterange_deprecated_error_CBH(self): + # GH#62849 + msg = "invalid custom frequency string: CBH, did you mean cbh?" + with pytest.raises(ValueError, match=msg): + bdate_range( + START, END, freq="CBH", weekmask="Mon Wed Fri", holidays=["2009-03-14"] + ) + class TestDateRangeNonNano: def test_date_range_reso_validation(self):