diff --git a/lib/arrow/_version.py b/lib/arrow/_version.py index 10aa336c..67bc602a 100644 --- a/lib/arrow/_version.py +++ b/lib/arrow/_version.py @@ -1 +1 @@ -__version__ = "1.2.3" +__version__ = "1.3.0" diff --git a/lib/arrow/arrow.py b/lib/arrow/arrow.py index 1ede107f..8d329efd 100644 --- a/lib/arrow/arrow.py +++ b/lib/arrow/arrow.py @@ -168,9 +168,9 @@ class Arrow: isinstance(tzinfo, dt_tzinfo) and hasattr(tzinfo, "localize") and hasattr(tzinfo, "zone") - and tzinfo.zone # type: ignore[attr-defined] + and tzinfo.zone ): - tzinfo = parser.TzinfoParser.parse(tzinfo.zone) # type: ignore[attr-defined] + tzinfo = parser.TzinfoParser.parse(tzinfo.zone) elif isinstance(tzinfo, str): tzinfo = parser.TzinfoParser.parse(tzinfo) @@ -495,7 +495,7 @@ class Arrow: yield current values = [getattr(current, f) for f in cls._ATTRS] - current = cls(*values, tzinfo=tzinfo).shift( # type: ignore + current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc] **{frame_relative: relative_steps} ) @@ -578,7 +578,7 @@ class Arrow: for _ in range(3 - len(values)): values.append(1) - floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore + floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc] if frame_absolute == "week": # if week_start is greater than self.isoweekday() go back one week by setting delta = 7 @@ -792,7 +792,6 @@ class Arrow: return self._datetime.isoformat() def __format__(self, formatstr: str) -> str: - if len(formatstr) > 0: return self.format(formatstr) @@ -804,7 +803,6 @@ class Arrow: # attributes and properties def __getattr__(self, name: str) -> int: - if name == "week": return self.isocalendar()[1] @@ -965,7 +963,6 @@ class Arrow: absolute_kwargs = {} for key, value in kwargs.items(): - if key in self._ATTRS: absolute_kwargs[key] = value elif key in ["week", "quarter"]: @@ -1022,7 +1019,6 @@ class Arrow: additional_attrs = ["weeks", "quarters", "weekday"] for key, value in kwargs.items(): - if key in self._ATTRS_PLURAL or key in additional_attrs: relative_kwargs[key] = value else: @@ -1259,11 +1255,10 @@ class Arrow: ) if trunc(abs(delta)) != 1: - granularity += "s" # type: ignore + granularity += "s" # type: ignore[assignment] return locale.describe(granularity, delta, only_distance=only_distance) else: - if not granularity: raise ValueError( "Empty granularity list provided. " @@ -1314,7 +1309,7 @@ class Arrow: def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": """Returns a new :class:`Arrow ` object, that represents - the time difference relative to the attrbiutes of the + the time difference relative to the attributes of the :class:`Arrow ` object. :param timestring: a ``str`` representing a humanized relative time. @@ -1367,7 +1362,6 @@ class Arrow: # Search input string for each time unit within locale for unit, unit_object in locale_obj.timeframes.items(): - # Need to check the type of unit_object to create the correct dictionary if isinstance(unit_object, Mapping): strings_to_search = unit_object @@ -1378,7 +1372,6 @@ class Arrow: # Needs to cycle all through strings as some locales have strings that # could overlap in a regex match, since input validation isn't being performed. for time_delta, time_string in strings_to_search.items(): - # Replace {0} with regex \d representing digits search_string = str(time_string) search_string = search_string.format(r"\d+") @@ -1419,7 +1412,7 @@ class Arrow: # Assert error if string does not modify any units if not any([True for k, v in unit_visited.items() if v]): raise ValueError( - "Input string not valid. Note: Some locales do not support the week granulairty in Arrow. " + "Input string not valid. Note: Some locales do not support the week granularity in Arrow. " "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error." ) @@ -1718,7 +1711,6 @@ class Arrow: # math def __add__(self, other: Any) -> "Arrow": - if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) @@ -1736,7 +1728,6 @@ class Arrow: pass # pragma: no cover def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]: - if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) @@ -1749,7 +1740,6 @@ class Arrow: return NotImplemented def __rsub__(self, other: Any) -> timedelta: - if isinstance(other, dt_datetime): return other - self._datetime @@ -1758,42 +1748,36 @@ class Arrow: # comparisons def __eq__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return False return self._datetime == self._get_datetime(other) def __ne__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return True return not self.__eq__(other) def __gt__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime > self._get_datetime(other) def __ge__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime >= self._get_datetime(other) def __lt__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime < self._get_datetime(other) def __le__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented @@ -1865,7 +1849,6 @@ class Arrow: def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]: """Sets default end and limit values for range method.""" if end is None: - if limit is None: raise ValueError("One of 'end' or 'limit' is required.") diff --git a/lib/arrow/factory.py b/lib/arrow/factory.py index aad4af8b..f35085f1 100644 --- a/lib/arrow/factory.py +++ b/lib/arrow/factory.py @@ -267,11 +267,9 @@ class ArrowFactory: raise TypeError(f"Cannot parse single argument of type {type(arg)!r}.") elif arg_count == 2: - arg_1, arg_2 = args[0], args[1] if isinstance(arg_1, datetime): - # (datetime, tzinfo/str) -> fromdatetime @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): return self.type.fromdatetime(arg_1, tzinfo=arg_2) @@ -281,7 +279,6 @@ class ArrowFactory: ) elif isinstance(arg_1, date): - # (date, tzinfo/str) -> fromdate @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): return self.type.fromdate(arg_1, tzinfo=arg_2) diff --git a/lib/arrow/formatter.py b/lib/arrow/formatter.py index 728bea1a..d45f7153 100644 --- a/lib/arrow/formatter.py +++ b/lib/arrow/formatter.py @@ -29,7 +29,6 @@ FORMAT_W3C: Final[str] = "YYYY-MM-DD HH:mm:ssZZ" class DateTimeFormatter: - # This pattern matches characters enclosed in square brackets are matched as # an atomic group. For more info on atomic groups and how to they are # emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578 @@ -41,18 +40,15 @@ class DateTimeFormatter: locale: locales.Locale def __init__(self, locale: str = DEFAULT_LOCALE) -> None: - self.locale = locales.get_locale(locale) def format(cls, dt: datetime, fmt: str) -> str: - # FIXME: _format_token() is nullable return cls._FORMAT_RE.sub( lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt ) def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]: - if token and token.startswith("[") and token.endswith("]"): return token[1:-1] diff --git a/lib/arrow/locales.py b/lib/arrow/locales.py index 3627497f..34b2a098 100644 --- a/lib/arrow/locales.py +++ b/lib/arrow/locales.py @@ -129,7 +129,6 @@ class Locale: _locale_map[locale_name.lower().replace("_", "-")] = cls def __init__(self) -> None: - self._month_name_to_ordinal = None def describe( @@ -174,7 +173,7 @@ class Locale: # Needed to determine the correct relative string to use timeframe_value = 0 - for _unit_name, unit_value in timeframes: + for _, unit_value in timeframes: if trunc(unit_value) != 0: timeframe_value = trunc(unit_value) break @@ -285,7 +284,6 @@ class Locale: timeframe: TimeFrameLiteral, delta: Union[float, int], ) -> str: - if timeframe == "now": return humanized @@ -425,7 +423,7 @@ class ItalianLocale(Locale): "hours": "{0} ore", "day": "un giorno", "days": "{0} giorni", - "week": "una settimana,", + "week": "una settimana", "weeks": "{0} settimane", "month": "un mese", "months": "{0} mesi", @@ -867,14 +865,16 @@ class FinnishLocale(Locale): timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "juuri nyt", - "second": "sekunti", - "seconds": {"past": "{0} muutama sekunti", "future": "{0} muutaman sekunnin"}, + "second": {"past": "sekunti", "future": "sekunnin"}, + "seconds": {"past": "{0} sekuntia", "future": "{0} sekunnin"}, "minute": {"past": "minuutti", "future": "minuutin"}, "minutes": {"past": "{0} minuuttia", "future": "{0} minuutin"}, "hour": {"past": "tunti", "future": "tunnin"}, "hours": {"past": "{0} tuntia", "future": "{0} tunnin"}, - "day": "päivä", + "day": {"past": "päivä", "future": "päivän"}, "days": {"past": "{0} päivää", "future": "{0} päivän"}, + "week": {"past": "viikko", "future": "viikon"}, + "weeks": {"past": "{0} viikkoa", "future": "{0} viikon"}, "month": {"past": "kuukausi", "future": "kuukauden"}, "months": {"past": "{0} kuukautta", "future": "{0} kuukauden"}, "year": {"past": "vuosi", "future": "vuoden"}, @@ -1887,7 +1887,7 @@ class GermanBaseLocale(Locale): future = "in {0}" and_word = "und" - timeframes = { + timeframes: ClassVar[Dict[TimeFrameLiteral, str]] = { "now": "gerade eben", "second": "einer Sekunde", "seconds": "{0} Sekunden", @@ -1982,7 +1982,9 @@ class GermanBaseLocale(Locale): return super().describe(timeframe, delta, only_distance) # German uses a different case without 'in' or 'ago' - humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) + humanized: str = self.timeframes_only_distance[timeframe].format( + trunc(abs(delta)) + ) return humanized @@ -2547,6 +2549,8 @@ class ArabicLocale(Locale): "hours": {"2": "ساعتين", "ten": "{0} ساعات", "higher": "{0} ساعة"}, "day": "يوم", "days": {"2": "يومين", "ten": "{0} أيام", "higher": "{0} يوم"}, + "week": "اسبوع", + "weeks": {"2": "اسبوعين", "ten": "{0} أسابيع", "higher": "{0} اسبوع"}, "month": "شهر", "months": {"2": "شهرين", "ten": "{0} أشهر", "higher": "{0} شهر"}, "year": "سنة", @@ -3709,6 +3713,8 @@ class HungarianLocale(Locale): "hours": {"past": "{0} órával", "future": "{0} óra"}, "day": {"past": "egy nappal", "future": "egy nap"}, "days": {"past": "{0} nappal", "future": "{0} nap"}, + "week": {"past": "egy héttel", "future": "egy hét"}, + "weeks": {"past": "{0} héttel", "future": "{0} hét"}, "month": {"past": "egy hónappal", "future": "egy hónap"}, "months": {"past": "{0} hónappal", "future": "{0} hónap"}, "year": {"past": "egy évvel", "future": "egy év"}, @@ -3934,7 +3940,6 @@ class ThaiLocale(Locale): class LaotianLocale(Locale): - names = ["lo", "lo-la"] past = "{0} ກ່ອນຫນ້ານີ້" @@ -4119,6 +4124,7 @@ class BengaliLocale(Locale): return f"{n}র্থ" if n == 6: return f"{n}ষ্ঠ" + return "" class RomanshLocale(Locale): @@ -4137,6 +4143,8 @@ class RomanshLocale(Locale): "hours": "{0} ura", "day": "in di", "days": "{0} dis", + "week": "in'emna", + "weeks": "{0} emnas", "month": "in mais", "months": "{0} mais", "year": "in onn", @@ -5399,7 +5407,7 @@ class LuxembourgishLocale(Locale): future = "an {0}" and_word = "an" - timeframes = { + timeframes: ClassVar[Dict[TimeFrameLiteral, str]] = { "now": "just elo", "second": "enger Sekonn", "seconds": "{0} Sekonnen", @@ -5487,7 +5495,9 @@ class LuxembourgishLocale(Locale): return super().describe(timeframe, delta, only_distance) # Luxembourgish uses a different case without 'in' or 'ago' - humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) + humanized: str = self.timeframes_only_distance[timeframe].format( + trunc(abs(delta)) + ) return humanized diff --git a/lib/arrow/parser.py b/lib/arrow/parser.py index e95d78b0..645e3da7 100644 --- a/lib/arrow/parser.py +++ b/lib/arrow/parser.py @@ -159,7 +159,6 @@ class DateTimeParser: _input_re_map: Dict[_FORMAT_TYPE, Pattern[str]] def __init__(self, locale: str = DEFAULT_LOCALE, cache_size: int = 0) -> None: - self.locale = locales.get_locale(locale) self._input_re_map = self._BASE_INPUT_RE_MAP.copy() self._input_re_map.update( @@ -196,7 +195,6 @@ class DateTimeParser: def parse_iso( self, datetime_string: str, normalize_whitespace: bool = False ) -> datetime: - if normalize_whitespace: datetime_string = re.sub(r"\s+", " ", datetime_string.strip()) @@ -236,13 +234,14 @@ class DateTimeParser: ] if has_time: - if has_space_divider: date_string, time_string = datetime_string.split(" ", 1) else: date_string, time_string = datetime_string.split("T", 1) - time_parts = re.split(r"[\+\-Z]", time_string, 1, re.IGNORECASE) + time_parts = re.split( + r"[\+\-Z]", time_string, maxsplit=1, flags=re.IGNORECASE + ) time_components: Optional[Match[str]] = self._TIME_RE.match(time_parts[0]) @@ -303,7 +302,6 @@ class DateTimeParser: fmt: Union[List[str], str], normalize_whitespace: bool = False, ) -> datetime: - if normalize_whitespace: datetime_string = re.sub(r"\s+", " ", datetime_string) @@ -341,12 +339,11 @@ class DateTimeParser: f"Unable to find a match group for the specified token {token!r}." ) - self._parse_token(token, value, parts) # type: ignore + self._parse_token(token, value, parts) # type: ignore[arg-type] return self._build_datetime(parts) def _generate_pattern_re(self, fmt: str) -> Tuple[List[_FORMAT_TYPE], Pattern[str]]: - # fmt is a string of tokens like 'YYYY-MM-DD' # we construct a new string by replacing each # token by its pattern: @@ -498,7 +495,6 @@ class DateTimeParser: value: Any, parts: _Parts, ) -> None: - if token == "YYYY": parts["year"] = int(value) @@ -508,7 +504,7 @@ class DateTimeParser: elif token in ["MMMM", "MMM"]: # FIXME: month_number() is nullable - parts["month"] = self.locale.month_number(value.lower()) # type: ignore + parts["month"] = self.locale.month_number(value.lower()) # type: ignore[typeddict-item] elif token in ["MM", "M"]: parts["month"] = int(value) @@ -588,7 +584,6 @@ class DateTimeParser: weekdate = parts.get("weekdate") if weekdate is not None: - year, week = int(weekdate[0]), int(weekdate[1]) if weekdate[2] is not None: @@ -712,7 +707,6 @@ class DateTimeParser: ) def _parse_multiformat(self, string: str, formats: Iterable[str]) -> datetime: - _datetime: Optional[datetime] = None for fmt in formats: @@ -740,12 +734,11 @@ class DateTimeParser: class TzinfoParser: _TZINFO_RE: ClassVar[Pattern[str]] = re.compile( - r"^([\+\-])?(\d{2})(?:\:?(\d{2}))?$" + r"^(?:\(UTC)*([\+\-])?(\d{2})(?:\:?(\d{2}))?" ) @classmethod def parse(cls, tzinfo_string: str) -> dt_tzinfo: - tzinfo: Optional[dt_tzinfo] = None if tzinfo_string == "local": @@ -755,7 +748,6 @@ class TzinfoParser: tzinfo = tz.tzutc() else: - iso_match = cls._TZINFO_RE.match(tzinfo_string) if iso_match: diff --git a/lib/dateutil-stubs/METADATA.toml b/lib/dateutil-stubs/METADATA.toml new file mode 100644 index 00000000..5f2be1e4 --- /dev/null +++ b/lib/dateutil-stubs/METADATA.toml @@ -0,0 +1,6 @@ +version = "2.9.*" +upstream_repository = "https://github.com/dateutil/dateutil" +partial_stub = true + +[tool.stubtest] +ignore_missing_stub = true diff --git a/lib/dateutil-stubs/__init__.pyi b/lib/dateutil-stubs/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/lib/dateutil-stubs/_common.pyi b/lib/dateutil-stubs/_common.pyi new file mode 100644 index 00000000..75bf74c5 --- /dev/null +++ b/lib/dateutil-stubs/_common.pyi @@ -0,0 +1,9 @@ +from typing_extensions import Self + +class weekday: + def __init__(self, weekday: int, n: int | None = None) -> None: ... + def __call__(self, n: int) -> Self: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + weekday: int + n: int diff --git a/lib/dateutil-stubs/easter.pyi b/lib/dateutil-stubs/easter.pyi new file mode 100644 index 00000000..952d1191 --- /dev/null +++ b/lib/dateutil-stubs/easter.pyi @@ -0,0 +1,8 @@ +from datetime import date +from typing import Literal + +EASTER_JULIAN: Literal[1] +EASTER_ORTHODOX: Literal[2] +EASTER_WESTERN: Literal[3] + +def easter(year: int, method: Literal[1, 2, 3] = 3) -> date: ... diff --git a/lib/dateutil-stubs/parser/__init__.pyi b/lib/dateutil-stubs/parser/__init__.pyi new file mode 100644 index 00000000..c375ff15 --- /dev/null +++ b/lib/dateutil-stubs/parser/__init__.pyi @@ -0,0 +1,67 @@ +from collections.abc import Callable, Mapping +from datetime import datetime, tzinfo +from typing import IO, Any +from typing_extensions import TypeAlias + +from .isoparser import isoparse as isoparse, isoparser as isoparser + +_FileOrStr: TypeAlias = bytes | str | IO[str] | IO[Any] +_TzData: TypeAlias = tzinfo | int | str | None +_TzInfo: TypeAlias = Mapping[str, _TzData] | Callable[[str, int], _TzData] + +class parserinfo: + JUMP: list[str] + WEEKDAYS: list[tuple[str, ...]] + MONTHS: list[tuple[str, ...]] + HMS: list[tuple[str, str, str]] + AMPM: list[tuple[str, str]] + UTCZONE: list[str] + PERTAIN: list[str] + TZOFFSET: dict[str, int] + def __init__(self, dayfirst: bool = False, yearfirst: bool = False) -> None: ... + def jump(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 pertain(self, name: str) -> bool: ... + def utczone(self, name: str) -> bool: ... + def tzoffset(self, name: str) -> int | None: ... + def convertyear(self, year: int) -> int: ... + def validate(self, res: datetime) -> bool: ... + +class parser: + def __init__(self, info: parserinfo | None = None) -> None: ... + def parse( + self, + timestr: _FileOrStr, + default: datetime | None = None, + ignoretz: bool = False, + tzinfos: _TzInfo | None = None, + *, + dayfirst: bool | None = ..., + yearfirst: bool | None = ..., + fuzzy: bool = ..., + fuzzy_with_tokens: bool = ..., + ) -> datetime: ... + +DEFAULTPARSER: parser + +def parse( + timestr: _FileOrStr, + parserinfo: parserinfo | None = None, + *, + dayfirst: bool | None = ..., + yearfirst: bool | None = ..., + ignoretz: bool = ..., + fuzzy: bool = ..., + fuzzy_with_tokens: bool = ..., + default: datetime | None = ..., + tzinfos: _TzInfo | None = ..., +) -> datetime: ... + +class _tzparser: ... + +DEFAULTTZPARSER: _tzparser + +class ParserError(ValueError): ... diff --git a/lib/dateutil-stubs/parser/isoparser.pyi b/lib/dateutil-stubs/parser/isoparser.pyi new file mode 100644 index 00000000..fc5e46d9 --- /dev/null +++ b/lib/dateutil-stubs/parser/isoparser.pyi @@ -0,0 +1,15 @@ +from _typeshed import SupportsRead +from datetime import date, datetime, time, tzinfo +from typing_extensions import TypeAlias + +_Readable: TypeAlias = SupportsRead[str | bytes] +_TakesAscii: TypeAlias = str | bytes | _Readable + +class isoparser: + def __init__(self, sep: str | bytes | 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 isoparse(dt_str: _TakesAscii) -> datetime: ... diff --git a/lib/dateutil-stubs/py.typed b/lib/dateutil-stubs/py.typed new file mode 100644 index 00000000..b648ac92 --- /dev/null +++ b/lib/dateutil-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/lib/dateutil-stubs/relativedelta.pyi b/lib/dateutil-stubs/relativedelta.pyi new file mode 100644 index 00000000..394713db --- /dev/null +++ b/lib/dateutil-stubs/relativedelta.pyi @@ -0,0 +1,97 @@ +from datetime import date, timedelta +from typing import SupportsFloat, TypeVar, overload +from typing_extensions import Self, TypeAlias + +# See #9817 for why we reexport this here +from ._common import weekday as weekday + +_DateT = TypeVar("_DateT", bound=date) +# Work around attribute and type having the same name. +_Weekday: TypeAlias = weekday + +MO: weekday +TU: weekday +WE: weekday +TH: weekday +FR: weekday +SA: weekday +SU: weekday + +class relativedelta: + years: int + months: int + days: int + leapdays: int + hours: int + minutes: int + seconds: int + microseconds: int + year: int | None + month: int | None + weekday: _Weekday | None + day: int | None + hour: int | None + minute: int | None + second: int | None + microsecond: int | None + def __init__( + self, + dt1: date | None = None, + dt2: date | None = None, + years: int | None = 0, + months: int | None = 0, + days: int | None = 0, + leapdays: int | None = 0, + weeks: int | None = 0, + hours: int | None = 0, + minutes: int | None = 0, + seconds: int | None = 0, + microseconds: int | None = 0, + year: int | None = None, + month: int | None = None, + day: int | None = None, + weekday: int | _Weekday | None = None, + yearday: int | None = None, + nlyearday: int | None = None, + hour: int | None = None, + minute: int | None = None, + second: int | None = None, + microsecond: int | None = None, + ) -> None: ... + @property + def weeks(self) -> int: ... + @weeks.setter + def weeks(self, value: int) -> None: ... + def normalized(self) -> Self: ... + # TODO: use Union when mypy will handle it properly in overloaded operator + # methods (#2129, #1442, #1264 in mypy) + @overload + def __add__(self, other: relativedelta) -> Self: ... + @overload + def __add__(self, other: timedelta) -> Self: ... + @overload + def __add__(self, other: _DateT) -> _DateT: ... + @overload + def __radd__(self, other: relativedelta) -> Self: ... + @overload + def __radd__(self, other: timedelta) -> Self: ... + @overload + def __radd__(self, other: _DateT) -> _DateT: ... + @overload + def __rsub__(self, other: relativedelta) -> Self: ... + @overload + def __rsub__(self, other: timedelta) -> Self: ... + @overload + def __rsub__(self, other: _DateT) -> _DateT: ... + 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 __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __div__(self, other: SupportsFloat) -> Self: ... + def __truediv__(self, other: SupportsFloat) -> Self: ... + def __abs__(self) -> Self: ... + def __hash__(self) -> int: ... diff --git a/lib/dateutil-stubs/rrule.pyi b/lib/dateutil-stubs/rrule.pyi new file mode 100644 index 00000000..f091cfbd --- /dev/null +++ b/lib/dateutil-stubs/rrule.pyi @@ -0,0 +1,111 @@ +import datetime +from _typeshed import Incomplete +from collections.abc import Iterable, Iterator, Sequence +from typing_extensions import TypeAlias + +from ._common import weekday as weekdaybase + +YEARLY: int +MONTHLY: int +WEEKLY: int +DAILY: int +HOURLY: int +MINUTELY: int +SECONDLY: int + +class weekday(weekdaybase): ... + +weekdays: tuple[weekday, weekday, weekday, weekday, weekday, weekday, weekday] +MO: weekday +TU: weekday +WE: weekday +TH: weekday +FR: weekday +SA: weekday +SU: weekday + +class rrulebase: + def __init__(self, cache: bool = False) -> None: ... + def __iter__(self) -> Iterator[datetime.datetime]: ... + def __getitem__(self, item): ... + def __contains__(self, item): ... + def count(self): ... + def before(self, dt, inc: bool = False): ... + def after(self, dt, inc: bool = False): ... + def xafter(self, dt, count: Incomplete | None = None, inc: bool = False): ... + def between(self, after, before, inc: bool = False, count: int = 1): ... + +class rrule(rrulebase): + def __init__( + self, + freq, + dtstart: datetime.date | None = None, + interval: int = 1, + wkst: weekday | int | None = None, + count: int | None = None, + until: datetime.date | int | 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, + byhour: int | Iterable[int] | None = None, + byminute: int | Iterable[int] | None = None, + bysecond: int | Iterable[int] | None = None, + cache: bool = False, + ) -> None: ... + def replace(self, **kwargs): ... + +_RRule: TypeAlias = rrule + +class _iterinfo: + rrule: _RRule + def __init__(self, rrule: _RRule) -> None: ... + yearlen: int | None + nextyearlen: int | None + yearordinal: int | None + yearweekday: int | None + mmask: Sequence[int] | None + mdaymask: Sequence[int] | None + nmdaymask: Sequence[int] | None + wdaymask: Sequence[int] | None + mrange: Sequence[int] | None + wnomask: Sequence[int] | None + nwdaymask: Sequence[int] | None + eastermask: Sequence[int] | None + lastyear: int | None + lastmonth: int | None + def rebuild(self, year, month): ... + def ydayset(self, year, month, day): ... + def mdayset(self, year, month, day): ... + def wdayset(self, year, month, day): ... + def ddayset(self, year, month, day): ... + def htimeset(self, hour, minute, second): ... + def mtimeset(self, hour, minute, second): ... + def stimeset(self, hour, minute, second): ... + +class rruleset(rrulebase): + class _genitem: + dt: Incomplete + genlist: list[Incomplete] + gen: Incomplete + def __init__(self, genlist, gen) -> None: ... + def __next__(self) -> None: ... + next = __next__ + def __lt__(self, other) -> bool: ... + def __gt__(self, other) -> bool: ... + def __eq__(self, other) -> bool: ... + def __ne__(self, other) -> bool: ... + + def __init__(self, cache: bool = False) -> None: ... + def rrule(self, rrule: _RRule): ... + def rdate(self, rdate): ... + def exrule(self, exrule): ... + def exdate(self, exdate): ... + +class _rrulestr: + def __call__(self, s, **kwargs) -> rrule | rruleset: ... + +rrulestr: _rrulestr diff --git a/lib/dateutil-stubs/tz/__init__.pyi b/lib/dateutil-stubs/tz/__init__.pyi new file mode 100644 index 00000000..334ca482 --- /dev/null +++ b/lib/dateutil-stubs/tz/__init__.pyi @@ -0,0 +1,15 @@ +from .tz import ( + datetime_ambiguous as datetime_ambiguous, + datetime_exists as datetime_exists, + gettz as gettz, + resolve_imaginary as resolve_imaginary, + tzfile as tzfile, + tzical as tzical, + tzlocal as tzlocal, + tzoffset as tzoffset, + tzrange as tzrange, + tzstr as tzstr, + tzutc as tzutc, +) + +UTC: tzutc diff --git a/lib/dateutil-stubs/tz/_common.pyi b/lib/dateutil-stubs/tz/_common.pyi new file mode 100644 index 00000000..4e64822f --- /dev/null +++ b/lib/dateutil-stubs/tz/_common.pyi @@ -0,0 +1,28 @@ +import abc +from datetime import datetime, timedelta, tzinfo +from typing import ClassVar + +def tzname_in_python2(namefunc): ... +def enfold(dt: datetime, fold: int = 1): ... + +class _DatetimeWithFold(datetime): + @property + def fold(self): ... + +# Doesn't actually have ABCMeta as the metaclass at runtime, +# but mypy complains if we don't have it in the stub. +# See discussion in #8908 +class _tzinfo(tzinfo, metaclass=abc.ABCMeta): + def is_ambiguous(self, dt: datetime) -> bool: ... + def fromutc(self, dt: datetime) -> datetime: ... + +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 fromutc(self, dt: datetime) -> datetime: ... + def is_ambiguous(self, dt: datetime) -> bool: ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ diff --git a/lib/dateutil-stubs/tz/tz.pyi b/lib/dateutil-stubs/tz/tz.pyi new file mode 100644 index 00000000..6addba65 --- /dev/null +++ b/lib/dateutil-stubs/tz/tz.pyi @@ -0,0 +1,115 @@ +import datetime +from _typeshed import Incomplete +from typing import ClassVar, Literal, Protocol, TypeVar + +from ..relativedelta import relativedelta +from ._common import _tzinfo as _tzinfo, enfold as enfold, tzname_in_python2 as tzname_in_python2, tzrangebase as tzrangebase + +_DT = TypeVar("_DT", bound=datetime.datetime) + +ZERO: datetime.timedelta +EPOCH: datetime.datetime +EPOCHORDINAL: int + +class tzutc(datetime.tzinfo): + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def is_ambiguous(self, dt: datetime.datetime | None) -> bool: ... + def fromutc(self, dt: _DT) -> _DT: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ + +class tzoffset(datetime.tzinfo): + def __init__(self, name, offset) -> None: ... + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def is_ambiguous(self, dt: datetime.datetime | None) -> bool: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def fromutc(self, dt: _DT) -> _DT: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ + @classmethod + def instance(cls, name, offset) -> tzoffset: ... + +class tzlocal(_tzinfo): + def __init__(self) -> None: ... + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def is_ambiguous(self, dt: datetime.datetime | None) -> bool: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ + +class _ttinfo: + def __init__(self) -> None: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + +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.datetime | None, idx: int | None = None) -> bool: ... + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + def __reduce__(self): ... + def __reduce_ex__(self, protocol): ... + +class tzrange(tzrangebase): + hasdst: bool + def __init__( + self, + stdabbr: str, + stdoffset: int | datetime.timedelta | None = None, + dstabbr: str | None = None, + dstoffset: int | datetime.timedelta | None = None, + start: relativedelta | None = None, + end: relativedelta | None = None, + ) -> None: ... + def transitions(self, year: int) -> tuple[datetime.datetime, datetime.datetime]: ... + def __eq__(self, other): ... + +class tzstr(tzrange): + hasdst: bool + def __init__(self, s: str, posix_offset: bool = False) -> None: ... + @classmethod + def instance(cls, name, offset) -> tzoffset: ... + +class _ICalReader(Protocol): + # optional attribute: + # name: str + def read(self) -> str: ... + +class tzical: + def __init__(self, fileobj: str | _ICalReader) -> None: ... + def keys(self): ... + def get(self, tzid: Incomplete | None = None): ... + +TZFILES: list[str] +TZPATHS: list[str] + +def datetime_exists(dt: datetime.datetime, tz: datetime.tzinfo | None = None) -> bool: ... +def datetime_ambiguous(dt: datetime.datetime, tz: datetime.tzinfo | None = None) -> bool: ... +def resolve_imaginary(dt: datetime.datetime) -> datetime.datetime: ... + +class _GetTZ: + def __call__(self, name: str | None = ...) -> datetime.tzinfo | None: ... + def nocache(self, name: str | None) -> datetime.tzinfo | None: ... + +gettz: _GetTZ diff --git a/lib/dateutil-stubs/utils.pyi b/lib/dateutil-stubs/utils.pyi new file mode 100644 index 00000000..8c9f5e44 --- /dev/null +++ b/lib/dateutil-stubs/utils.pyi @@ -0,0 +1,5 @@ +from datetime import datetime, timedelta, tzinfo + +def default_tzinfo(dt: datetime, tzinfo: tzinfo) -> datetime: ... +def today(tzinfo: tzinfo | None = None) -> datetime: ... +def within_delta(dt1: datetime, dt2: datetime, delta: timedelta) -> bool: ... diff --git a/lib/dateutil-stubs/zoneinfo/__init__.pyi b/lib/dateutil-stubs/zoneinfo/__init__.pyi new file mode 100644 index 00000000..7bd2845a --- /dev/null +++ b/lib/dateutil-stubs/zoneinfo/__init__.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from typing import IO +from typing_extensions import TypeAlias + +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] + +_MetadataType: TypeAlias = dict[str, Incomplete] + +class ZoneInfoFile: + zones: dict[Incomplete, Incomplete] + metadata: _MetadataType | None + def __init__(self, zonefile_stream: IO[bytes] | None = None) -> None: ... + def get(self, name, default: Incomplete | None = None): ... + +def get_zonefile_instance(new_instance: bool = False) -> ZoneInfoFile: ... +def gettz(name): ... +def gettz_db_metadata() -> _MetadataType: ... diff --git a/lib/dateutil-stubs/zoneinfo/rebuild.pyi b/lib/dateutil-stubs/zoneinfo/rebuild.pyi new file mode 100644 index 00000000..e3845926 --- /dev/null +++ b/lib/dateutil-stubs/zoneinfo/rebuild.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete, StrOrBytesPath +from collections.abc import Sequence +from tarfile import TarInfo + +def rebuild( + filename: StrOrBytesPath, + tag: Incomplete | None = None, + format: str = "gz", + zonegroups: Sequence[str | TarInfo] = [], + metadata: Incomplete | None = None, +) -> None: ... diff --git a/lib/dateutil/__init__.py b/lib/dateutil/__init__.py index 0defb82e..a2c19c06 100644 --- a/lib/dateutil/__init__.py +++ b/lib/dateutil/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import sys + try: from ._version import version as __version__ except ImportError: @@ -6,3 +8,17 @@ except ImportError: __all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', 'utils', 'zoneinfo'] + +def __getattr__(name): + import importlib + + if name in __all__: + return importlib.import_module("." + name, __name__) + raise AttributeError( + "module {!r} has not attribute {!r}".format(__name__, name) + ) + + +def __dir__(): + # __dir__ should include all the lazy-importable modules as well. + return [x for x in globals() if x not in sys.modules] + __all__ diff --git a/lib/dateutil/_version.py b/lib/dateutil/_version.py index b723056a..ddda9809 100644 --- a/lib/dateutil/_version.py +++ b/lib/dateutil/_version.py @@ -1,5 +1,4 @@ -# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '2.8.2' -version_tuple = (2, 8, 2) +__version__ = version = '2.9.0.post0' +__version_tuple__ = version_tuple = (2, 9, 0) diff --git a/lib/dateutil/parser/isoparser.py b/lib/dateutil/parser/isoparser.py index 5d7bee38..7060087d 100644 --- a/lib/dateutil/parser/isoparser.py +++ b/lib/dateutil/parser/isoparser.py @@ -72,7 +72,7 @@ class isoparser(object): Common: - ``YYYY`` - - ``YYYY-MM`` or ``YYYYMM`` + - ``YYYY-MM`` - ``YYYY-MM-DD`` or ``YYYYMMDD`` Uncommon: diff --git a/lib/dateutil/relativedelta.py b/lib/dateutil/relativedelta.py index a9e85f7e..cd323a54 100644 --- a/lib/dateutil/relativedelta.py +++ b/lib/dateutil/relativedelta.py @@ -48,7 +48,7 @@ class relativedelta(object): the corresponding arithmetic operation on the original datetime value with the information in the relativedelta. - weekday: + weekday: One of the weekday instances (MO, TU, etc) available in the relativedelta module. These instances may receive a parameter N, specifying the Nth weekday, which could be positive or negative diff --git a/lib/dateutil/rrule.py b/lib/dateutil/rrule.py index b3203393..571a0d2b 100644 --- a/lib/dateutil/rrule.py +++ b/lib/dateutil/rrule.py @@ -182,7 +182,7 @@ class rrulebase(object): # __len__() introduces a large performance penalty. def count(self): """ Returns the number of recurrences in this set. It will have go - trough the whole recurrence, if this hasn't been done before. """ + through the whole recurrence, if this hasn't been done before. """ if self._len is None: for x in self: pass diff --git a/lib/dateutil/tz/tz.py b/lib/dateutil/tz/tz.py index c67f56d4..61759144 100644 --- a/lib/dateutil/tz/tz.py +++ b/lib/dateutil/tz/tz.py @@ -34,7 +34,7 @@ except ImportError: from warnings import warn ZERO = datetime.timedelta(0) -EPOCH = datetime.datetime.utcfromtimestamp(0) +EPOCH = datetime.datetime(1970, 1, 1, 0, 0) EPOCHORDINAL = EPOCH.toordinal() diff --git a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz index 524c48e1..1461f8c8 100644 Binary files a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz and b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz differ diff --git a/requirements.txt b/requirements.txt index 73b6e088..84451150 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ apscheduler==3.10.1 -arrow==1.2.3 +arrow==1.3.0 backports.csv==1.0.7 backports.functools-lru-cache==1.6.6 backports.zoneinfo==0.2.1;python_version<"3.9"