Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timestamp -> datetime and Timedelta -> timedelta #841

Merged
merged 5 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
)