Skip to content

REF: re-share Period methods #46314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pandas/_libs/tslibs/dtypes.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ cdef enum PeriodDtypeCode:
cdef class PeriodDtypeBase:
cdef readonly:
PeriodDtypeCode _dtype_code

cpdef int _get_to_timestamp_base(self)
1 change: 1 addition & 0 deletions pandas/_libs/tslibs/dtypes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions pandas/_libs/tslibs/dtypes.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <c_FreqGroup>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.
Expand Down
13 changes: 8 additions & 5 deletions pandas/_libs/tslibs/period.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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: ...
21 changes: 1 addition & 20 deletions pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 3 additions & 54 deletions pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/tseries/frequencies/test_freq_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down