Skip to content

Commit a51279e

Browse files
authored
Add unit property and as_unit method to DatetimeIndex, TimedeltaIndex and Series.dt (#865)
* Add unit property and as_unit method to DatetimeIndex and TimedeltaIndex * Add unit property and as_unit method to TimelikeOps * Add unit property and as_unit method to TimestampSeries and TimedeltaSeries * Use TimeUnit type alias instead of str or Literal["s", "ms", "us", "ns"]
1 parent fdbe1df commit a51279e

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

pandas-stubs/core/arrays/datetimelike.pyi

+5
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ from pandas.core.arrays.base import (
55
ExtensionArray,
66
ExtensionOpsMixin,
77
)
8+
from typing_extensions import Self
89

910
from pandas._libs import (
1011
NaT as NaT,
1112
NaTType as NaTType,
1213
)
14+
from pandas._typing import TimeUnit
1315

1416
class DatelikeOps:
1517
def strftime(self, date_format): ...
1618

1719
class TimelikeOps:
20+
@property
21+
def unit(self) -> TimeUnit: ...
22+
def as_unit(self, unit: TimeUnit) -> Self: ...
1823
def round(self, freq, ambiguous: str = ..., nonexistent: str = ...): ...
1924
def floor(self, freq, ambiguous: str = ..., nonexistent: str = ...): ...
2025
def ceil(self, freq, ambiguous: str = ..., nonexistent: str = ...): ...

pandas-stubs/core/indexes/accessors.pyi

+21-14
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ from pandas._libs.tslibs import BaseOffset
3636
from pandas._libs.tslibs.offsets import DateOffset
3737
from pandas._typing import (
3838
TimestampConvention,
39+
TimeUnit,
3940
np_ndarray_bool,
4041
)
4142

@@ -154,16 +155,16 @@ class _DatetimeLikeOps(
154155
# type of the series, we don't know which kind of series was ...ed
155156
# in to the dt accessor
156157

157-
_DTRoundingMethodReturnType = TypeVar(
158-
"_DTRoundingMethodReturnType",
158+
_DTTimestampTimedeltaReturnType = TypeVar(
159+
"_DTTimestampTimedeltaReturnType",
159160
Series,
160-
TimedeltaSeries,
161161
TimestampSeries,
162+
TimedeltaSeries,
162163
DatetimeIndex,
163164
TimedeltaIndex,
164165
)
165166

166-
class _DatetimeRoundingMethods(Generic[_DTRoundingMethodReturnType]):
167+
class _DatetimeRoundingMethods(Generic[_DTTimestampTimedeltaReturnType]):
167168
def round(
168169
self,
169170
freq: str | BaseOffset | None,
@@ -173,7 +174,7 @@ class _DatetimeRoundingMethods(Generic[_DTRoundingMethodReturnType]):
173174
| timedelta
174175
| Timedelta
175176
) = ...,
176-
) -> _DTRoundingMethodReturnType: ...
177+
) -> _DTTimestampTimedeltaReturnType: ...
177178
def floor(
178179
self,
179180
freq: str | BaseOffset | None,
@@ -183,7 +184,7 @@ class _DatetimeRoundingMethods(Generic[_DTRoundingMethodReturnType]):
183184
| timedelta
184185
| Timedelta
185186
) = ...,
186-
) -> _DTRoundingMethodReturnType: ...
187+
) -> _DTTimestampTimedeltaReturnType: ...
187188
def ceil(
188189
self,
189190
freq: str | BaseOffset | None,
@@ -193,7 +194,7 @@ class _DatetimeRoundingMethods(Generic[_DTRoundingMethodReturnType]):
193194
| timedelta
194195
| Timedelta
195196
) = ...,
196-
) -> _DTRoundingMethodReturnType: ...
197+
) -> _DTTimestampTimedeltaReturnType: ...
197198

198199
_DTNormalizeReturnType = TypeVar(
199200
"_DTNormalizeReturnType", TimestampSeries, DatetimeIndex
@@ -202,9 +203,9 @@ _DTStrKindReturnType = TypeVar("_DTStrKindReturnType", Series[str], Index)
202203
_DTToPeriodReturnType = TypeVar("_DTToPeriodReturnType", PeriodSeries, PeriodIndex)
203204

204205
class _DatetimeLikeNoTZMethods(
205-
_DatetimeRoundingMethods[_DTRoundingMethodReturnType],
206+
_DatetimeRoundingMethods[_DTTimestampTimedeltaReturnType],
206207
Generic[
207-
_DTRoundingMethodReturnType,
208+
_DTTimestampTimedeltaReturnType,
208209
_DTNormalizeReturnType,
209210
_DTStrKindReturnType,
210211
_DTToPeriodReturnType,
@@ -238,15 +239,15 @@ class _DatetimeNoTZProperties(
238239
_DTFreqReturnType,
239240
],
240241
_DatetimeLikeNoTZMethods[
241-
_DTRoundingMethodReturnType,
242+
_DTTimestampTimedeltaReturnType,
242243
_DTNormalizeReturnType,
243244
_DTStrKindReturnType,
244245
_DTToPeriodReturnType,
245246
],
246247
Generic[
247248
_DTFieldOpsReturnType,
248249
_DTBoolOpsReturnType,
249-
_DTRoundingMethodReturnType,
250+
_DTTimestampTimedeltaReturnType,
250251
_DTOtherOpsDateReturnType,
251252
_DTOtherOpsTimeReturnType,
252253
_DTFreqReturnType,
@@ -261,7 +262,7 @@ class DatetimeProperties(
261262
_DatetimeNoTZProperties[
262263
_DTFieldOpsReturnType,
263264
_DTBoolOpsReturnType,
264-
_DTRoundingMethodReturnType,
265+
_DTTimestampTimedeltaReturnType,
265266
_DTOtherOpsDateReturnType,
266267
_DTOtherOpsTimeReturnType,
267268
_DTFreqReturnType,
@@ -272,7 +273,7 @@ class DatetimeProperties(
272273
Generic[
273274
_DTFieldOpsReturnType,
274275
_DTBoolOpsReturnType,
275-
_DTRoundingMethodReturnType,
276+
_DTTimestampTimedeltaReturnType,
276277
_DTOtherOpsDateReturnType,
277278
_DTOtherOpsTimeReturnType,
278279
_DTFreqReturnType,
@@ -283,6 +284,9 @@ class DatetimeProperties(
283284
):
284285
def to_pydatetime(self) -> np.ndarray: ...
285286
def isocalendar(self) -> DataFrame: ...
287+
@property
288+
def unit(self) -> TimeUnit: ...
289+
def as_unit(self, unit: TimeUnit) -> _DTTimestampTimedeltaReturnType: ...
286290

287291
_TDNoRoundingMethodReturnType = TypeVar(
288292
"_TDNoRoundingMethodReturnType", Series[int], Index
@@ -309,7 +313,10 @@ class TimedeltaProperties(
309313
Properties,
310314
_TimedeltaPropertiesNoRounding[Series[int], Series[float]],
311315
_DatetimeRoundingMethods[TimedeltaSeries],
312-
): ...
316+
):
317+
@property
318+
def unit(self) -> TimeUnit: ...
319+
def as_unit(self, unit: TimeUnit) -> TimedeltaSeries: ...
313320

314321
_PeriodDTReturnTypes = TypeVar("_PeriodDTReturnTypes", TimestampSeries, DatetimeIndex)
315322
_PeriodIntReturnTypes = TypeVar("_PeriodIntReturnTypes", Series[int], Index[int])

pandas-stubs/core/indexes/datetimelike.pyi

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
from pandas.core.indexes.extension import ExtensionIndex
22
from pandas.core.indexes.timedeltas import TimedeltaIndex
3+
from typing_extensions import Self
34

45
from pandas._libs.tslibs import BaseOffset
5-
from pandas._typing import S1
6+
from pandas._typing import (
7+
S1,
8+
TimeUnit,
9+
)
610

711
class DatetimeIndexOpsMixin(ExtensionIndex[S1]):
812
@property
@@ -19,4 +23,7 @@ class DatetimeIndexOpsMixin(ExtensionIndex[S1]):
1923
self, other: DatetimeIndexOpsMixin
2024
) -> TimedeltaIndex: ...
2125

22-
class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin[S1]): ...
26+
class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin[S1]):
27+
@property
28+
def unit(self) -> TimeUnit: ...
29+
def as_unit(self, unit: TimeUnit) -> Self: ...

tests/test_timefuncs.py

+47
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
TYPE_CHECKING,
66
Any,
77
Optional,
8+
cast,
89
)
910

1011
import numpy as np
@@ -24,6 +25,8 @@
2425
from pandas._typing import FulldatetimeDict
2526
else:
2627
FulldatetimeDict = Any
28+
from pandas._typing import TimeUnit
29+
2730
from tests import (
2831
TYPE_CHECKING_INVALID_USAGE,
2932
check,
@@ -428,6 +431,11 @@ def test_series_dt_accessors() -> None:
428431
)
429432
check(assert_type(s0.dt.month_name(), "pd.Series[str]"), pd.Series, str)
430433
check(assert_type(s0.dt.day_name(), "pd.Series[str]"), pd.Series, str)
434+
check(assert_type(s0.dt.unit, TimeUnit), str)
435+
check(assert_type(s0.dt.as_unit("s"), "TimestampSeries"), pd.Series, pd.Timestamp)
436+
check(assert_type(s0.dt.as_unit("ms"), "TimestampSeries"), pd.Series, pd.Timestamp)
437+
check(assert_type(s0.dt.as_unit("us"), "TimestampSeries"), pd.Series, pd.Timestamp)
438+
check(assert_type(s0.dt.as_unit("ns"), "TimestampSeries"), pd.Series, pd.Timestamp)
431439

432440
i1 = pd.period_range(start="2022-06-01", periods=10)
433441

@@ -455,6 +463,35 @@ def test_series_dt_accessors() -> None:
455463
check(assert_type(s2.dt.components, pd.DataFrame), pd.DataFrame)
456464
check(assert_type(s2.dt.to_pytimedelta(), np.ndarray), np.ndarray)
457465
check(assert_type(s2.dt.total_seconds(), "pd.Series[float]"), pd.Series, float)
466+
check(assert_type(s2.dt.unit, TimeUnit), str)
467+
check(assert_type(s2.dt.as_unit("s"), "TimedeltaSeries"), pd.Series, pd.Timedelta)
468+
check(assert_type(s2.dt.as_unit("ms"), "TimedeltaSeries"), pd.Series, pd.Timedelta)
469+
check(assert_type(s2.dt.as_unit("us"), "TimedeltaSeries"), pd.Series, pd.Timedelta)
470+
check(assert_type(s2.dt.as_unit("ns"), "TimedeltaSeries"), pd.Series, pd.Timedelta)
471+
472+
# Checks for general Series other than TimestampSeries and TimedeltaSeries
473+
474+
s4 = cast(
475+
"pd.Series[pd.Timestamp]",
476+
pd.Series([pd.Timestamp("2024-01-01"), pd.Timestamp("2024-01-02")]),
477+
)
478+
479+
check(assert_type(s4.dt.unit, TimeUnit), str)
480+
check(assert_type(s4.dt.as_unit("s"), pd.Series), pd.Series, pd.Timestamp)
481+
check(assert_type(s4.dt.as_unit("ms"), pd.Series), pd.Series, pd.Timestamp)
482+
check(assert_type(s4.dt.as_unit("us"), pd.Series), pd.Series, pd.Timestamp)
483+
check(assert_type(s4.dt.as_unit("ns"), pd.Series), pd.Series, pd.Timestamp)
484+
485+
s5 = cast(
486+
"pd.Series[pd.Timedelta]",
487+
pd.Series([pd.Timedelta("1 day"), pd.Timedelta("2 days")]),
488+
)
489+
490+
check(assert_type(s5.dt.unit, TimeUnit), str)
491+
check(assert_type(s5.dt.as_unit("s"), pd.Series), pd.Series, pd.Timedelta)
492+
check(assert_type(s5.dt.as_unit("ms"), pd.Series), pd.Series, pd.Timedelta)
493+
check(assert_type(s5.dt.as_unit("us"), pd.Series), pd.Series, pd.Timedelta)
494+
check(assert_type(s5.dt.as_unit("ns"), pd.Series), pd.Series, pd.Timedelta)
458495

459496

460497
def test_datetimeindex_accessors() -> None:
@@ -522,6 +559,11 @@ def test_datetimeindex_accessors() -> None:
522559
check(assert_type(i0.month_name(), pd.Index), pd.Index, str)
523560
check(assert_type(i0.day_name(), pd.Index), pd.Index, str)
524561
check(assert_type(i0.is_normalized, bool), bool)
562+
check(assert_type(i0.unit, TimeUnit), str)
563+
check(assert_type(i0.as_unit("s"), pd.DatetimeIndex), pd.DatetimeIndex)
564+
check(assert_type(i0.as_unit("ms"), pd.DatetimeIndex), pd.DatetimeIndex)
565+
check(assert_type(i0.as_unit("us"), pd.DatetimeIndex), pd.DatetimeIndex)
566+
check(assert_type(i0.as_unit("ns"), pd.DatetimeIndex), pd.DatetimeIndex)
525567

526568

527569
def test_timedeltaindex_accessors() -> None:
@@ -542,6 +584,11 @@ def test_timedeltaindex_accessors() -> None:
542584
assert_type(i0.floor("D"), pd.TimedeltaIndex), pd.TimedeltaIndex, pd.Timedelta
543585
)
544586
check(assert_type(i0.ceil("D"), pd.TimedeltaIndex), pd.TimedeltaIndex, pd.Timedelta)
587+
check(assert_type(i0.unit, TimeUnit), str)
588+
check(assert_type(i0.as_unit("s"), pd.TimedeltaIndex), pd.TimedeltaIndex)
589+
check(assert_type(i0.as_unit("ms"), pd.TimedeltaIndex), pd.TimedeltaIndex)
590+
check(assert_type(i0.as_unit("us"), pd.TimedeltaIndex), pd.TimedeltaIndex)
591+
check(assert_type(i0.as_unit("ns"), pd.TimedeltaIndex), pd.TimedeltaIndex)
545592

546593

547594
def test_periodindex_accessors() -> None:

0 commit comments

Comments
 (0)