diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index fc5bd626a712f..63f8352e4922e 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1299,7 +1299,7 @@ cdef class QuarterOffset(SingleConstructorOffset): return type(dtindex)._simple_new(shifted, dtype=dtindex.dtype) -cdef class MonthOffset(BaseOffset): +cdef class MonthOffset(SingleConstructorOffset): def is_on_offset(self, dt) -> bool: if self.normalize and not is_normalized(dt): return False @@ -1324,6 +1324,56 @@ cdef class MonthOffset(BaseOffset): return cls() +# --------------------------------------------------------------------- +# Special Offset Classes + +cdef class FY5253Mixin(SingleConstructorOffset): + cdef readonly: + int startingMonth + int weekday + str variation + + def __init__( + self, n=1, normalize=False, weekday=0, startingMonth=1, variation="nearest" + ): + BaseOffset.__init__(self, n, normalize) + self.startingMonth = startingMonth + self.weekday = weekday + self.variation = variation + + if self.n == 0: + raise ValueError("N cannot be 0") + + if self.variation not in ["nearest", "last"]: + raise ValueError(f"{self.variation} is not a valid variation") + + def is_anchored(self) -> bool: + return ( + self.n == 1 and self.startingMonth is not None and self.weekday is not None + ) + + # -------------------------------------------------------------------- + # Name-related methods + + @property + def rule_code(self) -> str: + prefix = self._prefix + suffix = self.get_rule_code_suffix() + return f"{prefix}-{suffix}" + + def _get_suffix_prefix(self) -> str: + if self.variation == "nearest": + return "N" + else: + return "L" + + def get_rule_code_suffix(self) -> str: + prefix = self._get_suffix_prefix() + month = MONTH_ALIASES[self.startingMonth] + weekday = int_to_weekday[self.weekday] + return f"{prefix}-{month}-{weekday}" + + # ---------------------------------------------------------------------- # RelativeDelta Arithmetic diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index ae745181692b8..9665087f1add5 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -790,7 +790,7 @@ def __init__( # Month-Based Offset Classes -class MonthEnd(SingleConstructorMixin, liboffsets.MonthOffset): +class MonthEnd(liboffsets.MonthOffset): """ DateOffset of one month end. """ @@ -799,7 +799,7 @@ class MonthEnd(SingleConstructorMixin, liboffsets.MonthOffset): _day_opt = "end" -class MonthBegin(SingleConstructorMixin, liboffsets.MonthOffset): +class MonthBegin(liboffsets.MonthOffset): """ DateOffset of one month at beginning. """ @@ -808,7 +808,7 @@ class MonthBegin(SingleConstructorMixin, liboffsets.MonthOffset): _day_opt = "start" -class BusinessMonthEnd(SingleConstructorMixin, liboffsets.MonthOffset): +class BusinessMonthEnd(liboffsets.MonthOffset): """ DateOffset increments between business EOM dates. """ @@ -817,7 +817,7 @@ class BusinessMonthEnd(SingleConstructorMixin, liboffsets.MonthOffset): _day_opt = "business_end" -class BusinessMonthBegin(SingleConstructorMixin, liboffsets.MonthOffset): +class BusinessMonthBegin(liboffsets.MonthOffset): """ DateOffset of one business month at beginning. """ @@ -827,9 +827,7 @@ class BusinessMonthBegin(SingleConstructorMixin, liboffsets.MonthOffset): @doc(bound="bound") -class _CustomBusinessMonth( - CustomMixin, BusinessMixin, SingleConstructorMixin, liboffsets.MonthOffset -): +class _CustomBusinessMonth(CustomMixin, BusinessMixin, liboffsets.MonthOffset): """ DateOffset subclass representing custom business month(s). @@ -1506,50 +1504,7 @@ class YearBegin(liboffsets.YearOffset): # Special Offset Classes -class FY5253Mixin(SingleConstructorOffset): - def __init__( - self, n=1, normalize=False, weekday=0, startingMonth=1, variation="nearest" - ): - BaseOffset.__init__(self, n, normalize) - object.__setattr__(self, "startingMonth", startingMonth) - object.__setattr__(self, "weekday", weekday) - - object.__setattr__(self, "variation", variation) - - if self.n == 0: - raise ValueError("N cannot be 0") - - if self.variation not in ["nearest", "last"]: - raise ValueError(f"{self.variation} is not a valid variation") - - def is_anchored(self) -> bool: - return ( - self.n == 1 and self.startingMonth is not None and self.weekday is not None - ) - - # -------------------------------------------------------------------- - # Name-related methods - - @property - def rule_code(self) -> str: - prefix = self._prefix - suffix = self.get_rule_code_suffix() - return f"{prefix}-{suffix}" - - def _get_suffix_prefix(self) -> str: - if self.variation == "nearest": - return "N" - else: - return "L" - - def get_rule_code_suffix(self) -> str: - prefix = self._get_suffix_prefix() - month = ccalendar.MONTH_ALIASES[self.startingMonth] - weekday = ccalendar.int_to_weekday[self.weekday] - return f"{prefix}-{month}-{weekday}" - - -class FY5253(FY5253Mixin): +class FY5253(liboffsets.FY5253Mixin): """ Describes 52-53 week fiscal year. This is also known as a 4-4-5 calendar. @@ -1600,6 +1555,10 @@ class FY5253(FY5253Mixin): _prefix = "RE" _attributes = frozenset(["weekday", "startingMonth", "variation"]) + def __reduce__(self): + tup = (self.n, self.normalize, self.weekday, self.startingMonth, self.variation) + return type(self), tup + def is_on_offset(self, dt: datetime) -> bool: if self.normalize and not is_normalized(dt): return False @@ -1717,7 +1676,7 @@ def _from_name(cls, *args): return cls(**cls._parse_suffix(*args)) -class FY5253Quarter(FY5253Mixin): +class FY5253Quarter(liboffsets.FY5253Mixin): """ DateOffset increments between business quarter dates for 52-53 week fiscal year (also known as a 4-4-5 calendar). @@ -1787,9 +1746,22 @@ def __init__( qtr_with_extra_week=1, variation="nearest", ): - FY5253Mixin.__init__(self, n, normalize, weekday, startingMonth, variation) + liboffsets.FY5253Mixin.__init__( + self, n, normalize, weekday, startingMonth, variation + ) object.__setattr__(self, "qtr_with_extra_week", qtr_with_extra_week) + def __reduce__(self): + tup = ( + self.n, + self.normalize, + self.weekday, + self.startingMonth, + self.qtr_with_extra_week, + self.variation, + ) + return type(self), tup + @cache_readonly def _offset(self): return FY5253( @@ -1913,7 +1885,7 @@ def is_on_offset(self, dt: datetime) -> bool: @property def rule_code(self) -> str: - suffix = FY5253Mixin.rule_code.fget(self) # type: ignore + suffix = liboffsets.FY5253Mixin.rule_code.__get__(self) qtr = self.qtr_with_extra_week return f"{suffix}-{qtr}"