From 73d34441413d2f5a5bf92f1d576404744ec26628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbyszek=20Kr=C3=B3likowski?= <63653095+zkrolikowski-vl@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:33:20 +0200 Subject: [PATCH] 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 --- tests/snippets/test_timestamp.py | 18 +++++++ .../3/pandas/_libs/tslibs/timedeltas.pyi | 2 +- .../3/pandas/_libs/tslibs/timestamps.pyi | 53 +++++++++++++------ 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/tests/snippets/test_timestamp.py b/tests/snippets/test_timestamp.py index 493fede..aea4662 100644 --- a/tests/snippets/test_timestamp.py +++ b/tests/snippets/test_timestamp.py @@ -3,6 +3,7 @@ import pandas as pd import datetime as dt + def test_types_init() -> None: ts: pd.Timestamp = pd.Timestamp('2021-03-01T12') ts1: pd.Timestamp = pd.Timestamp(dt.date(2021, 3, 15)) @@ -14,3 +15,20 @@ def test_types_init() -> None: ts7: pd.Timestamp = pd.Timestamp(2021, 3, 10, 12) ts8: pd.Timestamp = pd.Timestamp(year=2021, month=3, day=10, hour=12) ts9: pd.Timestamp = pd.Timestamp(year=2021, month=3, day=10, hour=12, tz='US/Pacific') + + +def test_types_arithmetic() -> None: + ts: pd.Timestamp = pd.to_datetime("2021-03-01") + ts2: pd.Timestamp = pd.to_datetime("2021-01-01") + delta: pd.Timedelta = pd.to_timedelta("1 day") + + tsr: pd.Timedelta = ts - ts2 + tsr2: pd.Timestamp = ts + delta + + +def test_types_comparison() -> None: + ts: pd.Timestamp = pd.to_datetime("2021-03-01") + ts2: pd.Timestamp = pd.to_datetime("2021-01-01") + + tsr: bool = ts < ts2 + tsr2: bool = ts > ts2 diff --git a/third_party/3/pandas/_libs/tslibs/timedeltas.pyi b/third_party/3/pandas/_libs/tslibs/timedeltas.pyi index a218ed9..b04fd93 100644 --- a/third_party/3/pandas/_libs/tslibs/timedeltas.pyi +++ b/third_party/3/pandas/_libs/tslibs/timedeltas.pyi @@ -2,6 +2,6 @@ from datetime import timedelta from typing import Any, Union import numpy as np -class Timedelta: +class Timedelta(timedelta): def __init__(self, value: Union[Timedelta, timedelta, np.timedelta64, str, int], unit: str = ..., **kwargs: Any) -> None: ... \ No newline at end of file diff --git a/third_party/3/pandas/_libs/tslibs/timestamps.pyi b/third_party/3/pandas/_libs/tslibs/timestamps.pyi index 66dca41..d282c2c 100644 --- a/third_party/3/pandas/_libs/tslibs/timestamps.pyi +++ b/third_party/3/pandas/_libs/tslibs/timestamps.pyi @@ -1,5 +1,11 @@ +""" +This class could be comprehensibly typed with the typeshed .pyi file but the +datatetime.datetime and pd.Timestamp API differ quite a bit and cannot be used interchangeably +""" import sys -from typing import Any, Optional, Union, overload +from typing import Any, Optional, Union, overload, TypeVar, Type + +from pandas._libs.tslibs.timedeltas import Timedelta if sys.version_info >= (3, 8): from typing import Literal @@ -7,7 +13,7 @@ else: from typing_extensions import Literal from dateutil.tz import tzfile -from datetime import tzinfo, date +from datetime import tzinfo, datetime, date as _date, time, timedelta from pandas._libs.tslibs.period import Period from pandas._typing import TimestampConvertible @@ -16,35 +22,52 @@ OptInt = Optional[int] Fold = Literal[0, 1] -class Timestamp: +_S = TypeVar("_S") + +class Timestamp(datetime): @overload - def __init__(self, ts_input: TimestampConvertible, freq: Optional[str] = ..., tz: Optional[Union[str, tzinfo, tzfile]] = ..., unit: Optional[str] = ..., tzinfo: Optional[tzinfo] = ..., fold: Optional[Fold] = ...): ... + def __init__(self, ts_input: TimestampConvertible, freq: Optional[str] = ..., tz: Optional[Union[str, tzinfo, tzfile]] = ..., + unit: Optional[str] = ..., tzinfo: Optional[tzinfo] = ..., fold: Optional[Fold] = ...): ... @overload - 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] = ...): ... + 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] = ...): ... def to_period(self, freq: Optional[str]) -> Period: ... def to_julian_date(self) -> float: ... def tz_localize(self, tz: Any = ..., ambigious: Any = ..., nonexistent: Any = ...) -> Timestamp: ... def tz_convert(self, tz: Any) -> Timestamp: ... - def astimezone(self, tz: Any) -> Timestamp: ... - 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: ... + @overload # type: ignore[override] + def __sub__(self, other: datetime) -> Timedelta: ... + @overload + def __sub__(self, other: timedelta) -> Timestamp: ... + def __add__(self, other: timedelta) -> Timestamp: ... + if sys.version_info >= (3, 8): + def astimezone(self: _S, tz: Optional[tzinfo] = ...) -> _S: ... + else: + def astimezone(self, tz: Optional[tzinfo] = ...) -> datetime: ... + # This is correct. Typeshed doesn't include Optionals, or the Literal correctly + def replace(self, year: OptInt = ..., month: OptInt = ..., # type: ignore[override] + day: OptInt = ..., hour: OptInt = ..., minute: OptInt = ..., + second: OptInt = ..., microsecond: OptInt = ..., nanosecond: OptInt = ..., + tzinfo: Optional[tzinfo] = ..., *, fold: Fold = ...) -> datetime: ... def round(self, freq: str, ambiguous: Any = ..., nonexistent: Any = ...) -> Timestamp: ... - def ceil(self, freq: str, ambiguous: Any = ...,nonexistent: Any = ...) -> Timestamp: ... - def floor(self, freq: str, ambiguous: Any = ...,nonexistent: Any = ...) -> Timestamp: ... - def isoformat(self, sep: str = ...) -> str: ... + def ceil(self, freq: str, ambiguous: Any = ..., nonexistent: Any = ...) -> Timestamp: ... + def floor(self, freq: str, ambiguous: Any = ..., nonexistent: Any = ...) -> Timestamp: ... + def isoformat(self, sep: str = ..., timespec: str = ...) -> str: ... def day_name(self, locale: Optional[str]) -> str: ... def month_name(self, locale: Optional[str]) -> str: ... def normalize(self) -> Timestamp: ... def strftime(self, format: str) -> str: ... - def date(self) -> date: ... - + def date(self) -> _date: ... @classmethod def utcnow(cls) -> Timestamp: ... @classmethod - def utcfromtimestamp(cls, ts: Timestamp) -> Timestamp: ... + def utcfromtimestamp(cls, ts: float) -> Timestamp: ... + # Pandas doesn't accept timezone here, unlike datetime @classmethod - def fromtimestamp(cls, ts: Timestamp) -> Timestamp: ... + def fromtimestamp(cls: Type[_S], t: float) -> _S: ... # type: ignore[override] @classmethod - def combine(cls, date: Any, time: Any) -> Timestamp: ... + def combine(cls, date: _date, time: time, tzinfo: Optional[tzinfo] = ...) -> Timestamp: ... @classmethod def now(cls, tz: Optional[Any] = ...) -> Timestamp: ... @classmethod