Skip to content

Commit 73d3444

Browse files
Make pandas types inherit from datetime types (#77)
* Inheritance, still a couple of problems * Timestamps and timedeltas now inherit from datetime classes * Fix an issue with tox not recognizing Python 3.6 and 3.7 versions * Go back to previous tox configuration
1 parent 91270b4 commit 73d3444

File tree

3 files changed

+57
-16
lines changed

3 files changed

+57
-16
lines changed

tests/snippets/test_timestamp.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pandas as pd
44
import datetime as dt
55

6+
67
def test_types_init() -> None:
78
ts: pd.Timestamp = pd.Timestamp('2021-03-01T12')
89
ts1: pd.Timestamp = pd.Timestamp(dt.date(2021, 3, 15))
@@ -14,3 +15,20 @@ def test_types_init() -> None:
1415
ts7: pd.Timestamp = pd.Timestamp(2021, 3, 10, 12)
1516
ts8: pd.Timestamp = pd.Timestamp(year=2021, month=3, day=10, hour=12)
1617
ts9: pd.Timestamp = pd.Timestamp(year=2021, month=3, day=10, hour=12, tz='US/Pacific')
18+
19+
20+
def test_types_arithmetic() -> None:
21+
ts: pd.Timestamp = pd.to_datetime("2021-03-01")
22+
ts2: pd.Timestamp = pd.to_datetime("2021-01-01")
23+
delta: pd.Timedelta = pd.to_timedelta("1 day")
24+
25+
tsr: pd.Timedelta = ts - ts2
26+
tsr2: pd.Timestamp = ts + delta
27+
28+
29+
def test_types_comparison() -> None:
30+
ts: pd.Timestamp = pd.to_datetime("2021-03-01")
31+
ts2: pd.Timestamp = pd.to_datetime("2021-01-01")
32+
33+
tsr: bool = ts < ts2
34+
tsr2: bool = ts > ts2

third_party/3/pandas/_libs/tslibs/timedeltas.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ from datetime import timedelta
22
from typing import Any, Union
33
import numpy as np
44

5-
class Timedelta:
5+
class Timedelta(timedelta):
66

77
def __init__(self, value: Union[Timedelta, timedelta, np.timedelta64, str, int], unit: str = ..., **kwargs: Any) -> None: ...

third_party/3/pandas/_libs/tslibs/timestamps.pyi

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1+
"""
2+
This class could be comprehensibly typed with the typeshed .pyi file but the
3+
datatetime.datetime and pd.Timestamp API differ quite a bit and cannot be used interchangeably
4+
"""
15
import sys
2-
from typing import Any, Optional, Union, overload
6+
from typing import Any, Optional, Union, overload, TypeVar, Type
7+
8+
from pandas._libs.tslibs.timedeltas import Timedelta
39

410
if sys.version_info >= (3, 8):
511
from typing import Literal
612
else:
713
from typing_extensions import Literal
814

915
from dateutil.tz import tzfile
10-
from datetime import tzinfo, date
16+
from datetime import tzinfo, datetime, date as _date, time, timedelta
1117

1218
from pandas._libs.tslibs.period import Period
1319
from pandas._typing import TimestampConvertible
@@ -16,35 +22,52 @@ OptInt = Optional[int]
1622

1723
Fold = Literal[0, 1]
1824

19-
class Timestamp:
25+
_S = TypeVar("_S")
26+
27+
class Timestamp(datetime):
2028
@overload
21-
def __init__(self, ts_input: TimestampConvertible, freq: Optional[str] = ..., tz: Optional[Union[str, tzinfo, tzfile]] = ..., unit: Optional[str] = ..., tzinfo: Optional[tzinfo] = ..., fold: Optional[Fold] = ...): ...
29+
def __init__(self, ts_input: TimestampConvertible, freq: Optional[str] = ..., tz: Optional[Union[str, tzinfo, tzfile]] = ...,
30+
unit: Optional[str] = ..., tzinfo: Optional[tzinfo] = ..., fold: Optional[Fold] = ...): ...
2231
@overload
23-
def __init__(self, year: OptInt = ..., month: OptInt = ..., day: OptInt = ..., hour: OptInt = ..., minute: OptInt = ..., second: OptInt = ..., microsecond: OptInt = ..., nanosecond: OptInt = ..., tz: Optional[Union[str, tzinfo, tzfile]] = ..., tzinfo: Optional[tzinfo] = ..., fold: Optional[Fold] = ..., freq: Optional[str] = ...): ...
32+
def __init__(self, year: OptInt = ..., month: OptInt = ..., day: OptInt = ..., hour: OptInt = ..., minute: OptInt = ...,
33+
second: OptInt = ..., microsecond: OptInt = ..., nanosecond: OptInt = ..., tz: Optional[Union[str, tzinfo, tzfile]] = ...,
34+
tzinfo: Optional[tzinfo] = ..., fold: Optional[Fold] = ..., freq: Optional[str] = ...): ...
2435
def to_period(self, freq: Optional[str]) -> Period: ...
2536
def to_julian_date(self) -> float: ...
2637
def tz_localize(self, tz: Any = ..., ambigious: Any = ..., nonexistent: Any = ...) -> Timestamp: ...
2738
def tz_convert(self, tz: Any) -> Timestamp: ...
28-
def astimezone(self, tz: Any) -> Timestamp: ...
29-
def replace(self, year: OptInt = ..., month: OptInt = ..., day: OptInt = ..., hour: OptInt = ..., minute: OptInt = ..., second: OptInt = ..., microsecond: OptInt = ..., nanosecond: OptInt = ..., tzinfo: Optional[tzinfo] = ..., fold: Fold = ...) -> Timestamp: ...
39+
@overload # type: ignore[override]
40+
def __sub__(self, other: datetime) -> Timedelta: ...
41+
@overload
42+
def __sub__(self, other: timedelta) -> Timestamp: ...
43+
def __add__(self, other: timedelta) -> Timestamp: ...
44+
if sys.version_info >= (3, 8):
45+
def astimezone(self: _S, tz: Optional[tzinfo] = ...) -> _S: ...
46+
else:
47+
def astimezone(self, tz: Optional[tzinfo] = ...) -> datetime: ...
48+
# This is correct. Typeshed doesn't include Optionals, or the Literal correctly
49+
def replace(self, year: OptInt = ..., month: OptInt = ..., # type: ignore[override]
50+
day: OptInt = ..., hour: OptInt = ..., minute: OptInt = ...,
51+
second: OptInt = ..., microsecond: OptInt = ..., nanosecond: OptInt = ...,
52+
tzinfo: Optional[tzinfo] = ..., *, fold: Fold = ...) -> datetime: ...
3053
def round(self, freq: str, ambiguous: Any = ..., nonexistent: Any = ...) -> Timestamp: ...
31-
def ceil(self, freq: str, ambiguous: Any = ...,nonexistent: Any = ...) -> Timestamp: ...
32-
def floor(self, freq: str, ambiguous: Any = ...,nonexistent: Any = ...) -> Timestamp: ...
33-
def isoformat(self, sep: str = ...) -> str: ...
54+
def ceil(self, freq: str, ambiguous: Any = ..., nonexistent: Any = ...) -> Timestamp: ...
55+
def floor(self, freq: str, ambiguous: Any = ..., nonexistent: Any = ...) -> Timestamp: ...
56+
def isoformat(self, sep: str = ..., timespec: str = ...) -> str: ...
3457
def day_name(self, locale: Optional[str]) -> str: ...
3558
def month_name(self, locale: Optional[str]) -> str: ...
3659
def normalize(self) -> Timestamp: ...
3760
def strftime(self, format: str) -> str: ...
38-
def date(self) -> date: ...
39-
61+
def date(self) -> _date: ...
4062
@classmethod
4163
def utcnow(cls) -> Timestamp: ...
4264
@classmethod
43-
def utcfromtimestamp(cls, ts: Timestamp) -> Timestamp: ...
65+
def utcfromtimestamp(cls, ts: float) -> Timestamp: ...
66+
# Pandas doesn't accept timezone here, unlike datetime
4467
@classmethod
45-
def fromtimestamp(cls, ts: Timestamp) -> Timestamp: ...
68+
def fromtimestamp(cls: Type[_S], t: float) -> _S: ... # type: ignore[override]
4669
@classmethod
47-
def combine(cls, date: Any, time: Any) -> Timestamp: ...
70+
def combine(cls, date: _date, time: time, tzinfo: Optional[tzinfo] = ...) -> Timestamp: ...
4871
@classmethod
4972
def now(cls, tz: Optional[Any] = ...) -> Timestamp: ...
5073
@classmethod

0 commit comments

Comments
 (0)