Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/event.py: 50%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""":rfc:`5545` VEVENT component."""
3from __future__ import annotations
5import uuid
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING, Optional, Sequence
9from icalendar.attr import (
10 X_MOZ_LASTACK_property,
11 X_MOZ_SNOOZE_TIME_property,
12 attendees_property,
13 categories_property,
14 class_property,
15 color_property,
16 contacts_property,
17 create_single_property,
18 description_property,
19 exdates_property,
20 location_property,
21 organizer_property,
22 priority_property,
23 property_del_duration,
24 property_doc_duration_template,
25 property_get_duration,
26 property_set_duration,
27 rdates_property,
28 rrules_property,
29 sequence_property,
30 status_property,
31 summary_property,
32 transparency_property,
33 uid_property,
34 url_property,
35)
36from icalendar.cal.component import Component
37from icalendar.cal.examples import get_example
38from icalendar.error import IncompleteComponent, InvalidCalendar
39from icalendar.tools import is_date
41if TYPE_CHECKING:
42 from icalendar.alarms import Alarms
43 from icalendar.enums import CLASS, STATUS, TRANSP
44 from icalendar.prop import vCalAddress
47class Event(Component):
48 """A grouping of component properties that describe an event.
50 Description:
51 A "VEVENT" calendar component is a grouping of
52 component properties, possibly including "VALARM" calendar
53 components, that represents a scheduled amount of time on a
54 calendar. For example, it can be an activity; such as a one-hour
55 long, department meeting from 8:00 AM to 9:00 AM, tomorrow.
56 Generally, an event will take up time on an individual calendar.
57 Hence, the event will appear as an opaque interval in a search for
58 busy time. Alternately, the event can have its Time Transparency
59 set to "TRANSPARENT" in order to prevent blocking of the event in
60 searches for busy time.
62 The "VEVENT" is also the calendar component used to specify an
63 anniversary or daily reminder within a calendar. These events
64 have a DATE value type for the "DTSTART" property instead of the
65 default value type of DATE-TIME. If such a "VEVENT" has a "DTEND"
66 property, it MUST be specified as a DATE value also. The
67 anniversary type of "VEVENT" can span more than one date (i.e.,
68 "DTEND" property value is set to a calendar date after the
69 "DTSTART" property value). If such a "VEVENT" has a "DURATION"
70 property, it MUST be specified as a "dur-day" or "dur-week" value.
72 The "DTSTART" property for a "VEVENT" specifies the inclusive
73 start of the event. For recurring events, it also specifies the
74 very first instance in the recurrence set. The "DTEND" property
75 for a "VEVENT" calendar component specifies the non-inclusive end
76 of the event. For cases where a "VEVENT" calendar component
77 specifies a "DTSTART" property with a DATE value type but no
78 "DTEND" nor "DURATION" property, the event's duration is taken to
79 be one day. For cases where a "VEVENT" calendar component
80 specifies a "DTSTART" property with a DATE-TIME value type but no
81 "DTEND" property, the event ends on the same calendar date and
82 time of day specified by the "DTSTART" property.
84 The "VEVENT" calendar component cannot be nested within another
85 calendar component. However, "VEVENT" calendar components can be
86 related to each other or to a "VTODO" or to a "VJOURNAL" calendar
87 component with the "RELATED-TO" property.
89 Examples:
90 The following is an example of the "VEVENT" calendar
91 component used to represent a meeting that will also be opaque to
92 searches for busy time:
94 .. code-block:: text
96 BEGIN:VEVENT
97 UID:19970901T130000Z-123401@example.com
98 DTSTAMP:19970901T130000Z
99 DTSTART:19970903T163000Z
100 DTEND:19970903T190000Z
101 SUMMARY:Annual Employee Review
102 CLASS:PRIVATE
103 CATEGORIES:BUSINESS,HUMAN RESOURCES
104 END:VEVENT
106 The following is an example of the "VEVENT" calendar component
107 used to represent a reminder that will not be opaque, but rather
108 transparent, to searches for busy time:
110 .. code-block:: text
112 BEGIN:VEVENT
113 UID:19970901T130000Z-123402@example.com
114 DTSTAMP:19970901T130000Z
115 DTSTART:19970401T163000Z
116 DTEND:19970402T010000Z
117 SUMMARY:Laurel is in sensitivity awareness class.
118 CLASS:PUBLIC
119 CATEGORIES:BUSINESS,HUMAN RESOURCES
120 TRANSP:TRANSPARENT
121 END:VEVENT
123 The following is an example of the "VEVENT" calendar component
124 used to represent an anniversary that will occur annually:
126 .. code-block:: text
128 BEGIN:VEVENT
129 UID:19970901T130000Z-123403@example.com
130 DTSTAMP:19970901T130000Z
131 DTSTART;VALUE=DATE:19971102
132 SUMMARY:Our Blissful Anniversary
133 TRANSP:TRANSPARENT
134 CLASS:CONFIDENTIAL
135 CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION
136 RRULE:FREQ=YEARLY
137 END:VEVENT
139 The following is an example of the "VEVENT" calendar component
140 used to represent a multi-day event scheduled from June 28th, 2007
141 to July 8th, 2007 inclusively. Note that the "DTEND" property is
142 set to July 9th, 2007, since the "DTEND" property specifies the
143 non-inclusive end of the event.
145 .. code-block:: text
147 BEGIN:VEVENT
148 UID:20070423T123432Z-541111@example.com
149 DTSTAMP:20070423T123432Z
150 DTSTART;VALUE=DATE:20070628
151 DTEND;VALUE=DATE:20070709
152 SUMMARY:Festival International de Jazz de Montreal
153 TRANSP:TRANSPARENT
154 END:VEVENT
156 Create a new Event:
158 .. code-block:: python
160 >>> from icalendar import Event
161 >>> from datetime import datetime
162 >>> event = Event.new(start=datetime(2021, 1, 1, 12, 30, 0))
163 >>> print(event.to_ical())
164 BEGIN:VEVENT
165 DTSTART:20210101T123000
166 DTSTAMP:20250517T080612Z
167 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
168 END:VEVENT
170 """
172 name = "VEVENT"
174 canonical_order = (
175 "SUMMARY",
176 "DTSTART",
177 "DTEND",
178 "DURATION",
179 "DTSTAMP",
180 "UID",
181 "RECURRENCE-ID",
182 "SEQUENCE",
183 "RRULE",
184 "RDATE",
185 "EXDATE",
186 )
188 required = (
189 "UID",
190 "DTSTAMP",
191 )
192 singletons = (
193 "CLASS",
194 "CREATED",
195 "COLOR",
196 "DESCRIPTION",
197 "DTSTART",
198 "GEO",
199 "LAST-MODIFIED",
200 "LOCATION",
201 "ORGANIZER",
202 "PRIORITY",
203 "DTSTAMP",
204 "SEQUENCE",
205 "STATUS",
206 "SUMMARY",
207 "TRANSP",
208 "URL",
209 "RECURRENCE-ID",
210 "DTEND",
211 "DURATION",
212 "UID",
213 )
214 exclusive = (
215 "DTEND",
216 "DURATION",
217 )
218 multiple = (
219 "ATTACH",
220 "ATTENDEE",
221 "CATEGORIES",
222 "COMMENT",
223 "CONTACT",
224 "EXDATE",
225 "RSTATUS",
226 "RELATED",
227 "RESOURCES",
228 "RDATE",
229 "RRULE",
230 )
231 ignore_exceptions = True
233 @property
234 def alarms(self) -> Alarms:
235 """Compute the alarm times for this component.
237 >>> from icalendar import Event
238 >>> event = Event.example("rfc_9074_example_1")
239 >>> len(event.alarms.times)
240 1
241 >>> alarm_time = event.alarms.times[0]
242 >>> alarm_time.trigger # The time when the alarm pops up
243 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York'))
244 >>> alarm_time.is_active() # This alarm has not been acknowledged
245 True
247 Note that this only uses DTSTART and DTEND, but ignores
248 RDATE, EXDATE, and RRULE properties.
249 """
250 from icalendar.alarms import Alarms
252 return Alarms(self)
254 @classmethod
255 def example(cls, name: str = "rfc_9074_example_3") -> Event:
256 """Return the calendar example with the given name."""
257 return cls.from_ical(get_example("events", name))
259 DTSTART = create_single_property(
260 "DTSTART",
261 "dt",
262 (datetime, date),
263 date,
264 'The "DTSTART" property for a "VEVENT" specifies the inclusive start of the event.', # noqa: E501
265 )
266 DTEND = create_single_property(
267 "DTEND",
268 "dt",
269 (datetime, date),
270 date,
271 'The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event.', # noqa: E501
272 )
274 def _get_start_end_duration(self):
275 """Verify the calendar validity and return the right attributes."""
276 start = self.DTSTART
277 end = self.DTEND
278 duration = self.DURATION
279 if duration is not None and end is not None:
280 raise InvalidCalendar(
281 "Only one of DTEND and DURATION may be in a VEVENT, not both."
282 )
283 if (
284 isinstance(start, date)
285 and not isinstance(start, datetime)
286 and duration is not None
287 and duration.seconds != 0
288 ):
289 raise InvalidCalendar(
290 "When DTSTART is a date, DURATION must be of days or weeks."
291 )
292 if start is not None and end is not None and is_date(start) != is_date(end):
293 raise InvalidCalendar(
294 "DTSTART and DTEND must be of the same type, either date or datetime."
295 )
296 return start, end, duration
298 DURATION = property(
299 property_get_duration,
300 property_set_duration,
301 property_del_duration,
302 property_doc_duration_template.format(component="VEVENT"),
303 )
305 @property
306 def duration(self) -> timedelta:
307 """The duration of the VEVENT.
309 Returns the DURATION property if set, otherwise calculated from start and end.
310 When setting duration, the end time is automatically calculated from start + duration.
311 """
312 # First check if DURATION property is explicitly set
313 if "DURATION" in self:
314 return self["DURATION"].dt
316 # Fall back to calculated duration from start and end
317 return self.end - self.start
319 @duration.setter
320 def duration(self, duration: timedelta):
321 """Set the duration of the event.
323 This automatically calculates and sets the end time as start + duration.
324 If no start time is set, raises IncompleteComponent.
325 """
326 if (
327 not hasattr(self, "_get_start_end_duration")
328 or self._get_start_end_duration()[0] is None
329 ):
330 raise IncompleteComponent(
331 "Cannot set duration without DTSTART. Set start time first."
332 )
334 start_time = self.start
335 self.end = start_time + duration
337 @property
338 def start(self) -> date | datetime:
339 """The start of the event.
341 Invalid values raise an InvalidCalendar.
342 If there is no start, we also raise an IncompleteComponent error.
344 You can get the start, end and duration of an event as follows:
346 >>> from datetime import datetime
347 >>> from icalendar import Event
348 >>> event = Event()
349 >>> event.start = datetime(2021, 1, 1, 12)
350 >>> event.end = datetime(2021, 1, 1, 12, 30) # 30 minutes
351 >>> event.duration # 1800 seconds == 30 minutes
352 datetime.timedelta(seconds=1800)
353 >>> print(event.to_ical())
354 BEGIN:VEVENT
355 DTSTART:20210101T120000
356 DTEND:20210101T123000
357 END:VEVENT
358 """
359 start = self._get_start_end_duration()[0]
360 if start is None:
361 raise IncompleteComponent("No DTSTART given.")
362 return start
364 @start.setter
365 def start(self, start: Optional[date | datetime]):
366 """Set the start."""
367 self.DTSTART = start
369 @property
370 def end(self) -> date | datetime:
371 """The end of the event.
373 Invalid values raise an InvalidCalendar error.
374 If there is no end, we also raise an IncompleteComponent error.
375 """
376 start, end, duration = self._get_start_end_duration()
377 if end is None and duration is None:
378 if start is None:
379 raise IncompleteComponent("No DTEND or DURATION+DTSTART given.")
380 if is_date(start):
381 return start + timedelta(days=1)
382 return start
383 if duration is not None:
384 if start is not None:
385 return start + duration
386 raise IncompleteComponent("No DTEND or DURATION+DTSTART given.")
387 return end
389 @end.setter
390 def end(self, end: date | datetime | None):
391 """Set the end."""
392 self.DTEND = end
394 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
395 X_MOZ_LASTACK = X_MOZ_LASTACK_property
396 color = color_property
397 sequence = sequence_property
398 categories = categories_property
399 rdates = rdates_property
400 exdates = exdates_property
401 rrules = rrules_property
402 uid = uid_property
403 summary = summary_property
404 description = description_property
405 classification = class_property
406 url = url_property
407 organizer = organizer_property
408 location = location_property
409 priority = priority_property
410 contacts = contacts_property
411 transparency = transparency_property
412 status = status_property
413 attendees = attendees_property
415 @classmethod
416 def new(
417 cls,
418 /,
419 attendees: Optional[list[vCalAddress]] = None,
420 categories: Sequence[str] = (),
421 classification: Optional[CLASS] = None,
422 color: Optional[str] = None,
423 comments: list[str] | str | None = None,
424 contacts: list[str] | str | None = None,
425 created: Optional[date] = None,
426 description: Optional[str] = None,
427 end: Optional[date | datetime] = None,
428 last_modified: Optional[date] = None,
429 location: Optional[str] = None,
430 organizer: Optional[vCalAddress | str] = None,
431 priority: Optional[int] = None,
432 sequence: Optional[int] = None,
433 stamp: Optional[date] = None,
434 start: Optional[date | datetime] = None,
435 status: Optional[STATUS] = None,
436 transparency: Optional[TRANSP] = None,
437 summary: Optional[str] = None,
438 uid: Optional[str | uuid.UUID] = None,
439 url: Optional[str] = None,
440 ):
441 """Create a new event with all required properties.
443 This creates a new Event in accordance with :rfc:`5545`.
445 Arguments:
446 attendees: The :attr:`attendees` of the event.
447 categories: The :attr:`categories` of the event.
448 classification: The :attr:`classification` of the event.
449 color: The :attr:`color` of the event.
450 comments: The :attr:`Component.comments` of the event.
451 created: The :attr:`Component.created` of the event.
452 description: The :attr:`description` of the event.
453 end: The :attr:`end` of the event.
454 last_modified: The :attr:`Component.last_modified` of the event.
455 location: The :attr:`location` of the event.
456 organizer: The :attr:`organizer` of the event.
457 priority: The :attr:`priority` of the event.
458 sequence: The :attr:`sequence` of the event.
459 stamp: The :attr:`Component.stamp` of the event.
460 If None, this is set to the current time.
461 start: The :attr:`start` of the event.
462 status: The :attr:`status` of the event.
463 summary: The :attr:`summary` of the event.
464 transparency: The :attr:`transparency` of the event.
465 uid: The :attr:`uid` of the event.
466 If None, this is set to a new :func:`uuid.uuid4`.
467 url: The :attr:`url` of the event.
469 Returns:
470 :class:`Event`
472 Raises:
473 InvalidCalendar: If the content is not valid according to :rfc:`5545`.
475 .. warning:: As time progresses, we will be stricter with the validation.
476 """
477 event = super().new(
478 stamp=stamp if stamp is not None else cls._utc_now(),
479 created=created,
480 last_modified=last_modified,
481 comments=comments,
482 )
483 event.summary = summary
484 event.description = description
485 event.uid = uid if uid is not None else uuid.uuid4()
486 event.start = start
487 event.end = end
488 event.color = color
489 event.categories = categories
490 event.sequence = sequence
491 event.classification = classification
492 event.url = url
493 event.organizer = organizer
494 event.location = location
495 event.priority = priority
496 event.transparency = transparency
497 event.contacts = contacts
498 event.status = status
499 event.attendees = attendees
500 if cls._validate_new:
501 cls._validate_start_and_end(start, end)
502 return event
505__all__ = ["Event"]