Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 99 additions & 28 deletions ical/types/recur.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@
from typing import Any, Optional, Union

from dateutil import rrule

try:
from pydantic.v1 import BaseModel, Field
except ImportError:
from pydantic import BaseModel, Field # type: ignore[assignment]
from pydantic import BaseModel, Field # type: ignore[assignment]

from ical.parsing.property import ParsedProperty
from ical.parsing.property import ParsedProperty, ParsedPropertyParameter

from .data_types import DATA_TYPE
from .date import DateEncoder
Expand Down Expand Up @@ -91,7 +92,6 @@ def __str__(self) -> str:
"""Return the WeekdayValue as an encoded string."""
return f"{self.occurrence or ''}{self.weekday}"


def as_rrule_weekday(self) -> rrule.weekday:
"""Convert the occurrence to a weekday value."""
wd = RRULE_WEEKDAY[self.weekday]
Expand Down Expand Up @@ -132,8 +132,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
Expand All @@ -143,6 +146,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."""
Expand All @@ -168,27 +183,86 @@ 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)
elif 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
elif isinstance(value, dict):
prop_value = DateTimeEncoder.__encode_property_value__(value)
if prop_value:
return prop_value
elif 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 | 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, 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

@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


RRULE_FREQ = {
Expand Down Expand Up @@ -257,10 +331,7 @@ def as_rrule(self, dtstart: datetime.datetime | datetime.date) -> rrule.rrule:

byweekday: list[rrule.weekday] | None = None
if self.by_weekday:
byweekday = [
weekday.as_rrule_weekday()
for weekday in self.by_weekday
]
byweekday = [weekday.as_rrule_weekday() for weekday in self.by_weekday]
return rrule.rrule(
freq=freq,
dtstart=dtstart,
Expand Down
Loading