diff --git a/stdlib/@tests/test_cases/check_datetime.py b/stdlib/@tests/test_cases/check_datetime.py new file mode 100644 index 000000000000..055776ba0b9f --- /dev/null +++ b/stdlib/@tests/test_cases/check_datetime.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from datetime import date, datetime, time, timedelta, timezone, tzinfo +from typing import Union, cast +from typing_extensions import Never, assert_type + +UTC: timezone = timezone.utc + +dt_none = cast(datetime[None], None) +dt_tz = cast(datetime[tzinfo], None) +dt_both = cast(datetime[Union[tzinfo, None]], None) + +# Constructors + +assert_type(datetime(2000, 1, 1), datetime[None]) +assert_type(datetime(2000, 1, 1, tzinfo=None), datetime[None]) +assert_type(datetime(2000, 1, 1, tzinfo=UTC), datetime[tzinfo]) + +assert_type(datetime.fromtimestamp(0), datetime[None]) +assert_type(datetime.fromtimestamp(0, None), datetime[None]) +assert_type(datetime.fromtimestamp(0, UTC), datetime[tzinfo]) +assert_type(datetime.utcfromtimestamp(0), datetime[None]) # pyright: ignore[reportDeprecated] + +assert_type(datetime.now(), datetime[None]) +assert_type(datetime.now(None), datetime[None]) +assert_type(datetime.now(UTC), datetime[tzinfo]) +assert_type(datetime.today(), datetime[None]) +assert_type(datetime.utcnow(), datetime[None]) # pyright: ignore[reportDeprecated] + +assert_type(datetime.fromisoformat("2000-01-01"), datetime[Union[tzinfo, None]]) + +# Comparisons + +assert_type(dt_none < dt_none, bool) +assert_type(dt_tz < dt_tz, bool) +assert_type(dt_both < dt_both, bool) + +assert_type(dt_none < dt_tz, Never) +assert_type(dt_tz < dt_none, Never) +assert_type(dt_both < dt_none, bool) +assert_type(dt_both < dt_tz, bool) +assert_type(dt_none < dt_both, bool) + +# Sub + +assert_type(dt_none - dt_none, timedelta) +assert_type(dt_tz - dt_tz, timedelta) +assert_type(dt_both - dt_both, timedelta) + +assert_type(dt_none - dt_tz, Never) +assert_type(dt_tz - dt_none, Never) +assert_type(dt_both - dt_none, timedelta) +assert_type(dt_both - dt_tz, timedelta) +assert_type(dt_none - dt_both, timedelta) +assert_type(dt_tz - dt_both, timedelta) + +# Combine + +assert_type(datetime.combine(date(2000, 1, 1), time(12, 0)), datetime[None]) +assert_type(datetime.combine(date(2000, 1, 1), time(12, 0), tzinfo=None), datetime[None]) +assert_type(datetime.combine(date(2000, 1, 1), time(12, 0), tzinfo=UTC), datetime[tzinfo]) + +# Replace + +assert_type(dt_none.replace(year=2001), datetime[None]) +assert_type(dt_none.replace(year=2001, tzinfo=None), datetime[None]) +assert_type(dt_none.replace(year=2001, tzinfo=UTC), datetime[tzinfo]) +assert_type(dt_tz.replace(year=2001), datetime[tzinfo]) +assert_type(dt_tz.replace(year=2001, tzinfo=None), datetime[None]) +assert_type(dt_tz.replace(year=2001, tzinfo=UTC), datetime[tzinfo]) +assert_type(dt_both.replace(year=2001), datetime[Union[tzinfo, None]]) +assert_type(dt_both.replace(year=2001, tzinfo=None), datetime[None]) +assert_type(dt_both.replace(year=2001, tzinfo=UTC), datetime[tzinfo]) + +# Attributes + +assert_type(dt_none.tzinfo, None) +assert_type(dt_tz.tzinfo, tzinfo) +assert_type(dt_both.tzinfo, Union[tzinfo, None]) diff --git a/stdlib/@tests/test_cases/check_unittest.py b/stdlib/@tests/test_cases/check_unittest.py index bbc2a2ba4843..977b940fd61e 100644 --- a/stdlib/@tests/test_cases/check_unittest.py +++ b/stdlib/@tests/test_cases/check_unittest.py @@ -19,6 +19,7 @@ case.assertAlmostEqual(2.4, 2.41) case.assertAlmostEqual(Fraction(49, 50), Fraction(48, 50)) case.assertAlmostEqual(3.14, complex(5, 6)) +case.assertAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1)) case.assertAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1), delta=timedelta(hours=1)) case.assertAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1), None, "foo", timedelta(hours=1)) case.assertAlmostEqual(Decimal("1.1"), Decimal("1.11")) @@ -28,7 +29,6 @@ case.assertAlmostEqual(2.4, 2.41, places=9, delta=0.02) # type: ignore case.assertAlmostEqual("foo", "bar") # type: ignore -case.assertAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1)) # type: ignore case.assertAlmostEqual(Decimal("0.4"), Fraction(1, 2)) # type: ignore case.assertAlmostEqual(complex(2, 3), Decimal("0.9")) # type: ignore @@ -39,12 +39,12 @@ case.assertAlmostEqual(1, 2.4) case.assertNotAlmostEqual(Fraction(49, 50), Fraction(48, 50)) case.assertAlmostEqual(3.14, complex(5, 6)) +case.assertNotAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1)) case.assertNotAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1), delta=timedelta(hours=1)) case.assertNotAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1), None, "foo", timedelta(hours=1)) case.assertNotAlmostEqual(2.4, 2.41, places=9, delta=0.02) # type: ignore case.assertNotAlmostEqual("foo", "bar") # type: ignore -case.assertNotAlmostEqual(datetime(1999, 1, 2), datetime(1999, 1, 2, microsecond=1)) # type: ignore case.assertNotAlmostEqual(Decimal("0.4"), Fraction(1, 2)) # type: ignore case.assertNotAlmostEqual(complex(2, 3), Decimal("0.9")) # type: ignore diff --git a/stdlib/datetime.pyi b/stdlib/datetime.pyi index 8a0536c006d5..fcf4127b3218 100644 --- a/stdlib/datetime.pyi +++ b/stdlib/datetime.pyi @@ -1,7 +1,7 @@ import sys from abc import abstractmethod from time import struct_time -from typing import ClassVar, Final, NoReturn, SupportsIndex, final, overload, type_check_only +from typing import Any, ClassVar, Final, Generic, NoReturn, SupportsIndex, TypeVar, final, overload, type_check_only from typing_extensions import CapsuleType, Self, TypeAlias, deprecated, disjoint_base if sys.version_info >= (3, 11): @@ -242,10 +242,13 @@ class timedelta: def __bool__(self) -> bool: ... def __hash__(self) -> int: ... +_TzInfoT = TypeVar("_TzInfoT", bound=tzinfo | None, default=Any) + @disjoint_base -class datetime(date): +class datetime(date, Generic[_TzInfoT]): min: ClassVar[datetime] max: ClassVar[datetime] + @overload def __new__( cls, year: SupportsIndex, @@ -255,10 +258,40 @@ class datetime(date): minute: SupportsIndex = 0, second: SupportsIndex = 0, microsecond: SupportsIndex = 0, - tzinfo: _TzInfo | None = None, + tzinfo: None = None, *, fold: int = 0, - ) -> Self: ... + ) -> datetime[None]: ... + @overload + def __new__( + cls, + year: SupportsIndex, + month: SupportsIndex, + day: SupportsIndex, + hour: SupportsIndex = 0, + minute: SupportsIndex = 0, + second: SupportsIndex = 0, + microsecond: SupportsIndex = 0, + *, + tzinfo: _TzInfo, + fold: int = 0, + ) -> datetime[_TzInfo]: ... + @overload + def __new__( + cls, + year: SupportsIndex, + month: SupportsIndex, + day: SupportsIndex, + hour: SupportsIndex, + minute: SupportsIndex, + second: SupportsIndex, + microsecond: SupportsIndex, + tzinfo: _TzInfo, + *, + fold: int = 0, + ) -> datetime[_TzInfo]: ... + @classmethod + def fromisoformat(cls, date_string: str, /) -> datetime[_TzInfo | None]: ... # type: ignore[override] @property def hour(self) -> int: ... @property @@ -268,29 +301,47 @@ class datetime(date): @property def microsecond(self) -> int: ... @property - def tzinfo(self) -> _TzInfo | None: ... + def tzinfo(self) -> _TzInfoT: ... @property def fold(self) -> int: ... # On <3.12, the name of the first parameter in the pure-Python implementation # didn't match the name in the C implementation, # meaning it is only *safe* to pass it as a keyword argument on 3.12+ if sys.version_info >= (3, 12): + @overload # type: ignore[override] @classmethod - def fromtimestamp(cls, timestamp: float, tz: _TzInfo | None = None) -> Self: ... + def fromtimestamp(cls, timestamp: float, tz: None = None) -> datetime[None]: ... + @overload + @classmethod + def fromtimestamp(cls, timestamp: float, tz: _TzInfo) -> datetime[_TzInfo]: ... else: + @overload # type: ignore[override] + @classmethod + def fromtimestamp(cls, timestamp: float, /, tz: None = None) -> datetime[None]: ... + @overload @classmethod - def fromtimestamp(cls, timestamp: float, /, tz: _TzInfo | None = None) -> Self: ... + def fromtimestamp(cls, timestamp: float, /, tz: _TzInfo) -> datetime[_TzInfo]: ... @classmethod - @deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.timezone.utc)") - def utcfromtimestamp(cls, t: float, /) -> Self: ... + @deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.UTC)") + def utcfromtimestamp(cls, t: float, /) -> datetime[None]: ... + @overload + @classmethod + def now(cls, tz: None = None) -> datetime[None]: ... + @overload + @classmethod + def now(cls, tz: _TzInfo) -> datetime[_TzInfo]: ... @classmethod - def now(cls, tz: _TzInfo | None = None) -> Self: ... + def today(cls) -> datetime[None]: ... # type: ignore[override] @classmethod - @deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.timezone.utc)") - def utcnow(cls) -> Self: ... + @deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)") + def utcnow(cls) -> datetime[None]: ... + @overload + @classmethod + def combine(cls, date: _Date, time: _Time, tzinfo: None = None) -> datetime[None]: ... + @overload @classmethod - def combine(cls, date: _Date, time: _Time, tzinfo: _TzInfo | None = ...) -> Self: ... + def combine(cls, date: _Date, time: _Time, tzinfo: _TzInfo) -> datetime[_TzInfo]: ... def timestamp(self) -> float: ... def utctimetuple(self) -> struct_time: ... def date(self) -> _Date: ... @@ -312,6 +363,7 @@ class datetime(date): fold: int = ..., ) -> Self: ... + @overload def replace( self, year: SupportsIndex = ..., @@ -321,25 +373,116 @@ class datetime(date): minute: SupportsIndex = ..., second: SupportsIndex = ..., microsecond: SupportsIndex = ..., - tzinfo: _TzInfo | None = ..., *, fold: int = ..., ) -> Self: ... + @overload + def replace( + self, + year: SupportsIndex, + month: SupportsIndex, + day: SupportsIndex, + hour: SupportsIndex, + minute: SupportsIndex, + second: SupportsIndex, + microsecond: SupportsIndex, + tzinfo: _TzInfo, + *, + fold: int = ..., + ) -> datetime[_TzInfo]: ... + @overload + def replace( + self, + year: SupportsIndex = ..., + month: SupportsIndex = ..., + day: SupportsIndex = ..., + hour: SupportsIndex = ..., + minute: SupportsIndex = ..., + second: SupportsIndex = ..., + microsecond: SupportsIndex = ..., + *, + tzinfo: _TzInfo, + fold: int = ..., + ) -> datetime[_TzInfo]: ... + @overload + def replace( + self, + year: SupportsIndex, + month: SupportsIndex, + day: SupportsIndex, + hour: SupportsIndex, + minute: SupportsIndex, + second: SupportsIndex, + microsecond: SupportsIndex, + tzinfo: None, + *, + fold: int = ..., + ) -> datetime[None]: ... + @overload + def replace( + self, + year: SupportsIndex = ..., + month: SupportsIndex = ..., + day: SupportsIndex = ..., + hour: SupportsIndex = ..., + minute: SupportsIndex = ..., + second: SupportsIndex = ..., + microsecond: SupportsIndex = ..., + *, + tzinfo: None, + fold: int = ..., + ) -> datetime[None]: ... def astimezone(self, tz: _TzInfo | None = None) -> Self: ... def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: ... @classmethod - def strptime(cls, date_string: str, format: str, /) -> Self: ... + def strptime(cls, date_string: str, format: str, /) -> datetime[_TzInfo | None]: ... # type: ignore[override] def utcoffset(self) -> timedelta | None: ... def tzname(self) -> str | None: ... def dst(self) -> timedelta | None: ... - def __le__(self, value: datetime, /) -> bool: ... # type: ignore[override] - def __lt__(self, value: datetime, /) -> bool: ... # type: ignore[override] - def __ge__(self, value: datetime, /) -> bool: ... # type: ignore[override] - def __gt__(self, value: datetime, /) -> bool: ... # type: ignore[override] + @overload # type: ignore[override] + def __le__( # type: ignore[overload-overlap] + self: datetime[_TzInfo] | datetime[_TzInfo | None], value: datetime[_TzInfo] | datetime[_TzInfo | None], / + ) -> bool: ... # type: ignore[misc] + @overload + def __le__(self: datetime[None] | datetime[_TzInfo | None], value: datetime[None] | datetime[_TzInfo | None], /) -> bool: ... # type: ignore[misc] + @overload + def __le__(self: datetime[Any], value: datetime[Any], /) -> NoReturn: ... + @overload # type: ignore[override] + def __lt__( # type: ignore[overload-overlap] + self: datetime[_TzInfo] | datetime[_TzInfo | None], value: datetime[_TzInfo] | datetime[_TzInfo | None], / + ) -> bool: ... # type: ignore[misc] + @overload + def __lt__(self: datetime[None] | datetime[_TzInfo | None], value: datetime[None] | datetime[_TzInfo | None], /) -> bool: ... # type: ignore[misc] + @overload + def __lt__(self: datetime[Any], value: datetime[Any], /) -> NoReturn: ... + @overload # type: ignore[override] + def __ge__( # type: ignore[overload-overlap] + self: datetime[_TzInfo] | datetime[_TzInfo | None], value: datetime[_TzInfo] | datetime[_TzInfo | None], / + ) -> bool: ... # type: ignore[misc] + @overload + def __ge__(self: datetime[None] | datetime[_TzInfo | None], value: datetime[None] | datetime[_TzInfo | None], /) -> bool: ... # type: ignore[misc] + @overload + def __ge__(self: datetime[Any], value: datetime[Any], /) -> NoReturn: ... + @overload # type: ignore[override] + def __gt__( # type: ignore[overload-overlap] + self: datetime[_TzInfo] | datetime[_TzInfo | None], value: datetime[_TzInfo] | datetime[_TzInfo | None], / + ) -> bool: ... # type: ignore[misc] + @overload + def __gt__(self: datetime[None] | datetime[_TzInfo | None], value: datetime[None] | datetime[_TzInfo | None], /) -> bool: ... # type: ignore[misc] + @overload + def __gt__(self: datetime[Any], value: datetime[Any], /) -> NoReturn: ... def __eq__(self, value: object, /) -> bool: ... def __hash__(self) -> int: ... @overload # type: ignore[override] - def __sub__(self, value: Self, /) -> timedelta: ... + def __sub__( # type: ignore[overload-overlap] + self: datetime[_TzInfo] | datetime[_TzInfo | None], value: datetime[_TzInfo] | datetime[_TzInfo | None], / + ) -> timedelta: ... + @overload + def __sub__( # type: ignore[overload-overlap] + self: datetime[None] | datetime[_TzInfo | None], value: datetime[None] | datetime[_TzInfo | None], / + ) -> timedelta: ... + @overload + def __sub__(self: datetime[Any], value: datetime[Any], /) -> NoReturn: ... @overload def __sub__(self, value: timedelta, /) -> Self: ... diff --git a/stubs/python-dateutil/@tests/test_cases/check_inheritance.py b/stubs/python-dateutil/@tests/test_cases/check_inheritance.py index 3ab5a78cdda2..dfdbed5e5b63 100644 --- a/stubs/python-dateutil/@tests/test_cases/check_inheritance.py +++ b/stubs/python-dateutil/@tests/test_cases/check_inheritance.py @@ -10,11 +10,11 @@ class MyDateTime(datetime): d = MyDateTime.now() x = d - relativedelta(days=1) -assert_type(x, MyDateTime) +assert_type(x, datetime[None]) d3 = datetime.today() x3 = d3 - relativedelta(days=1) -assert_type(x3, datetime) +assert_type(x3, datetime[None]) d2 = date.today() x2 = d2 - relativedelta(days=1)