diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index c7b7d612f..5097b2061 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -45,7 +45,15 @@ class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin): _DTFieldOpsReturnType = TypeVar("_DTFieldOpsReturnType", Series[int], IntegerIndex) -class _DatetimeFieldOps(Generic[_DTFieldOpsReturnType]): +class _DatetimeFieldOps( + _DayLikeFieldOps[_DTFieldOpsReturnType], _MiniSeconds[_DTFieldOpsReturnType] +): ... +class PeriodIndexFieldOps( + _DayLikeFieldOps[IntegerIndex], + _PeriodProperties[DatetimeIndex, IntegerIndex, Index, DatetimeIndex, PeriodIndex], +): ... + +class _DayLikeFieldOps(Generic[_DTFieldOpsReturnType]): @property def year(self) -> _DTFieldOpsReturnType: ... @property @@ -74,6 +82,8 @@ class _DatetimeFieldOps(Generic[_DTFieldOpsReturnType]): def days_in_month(self) -> _DTFieldOpsReturnType: ... @property def daysinmonth(self) -> _DTFieldOpsReturnType: ... + +class _MiniSeconds(Generic[_DTFieldOpsReturnType]): @property def microsecond(self) -> _DTFieldOpsReturnType: ... @property @@ -295,28 +305,44 @@ class TimedeltaProperties( _DatetimeRoundingMethods[TimedeltaSeries], ): ... -class _PeriodProperties: +_PeriodDTReturnTypes = TypeVar("_PeriodDTReturnTypes", TimestampSeries, DatetimeIndex) +_PeriodIntReturnTypes = TypeVar("_PeriodIntReturnTypes", Series[int], IntegerIndex) +_PeriodStrReturnTypes = TypeVar("_PeriodStrReturnTypes", Series[str], Index) +_PeriodDTAReturnTypes = TypeVar("_PeriodDTAReturnTypes", DatetimeArray, DatetimeIndex) +_PeriodPAReturnTypes = TypeVar("_PeriodPAReturnTypes", PeriodArray, PeriodIndex) + +class _PeriodProperties( + Generic[ + _PeriodDTReturnTypes, + _PeriodIntReturnTypes, + _PeriodStrReturnTypes, + _PeriodDTAReturnTypes, + _PeriodPAReturnTypes, + ] +): @property - def start_time(self) -> TimestampSeries: ... + def start_time(self) -> _PeriodDTReturnTypes: ... @property - def end_time(self) -> TimestampSeries: ... + def end_time(self) -> _PeriodDTReturnTypes: ... @property - def qyear(self) -> Series[int]: ... - def strftime(self, date_format: str) -> Series[str]: ... + def qyear(self) -> _PeriodIntReturnTypes: ... + def strftime(self, date_format: str) -> _PeriodStrReturnTypes: ... def to_timestamp( self, freq: str | DateOffset | None = ..., how: TimestampConvention = ..., - ) -> DatetimeArray: ... + ) -> _PeriodDTAReturnTypes: ... def asfreq( self, freq: str | DateOffset | None = ..., how: Literal["E", "END", "FINISH", "S", "START", "BEGIN"] = ..., - ) -> PeriodArray: ... + ) -> _PeriodPAReturnTypes: ... class PeriodProperties( Properties, - _PeriodProperties, + _PeriodProperties[ + TimestampSeries, Series[int], Series[str], DatetimeArray, PeriodArray + ], _DatetimeFieldOps[Series[int]], _IsLeapYearProperty, _FreqProperty[BaseOffset], diff --git a/pandas-stubs/core/indexes/datetimelike.pyi b/pandas-stubs/core/indexes/datetimelike.pyi index dc66aa0fc..073af7855 100644 --- a/pandas-stubs/core/indexes/datetimelike.pyi +++ b/pandas-stubs/core/indexes/datetimelike.pyi @@ -6,7 +6,8 @@ from pandas._libs.tslibs import BaseOffset class DatetimeIndexOpsMixin(ExtensionIndex): @property def freq(self) -> BaseOffset | None: ... - freqstr: str | None + @property + def freqstr(self) -> str | None: ... @property def is_all_dates(self) -> bool: ... @property diff --git a/pandas-stubs/core/indexes/period.pyi b/pandas-stubs/core/indexes/period.pyi index b7dd8eac3..dc1ab02a6 100644 --- a/pandas-stubs/core/indexes/period.pyi +++ b/pandas-stubs/core/indexes/period.pyi @@ -4,6 +4,7 @@ from typing import overload import numpy as np import pandas as pd from pandas import Index +from pandas.core.indexes.accessors import PeriodIndexFieldOps from pandas.core.indexes.datetimelike import ( DatetimeIndexOpsMixin as DatetimeIndexOpsMixin, ) @@ -15,7 +16,7 @@ from pandas._libs.tslibs import ( Period, ) -class PeriodIndex(DatetimeIndexOpsMixin, Int64Index): +class PeriodIndex(DatetimeIndexOpsMixin, Int64Index, PeriodIndexFieldOps): def __new__( cls, data=..., @@ -61,6 +62,8 @@ class PeriodIndex(DatetimeIndexOpsMixin, Int64Index): def intersection(self, other, sort: bool = ...): ... def difference(self, other, sort=...): ... def memory_usage(self, deep: bool = ...): ... + @property + def freqstr(self) -> str: ... def period_range( start: str | pd.Period | None = ..., diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 23e3e2839..3dd4ffdec 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -528,6 +528,37 @@ def test_timedeltaindex_accessors() -> None: check(assert_type(i0.ceil("D"), pd.TimedeltaIndex), pd.TimedeltaIndex, pd.Timedelta) +def test_periodindex_accessors() -> None: + # GH 395 + + i0 = pd.period_range(start="2022-06-01", periods=10) + check(assert_type(i0, pd.PeriodIndex), pd.PeriodIndex, pd.Period) + + check(assert_type(i0.year, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.month, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.day, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.hour, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.minute, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.second, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.dayofweek, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.day_of_week, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.weekday, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.dayofyear, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.day_of_year, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.quarter, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.daysinmonth, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.days_in_month, IntegerIndex), IntegerIndex, int) + check(assert_type(i0.freq, Optional[BaseOffset]), BaseOffset) + check(assert_type(i0.strftime("%Y"), pd.Index), pd.Index, str) + check(assert_type(i0.asfreq("D"), pd.PeriodIndex), pd.PeriodIndex, pd.Period) + check(assert_type(i0.end_time, pd.DatetimeIndex), pd.DatetimeIndex, pd.Timestamp) + check(assert_type(i0.start_time, pd.DatetimeIndex), pd.DatetimeIndex, pd.Timestamp) + check( + assert_type(i0.to_timestamp(), pd.DatetimeIndex), pd.DatetimeIndex, pd.Timestamp + ) + check(assert_type(i0.freqstr, str), str) + + def test_some_offsets() -> None: # GH 222 check(