Skip to content

Commit

Permalink
Timestamp -> datetime and Timedelta -> timedelta (#841)
Browse files Browse the repository at this point in the history
* Timestamp -> datetime and Timedelta -> timedelta

* keep redundancy

* test

* redundancy
  • Loading branch information
twoertwein committed Dec 26, 2023
1 parent 0a567b4 commit 52384d3
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 36 deletions.
2 changes: 1 addition & 1 deletion pandas-stubs/_libs/tslibs/timedeltas.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ class Timedelta(timedelta):
@overload
def __rsub__(self, other: timedelta | Timedelta | np.timedelta64) -> Timedelta: ...
@overload
def __rsub__(self, other: Timestamp | np.datetime64) -> Timestamp: ...
def __rsub__(self, other: dt.datetime | Timestamp | np.datetime64) -> Timestamp: ... # type: ignore[misc]
@overload
def __rsub__(self, other: NaTType) -> NaTType: ...
@overload
Expand Down
21 changes: 10 additions & 11 deletions pandas-stubs/core/frame.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ from collections.abc import (
MutableMapping,
Sequence,
)
import datetime
import datetime as _dt
import datetime as dt
from re import Pattern
from typing import (
Any,
Expand Down Expand Up @@ -373,7 +372,7 @@ class DataFrame(NDFrame, OpsMixin):
convert_dates: dict[HashableT1, StataDateFormat] | None = ...,
write_index: _bool = ...,
byteorder: Literal["<", ">", "little", "big"] | None = ...,
time_stamp: _dt.datetime | None = ...,
time_stamp: dt.datetime | None = ...,
data_label: _str | None = ...,
variable_labels: dict[HashableT2, str] | None = ...,
version: Literal[114, 117, 118, 119] | None = ...,
Expand Down Expand Up @@ -1565,14 +1564,14 @@ class DataFrame(NDFrame, OpsMixin):
) -> DataFrame: ...
def at_time(
self,
time: _str | datetime.time,
time: _str | dt.time,
asof: _bool = ...,
axis: Axis | None = ...,
) -> DataFrame: ...
def between_time(
self,
start_time: _str | datetime.time,
end_time: _str | datetime.time,
start_time: _str | dt.time,
end_time: _str | dt.time,
axis: Axis | None = ...,
) -> DataFrame: ...
@overload
Expand Down Expand Up @@ -1941,7 +1940,7 @@ class DataFrame(NDFrame, OpsMixin):
level: Level | None = ...,
origin: Timestamp
| Literal["epoch", "start", "start_day", "end", "end_day"] = ...,
offset: Timedelta | _str | None = ...,
offset: dt.timedelta | Timedelta | _str | None = ...,
group_keys: _bool = ...,
) -> Resampler[DataFrame]: ...
def rfloordiv(
Expand All @@ -1968,7 +1967,7 @@ class DataFrame(NDFrame, OpsMixin):
@overload
def rolling(
self,
window: int | str | _dt.timedelta | BaseOffset | BaseIndexer,
window: int | str | dt.timedelta | BaseOffset | BaseIndexer,
min_periods: int | None = ...,
center: _bool = ...,
on: Hashable | None = ...,
Expand All @@ -1982,7 +1981,7 @@ class DataFrame(NDFrame, OpsMixin):
@overload
def rolling(
self,
window: int | str | _dt.timedelta | BaseOffset | BaseIndexer,
window: int | str | dt.timedelta | BaseOffset | BaseIndexer,
min_periods: int | None = ...,
center: _bool = ...,
on: Hashable | None = ...,
Expand Down Expand Up @@ -2217,8 +2216,8 @@ class DataFrame(NDFrame, OpsMixin):
) -> DataFrame: ...
def truncate(
self,
before: datetime.date | _str | int | None = ...,
after: datetime.date | _str | int | None = ...,
before: dt.date | _str | int | None = ...,
after: dt.date | _str | int | None = ...,
axis: Axis | None = ...,
copy: _bool = ...,
) -> DataFrame: ...
Expand Down
9 changes: 8 additions & 1 deletion pandas-stubs/core/indexes/accessors.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import datetime as dt
from datetime import tzinfo
from datetime import (
timedelta,
tzinfo,
)
from typing import (
Generic,
Literal,
Expand Down Expand Up @@ -166,20 +169,23 @@ class _DatetimeRoundingMethods(Generic[_DTRoundingMethodReturnType]):
freq: str | BaseOffset | None,
ambiguous: Literal["raise", "infer", "NaT"] | np_ndarray_bool = ...,
nonexistent: Literal["shift_forward", "shift_backward", "NaT", "raise"]
| timedelta
| Timedelta = ...,
) -> _DTRoundingMethodReturnType: ...
def floor(
self,
freq: str | BaseOffset | None,
ambiguous: Literal["raise", "infer", "NaT"] | np_ndarray_bool = ...,
nonexistent: Literal["shift_forward", "shift_backward", "NaT", "raise"]
| timedelta
| Timedelta = ...,
) -> _DTRoundingMethodReturnType: ...
def ceil(
self,
freq: str | BaseOffset | None,
ambiguous: Literal["raise", "infer", "NaT"] | np_ndarray_bool = ...,
nonexistent: Literal["shift_forward", "shift_backward", "NaT", "raise"]
| timedelta
| Timedelta = ...,
) -> _DTRoundingMethodReturnType: ...

Expand All @@ -206,6 +212,7 @@ class _DatetimeLikeNoTZMethods(
tz: tzinfo | str | None,
ambiguous: Literal["raise", "infer", "NaT"] | np_ndarray_bool = ...,
nonexistent: Literal["shift_forward", "shift_backward", "NaT", "raise"]
| timedelta
| Timedelta = ...,
) -> _DTNormalizeReturnType: ...
def tz_convert(self, tz: tzinfo | str | None) -> _DTNormalizeReturnType: ...
Expand Down
13 changes: 10 additions & 3 deletions pandas-stubs/core/indexes/datetimes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ from collections.abc import (
Sequence,
)
from datetime import (
datetime,
timedelta,
tzinfo,
)
Expand Down Expand Up @@ -59,13 +60,19 @@ class DatetimeIndex(DatetimeTimedeltaMixin[Timestamp], DatetimeIndexProperties):
@overload
def __add__(self, other: TimedeltaSeries) -> TimestampSeries: ...
@overload
def __add__(self, other: Timedelta | TimedeltaIndex) -> DatetimeIndex: ...
def __add__(
self, other: timedelta | Timedelta | TimedeltaIndex
) -> DatetimeIndex: ...
@overload
def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ...
@overload
def __sub__(self, other: Timedelta | TimedeltaIndex) -> DatetimeIndex: ...
def __sub__(
self, other: timedelta | Timedelta | TimedeltaIndex
) -> DatetimeIndex: ...
@overload
def __sub__(self, other: Timestamp | DatetimeIndex) -> TimedeltaIndex: ...
def __sub__(
self, other: datetime | Timestamp | DatetimeIndex
) -> TimedeltaIndex: ...
def to_series(self, index=..., name=...) -> TimestampSeries: ...
def snap(self, freq: str = ...): ...
def get_value(self, series, key): ...
Expand Down
6 changes: 3 additions & 3 deletions pandas-stubs/core/indexes/timedeltas.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ class TimedeltaIndex(DatetimeTimedeltaMixin[Timedelta], TimedeltaIndexProperties
@overload
def __add__(self, other: DatetimeIndex) -> DatetimeIndex: ...
@overload
def __add__(self, other: Timedelta | Self) -> Self: ...
def __radd__(self, other: Timestamp | DatetimeIndex) -> DatetimeIndex: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __sub__(self, other: Timedelta | Self) -> Self: ...
def __add__(self, other: dt.timedelta | Timedelta | Self) -> Self: ...
def __radd__(self, other: dt.datetime | Timestamp | DatetimeIndex) -> DatetimeIndex: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __sub__(self, other: dt.timedelta | Timedelta | Self) -> Self: ...
def __mul__(self, other: num) -> Self: ...
@overload # type: ignore[override]
def __truediv__(self, other: num | Sequence[float]) -> Self: ...
Expand Down
5 changes: 3 additions & 2 deletions pandas-stubs/core/reshape/merge.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import timedelta
from typing import (
Literal,
overload,
Expand All @@ -6,9 +7,9 @@ from typing import (
from pandas import (
DataFrame,
Series,
Timedelta,
)

from pandas._libs.tslibs import Timedelta
from pandas._typing import (
AnyArrayLike,
HashableT,
Expand Down Expand Up @@ -99,7 +100,7 @@ def merge_asof(
| tuple[str, str]
| tuple[None, str]
| tuple[str, None] = ...,
tolerance: int | Timedelta | None = ...,
tolerance: int | timedelta | Timedelta | None = ...,
allow_exact_matches: bool = ...,
direction: Literal["backward", "forward", "nearest"] = ...,
) -> DataFrame: ...
23 changes: 15 additions & 8 deletions pandas-stubs/core/series.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1352,9 +1352,10 @@ class Series(IndexOpsMixin[S1], NDFrame):
base: int = ...,
on: _str | None = ...,
level: Level | None = ...,
origin: Timestamp
origin: datetime
| Timestamp
| Literal["epoch", "start", "start_day", "end", "end_day"] = ...,
offset: Timedelta | _str | None = ...,
offset: timedelta | Timedelta | _str | None = ...,
) -> Resampler[Series]: ...
def first(self, offset) -> Series[S1]: ...
def last(self, offset) -> Series[S1]: ...
Expand Down Expand Up @@ -1456,7 +1457,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
def __add__(self, other: S1 | Self) -> Self: ...
@overload
def __add__(
self, other: num | _str | Timedelta | _ListLike | Series | np.timedelta64
self,
other: num | _str | timedelta | Timedelta | _ListLike | Series | np.timedelta64,
) -> Series: ...
# ignore needed for mypy as we want different results based on the arguments
@overload # type: ignore[override]
Expand Down Expand Up @@ -1485,7 +1487,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
) -> Series[_bool]: ...
@overload
def __mul__(
self, other: Timedelta | TimedeltaSeries | np.timedelta64
self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64
) -> TimedeltaSeries: ...
@overload
def __mul__(self, other: num | _ListLike | Series) -> Series: ...
Expand Down Expand Up @@ -2043,18 +2045,23 @@ class TimedeltaSeries(Series[Timedelta]):
def __add__(self, other: Period) -> PeriodSeries: ...
@overload
def __add__(
self, other: Timestamp | TimestampSeries | DatetimeIndex
self, other: datetime | Timestamp | TimestampSeries | DatetimeIndex
) -> TimestampSeries: ...
@overload
def __add__( # pyright: ignore[reportIncompatibleMethodOverride]
self, other: Timedelta | np.timedelta64
self, other: timedelta | Timedelta | np.timedelta64
) -> TimedeltaSeries: ...
def __radd__(self, other: Timestamp | TimestampSeries) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __radd__(self, other: datetime | Timestamp | TimestampSeries) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __mul__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
self, other: num | Sequence[num] | Series[int] | Series[float]
) -> TimedeltaSeries: ...
def __sub__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
self, other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64
self,
other: timedelta
| Timedelta
| TimedeltaSeries
| TimedeltaIndex
| np.timedelta64,
) -> TimedeltaSeries: ...
@overload # type: ignore[override]
def __truediv__(self, other: float | Sequence[float]) -> Self: ...
Expand Down
6 changes: 3 additions & 3 deletions pandas-stubs/tseries/holiday.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ def register(cls: type[AbstractHolidayCalendar]) -> None: ...
def get_calendar(name: str) -> AbstractHolidayCalendar: ...

class AbstractHolidayCalendar:
rules: list[Holiday] = ...
start_date: Timestamp = ...
end_date: Timestamp = ...
rules: list[Holiday]
start_date: Timestamp
end_date: Timestamp

def __init__(self, name: str = "", rules: list[Holiday] | None = None) -> None: ...
def rule_from_name(self, name: str) -> Holiday | None: ...
Expand Down
1 change: 1 addition & 0 deletions tests/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,7 @@ def test_types_resample() -> None:
with pytest_warns_bounded(FutureWarning, "'M' is deprecated", lower="2.1.99"):
df.resample("M", on="date")
df.resample("20min", origin="epoch", offset=pd.Timedelta(2, "minutes"), on="date")
df.resample("20min", origin="epoch", offset=datetime.timedelta(2), on="date")


def test_types_to_dict() -> None:
Expand Down
12 changes: 12 additions & 0 deletions tests/test_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,3 +1048,15 @@ def test_timedelta_div() -> None:
[1] / index # type: ignore[operator] # pyright: ignore[reportGeneralTypeIssues]
1 // index # type: ignore[operator] # pyright: ignore[reportGeneralTypeIssues]
[1] // index # type: ignore[operator] # pyright: ignore[reportGeneralTypeIssues]


def test_datetime_operators_builtin() -> None:
time = pd.date_range("2022-01-01", "2022-01-31", freq="D")
check(assert_type(time + dt.timedelta(0), pd.DatetimeIndex), pd.DatetimeIndex)
check(assert_type(time - dt.timedelta(0), pd.DatetimeIndex), pd.DatetimeIndex)
check(assert_type(time - dt.datetime.now(), pd.TimedeltaIndex), pd.TimedeltaIndex)

delta = check(assert_type(time - time, pd.TimedeltaIndex), pd.TimedeltaIndex)
check(assert_type(delta + dt.timedelta(0), pd.TimedeltaIndex), pd.TimedeltaIndex)
check(assert_type(dt.datetime.now() + delta, pd.DatetimeIndex), pd.DatetimeIndex)
check(assert_type(delta - dt.timedelta(0), pd.TimedeltaIndex), pd.TimedeltaIndex)
9 changes: 9 additions & 0 deletions tests/test_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,15 @@ def test_merge_asof() -> None:
),
pd.DataFrame,
)
check(
assert_type(
pd.merge_asof(
trades, quotes, on="time", by="ticker", tolerance=dt.timedelta(1)
),
pd.DataFrame,
),
pd.DataFrame,
)
check(
assert_type(
pd.merge_asof(
Expand Down
25 changes: 25 additions & 0 deletions tests/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@ def test_types_resample() -> None:
s.resample("3min").sum()
# origin and offset params added in 1.1.0 https://pandas.pydata.org/docs/whatsnew/v1.1.0.html
s.resample("20min", origin="epoch", offset=pd.Timedelta(value=2, unit="minutes"))
s.resample("20min", origin=datetime.datetime.now(), offset=datetime.timedelta(1))


# set_flags() method added in 1.2.0 https://pandas.pydata.org/docs/whatsnew/v1.2.0.html
Expand Down Expand Up @@ -2860,3 +2861,27 @@ def test_round() -> None:
def test_series_new_empty() -> None:
# GH 826
check(assert_type(pd.Series(), "pd.Series[Any]"), pd.Series)


def test_timedeltaseries_operators() -> None:
series = pd.Series([pd.Timedelta(days=1)])
check(
assert_type(series + datetime.datetime.now(), TimestampSeries),
pd.Series,
pd.Timestamp,
)
check(
assert_type(series + datetime.timedelta(1), TimedeltaSeries),
pd.Series,
pd.Timedelta,
)
check(
assert_type(datetime.datetime.now() + series, TimestampSeries),
pd.Series,
pd.Timestamp,
)
check(
assert_type(series - datetime.timedelta(1), TimedeltaSeries),
pd.Series,
pd.Timedelta,
)
34 changes: 30 additions & 4 deletions tests/test_timefuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,10 @@ def test_series_dt_accessors() -> None:
assert_type(s0.dt.tz_localize(None), "TimestampSeries"), pd.Series, pd.Timestamp
)
check(
assert_type(s0.dt.tz_localize(pytz.UTC), "TimestampSeries"),
assert_type(
s0.dt.tz_localize(pytz.UTC, nonexistent=dt.timedelta(0)),
"TimestampSeries",
),
pd.Series,
pd.Timestamp,
)
Expand Down Expand Up @@ -408,9 +411,21 @@ def test_series_dt_accessors() -> None:
check(assert_type(s0_local.dt.tz, Optional[dt.tzinfo]), dt.tzinfo)
check(assert_type(s0.dt.normalize(), "TimestampSeries"), pd.Series, pd.Timestamp)
check(assert_type(s0.dt.strftime("%Y"), "pd.Series[str]"), pd.Series, str)
check(assert_type(s0.dt.round("D"), "TimestampSeries"), pd.Series, pd.Timestamp)
check(assert_type(s0.dt.floor("D"), "TimestampSeries"), pd.Series, pd.Timestamp)
check(assert_type(s0.dt.ceil("D"), "TimestampSeries"), pd.Series, pd.Timestamp)
check(
assert_type(s0.dt.round("D", nonexistent=dt.timedelta(1)), "TimestampSeries"),
pd.Series,
pd.Timestamp,
)
check(
assert_type(s0.dt.floor("D", nonexistent=dt.timedelta(1)), "TimestampSeries"),
pd.Series,
pd.Timestamp,
)
check(
assert_type(s0.dt.ceil("D", nonexistent=dt.timedelta(1)), "TimestampSeries"),
pd.Series,
pd.Timestamp,
)
check(assert_type(s0.dt.month_name(), "pd.Series[str]"), pd.Series, str)
check(assert_type(s0.dt.day_name(), "pd.Series[str]"), pd.Series, str)

Expand Down Expand Up @@ -1234,3 +1249,14 @@ def test_date_range_unit():
),
pd.DatetimeIndex,
)


def test_DatetimeIndex_sub_timedelta() -> None:
# GH838
check(
assert_type(
pd.date_range("2023-01-01", periods=10, freq="1D") - dt.timedelta(days=1),
"pd.DatetimeIndex",
),
pd.DatetimeIndex,
)

0 comments on commit 52384d3

Please sign in to comment.