Skip to content

REF: partially decouple Period from Tick #53440

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
May 30, 2023
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
1 change: 1 addition & 0 deletions pandas/_libs/tslibs/dtypes.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,4 @@ cdef class PeriodDtypeBase:
int64_t _n

cpdef int _get_to_timestamp_base(self)
cpdef bint _is_tick_like(self)
5 changes: 5 additions & 0 deletions pandas/_libs/tslibs/dtypes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class PeriodDtypeBase:
@property
def _freqstr(self) -> str: ...
def __hash__(self) -> int: ...
def _is_tick_like(self) -> bool: ...
@property
def _creso(self) -> int: ...
@property
def _td64_unit(self) -> str: ...

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 @@ -77,6 +77,25 @@ cdef class PeriodDtypeBase:
return FR_SEC
return base

cpdef bint _is_tick_like(self):
return self._dtype_code >= PeriodDtypeCode.D

@property
def _creso(self) -> int:
return {
PeriodDtypeCode.D: NPY_DATETIMEUNIT.NPY_FR_D,
PeriodDtypeCode.H: NPY_DATETIMEUNIT.NPY_FR_h,
PeriodDtypeCode.T: NPY_DATETIMEUNIT.NPY_FR_m,
PeriodDtypeCode.S: NPY_DATETIMEUNIT.NPY_FR_s,
PeriodDtypeCode.L: NPY_DATETIMEUNIT.NPY_FR_ms,
PeriodDtypeCode.U: NPY_DATETIMEUNIT.NPY_FR_us,
PeriodDtypeCode.N: NPY_DATETIMEUNIT.NPY_FR_ns,
}[self._dtype_code]

@property
def _td64_unit(self) -> str:
return npy_unit_to_abbrev(self._creso)


_period_code_map = {
# Annual freqs with various fiscal year ends.
Expand Down
1 change: 0 additions & 1 deletion pandas/_libs/tslibs/offsets.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ def to_offset(freq: timedelta | str) -> BaseOffset: ...
class Tick(SingleConstructorOffset):
_creso: int
_prefix: str
_td64_unit: str
def __init__(self, n: int = ..., normalize: bool = ...) -> None: ...
@property
def delta(self) -> Timedelta: ...
Expand Down
8 changes: 0 additions & 8 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,6 @@ cdef class SingleConstructorOffset(BaseOffset):
cdef class Tick(SingleConstructorOffset):
_adjust_dst = False
_prefix = "undefined"
_td64_unit = "undefined"
_attributes = tuple(["n", "normalize"])

def __init__(self, n=1, normalize=False):
Expand Down Expand Up @@ -1092,55 +1091,48 @@ cdef class Tick(SingleConstructorOffset):
cdef class Day(Tick):
_nanos_inc = 24 * 3600 * 1_000_000_000
_prefix = "D"
_td64_unit = "D"
_period_dtype_code = PeriodDtypeCode.D
_creso = NPY_DATETIMEUNIT.NPY_FR_D


cdef class Hour(Tick):
_nanos_inc = 3600 * 1_000_000_000
_prefix = "H"
_td64_unit = "h"
_period_dtype_code = PeriodDtypeCode.H
_creso = NPY_DATETIMEUNIT.NPY_FR_h


cdef class Minute(Tick):
_nanos_inc = 60 * 1_000_000_000
_prefix = "T"
_td64_unit = "m"
_period_dtype_code = PeriodDtypeCode.T
_creso = NPY_DATETIMEUNIT.NPY_FR_m


cdef class Second(Tick):
_nanos_inc = 1_000_000_000
_prefix = "S"
_td64_unit = "s"
_period_dtype_code = PeriodDtypeCode.S
_creso = NPY_DATETIMEUNIT.NPY_FR_s


cdef class Milli(Tick):
_nanos_inc = 1_000_000
_prefix = "L"
_td64_unit = "ms"
_period_dtype_code = PeriodDtypeCode.L
_creso = NPY_DATETIMEUNIT.NPY_FR_ms


cdef class Micro(Tick):
_nanos_inc = 1000
_prefix = "U"
_td64_unit = "us"
_period_dtype_code = PeriodDtypeCode.U
_creso = NPY_DATETIMEUNIT.NPY_FR_us


cdef class Nano(Tick):
_nanos_inc = 1
_prefix = "N"
_td64_unit = "ns"
_period_dtype_code = PeriodDtypeCode.N
_creso = NPY_DATETIMEUNIT.NPY_FR_ns

Expand Down
5 changes: 2 additions & 3 deletions pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ from pandas._libs.tslibs.nattype cimport (
from pandas._libs.tslibs.offsets cimport (
BaseOffset,
is_offset_object,
is_tick_object,
to_offset,
)

Expand Down Expand Up @@ -1793,7 +1792,7 @@ cdef class _Period(PeriodMixin):
cdef:
int64_t inc

if not is_tick_object(self.freq):
if not self._dtype._is_tick_like():
raise IncompatibleFrequency("Input cannot be converted to "
f"Period(freq={self.freqstr})")

Expand All @@ -1805,7 +1804,7 @@ cdef class _Period(PeriodMixin):
return NaT

try:
inc = delta_to_nanoseconds(other, reso=self.freq._creso, round_ok=False)
inc = delta_to_nanoseconds(other, reso=self._dtype._creso, round_ok=False)
except ValueError as err:
raise IncompatibleFrequency("Input cannot be converted to "
f"Period(freq={self.freqstr})") from err
Expand Down
2 changes: 1 addition & 1 deletion pandas/_testing/asserters.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def _get_ilevel_values(index, level):
if check_names:
assert_attr_equal("names", left, right, obj=obj)
if isinstance(left, PeriodIndex) or isinstance(right, PeriodIndex):
assert_attr_equal("freq", left, right, obj=obj)
assert_attr_equal("dtype", left, right, obj=obj)
if isinstance(left, IntervalIndex) or isinstance(right, IntervalIndex):
assert_interval_array_equal(left._values, right._values)

Expand Down
9 changes: 4 additions & 5 deletions pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,14 +767,13 @@ def _add_timedelta_arraylike(
-------
PeriodArray
"""
freq = self.freq
if not isinstance(freq, Tick):
if not self.dtype._is_tick_like():
# We cannot add timedelta-like to non-tick PeriodArray
raise TypeError(
f"Cannot add or subtract timedelta64[ns] dtype from {self.dtype}"
)

dtype = np.dtype(f"m8[{freq._td64_unit}]")
dtype = np.dtype(f"m8[{self.dtype._td64_unit}]")

# Similar to _check_timedeltalike_freq_compat, but we raise with a
# more specific exception message if necessary.
Expand Down Expand Up @@ -818,9 +817,9 @@ def _check_timedeltalike_freq_compat(self, other):
------
IncompatibleFrequency
"""
assert isinstance(self.freq, Tick) # checked by calling function
assert self.dtype._is_tick_like() # checked by calling function

dtype = np.dtype(f"m8[{self.freq._td64_unit}]")
dtype = np.dtype(f"m8[{self.dtype._td64_unit}]")

if isinstance(other, (timedelta, np.timedelta64, Tick)):
td = np.asarray(Timedelta(other).asm8)
Expand Down