diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 3a9639a700953..1dd1565b066ef 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -23,7 +23,7 @@ from pandas._libs.tslibs.util cimport is_integer_object from pandas._libs.tslibs.base cimport ABCTick, ABCTimestamp, is_tick_object -from pandas._libs.tslibs.ccalendar import MONTHS, DAYS +from pandas._libs.tslibs.ccalendar import MONTHS, DAYS, weekday_to_int, int_to_weekday from pandas._libs.tslibs.ccalendar cimport get_days_in_month, dayofweek from pandas._libs.tslibs.conversion cimport ( convert_datetime_to_tsobject, @@ -922,10 +922,16 @@ class CustomMixin: object.__setattr__(self, "calendar", calendar) -class WeekOfMonthMixin: +class WeekOfMonthMixin(BaseOffset): """ Mixin for methods common to WeekOfMonth and LastWeekOfMonth. """ + def __init__(self, n=1, normalize=False, weekday=0): + BaseOffset.__init__(self, n, normalize) + object.__setattr__(self, "weekday", weekday) + + if weekday < 0 or weekday > 6: + raise ValueError(f"Day must be 0<=day<=6, got {weekday}") @apply_wraps def apply(self, other): @@ -946,6 +952,14 @@ class WeekOfMonthMixin: return False return dt.day == self._get_offset_day(dt) + @property + def rule_code(self) -> str: + weekday = int_to_weekday.get(self.weekday, "") + if self.week == -1: + # LastWeekOfMonth + return f"{self._prefix}-{weekday}" + return f"{self._prefix}-{self.week + 1}{weekday}" + # ---------------------------------------------------------------------- # RelativeDelta Arithmetic diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index 6213ea198f2cb..d95ffd5b0876d 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -165,7 +165,7 @@ def to_offset(freq) -> Optional[DateOffset]: ) stride = int(stride) offset = _get_offset(name) - offset = offset * int(np.fabs(stride) * stride_sign) + offset = offset * int(np.fabs(stride) * stride_sign) # type: ignore if delta is None: delta = offset else: diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 4912dc0eb349e..5912e003b9663 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -296,7 +296,7 @@ def is_on_offset(self, dt): return True -class SingleConstructorOffset(BaseOffset): +class SingleConstructorMixin: _params = cache_readonly(BaseOffset._params.fget) freqstr = cache_readonly(BaseOffset.freqstr.fget) @@ -308,6 +308,10 @@ def _from_name(cls, suffix=None): return cls() +class SingleConstructorOffset(SingleConstructorMixin, BaseOffset): + pass + + class BusinessDay(BusinessMixin, SingleConstructorOffset): """ DateOffset subclass representing possibly n business days. @@ -1308,7 +1312,7 @@ def _from_name(cls, suffix=None): return cls(weekday=weekday) -class WeekOfMonth(liboffsets.WeekOfMonthMixin, SingleConstructorOffset): +class WeekOfMonth(SingleConstructorMixin, liboffsets.WeekOfMonthMixin): """ Describes monthly dates like "the Tuesday of the 2nd week of each month". @@ -1334,12 +1338,9 @@ class WeekOfMonth(liboffsets.WeekOfMonthMixin, SingleConstructorOffset): _attributes = frozenset(["n", "normalize", "week", "weekday"]) def __init__(self, n=1, normalize=False, week=0, weekday=0): - BaseOffset.__init__(self, n, normalize) - object.__setattr__(self, "weekday", weekday) + liboffsets.WeekOfMonthMixin.__init__(self, n, normalize, weekday) object.__setattr__(self, "week", week) - if self.weekday < 0 or self.weekday > 6: - raise ValueError(f"Day must be 0<=day<=6, got {self.weekday}") if self.week < 0 or self.week > 3: raise ValueError(f"Week must be 0<=week<=3, got {self.week}") @@ -1361,11 +1362,6 @@ def _get_offset_day(self, other: datetime) -> int: shift_days = (self.weekday - wday) % 7 return 1 + shift_days + self.week * 7 - @property - def rule_code(self) -> str: - weekday = ccalendar.int_to_weekday.get(self.weekday, "") - return f"{self._prefix}-{self.week + 1}{weekday}" - @classmethod def _from_name(cls, suffix=None): if not suffix: @@ -1377,7 +1373,7 @@ def _from_name(cls, suffix=None): return cls(week=week, weekday=weekday) -class LastWeekOfMonth(liboffsets.WeekOfMonthMixin, SingleConstructorOffset): +class LastWeekOfMonth(SingleConstructorMixin, liboffsets.WeekOfMonthMixin): """ Describes monthly dates in last week of month like "the last Tuesday of each month". @@ -1401,14 +1397,11 @@ class LastWeekOfMonth(liboffsets.WeekOfMonthMixin, SingleConstructorOffset): _attributes = frozenset(["n", "normalize", "weekday"]) def __init__(self, n=1, normalize=False, weekday=0): - BaseOffset.__init__(self, n, normalize) - object.__setattr__(self, "weekday", weekday) + liboffsets.WeekOfMonthMixin.__init__(self, n, normalize, weekday) if self.n == 0: raise ValueError("N cannot be 0") - - if self.weekday < 0 or self.weekday > 6: - raise ValueError(f"Day must be 0<=day<=6, got {self.weekday}") + object.__setattr__(self, "week", -1) def _get_offset_day(self, other: datetime) -> int: """ @@ -1429,11 +1422,6 @@ def _get_offset_day(self, other: datetime) -> int: shift_days = (wday - self.weekday) % 7 return dim - shift_days - @property - def rule_code(self) -> str: - weekday = ccalendar.int_to_weekday.get(self.weekday, "") - return f"{self._prefix}-{weekday}" - @classmethod def _from_name(cls, suffix=None): if not suffix: