Source code for icalendar.cal.alarm

""":rfc:`5545` VALARM component."""

from __future__ import annotations

from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, NamedTuple

from icalendar.attr import (
    CONCEPTS_TYPE_SETTER,
    LINKS_TYPE_SETTER,
    RELATED_TO_TYPE_SETTER,
    attendees_property,
    create_single_property,
    description_property,
    property_del_duration,
    property_get_duration,
    property_set_duration,
    single_int_property,
    single_string_property,
    single_utc_property,
    summary_property,
    uid_property,
)
from icalendar.cal.component import Component
from icalendar.cal.examples import get_example

if TYPE_CHECKING:
    import uuid

    from icalendar.prop import vCalAddress


[docs] class Alarm(Component): """ A "VALARM" calendar component is a grouping of component properties that defines an alarm or reminder for an event or a to-do. For example, it may be used to define a reminder for a pending event or an overdue to-do. Example: The following example creates an alarm which uses an audio file from an FTP server. .. code-block:: pycon >>> from icalendar import Alarm >>> alarm = Alarm.example() >>> print(alarm.to_ical().decode()) BEGIN:VALARM ACTION:AUDIO ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud DURATION:PT15M REPEAT:4 TRIGGER;VALUE=DATE-TIME:19970317T133000Z END:VALARM """ name = "VALARM" # some properties MAY/MUST/MUST NOT appear depending on ACTION value required = ( "ACTION", "TRIGGER", ) singletons = ( "ATTACH", "ACTION", "DESCRIPTION", "SUMMARY", "TRIGGER", "DURATION", "REPEAT", "UID", "PROXIMITY", "ACKNOWLEDGED", ) inclusive = ( ( "DURATION", "REPEAT", ), ( "SUMMARY", "ATTENDEE", ), ) multiple = ("ATTENDEE", "ATTACH", "RELATED-TO") REPEAT = single_int_property( "REPEAT", 0, """The number of additional times the alarm is triggered after the initial trigger. Defaults to ``0``, meaning the alarm fires once. To repeat the alarm, set both :attr:`REPEAT` and :attr:`DURATION`. The :attr:`DURATION` sets the gap between repetitions. ``REPEAT`` is the count of *additional* triggers, so a ``REPEAT`` of ``2`` produces three alarms in total (the initial trigger plus two repeats). Conforming with :rfc:`5545#section-3.8.6.2`, this property can appear once in an :class:`~icalendar.cal.alarm.Alarm` component and must be paired with :attr:`DURATION`. Example: Build an alarm that fires once and then repeats twice at five-minute intervals. .. code-block:: pycon >>> from datetime import timedelta >>> from icalendar import Alarm >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(minutes=-15) >>> alarm.DURATION = timedelta(minutes=5) >>> alarm.REPEAT = 2 >>> alarm.REPEAT 2 """, ) DURATION = property( property_get_duration, property_set_duration, property_del_duration, """The delay between repeated triggers of a repeating alarm. Returns a :class:`datetime.timedelta` or ``None`` when the alarm has no :attr:`DURATION` set. Setting this attribute accepts a :class:`~datetime.timedelta`; deleting it removes the property from the component. :attr:`DURATION` is meaningful only for repeating alarms and must be paired with :attr:`REPEAT`. The two together produce ``REPEAT`` additional triggers, each spaced by ``DURATION`` after the initial trigger. Conforming with :rfc:`5545#section-3.8.2.5`, the ``DURATION`` property can appear once in an :class:`~icalendar.cal.alarm.Alarm` component. Example: Pair :attr:`DURATION` with :attr:`REPEAT` to produce three triggers spaced ten minutes apart. .. code-block:: pycon >>> from datetime import timedelta >>> from icalendar import Alarm >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(minutes=-30) >>> alarm.DURATION = timedelta(minutes=10) >>> alarm.REPEAT = 2 >>> alarm.DURATION datetime.timedelta(seconds=600) """, ) ACKNOWLEDGED = single_utc_property( "ACKNOWLEDGED", """The UTC datetime at which this alarm was last sent or acknowledged. Defined in :rfc:`9074`. Setting this property allows calendar clients to dismiss or suppress an alarm across multiple devices. Once set to a value greater than or equal to the alarm's computed trigger time, conforming clients will not re-fire the alarm. Returns ``None`` when no acknowledgment has been recorded. Example: Mark an alarm as acknowledged at the current time. .. code-block:: pycon >>> from datetime import timezone, datetime >>> from icalendar import Alarm >>> alarm = Alarm() >>> alarm.ACKNOWLEDGED = datetime(2024, 1, 15, 10, 0, 0, tzinfo=timezone.utc) >>> alarm.ACKNOWLEDGED # doctest: +ELLIPSIS datetime.datetime(2024, 1, 15, 10, 0, tzinfo=...) See also: :attr:`TRIGGER` — the time at which the alarm fires. """, ) TRIGGER = create_single_property( "TRIGGER", "dt", (datetime, timedelta), timedelta | datetime | None, """The time at which this alarm fires, per :rfc:`5545#section-3.8.6.3`. The value is either a :class:`~datetime.timedelta` (relative trigger) or a UTC :class:`~datetime.datetime` (absolute trigger). A negative :class:`~datetime.timedelta` fires *before* the related component boundary (start or end); a positive one fires *after* it. Use :attr:`TRIGGER_RELATED` to choose whether the offset is measured from the start or the end of the parent event or to-do. An absolute trigger fires at an exact UTC point in time regardless of the parent component's dates. Example: Set an alarm to fire 15 minutes before the start of an event. .. code-block:: pycon >>> from datetime import timedelta >>> from icalendar import Alarm >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(minutes=-15) >>> alarm.TRIGGER # doctest: +ELLIPSIS datetime.timedelta(...) See also: :attr:`TRIGGER_RELATED`, :attr:`DURATION`, :attr:`REPEAT` """, ) @property def TRIGGER_RELATED(self) -> str: """The RELATED parameter of the TRIGGER property. Values are either "START" (default) or "END". A value of START will set the alarm to trigger off the start of the associated event or to-do. A value of END will set the alarm to trigger off the end of the associated event or to-do. In this example, we create an alarm that triggers two hours after the end of its parent component: >>> from icalendar import Alarm >>> from datetime import timedelta >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(hours=2) >>> alarm.TRIGGER_RELATED = "END" """ trigger = self.get("TRIGGER") if trigger is None: return "START" return trigger.params.get("RELATED", "START") @TRIGGER_RELATED.setter def TRIGGER_RELATED(self, value: str): """Set "START" or "END".""" trigger = self.get("TRIGGER") if trigger is None: raise ValueError( "You must set a TRIGGER before setting the RELATED parameter." ) trigger.params["RELATED"] = value
[docs] class Triggers(NamedTuple): """The computed times of alarm triggers. start - triggers relative to the start of the Event or Todo (timedelta) end - triggers relative to the end of the Event or Todo (timedelta) absolute - triggers at a datetime in UTC """ start: tuple[timedelta] end: tuple[timedelta] absolute: tuple[datetime]
@property def triggers(self): """The computed triggers of an Alarm. This takes the TRIGGER, DURATION and REPEAT properties into account. Here, we create an alarm that triggers 3 times before the start of the parent component: >>> from icalendar import Alarm >>> from datetime import timedelta >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(hours=-4) # trigger 4 hours before START >>> alarm.DURATION = timedelta(hours=1) # after 1 hour trigger again >>> alarm.REPEAT = 2 # trigger 2 more times >>> alarm.triggers.start == (timedelta(hours=-4), timedelta(hours=-3), timedelta(hours=-2)) True >>> alarm.triggers.end () >>> alarm.triggers.absolute () """ start = [] end = [] absolute = [] trigger = self.TRIGGER if trigger is not None: if isinstance(trigger, date): absolute.append(trigger) add = absolute elif self.TRIGGER_RELATED == "START": start.append(trigger) add = start else: end.append(trigger) add = end duration = self.DURATION if duration is not None: for _ in range(self.REPEAT): add.append(add[-1] + duration) return self.Triggers( start=tuple(start), end=tuple(end), absolute=tuple(absolute) ) uid = single_string_property( "UID", uid_property.__doc__, "X-ALARMUID", ) summary = summary_property description = description_property attendees = attendees_property
[docs] @classmethod def new( cls, /, attendees: list[vCalAddress] | None = None, concepts: CONCEPTS_TYPE_SETTER = None, description: str | None = None, links: LINKS_TYPE_SETTER = None, refids: list[str] | str | None = None, related_to: RELATED_TO_TYPE_SETTER = None, summary: str | None = None, uid: str | uuid.UUID | None = None, ): """Create a new alarm with all required properties. This creates a new Alarm in accordance with :rfc:`5545`. Parameters: attendees: The :attr:`attendees` of the alarm. concepts: The :attr:`~icalendar.Component.concepts` of the alarm. description: The :attr:`description` of the alarm. links: The :attr:`~icalendar.Component.links` of the alarm. refids: :attr:`~icalendar.Component.refids` of the alarm. related_to: :attr:`~icalendar.Component.related_to` of the alarm. summary: The :attr:`summary` of the alarm. uid: The :attr:`uid` of the alarm. Returns: :class:`Alarm` Raises: ~error.InvalidCalendar: If the content is not valid according to :rfc:`5545`. .. warning:: As time progresses, we will be stricter with the validation. """ alarm: Alarm = super().new( links=links, related_to=related_to, refids=refids, concepts=concepts, ) alarm.summary = summary alarm.description = description alarm.uid = uid alarm.attendees = attendees return alarm
[docs] @classmethod def example(cls, name: str = "example") -> Alarm: """Return the alarm example with the given name.""" return cls.from_ical(get_example("alarms", name))
__all__ = ["Alarm"]