From b709eff34c58bad3e0d42f6ca444ead7478764f3 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Sun, 26 Oct 2025 23:31:28 +0100 Subject: [PATCH 1/3] fix bdate_range with cbh --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/indexes/datetimes.py | 2 +- pandas/tests/indexes/datetimes/test_date_range.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 75b4c5c0fe14d..f764983cf8902 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -957,6 +957,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..4528c203a86e0 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -1133,7 +1133,7 @@ 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"): try: weekmask = weekmask or "Mon Tue Wed Thu Fri" freq = prefix_mapping[freq](holidays=holidays, weekmask=weekmask) diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index 85e2f6a8070e0..9fcd87b6498a3 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 From cc68f8ae2ea690a87b722d670dc4fb20e1c85943 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Mon, 27 Oct 2025 01:15:42 +0100 Subject: [PATCH 2/3] add a test --- .../indexes/datetimes/test_date_range.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index 9fcd87b6498a3..f6e61b2193177 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -1280,6 +1280,31 @@ 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) + class TestDateRangeNonNano: def test_date_range_reso_validation(self): From a4d86bd7689e88265f777de3089cca1b734bd6bc Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Tue, 28 Oct 2025 19:40:44 +0100 Subject: [PATCH 3/3] add the specific exception message for CBH --- pandas/core/indexes/datetimes.py | 4 +++- pandas/tests/indexes/datetimes/test_date_range.py | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 4528c203a86e0..36bd5df8cf20b 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -1134,11 +1134,13 @@ def bdate_range( raise TypeError(msg) 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 f6e61b2193177..e7b70303be451 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -1305,6 +1305,14 @@ def test_cdaterange_cbh(self): ) 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):