Source code for icalendar.cal.event

""":rfc:`5545` VEVENT component."""

from __future__ import annotations

import uuid
from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, Optional, Sequence

from icalendar.attr import (
    X_MOZ_LASTACK_property,
    X_MOZ_SNOOZE_TIME_property,
    categories_property,
    color_property,
    create_single_property,
    description_property,
    exdates_property,
    property_del_duration,
    property_doc_duration_template,
    property_get_duration,
    property_set_duration,
    rdates_property,
    rrules_property,
    sequence_property,
    summary_property,
    uid_property,
)
from icalendar.cal.component import Component
from icalendar.cal.examples import get_example
from icalendar.error import IncompleteComponent, InvalidCalendar
from icalendar.tools import is_date

if TYPE_CHECKING:
    from icalendar.alarms import Alarms


[docs] class Event(Component): """A grouping of component properties that describe an event. Description: A "VEVENT" calendar component is a grouping of component properties, possibly including "VALARM" calendar components, that represents a scheduled amount of time on a calendar. For example, it can be an activity; such as a one-hour long, department meeting from 8:00 AM to 9:00 AM, tomorrow. Generally, an event will take up time on an individual calendar. Hence, the event will appear as an opaque interval in a search for busy time. Alternately, the event can have its Time Transparency set to "TRANSPARENT" in order to prevent blocking of the event in searches for busy time. The "VEVENT" is also the calendar component used to specify an anniversary or daily reminder within a calendar. These events have a DATE value type for the "DTSTART" property instead of the default value type of DATE-TIME. If such a "VEVENT" has a "DTEND" property, it MUST be specified as a DATE value also. The anniversary type of "VEVENT" can span more than one date (i.e., "DTEND" property value is set to a calendar date after the "DTSTART" property value). If such a "VEVENT" has a "DURATION" property, it MUST be specified as a "dur-day" or "dur-week" value. The "DTSTART" property for a "VEVENT" specifies the inclusive start of the event. For recurring events, it also specifies the very first instance in the recurrence set. The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event. For cases where a "VEVENT" calendar component specifies a "DTSTART" property with a DATE value type but no "DTEND" nor "DURATION" property, the event's duration is taken to be one day. For cases where a "VEVENT" calendar component specifies a "DTSTART" property with a DATE-TIME value type but no "DTEND" property, the event ends on the same calendar date and time of day specified by the "DTSTART" property. The "VEVENT" calendar component cannot be nested within another calendar component. However, "VEVENT" calendar components can be related to each other or to a "VTODO" or to a "VJOURNAL" calendar component with the "RELATED-TO" property. Examples: The following is an example of the "VEVENT" calendar component used to represent a meeting that will also be opaque to searches for busy time: .. code-block:: text BEGIN:VEVENT UID:19970901T130000Z-123401@example.com DTSTAMP:19970901T130000Z DTSTART:19970903T163000Z DTEND:19970903T190000Z SUMMARY:Annual Employee Review CLASS:PRIVATE CATEGORIES:BUSINESS,HUMAN RESOURCES END:VEVENT The following is an example of the "VEVENT" calendar component used to represent a reminder that will not be opaque, but rather transparent, to searches for busy time: .. code-block:: text BEGIN:VEVENT UID:19970901T130000Z-123402@example.com DTSTAMP:19970901T130000Z DTSTART:19970401T163000Z DTEND:19970402T010000Z SUMMARY:Laurel is in sensitivity awareness class. CLASS:PUBLIC CATEGORIES:BUSINESS,HUMAN RESOURCES TRANSP:TRANSPARENT END:VEVENT The following is an example of the "VEVENT" calendar component used to represent an anniversary that will occur annually: .. code-block:: text BEGIN:VEVENT UID:19970901T130000Z-123403@example.com DTSTAMP:19970901T130000Z DTSTART;VALUE=DATE:19971102 SUMMARY:Our Blissful Anniversary TRANSP:TRANSPARENT CLASS:CONFIDENTIAL CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION RRULE:FREQ=YEARLY END:VEVENT The following is an example of the "VEVENT" calendar component used to represent a multi-day event scheduled from June 28th, 2007 to July 8th, 2007 inclusively. Note that the "DTEND" property is set to July 9th, 2007, since the "DTEND" property specifies the non-inclusive end of the event. .. code-block:: text BEGIN:VEVENT UID:20070423T123432Z-541111@example.com DTSTAMP:20070423T123432Z DTSTART;VALUE=DATE:20070628 DTEND;VALUE=DATE:20070709 SUMMARY:Festival International de Jazz de Montreal TRANSP:TRANSPARENT END:VEVENT Create a new Event: .. code-block:: python >>> from icalendar import Event >>> from datetime import datetime >>> event = Event.new(start=datetime(2021, 1, 1, 12, 30, 0)) >>> print(event.to_ical()) BEGIN:VEVENT DTSTART:20210101T123000 DTSTAMP:20250517T080612Z UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 END:VEVENT """ name = "VEVENT" canonical_order = ( "SUMMARY", "DTSTART", "DTEND", "DURATION", "DTSTAMP", "UID", "RECURRENCE-ID", "SEQUENCE", "RRULE", "RDATE", "EXDATE", ) required = ( "UID", "DTSTAMP", ) singletons = ( "CLASS", "CREATED", "COLOR", "DESCRIPTION", "DTSTART", "GEO", "LAST-MODIFIED", "LOCATION", "ORGANIZER", "PRIORITY", "DTSTAMP", "SEQUENCE", "STATUS", "SUMMARY", "TRANSP", "URL", "RECURRENCE-ID", "DTEND", "DURATION", "UID", ) exclusive = ( "DTEND", "DURATION", ) multiple = ( "ATTACH", "ATTENDEE", "CATEGORIES", "COMMENT", "CONTACT", "EXDATE", "RSTATUS", "RELATED", "RESOURCES", "RDATE", "RRULE", ) ignore_exceptions = True @property def alarms(self) -> Alarms: """Compute the alarm times for this component. >>> from icalendar import Event >>> event = Event.example("rfc_9074_example_1") >>> len(event.alarms.times) 1 >>> alarm_time = event.alarms.times[0] >>> alarm_time.trigger # The time when the alarm pops up datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York')) >>> alarm_time.is_active() # This alarm has not been acknowledged True Note that this only uses DTSTART and DTEND, but ignores RDATE, EXDATE, and RRULE properties. """ from icalendar.alarms import Alarms return Alarms(self)
[docs] @classmethod def example(cls, name: str = "rfc_9074_example_3") -> Event: """Return the calendar example with the given name.""" return cls.from_ical(get_example("events", name))
DTSTART = create_single_property( "DTSTART", "dt", (datetime, date), date, 'The "DTSTART" property for a "VEVENT" specifies the inclusive start of the event.', # noqa: E501 ) DTEND = create_single_property( "DTEND", "dt", (datetime, date), date, 'The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event.', # noqa: E501 ) def _get_start_end_duration(self): """Verify the calendar validity and return the right attributes.""" start = self.DTSTART end = self.DTEND duration = self.DURATION if duration is not None and end is not None: raise InvalidCalendar( "Only one of DTEND and DURATION may be in a VEVENT, not both." ) if ( isinstance(start, date) and not isinstance(start, datetime) and duration is not None and duration.seconds != 0 ): raise InvalidCalendar( "When DTSTART is a date, DURATION must be of days or weeks." ) if start is not None and end is not None and is_date(start) != is_date(end): raise InvalidCalendar( "DTSTART and DTEND must be of the same type, either date or datetime." ) return start, end, duration DURATION = property( property_get_duration, property_set_duration, property_del_duration, property_doc_duration_template.format(component="VEVENT"), ) @property def duration(self) -> timedelta: """The duration of the VEVENT. This duration is calculated from the start and end of the event. You cannot set the duration as it is unclear what happens to start and end. """ return self.end - self.start @property def start(self) -> date | datetime: """The start of the event. Invalid values raise an InvalidCalendar. If there is no start, we also raise an IncompleteComponent error. You can get the start, end and duration of an event as follows: >>> from datetime import datetime >>> from icalendar import Event >>> event = Event() >>> event.start = datetime(2021, 1, 1, 12) >>> event.end = datetime(2021, 1, 1, 12, 30) # 30 minutes >>> event.duration # 1800 seconds == 30 minutes datetime.timedelta(seconds=1800) >>> print(event.to_ical()) BEGIN:VEVENT DTSTART:20210101T120000 DTEND:20210101T123000 END:VEVENT """ start = self._get_start_end_duration()[0] if start is None: raise IncompleteComponent("No DTSTART given.") return start @start.setter def start(self, start: Optional[date | datetime]): """Set the start.""" self.DTSTART = start @property def end(self) -> date | datetime: """The end of the event. Invalid values raise an InvalidCalendar error. If there is no end, we also raise an IncompleteComponent error. """ start, end, duration = self._get_start_end_duration() if end is None and duration is None: if start is None: raise IncompleteComponent("No DTEND or DURATION+DTSTART given.") if is_date(start): return start + timedelta(days=1) return start if duration is not None: if start is not None: return start + duration raise IncompleteComponent("No DTEND or DURATION+DTSTART given.") return end @end.setter def end(self, end: date | datetime | None): """Set the end.""" self.DTEND = end X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property X_MOZ_LASTACK = X_MOZ_LASTACK_property color = color_property sequence = sequence_property categories = categories_property rdates = rdates_property exdates = exdates_property rrules = rrules_property uid = uid_property summary = summary_property description = description_property
[docs] @classmethod def new( cls, /, categories: Sequence[str] = (), color: Optional[str] = None, description: Optional[str] = None, dtstamp: Optional[date] = None, end: Optional[date | datetime] = None, sequence: Optional[int] = None, start: Optional[date | datetime] = None, summary: Optional[str] = None, uid: Optional[str | uuid.UUID] = None, ): """Create a new event with all required properties. This creates a new Event in accordance with :rfc:`5545`. Arguments: categories: The :attr:`categories` of the event. color: The :attr:`color` of the event. description: The :attr:`description` of the event. dtstamp: The :attr:`DTSTAMP` of the event. If None, this is set to the current time. end: The :attr:`end` of the event. sequence: The :attr:`sequence` of the event. start: The :attr:`start` of the event. summary: The :attr:`summary` of the event. uid: The :attr:`uid` of the event. If None, this is set to a new :func:`uuid.uuid4`. Returns: :class:`Event` Raises: IncompleteComponent: If the content is not valid according to :rfc:`5545`. .. warning:: As time progresses, we will be stricter with the validation. """ event = super().new(dtstamp=dtstamp if dtstamp is not None else cls._utc_now()) event.summary = summary event.description = description event.uid = uid if uid is not None else uuid.uuid4() event.start = start event.end = end event.color = color event.categories = categories event.sequence = sequence return event
__all__ = ["Event"]