Skip to content

TYP: timestamps.pyi #40945

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

Merged
merged 4 commits into from
Apr 26, 2021
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
205 changes: 205 additions & 0 deletions pandas/_libs/tslibs/timestamps.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
from datetime import (
date as _date,
datetime,
time as _time,
timedelta,
tzinfo as _tzinfo,
)
import sys
from time import struct_time
from typing import (
ClassVar,
Optional,
Type,
TypeVar,
overload,
)

import numpy as np

from pandas._libs.tslibs import (
NaT,
NaTType,
Period,
Timedelta,
)

_S = TypeVar("_S")


def integer_op_not_supported(obj) -> None: ...


class Timestamp(datetime):
min: ClassVar[Timestamp]
max: ClassVar[Timestamp]

resolution: ClassVar[Timedelta]
value: int # np.int64

# error: "__new__" must return a class instance (got "Union[Timestamp, NaTType]")
def __new__( # type: ignore[misc]
cls: Type[_S],
ts_input: int | np.integer | float | str | _date | datetime | np.datetime64 = ...,
freq=...,
tz: str | _tzinfo | None | int= ...,
unit=...,
year: int | None = ...,
month: int | None = ...,
day: int | None = ...,
hour: int | None = ...,
minute: int | None = ...,
second: int | None = ...,
microsecond: int | None = ...,
nanosecond: int | None = ...,
tzinfo: _tzinfo | None = ...,
*,
fold: int | None= ...,
) -> _S | NaTType: ...

@property
def year(self) -> int: ...
@property
def month(self) -> int: ...
@property
def day(self) -> int: ...
@property
def hour(self) -> int: ...
@property
def minute(self) -> int: ...
@property
def second(self) -> int: ...
@property
def microsecond(self) -> int: ...
@property
def tzinfo(self) -> Optional[_tzinfo]: ...
@property
def tz(self) -> Optional[_tzinfo]: ...

@property
def fold(self) -> int: ...

@classmethod
def fromtimestamp(cls: Type[_S], t: float, tz: Optional[_tzinfo] = ...) -> _S: ...
@classmethod
def utcfromtimestamp(cls: Type[_S], t: float) -> _S: ...
@classmethod
def today(cls: Type[_S]) -> _S: ...
@classmethod
def fromordinal(cls: Type[_S], n: int) -> _S: ...

if sys.version_info >= (3, 8):
@classmethod
def now(cls: Type[_S], tz: _tzinfo | str | None = ...) -> _S: ...
else:
@overload
@classmethod
def now(cls: Type[_S], tz: None = ...) -> _S: ...
@overload
@classmethod
def now(cls, tz: _tzinfo) -> datetime: ...

@classmethod
def utcnow(cls: Type[_S]) -> _S: ...
@classmethod
def combine(cls, date: _date, time: _time, tzinfo: Optional[_tzinfo] = ...) -> datetime: ...

@classmethod
def fromisoformat(cls: Type[_S], date_string: str) -> _S: ...

def strftime(self, fmt: str) -> str: ...
def __format__(self, fmt: str) -> str: ...

def toordinal(self) -> int: ...
def timetuple(self) -> struct_time: ...

def timestamp(self) -> float: ...

def utctimetuple(self) -> struct_time: ...
def date(self) -> _date: ...
def time(self) -> _time: ...
def timetz(self) -> _time: ...

def replace(
self,
year: int = ...,
month: int = ...,
day: int = ...,
hour: int = ...,
minute: int = ...,
second: int = ...,
microsecond: int = ...,
tzinfo: Optional[_tzinfo] = ...,
*,
fold: int = ...,
) -> datetime: ...

if sys.version_info >= (3, 8):
def astimezone(self: _S, tz: Optional[_tzinfo] = ...) -> _S: ...
else:
def astimezone(self, tz: Optional[_tzinfo] = ...) -> datetime: ...

def ctime(self) -> str: ...
def isoformat(self, sep: str = ..., timespec: str = ...) -> str: ...

@classmethod
def strptime(cls, date_string: str, format: str) -> datetime: ...

def utcoffset(self) -> Optional[timedelta]: ...
def tzname(self) -> Optional[str]: ...
def dst(self) -> Optional[timedelta]: ...

def __le__(self, other: datetime) -> bool: ... # type: ignore
def __lt__(self, other: datetime) -> bool: ... # type: ignore
def __ge__(self, other: datetime) -> bool: ... # type: ignore
def __gt__(self, other: datetime) -> bool: ... # type: ignore
if sys.version_info >= (3, 8):
def __add__(self: _S, other: timedelta) -> _S: ...
def __radd__(self: _S, other: timedelta) -> _S: ...
else:
def __add__(self, other: timedelta) -> datetime: ...
def __radd__(self, other: timedelta) -> datetime: ...
@overload # type: ignore
def __sub__(self, other: datetime) -> timedelta: ...
@overload
def __sub__(self, other: timedelta) -> datetime: ...

def __hash__(self) -> int: ...
def weekday(self) -> int: ...
def isoweekday(self) -> int: ...
def isocalendar(self) -> tuple[int, int, int]: ...

@property
def is_leap_year(self) -> bool: ...
@property
def is_month_start(self) -> bool: ...
@property
def is_quarter_start(self) -> bool: ...
@property
def is_year_start(self) -> bool: ...
@property
def is_month_end(self) -> bool: ...
@property
def is_quarter_end(self) -> bool: ...
@property
def is_year_end(self) -> bool: ...

def to_pydatetime(self, warn: bool = ...) -> datetime: ...
def to_datetime64(self) -> np.datetime64: ...
def to_period(self, freq) -> Period: ...
def to_julian_date(self) -> np.float64: ...

@property
def asm8(self) -> np.datetime64: ...

def tz_convert(self: _S, tz) -> _S: ...

# TODO: could return NaT?
def tz_localize(self: _S, tz, ambiguous: str = ..., nonexistent: str = ...) -> _S: ...

def normalize(self: _S) -> _S: ...

# TODO: round/floor/ceil could return NaT?
def round(self: _S, freq, ambiguous: bool | str = ..., nonexistent: str = ...) -> _S: ...
def floor(self: _S, freq, ambiguous: bool | str = ..., nonexistent: str = ...) -> _S: ...
def ceil(self: _S, freq, ambiguous: bool | str = ..., nonexistent: str = ...) -> _S: ...
16 changes: 8 additions & 8 deletions pandas/core/arrays/_ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@ def generate_regular_range(
-------
ndarray[np.int64] Representing nanoseconds.
"""
start = start.value if start is not None else None
end = end.value if end is not None else None
istart = start.value if start is not None else None
iend = end.value if end is not None else None
stride = freq.nanos

if periods is None:
b = start
b = istart
# cannot just use e = Timestamp(end) + 1 because arange breaks when
# stride is too large, see GH10887
e = b + (end - b) // stride * stride + stride // 2 + 1
elif start is not None:
b = start
e = b + (iend - b) // stride * stride + stride // 2 + 1
elif istart is not None:
b = istart
e = _generate_range_overflow_safe(b, periods, stride, side="start")
elif end is not None:
e = end + stride
elif iend is not None:
e = iend + stride
b = _generate_range_overflow_safe(e, periods, stride, side="end")
else:
raise ValueError(
Expand Down
4 changes: 3 additions & 1 deletion pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,9 @@ def _sub_datetimelike_scalar(self, other):
assert isinstance(other, (datetime, np.datetime64))
assert other is not NaT
other = Timestamp(other)
if other is NaT:
# error: Non-overlapping identity check (left operand type: "Timestamp",
# right operand type: "NaTType")
if other is NaT: # type: ignore[comparison-overlap]
return self - NaT

if not self._has_same_tz(other):
Expand Down
12 changes: 7 additions & 5 deletions pandas/core/dtypes/cast.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,13 @@ def maybe_box_native(value: Scalar) -> Scalar:
value = maybe_box_datetimelike(value)
elif is_float(value):
# error: Argument 1 to "float" has incompatible type
# "Union[Union[str, int, float, bool], Union[Any, Any, Timedelta, Any]]";
# "Union[Union[str, int, float, bool], Union[Any, Timestamp, Timedelta, Any]]";
# expected "Union[SupportsFloat, _SupportsIndex, str]"
value = float(value) # type: ignore[arg-type]
elif is_integer(value):
# error: Argument 1 to "int" has incompatible type
# "Union[Union[str, int, float, bool], Union[Any, Any, Timedelta, Any]]";
# pected "Union[str, SupportsInt, _SupportsIndex, _SupportsTrunc]"
# "Union[Union[str, int, float, bool], Union[Any, Timestamp, Timedelta, Any]]";
# expected "Union[str, SupportsInt, _SupportsIndex, _SupportsTrunc]"
value = int(value) # type: ignore[arg-type]
elif is_bool(value):
value = bool(value)
Expand Down Expand Up @@ -729,7 +729,9 @@ def infer_dtype_from_scalar(val, pandas_dtype: bool = False) -> tuple[DtypeObj,
except OutOfBoundsDatetime:
return np.dtype(object), val

if val is NaT or val.tz is None:
# error: Non-overlapping identity check (left operand type: "Timestamp",
# right operand type: "NaTType")
if val is NaT or val.tz is None: # type: ignore[comparison-overlap]
dtype = np.dtype("M8[ns]")
val = val.to_datetime64()
else:
Expand Down Expand Up @@ -2056,7 +2058,7 @@ def validate_numeric_casting(dtype: np.dtype, value: Scalar) -> None:
ValueError
"""
# error: Argument 1 to "__call__" of "ufunc" has incompatible type
# "Union[Union[str, int, float, bool], Union[Any, Any, Timedelta, Any]]";
# "Union[Union[str, int, float, bool], Union[Any, Timestamp, Timedelta, Any]]";
# expected "Union[Union[int, float, complex, str, bytes, generic],
# Sequence[Union[int, float, complex, str, bytes, generic]],
# Sequence[Sequence[Any]], _SupportsArray]"
Expand Down
4 changes: 3 additions & 1 deletion pandas/core/internals/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1892,7 +1892,9 @@ def get_block_type(values, dtype: Dtype | None = None):
cls = ExtensionBlock
elif isinstance(dtype, CategoricalDtype):
cls = CategoricalBlock
elif vtype is Timestamp:
# error: Non-overlapping identity check (left operand type: "Type[generic]",
# right operand type: "Type[Timestamp]")
elif vtype is Timestamp: # type: ignore[comparison-overlap]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a cast on L1895 that looks iffy. remove it and this ignore is not needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've merged master locally so the line numbers differ.

cls = DatetimeTZBlock
elif isinstance(dtype, ExtensionDtype):
# Note: need to be sure PandasArray is unwrapped before we get here
Expand Down
19 changes: 13 additions & 6 deletions pandas/core/tools/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,16 +628,16 @@ def _adjust_to_origin(arg, origin, unit):

if offset.tz is not None:
raise ValueError(f"origin offset {offset} must be tz-naive")
offset -= Timestamp(0)
td_offset = offset - Timestamp(0)

# convert the offset to the unit of the arg
# this should be lossless in terms of precision
offset = offset // Timedelta(1, unit=unit)
ioffset = td_offset // Timedelta(1, unit=unit)

# scalars & ndarray-like can handle the addition
if is_list_like(arg) and not isinstance(arg, (ABCSeries, Index, np.ndarray)):
arg = np.asarray(arg)
arg = arg + offset
arg = arg + ioffset
return arg


Expand Down Expand Up @@ -887,13 +887,17 @@ def to_datetime(
infer_datetime_format=infer_datetime_format,
)

result: Timestamp | NaTType | Series | Index

if isinstance(arg, Timestamp):
result = arg
if tz is not None:
if arg.tz is not None:
result = result.tz_convert(tz)
# error: Too many arguments for "tz_convert" of "NaTType"
result = result.tz_convert(tz) # type: ignore[call-arg]
else:
result = result.tz_localize(tz)
# error: Too many arguments for "tz_localize" of "NaTType"
result = result.tz_localize(tz) # type: ignore[call-arg]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you update

    def tz_convert(self) -> NaTType: ...
    def tz_localize(self) -> NaTType: ...

in pandas/_libs/tslibs/nattype.pyi to match the signatures added here in pandas/_libs/tslibs/timestamps.pyi you could remove these ignores.

since these ignores appear to be false positives...

>>> pd.NaT.tz_convert("utc")
NaT
>>> 
>>> pd.NaT.tz_localize("Europe/London")
NaT

elif isinstance(arg, ABCSeries):
cache_array = _maybe_cache(arg, format, cache, convert_listlike)
if not cache_array.empty:
Expand Down Expand Up @@ -928,7 +932,10 @@ def to_datetime(
else:
result = convert_listlike(np.array([arg]), format)[0]

return result
# error: Incompatible return value type (got "Union[Timestamp, NaTType,
# Series, Index]", expected "Union[DatetimeIndex, Series, float, str,
# NaTType, None]")
return result # type: ignore[return-value]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use returns instead of assignment to result or change the type declaration of result to match the return type.



# mappings for assembling units
Expand Down
10 changes: 3 additions & 7 deletions pandas/io/excel/_odfreader.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from typing import (
List,
cast,
)
from typing import List

import numpy as np

Expand Down Expand Up @@ -200,10 +197,9 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar:
cell_value = cell.attributes.get((OFFICENS, "date-value"))
return pd.to_datetime(cell_value)
elif cell_type == "time":
result = pd.to_datetime(str(cell))
result = cast(pd.Timestamp, result)
stamp = pd.to_datetime(str(cell))
# error: Item "str" of "Union[float, str, NaTType]" has no attribute "time"
return result.time() # type: ignore[union-attr]
return stamp.time() # type: ignore[union-attr]
else:
self.close()
raise ValueError(f"Unrecognized type {cell_type}")
Expand Down
8 changes: 7 additions & 1 deletion pandas/tests/indexes/test_engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ class TestTimedeltaEngine:
@pytest.mark.parametrize(
"scalar",
[
pd.Timestamp(pd.Timedelta(days=42).asm8.view("datetime64[ns]")),
# error: Argument 1 to "Timestamp" has incompatible type "timedelta64";
# expected "Union[integer[Any], float, str, date, datetime64]"
pd.Timestamp(
pd.Timedelta(days=42).asm8.view(
"datetime64[ns]"
) # type: ignore[arg-type]
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a numpy issue. can't see an issue on github, maybe fixed in master. will check soon.

pd.Timedelta(days=42).value,
pd.Timedelta(days=42).to_pytimedelta(),
pd.Timedelta(days=42).to_timedelta64(),
Expand Down
4 changes: 3 additions & 1 deletion pandas/tests/scalar/timestamp/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,9 @@ def test_constructor_fromordinal(self):
tz="UTC",
),
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, None),
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, pytz.UTC),
# error: Argument 9 to "Timestamp" has incompatible type "_UTCclass";
# expected "Optional[int]"
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, pytz.UTC), # type: ignore[arg-type]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, looks like will also need overloads for the constructor.

],
)
def test_constructor_nanosecond(self, result):
Expand Down