Skip to content
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
121 changes: 65 additions & 56 deletions pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,60 @@ cdef class PeriodMixin:
return FR_SEC
return base

@property
def start_time(self) -> Timestamp:
"""
Get the Timestamp for the start of the period.

Returns
-------
Timestamp

See Also
--------
Period.end_time : Return the end Timestamp.
Period.dayofyear : Return the day of year.
Period.daysinmonth : Return the days in that month.
Period.dayofweek : Return the day of the week.

Examples
--------
>>> period = pd.Period('2012-1-1', freq='D')
>>> period
Period('2012-01-01', 'D')

>>> period.start_time
Timestamp('2012-01-01 00:00:00')

>>> period.end_time
Timestamp('2012-01-01 23:59:59.999999999')
"""
return self.to_timestamp(how="start")

@property
def end_time(self) -> Timestamp:
return self.to_timestamp(how="end")

def _require_matching_freq(self, other, base=False):
# See also arrays.period.raise_on_incompatible
if is_offset_object(other):
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)


cdef class _Period(PeriodMixin):

Expand Down Expand Up @@ -1551,10 +1605,7 @@ cdef class _Period(PeriodMixin):
return False
elif op == Py_NE:
return True
msg = DIFFERENT_FREQ.format(cls=type(self).__name__,
own_freq=self.freqstr,
other_freq=other.freqstr)
raise IncompatibleFrequency(msg)
self._require_matching_freq(other)
return PyObject_RichCompareBool(self.ordinal, other.ordinal, op)
elif other is NaT:
return _nat_scalar_rules[op]
Expand All @@ -1563,15 +1614,15 @@ cdef class _Period(PeriodMixin):
def __hash__(self):
return hash((self.ordinal, self.freqstr))

def _add_delta(self, other) -> "Period":
def _add_timedeltalike_scalar(self, other) -> "Period":
cdef:
int64_t nanos, offset_nanos
int64_t nanos, base_nanos

if is_tick_object(self.freq):
nanos = delta_to_nanoseconds(other)
offset_nanos = self.freq.base.nanos
if nanos % offset_nanos == 0:
ordinal = self.ordinal + (nanos // offset_nanos)
base_nanos = self.freq.base.nanos
if nanos % base_nanos == 0:
ordinal = self.ordinal + (nanos // base_nanos)
return Period(ordinal=ordinal, freq=self.freq)
raise IncompatibleFrequency("Input cannot be converted to "
f"Period(freq={self.freqstr})")
Expand All @@ -1581,14 +1632,10 @@ cdef class _Period(PeriodMixin):
cdef:
int64_t ordinal

if other.base == self.freq.base:
ordinal = self.ordinal + other.n
return Period(ordinal=ordinal, freq=self.freq)
self._require_matching_freq(other, base=True)

msg = DIFFERENT_FREQ.format(cls=type(self).__name__,
own_freq=self.freqstr,
other_freq=other.freqstr)
raise IncompatibleFrequency(msg)
ordinal = self.ordinal + other.n
return Period(ordinal=ordinal, freq=self.freq)

def __add__(self, other):
if not is_period_object(self):
Expand All @@ -1598,7 +1645,7 @@ cdef class _Period(PeriodMixin):
return other.__add__(self)

if is_any_td_scalar(other):
return self._add_delta(other)
return self._add_timedeltalike_scalar(other)
elif is_offset_object(other):
return self._add_offset(other)
elif other is NaT:
Expand Down Expand Up @@ -1635,11 +1682,7 @@ cdef class _Period(PeriodMixin):
ordinal = self.ordinal - other * self.freq.n
return Period(ordinal=ordinal, freq=self.freq)
elif is_period_object(other):
if other.freq != self.freq:
msg = DIFFERENT_FREQ.format(cls=type(self).__name__,
own_freq=self.freqstr,
other_freq=other.freqstr)
raise IncompatibleFrequency(msg)
self._require_matching_freq(other)
# GH 23915 - mul by base freq since __add__ is agnostic of n
return (self.ordinal - other.ordinal) * self.freq.base
elif other is NaT:
Expand Down Expand Up @@ -1677,40 +1720,6 @@ cdef class _Period(PeriodMixin):

return Period(ordinal=ordinal, freq=freq)

@property
def start_time(self) -> Timestamp:
"""
Get the Timestamp for the start of the period.

Returns
-------
Timestamp

See Also
--------
Period.end_time : Return the end Timestamp.
Period.dayofyear : Return the day of year.
Period.daysinmonth : Return the days in that month.
Period.dayofweek : Return the day of the week.

Examples
--------
>>> period = pd.Period('2012-1-1', freq='D')
>>> period
Period('2012-01-01', 'D')

>>> period.start_time
Timestamp('2012-01-01 00:00:00')

>>> period.end_time
Timestamp('2012-01-01 23:59:59.999999999')
"""
return self.to_timestamp(how='S')

@property
def end_time(self) -> Timestamp:
return self.to_timestamp(how="end")

def to_timestamp(self, freq=None, how='start', tz=None) -> Timestamp:
"""
Return the Timestamp representation of the Period.
Expand Down
20 changes: 3 additions & 17 deletions pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,7 @@ def _scalar_from_string(self, value: str) -> Period:
def _check_compatible_with(self, other, setitem: bool = False):
if other is NaT:
return
if self.freqstr != other.freqstr:
raise raise_on_incompatible(self, other)
self._require_matching_freq(other)

# --------------------------------------------------------------------
# Data / Attributes
Expand Down Expand Up @@ -425,14 +424,6 @@ def is_leap_year(self) -> np.ndarray:
"""
return isleapyear_arr(np.asarray(self.year))

@property
def start_time(self):
return self.to_timestamp(how="start")

@property
def end_time(self):
return self.to_timestamp(how="end")

def to_timestamp(self, freq=None, how="start"):
"""
Cast to DatetimeArray/Index.
Expand Down Expand Up @@ -659,11 +650,7 @@ def _sub_period_array(self, other):
result : np.ndarray[object]
Array of DateOffset objects; nulls represented by NaT.
"""
if self.freq != other.freq:
msg = DIFFERENT_FREQ.format(
cls=type(self).__name__, own_freq=self.freqstr, other_freq=other.freqstr
)
raise IncompatibleFrequency(msg)
self._require_matching_freq(other)

new_values = algos.checked_add_with_arr(
self.asi8, -other.asi8, arr_mask=self._isnan, b_mask=other._isnan
Expand Down Expand Up @@ -702,8 +689,7 @@ def _addsub_int_array(
def _add_offset(self, other: BaseOffset):
assert not isinstance(other, Tick)

if other.base != self.freq.base:
raise raise_on_incompatible(self, other)
self._require_matching_freq(other, base=True)

# Note: when calling parent class's _add_timedeltalike_scalar,
# it will call delta_to_nanoseconds(delta). Because delta here
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/indexes/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ def inherit_from_data(name: str, delegate, cache: bool = False, wrap: bool = Fal
"""
attr = getattr(delegate, name)

if isinstance(attr, property):
if isinstance(attr, property) or type(attr).__name__ == "getset_descriptor":
# getset_descriptor i.e. property defined in cython class
if cache:

def cached(self):
Expand Down