From 05e8770864dc1e7f0a7bfb088fc74494a4101335 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 25 Oct 2026 15:30:33 -0400 Subject: [PATCH] Update python-dateutil stubs for 3.0 and structural inputs Carry over corrections identified while annotating the implementation, including parser overloads, recurrence return types and duck-typed inputs. Update the timezone and deprecated zoneinfo APIs for the released 3.0 implementation, and add regression cases for protocol inference and date subclass behavior. --- .../@tests/test_cases/check_inheritance.py | 8 ++ .../@tests/test_cases/check_structural.py | 64 +++++++++++++ stubs/python-dateutil/METADATA.toml | 2 +- stubs/python-dateutil/dateutil/__init__.pyi | 4 +- stubs/python-dateutil/dateutil/_common.pyi | 35 ++++++- .../dateutil/parser/_parser.pyi | 94 +++++++++++++++---- .../dateutil/parser/isoparser.pyi | 12 ++- .../dateutil/relativedelta.pyi | 65 +++++++------ stubs/python-dateutil/dateutil/rrule.pyi | 41 ++++---- .../python-dateutil/dateutil/tz/__init__.pyi | 10 ++ stubs/python-dateutil/dateutil/tz/_common.pyi | 13 ++- stubs/python-dateutil/dateutil/tz/_tzfile.pyi | 31 ++++++ stubs/python-dateutil/dateutil/tz/_tzpath.pyi | 1 + stubs/python-dateutil/dateutil/tz/tz.pyi | 91 ++++++------------ stubs/python-dateutil/dateutil/utils.pyi | 30 +++++- .../dateutil/zoneinfo/__init__.pyi | 39 ++++---- .../dateutil/zoneinfo/rebuild.pyi | 14 ++- 17 files changed, 378 insertions(+), 176 deletions(-) create mode 100644 stubs/python-dateutil/@tests/test_cases/check_structural.py create mode 100644 stubs/python-dateutil/dateutil/tz/_tzfile.pyi create mode 100644 stubs/python-dateutil/dateutil/tz/_tzpath.pyi diff --git a/stubs/python-dateutil/@tests/test_cases/check_inheritance.py b/stubs/python-dateutil/@tests/test_cases/check_inheritance.py index 3ab5a78cdda2..241b765a7ca6 100644 --- a/stubs/python-dateutil/@tests/test_cases/check_inheritance.py +++ b/stubs/python-dateutil/@tests/test_cases/check_inheritance.py @@ -19,3 +19,11 @@ class MyDateTime(datetime): d2 = date.today() x2 = d2 - relativedelta(days=1) assert_type(x2, date) + + +class MyDate(date): + pass + + +# A date subclass can be promoted to datetime when the delta includes time. +assert_type(MyDate(2024, 1, 1) + relativedelta(hours=1), date) diff --git a/stubs/python-dateutil/@tests/test_cases/check_structural.py b/stubs/python-dateutil/@tests/test_cases/check_structural.py new file mode 100644 index 000000000000..ac54157bada7 --- /dev/null +++ b/stubs/python-dateutil/@tests/test_cases/check_structural.py @@ -0,0 +1,64 @@ +from collections.abc import Sequence +from datetime import date, datetime, time, timedelta, tzinfo +from io import StringIO +from typing_extensions import assert_type + +from dateutil import parser, utils +from dateutil.relativedelta import relativedelta +from dateutil.rrule import WEEKLY, rrule, rruleset +from dateutil.tz import UTC, tzoffset, tzstr + + +class CharacterStream: + def __init__(self, text: str) -> None: + self.stream = StringIO(text) + + def read(self, size: int, /) -> str: + return self.stream.read(size) + + +class TimezoneLookup: + def get(self, name: str, /) -> tzinfo | None: + return UTC if name == "CSTM" else None + + def __contains__(self, name: object, /) -> bool: + return name == "CSTM" + + +class Monday: + weekday = 0 + n = None + + +class Offset: + def total_seconds(self) -> float: + return 5400 + + +def check_parser(flag: bool) -> None: + assert_type(parser.parse(CharacterStream("2024-01-01")), datetime) + assert_type(parser.parse("2024-01-01", fuzzy_with_tokens=flag), datetime | tuple[datetime, tuple[str, ...]]) + assert_type(parser.parser().parse("2024-01-01", fuzzy_with_tokens=flag), datetime | tuple[datetime, tuple[str, ...]]) + parser.parse("2024-01-01 00:00 CSTM", tzinfos=TimezoneLookup()) + + +def check_utilities() -> None: + assert_type(utils.default_tzinfo(time(12), UTC), time) + assert_type(utils.default_tzinfo(datetime(2024, 1, 1), None), datetime) + utils.within_delta(3, 4, 1) + utils.within_delta(date(2024, 1, 1), date(2024, 1, 2), timedelta(days=1)) + + +def check_recurrence() -> None: + rule = rrule(WEEKLY, dtstart=datetime(2024, 1, 1), byweekday=(day for day in (Monday(), 2)), count=3) + assert_type(rule[0], datetime) + assert_type(rule[:2], Sequence[datetime]) + rules = rruleset() + rules.rrule(iter(rule)) + rules.exrule((datetime(2024, 1, 1),)) + assert_type(relativedelta(days=1.5, weekday=Monday()) * "2", relativedelta) + + +def check_offsets() -> None: + assert_type(tzoffset("custom", Offset()).utcoffset(None), timedelta) + assert_type(tzstr.instance("EST5"), tzstr) diff --git a/stubs/python-dateutil/METADATA.toml b/stubs/python-dateutil/METADATA.toml index 995b1aa6aa82..cbd1bc33a880 100644 --- a/stubs/python-dateutil/METADATA.toml +++ b/stubs/python-dateutil/METADATA.toml @@ -1,2 +1,2 @@ -version = "2.9.*" +version = "3.0.*" upstream-repository = "https://github.com/dateutil/dateutil" diff --git a/stubs/python-dateutil/dateutil/__init__.pyi b/stubs/python-dateutil/dateutil/__init__.pyi index 9fb5d4b1fe59..4801277a5c5e 100644 --- a/stubs/python-dateutil/dateutil/__init__.pyi +++ b/stubs/python-dateutil/dateutil/__init__.pyi @@ -1,5 +1,5 @@ -from dateutil import easter, parser, relativedelta, rrule, tz, utils, zoneinfo +from dateutil import easter, parser, relativedelta, rrule, tz, utils -__all__ = ["easter", "parser", "relativedelta", "rrule", "tz", "utils", "zoneinfo"] +__all__ = ["easter", "parser", "relativedelta", "rrule", "tz", "utils"] def __dir__() -> list[str]: ... diff --git a/stubs/python-dateutil/dateutil/_common.pyi b/stubs/python-dateutil/dateutil/_common.pyi index af892a5d7a0d..d092c513ed1f 100644 --- a/stubs/python-dateutil/dateutil/_common.pyi +++ b/stubs/python-dateutil/dateutil/_common.pyi @@ -1,10 +1,41 @@ +from typing import Protocol, TypeVar, type_check_only from typing_extensions import Self class weekday: __slots__ = ["weekday", "n"] def __init__(self, weekday: int, n: int | None = None) -> None: ... - def __call__(self, n: int) -> Self: ... + def __call__(self, n: int | None) -> Self: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... weekday: int - n: int + n: int | None + +_T_co = TypeVar("_T_co", covariant=True) +_T_contra = TypeVar("_T_contra", contravariant=True) + +@type_check_only +class _SizedReader(Protocol[_T_co]): # noqa: Y046 # Used by other modules. + def read(self, size: int, /) -> _T_co: ... + +@type_check_only +class _ReadAll(Protocol[_T_co]): # noqa: Y046 # Used by other modules. + def read(self) -> _T_co: ... + +@type_check_only +class _TotalSeconds(Protocol): # noqa: Y046 # Used by other modules. + def total_seconds(self) -> float: ... + +@type_check_only +class _Weekday(Protocol): # noqa: Y046 # Used by other modules. + @property + def weekday(self) -> int: ... + @property + def n(self) -> int | None: ... + +@type_check_only +class _Gettable(Protocol[_T_contra, _T_co]): + def get(self, key: _T_contra, /) -> _T_co | None: ... + +@type_check_only +class _ContainsGet(_Gettable[_T_contra, _T_co], Protocol[_T_contra, _T_co]): # noqa: Y046 # Used by other modules. + def __contains__(self, key: object, /) -> bool: ... diff --git a/stubs/python-dateutil/dateutil/parser/_parser.pyi b/stubs/python-dateutil/dateutil/parser/_parser.pyi index 700b9fad63c1..7c6e950f5a84 100644 --- a/stubs/python-dateutil/dateutil/parser/_parser.pyi +++ b/stubs/python-dateutil/dateutil/parser/_parser.pyi @@ -1,24 +1,25 @@ import re -from _typeshed import SupportsRead -from collections.abc import Callable, Mapping +from collections.abc import Callable, Sequence from datetime import _TzInfo, datetime from io import StringIO -from typing import IO, Any, Literal, TypeAlias, overload +from typing import Literal, Protocol, TypeAlias, overload, type_check_only from typing_extensions import Self -_FileOrStr: TypeAlias = bytes | str | IO[str] | IO[Any] +from .._common import _ContainsGet, _SizedReader + +_FileOrStr: TypeAlias = bytes | bytearray | str | _SizedReader[str] _TzData: TypeAlias = _TzInfo | int | str | None -_TzInfos: TypeAlias = Mapping[str, _TzData] | Callable[[str, int], _TzData] +_TzInfos: TypeAlias = _ContainsGet[str, _TzData] | Callable[[str | None, int | None], _TzData] __all__ = ["parse", "parserinfo", "ParserError"] class _timelex: _split_decimal: re.Pattern[str] - instream: StringIO | SupportsRead[str] + instream: StringIO | _SizedReader[str] charstack: list[str] tokenstack: list[str] eof: bool - def __init__(self, instream: str | bytes | bytearray | SupportsRead[str]) -> None: ... + def __init__(self, instream: str | bytes | bytearray | _SizedReader[str]) -> None: ... def get_token(self) -> str | None: ... def __iter__(self) -> Self: ... def __next__(self) -> str: ... @@ -37,7 +38,28 @@ class _resultbase: def _repr(self, classname: str) -> str: ... def __len__(self) -> int: ... +@type_check_only +class _ParserInfo(Protocol): + @property + def dayfirst(self) -> bool: ... + @property + def yearfirst(self) -> bool: ... + @property + def UTCZONE(self) -> Sequence[str]: ... + def convertyear(self, year: int, century_specified: bool = False) -> int: ... + def validate(self, res: parser._result) -> bool: ... + def jump(self, name: str) -> bool: ... + def pertain(self, name: str) -> bool: ... + def utczone(self, name: str) -> bool: ... + def weekday(self, name: str) -> int | None: ... + def month(self, name: str) -> int | None: ... + def hms(self, name: str) -> int | None: ... + def ampm(self, name: str) -> int | None: ... + def tzoffset(self, name: str) -> int | None: ... + class parserinfo: + dayfirst: bool + yearfirst: bool JUMP: list[str] WEEKDAYS: list[tuple[str, str]] MONTHS: list[tuple[str, str] | tuple[str, str, str]] @@ -56,7 +78,7 @@ class parserinfo: def utczone(self, name: str) -> bool: ... def tzoffset(self, name: str) -> int | None: ... def convertyear(self, year: int, century_specified: bool = False) -> int: ... - def validate(self, res: datetime) -> bool: ... + def validate(self, res: parser._result) -> bool: ... class _ymd(list[int]): century_specified: bool @@ -75,15 +97,29 @@ class _ymd(list[int]): def resolve_ymd(self, yearfirst: bool | None, dayfirst: bool | None) -> tuple[int, int, int]: ... class parser: - info: parserinfo - def __init__(self, info: parserinfo | None = None) -> None: ... + class _result(_resultbase): + year: int | None + month: int | None + day: int | None + weekday: int | None + hour: int | None + minute: int | None + second: int | None + microsecond: int | None + tzname: str | None + tzoffset: int | None + ampm: int | None + any_unused_tokens: bool | None + + info: _ParserInfo + def __init__(self, info: _ParserInfo | None = None) -> None: ... @overload def parse( self, timestr: _FileOrStr, default: datetime | None = None, - ignoretz: bool = False, + ignoretz: bool | None = False, tzinfos: _TzInfos | None = None, *, dayfirst: bool | None = ..., @@ -96,7 +132,7 @@ class parser: self, timestr: _FileOrStr, default: datetime | None = None, - ignoretz: bool = False, + ignoretz: bool | None = False, tzinfos: _TzInfos | None = None, *, dayfirst: bool | None = ..., @@ -104,17 +140,30 @@ class parser: fuzzy: bool = ..., fuzzy_with_tokens: Literal[True], ) -> tuple[datetime, tuple[str, ...]]: ... + @overload + def parse( + self, + timestr: _FileOrStr, + default: datetime | None = None, + ignoretz: bool | None = False, + tzinfos: _TzInfos | None = None, + *, + dayfirst: bool | None = ..., + yearfirst: bool | None = ..., + fuzzy: bool = ..., + fuzzy_with_tokens: bool, + ) -> datetime | tuple[datetime, tuple[str, ...]]: ... DEFAULTPARSER: parser @overload def parse( timestr: _FileOrStr, - parserinfo: parserinfo | None = None, + parserinfo: _ParserInfo | None = None, *, dayfirst: bool | None = ..., yearfirst: bool | None = ..., - ignoretz: bool = ..., + ignoretz: bool | None = ..., fuzzy: bool = ..., fuzzy_with_tokens: Literal[False] = False, default: datetime | None = ..., @@ -123,16 +172,29 @@ def parse( @overload def parse( timestr: _FileOrStr, - parserinfo: parserinfo | None = None, + parserinfo: _ParserInfo | None = None, *, dayfirst: bool | None = ..., yearfirst: bool | None = ..., - ignoretz: bool = ..., + ignoretz: bool | None = ..., fuzzy: bool = ..., fuzzy_with_tokens: Literal[True], default: datetime | None = ..., tzinfos: _TzInfos | None = ..., ) -> tuple[datetime, tuple[str, ...]]: ... +@overload +def parse( + timestr: _FileOrStr, + parserinfo: _ParserInfo | None = None, + *, + dayfirst: bool | None = ..., + yearfirst: bool | None = ..., + ignoretz: bool | None = ..., + fuzzy: bool = ..., + fuzzy_with_tokens: bool, + default: datetime | None = ..., + tzinfos: _TzInfos | None = ..., +) -> datetime | tuple[datetime, tuple[str, ...]]: ... class _tzparser: class _result(_resultbase): diff --git a/stubs/python-dateutil/dateutil/parser/isoparser.pyi b/stubs/python-dateutil/dateutil/parser/isoparser.pyi index bf589f592296..49bc15a3bd48 100644 --- a/stubs/python-dateutil/dateutil/parser/isoparser.pyi +++ b/stubs/python-dateutil/dateutil/parser/isoparser.pyi @@ -1,17 +1,19 @@ -from _typeshed import SupportsRead -from datetime import date, datetime, time, tzinfo +from datetime import date, datetime, time from typing import TypeAlias -_Readable: TypeAlias = SupportsRead[str | bytes] +from .._common import _ReadAll +from ..tz import tzoffset, tzutc + +_Readable: TypeAlias = _ReadAll[str | bytes] _TakesAscii: TypeAlias = str | bytes | _Readable __all__ = ["isoparse", "isoparser"] class isoparser: - def __init__(self, sep: str | bytes | None = None) -> None: ... + def __init__(self, sep: str | None = None) -> None: ... def isoparse(self, dt_str: _TakesAscii) -> datetime: ... def parse_isodate(self, datestr: _TakesAscii) -> date: ... def parse_isotime(self, timestr: _TakesAscii) -> time: ... - def parse_tzstr(self, tzstr: _TakesAscii, zero_as_utc: bool = True) -> tzinfo: ... + def parse_tzstr(self, tzstr: _TakesAscii, zero_as_utc: bool = True) -> tzutc | tzoffset: ... def isoparse(dt_str: _TakesAscii) -> datetime: ... diff --git a/stubs/python-dateutil/dateutil/relativedelta.pyi b/stubs/python-dateutil/dateutil/relativedelta.pyi index e8f2528a9ac6..174626b68dbd 100644 --- a/stubs/python-dateutil/dateutil/relativedelta.pyi +++ b/stubs/python-dateutil/dateutil/relativedelta.pyi @@ -1,13 +1,13 @@ -from datetime import date, timedelta -from typing import SupportsFloat, TypeAlias, TypeVar, overload +from datetime import date, datetime, timedelta +from typing import SupportsFloat, SupportsIndex, TypeAlias, TypeVar, overload from typing_extensions import Self # See #9817 for why we reexport this here -from ._common import weekday as weekday +from ._common import _Weekday, weekday as weekday -_DateT = TypeVar("_DateT", bound=date) -# Work around attribute and type having the same name. -_Weekday: TypeAlias = weekday +_DateTimeT = TypeVar("_DateTimeT", bound=datetime) + +_FloatInput: TypeAlias = SupportsFloat | SupportsIndex | str | bytes | bytearray MO: weekday TU: weekday @@ -22,12 +22,12 @@ __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] class relativedelta: years: int months: int - days: int - leapdays: int - hours: int - minutes: int - seconds: int - microseconds: int + days: float + leapdays: float + hours: float + minutes: float + seconds: float + microseconds: float year: int | None month: int | None weekday: _Weekday | None @@ -40,15 +40,15 @@ class relativedelta: self, dt1: date | None = None, dt2: date | None = None, - years: int = 0, - months: int = 0, - days: int = 0, - leapdays: int = 0, - weeks: int = 0, - hours: int = 0, - minutes: int = 0, - seconds: int = 0, - microseconds: int = 0, + years: float = 0, + months: float = 0, + days: float = 0, + leapdays: float = 0, + weeks: float = 0, + hours: float = 0, + minutes: float = 0, + seconds: float = 0, + microseconds: float = 0, year: int | None = None, month: int | None = None, day: int | None = None, @@ -64,34 +64,39 @@ class relativedelta: @property def weeks(self) -> int: ... @weeks.setter - def weeks(self, value: int) -> None: ... + def weeks(self, value: float) -> None: ... def normalized(self) -> Self: ... @overload def __add__(self, other: timedelta | relativedelta) -> Self: ... @overload - def __add__(self, other: _DateT) -> _DateT: ... + def __add__(self, other: _DateTimeT) -> _DateTimeT: ... + @overload + def __add__(self, other: date) -> date: ... @overload def __radd__(self, other: timedelta | relativedelta) -> Self: ... @overload - def __radd__(self, other: _DateT) -> _DateT: ... + def __radd__(self, other: _DateTimeT) -> _DateTimeT: ... + @overload + def __radd__(self, other: date) -> date: ... @overload def __rsub__(self, other: timedelta | relativedelta) -> Self: ... @overload - def __rsub__(self, other: _DateT) -> _DateT: ... + def __rsub__(self, other: _DateTimeT) -> _DateTimeT: ... + @overload + def __rsub__(self, other: date) -> date: ... def __sub__(self, other: relativedelta) -> Self: ... def __neg__(self) -> Self: ... def __bool__(self) -> bool: ... def __nonzero__(self) -> bool: ... - def __mul__(self, other: SupportsFloat) -> Self: ... - def __rmul__(self, other: SupportsFloat) -> Self: ... + def __mul__(self, other: _FloatInput) -> Self: ... + def __rmul__(self, other: _FloatInput) -> Self: ... def __eq__(self, other: object) -> bool: ... - def __ne__(self, other: object) -> bool: ... - def __div__(self, other: SupportsFloat) -> Self: ... - def __truediv__(self, other: SupportsFloat) -> Self: ... + def __div__(self, other: _FloatInput) -> Self: ... + def __truediv__(self, other: _FloatInput) -> Self: ... def __abs__(self) -> Self: ... def __hash__(self) -> int: ... diff --git a/stubs/python-dateutil/dateutil/rrule.pyi b/stubs/python-dateutil/dateutil/rrule.pyi index 64cf1ef8e1cb..b2970ab124de 100644 --- a/stubs/python-dateutil/dateutil/rrule.pyi +++ b/stubs/python-dateutil/dateutil/rrule.pyi @@ -1,11 +1,11 @@ import datetime -from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Sequence +from collections.abc import Callable, Generator, Iterable, Iterator, Sequence from typing import Final, Literal, TypeAlias, overload from typing_extensions import Self from dateutil.parser._parser import _TzInfos -from ._common import weekday as weekdaybase +from ._common import _Gettable, _Weekday, weekday as weekdaybase __all__ = [ "rrule", @@ -59,9 +59,14 @@ SU: weekday class rrulebase: def __init__(self, cache: bool | None = False) -> None: ... def __iter__(self) -> Iterator[datetime.datetime]: ... - def __getitem__(self, item: int | slice) -> datetime.datetime: ... + + @overload + def __getitem__(self, item: int) -> datetime.datetime: ... + @overload + def __getitem__(self, item: slice) -> Sequence[datetime.datetime]: ... + def __contains__(self, item: datetime.datetime) -> bool: ... - def count(self) -> int | None: ... + def count(self) -> int: ... def before(self, dt: datetime.datetime, inc: bool = False) -> datetime.datetime | None: ... def after(self, dt: datetime.datetime, inc: bool = False) -> datetime.datetime | None: ... def xafter(self, dt: datetime.datetime, count: int | None = None, inc: bool = False) -> Generator[datetime.datetime]: ... @@ -72,19 +77,19 @@ class rrulebase: class rrule(rrulebase): def __init__( self, - freq: Literal[0, 1, 2, 3, 4, 5, 6], + freq: int, dtstart: datetime.date | None = None, interval: int = 1, - wkst: weekday | int | None = None, + wkst: _Weekday | int | None = None, count: int | None = None, - until: datetime.date | int | None = None, + until: datetime.date | None = None, bysetpos: int | Iterable[int] | None = None, bymonth: int | Iterable[int] | None = None, bymonthday: int | Iterable[int] | None = None, byyearday: int | Iterable[int] | None = None, byeaster: int | Iterable[int] | None = None, byweekno: int | Iterable[int] | None = None, - byweekday: int | weekday | Iterable[int] | Iterable[weekday] | None = None, + byweekday: int | _Weekday | Iterable[int | _Weekday] | None = None, byhour: int | Iterable[int] | None = None, byminute: int | Iterable[int] | None = None, bysecond: int | Iterable[int] | None = None, @@ -93,24 +98,24 @@ class rrule(rrulebase): def replace( self, *, - freq: Literal[0, 1, 2, 3, 4, 5, 6] = ..., + freq: int = ..., dtstart: datetime.date | None = ..., interval: int = ..., - wkst: weekday | int | None = ..., + wkst: _Weekday | int | None = ..., count: int | None = ..., - until: datetime.date | int | None = ..., + until: datetime.date | None = ..., bysetpos: int | Iterable[int] | None = None, bymonth: int | Iterable[int] | None = None, bymonthday: int | Iterable[int] | None = None, byyearday: int | Iterable[int] | None = None, byeaster: int | Iterable[int] | None = None, byweekno: int | Iterable[int] | None = None, - byweekday: int | weekday | Iterable[int] | Iterable[weekday] | None = None, + byweekday: int | _Weekday | Iterable[int | _Weekday] | None = None, byhour: int | Iterable[int] | None = None, byminute: int | Iterable[int] | None = None, bysecond: int | Iterable[int] | None = None, cache: bool | None = ..., - ) -> Self: ... + ) -> rrule: ... _RRule: TypeAlias = rrule @@ -171,9 +176,9 @@ class rruleset(rrulebase): def __ne__(self, other: object) -> bool: ... def __init__(self, cache: bool | None = False) -> None: ... - def rrule(self, rrule: _RRule) -> None: ... + def rrule(self, rrule: Iterable[datetime.datetime]) -> None: ... def rdate(self, rdate: datetime.datetime) -> None: ... - def exrule(self, exrule: _RRule) -> None: ... + def exrule(self, exrule: Iterable[datetime.datetime]) -> None: ... def exdate(self, exdate: datetime.datetime) -> None: ... class _rrulestr: @@ -188,7 +193,7 @@ class _rrulestr: unfold: bool = False, compatible: bool = False, ignoretz: bool = False, - tzids: Callable[[str], datetime.tzinfo] | Mapping[str, datetime.tzinfo] | None = None, + tzids: Callable[[str], datetime.tzinfo | None] | _Gettable[str, datetime.tzinfo] | None = None, tzinfos: _TzInfos | None = None, ) -> rruleset: ... @overload @@ -202,7 +207,7 @@ class _rrulestr: unfold: bool = False, forceset: bool = False, ignoretz: bool = False, - tzids: Callable[[str], datetime.tzinfo] | Mapping[str, datetime.tzinfo] | None = None, + tzids: Callable[[str], datetime.tzinfo | None] | _Gettable[str, datetime.tzinfo] | None = None, tzinfos: _TzInfos | None = None, ) -> rruleset: ... @overload @@ -216,7 +221,7 @@ class _rrulestr: forceset: bool = False, compatible: bool = False, ignoretz: bool = False, - tzids: Callable[[str], datetime.tzinfo] | Mapping[str, datetime.tzinfo] | None = None, + tzids: Callable[[str], datetime.tzinfo | None] | _Gettable[str, datetime.tzinfo] | None = None, tzinfos: _TzInfos | None = None, ) -> rrule | rruleset: ... diff --git a/stubs/python-dateutil/dateutil/tz/__init__.pyi b/stubs/python-dateutil/dateutil/tz/__init__.pyi index 406980bdfdab..6dcf21db36c9 100644 --- a/stubs/python-dateutil/dateutil/tz/__init__.pyi +++ b/stubs/python-dateutil/dateutil/tz/__init__.pyi @@ -1,10 +1,14 @@ import builtins import sys +from collections.abc import Sequence from datetime import datetime from typing_extensions import Self +from zoneinfo import reset_tzpath as reset_tzpath from ._common import tzrangebase from .tz import ( + DeprecatedTzKeyWarning as DeprecatedTzKeyWarning, + available_iana_timezones as available_iana_timezones, datetime_ambiguous as datetime_ambiguous, datetime_exists as datetime_exists, enfold as enfold, @@ -44,6 +48,8 @@ else: tzwin: None tzwinlocal: None +TZPATH: Sequence[str] + UTC: tzutc __all__ = [ @@ -61,7 +67,11 @@ __all__ = [ "datetime_ambiguous", "datetime_exists", "resolve_imaginary", + "available_iana_timezones", "UTC", + "TZPATH", + "reset_tzpath", + "DeprecatedTzKeyWarning", "DeprecatedTzFormatWarning", ] diff --git a/stubs/python-dateutil/dateutil/tz/_common.pyi b/stubs/python-dateutil/dateutil/tz/_common.pyi index b160d0bc7398..08e52f5e7695 100644 --- a/stubs/python-dateutil/dateutil/tz/_common.pyi +++ b/stubs/python-dateutil/dateutil/tz/_common.pyi @@ -1,7 +1,12 @@ import abc from collections.abc import Callable from datetime import datetime, timedelta, tzinfo -from typing import ClassVar, ParamSpec, TypeVar +from typing import ClassVar, ParamSpec, Protocol, TypeVar, type_check_only +from typing_extensions import Self + +@type_check_only +class _Foldable(Protocol): + def replace(self, *, fold: int) -> Self: ... ZERO: timedelta @@ -9,10 +14,10 @@ __all__ = ["tzname_in_python2", "enfold"] _P = ParamSpec("_P") _R = TypeVar("_R") -_DateTimeT = TypeVar("_DateTimeT", bound=datetime) +_FoldableT = TypeVar("_FoldableT", bound=_Foldable) def tzname_in_python2(namefunc: Callable[_P, _R]) -> Callable[_P, _R]: ... -def enfold(dt: _DateTimeT, fold: int = 1) -> _DateTimeT: ... +def enfold(dt: _FoldableT, fold: int = 1) -> _FoldableT: ... # Doesn't actually have ABCMeta as the metaclass at runtime, # but mypy complains if we don't have it in the stub. @@ -25,7 +30,7 @@ class tzrangebase(_tzinfo): def __init__(self) -> None: ... def utcoffset(self, dt: datetime | None) -> timedelta | None: ... def dst(self, dt: datetime | None) -> timedelta | None: ... - def tzname(self, dt: datetime | None) -> str: ... + def tzname(self, dt: datetime | None) -> str | None: ... def fromutc(self, dt: datetime) -> datetime: ... def is_ambiguous(self, dt: datetime) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] diff --git a/stubs/python-dateutil/dateutil/tz/_tzfile.pyi b/stubs/python-dateutil/dateutil/tz/_tzfile.pyi new file mode 100644 index 000000000000..e97261b9437b --- /dev/null +++ b/stubs/python-dateutil/dateutil/tz/_tzfile.pyi @@ -0,0 +1,31 @@ +from _typeshed import SupportsRead, Unused +from collections.abc import Callable, Sequence +from datetime import datetime, timedelta +from typing import ClassVar, TypeAlias + +from ._common import _tzinfo + +_TZifData: TypeAlias = tuple[Sequence[int], Sequence[int], Sequence[int], Sequence[int], Sequence[str], bytes | None] +_PickleArgs: TypeAlias = tuple[type[tzfile], str | None, object, _TZifData] +_Reduction: TypeAlias = tuple[Callable[..., tzfile], _PickleArgs] | tuple[Callable[..., tzfile], _PickleArgs, dict[str, object]] + +EPOCH: datetime +EPOCHORDINAL: int + +class tzfile(_tzinfo): + def __init__( + self, fileobj: str | SupportsRead[bytes] | None, filename: str | None = None, key: str | None = None + ) -> None: ... + @property + def key(self) -> str | None: ... + def is_ambiguous(self, dt: datetime | None) -> bool: ... + def utcoffset(self, dt: datetime | None) -> timedelta | None: ... + def dst(self, dt: datetime | None) -> timedelta | None: ... + def tzname(self, dt: datetime | None) -> str | None: ... + def __eq__(self, other: object) -> bool: ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other: object) -> bool: ... + def __reduce__(self) -> _Reduction: ... + def __reduce_ex__(self, protocol: Unused) -> _Reduction: ... + +def load_data(fobj: SupportsRead[bytes]) -> _TZifData: ... diff --git a/stubs/python-dateutil/dateutil/tz/_tzpath.pyi b/stubs/python-dateutil/dateutil/tz/_tzpath.pyi new file mode 100644 index 000000000000..5b5a5c8acf7e --- /dev/null +++ b/stubs/python-dateutil/dateutil/tz/_tzpath.pyi @@ -0,0 +1 @@ +from zoneinfo import TZPATH as TZPATH, InvalidTZPathWarning as InvalidTZPathWarning, reset_tzpath as reset_tzpath diff --git a/stubs/python-dateutil/dateutil/tz/tz.pyi b/stubs/python-dateutil/dateutil/tz/tz.pyi index fe2eaf50ee22..56683143c904 100644 --- a/stubs/python-dateutil/dateutil/tz/tz.pyi +++ b/stubs/python-dateutil/dateutil/tz/tz.pyi @@ -1,11 +1,11 @@ import sys -from _typeshed import Unused +from _typeshed import SupportsRAdd from datetime import datetime, timedelta, tzinfo -from typing import Any, ClassVar, Literal, Protocol, TypeVar, type_check_only -from typing_extensions import Self +from typing import ClassVar, Literal, TypeVar, type_check_only -from ..relativedelta import relativedelta +from .._common import _ReadAll, _TotalSeconds from ._common import _tzinfo, enfold as enfold, tzrangebase +from ._tzfile import tzfile as tzfile if sys.platform == "win32": from .win import tzwin as tzwin, tzwinlocal as tzwinlocal @@ -17,13 +17,12 @@ _DateTimeT = TypeVar("_DateTimeT", bound=datetime) ZERO: timedelta EPOCH: datetime -EPOCHORDINAL: int class tzutc(tzinfo): - def utcoffset(self, dt: datetime | None) -> timedelta | None: ... - def dst(self, dt: datetime | None) -> timedelta | None: ... - def tzname(self, dt: datetime | None) -> str: ... - def is_ambiguous(self, dt: datetime | None) -> bool: ... + def utcoffset(self, dt: object) -> timedelta: ... + def dst(self, dt: object) -> timedelta: ... + def tzname(self, dt: object) -> str: ... + def is_ambiguous(self, dt: object) -> bool: ... def fromutc(self, dt: _DateTimeT) -> _DateTimeT: ... def __eq__(self, other: object) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] @@ -33,18 +32,18 @@ class tzutc(tzinfo): UTC: tzutc class tzoffset(tzinfo): - def __init__(self, name: str | None, offset: float | timedelta) -> None: ... - def utcoffset(self, dt: datetime | None) -> timedelta | None: ... - def dst(self, dt: datetime | None) -> timedelta | None: ... - def is_ambiguous(self, dt: datetime | None) -> bool: ... - def tzname(self, dt: datetime | None) -> str: ... + def __init__(self, name: str | None, offset: float | _TotalSeconds) -> None: ... + def utcoffset(self, dt: object) -> timedelta: ... + def dst(self, dt: object) -> timedelta: ... + def is_ambiguous(self, dt: object) -> bool: ... + def tzname(self, dt: object) -> str | None: ... def fromutc(self, dt: _DateTimeT) -> _DateTimeT: ... def __eq__(self, other: object) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] def __ne__(self, other: object) -> bool: ... __reduce__ = object.__reduce__ @classmethod - def instance(cls, name: str | None, offset: float | timedelta) -> tzoffset: ... + def instance(cls, name: str | None, offset: float | _TotalSeconds) -> tzoffset: ... class tzlocal(_tzinfo): def __init__(self) -> None: ... @@ -57,72 +56,32 @@ class tzlocal(_tzinfo): def __ne__(self, other: object) -> bool: ... __reduce__ = object.__reduce__ -class _ttinfo: - __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt", "dstoffset"] - offset: float - delta: timedelta - isdst: bool - abbr: str - isstd: bool - isgmt: bool - dstoffset: timedelta - def __init__(self) -> None: ... - def __eq__(self, other: object) -> bool: ... - __hash__: ClassVar[None] # type: ignore[assignment] - def __ne__(self, other: object) -> bool: ... - -@type_check_only -class _TZFileReader(Protocol): - # optional attribute: - # name: str - def read(self, size: int, /) -> bytes: ... - def seek(self, target: int, whence: Literal[1], /) -> object: ... - -class tzfile(_tzinfo): - def __init__(self, fileobj: str | _TZFileReader, filename: str | None = None) -> None: ... - def is_ambiguous(self, dt: datetime | None, idx: int | None = None) -> bool: ... - def utcoffset(self, dt: datetime | None) -> timedelta | None: ... - def dst(self, dt: datetime | None) -> timedelta | None: ... - def tzname(self, dt: datetime | None) -> str: ... - def __eq__(self, other: object) -> bool: ... - __hash__: ClassVar[None] # type: ignore[assignment] - def __ne__(self, other: object) -> bool: ... - def __reduce__(self) -> tuple[type[Self], tuple[None, str], dict[str, Any]]: ... - def __reduce_ex__(self, protocol: Unused) -> tuple[type[Self], tuple[None, str], dict[str, Any]]: ... - class tzrange(tzrangebase): hasdst: bool def __init__( self, - stdabbr: str, - stdoffset: int | timedelta | None = None, + stdabbr: str | None, + stdoffset: float | _TotalSeconds | None = None, dstabbr: str | None = None, - dstoffset: int | timedelta | None = None, - start: relativedelta | None = None, - end: relativedelta | None = None, + dstoffset: float | _TotalSeconds | None = None, + start: timedelta | SupportsRAdd[datetime, datetime] | Literal[False] | None = None, + end: timedelta | SupportsRAdd[datetime, datetime] | Literal[False] | None = None, ) -> None: ... - def transitions(self, year: int) -> tuple[datetime, datetime]: ... + def transitions(self, year: int) -> tuple[datetime, datetime] | None: ... def __eq__(self, other: object) -> bool: ... class tzstr(tzrange): hasdst: bool def __init__(self, s: str, posix_offset: bool = False) -> None: ... @classmethod - def instance(cls, name: str | None, offset: float | timedelta) -> tzoffset: ... - -@type_check_only -class _ICalReader(Protocol): - # optional attribute: - # name: str - def read(self) -> str: ... + def instance(cls, s: str, posix_offset: bool = False) -> tzstr: ... class tzical: - def __init__(self, fileobj: str | _ICalReader) -> None: ... + def __init__(self, fileobj: str | _ReadAll[str]) -> None: ... def keys(self) -> list[str]: ... def get(self, tzid: str | None = None) -> tzinfo | None: ... TZFILES: list[str] -TZPATHS: list[str] def datetime_exists(dt: datetime, tz: tzinfo | None = None) -> bool: ... def datetime_ambiguous(dt: datetime, tz: tzinfo | None = None) -> bool: ... @@ -134,6 +93,10 @@ class _GetTZ: def __call__(self, name: str | None = None) -> tzinfo | None: ... def set_cache_size(self, size: int) -> None: ... def cache_clear(self) -> None: ... - def nocache(self, name: str | None) -> tzinfo | None: ... + def nocache(self, name: str | None = None) -> tzinfo | None: ... gettz: _GetTZ + +class DeprecatedTzKeyWarning(FutureWarning): ... + +def available_iana_timezones() -> set[str]: ... diff --git a/stubs/python-dateutil/dateutil/utils.pyi b/stubs/python-dateutil/dateutil/utils.pyi index ce4d8d38ccc0..55c36a073e94 100644 --- a/stubs/python-dateutil/dateutil/utils.pyi +++ b/stubs/python-dateutil/dateutil/utils.pyi @@ -1,8 +1,28 @@ -from datetime import _TzInfo, datetime, timedelta -from typing import TypeVar +from _typeshed import SupportsSub +from datetime import _TzInfo, date, datetime, timedelta +from typing import Protocol, TypeVar, overload, type_check_only +from typing_extensions import Self -_DateTimeT = TypeVar("_DateTimeT", bound=datetime) +@type_check_only +class _WithTimezone(Protocol): + @property + def tzinfo(self) -> _TzInfo | None: ... + def replace(self, *, tzinfo: _TzInfo | None) -> Self: ... + +@type_check_only +class _Delta(Protocol): + def __abs__(self) -> Self: ... + def __neg__(self) -> Self: ... + def __le__(self, other: Self, /) -> bool: ... + +_WithTimezoneT = TypeVar("_WithTimezoneT", bound=_WithTimezone) +_DeltaT = TypeVar("_DeltaT", bound=_Delta) +_OperandT = TypeVar("_OperandT") def today(tzinfo: _TzInfo | None = None) -> datetime: ... -def default_tzinfo(dt: _DateTimeT, tzinfo: _TzInfo) -> _DateTimeT: ... -def within_delta(dt1: datetime, dt2: datetime, delta: timedelta) -> bool: ... +def default_tzinfo(dt: _WithTimezoneT, tzinfo: _TzInfo | None) -> _WithTimezoneT: ... + +@overload +def within_delta(dt1: date, dt2: date, delta: timedelta) -> bool: ... +@overload +def within_delta(dt1: SupportsSub[_OperandT, _DeltaT], dt2: _OperandT, delta: _DeltaT) -> bool: ... diff --git a/stubs/python-dateutil/dateutil/zoneinfo/__init__.pyi b/stubs/python-dateutil/dateutil/zoneinfo/__init__.pyi index adee85032da2..f2e81d7c4c72 100644 --- a/stubs/python-dateutil/dateutil/zoneinfo/__init__.pyi +++ b/stubs/python-dateutil/dateutil/zoneinfo/__init__.pyi @@ -1,44 +1,41 @@ -from _typeshed import Incomplete -from collections.abc import Callable -from io import BytesIO -from tarfile import _Fileobj -from typing import Final, TypeAlias, TypeVar, overload -from typing_extensions import Self, deprecated +from collections.abc import Mapping, Sequence +from typing import Final, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import deprecated -from dateutil.tz import tzfile as _tzfile +from dateutil.tz import tzfile as tzfile _T = TypeVar("_T") -_MetadataType: TypeAlias = dict[str, Incomplete] +_MetadataType: TypeAlias = bool | int | float | str | Sequence[_MetadataType] | Mapping[str, _MetadataType] | None + +@type_check_only +class _ArchiveReader(Protocol): + def read(self, size: int = -1, /) -> bytes: ... + def seek(self, offset: int, whence: int = 0, /) -> int: ... + def tell(self) -> int: ... __all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] -ZONEFILENAME: Final[str] +ZONEFILENAME: Final[None] METADATA_FN: Final[str] -class tzfile(_tzfile): - # source code does this override, changing the type - def __reduce__(self) -> tuple[Callable[[str], Self], tuple[str]]: ... # type: ignore[override] - -def getzoneinfofile_stream() -> BytesIO | None: ... - class ZoneInfoFile: - zones: dict[str, _tzfile] + zones: dict[str, tzfile] metadata: _MetadataType | None - def __init__(self, zonefile_stream: _Fileobj | None = None) -> None: ... + def __init__(self, zonefile_stream: _ArchiveReader | None = None) -> None: ... @overload - def get(self, name: str, default: None = None) -> _tzfile | None: ... + def get(self, name: str, default: None = None) -> tzfile | None: ... @overload - def get(self, name: str, default: _tzfile) -> _tzfile: ... + def get(self, name: str, default: tzfile) -> tzfile: ... @overload - def get(self, name: str, default: _T) -> _tzfile | _T: ... + def get(self, name: str, default: _T) -> tzfile | _T: ... def get_zonefile_instance(new_instance: bool = False) -> ZoneInfoFile: ... @deprecated( "zoneinfo.gettz() will be removed in future versions, to use the dateutil-provided " "zoneinfo files, instantiate a ZoneInfoFile object and use ZoneInfoFile.zones.get() instead." ) -def gettz(name: str) -> _tzfile: ... +def gettz(name: str) -> tzfile | None: ... @deprecated( "zoneinfo.gettz_db_metadata() will be removed in future versions, to use the " "dateutil-provided zoneinfo files, ZoneInfoFile object and query the 'metadata' attribute instead." diff --git a/stubs/python-dateutil/dateutil/zoneinfo/rebuild.pyi b/stubs/python-dateutil/dateutil/zoneinfo/rebuild.pyi index 325950a2eea3..1271a648a5cc 100644 --- a/stubs/python-dateutil/dateutil/zoneinfo/rebuild.pyi +++ b/stubs/python-dateutil/dateutil/zoneinfo/rebuild.pyi @@ -1,12 +1,10 @@ -from _typeshed import StrOrBytesPath, Unused -from collections.abc import Iterable +from typing_extensions import deprecated -from ..zoneinfo import _MetadataType +__all__ = ["rebuild", "RebuildDeprecationWarning"] +class RebuildDeprecationWarning(FutureWarning): ... + +@deprecated("dateutil no longer ships a zoneinfo tarball; use tzdata or system time zone data instead") def rebuild( - filename: StrOrBytesPath, - tag: Unused | None = None, - format: str = "gz", - zonegroups: Iterable[str] = [], - metadata: _MetadataType | None = None, + filename: object, tag: object = None, format: object = "gz", zonegroups: object = [], metadata: object = None ) -> None: ...