Files
syn-chat-bot/.venv/lib/python3.9/site-packages/recurring_ical_events/occurrence.py
Hyungi Ahn c2257d3a86 fix: 포트 충돌 회피 — note_bridge 8098, intent_service 8099
Jellyfin(8096), OrbStack(8097) 포트 충돌으로 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:53:55 +09:00

213 lines
6.4 KiB
Python

"""Occurrences of events and other components."""
from __future__ import annotations
from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, NamedTuple, Optional
from icalendar import Alarm
from recurring_ical_events.adapters.component import ComponentAdapter
from recurring_ical_events.util import (
cached_property,
make_comparable,
time_span_contains_event,
)
if TYPE_CHECKING:
from icalendar import Alarm
from icalendar.cal import Component
from recurring_ical_events.adapters.component import ComponentAdapter
from recurring_ical_events.types import UID, RecurrenceIDs, Time
class OccurrenceID(NamedTuple):
"""The ID of a component's occurrence to identify it clearly.
Attributes:
name: The name of the component, e.g. "VEVENT"
uid: The UID of the component.
recurrence_id: The Recurrence-ID of the component in UTC but without tzinfo.
start: The start of the component
"""
name: str
uid: UID
recurrence_id: Optional[Time]
start: Time
def to_string(self) -> str:
"""Return a string representation of this id."""
return "#".join(
[
self.name,
self.recurrence_id.isoformat() if self.recurrence_id else "",
self.start.isoformat(),
self.uid,
]
)
@staticmethod
def _dt_from_string(iso_string: str) -> Time:
"""Create a datetime from the string representation."""
if len(iso_string) == 10:
return date.fromisoformat(iso_string)
return datetime.fromisoformat(iso_string)
@classmethod
def from_string(cls, string_id: str) -> OccurrenceID:
"""Parse a string and return the component id."""
name, recurrence_id, start, uid = string_id.split("#", 3)
return cls(
name,
uid,
cls._dt_from_string(recurrence_id) if recurrence_id else None,
cls._dt_from_string(start),
)
@classmethod
def from_occurrence(
cls, name: str, uid: str, recurrence_ids: RecurrenceIDs, start: Time
):
"""Create a new OccurrenceID from the given values.
Args:
name: The component name.
uid: The UID string.
recurrence_ids: The recurrence ID tuple.
This is expected as UTC with tzinfo being None.
start: start time of the component either with or without timezone
"""
return cls(name, uid, recurrence_ids[0] if recurrence_ids else None, start)
class Occurrence:
"""A repetition of an event."""
def __init__(
self,
adapter: ComponentAdapter,
start: Time | None = None,
end: Time | None | timedelta = None,
sequence: int = -1,
):
"""Create an event repetition.
- source - the icalendar Event
- start - the start date/datetime to replace
- stop - the end date/datetime to replace
- sequence - if positive or 0, this sets the SEQUENCE property
"""
self._adapter = adapter
self.start = adapter.start if start is None else start
self.end = adapter.end if end is None else end
self.sequence = sequence
def as_component(self, keep_recurrence_attributes: bool) -> Component: # noqa: FBT001
"""Create a shallow copy of the source component and modify some attributes."""
component = self._adapter.as_component(
self.start, self.end, keep_recurrence_attributes
)
if self.sequence >= 0:
component["SEQUENCE"] = self.sequence
return component
def is_in_span(self, span_start: Time, span_stop: Time) -> bool:
"""Return whether the component is in the span."""
return time_span_contains_event(span_start, span_stop, self.start, self.end)
def __lt__(self, other: Occurrence) -> bool:
"""Compare two occurrences for sorting.
See https://stackoverflow.com/a/4010558/1320237
"""
self_start, other_start = make_comparable((self.start, other.start))
return self_start < other_start
@cached_property
def id(self) -> OccurrenceID:
"""The id of the component."""
return OccurrenceID.from_occurrence(
self._adapter.component_name(),
self._adapter.uid,
self._adapter.recurrence_ids,
self.start,
)
def __hash__(self) -> int:
"""Hash this for an occurrence."""
return hash(self.id)
def __eq__(self, other: Occurrence) -> bool:
"""self == other"""
return self.id == other.id
def component_name(self) -> str:
"""The name of this component."""
return self._adapter.component_name()
@property
def uid(self) -> str:
"""The UID of this occurrence."""
return self._adapter.uid
def has_alarm(self, alarm: Alarm) -> bool:
"""Wether this alarm is in this occurrence."""
return alarm in self._adapter.alarms
@property
def recurrence_ids(self) -> RecurrenceIDs:
"""The recurrence ids."""
return self._adapter.recurrence_ids
class AlarmOccurrence(Occurrence):
"""Adapter for absolute alarms."""
def __init__(
self,
trigger: datetime,
alarm: Alarm,
parent: ComponentAdapter | Occurrence,
) -> None:
super().__init__(alarm, trigger, trigger)
self.parent = parent
self.alarm = alarm
def as_component(self, keep_recurrence_attributes):
"""Return the alarm's parent as a modified component."""
parent = self.parent.as_component(
keep_recurrence_attributes=keep_recurrence_attributes
)
alarm_once = self.alarm.copy()
alarm_once.TRIGGER = self.start
alarm_once.REPEAT = 0
parent.subcomponents = [alarm_once]
return parent
@cached_property
def id(self) -> OccurrenceID:
"""The id of the component."""
return OccurrenceID.from_occurrence(
self.parent.component_name(),
self.parent.uid,
self.parent.recurrence_ids,
self.start,
)
def __repr__(self) -> str:
"""repr(self)"""
return (
f"<{self.__class__.__name__} at {self.start} of"
f" {self.alarm} in {self.parent}"
)
__all__ = [
"AlarmOccurrence",
"Occurrence",
"OccurrenceID",
]