Skip to content

Commit 56bcec7

Browse files
bashtageKevin Sheppard
and
Kevin Sheppard
authored
ENH: Improve typing for Timestamp (#389)
* ENH: Improve typing for Timestamp * MAINT: Add dep for testing * ENH: Improve fold * REV: Revert Literal due to violation of Liskov subst. princip. * TST: Add type tests for series * CLN: Clean testing code * ENH: Further refinements of timestamp and tests * BUG: Correct new * Final types and tests for timestamp * REV: Remove mypy coverage changes * CLN: Final clean * CLN: Final clean Co-authored-by: Kevin Sheppard <[email protected]>
1 parent 001c0af commit 56bcec7

File tree

3 files changed

+722
-45
lines changed

3 files changed

+722
-45
lines changed

pandas-stubs/_libs/tslibs/timestamps.pyi

+112-40
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,53 @@ from datetime import (
88
from time import struct_time
99
from typing import (
1010
ClassVar,
11+
Literal,
1112
TypeVar,
13+
Union,
1214
overload,
1315
)
1416

1517
import numpy as np
16-
from pandas import Index
18+
from pandas import (
19+
DatetimeIndex,
20+
Index,
21+
TimedeltaIndex,
22+
)
1723
from pandas.core.series import (
1824
Series,
25+
TimedeltaSeries,
1926
TimestampSeries,
2027
)
28+
from typing_extensions import TypeAlias
2129

2230
from pandas._libs.tslibs import (
2331
BaseOffset,
2432
Period,
2533
Tick,
2634
Timedelta,
2735
)
28-
from pandas._typing import np_ndarray_bool
36+
from pandas._typing import (
37+
np_ndarray_bool,
38+
npt,
39+
)
2940

3041
_DatetimeT = TypeVar("_DatetimeT", bound=datetime)
31-
32-
def integer_op_not_supported(obj: object) -> TypeError: ...
42+
_Ambiguous: TypeAlias = Union[bool, Literal["raise", "NaT"]]
43+
_Nonexistent: TypeAlias = Union[
44+
Literal["raise", "NaT", "shift_backward", "shift_forward"], Timedelta, timedelta
45+
]
3346

3447
class Timestamp(datetime):
3548
min: ClassVar[Timestamp]
3649
max: ClassVar[Timestamp]
3750

3851
resolution: ClassVar[Timedelta]
39-
value: int # np.int64
52+
value: int
4053
def __new__(
4154
cls: type[_DatetimeT],
4255
ts_input: np.integer | float | str | _date | datetime | np.datetime64 = ...,
56+
# Freq is deprecated but is left in to allow code like Timestamp(2000,1,1)
57+
# Removing it would make the other arguments position only
4358
freq: int | str | BaseOffset | None = ...,
4459
tz: str | _tzinfo | int | None = ...,
4560
unit: str | int | None = ...,
@@ -53,7 +68,7 @@ class Timestamp(datetime):
5368
nanosecond: int | None = ...,
5469
tzinfo: _tzinfo | None = ...,
5570
*,
56-
fold: int | None = ...,
71+
fold: Literal[0, 1] | None = ...,
5772
) -> _DatetimeT: ...
5873
# GH 46171
5974
# While Timestamp can return pd.NaT, having the constructor return
@@ -73,14 +88,16 @@ class Timestamp(datetime):
7388
@property
7489
def microsecond(self) -> int: ...
7590
@property
91+
def nanosecond(self) -> int: ...
92+
@property
7693
def tzinfo(self) -> _tzinfo | None: ...
7794
@property
7895
def tz(self) -> _tzinfo | None: ...
7996
@property
8097
def fold(self) -> int: ...
8198
@classmethod
8299
def fromtimestamp(
83-
cls: type[_DatetimeT], t: float, tz: _tzinfo | None = ...
100+
cls: type[_DatetimeT], t: float, tz: _tzinfo | str | None = ...
84101
) -> _DatetimeT: ...
85102
@classmethod
86103
def utcfromtimestamp(cls: type[_DatetimeT], ts: float) -> _DatetimeT: ...
@@ -90,7 +107,8 @@ class Timestamp(datetime):
90107
def fromordinal(
91108
cls: type[_DatetimeT],
92109
ordinal: int,
93-
freq: str | BaseOffset | None = ...,
110+
# freq produces a FutureWarning about being deprecated in a future version
111+
freq: None = ...,
94112
tz: _tzinfo | str | None = ...,
95113
) -> _DatetimeT: ...
96114
@classmethod
@@ -111,17 +129,21 @@ class Timestamp(datetime):
111129
def date(self) -> _date: ...
112130
def time(self) -> _time: ...
113131
def timetz(self) -> _time: ...
114-
def replace(
132+
# Override since fold is more precise than datetime.replace(fold:int)
133+
# Here it is restricted to be 0 or 1 using a Literal
134+
# Violation of Liskov substitution principle
135+
def replace( # type:ignore[override]
115136
self,
116-
year: int = ...,
117-
month: int = ...,
118-
day: int = ...,
119-
hour: int = ...,
120-
minute: int = ...,
121-
second: int = ...,
122-
microsecond: int = ...,
137+
year: int | None = ...,
138+
month: int | None = ...,
139+
day: int | None = ...,
140+
hour: int | None = ...,
141+
minute: int | None = ...,
142+
second: int | None = ...,
143+
microsecond: int | None = ...,
123144
tzinfo: _tzinfo | None = ...,
124-
fold: int = ...,
145+
*,
146+
fold: Literal[0, 1] | None = ...,
125147
) -> Timestamp: ...
126148
def astimezone(self: _DatetimeT, tz: _tzinfo | None = ...) -> _DatetimeT: ...
127149
def ctime(self) -> str: ...
@@ -131,45 +153,86 @@ class Timestamp(datetime):
131153
def utcoffset(self) -> timedelta | None: ...
132154
def tzname(self) -> str | None: ...
133155
def dst(self) -> timedelta | None: ...
156+
# Mypy complains Forward operator "<inequality op>" is not callable, so ignore misc
157+
# for le, lt ge and gt
134158
@overload # type: ignore[override]
135-
def __le__(self, other: datetime) -> bool: ...
159+
def __le__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc]
136160
@overload
137-
def __le__(self, other: Index) -> np_ndarray_bool: ...
161+
def __le__(self, other: Index | npt.NDArray[np.datetime64]) -> np_ndarray_bool: ...
138162
@overload
139-
def __le__(self, other: TimestampSeries) -> Series[bool]: ...
163+
def __le__(self, other: TimestampSeries | Series[Timestamp]) -> Series[bool]: ...
140164
@overload # type: ignore[override]
141-
def __lt__(self, other: datetime) -> bool: ...
165+
def __lt__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc]
142166
@overload
143-
def __lt__(self, other: Index) -> np_ndarray_bool: ...
167+
def __lt__(self, other: Index | npt.NDArray[np.datetime64]) -> np_ndarray_bool: ...
144168
@overload
145-
def __lt__(self, other: TimestampSeries) -> Series[bool]: ...
169+
def __lt__(self, other: TimestampSeries | Series[Timestamp]) -> Series[bool]: ...
146170
@overload # type: ignore[override]
147-
def __ge__(self, other: datetime) -> bool: ...
171+
def __ge__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc]
148172
@overload
149-
def __ge__(self, other: Index) -> np_ndarray_bool: ...
173+
def __ge__(self, other: Index | npt.NDArray[np.datetime64]) -> np_ndarray_bool: ...
150174
@overload
151-
def __ge__(self, other: TimestampSeries) -> Series[bool]: ...
175+
def __ge__(self, other: TimestampSeries | Series[Timestamp]) -> Series[bool]: ...
152176
@overload # type: ignore[override]
153-
def __gt__(self, other: datetime) -> bool: ...
177+
def __gt__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc]
154178
@overload
155-
def __gt__(self, other: Index) -> np_ndarray_bool: ...
179+
def __gt__(self, other: Index | npt.NDArray[np.datetime64]) -> np_ndarray_bool: ...
156180
@overload
157-
def __gt__(self, other: TimestampSeries) -> Series[bool]: ...
181+
def __gt__(self, other: TimestampSeries | Series[Timestamp]) -> Series[bool]: ...
158182
# error: Signature of "__add__" incompatible with supertype "date"/"datetime"
159183
@overload # type: ignore[override]
160-
def __add__(self, other: np.ndarray) -> np.ndarray: ...
184+
def __add__(
185+
self, other: npt.NDArray[np.timedelta64]
186+
) -> npt.NDArray[np.datetime64]: ...
161187
@overload
162188
def __add__(
163189
self: _DatetimeT, other: timedelta | np.timedelta64 | Tick
164190
) -> _DatetimeT: ...
191+
@overload
192+
def __add__(
193+
self, other: TimedeltaSeries | Series[Timedelta]
194+
) -> TimestampSeries: ...
195+
@overload
196+
def __add__(self, other: TimedeltaIndex) -> DatetimeIndex: ...
197+
@overload
165198
def __radd__(self: _DatetimeT, other: timedelta) -> _DatetimeT: ...
199+
@overload
200+
def __radd__(self, other: TimedeltaIndex) -> DatetimeIndex: ...
201+
@overload
202+
def __radd__(
203+
self, other: npt.NDArray[np.timedelta64]
204+
) -> npt.NDArray[np.datetime64]: ...
205+
# TODO: test dt64
166206
@overload # type: ignore[override]
167-
def __sub__(self, other: datetime) -> Timedelta: ...
207+
def __sub__(self, other: Timestamp | datetime | np.datetime64) -> Timedelta: ...
168208
@overload
169209
def __sub__(
170210
self: _DatetimeT, other: timedelta | np.timedelta64 | Tick
171211
) -> _DatetimeT: ...
172-
def __hash__(self) -> int: ...
212+
@overload
213+
def __sub__(self, other: TimedeltaIndex) -> DatetimeIndex: ...
214+
@overload
215+
def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ...
216+
@overload
217+
def __sub__(
218+
self, other: npt.NDArray[np.timedelta64]
219+
) -> npt.NDArray[np.datetime64]: ...
220+
@overload
221+
def __eq__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc]
222+
@overload
223+
def __eq__(self, other: TimestampSeries | Series[Timestamp]) -> Series[bool]: ... # type: ignore[misc]
224+
@overload
225+
def __eq__(self, other: npt.NDArray[np.datetime64] | Index) -> np_ndarray_bool: ... # type: ignore[misc]
226+
@overload
227+
def __eq__(self, other: object) -> Literal[False]: ...
228+
@overload
229+
def __ne__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc]
230+
@overload
231+
def __ne__(self, other: TimestampSeries | Series[Timestamp]) -> Series[bool]: ... # type: ignore[misc]
232+
@overload
233+
def __ne__(self, other: npt.NDArray[np.datetime64] | Index) -> np_ndarray_bool: ... # type: ignore[misc]
234+
@overload
235+
def __ne__(self, other: object) -> Literal[True]: ...
173236
def weekday(self) -> int: ...
174237
def isoweekday(self) -> int: ...
175238
def isocalendar(self) -> tuple[int, int, int]: ...
@@ -198,19 +261,28 @@ class Timestamp(datetime):
198261
def tz_localize(
199262
self: _DatetimeT,
200263
tz: _tzinfo | str | None,
201-
ambiguous: str = ...,
202-
nonexistent: str = ...,
264+
ambiguous: _Ambiguous = ...,
265+
nonexistent: _Nonexistent = ...,
203266
) -> _DatetimeT: ...
204267
def normalize(self: _DatetimeT) -> _DatetimeT: ...
205268
# TODO: round/floor/ceil could return NaT?
206269
def round(
207-
self: _DatetimeT, freq: str, ambiguous: bool | str = ..., nonexistent: str = ...
270+
self: _DatetimeT,
271+
freq: str,
272+
ambiguous: _Ambiguous = ...,
273+
nonexistent: _Nonexistent = ...,
208274
) -> _DatetimeT: ...
209275
def floor(
210-
self: _DatetimeT, freq: str, ambiguous: bool | str = ..., nonexistent: str = ...
276+
self: _DatetimeT,
277+
freq: str,
278+
ambiguous: _Ambiguous = ...,
279+
nonexistent: _Nonexistent = ...,
211280
) -> _DatetimeT: ...
212281
def ceil(
213-
self: _DatetimeT, freq: str, ambiguous: bool | str = ..., nonexistent: str = ...
282+
self: _DatetimeT,
283+
freq: str,
284+
ambiguous: _Ambiguous = ...,
285+
nonexistent: _Nonexistent = ...,
214286
) -> _DatetimeT: ...
215287
def day_name(self, locale: str | None = ...) -> str: ...
216288
def month_name(self, locale: str | None = ...) -> str: ...
@@ -223,12 +295,12 @@ class Timestamp(datetime):
223295
@property
224296
def dayofyear(self) -> int: ...
225297
@property
298+
def weekofyear(self) -> int: ...
299+
@property
226300
def quarter(self) -> int: ...
227301
@property
228302
def week(self) -> int: ...
229-
def to_numpy(
230-
self, dtype: np.dtype | None = ..., copy: bool = ...
231-
) -> np.datetime64: ...
303+
def to_numpy(self) -> np.datetime64: ...
232304
@property
233305
def days_in_month(self) -> int: ...
234306
@property

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ tabulate = ">=0.8.10"
5858
jinja2 = "^3.1"
5959
scipy = ">=1.9.1"
6060
SQLAlchemy = "^1.4.41"
61+
types-python-dateutil = ">=2.8.19"
6162

6263
[build-system]
6364
requires = ["poetry-core>=1.0.0"]

0 commit comments

Comments
 (0)