Skip to content
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ I/O
Period
^^^^^^
- Bug in :class:`Period` addition silently wrapping around instead of raising ``OverflowError`` (:issue:`55503`)
- Bug in incorrectly allowing construction of :class:`Period` with certain offsets which don't have an attribute _period_dtype_code (:issue:`55785`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're giving a better error message here, rather than fixing a bug - could we rephrase to reflect that, and put this in "other enhancements"?

something like

- Improved error message when constructing :class:`Period` with invalid offsets such as "QS" (:issue:`55785`) 
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, I did as you suggested.

-

Plotting
Expand Down
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4177,7 +4177,7 @@ cdef class CustomBusinessDay(BusinessDay):
def _period_dtype_code(self):
# GH#52534
raise TypeError(
"CustomBusinessDay cannot be used with Period or PeriodDtype"
"CustomBusinessDay is not supported as period frequency"
)

_apply_array = BaseOffset._apply_array
Expand Down
10 changes: 9 additions & 1 deletion pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,14 @@ class Period(_Period):

if freq is not None:
freq = cls._maybe_convert_freq(freq)
try:
period_dtype_code = freq._period_dtype_code
except (AttributeError, TypeError):
# AttributeError: _period_dtype_code might not exist
# TypeError: _period_dtype_code might intentionally raise
raise TypeError(
f"{(type(freq).__name__)} is not supported as period frequency"
)
nanosecond = 0

if ordinal is not None and value is not None:
Expand Down Expand Up @@ -2758,7 +2766,7 @@ class Period(_Period):

elif is_period_object(value):
other = value
if freq is None or freq._period_dtype_code == other._dtype._dtype_code:
if freq is None or period_dtype_code == other._dtype._dtype_code:
ordinal = other.ordinal
freq = other.freq
else:
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/dtypes/test_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ def test_construction(self):

def test_cannot_use_custom_businessday(self):
# GH#52534
msg = "CustomBusinessDay cannot be used with Period or PeriodDtype"
msg = "CustomBusinessDay is not supported as period frequency"
msg2 = r"PeriodDtype\[B\] is deprecated"
with pytest.raises(TypeError, match=msg):
with tm.assert_produces_warning(FutureWarning, match=msg2):
Expand Down
15 changes: 15 additions & 0 deletions pandas/tests/indexes/period/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,21 @@ def test_map_with_string_constructor(self):
# lastly, values should compare equal
tm.assert_index_equal(res, expected)

@pytest.mark.parametrize(
"freq, freq_msg",
[
(offsets.BYearBegin(), "BYearBegin"),
(offsets.YearBegin(2), "YearBegin"),
(offsets.QuarterBegin(startingMonth=12), "QuarterBegin"),
(offsets.BusinessMonthEnd(2), "BusinessMonthEnd"),
],
)
def test_offsets_not_supported(self, freq, freq_msg):
# GH#55785
msg = f"{freq_msg} is not supported as period frequency"
with pytest.raises(TypeError, match=msg):
Period(year=2014, freq=freq)


class TestShallowCopy:
def test_shallow_copy_empty(self):
Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/scalar/period/test_asfreq.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,8 @@ def test_asfreq_MS(self):
with pytest.raises(ValueError, match=msg):
initial.asfreq(freq="MS", how="S")

with pytest.raises(ValueError, match=msg):
msg = "MonthBegin is not supported as period frequency"
with pytest.raises(TypeError, match=msg):
Period("2013-01", "MS")

assert _period_code_map.get("MS") is None
6 changes: 3 additions & 3 deletions pandas/tests/scalar/period/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
class TestPeriodConstruction:
def test_custom_business_day_freq_raises(self):
# GH#52534
msg = "CustomBusinessDay cannot be used with Period or PeriodDtype"
msg = "CustomBusinessDay is not supported as period frequency"
with pytest.raises(TypeError, match=msg):
Period("2023-04-10", freq="C")
with pytest.raises(TypeError, match=msg):
Expand Down Expand Up @@ -1628,8 +1628,8 @@ def test_negone_ordinals():


def test_invalid_frequency_error_message():
msg = "Invalid frequency: <WeekOfMonth: week=0, weekday=0>"
with pytest.raises(ValueError, match=msg):
msg = "WeekOfMonth is not supported as period frequency"
with pytest.raises(TypeError, match=msg):
Period("2012-01-02", freq="WOM-1MON")


Expand Down