From 0fa86b09d1dd28369520c6fdda2ae29acd9bba4b Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Tue, 8 Jul 2025 16:30:38 -0400 Subject: [PATCH 01/14] Rework RecurrenceId to use date/datetime and range --- ical/types/recur.py | 108 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index 9bbfde3f..9e511e1d 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -52,7 +52,7 @@ ) from pydantic_core import CoreSchema, core_schema -from ical.parsing.property import ParsedProperty +from ical.parsing.property import ParsedProperty, ParsedPropertyParameter from ical.util import parse_date_and_datetime from .data_types import DATA_TYPE, serialize_field @@ -137,8 +137,11 @@ class Range(str, enum.Enum): """The range of the recurrence identifier and all subsequent values.""" -@DATA_TYPE.register(disable_value_param=True) -class RecurrenceId(str): +RecurIdInputDict = dict[str, datetime.date | datetime.datetime | bool] + + +@DATA_TYPE.register("RECURRENCE-ID", disable_value_param=True) +class RecurrenceId(BaseModel): """Identifies a specific instance of a recurring calendar component. A property type used in conjunction with the "UID" and "SEQUENCE" properties @@ -148,6 +151,18 @@ class RecurrenceId(str): recurrence id can reference a specific instance within the set. """ + date: Union[datetime.datetime, datetime.date] + """The date (all-day) or datetime of the recurrence to override.""" + + this_and_future: bool = False + """Whether this should affect future occurrence as well.""" + + class Config: + """Pydantic model configuration.""" + + validate_assignment = True + allow_population_by_field_name = True + @classmethod def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: """Convert a string RecurrenceId into a date or time value.""" @@ -173,27 +188,76 @@ def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: raise ValueError(f"Unable to parse date/time value: {errors}") @classmethod - def __parse_property_value__(cls, value: Any) -> RecurrenceId: - """Parse a calendar user address.""" - if isinstance(value, ParsedProperty): - value = cls._parse_value(value.value) - if isinstance(value, str): - value = cls._parse_value(value) - if isinstance(value, datetime.datetime): - value = DateTimeEncoder.__encode_property_json__(value) - elif isinstance(value, datetime.date): - value = DateEncoder.__encode_property_json__(value) - else: - value = str(value) - return RecurrenceId(value) + def __encode_property_value__(cls, data: dict[str, Any]) -> str: + for key, value in data.items(): + if key == "date": + if isinstance(value, str): + return value + if isinstance(value, datetime.datetime): + prop = DateTimeEncoder.__encode_property_json__(value) + prop_value = DateTimeEncoder.__encode_property_value__(prop) + if prop_value: + return prop_value + else: + return DateEncoder.__encode_property_json__(value) + raise ValueError("No date provided to RecurrenceId") @classmethod - def _parse_value(cls, value: str) -> datetime.datetime | datetime.date | str: - try: - return cls.to_value(value) - except ValueError: - pass - return str(value) + def __encode_property_params__( + cls, value: str | dict[str, str | datetime.date | datetime.datetime] + ) -> list[ParsedPropertyParameter]: + if not isinstance(value, dict): + return [] + + params = [] + for key, param in value.items(): + if key == "date": + if isinstance(param, datetime.datetime): + prop = DateTimeEncoder.__encode_property_json__(param) + params.extend(DateTimeEncoder.__encode_property_params__(prop)) + params.append(ParsedPropertyParameter("VALUE", ["DATE-TIME"])) + else: + params.append(ParsedPropertyParameter("VALUE", ["DATE"])) + elif key == "this_and_future": + if param: + params.append(ParsedPropertyParameter("RANGE", ["THISANDFUTURE"])) + return params + + @classmethod + def __parse_property_value__( + cls, prop: ParsedProperty | datetime.datetime | datetime.date + ) -> RecurIdInputDict: + if isinstance(prop, datetime.datetime) or isinstance(prop, datetime.date): + return {"date": prop} + + result: RecurIdInputDict = {} + mode = prop.get_parameter_value("MODE") + if mode: + if mode == "DATE": + value = DateEncoder.__parse_property_value__(prop) + else: + value = DateTimeEncoder.__parse_property_value__(prop) + if value is None: + raise ValueError(f"Cannot parse {prop} as a DATE") + result["date"] = value + else: + try: + value = DateEncoder.__parse_property_value__(prop) + if value is None: + raise ValueError() + result["date"] = value + except ValueError as err: + try: + value = DateTimeEncoder.__parse_property_value__(prop) + if value is None: + raise ValueError() + result["date"] = value + except ValueError as err: + raise ValueError(f"Unable to parse {prop} as DATE or DATE-TIME") + range_param = prop.get_parameter_value("RANGE") + if range_param and range_param == "THISANDFUTURE": + result["this_and_future"] = True + return result @classmethod def __get_pydantic_core_schema__( From 1f52b864a72df6bcf1dae739b6377dfff222687b Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Tue, 8 Jul 2025 16:54:54 -0400 Subject: [PATCH 02/14] Improve RecurrenceId input parsing --- ical/types/recur.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index 9e511e1d..7cfa6717 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -193,18 +193,24 @@ def __encode_property_value__(cls, data: dict[str, Any]) -> str: if key == "date": if isinstance(value, str): return value + if isinstance(value, dict): + prop_value = DateTimeEncoder.__encode_property_value__(value) + if prop_value: + return prop_value if isinstance(value, datetime.datetime): prop = DateTimeEncoder.__encode_property_json__(value) prop_value = DateTimeEncoder.__encode_property_value__(prop) if prop_value: return prop_value - else: - return DateEncoder.__encode_property_json__(value) + return DateEncoder.__encode_property_json__(value) raise ValueError("No date provided to RecurrenceId") @classmethod def __encode_property_params__( - cls, value: str | dict[str, str | datetime.date | datetime.datetime] + cls, + value: ( + str | dict[str, str | dict[str, str] | datetime.date | datetime.datetime] + ), ) -> list[ParsedPropertyParameter]: if not isinstance(value, dict): return [] @@ -212,7 +218,10 @@ def __encode_property_params__( params = [] for key, param in value.items(): if key == "date": - if isinstance(param, datetime.datetime): + if isinstance(param, dict): + params.extend(DateTimeEncoder.__encode_property_params__(param)) + params.append(ParsedPropertyParameter("VALUE", ["DATE-TIME"])) + elif isinstance(param, datetime.datetime): prop = DateTimeEncoder.__encode_property_json__(param) params.extend(DateTimeEncoder.__encode_property_params__(prop)) params.append(ParsedPropertyParameter("VALUE", ["DATE-TIME"])) From b6760d35280997b61d2291e8c3200b75c8ff7768 Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Mon, 16 Mar 2026 12:05:50 -0400 Subject: [PATCH 03/14] Update for Pydantic v2 --- ical/types/recur.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index 7cfa6717..b6509d9b 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -161,7 +161,7 @@ class Config: """Pydantic model configuration.""" validate_assignment = True - allow_population_by_field_name = True + populate_by_name = True @classmethod def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: From e57c77d6a15213169329fee5476817553dd781ce Mon Sep 17 00:00:00 2001 From: Brian Manson Date: Wed, 18 Mar 2026 15:32:00 -0500 Subject: [PATCH 04/14] Handle props that are passed in as date, datetime. --- ical/types/recur.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index b6509d9b..ddefe0f2 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -240,7 +240,13 @@ def __parse_property_value__( return {"date": prop} result: RecurIdInputDict = {} - mode = prop.get_parameter_value("MODE") + if isinstance(prop, ParsedProperty): + mode = prop.get_parameter_value("MODE") + elif isinstance(prop, datetime.datetime): + mode = "DATETIME" + elif isinstance(prop, datetime.date): + mode = "DATE" + if mode: if mode == "DATE": value = DateEncoder.__parse_property_value__(prop) @@ -263,9 +269,11 @@ def __parse_property_value__( result["date"] = value except ValueError as err: raise ValueError(f"Unable to parse {prop} as DATE or DATE-TIME") - range_param = prop.get_parameter_value("RANGE") - if range_param and range_param == "THISANDFUTURE": - result["this_and_future"] = True + + if isinstance(prop, ParsedProperty): + range_param = prop.get_parameter_value("RANGE") + if range_param and range_param == "THISANDFUTURE": + result["this_and_future"] = True return result @classmethod From 0705aa0b388f486713b3d7b6b95e99611973c402 Mon Sep 17 00:00:00 2001 From: Brian Manson Date: Wed, 18 Mar 2026 16:35:23 -0500 Subject: [PATCH 05/14] Missed a test case where prop is an instance of RecurrenceId --- ical/types/recur.py | 46 +++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index ddefe0f2..ac640710 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -242,38 +242,40 @@ def __parse_property_value__( result: RecurIdInputDict = {} if isinstance(prop, ParsedProperty): mode = prop.get_parameter_value("MODE") - elif isinstance(prop, datetime.datetime): - mode = "DATETIME" - elif isinstance(prop, datetime.date): - mode = "DATE" - - if mode: - if mode == "DATE": - value = DateEncoder.__parse_property_value__(prop) - else: - value = DateTimeEncoder.__parse_property_value__(prop) - if value is None: - raise ValueError(f"Cannot parse {prop} as a DATE") - result["date"] = value - else: - try: - value = DateEncoder.__parse_property_value__(prop) + if mode: + if mode == "DATE": + value = DateEncoder.__parse_property_value__(prop) + else: + value = DateTimeEncoder.__parse_property_value__(prop) if value is None: - raise ValueError() + raise ValueError(f"Cannot parse {prop} as a DATE") result["date"] = value - except ValueError as err: + else: try: - value = DateTimeEncoder.__parse_property_value__(prop) + value = DateEncoder.__parse_property_value__(prop) if value is None: raise ValueError() result["date"] = value except ValueError as err: - raise ValueError(f"Unable to parse {prop} as DATE or DATE-TIME") - - if isinstance(prop, ParsedProperty): + try: + value = DateTimeEncoder.__parse_property_value__(prop) + if value is None: + raise ValueError() + result["date"] = value + except ValueError as err: + raise ValueError(f"Unable to parse {prop} as DATE or DATE-TIME") + range_param = prop.get_parameter_value("RANGE") if range_param and range_param == "THISANDFUTURE": result["this_and_future"] = True + + elif isinstance(prop, dict): + prop_date = prop.get("date") + if prop_date and (isinstance(prop_date, datetime.datetime) or isinstance(prop_date, datetime.date)): + result["date"] = prop_date + elif isinstance(prop, RecurrenceId): + result = prop + return result @classmethod From 57834be37a022ccabba3017e91597b52b7201183 Mon Sep 17 00:00:00 2001 From: Brian Manson Date: Thu, 19 Mar 2026 10:32:46 -0500 Subject: [PATCH 06/14] Update type hinting --- ical/types/recur.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index ac640710..00d3b48a 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -234,11 +234,8 @@ def __encode_property_params__( @classmethod def __parse_property_value__( - cls, prop: ParsedProperty | datetime.datetime | datetime.date + cls, prop: ParsedProperty | dict | RecurrenceId ) -> RecurIdInputDict: - if isinstance(prop, datetime.datetime) or isinstance(prop, datetime.date): - return {"date": prop} - result: RecurIdInputDict = {} if isinstance(prop, ParsedProperty): mode = prop.get_parameter_value("MODE") From d0c7593c3a152ed90bdcd55a8aa1b72c8f2b726f Mon Sep 17 00:00:00 2001 From: Brian Manson Date: Thu, 19 Mar 2026 16:17:39 -0500 Subject: [PATCH 07/14] Fixing an error with hashing the recurrenceId --- ical/types/recur.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ical/types/recur.py b/ical/types/recur.py index 00d3b48a..f520ad9f 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -163,6 +163,14 @@ class Config: validate_assignment = True populate_by_name = True + def __eq__(self, other): + if not isinstance(other, RecurrenceId): + return NotImplemented + return self.date == other.date and self.this_and_future == other.this_and_future + + def __hash__(self): + return hash((self.date, self.this_and_future)) + @classmethod def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: """Convert a string RecurrenceId into a date or time value.""" From 1721964bbb2d0a0d71c7ad9a5700a0acfa647ec0 Mon Sep 17 00:00:00 2001 From: Brian Manson Date: Mon, 23 Mar 2026 14:01:10 -0500 Subject: [PATCH 08/14] Fix iterating over a recurrence. --- ical/recur_adapter.py | 2 +- ical/types/recur.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ical/recur_adapter.py b/ical/recur_adapter.py index 0d1ea9d1..c5a59f6e 100644 --- a/ical/recur_adapter.py +++ b/ical/recur_adapter.py @@ -39,7 +39,7 @@ def _recurrence_id_for(dt: _DateOrDatetime) -> RecurrenceId: # the context of dtstart which may have a timezone. if isinstance(dt, datetime.datetime) and dt.tzinfo: dt = dt.replace(tzinfo=None) - return RecurrenceId.__parse_property_value__(dt) + return RecurrenceId.model_validate(RecurrenceId.__parse_property_value__(dt)) class FilteredRecurrenceIterable(Iterable[_DateOrDatetime]): diff --git a/ical/types/recur.py b/ical/types/recur.py index f520ad9f..c811ae06 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -242,8 +242,11 @@ def __encode_property_params__( @classmethod def __parse_property_value__( - cls, prop: ParsedProperty | dict | RecurrenceId + cls, prop: ParsedProperty | dict | RecurrenceId | datetime.datetime | datetime.date ) -> RecurIdInputDict: + if isinstance(prop, datetime.datetime) or isinstance(prop, datetime.date): + return {"date": prop} + result: RecurIdInputDict = {} if isinstance(prop, ParsedProperty): mode = prop.get_parameter_value("MODE") From 914876e9897546d0f618b46b450481fde4d098fc Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Thu, 16 Apr 2026 15:32:42 -0400 Subject: [PATCH 09/14] Update RecurrenceId to use new dunder methods --- ical/types/recur.py | 185 ++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 93 deletions(-) diff --git a/ical/types/recur.py b/ical/types/recur.py index c811ae06..76204b63 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -40,6 +40,7 @@ import re from dataclasses import dataclass from typing import Annotated, Any, Literal, Optional, Union +from zoneinfo import ZoneInfo from dateutil import rrule from pydantic import ( @@ -151,33 +152,66 @@ class RecurrenceId(BaseModel): recurrence id can reference a specific instance within the set. """ - date: Union[datetime.datetime, datetime.date] + date: Annotated[ + Union[datetime.date, datetime.datetime], + BeforeValidator(parse_date_and_datetime), + ] """The date (all-day) or datetime of the recurrence to override.""" this_and_future: bool = False """Whether this should affect future occurrence as well.""" - class Config: - """Pydantic model configuration.""" + @property + def range(self) -> Range: + return Range.THIS_AND_FUTURE if self.this_and_future else Range.NONE - validate_assignment = True - populate_by_name = True + model_config = ConfigDict( + populate_by_name=True, validate_assignment=True, arbitrary_types_allowed=True + ) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if not isinstance(other, RecurrenceId): return NotImplemented return self.date == other.date and self.this_and_future == other.this_and_future - def __hash__(self): + def __hash__(self) -> int: return hash((self.date, self.this_and_future)) + serialize_fields = field_serializer("*")(serialize_field) # type: ignore[pydantic-field] + + @classmethod + def from_value( + cls, + recurrence_id: RecurrenceId | str | datetime.date | datetime.datetime, + this_and_future: Range | bool | None = None, + timezone: datetime.tzinfo | None = None, + ) -> "RecurrenceId": + if isinstance(recurrence_id, RecurrenceId): + date = recurrence_id.date + future = recurrence_id.this_and_future + elif isinstance(recurrence_id, str): + date = cls.string_to_date(recurrence_id) + future = False + else: + date = recurrence_id + future = False + if this_and_future is not None: + if isinstance(this_and_future, Range): + future = this_and_future == Range.THIS_AND_FUTURE + else: + future = this_and_future + if timezone is not None: + if isinstance(date, datetime.datetime): + date = date.astimezone(timezone) + return RecurrenceId(date=date, this_and_future=future) + @classmethod - def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: + def string_to_date(cls, date_str: str) -> datetime.datetime | datetime.date: """Convert a string RecurrenceId into a date or time value.""" errors = [] try: date_value = DateEncoder.__parse_property_value__( - ParsedProperty(name="", value=recurrence_id) + ParsedProperty(name="", value=date_str) ) if date_value: return date_value @@ -186,7 +220,7 @@ def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: try: date_time_value = DateTimeEncoder.__parse_property_value__( - ParsedProperty(name="", value=recurrence_id) + ParsedProperty(name="", value=date_str) ) if date_time_value: return date_time_value @@ -196,95 +230,60 @@ def to_value(cls, recurrence_id: str) -> datetime.datetime | datetime.date: raise ValueError(f"Unable to parse date/time value: {errors}") @classmethod - def __encode_property_value__(cls, data: dict[str, Any]) -> str: - for key, value in data.items(): - if key == "date": - if isinstance(value, str): - return value - if isinstance(value, dict): - prop_value = DateTimeEncoder.__encode_property_value__(value) - if prop_value: - return prop_value - if isinstance(value, datetime.datetime): - prop = DateTimeEncoder.__encode_property_json__(value) - prop_value = DateTimeEncoder.__encode_property_value__(prop) - if prop_value: - return prop_value - return DateEncoder.__encode_property_json__(value) - raise ValueError("No date provided to RecurrenceId") + def __encode_property__(cls, model_data: dict[str, Any]) -> ParsedProperty: + params = [] - @classmethod - def __encode_property_params__( - cls, - value: ( - str | dict[str, str | dict[str, str] | datetime.date | datetime.datetime] - ), - ) -> list[ParsedPropertyParameter]: - if not isinstance(value, dict): - return [] + date_value = model_data["date"] + if isinstance(date_value, str): + prop = ParsedProperty(name="recurrence-id", value=date_value) + else: + prop = ParsedProperty(name="recurrence-id", value=date_value["VALUE"]) + if "TZID" in date_value: + params.append( + ParsedPropertyParameter(name="TZID", values=[date_value["TZID"]]) + ) - params = [] - for key, param in value.items(): - if key == "date": - if isinstance(param, dict): - params.extend(DateTimeEncoder.__encode_property_params__(param)) - params.append(ParsedPropertyParameter("VALUE", ["DATE-TIME"])) - elif isinstance(param, datetime.datetime): - prop = DateTimeEncoder.__encode_property_json__(param) - params.extend(DateTimeEncoder.__encode_property_params__(prop)) - params.append(ParsedPropertyParameter("VALUE", ["DATE-TIME"])) - else: - params.append(ParsedPropertyParameter("VALUE", ["DATE"])) - elif key == "this_and_future": - if param: - params.append(ParsedPropertyParameter("RANGE", ["THISANDFUTURE"])) - return params + date_type = cls.string_to_date(prop.value) + if isinstance(date_type, datetime.datetime): + params.append(ParsedPropertyParameter("VALUE", ["DATE-TIME"])) + else: + params.append(ParsedPropertyParameter("VALUE", ["DATE"])) + + if model_data.get("this_and_future"): + params.append( + ParsedPropertyParameter("RANGE", [Range.THIS_AND_FUTURE.value]) + ) + + if params: + prop.params = params + return prop @classmethod def __parse_property_value__( - cls, prop: ParsedProperty | dict | RecurrenceId | datetime.datetime | datetime.date - ) -> RecurIdInputDict: - if isinstance(prop, datetime.datetime) or isinstance(prop, datetime.date): - return {"date": prop} - - result: RecurIdInputDict = {} - if isinstance(prop, ParsedProperty): - mode = prop.get_parameter_value("MODE") - if mode: - if mode == "DATE": - value = DateEncoder.__parse_property_value__(prop) - else: - value = DateTimeEncoder.__parse_property_value__(prop) - if value is None: - raise ValueError(f"Cannot parse {prop} as a DATE") - result["date"] = value - else: - try: - value = DateEncoder.__parse_property_value__(prop) - if value is None: - raise ValueError() - result["date"] = value - except ValueError as err: - try: - value = DateTimeEncoder.__parse_property_value__(prop) - if value is None: - raise ValueError() - result["date"] = value - except ValueError as err: - raise ValueError(f"Unable to parse {prop} as DATE or DATE-TIME") - - range_param = prop.get_parameter_value("RANGE") - if range_param and range_param == "THISANDFUTURE": - result["this_and_future"] = True - - elif isinstance(prop, dict): - prop_date = prop.get("date") - if prop_date and (isinstance(prop_date, datetime.datetime) or isinstance(prop_date, datetime.date)): - result["date"] = prop_date + cls, prop: ParsedProperty | dict[str, Any] | datetime.datetime | datetime.date + ) -> RecurrenceId | dict[str, Any]: + if isinstance(prop, dict): + return prop + elif isinstance(prop, datetime.datetime) or isinstance(prop, datetime.date): + return cls.from_value(prop) elif isinstance(prop, RecurrenceId): - result = prop - - return result + return prop + else: + this_and_future = False + timezone: datetime.tzinfo | None = None + if prop.params: + for param in prop.params: + if param.name == "RANGE" and Range.THIS_AND_FUTURE in param.values: + this_and_future = True + if param.name == "TZID" and param.values: + timezone_value = param.values[0] + if isinstance(timezone_value, str): + timezone = ZoneInfo(timezone_value) + else: + timezone = timezone_value + return cls.from_value( + prop.value, this_and_future=this_and_future, timezone=timezone + ) @classmethod def __get_pydantic_core_schema__( From 43ece515bd6d8b3f46d77993ec62267e04a58d81 Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Thu, 16 Apr 2026 15:34:40 -0400 Subject: [PATCH 10/14] Use RecurrenceId date directly to filter recurrence iterator --- ical/recur_adapter.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/ical/recur_adapter.py b/ical/recur_adapter.py index c5a59f6e..62a48744 100644 --- a/ical/recur_adapter.py +++ b/ical/recur_adapter.py @@ -28,20 +28,6 @@ _DateOrDatetime = datetime.datetime | datetime.date -def _recurrence_id_for(dt: _DateOrDatetime) -> RecurrenceId: - """Compute the RecurrenceId for a recurrence date. - - This converts a date/datetime from a recurrence expansion into the - floating-time RecurrenceId string used to identify that instance. - """ - # Make recurrence_id floating time to avoid dealing with serializing - # TZID. This value will still be unique within the series and is in - # the context of dtstart which may have a timezone. - if isinstance(dt, datetime.datetime) and dt.tzinfo: - dt = dt.replace(tzinfo=None) - return RecurrenceId.model_validate(RecurrenceId.__parse_property_value__(dt)) - - class FilteredRecurrenceIterable(Iterable[_DateOrDatetime]): """An iterable that filters out dates from a recurrence expansion. @@ -52,7 +38,7 @@ class FilteredRecurrenceIterable(Iterable[_DateOrDatetime]): def __init__( self, recur: Iterable[_DateOrDatetime], - exclude_ids: frozenset[RecurrenceId], + exclude_ids: frozenset[datetime.date | datetime.datetime], ) -> None: """Initialize the filtered iterable.""" self._recur = recur @@ -61,7 +47,7 @@ def __init__( def __iter__(self) -> Iterator[_DateOrDatetime]: """Iterate over recurrence dates, excluding overridden ones.""" for dt in self._recur: - if _recurrence_id_for(dt) not in self._exclude_ids: + if dt not in self._exclude_ids: yield dt @@ -89,7 +75,7 @@ def get( """Return a lazy sortable item.""" dtend = dtstart + self._duration if self._duration else dtstart - recurrence_id = _recurrence_id_for(dtstart) + recurrence_id = RecurrenceId.from_value(dtstart) def build() -> ItemType: updates = { @@ -137,7 +123,7 @@ def merge_and_expand_items( # An edited instance has a recurrence_id (identifying which # instance it replaces) but no rrule (it's a single instance). exclude_ids = frozenset( - item.recurrence_id + item.recurrence_id.date for item in uid_items if item.recurrence_id and not item.rrule ) From bfffa0cd1aac9a1b01cd6e42acb85ec7cfaa3493 Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Thu, 16 Apr 2026 15:39:16 -0400 Subject: [PATCH 11/14] Add support for RecurrenceId to store edit and delete --- ical/store.py | 104 ++++++++++++++++++++--------------------- uv.lock | 125 ++++++++++++++++++++++++-------------------------- 2 files changed, 111 insertions(+), 118 deletions(-) diff --git a/ical/store.py b/ical/store.py index f31ca53e..30360701 100644 --- a/ical/store.py +++ b/ical/store.py @@ -72,7 +72,7 @@ def _ensure_timezone( ) from err -def _match_item(item: _T, uid: str, recurrence_id: str | None) -> bool: +def _match_item(item: _T, uid: str, recurrence_id: RecurrenceId | None) -> bool: """Return True if the item is an instance of a recurring event.""" if item.uid != uid: return False @@ -81,21 +81,23 @@ def _match_item(item: _T, uid: str, recurrence_id: str | None) -> bool: return True # Match a single item with the specified recurrence_id. If the item is an # edited instance match return it - if item.recurrence_id == recurrence_id: + if isinstance(recurrence_id.date, datetime.datetime) and isinstance( + item.dtstart, datetime.datetime + ): + # Add timezone info if we're missing it + if item.dtstart.tzinfo is not None: + recurrence_id = recurrence_id.from_value( + recurrence_id, timezone=item.dtstart.tzinfo + ) + + dtstart = recurrence_id.date + if item.recurrence_id and item.recurrence_id.date == dtstart: _LOGGER.debug("Matched exact recurrence_id: %s", item) return True # Otherwise, determine if this instance is in the series _LOGGER.debug( "Expanding item %s %s to look for match of %s", uid, item.dtstart, recurrence_id ) - dtstart = RecurrenceId.to_value(recurrence_id) - if isinstance(dtstart, datetime.datetime) and isinstance( - item.dtstart, datetime.datetime - ): - # The recurrence_id does not support timezone information, so put it in the - # same timezone as the item to compare. - if item.dtstart.tzinfo is not None: - dtstart = dtstart.replace(tzinfo=item.dtstart.tzinfo) for dt in item.as_rrule() or (): if isinstance(dt, datetime.datetime): if dt.date() > _MAX_SCAN_DATE: @@ -111,7 +113,7 @@ def _match_item(item: _T, uid: str, recurrence_id: str | None) -> bool: def _match_items( - items: list[_T], uid: str, recurrence_id: str | None + items: list[_T], uid: str, recurrence_id: RecurrenceId | None ) -> Generator[tuple[int, _T], None, None]: """Return items from the list that match the uid and recurrence_id.""" for index, item in enumerate(items): @@ -122,8 +124,7 @@ def _match_items( def _prepare_update( store_item: Event | Todo, item: Event | Todo, - recurrence_id: str | None = None, - recurrence_range: Range = Range.NONE, + recurrence_id: RecurrenceId | None = None, ) -> dict[str, Any]: """Prepare an update to an existing event.""" partial_update = item.model_dump( @@ -162,7 +163,7 @@ def _prepare_update( "recurrence_id": recurrence_id, } ) - if recurrence_range == Range.NONE: + if recurrence_id.range == Range.NONE: # The new event copied from the original is a single instance, # which is not recurring. update["rrule"] = None @@ -171,9 +172,7 @@ def _prepare_update( update["created"] = item.dtstamp # Adjust start and end time of the event - dtstart: datetime.datetime | datetime.date = RecurrenceId.to_value( - recurrence_id - ) + dtstart: datetime.datetime | datetime.date = recurrence_id.date if item.dtstart: dtstart = item.dtstart update["dtstart"] = dtstart @@ -245,8 +244,12 @@ def add(self, item: _T) -> _T: def delete( self, uid: str, - recurrence_id: str | None = None, - recurrence_range: Range = Range.NONE, + recurrence_id: RecurrenceId + | datetime.datetime + | datetime.date + | str + | None = None, + recurrence_range: Range | None = None, ) -> None: """Delete the item from the calendar. @@ -260,27 +263,28 @@ def delete( When deleting individual instances, the range property may specify if deletion of just a specific instance, or a range of instances. """ + recurrence = None + if recurrence_id: + recurrence = RecurrenceId.from_value(recurrence_id, recurrence_range) + items_to_delete: list[_T] = [ - item for _, item in _match_items(self._items, uid, recurrence_id) + item for _, item in _match_items(self._items, uid, recurrence) ] if not items_to_delete: raise self._exc( - f"No existing item with uid/recurrence_id: {uid}/{recurrence_id}" + f"No existing item with uid/recurrence_id: {uid}/{recurrence}" ) for store_item in items_to_delete: - self._apply_delete(store_item, recurrence_id, recurrence_range) + self._apply_delete(store_item, recurrence) def _apply_delete( - self, - store_item: _T, - recurrence_id: str | None = None, - recurrence_range: Range = Range.NONE, + self, store_item: _T, recurrence_id: RecurrenceId | None = None ) -> None: if ( recurrence_id - and recurrence_range == Range.THIS_AND_FUTURE - and RecurrenceId.to_value(recurrence_id) == store_item.dtstart + and recurrence_id.range == Range.THIS_AND_FUTURE + and recurrence_id.date == store_item.dtstart ): # Editing the first instance and all forward is the same as editing the # entire series so don't bother forking a new event @@ -302,18 +306,10 @@ def _apply_delete( self._items.remove(store_item) return - exdate = RecurrenceId.to_value(recurrence_id) - if recurrence_range == Range.NONE: + exdate = recurrence_id.date + if recurrence_id.range == Range.NONE: # A single recurrence instance is removed. Add an exclusion to # to the event. - # RecurrenceId does not support timezone information. The exclusion - # must have the same timezone as the item to compare. - if ( - isinstance(exdate, datetime.datetime) - and isinstance(store_item.dtstart, datetime.datetime) - and store_item.dtstart.tzinfo - ): - exdate = exdate.replace(tzinfo=store_item.dtstart.tzinfo) store_item.exdate.append(exdate) return @@ -338,7 +334,11 @@ def edit( self, uid: str, item: _T, - recurrence_id: str | None = None, + recurrence_id: RecurrenceId + | datetime.datetime + | datetime.date + | str + | None = None, recurrence_range: Range = Range.NONE, ) -> None: """Update the item with the specified uid. @@ -359,39 +359,39 @@ def edit( `ical.timezone.Timezone` needed to fully specify the item time information when encoded. """ + recurrence = None + if recurrence_id: + recurrence = RecurrenceId.from_value(recurrence_id, recurrence_range) + items_to_edit: list[tuple[int, _T]] = [ - (index, item) - for index, item in _match_items(self._items, uid, recurrence_id) + (index, item) for index, item in _match_items(self._items, uid, recurrence) ] if not items_to_edit: raise self._exc( - f"No existing item with uid/recurrence_id: {uid}/{recurrence_id}" + f"No existing item with uid/recurrence_id: {uid}/{recurrence}" ) for store_index, store_item in items_to_edit: - self._apply_edit( - store_index, store_item, item, recurrence_id, recurrence_range - ) + self._apply_edit(store_index, store_item, item, recurrence) def _apply_edit( self, store_index: int, store_item: _T, item: _T, - recurrence_id: str | None = None, - recurrence_range: Range = Range.NONE, + recurrence_id: RecurrenceId | None = None, ) -> None: if ( recurrence_id - and recurrence_range == Range.THIS_AND_FUTURE - and RecurrenceId.to_value(recurrence_id) == store_item.dtstart + and recurrence_id.range == Range.THIS_AND_FUTURE + and recurrence_id.date == store_item.dtstart ): # Editing the first instance and all forward is the same as editing the # entire series so don't bother forking a new item recurrence_id = None - update = _prepare_update(store_item, item, recurrence_id, recurrence_range) - if recurrence_range == Range.NONE: + update = _prepare_update(store_item, item, recurrence_id) + if recurrence_id and recurrence_id.range == Range.NONE: # Changing the recurrence rule of a single item in the middle of the series # is not allowed. It is allowed to convert a single instance item to recurring. if item.rrule and store_item.rrule: @@ -439,7 +439,7 @@ def _apply_edit( self.delete( store_item.uid, recurrence_id=recurrence_id, - recurrence_range=recurrence_range, + recurrence_range=recurrence_id.range if recurrence_id else None, ) self._items.insert(store_index, new_item) diff --git a/uv.lock b/uv.lock index a3ef16c9..79145ec8 100644 --- a/uv.lock +++ b/uv.lock @@ -1,18 +1,19 @@ version = 1 +revision = 3 requires-python = ">=3.13" [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "ical" -version = "13.0.1" +version = "13.2.2" source = { editable = "." } dependencies = [ { name = "pydantic" }, @@ -37,9 +38,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591 } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580 }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] @@ -49,58 +50,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403 }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206 }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307 }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258 }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917 }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186 }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164 }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146 }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788 }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133 }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852 }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679 }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766 }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005 }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622 }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725 }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040 }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691 }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897 }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302 }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877 }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680 }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960 }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102 }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039 }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126 }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489 }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288 }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255 }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760 }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092 }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385 }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832 }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585 }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078 }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914 }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560 }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244 }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955 }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906 }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607 }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769 }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441 }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291 }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632 }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905 }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495 }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388 }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879 }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017 }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, ] [[package]] @@ -110,27 +103,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] @@ -140,16 +133,16 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949 } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] From 8165868ab80ec11cdeff828652316ca756ee3505 Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Thu, 16 Apr 2026 15:43:17 -0400 Subject: [PATCH 12/14] Update tests to use RecurrenceId model --- tests/__snapshots__/test_calendar_stream.ambr | 7 +- tests/__snapshots__/test_store.ambr | 501 ++++++++++++++---- .../__snapshots__/test_ics_examples.ambr | 48 +- tests/examples/test_ics_examples.py | 19 +- tests/test_list.py | 10 +- tests/test_store.py | 136 ++++- tests/test_timeline.py | 18 +- tests/types/test_recur.py | 10 +- 8 files changed, 572 insertions(+), 177 deletions(-) diff --git a/tests/__snapshots__/test_calendar_stream.ambr b/tests/__snapshots__/test_calendar_stream.ambr index bfc11746..0e98453f 100644 --- a/tests/__snapshots__/test_calendar_stream.ambr +++ b/tests/__snapshots__/test_calendar_stream.ambr @@ -915,7 +915,10 @@ 'dtstart': '2022-08-01', 'last_modified': '2022-07-31T19:03:39Z', 'location': '', - 'recurrence_id': '20220901', + 'recurrence_id': dict({ + 'date': '2022-09-01', + 'this_and_future': False, + }), 'rrule': dict({ 'by_month_day': list([ 1, @@ -1952,7 +1955,7 @@ DESCRIPTION: LAST-MODIFIED:20220731T190339Z LOCATION: - RECURRENCE-ID:20220901 + RECURRENCE-ID;VALUE=DATE:20220901 RRULE:FREQ=MONTHLY;BYMONTHDAY=1 SEQUENCE:0 STATUS:CONFIRMED diff --git a/tests/__snapshots__/test_store.ambr b/tests/__snapshots__/test_store.ambr index ecfbea37..107355d9 100644 --- a/tests/__snapshots__/test_store.ambr +++ b/tests/__snapshots__/test_store.ambr @@ -82,19 +82,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Daily meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-08-30T09:00:00', - 'recurrence_id': '20220830T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 30, 9, 0), + 'this_and_future': False, + }), 'summary': 'Daily meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-08-31T09:00:00', - 'recurrence_id': '20220831T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 31, 9, 0), + 'this_and_future': False, + }), 'summary': 'Daily meeting', 'uid': 'mock-uid-1', }), @@ -117,19 +126,28 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -139,13 +157,19 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -177,7 +201,7 @@ list([ tuple( '2024-01-09', - '20240109', + RecurrenceId(date=FakeDate(2024, 1, 9), this_and_future=False), None, list([ ]), @@ -196,7 +220,7 @@ list([ tuple( '2024-01-09', - '20240109', + RecurrenceId(date=FakeDate(2024, 1, 9), this_and_future=False), None, list([ ]), @@ -216,7 +240,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240109', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 9), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -227,7 +254,10 @@ list([ dict({ 'due': '2024-01-12', - 'recurrence_id': '20240111', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 11), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -238,7 +268,10 @@ list([ dict({ 'due': '2024-01-13', - 'recurrence_id': '20240112', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 12), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -262,19 +295,28 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Mondays', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'summary': 'Mondays', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Mondays', 'uid': 'mock-uid-1', }), @@ -284,19 +326,28 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Mondays', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'summary': 'Mondays', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Mondays', 'uid': 'mock-uid-1', }), @@ -306,19 +357,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -328,19 +388,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -350,19 +419,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26T09:00:00', - 'recurrence_id': '20220926T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 26, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -372,19 +450,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26T09:00:00', - 'recurrence_id': '20220926T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 26, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -421,19 +508,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-19T09:00:00', - 'recurrence_id': '20220919T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 19, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), @@ -443,19 +539,28 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26', - 'recurrence_id': '20220926', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 26), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), @@ -465,19 +570,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26T09:00:00', - 'recurrence_id': '20220926T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 26, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), @@ -487,21 +601,30 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'sequence': 0, 'summary': 'Monday event', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'sequence': 1, 'summary': 'Tuesday event', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'sequence': 0, 'summary': 'Monday event', 'uid': 'mock-uid-1', @@ -512,21 +635,30 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'sequence': 0, 'summary': 'Monday event', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'sequence': 1, 'summary': 'Tuesday event', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'sequence': 0, 'summary': 'Monday event', 'uid': 'mock-uid-1', @@ -537,19 +669,28 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Monday', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), @@ -559,19 +700,28 @@ list([ dict({ 'dtstart': '2022-08-29', - 'recurrence_id': '20220829', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 8, 29), + 'this_and_future': False, + }), 'summary': 'Monday', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05', - 'recurrence_id': '20220905', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 5), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12', - 'recurrence_id': '20220912', + 'recurrence_id': dict({ + 'date': FakeDate(2022, 9, 12), + 'this_and_future': False, + }), 'summary': 'Mondays [edit]', 'uid': 'mock-uid-1', }), @@ -581,19 +731,28 @@ list([ dict({ 'dtstart': '2022-08-30T09:00:00', - 'recurrence_id': '20220830T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 30, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06T09:00:00', - 'recurrence_id': '20220906T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 6, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-13T09:00:00', - 'recurrence_id': '20220913T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 13, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), @@ -603,19 +762,28 @@ list([ dict({ 'dtstart': '2022-08-30T09:00:00', - 'recurrence_id': '20220830T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 30, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06T09:00:00', - 'recurrence_id': '20220906T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 6, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-13T09:00:00', - 'recurrence_id': '20220913T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 13, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), @@ -625,19 +793,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -647,19 +824,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -669,19 +855,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), @@ -691,19 +886,28 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Team meeting', 'uid': 'mock-uid-1', }), @@ -713,13 +917,19 @@ list([ dict({ 'dtstart': '2022-08-30T09:00:00', - 'recurrence_id': '20220830T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 30, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-06T09:00:00', - 'recurrence_id': '20220906T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 6, 9, 0), + 'this_and_future': False, + }), 'summary': 'Tuesday meeting', 'uid': 'mock-uid-1', }), @@ -850,7 +1060,10 @@ list([ dict({ 'due': '2024-01-07', - 'recurrence_id': '20240106', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 6), + 'this_and_future': False, + }), 'status': , 'summary': 'Wash car (Sa)', 'uid': 'mock-uid-1', @@ -861,7 +1074,10 @@ list([ dict({ 'due': '2024-01-07', - 'recurrence_id': '20240106', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 6), + 'this_and_future': False, + }), 'status': , 'summary': 'Wash car (Sa)', 'uid': 'mock-uid-1', @@ -872,7 +1088,10 @@ list([ dict({ 'due': '2024-01-14', - 'recurrence_id': '20240113', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 13), + 'this_and_future': False, + }), 'status': , 'summary': 'Wash car (Sa)', 'uid': 'mock-uid-1', @@ -883,7 +1102,10 @@ list([ dict({ 'due': '2024-01-22', - 'recurrence_id': '20240121', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 21), + 'this_and_future': False, + }), 'status': , 'summary': 'Wash car (Su)', 'uid': 'mock-uid-1', @@ -902,7 +1124,7 @@ DTSTART:20240121 DUE:20240122 LAST-MODIFIED:20220903T093805Z - RECURRENCE-ID:20240120 + RECURRENCE-ID;VALUE=DATE;RANGE=THISANDFUTURE:20240120 RRULE:FREQ=WEEKLY;COUNT=8;BYDAY=SU SEQUENCE:1 STATUS:NEEDS-ACTION @@ -927,31 +1149,46 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-19T09:00:00', - 'recurrence_id': '20220919T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 19, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26T09:00:00', - 'recurrence_id': '20220926T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 26, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -961,31 +1198,46 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-19T09:00:00', - 'recurrence_id': '20220919T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 19, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26T09:00:00', - 'recurrence_id': '20220926T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 26, 9, 0), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -995,31 +1247,46 @@ list([ dict({ 'dtstart': '2022-08-29T09:00:00-07:00', - 'recurrence_id': '20220829T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 8, 29, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-05T09:00:00-07:00', - 'recurrence_id': '20220905T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 5, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-12T09:00:00-07:00', - 'recurrence_id': '20220912T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 12, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-19T09:00:00-07:00', - 'recurrence_id': '20220919T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 19, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), dict({ 'dtstart': '2022-09-26T09:00:00-07:00', - 'recurrence_id': '20220926T090000', + 'recurrence_id': dict({ + 'date': FakeDatetime(2022, 9, 26, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), + 'this_and_future': False, + }), 'summary': 'Monday meeting', 'uid': 'mock-uid-1', }), @@ -1050,7 +1317,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240109', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 9), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1061,7 +1331,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240109', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 9), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1072,7 +1345,10 @@ list([ dict({ 'due': '2024-01-11', - 'recurrence_id': '20240110', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 10), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1083,7 +1359,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240109', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 9), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1102,7 +1381,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240109', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 9), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1113,7 +1395,10 @@ list([ dict({ 'due': '2024-01-11', - 'recurrence_id': '20240110', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 10), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1124,7 +1409,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240110', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 10), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk cat', 'uid': 'mock-uid-1', @@ -1135,7 +1423,10 @@ list([ dict({ 'due': '2024-01-10', - 'recurrence_id': '20240109', + 'recurrence_id': dict({ + 'date': FakeDate(2024, 1, 9), + 'this_and_future': False, + }), 'status': , 'summary': 'Walk dog', 'uid': 'mock-uid-1', @@ -1155,7 +1446,7 @@ DTSTART:20240109 DUE:20240110 LAST-MODIFIED:20240109T100005Z - RECURRENCE-ID:20240109 + RECURRENCE-ID;VALUE=DATE:20240109 SEQUENCE:1 STATUS:COMPLETED SUMMARY:Walk dog @@ -1189,7 +1480,7 @@ DTSTART:20240109 DUE:20240110 LAST-MODIFIED:20240109T100005Z - RECURRENCE-ID:20240109 + RECURRENCE-ID;VALUE=DATE:20240109 SEQUENCE:1 STATUS:COMPLETED SUMMARY:Walk dog @@ -1202,7 +1493,7 @@ DTSTART:20240109 DUE:20240110 LAST-MODIFIED:20240110T100000Z - RECURRENCE-ID:20240110 + RECURRENCE-ID;VALUE=DATE:20240110 EXDATE:20240109 SEQUENCE:1 STATUS:COMPLETED @@ -1237,7 +1528,7 @@ DTSTART:20240109 DUE:20240110 LAST-MODIFIED:20240109T100005Z - RECURRENCE-ID:20240109 + RECURRENCE-ID;VALUE=DATE:20240109 SEQUENCE:1 STATUS:COMPLETED SUMMARY:Walk dog @@ -1250,7 +1541,7 @@ DTSTART:20240109 DUE:20240110 LAST-MODIFIED:20240110T100000Z - RECURRENCE-ID:20240110 + RECURRENCE-ID;VALUE=DATE:20240110 EXDATE:20240109 SEQUENCE:2 STATUS:COMPLETED diff --git a/tests/examples/__snapshots__/test_ics_examples.ambr b/tests/examples/__snapshots__/test_ics_examples.ambr index 1b3689bc..0639232f 100644 --- a/tests/examples/__snapshots__/test_ics_examples.ambr +++ b/tests/examples/__snapshots__/test_ics_examples.ambr @@ -2,10 +2,10 @@ # name: test_iterate_events[apple_ical] list([ Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 15, 45, tzinfo=datetime.timezone.utc), uid='A409C8CF-31E9-4234-BE8A-6CE2B6BB875A', dtstart=datetime.datetime(2022, 9, 12, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 12, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='New Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 43, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 25, 19, 15, 43, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=None, related_to=[], related=[], resources=[], rrule=None, rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 13, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 13, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20220913T090000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 14, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 14, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20220914T090000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 15, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 15, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20220915T090000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 16, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 16, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20220916T090000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 13, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 13, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2022, 9, 13, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 14, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 14, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2022, 9, 14, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 15, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 15, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2022, 9, 15, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2022, 9, 25, 19, 22, 30, tzinfo=datetime.timezone.utc), uid='E53B06A1-9F72-41D9-9446-68E335D2D4F4', dtstart=datetime.datetime(2022, 9, 16, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), dtend=datetime.datetime(2022, 9, 16, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), duration=None, summary='Daily', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2022, 9, 25, 19, 15, 59, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2022, 9, 24, 17, 6, 16, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2022, 9, 16, 9, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2022, 9, 26, 15, 59, 59, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=1, status=None, transparency=None, url=None, extras=[], alarm=[]), ]) # --- # name: test_iterate_events[extended_values] @@ -14,11 +14,11 @@ # --- # name: test_iterate_events[google_calendar_invalid_offset] list([ - Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 3, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 3, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20030203T170000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 4, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 4, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20030204T170000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 5, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 5, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20030205T170000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 6, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 6, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20030206T170000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 7, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 7, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20030207T170000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 3, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 3, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2003, 2, 3, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 4, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 4, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2003, 2, 4, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 5, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 5, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2003, 2, 5, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 6, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 6, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2003, 2, 6, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 11, 23, 39, 45, tzinfo=datetime.timezone.utc), uid='KOrganizer-854889822.731//199594j26139acq39nj78tbhac@group.calendar.google.com', dtstart=datetime.datetime(2003, 2, 7, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), dtend=datetime.datetime(2003, 2, 7, 19, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), duration=None, summary='Event Summary', attendees=[], categories=[], classification=, comment=[], contacts=[], created=datetime.datetime(2006, 3, 15, 21, 4, 21, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2023, 2, 20, 3, 40, 45, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2003, 2, 7, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=datetime.datetime(2003, 2, 13, 16, 0, tzinfo=datetime.timezone.utc), count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[datetime.datetime(2003, 2, 8, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')), datetime.datetime(2003, 2, 9, 17, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires'))], request_status=None, sequence=3, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), ]) # --- # name: test_iterate_events[google_calendar_public_holidays] @@ -32,27 +32,27 @@ # --- # name: test_iterate_events[recurring_event] list([ - Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 5), dtend=datetime.date(2025, 5, 6), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20250505', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 6), dtend=datetime.date(2025, 5, 7), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20250506', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 7), dtend=datetime.date(2025, 5, 8), duration=None, summary='Some Modified Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 38, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20250507', related_to=[], related=[], resources=[], rrule=None, rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 8), dtend=datetime.date(2025, 5, 9), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20250508', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 9), dtend=datetime.date(2025, 5, 10), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20250509', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 5), dtend=datetime.date(2025, 5, 6), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.date(2025, 5, 5), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 6), dtend=datetime.date(2025, 5, 7), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.date(2025, 5, 6), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 7), dtend=datetime.date(2025, 5, 8), duration=None, summary='Some Modified Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 38, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.date(2025, 5, 7), this_and_future=False), related_to=[], related=[], resources=[], rrule=None, rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 8), dtend=datetime.date(2025, 5, 9), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.date(2025, 5, 8), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2025, 5, 4, 15, 1, 36, tzinfo=datetime.timezone.utc), uid='v8rukz8ier7ijmn@google.com', dtstart=datetime.date(2025, 5, 9), dtend=datetime.date(2025, 5, 10), duration=None, summary='Some Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2025, 5, 4, 14, 56, 10, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2025, 5, 4, 14, 57, 29, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.date(2025, 5, 9), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='TRANSPARENT', url=None, extras=[], alarm=[]), ]) # --- # name: test_iterate_events[recurring_with_single_change] list([ - Event(dtstamp=datetime.datetime(2026, 2, 2, 1, 0, 20, tzinfo=datetime.timezone.utc), uid='5st6kahlb53s6sdmrgkldms9k2@google.com', dtstart=datetime.datetime(2026, 2, 1, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), dtend=datetime.datetime(2026, 2, 1, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), duration=None, summary='Initial Title', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2026, 2, 2, 0, 57, 49, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2026, 2, 2, 0, 57, 57, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20260201T100000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=3, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2026, 2, 2, 1, 0, 20, tzinfo=datetime.timezone.utc), uid='5st6kahlb53s6sdmrgkldms9k2@google.com', dtstart=datetime.datetime(2026, 2, 2, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), dtend=datetime.datetime(2026, 2, 2, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), duration=None, summary='Edited Title', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2026, 2, 2, 0, 57, 49, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2026, 2, 2, 0, 57, 57, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20260202T100000', related_to=[], related=[], resources=[], rrule=None, rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2026, 2, 2, 1, 0, 20, tzinfo=datetime.timezone.utc), uid='5st6kahlb53s6sdmrgkldms9k2@google.com', dtstart=datetime.datetime(2026, 2, 3, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), dtend=datetime.datetime(2026, 2, 3, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), duration=None, summary='Initial Title', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2026, 2, 2, 0, 57, 49, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2026, 2, 2, 0, 57, 57, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id='20260203T100000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=3, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2026, 2, 2, 1, 0, 20, tzinfo=datetime.timezone.utc), uid='5st6kahlb53s6sdmrgkldms9k2@google.com', dtstart=datetime.datetime(2026, 2, 1, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), dtend=datetime.datetime(2026, 2, 1, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), duration=None, summary='Initial Title', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2026, 2, 2, 0, 57, 49, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2026, 2, 2, 0, 57, 57, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2026, 2, 1, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=3, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2026, 2, 2, 1, 0, 20, tzinfo=datetime.timezone.utc), uid='5st6kahlb53s6sdmrgkldms9k2@google.com', dtstart=datetime.datetime(2026, 2, 2, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), dtend=datetime.datetime(2026, 2, 2, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), duration=None, summary='Edited Title', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2026, 2, 2, 0, 57, 49, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2026, 2, 2, 0, 57, 57, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2026, 2, 2, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), this_and_future=False), related_to=[], related=[], resources=[], rrule=None, rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2026, 2, 2, 1, 0, 20, tzinfo=datetime.timezone.utc), uid='5st6kahlb53s6sdmrgkldms9k2@google.com', dtstart=datetime.datetime(2026, 2, 3, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), dtend=datetime.datetime(2026, 2, 3, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), duration=None, summary='Initial Title', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=datetime.datetime(2026, 2, 2, 0, 57, 49, tzinfo=datetime.timezone.utc), description=None, geo=None, last_modified=datetime.datetime(2026, 2, 2, 0, 57, 57, tzinfo=datetime.timezone.utc), location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2026, 2, 3, 10, 0, tzinfo=zoneinfo.ZoneInfo(key='America/New_York')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=3, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency='OPAQUE', url=None, extras=[], alarm=[]), ]) # --- # name: test_iterate_events[store_edit_bugs] list([ - Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 4, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 4, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id='20210904T110000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 11, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 11, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id='20210911T110000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 18, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 18, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id='20210918T110000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 25, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 25, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id='20210925T110000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), - Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 10, 2, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 10, 2, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id='20211002T110000', related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 4, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 4, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2021, 9, 4, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 11, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 11, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2021, 9, 11, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 18, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 18, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2021, 9, 18, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 9, 25, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 9, 25, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2021, 9, 25, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), + Event(dtstamp=datetime.datetime(2021, 8, 5, 16, 33, 53, tzinfo=datetime.timezone.utc), uid='HX7KXxJM9ZyvyH8D00BMVP-K9drT@proton.me', dtstart=datetime.datetime(2021, 10, 2, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), dtend=datetime.datetime(2021, 10, 2, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), duration=None, summary='My Event', attendees=[], categories=[], classification=None, comment=[], contacts=[], created=None, description='Example', geo=None, last_modified=None, location=None, organizer=None, priority=None, recurrence_id=RecurrenceId(date=datetime.datetime(2021, 10, 2, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam')), this_and_future=False), related_to=[], related=[], resources=[], rrule=Recur(freq=, until=None, count=None, interval=1, by_weekday=[], by_month_day=[], by_month=[], by_setpos=[], wkst=None), rdate=[], exdate=[], request_status=None, sequence=0, status=, transparency=None, url=None, extras=[], alarm=[]), ]) # --- # name: test_parse[apple_ical] @@ -2010,7 +2010,7 @@ SUMMARY:Some Modified Event CREATED:20250504T145610Z LAST-MODIFIED:20250504T145738Z - RECURRENCE-ID:20250507 + RECURRENCE-ID;VALUE=DATE:20250507 SEQUENCE:0 STATUS:CONFIRMED TRANSP:TRANSPARENT @@ -2035,7 +2035,7 @@ SUMMARY:Edited Title CREATED:20260202T005749Z LAST-MODIFIED:20260202T005757Z - RECURRENCE-ID:20260202T100000 + RECURRENCE-ID;TZID=America/New_York;VALUE=DATE-TIME:20260202T100000 SEQUENCE:0 STATUS:CONFIRMED TRANSP:OPAQUE diff --git a/tests/examples/test_ics_examples.py b/tests/examples/test_ics_examples.py index bf2fb3d8..09f182aa 100644 --- a/tests/examples/test_ics_examples.py +++ b/tests/examples/test_ics_examples.py @@ -9,6 +9,7 @@ from syrupy import SnapshotAssertion from ical.calendar_stream import IcsCalendarStream +from ical.types import RecurrenceId TEST_DIR = Path("tests/examples") TEST_FILES = sorted(list(TEST_DIR.glob("testdata/*.ics"))) @@ -44,15 +45,27 @@ def test_recurring_with_single_change() -> None: # Feb 1 - original title assert events[0].summary == "Initial Title" - assert events[0].recurrence_id == "20260201T100000" + assert events[0].recurrence_id == RecurrenceId( + date=datetime.datetime( + 2026, 2, 1, 10, 0, tzinfo=zoneinfo.ZoneInfo(key="America/New_York") + ) + ) assert events[0].dtstart == datetime.datetime(2026, 2, 1, 10, 0, tzinfo=tz) # Feb 2 - edited instance with RECURRENCE-ID assert events[1].summary == "Edited Title" - assert events[1].recurrence_id == "20260202T100000" + assert events[1].recurrence_id == RecurrenceId( + date=datetime.datetime( + 2026, 2, 2, 10, 0, tzinfo=zoneinfo.ZoneInfo(key="America/New_York") + ) + ) assert events[1].dtstart == datetime.datetime(2026, 2, 2, 10, 0, tzinfo=tz) # Feb 3 - original title assert events[2].summary == "Initial Title" - assert events[2].recurrence_id == "20260203T100000" + assert events[2].recurrence_id == RecurrenceId( + date=datetime.datetime( + 2026, 2, 3, 10, 0, tzinfo=zoneinfo.ZoneInfo(key="America/New_York") + ) + ) assert events[2].dtstart == datetime.datetime(2026, 2, 3, 10, 0, tzinfo=tz) diff --git a/tests/test_list.py b/tests/test_list.py index 2a76fb03..013d528c 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -7,7 +7,7 @@ from ical.list import todo_list_view from ical.todo import Todo -from ical.types.recur import Recur +from ical.types.recur import Recur, RecurrenceId def test_empty_list() -> None: @@ -39,7 +39,7 @@ def test_daily_recurring_item_due_today_incomplete(status: str) -> None: assert view[0].summary == todo.summary assert view[0].dtstart == datetime.date(2024, 1, 10) assert view[0].due == datetime.date(2024, 1, 11) - assert view[0].recurrence_id == "20240110" + assert view[0].recurrence_id == RecurrenceId(date=datetime.date(2024, 1, 10)) @pytest.mark.parametrize( @@ -65,7 +65,7 @@ def test_daily_recurring_item_due_tomorrow(status: str) -> None: assert view[0].summary == todo.summary assert view[0].dtstart == datetime.date(2024, 1, 10) assert view[0].due == datetime.date(2024, 1, 11) - assert view[0].recurrence_id == "20240110" + assert view[0].recurrence_id == RecurrenceId(date=datetime.date(2024, 1, 10)) @pytest.mark.parametrize( @@ -93,7 +93,7 @@ def test_daily_recurring_item_due_yesterday(status: str) -> None: assert view[0].summary == todo.summary assert view[0].dtstart == datetime.date(2024, 1, 10) assert view[0].due == datetime.date(2024, 1, 11) - assert view[0].recurrence_id == "20240110" + assert view[0].recurrence_id == RecurrenceId(date=datetime.date(2024, 1, 10)) assert view[0].status == status with freezegun.freeze_time("2024-01-11T08:05:00-05:00"): @@ -103,5 +103,5 @@ def test_daily_recurring_item_due_yesterday(status: str) -> None: assert view[0].summary == todo.summary assert view[0].dtstart == datetime.date(2024, 1, 11) assert view[0].due == datetime.date(2024, 1, 12) - assert view[0].recurrence_id == "20240111" + assert view[0].recurrence_id == RecurrenceId(date=datetime.date(2024, 1, 11)) assert view[0].status == status diff --git a/tests/test_store.py b/tests/test_store.py index 79a4950a..eab580b4 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -22,7 +22,7 @@ from ical.event import Event from ical.todo import Todo from ical.store import EventStore, TodoStore, StoreError -from ical.types.recur import Range, Recur +from ical.types.recur import Range, Recur, RecurrenceId from ical.types import RelationshipType, RelatedTo TZ = zoneinfo.ZoneInfo("America/Los_Angeles") @@ -1605,17 +1605,29 @@ def test_store_edit_year_overrun_edit_once( # Pick an arbitrary event in the series iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 5, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 12, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event2.dtstart == datetime.datetime( 2024, 10, 12, 11, 0, 0, tzinfo=calendar_tz ) event3 = next(iter) - assert event3.recurrence_id == "20241019T110000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 19, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 11, 0, 0, tzinfo=calendar_tz ) @@ -1642,22 +1654,38 @@ def test_store_edit_year_overrun_edit_once( timeline = calendar.timeline_tz(tzinfo=viewer_tz) iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 5, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 12, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event2.dtstart == datetime.datetime( 2024, 10, 12, 10, 0, 0, tzinfo=calendar_tz ) event3 = next(iter) - assert event3.recurrence_id == "20241019T110000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 19, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 11, 0, 0, tzinfo=calendar_tz ) event4 = next(iter) - assert event4.recurrence_id == "20241026T110000" + assert event4.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 26, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event4.dtstart == datetime.datetime( 2024, 10, 26, 11, 0, 0, tzinfo=calendar_tz ) @@ -1683,22 +1711,38 @@ def test_store_edit_year_overrun_edit_once( timeline = calendar.timeline_tz(tzinfo=viewer_tz) iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 5, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 12, 11, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Amsterdam") + ) + ) assert event2.dtstart == datetime.datetime( 2024, 10, 12, 10, 0, 0, tzinfo=calendar_tz ) event3 = next(iter) - assert event3.recurrence_id == "20241019T043000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 19, 4, 30, tzinfo=zoneinfo.ZoneInfo(key="America/New_York") + ) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 10, 30, 0, tzinfo=calendar_tz ) event4 = next(iter) - assert event4.recurrence_id == "20241026T043000" + assert event4.recurrence_id == RecurrenceId( + date=datetime.datetime( + 2024, 10, 26, 4, 30, tzinfo=zoneinfo.ZoneInfo(key="America/New_York") + ) + ) assert event4.dtstart == datetime.datetime( 2024, 10, 26, 10, 30, 0, tzinfo=calendar_tz ) @@ -1732,17 +1776,23 @@ def test_store_edit_year_overrun_edit_this_and_future( # Pick an arbitrary event in the series iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 5, 11, 0, tzinfo=calendar_tz) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 12, 11, 0, tzinfo=calendar_tz) + ) assert event2.dtstart == datetime.datetime( 2024, 10, 12, 11, 0, 0, tzinfo=calendar_tz ) event3 = next(iter) - assert event3.recurrence_id == "20241019T110000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 19, 11, 0, tzinfo=calendar_tz) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 11, 0, 0, tzinfo=calendar_tz ) @@ -1770,17 +1820,23 @@ def test_store_edit_year_overrun_edit_this_and_future( timeline = calendar.timeline_tz(tzinfo=viewer_tz) iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 5, 11, 0, tzinfo=calendar_tz) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T040000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 12, 4, 0, tzinfo=viewer_tz) + ) assert event2.dtstart == datetime.datetime( 2024, 10, 12, 10, 0, 0, tzinfo=calendar_tz ) event3 = next(iter) - assert event3.recurrence_id == "20241019T040000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 19, 4, 0, tzinfo=viewer_tz) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 10, 0, 0, tzinfo=calendar_tz ) @@ -1814,17 +1870,23 @@ def test_store_edit_year_override_set_floating_dates( # Pick an arbitrary event in the series iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 5, 11, 0, tzinfo=calendar_tz) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 12, 11, 0, tzinfo=calendar_tz) + ) assert event2.dtstart == datetime.datetime( 2024, 10, 12, 11, 0, 0, tzinfo=calendar_tz ) event3 = next(iter) - assert event3.recurrence_id == "20241019T110000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 19, 11, 0, tzinfo=calendar_tz) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 11, 0, 0, tzinfo=calendar_tz ) @@ -1856,12 +1918,16 @@ def test_store_edit_year_override_set_floating_dates( timeline = calendar.timeline_tz(tzinfo=viewer_tz) iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 5, 11, 0, tzinfo=calendar_tz) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 12, 11, 0, tzinfo=calendar_tz) + ) assert event2.dtstart == datetime.datetime( 2024, 10, @@ -1871,12 +1937,16 @@ def test_store_edit_year_override_set_floating_dates( 0, ) event3 = next(iter) - assert event3.recurrence_id == "20241019T110000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 19, 11, 0, tzinfo=calendar_tz) + ) assert event3.dtstart == datetime.datetime( 2024, 10, 19, 11, 0, 0, tzinfo=calendar_tz ) event4 = next(iter) - assert event4.recurrence_id == "20241026T110000" + assert event4.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 26, 11, 0, tzinfo=calendar_tz) + ) assert event4.dtstart == datetime.datetime( 2024, 10, 26, 11, 0, 0, tzinfo=calendar_tz ) @@ -1903,18 +1973,26 @@ def test_store_edit_year_override_set_floating_dates( timeline = calendar.timeline_tz(tzinfo=viewer_tz) iter = timeline.active_after(datetime.datetime(2024, 10, 1, tzinfo=viewer_tz)) event1 = next(iter) - assert event1.recurrence_id == "20241005T110000" + assert event1.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 5, 11, 0, tzinfo=calendar_tz) + ) assert event1.dtstart == datetime.datetime( 2024, 10, 5, 11, 0, 0, tzinfo=calendar_tz ) event2 = next(iter) - assert event2.recurrence_id == "20241012T110000" + assert event2.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 12, 11, 0, tzinfo=calendar_tz) + ) assert event2.dtstart == datetime.datetime(2024, 10, 12, 4, 0, 0) event3 = next(iter) - assert event3.recurrence_id == "20241019T043000" + assert event3.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 19, 4, 30) + ) assert event3.dtstart == datetime.datetime(2024, 10, 19, 4, 30, 0) event4 = next(iter) - assert event4.recurrence_id == "20241026T043000" + assert event4.recurrence_id == RecurrenceId( + date=datetime.datetime(2024, 10, 26, 4, 30) + ) assert event4.dtstart == datetime.datetime(2024, 10, 26, 4, 30, 0) diff --git a/tests/test_timeline.py b/tests/test_timeline.py index d7b87ac5..bfb0ff8d 100644 --- a/tests/test_timeline.py +++ b/tests/test_timeline.py @@ -11,7 +11,7 @@ from ical.calendar import Calendar from ical.event import Event from ical.journal import Journal -from ical.types.recur import Recur +from ical.types.recur import Recur, RecurrenceId from ical.timeline import generic_timeline, materialize_timeline, calendar_timeline TZ = zoneinfo.ZoneInfo("America/Regina") @@ -79,31 +79,37 @@ def test_journal_timeline() -> None: ): timeline = generic_timeline([journal], TZ) assert list(timeline) == [ - Journal.model_copy(journal, update={"recurrence_id": "20220807"}), + Journal.model_copy( + journal, + update={"recurrence_id": RecurrenceId(date=datetime.date(2022, 8, 7))}, + ), Journal.model_copy( journal, update={ "dtstart": datetime.date(2022, 8, 8), - "recurrence_id": "20220808", + "recurrence_id": RecurrenceId(date=datetime.date(2022, 8, 8)), }, ), Journal.model_copy( journal, update={ "dtstart": datetime.date(2022, 8, 9), - "recurrence_id": "20220809", + "recurrence_id": RecurrenceId(date=datetime.date(2022, 8, 9)), }, ), ] assert list( timeline.overlapping(datetime.date(2022, 8, 7), datetime.date(2022, 8, 9)) ) == [ - Journal.model_copy(journal, update={"recurrence_id": "20220807"}), + Journal.model_copy( + journal, + update={"recurrence_id": RecurrenceId(date=datetime.date(2022, 8, 7))}, + ), Journal.model_copy( journal, update={ "dtstart": datetime.date(2022, 8, 8), - "recurrence_id": "20220808", + "recurrence_id": RecurrenceId(date=datetime.date(2022, 8, 8)), }, ), ] diff --git a/tests/types/test_recur.py b/tests/types/test_recur.py index 1ab38294..a12e3342 100644 --- a/tests/types/test_recur.py +++ b/tests/types/test_recur.py @@ -40,7 +40,9 @@ def test_recurrence_id_datetime() -> None: ] } ) - assert model.recurrence_id == "20220724T120000" + assert model.recurrence_id == RecurrenceId( + date=datetime.datetime(2022, 7, 24, 12, 0) + ) def test_recurrence_id_date() -> None: @@ -49,7 +51,7 @@ def test_recurrence_id_date() -> None: model = FakeModel.model_validate( {"recurrence_id": [ParsedProperty(name="recurrence_id", value="20220724")]} ) - assert model.recurrence_id == "20220724" + assert model.recurrence_id == RecurrenceId(date=datetime.date(2022, 7, 24)) def test_recurrence_id_ignore_params() -> None: @@ -68,7 +70,9 @@ def test_recurrence_id_ignore_params() -> None: ] } ) - assert model.recurrence_id == "20220724T120000" + assert model.recurrence_id == RecurrenceId( + date=datetime.datetime(2022, 7, 24, 12, 0), this_and_future=True + ) def test_invalid_recurrence_id() -> None: From 074534fbc798e1bf526b1b52db5b36f08e933483 Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Thu, 16 Apr 2026 15:43:34 -0400 Subject: [PATCH 13/14] Update invalid RecurrenceId to raise error --- tests/types/test_recur.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/types/test_recur.py b/tests/types/test_recur.py index a12e3342..d4b13773 100644 --- a/tests/types/test_recur.py +++ b/tests/types/test_recur.py @@ -77,10 +77,10 @@ def test_recurrence_id_ignore_params() -> None: def test_invalid_recurrence_id() -> None: """Test for a recurrence id that is not a valid DATE or DATE-TIME string.""" - model = FakeModel.model_validate( - {"recurrence_id": [ParsedProperty(name="recurrence_id", value="invalid")]} - ) - assert model.recurrence_id == "invalid" + with pytest.raises(CalendarParseError): + FakeModel.model_validate( + {"recurrence_id": [ParsedProperty(name="recurrence_id", value="invalid")]} + ) @pytest.mark.parametrize( From 99320d236cb496b5d2d763b8675ad2dcd1063063 Mon Sep 17 00:00:00 2001 From: Peter DeVita Date: Thu, 16 Apr 2026 16:03:36 -0400 Subject: [PATCH 14/14] Clean up RecurrenceId changes --- ical/store.py | 4 ++-- ical/types/recur.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ical/store.py b/ical/store.py index 30360701..b39f005f 100644 --- a/ical/store.py +++ b/ical/store.py @@ -272,7 +272,7 @@ def delete( ] if not items_to_delete: raise self._exc( - f"No existing item with uid/recurrence_id: {uid}/{recurrence}" + f"No existing item with uid/recurrence_id: {uid}/{recurrence_id}" ) for store_item in items_to_delete: @@ -368,7 +368,7 @@ def edit( ] if not items_to_edit: raise self._exc( - f"No existing item with uid/recurrence_id: {uid}/{recurrence}" + f"No existing item with uid/recurrence_id: {uid}/{recurrence_id}" ) for store_index, store_item in items_to_edit: diff --git a/ical/types/recur.py b/ical/types/recur.py index 76204b63..b068eb16 100644 --- a/ical/types/recur.py +++ b/ical/types/recur.py @@ -138,9 +138,6 @@ class Range(str, enum.Enum): """The range of the recurrence identifier and all subsequent values.""" -RecurIdInputDict = dict[str, datetime.date | datetime.datetime | bool] - - @DATA_TYPE.register("RECURRENCE-ID", disable_value_param=True) class RecurrenceId(BaseModel): """Identifies a specific instance of a recurring calendar component.