diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 71b4eeabbaaf5..ae25de38c5bfd 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -74,3 +74,5 @@ cdef enum PeriodDtypeCode: cdef class PeriodDtypeBase: cdef readonly: PeriodDtypeCode _dtype_code + + cpdef int _get_to_timestamp_base(self) diff --git a/pandas/_libs/tslibs/dtypes.pyi b/pandas/_libs/tslibs/dtypes.pyi index 8e47993e9d85f..8c642bc847a29 100644 --- a/pandas/_libs/tslibs/dtypes.pyi +++ b/pandas/_libs/tslibs/dtypes.pyi @@ -16,6 +16,7 @@ class PeriodDtypeBase: def from_date_offset(cls, offset: BaseOffset) -> PeriodDtypeBase: ... @property def resolution(self) -> Resolution: ... + def _get_to_timestamp_base(self) -> int: ... class FreqGroup(Enum): FR_ANN: int diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index ea5454572ca7e..b64a360510ab9 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -50,6 +50,25 @@ cdef class PeriodDtypeBase: code = offset._period_dtype_code return cls(code) + cpdef int _get_to_timestamp_base(self): + """ + Return frequency code group used for base of to_timestamp against + frequency code. + + Return day freq code against longer freq than day. + Return second freq code against hour between second. + + Returns + ------- + int + """ + base = self._dtype_code + if base < FR_BUS: + return FR_DAY + elif FR_HR <= base <= FR_SEC: + return FR_SEC + return base + _period_code_map = { # Annual freqs with various fiscal year ends. diff --git a/pandas/_libs/tslibs/period.pyi b/pandas/_libs/tslibs/period.pyi index 7d6e31f339e29..5ad919649262c 100644 --- a/pandas/_libs/tslibs/period.pyi +++ b/pandas/_libs/tslibs/period.pyi @@ -52,7 +52,14 @@ def period_ordinal( def freq_to_dtype_code(freq: BaseOffset) -> int: ... def validate_end_alias(how: str) -> Literal["E", "S"]: ... -class Period: +class PeriodMixin: + @property + def end_time(self) -> Timestamp: ... + @property + def start_time(self) -> Timestamp: ... + def _require_matching_freq(self, other, base: bool = ...) -> None: ... + +class Period(PeriodMixin): ordinal: int # int64_t freq: BaseOffset @@ -118,9 +125,5 @@ class Period: def month(self) -> int: ... @property def year(self) -> int: ... - @property - def end_time(self) -> Timestamp: ... - @property - def start_time(self) -> Timestamp: ... def __sub__(self, other) -> Period | BaseOffset: ... def __add__(self, other) -> Period: ... diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 40a83afee8eab..54cae834d7024 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1545,25 +1545,6 @@ class IncompatibleFrequency(ValueError): cdef class PeriodMixin: # Methods shared between Period and PeriodArray - cpdef int _get_to_timestamp_base(self): - """ - Return frequency code group used for base of to_timestamp against - frequency code. - - Return day freq code against longer freq than day. - Return second freq code against hour between second. - - Returns - ------- - int - """ - base = self._dtype._dtype_code - if base < FR_BUS: - return FR_DAY - elif FR_HR <= base <= FR_SEC: - return FR_SEC - return base - @property def start_time(self) -> Timestamp: """ @@ -1861,7 +1842,7 @@ cdef class _Period(PeriodMixin): return endpoint - Timedelta(1, 'ns') if freq is None: - freq = self._get_to_timestamp_base() + freq = self._dtype._get_to_timestamp_base() base = freq else: freq = self._maybe_convert_freq(freq) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 762f681f97e6d..0bac4fe59d51c 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -104,7 +104,7 @@ def f(self): return property(f) -class PeriodArray(dtl.DatelikeOps): +class PeriodArray(dtl.DatelikeOps, libperiod.PeriodMixin): """ Pandas ExtensionArray for storing Period data. @@ -500,7 +500,7 @@ def to_timestamp(self, freq=None, how: str = "start") -> DatetimeArray: return (self + self.freq).to_timestamp(how="start") - adjust if freq is None: - freq = self._get_to_timestamp_base() + freq = self._dtype._get_to_timestamp_base() base = freq else: freq = Period._maybe_convert_freq(freq) @@ -606,8 +606,7 @@ def asfreq(self, freq=None, how: str = "E") -> PeriodArray: freq = Period._maybe_convert_freq(freq) - # error: "BaseOffset" has no attribute "_period_dtype_code" - base1 = self.freq._period_dtype_code # type: ignore[attr-defined] + base1 = self._dtype._dtype_code base2 = freq._period_dtype_code asi8 = self.asi8 @@ -884,56 +883,6 @@ def _check_timedeltalike_freq_compat(self, other): raise raise_on_incompatible(self, other) - # ------------------------------------------------------------------ - # TODO: See if we can re-share this with Period - - def _get_to_timestamp_base(self) -> int: - """ - Return frequency code group used for base of to_timestamp against - frequency code. - - Return day freq code against longer freq than day. - Return second freq code against hour between second. - - Returns - ------- - int - """ - base = self._dtype._dtype_code - if base < FreqGroup.FR_BUS.value: - return FreqGroup.FR_DAY.value - elif FreqGroup.FR_HR.value <= base <= FreqGroup.FR_SEC.value: - return FreqGroup.FR_SEC.value - return base - - @property - def start_time(self) -> DatetimeArray: - return self.to_timestamp(how="start") - - @property - def end_time(self) -> DatetimeArray: - return self.to_timestamp(how="end") - - def _require_matching_freq(self, other, base: bool = False) -> None: - # See also arrays.period.raise_on_incompatible - if isinstance(other, BaseOffset): - other_freq = other - else: - other_freq = other.freq - - if base: - condition = self.freq.base != other_freq.base - else: - condition = self.freq != other_freq - - if condition: - msg = DIFFERENT_FREQ.format( - cls=type(self).__name__, - own_freq=self.freqstr, - other_freq=other_freq.freqstr, - ) - raise IncompatibleFrequency(msg) - def raise_on_incompatible(left, right): """ diff --git a/pandas/tests/tseries/frequencies/test_freq_code.py b/pandas/tests/tseries/frequencies/test_freq_code.py index 4d8c4ddbce441..41257bb3f67fd 100644 --- a/pandas/tests/tseries/frequencies/test_freq_code.py +++ b/pandas/tests/tseries/frequencies/test_freq_code.py @@ -18,7 +18,7 @@ def test_get_to_timestamp_base(freqstr, exp_freqstr): per = Period._from_ordinal(1, off) exp_code = to_offset(exp_freqstr)._period_dtype_code - result_code = per._get_to_timestamp_base() + result_code = per._dtype._get_to_timestamp_base() assert result_code == exp_code