Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 29%
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"""Attributes of Components and properties."""
3from __future__ import annotations
5import itertools
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING, Literal, Optional, Sequence, Union
9from icalendar.enums import BUSYTYPE, CLASS, STATUS, TRANSP, StrEnum
10from icalendar.error import IncompleteComponent, InvalidCalendar
11from icalendar.parser_tools import SEQUENCE_TYPES
12from icalendar.prop import (
13 vCalAddress,
14 vCategory,
15 vDDDTypes,
16 vDuration,
17 vRecur,
18 vText,
19)
20from icalendar.prop.conference import Conference
21from icalendar.prop.image import Image
22from icalendar.timezone import tzp
23from icalendar.tools import is_date
25if TYPE_CHECKING:
26 from icalendar.cal import Component
29def _get_rdates(
30 self: Component,
31) -> list[Union[tuple[date, None], tuple[datetime, None], tuple[datetime, datetime]]]:
32 """The RDATE property defines the list of DATE-TIME values for recurring components.
34 RDATE is defined in :rfc:`5545`.
35 The return value is a list of tuples ``(start, end)``.
37 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
38 with and without timezone.
40 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
41 if the end is specified.
43 Value Type:
44 The default value type for this property is DATE-TIME.
45 The value type can be set to DATE or PERIOD.
47 Property Parameters:
48 IANA, non-standard, value data type, and time
49 zone identifier property parameters can be specified on this
50 property.
52 Conformance:
53 This property can be specified in recurring "VEVENT",
54 "VTODO", and "VJOURNAL" calendar components as well as in the
55 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
56 calendar component.
58 Description:
59 This property can appear along with the "RRULE"
60 property to define an aggregate set of repeating occurrences.
61 When they both appear in a recurring component, the recurrence
62 instances are defined by the union of occurrences defined by both
63 the "RDATE" and "RRULE".
65 The recurrence dates, if specified, are used in computing the
66 recurrence set. The recurrence set is the complete set of
67 recurrence instances for a calendar component. The recurrence set
68 is generated by considering the initial "DTSTART" property along
69 with the "RRULE", "RDATE", and "EXDATE" properties contained
70 within the recurring component. The "DTSTART" property defines
71 the first instance in the recurrence set. The "DTSTART" property
72 value SHOULD match the pattern of the recurrence rule, if
73 specified. The recurrence set generated with a "DTSTART" property
74 value that doesn't match the pattern of the rule is undefined.
75 The final recurrence set is generated by gathering all of the
76 start DATE-TIME values generated by any of the specified "RRULE"
77 and "RDATE" properties, and then excluding any start DATE-TIME
78 values specified by "EXDATE" properties. This implies that start
79 DATE-TIME values specified by "EXDATE" properties take precedence
80 over those specified by inclusion properties (i.e., "RDATE" and
81 "RRULE"). Where duplicate instances are generated by the "RRULE"
82 and "RDATE" properties, only one recurrence is considered.
83 Duplicate instances are ignored.
85 Example:
86 Below, we set one RDATE in a list and get the resulting tuple of start and end.
88 .. code-block:: pycon
90 >>> from icalendar import Event
91 >>> from datetime import datetime
92 >>> event = Event()
94 # Add a list of recurrence dates
95 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
96 >>> event.rdates
97 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
99 .. note::
101 You cannot modify the RDATE value by modifying the result.
102 Use :func:`icalendar.cal.Component.add` to add values.
104 If you want to compute recurrences, have a look at :ref:`Related projects`.
106 """
107 result = []
108 rdates = self.get("RDATE", [])
109 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
110 for dts in rdates.dts:
111 rdate = dts.dt
112 if isinstance(rdate, tuple):
113 # we have a period as rdate
114 if isinstance(rdate[1], timedelta):
115 result.append((rdate[0], rdate[0] + rdate[1]))
116 else:
117 result.append(rdate)
118 else:
119 # we have a date/datetime
120 result.append((rdate, None))
121 return result
124rdates_property = property(_get_rdates)
127def _get_exdates(self: Component) -> list[date | datetime]:
128 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
130 EXDATE is defined in :rfc:`5545`.
132 Value Type:
133 The default value type for this property is DATE-TIME.
134 The value type can be set to DATE.
136 Property Parameters:
137 IANA, non-standard, value data type, and time
138 zone identifier property parameters can be specified on this
139 property.
141 Conformance:
142 This property can be specified in recurring "VEVENT",
143 "VTODO", and "VJOURNAL" calendar components as well as in the
144 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
145 calendar component.
147 Description:
148 The exception dates, if specified, are used in
149 computing the recurrence set. The recurrence set is the complete
150 set of recurrence instances for a calendar component. The
151 recurrence set is generated by considering the initial "DTSTART"
152 property along with the "RRULE", "RDATE", and "EXDATE" properties
153 contained within the recurring component. The "DTSTART" property
154 defines the first instance in the recurrence set. The "DTSTART"
155 property value SHOULD match the pattern of the recurrence rule, if
156 specified. The recurrence set generated with a "DTSTART" property
157 value that doesn't match the pattern of the rule is undefined.
158 The final recurrence set is generated by gathering all of the
159 start DATE-TIME values generated by any of the specified "RRULE"
160 and "RDATE" properties, and then excluding any start DATE-TIME
161 values specified by "EXDATE" properties. This implies that start
162 DATE-TIME values specified by "EXDATE" properties take precedence
163 over those specified by inclusion properties (i.e., "RDATE" and
164 "RRULE"). When duplicate instances are generated by the "RRULE"
165 and "RDATE" properties, only one recurrence is considered.
166 Duplicate instances are ignored.
168 The "EXDATE" property can be used to exclude the value specified
169 in "DTSTART". However, in such cases, the original "DTSTART" date
170 MUST still be maintained by the calendaring and scheduling system
171 because the original "DTSTART" value has inherent usage
172 dependencies by other properties such as the "RECURRENCE-ID".
174 Example:
175 Below, we add an exdate in a list and get the resulting list of exdates.
177 .. code-block:: pycon
179 >>> from icalendar import Event
180 >>> from datetime import datetime
181 >>> event = Event()
183 # Add a list of excluded dates
184 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
185 >>> event.exdates
186 [datetime.datetime(2025, 4, 28, 16, 5)]
188 .. note::
190 You cannot modify the EXDATE value by modifying the result.
191 Use :func:`icalendar.cal.Component.add` to add values.
193 If you want to compute recurrences, have a look at :ref:`Related projects`.
195 """
196 result = []
197 exdates = self.get("EXDATE", [])
198 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
199 for dts in exdates.dts:
200 exdate = dts.dt
201 # we have a date/datetime
202 result.append(exdate)
203 return result
206exdates_property = property(_get_exdates)
209def _get_rrules(self: Component) -> list[vRecur]:
210 """RRULE defines a rule or repeating pattern for recurring components.
212 RRULE is defined in :rfc:`5545`.
213 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
215 Property Parameters:
216 IANA and non-standard property parameters can
217 be specified on this property.
219 Conformance:
220 This property can be specified in recurring "VEVENT",
221 "VTODO", and "VJOURNAL" calendar components as well as in the
222 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
223 calendar component, but it SHOULD NOT be specified more than once.
224 The recurrence set generated with multiple "RRULE" properties is
225 undefined.
227 Description:
228 The recurrence rule, if specified, is used in computing
229 the recurrence set. The recurrence set is the complete set of
230 recurrence instances for a calendar component. The recurrence set
231 is generated by considering the initial "DTSTART" property along
232 with the "RRULE", "RDATE", and "EXDATE" properties contained
233 within the recurring component. The "DTSTART" property defines
234 the first instance in the recurrence set. The "DTSTART" property
235 value SHOULD be synchronized with the recurrence rule, if
236 specified. The recurrence set generated with a "DTSTART" property
237 value not synchronized with the recurrence rule is undefined. The
238 final recurrence set is generated by gathering all of the start
239 DATE-TIME values generated by any of the specified "RRULE" and
240 "RDATE" properties, and then excluding any start DATE-TIME values
241 specified by "EXDATE" properties. This implies that start DATE-
242 TIME values specified by "EXDATE" properties take precedence over
243 those specified by inclusion properties (i.e., "RDATE" and
244 "RRULE"). Where duplicate instances are generated by the "RRULE"
245 and "RDATE" properties, only one recurrence is considered.
246 Duplicate instances are ignored.
248 The "DTSTART" property specified within the iCalendar object
249 defines the first instance of the recurrence. In most cases, a
250 "DTSTART" property of DATE-TIME value type used with a recurrence
251 rule, should be specified as a date with local time and time zone
252 reference to make sure all the recurrence instances start at the
253 same local time regardless of time zone changes.
255 If the duration of the recurring component is specified with the
256 "DTEND" or "DUE" property, then the same exact duration will apply
257 to all the members of the generated recurrence set. Else, if the
258 duration of the recurring component is specified with the
259 "DURATION" property, then the same nominal duration will apply to
260 all the members of the generated recurrence set and the exact
261 duration of each recurrence instance will depend on its specific
262 start time. For example, recurrence instances of a nominal
263 duration of one day will have an exact duration of more or less
264 than 24 hours on a day where a time zone shift occurs. The
265 duration of a specific recurrence may be modified in an exception
266 component or simply by using an "RDATE" property of PERIOD value
267 type.
269 Examples:
270 Daily for 10 occurrences:
272 .. code-block:: pycon
274 >>> from icalendar import Event
275 >>> from datetime import datetime
276 >>> from zoneinfo import ZoneInfo
277 >>> event = Event()
278 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
279 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
280 >>> print(event.to_ical())
281 BEGIN:VEVENT
282 DTSTART;TZID=America/New_York:19970902T090000
283 RRULE:FREQ=DAILY;COUNT=10
284 END:VEVENT
285 >>> event.rrules
286 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
288 Daily until December 24, 1997:
290 .. code-block:: pycon
292 >>> from icalendar import Event, vRecur
293 >>> from datetime import datetime
294 >>> from zoneinfo import ZoneInfo
295 >>> event = Event()
296 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
297 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
298 >>> print(event.to_ical())
299 BEGIN:VEVENT
300 DTSTART;TZID=America/New_York:19970902T090000
301 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
302 END:VEVENT
303 >>> event.rrules
304 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
306 .. note::
308 You cannot modify the RRULE value by modifying the result.
309 Use :func:`icalendar.cal.Component.add` to add values.
311 If you want to compute recurrences, have a look at :ref:`Related projects`.
313 """ # noqa: E501
314 rrules = self.get("RRULE", [])
315 if not isinstance(rrules, list):
316 return [rrules]
317 return rrules
320rrules_property = property(_get_rrules)
323def multi_language_text_property(
324 main_prop: str, compatibility_prop: Optional[str], doc: str
325) -> property:
326 """This creates a text property.
328 This property can be defined several times with different ``LANGUAGE`` parameters.
330 Args:
331 main_prop (str): The property to set and get, such as ``NAME``
332 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
333 doc (str): The documentation string
334 """
336 def fget(self: Component) -> Optional[str]:
337 """Get the property"""
338 result = self.get(main_prop)
339 if result is None and compatibility_prop is not None:
340 result = self.get(compatibility_prop)
341 if isinstance(result, list):
342 for item in result:
343 if "LANGUAGE" not in item.params:
344 return item
345 return result
347 def fset(self: Component, value: Optional[str]):
348 """Set the property."""
349 fdel(self)
350 if value is not None:
351 self.add(main_prop, value)
353 def fdel(self: Component):
354 """Delete the property."""
355 self.pop(main_prop, None)
356 if compatibility_prop is not None:
357 self.pop(compatibility_prop, None)
359 return property(fget, fset, fdel, doc)
362def single_int_property(prop: str, default: int, doc: str) -> property:
363 """Create a property for an int value that exists only once.
365 Args:
366 prop: The name of the property
367 default: The default value
368 doc: The documentation string
369 """
371 def fget(self: Component) -> int:
372 """Get the property"""
373 try:
374 return int(self.get(prop, default))
375 except ValueError as e:
376 raise InvalidCalendar(f"{prop} must be an int") from e
378 def fset(self: Component, value: Optional[int]):
379 """Set the property."""
380 fdel(self)
381 if value is not None:
382 self.add(prop, value)
384 def fdel(self: Component):
385 """Delete the property."""
386 self.pop(prop, None)
388 return property(fget, fset, fdel, doc)
391def single_utc_property(name: str, docs: str) -> property:
392 """Create a property to access a value of datetime in UTC timezone.
394 Args:
395 name: name of the property
396 docs: documentation string
397 """
398 docs = (
399 f"""The {name} property. datetime in UTC
401 All values will be converted to a datetime in UTC.
402 """
403 + docs
404 )
406 def fget(self: Component) -> Optional[datetime]:
407 """Get the value."""
408 if name not in self:
409 return None
410 dt = self.get(name)
411 if isinstance(dt, vText):
412 # we might be in an attribute that is not typed
413 value = vDDDTypes.from_ical(dt)
414 else:
415 value = getattr(dt, "dt", dt)
416 if value is None or not isinstance(value, date):
417 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
418 return tzp.localize_utc(value)
420 def fset(self: Component, value: Optional[datetime]):
421 """Set the value"""
422 if value is None:
423 fdel(self)
424 return
425 if not isinstance(value, date):
426 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
427 fdel(self)
428 self.add(name, tzp.localize_utc(value))
430 def fdel(self: Component):
431 """Delete the property."""
432 self.pop(name, None)
434 return property(fget, fset, fdel, doc=docs)
437def single_string_property(
438 name: str, docs: str, other_name: Optional[str] = None, default: str = ""
439) -> property:
440 """Create a property to access a single string value."""
442 def fget(self: Component) -> str:
443 """Get the value."""
444 result = self.get(
445 name, None if other_name is None else self.get(other_name, None)
446 )
447 if result is None or result == []:
448 return default
449 if isinstance(result, list):
450 return result[0]
451 return result
453 def fset(self: Component, value: Optional[str]):
454 """Set the value.
456 Setting the value to None will delete it.
457 """
458 fdel(self)
459 if value is not None:
460 self.add(name, value)
462 def fdel(self: Component):
463 """Delete the property."""
464 self.pop(name, None)
465 if other_name is not None:
466 self.pop(other_name, None)
468 return property(fget, fset, fdel, doc=docs)
471color_property = single_string_property(
472 "COLOR",
473 """This property specifies a color used for displaying the component.
475 This implements :rfc:`7986` ``COLOR`` property.
477 Property Parameters:
478 IANA and non-standard property parameters can
479 be specified on this property.
481 Conformance:
482 This property can be specified once in an iCalendar
483 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
485 Description:
486 This property specifies a color that clients MAY use
487 when presenting the relevant data to a user. Typically, this
488 would appear as the "background" color of events or tasks. The
489 value is a case-insensitive color name taken from the CSS3 set of
490 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
492 Example:
493 ``"turquoise"``, ``"#ffffff"``
495 .. code-block:: pycon
497 >>> from icalendar import Todo
498 >>> todo = Todo()
499 >>> todo.color = "green"
500 >>> print(todo.to_ical())
501 BEGIN:VTODO
502 COLOR:green
503 END:VTODO
504 """,
505)
507sequence_property = single_int_property(
508 "SEQUENCE",
509 0,
510 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
512Value Type:
513 INTEGER
515Property Parameters:
516 IANA and non-standard property parameters can be specified on this property.
518Conformance:
519 The property can be specified in "VEVENT", "VTODO", or
520 "VJOURNAL" calendar component.
522Description:
523 When a calendar component is created, its sequence
524 number is 0. It is monotonically incremented by the "Organizer's"
525 CUA each time the "Organizer" makes a significant revision to the
526 calendar component.
528 The "Organizer" includes this property in an iCalendar object that
529 it sends to an "Attendee" to specify the current version of the
530 calendar component.
532 The "Attendee" includes this property in an iCalendar object that
533 it sends to the "Organizer" to specify the version of the calendar
534 component to which the "Attendee" is referring.
536 A change to the sequence number is not the mechanism that an
537 "Organizer" uses to request a response from the "Attendees". The
538 "RSVP" parameter on the "ATTENDEE" property is used by the
539 "Organizer" to indicate that a response from the "Attendees" is
540 requested.
542 Recurrence instances of a recurring component MAY have different
543 sequence numbers.
545Examples:
546 The following is an example of this property for a calendar
547 component that was just created by the "Organizer":
549 .. code-block:: pycon
551 >>> from icalendar import Event
552 >>> event = Event()
553 >>> event.sequence
554 0
556 The following is an example of this property for a calendar
557 component that has been revised 10 different times by the
558 "Organizer":
560 .. code-block:: pycon
562 >>> from icalendar import Calendar
563 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
564 >>> event = calendar.events[0]
565 >>> event.sequence
566 10
567 """, # noqa: E501
568)
571def _get_categories(component: Component) -> list[str]:
572 """Get all the categories."""
573 categories: Optional[vCategory | list[vCategory]] = component.get("CATEGORIES")
574 if isinstance(categories, list):
575 _set_categories(
576 component,
577 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
578 )
579 return _get_categories(component)
580 if categories is None:
581 categories = vCategory([])
582 component.add("CATEGORIES", categories)
583 return categories.cats
586def _set_categories(component: Component, cats: Optional[Sequence[str]]) -> None:
587 """Set the categories."""
588 if not cats and cats != []:
589 _del_categories(component)
590 return
591 component["CATEGORIES"] = categories = vCategory(cats)
592 if isinstance(cats, list):
593 cats.clear()
594 cats.extend(categories.cats)
595 categories.cats = cats
598def _del_categories(component: Component) -> None:
599 """Delete the categories."""
600 component.pop("CATEGORIES", None)
603categories_property = property(
604 _get_categories,
605 _set_categories,
606 _del_categories,
607 """This property defines the categories for a component.
609Property Parameters:
610 IANA, non-standard, and language property parameters can be specified on this
611 property.
613Conformance:
614 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
615 components.
616 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
618Description:
619 This property is used to specify categories or subtypes
620 of the calendar component. The categories are useful in searching
621 for a calendar component of a particular type and category.
622 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
623 more than one category can be specified as a COMMA-separated list
624 of categories.
626Example:
627 Below, we add the categories to an event:
629 .. code-block:: pycon
631 >>> from icalendar import Event
632 >>> event = Event()
633 >>> event.categories = ["Work", "Meeting"]
634 >>> print(event.to_ical())
635 BEGIN:VEVENT
636 CATEGORIES:Work,Meeting
637 END:VEVENT
638 >>> event.categories.append("Lecture")
639 >>> event.categories == ["Work", "Meeting", "Lecture"]
640 True
642.. note::
644 At present, we do not take the LANGUAGE parameter into account.
645""",
646)
649def _get_attendees(self: Component) -> list[vCalAddress]:
650 """Get attendees."""
651 value = self.get("ATTENDEE")
652 if value is None:
653 value = []
654 self["ATTENDEE"] = value
655 return value
656 if isinstance(value, vCalAddress):
657 return [value]
658 return value
661def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
662 """Set attendees."""
663 _del_attendees(self)
664 if value is None:
665 return
666 if not isinstance(value, list):
667 value = [value]
668 self["ATTENDEE"] = value
671def _del_attendees(self: Component):
672 """Delete all attendees."""
673 self.pop("ATTENDEE", None)
676attendees_property = property(
677 _get_attendees,
678 _set_attendees,
679 _del_attendees,
680 """ATTENDEE defines one or more "Attendees" within a calendar component.
682Conformance:
683 This property MUST be specified in an iCalendar object
684 that specifies a group-scheduled calendar entity. This property
685 MUST NOT be specified in an iCalendar object when publishing the
686 calendar information (e.g., NOT in an iCalendar object that
687 specifies the publication of a calendar user's busy time, event,
688 to-do, or journal). This property is not specified in an
689 iCalendar object that specifies only a time zone definition or
690 that defines calendar components that are not group-scheduled
691 components, but are components only on a single user's calendar.
693Description:
694 This property MUST only be specified within calendar
695 components to specify participants, non-participants, and the
696 chair of a group-scheduled calendar entity. The property is
697 specified within an "EMAIL" category of the "VALARM" calendar
698 component to specify an email address that is to receive the email
699 type of iCalendar alarm.
701Examples:
702 Add a new attendee to an existing event.
704 .. code-block:: pycon
706 >>> from icalendar import Event, vCalAddress
707 >>> event = Event()
708 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
709 >>> print(event.to_ical())
710 BEGIN:VEVENT
711 ATTENDEE:mailto:me@my-domain.com
712 END:VEVENT
714 Create an email alarm with several attendees:
716 >>> from icalendar import Alarm, vCalAddress
717 >>> alarm = Alarm.new(attendees = [
718 ... vCalAddress("mailto:me@my-domain.com"),
719 ... vCalAddress("mailto:you@my-domain.com"),
720 ... ], summary = "Email alarm")
721 >>> print(alarm.to_ical())
722 BEGIN:VALARM
723 ATTENDEE:mailto:me@my-domain.com
724 ATTENDEE:mailto:you@my-domain.com
725 SUMMARY:Email alarm
726 END:VALARM
727""",
728)
730uid_property = single_string_property(
731 "UID",
732 """UID specifies the persistent, globally unique identifier for a component.
734We recommend using :func:`uuid.uuid4` to generate new values.
736Returns:
737 The value of the UID property as a string or ``""`` if no value is set.
739Description:
740 The "UID" itself MUST be a globally unique identifier.
741 The generator of the identifier MUST guarantee that the identifier
742 is unique.
744 This is the method for correlating scheduling messages with the
745 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
746 The full range of calendar components specified by a recurrence
747 set is referenced by referring to just the "UID" property value
748 corresponding to the calendar component. The "RECURRENCE-ID"
749 property allows the reference to an individual instance within the
750 recurrence set.
752 This property is an important method for group-scheduling
753 applications to match requests with later replies, modifications,
754 or deletion requests. Calendaring and scheduling applications
755 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
756 calendar components to assure interoperability with other group-
757 scheduling applications. This identifier is created by the
758 calendar system that generates an iCalendar object.
760 Implementations MUST be able to receive and persist values of at
761 least 255 octets for this property, but they MUST NOT truncate
762 values in the middle of a UTF-8 multi-octet sequence.
764 :rfc:`7986` states that UID can be used, for
765 example, to identify duplicate calendar streams that a client may
766 have been given access to. It can be used in conjunction with the
767 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
768 to identify the most recent version of a calendar.
770Conformance:
771 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
772 and "VJOURNAL" calendar components.
773 :rfc:`7986` modifies the definition of the "UID" property to
774 allow it to be defined in an iCalendar object.
775 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
776 identifier to be specified. The value of this property can then be used
777 to refer uniquely to the "VALARM" component.
779 This property can be specified once only.
781Security:
782 :rfc:`7986` states that UID values MUST NOT include any data that
783 might identify a user, host, domain, or any other security- or
784 privacy-sensitive information. It is RECOMMENDED that calendar user
785 agents now generate "UID" values that are hex-encoded random
786 Universally Unique Identifier (UUID) values as defined in
787 Sections 4.4 and 4.5 of :rfc:`4122`.
788 You can use the :mod:`uuid` module to generate new UUIDs.
790Compatibility:
791 For Alarms, ``X-ALARMUID`` is also considered.
793Examples:
794 The following is an example of such a property value:
795 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
797 Set the UID of a calendar:
799 .. code-block:: pycon
801 >>> from icalendar import Calendar
802 >>> from uuid import uuid4
803 >>> calendar = Calendar()
804 >>> calendar.uid = uuid4()
805 >>> print(calendar.to_ical())
806 BEGIN:VCALENDAR
807 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
808 END:VCALENDAR
810""",
811)
813summary_property = multi_language_text_property(
814 "SUMMARY",
815 None,
816 """SUMMARY defines a short summary or subject for the calendar component.
818Property Parameters:
819 IANA, non-standard, alternate text
820 representation, and language property parameters can be specified
821 on this property.
823Conformance:
824 The property can be specified in "VEVENT", "VTODO",
825 "VJOURNAL", or "VALARM" calendar components.
827Description:
828 This property is used in the "VEVENT", "VTODO", and
829 "VJOURNAL" calendar components to capture a short, one-line
830 summary about the activity or journal entry.
832 This property is used in the "VALARM" calendar component to
833 capture the subject of an EMAIL category of alarm.
835Examples:
836 The following is an example of this property:
838 .. code-block:: pycon
840 SUMMARY:Department Party
841""",
842)
844description_property = multi_language_text_property(
845 "DESCRIPTION",
846 None,
847 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
849Property Parameters:
850 IANA, non-standard, alternate text
851 representation, and language property parameters can be specified
852 on this property.
854Conformance:
855 The property can be specified in the "VEVENT", "VTODO",
856 "VJOURNAL", or "VALARM" calendar components. The property can be
857 specified multiple times only within a "VJOURNAL" calendar
858 component.
860Description:
861 This property is used in the "VEVENT" and "VTODO" to
862 capture lengthy textual descriptions associated with the activity.
864 This property is used in the "VALARM" calendar component to
865 capture the display text for a DISPLAY category of alarm, and to
866 capture the body text for an EMAIL category of alarm.
868Examples:
869 The following is an example of this property with formatted
870 line breaks in the property value:
872 .. code-block:: pycon
874 DESCRIPTION:Meeting to provide technical review for "Phoenix"
875 design.\\nHappy Face Conference Room. Phoenix design team
876 MUST attend this meeting.\\nRSVP to team leader.
878 """, # noqa: E501
879)
882def create_single_property(
883 prop: str,
884 value_attr: Optional[str],
885 value_type: tuple[type],
886 type_def: type,
887 doc: str,
888 vProp: type = vDDDTypes, # noqa: N803
889):
890 """Create a single property getter and setter.
892 :param prop: The name of the property.
893 :param value_attr: The name of the attribute to get the value from.
894 :param value_type: The type of the value.
895 :param type_def: The type of the property.
896 :param doc: The docstring of the property.
897 :param vProp: The type of the property from :mod:`icalendar.prop`.
898 """
900 def p_get(self: Component):
901 default = object()
902 result = self.get(prop, default)
903 if result is default:
904 return None
905 if isinstance(result, list):
906 raise InvalidCalendar(f"Multiple {prop} defined.")
907 value = result if value_attr is None else getattr(result, value_attr, result)
908 if not isinstance(value, value_type):
909 raise InvalidCalendar(
910 f"{prop} must be either a "
911 f"{' or '.join(t.__name__ for t in value_type)},"
912 f" not {value}."
913 )
914 return value
916 def p_set(self: Component, value) -> None:
917 if value is None:
918 p_del(self)
919 return
920 if not isinstance(value, value_type):
921 raise TypeError(
922 f"Use {' or '.join(t.__name__ for t in value_type)}, "
923 f"not {type(value).__name__}."
924 )
925 self[prop] = vProp(value)
926 if prop in self.exclusive:
927 for other_prop in self.exclusive:
928 if other_prop != prop:
929 self.pop(other_prop, None)
931 p_set.__annotations__["value"] = p_get.__annotations__["return"] = Optional[
932 type_def
933 ]
935 def p_del(self: Component):
936 self.pop(prop)
938 p_doc = f"""The {prop} property.
940 {doc}
942 Accepted values: {", ".join(t.__name__ for t in value_type)}.
943 If the attribute has invalid values, we raise InvalidCalendar.
944 If the value is absent, we return None.
945 You can also delete the value with del or by setting it to None.
946 """
947 return property(p_get, p_set, p_del, p_doc)
950X_MOZ_SNOOZE_TIME_property = single_utc_property(
951 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
952)
953X_MOZ_LASTACK_property = single_utc_property(
954 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
955)
958def property_get_duration(self: Component) -> Optional[timedelta]:
959 """Getter for property DURATION."""
960 default = object()
961 duration = self.get("duration", default)
962 if isinstance(duration, vDDDTypes):
963 return duration.dt
964 if isinstance(duration, vDuration):
965 return duration.td
966 if duration is not default and not isinstance(duration, timedelta):
967 raise InvalidCalendar(
968 f"DURATION must be a timedelta, not {type(duration).__name__}."
969 )
970 return None
973def property_set_duration(self: Component, value: Optional[timedelta]):
974 """Setter for property DURATION."""
975 if value is None:
976 self.pop("duration", None)
977 return
978 if not isinstance(value, timedelta):
979 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
980 self["duration"] = vDuration(value)
981 self.pop("DTEND")
982 self.pop("DUE")
985def property_del_duration(self: Component):
986 """Delete property DURATION."""
987 self.pop("DURATION")
990property_doc_duration_template = """The DURATION property.
992The "DTSTART" property for a "{component}" specifies the inclusive
993start of the {component}.
994The "DURATION" property in conjunction with the DTSTART property
995for a "{component}" calendar component specifies the non-inclusive end
996of the event.
998If you would like to calculate the duration of a {component}, do not use this.
999Instead use the duration property (lower case).
1000"""
1003def duration_property(component: str) -> property:
1004 """Return the duration property."""
1005 return property(
1006 property_get_duration,
1007 property_set_duration,
1008 property_del_duration,
1009 property_doc_duration_template.format(component=component),
1010 )
1013def multi_text_property(name: str, docs: str) -> property:
1014 """Get a property that can occur several times and is text.
1016 Examples: Journal.descriptions, Event.comments
1017 """
1019 def fget(self: Component) -> list[str]:
1020 """Get the values."""
1021 descriptions = self.get(name)
1022 if descriptions is None:
1023 return []
1024 if not isinstance(descriptions, SEQUENCE_TYPES):
1025 return [descriptions]
1026 return descriptions
1028 def fset(self: Component, values: Optional[str | Sequence[str]]):
1029 """Set the values."""
1030 fdel(self)
1031 if values is None:
1032 return
1033 if isinstance(values, str):
1034 self.add(name, values)
1035 else:
1036 for description in values:
1037 self.add(name, description)
1039 def fdel(self: Component):
1040 """Delete the values."""
1041 self.pop(name)
1043 return property(fget, fset, fdel, docs)
1046descriptions_property = multi_text_property(
1047 "DESCRIPTION",
1048 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1050Property Parameters:
1051 IANA, non-standard, alternate text
1052 representation, and language property parameters can be specified
1053 on this property.
1055Conformance:
1056 The property can be
1057 specified multiple times only within a "VJOURNAL" calendar component.
1059Description:
1060 This property is used in the "VJOURNAL" calendar component to
1061 capture one or more textual journal entries.
1063Examples:
1064 The following is an example of this property with formatted
1065 line breaks in the property value:
1067 .. code-block:: pycon
1069 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1070 design.\\nHappy Face Conference Room. Phoenix design team
1071 MUST attend this meeting.\\nRSVP to team leader.
1073""", # noqa: E501
1074)
1076comments_property = multi_text_property(
1077 "COMMENT",
1078 """COMMENT is used to specify a comment to the calendar user.
1080Purpose:
1081 This property specifies non-processing information intended
1082 to provide a comment to the calendar user.
1084Conformance:
1085 In :rfc:`5545`, this property can be specified multiple times in
1086 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1087 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1088 In :rfc:`7953`, this property can be specified multiple times in
1089 "VAVAILABILITY" and "VAVAILABLE".
1091Property Parameters:
1092 IANA, non-standard, alternate text
1093 representation, and language property parameters can be specified
1094 on this property.
1096""",
1097)
1100def _get_organizer(self: Component) -> Optional[vCalAddress]:
1101 """ORGANIZER defines the organizer for a calendar component.
1103 Property Parameters:
1104 IANA, non-standard, language, common name,
1105 directory entry reference, and sent-by property parameters can be
1106 specified on this property.
1108 Conformance:
1109 This property MUST be specified in an iCalendar object
1110 that specifies a group-scheduled calendar entity. This property
1111 MUST be specified in an iCalendar object that specifies the
1112 publication of a calendar user's busy time. This property MUST
1113 NOT be specified in an iCalendar object that specifies only a time
1114 zone definition or that defines calendar components that are not
1115 group-scheduled components, but are components only on a single
1116 user's calendar.
1118 Description:
1119 This property is specified within the "VEVENT",
1120 "VTODO", and "VJOURNAL" calendar components to specify the
1121 organizer of a group-scheduled calendar entity. The property is
1122 specified within the "VFREEBUSY" calendar component to specify the
1123 calendar user requesting the free or busy time. When publishing a
1124 "VFREEBUSY" calendar component, the property is used to specify
1125 the calendar that the published busy time came from.
1127 The property has the property parameters "CN", for specifying the
1128 common or display name associated with the "Organizer", "DIR", for
1129 specifying a pointer to the directory information associated with
1130 the "Organizer", "SENT-BY", for specifying another calendar user
1131 that is acting on behalf of the "Organizer". The non-standard
1132 parameters may also be specified on this property. If the
1133 "LANGUAGE" property parameter is specified, the identified
1134 language applies to the "CN" parameter value.
1135 """
1136 return self.get("ORGANIZER")
1139def _set_organizer(self: Component, value: Optional[vCalAddress | str]):
1140 """Set the value."""
1141 _del_organizer(self)
1142 if value is not None:
1143 self.add("ORGANIZER", value)
1146def _del_organizer(self: Component):
1147 """Delete the value."""
1148 self.pop("ORGANIZER")
1151organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1154def single_string_enum_property(
1155 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1156) -> property:
1157 """Create a property to access a single string value and convert it to an enum."""
1158 prop = single_string_property(name, docs, default=default)
1160 def fget(self: Component) -> StrEnum:
1161 """Get the value."""
1162 value = prop.fget(self)
1163 if value == default:
1164 return default
1165 return enum(str(value))
1167 def fset(self: Component, value: str | StrEnum | None) -> None:
1168 """Set the value."""
1169 if value == "":
1170 value = None
1171 prop.fset(self, value)
1173 return property(fget, fset, prop.fdel, doc=docs)
1176busy_type_property = single_string_enum_property(
1177 "BUSYTYPE",
1178 BUSYTYPE,
1179 BUSYTYPE.BUSY_UNAVAILABLE,
1180 """BUSYTYPE specifies the default busy time type.
1182Returns:
1183 :class:`icalendar.enums.BUSYTYPE`
1185Description:
1186 This property is used to specify the default busy time
1187 type. The values correspond to those used by the FBTYPE"
1188 parameter used on a "FREEBUSY" property, with the exception that
1189 the "FREE" value is not used in this property. If not specified
1190 on a component that allows this property, the default is "BUSY-
1191 UNAVAILABLE".
1192""",
1193)
1195priority_property = single_int_property(
1196 "PRIORITY",
1197 0,
1198 """
1200Conformance:
1201 This property can be specified in "VEVENT" and "VTODO" calendar components
1202 according to :rfc:`5545`.
1203 :rfc:`7953` adds this property to "VAVAILABILITY".
1205Description:
1206 This priority is specified as an integer in the range 0
1207 to 9. A value of 0 specifies an undefined priority. A value of 1
1208 is the highest priority. A value of 2 is the second highest
1209 priority. Subsequent numbers specify a decreasing ordinal
1210 priority. A value of 9 is the lowest priority.
1212 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1213 "LOW" is mapped into this property such that a property value in
1214 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1215 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1216 is "LOW" priority.
1218 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1219 "C3" is mapped into this property such that a property value of 1
1220 specifies "A1", a property value of 2 specifies "A2", a property
1221 value of 3 specifies "A3", and so forth up to a property value of
1222 9 specifies "C3".
1224 Other integer values are reserved for future use.
1226 Within a "VEVENT" calendar component, this property specifies a
1227 priority for the event. This property may be useful when more
1228 than one event is scheduled for a given time period.
1230 Within a "VTODO" calendar component, this property specified a
1231 priority for the to-do. This property is useful in prioritizing
1232 multiple action items for a given time period.
1233""",
1234)
1236class_property = single_string_enum_property(
1237 "CLASS",
1238 CLASS,
1239 CLASS.PUBLIC,
1240 """CLASS specifies the class of the calendar component.
1242Returns:
1243 :class:`icalendar.enums.CLASS`
1245Description:
1246 An access classification is only one component of the
1247 general security system within a calendar application. It
1248 provides a method of capturing the scope of the access the
1249 calendar owner intends for information within an individual
1250 calendar entry. The access classification of an individual
1251 iCalendar component is useful when measured along with the other
1252 security components of a calendar system (e.g., calendar user
1253 authentication, authorization, access rights, access role, etc.).
1254 Hence, the semantics of the individual access classifications
1255 cannot be completely defined by this memo alone. Additionally,
1256 due to the "blind" nature of most exchange processes using this
1257 memo, these access classifications cannot serve as an enforcement
1258 statement for a system receiving an iCalendar object. Rather,
1259 they provide a method for capturing the intention of the calendar
1260 owner for the access to the calendar component. If not specified
1261 in a component that allows this property, the default value is
1262 PUBLIC. Applications MUST treat x-name and iana-token values they
1263 don't recognize the same way as they would the PRIVATE value.
1264""",
1265)
1267transparency_property = single_string_enum_property(
1268 "TRANSP",
1269 TRANSP,
1270 TRANSP.OPAQUE,
1271 """TRANSP defines whether or not an event is transparent to busy time searches.
1273Returns:
1274 :class:`icalendar.enums.TRANSP`
1276Description:
1277 Time Transparency is the characteristic of an event
1278 that determines whether it appears to consume time on a calendar.
1279 Events that consume actual time for the individual or resource
1280 associated with the calendar SHOULD be recorded as OPAQUE,
1281 allowing them to be detected by free/busy time searches. Other
1282 events, which do not take up the individual's (or resource's) time
1283 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1284 busy time searches.
1285""",
1286)
1287status_property = single_string_enum_property(
1288 "STATUS",
1289 STATUS,
1290 "",
1291 """STATUS defines the overall status or confirmation for the calendar component.
1293Returns:
1294 :class:`icalendar.enums.STATUS`
1296The default value is ``""``.
1298Description:
1299 In a group-scheduled calendar component, the property
1300 is used by the "Organizer" to provide a confirmation of the event
1301 to the "Attendees". For example in a "VEVENT" calendar component,
1302 the "Organizer" can indicate that a meeting is tentative,
1303 confirmed, or cancelled. In a "VTODO" calendar component, the
1304 "Organizer" can indicate that an action item needs action, is
1305 completed, is in process or being worked on, or has been
1306 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1307 can indicate that a journal entry is draft, final, or has been
1308 cancelled or removed.
1309""",
1310)
1312url_property = single_string_property(
1313 "URL",
1314 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1316Description:
1317 This property may be used in a calendar component to
1318 convey a location where a more dynamic rendition of the calendar
1319 information associated with the calendar component can be found.
1320 This memo does not attempt to standardize the form of the URI, nor
1321 the format of the resource pointed to by the property value. If
1322 the URL property and Content-Location MIME header are both
1323 specified, they MUST point to the same resource.
1325Conformance:
1326 This property can be specified once in the "VEVENT",
1327 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1328 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1330Example:
1331 The following is an example of this property:
1333 .. code-block:: text
1335 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1337""",
1338)
1340source_property = single_string_property(
1341 "SOURCE",
1342 """A URI from where calendar data can be refreshed.
1344Description:
1345 This property identifies a location where a client can
1346 retrieve updated data for the calendar. Clients SHOULD honor any
1347 specified "REFRESH-INTERVAL" value when periodically retrieving
1348 data. Note that this property differs from the "URL" property in
1349 that "URL" is meant to provide an alternative representation of
1350 the calendar data rather than the original location of the data.
1352Conformance:
1353 This property can be specified once in an iCalendar object.
1355Example:
1356 The following is an example of this property:
1358 .. code-block:: text
1360 SOURCE;VALUE=URI:https://example.com/holidays.ics
1362""",
1363)
1365location_property = multi_language_text_property(
1366 "LOCATION",
1367 None,
1368 """The intended venue for the activity defined by a calendar component.
1370Property Parameters:
1371 IANA, non-standard, alternate text
1372 representation, and language property parameters can be specified
1373 on this property.
1375Conformance:
1376 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1377 calendar component.
1378 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1380Description:
1381 Specific venues such as conference or meeting rooms may
1382 be explicitly specified using this property. An alternate
1383 representation may be specified that is a URI that points to
1384 directory information with more structured specification of the
1385 location. For example, the alternate representation may specify
1386 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1387 CID URL :rfc:`2392` pointing to a MIME body part containing a
1388 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1390""",
1391)
1393contacts_property = multi_text_property(
1394 "CONTACT",
1395 """Contact information associated with the calendar component.
1397Purpose:
1398 This property is used to represent contact information or
1399 alternately a reference to contact information associated with the
1400 calendar component.
1402Property Parameters:
1403 IANA, non-standard, alternate text
1404 representation, and language property parameters can be specified
1405 on this property.
1407Conformance:
1408 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1409 "VJOURNAL", or "VFREEBUSY" calendar component.
1410 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1411 amd "VAVAILABLE" calendar component.
1413Description:
1414 The property value consists of textual contact
1415 information. An alternative representation for the property value
1416 can also be specified that refers to a URI pointing to an
1417 alternate form, such as a vCard :rfc:`2426`, for the contact
1418 information.
1420Example:
1421 The following is an example of this property referencing
1422 textual contact information:
1424 .. code-block:: text
1426 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1428 The following is an example of this property with an alternate
1429 representation of an LDAP URI to a directory entry containing the
1430 contact information:
1432 .. code-block:: text
1434 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1435 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1436 +1-919-555-1234
1438 The following is an example of this property with an alternate
1439 representation of a MIME body part containing the contact
1440 information, such as a vCard :rfc:`2426` embedded in a text/
1441 directory media type :rfc:`2425`:
1443 .. code-block:: text
1445 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1446 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1448 The following is an example of this property referencing a network
1449 resource, such as a vCard :rfc:`2426` object containing the contact
1450 information:
1452 .. code-block:: text
1454 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1455 Dolittle\\, ABC Industries\\, +1-919-555-1234
1456""",
1457)
1460def timezone_datetime_property(name: str, docs: str):
1461 """Create a property to access the values with a proper timezone."""
1463 return single_utc_property(name, docs)
1466rfc_7953_dtstart_property = timezone_datetime_property(
1467 "DTSTART",
1468 """Start of the component.
1470 This is almost the same as :attr:`Event.DTSTART` with one exception:
1471 The values MUST have a timezone and DATE is not allowed.
1473 Description:
1474 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1475 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1476 "DATE-TIME" values specified as either the date with UTC time or
1477 the date with local time and a time zone reference.
1479 """,
1480)
1482rfc_7953_dtend_property = timezone_datetime_property(
1483 "DTEND",
1484 """Start of the component.
1486 This is almost the same as :attr:`Event.DTEND` with one exception:
1487 The values MUST have a timezone and DATE is not allowed.
1489 Description:
1490 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1491 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1492 "DATE-TIME" values specified as either the date with UTC time or
1493 the date with local time and a time zone reference.
1494 """,
1495)
1498@property
1499def rfc_7953_duration_property(self) -> Optional[timedelta]:
1500 """Compute the duration of this component.
1502 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1503 Otherwise, the duration is calculated from :attr:`DTSTART` and
1504 :attr:`DTEND`/:attr:`DURATION`.
1506 This is in accordance with :rfc:`7953`:
1507 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1508 """
1509 duration = self.DURATION
1510 if duration:
1511 return duration
1512 end = self.DTEND
1513 if end is None:
1514 return None
1515 start = self.DTSTART
1516 if start is None:
1517 raise IncompleteComponent("Cannot compute duration without start.")
1518 return end - start
1521@property
1522def rfc_7953_end_property(self) -> Optional[timedelta]:
1523 """Compute the duration of this component.
1525 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1526 Otherwise, the duration is calculated from :attr:`DTSTART` and
1527 :attr:`DTEND`/:attr:`DURATION`.
1529 This is in accordance with :rfc:`7953`:
1530 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1531 """
1532 duration = self.DURATION
1533 if duration:
1534 start = self.DTSTART
1535 if start is None:
1536 raise IncompleteComponent("Cannot compute end without start.")
1537 return start + duration
1538 end = self.DTEND
1539 if end is None:
1540 return None
1541 return end
1544@rfc_7953_end_property.setter
1545def rfc_7953_end_property(self, value: datetime):
1546 self.DTEND = value
1549@rfc_7953_end_property.deleter
1550def rfc_7953_end_property(self):
1551 del self.DTEND
1554def get_start_end_duration_with_validation(
1555 component: Component,
1556 start_property: str,
1557 end_property: str,
1558 component_name: str,
1559) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1560 """
1561 Validate the component and return start, end, and duration.
1563 This tests validity according to :rfc:`5545` rules
1564 for ``Event`` and ``Todo`` components.
1566 Args:
1567 component: The component to validate, either ``Event`` or ``Todo``.
1568 start_property: The start property name, ``DTSTART``.
1569 end_property: The end property name, either ``DTEND`` for ``Event`` or
1570 ``DUE`` for ``Todo``.
1571 component_name: The component name for error messages,
1572 either ``VEVENT`` or ``VTODO``.
1574 Returns:
1575 tuple: (start, end, duration) values from the component.
1577 Raises:
1578 InvalidCalendar: If the component violates RFC 5545 constraints.
1580 """
1581 start = getattr(component, start_property, None)
1582 end = getattr(component, end_property, None)
1583 duration = component.DURATION
1585 # RFC 5545: Only one of end property and DURATION may be present
1586 if duration is not None and end is not None:
1587 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1588 msg = (
1589 f"Only one of {end_name} and DURATION "
1590 f"may be in a {component_name}, not both."
1591 )
1592 raise InvalidCalendar(msg)
1594 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1595 if (
1596 start is not None
1597 and is_date(start)
1598 and duration is not None
1599 and duration.seconds != 0
1600 ):
1601 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1602 raise InvalidCalendar(msg)
1604 # RFC 5545: DTSTART and end property must be of the same type
1605 if start is not None and end is not None and is_date(start) != is_date(end):
1606 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1607 msg = (
1608 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1609 )
1610 raise InvalidCalendar(msg)
1612 return start, end, duration
1615def get_start_property(component: Component) -> date | datetime:
1616 """
1617 Get the start property with validation.
1619 Args:
1620 component: The component from which to get its start property.
1622 Returns:
1623 The ``DTSTART`` value.
1625 Raises:
1626 IncompleteComponent: If no ``DTSTART`` is present.
1628 """
1629 # Trigger validation by calling _get_start_end_duration
1630 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1631 if start is None:
1632 msg = "No DTSTART given."
1633 raise IncompleteComponent(msg)
1634 return start
1637def get_end_property(component: Component, end_property: str) -> date | datetime:
1638 """
1639 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1641 Args:
1642 component: The component to get end from
1643 end_property: The end property name, either ``DTEND`` for ``Event`` or
1644 ``DUE`` for ``Todo``.
1646 Returns:
1647 The computed end value.
1649 Raises:
1650 IncompleteComponent: If the provided information is incomplete
1651 to compute the end property.
1653 """
1654 # Trigger validation by calling _get_start_end_duration
1655 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1657 if end is None and duration is None:
1658 if start is None:
1659 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1660 msg = f"No {end_name} or DURATION+DTSTART given."
1661 raise IncompleteComponent(msg)
1663 # Default behavior: date gets +1 day, datetime gets same time
1664 if is_date(start):
1665 return start + timedelta(days=1)
1666 return start
1668 if duration is not None:
1669 if start is not None:
1670 return start + duration
1671 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1672 msg = f"No {end_name} or DURATION+DTSTART given."
1673 raise IncompleteComponent(msg)
1675 return end
1678def get_duration_property(component: Component) -> timedelta:
1679 """
1680 Get the duration property with fallback calculation from start and end.
1682 Args:
1683 component: The component from which to get its duration property.
1685 Returns:
1686 The duration as a timedelta.
1688 """
1689 # First check if DURATION property is explicitly set
1690 if "DURATION" in component:
1691 return component["DURATION"].dt
1693 # Fall back to calculated duration from start and end
1694 return component.end - component.start
1697def set_duration_with_locking(
1698 component: Component,
1699 duration: timedelta | None,
1700 locked: Literal["start", "end"],
1701 end_property: str,
1702) -> None:
1703 """
1704 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1706 Args:
1707 component: The component to modify, either ``Event`` or ``Todo``.
1708 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1709 locked: Which property to keep unchanged, either ``start`` or ``end``.
1710 end_property: The end property name, either ``DTEND`` for ``Event`` or
1711 ``DUE`` for ``Todo``.
1713 """
1714 # Convert to DURATION property if duration is None
1715 if duration is None:
1716 if "DURATION" in component:
1717 return # Already has DURATION property
1718 current_duration = component.duration
1719 component.DURATION = current_duration
1720 return
1722 if not isinstance(duration, timedelta):
1723 msg = f"Use timedelta, not {type(duration).__name__}."
1724 raise TypeError(msg)
1726 # Validate date/duration compatibility
1727 start = component.DTSTART
1728 if start is not None and is_date(start) and duration.seconds != 0:
1729 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1730 raise InvalidCalendar(msg)
1732 if locked == "start":
1733 # Keep start locked, adjust end
1734 if start is None:
1735 msg = "Cannot set duration without DTSTART. Set start time first."
1736 raise IncompleteComponent(msg)
1737 component.pop(end_property, None) # Remove end property
1738 component.DURATION = duration
1739 elif locked == "end":
1740 # Keep end locked, adjust start
1741 current_end = component.end
1742 component.DTSTART = current_end - duration
1743 component.pop(end_property, None) # Remove end property
1744 component.DURATION = duration
1745 else:
1746 msg = f"locked must be 'start' or 'end', not {locked!r}"
1747 raise ValueError(msg)
1750def set_start_with_locking(
1751 component: Component,
1752 start: date | datetime,
1753 locked: Literal["duration", "end"] | None,
1754 end_property: str,
1755) -> None:
1756 """
1757 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1759 Args:
1760 component: The component to modify, either ``Event`` or ``Todo``.
1761 start: The start time to set.
1762 locked: Which property to keep unchanged, either ``duration``, ``end``,
1763 or ``None`` for auto-detect.
1764 end_property: The end property name, either ``DTEND`` for ``Event`` or
1765 ``DUE`` for ``Todo``.
1767 """
1768 if locked is None:
1769 # Auto-detect based on existing properties
1770 if "DURATION" in component:
1771 locked = "duration"
1772 elif end_property in component:
1773 locked = "end"
1774 else:
1775 # Default to duration if no existing properties
1776 locked = "duration"
1778 if locked == "duration":
1779 # Keep duration locked, adjust end
1780 current_duration = (
1781 component.duration
1782 if "DURATION" in component or end_property in component
1783 else None
1784 )
1785 component.DTSTART = start
1786 if current_duration is not None:
1787 component.pop(end_property, None) # Remove end property
1788 component.DURATION = current_duration
1789 elif locked == "end":
1790 # Keep end locked, adjust duration
1791 current_end = component.end
1792 component.DTSTART = start
1793 component.pop("DURATION", None) # Remove duration property
1794 setattr(component, end_property, current_end)
1795 else:
1796 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1797 raise ValueError(msg)
1800def set_end_with_locking(
1801 component: Component,
1802 end: date | datetime,
1803 locked: Literal["start", "duration"],
1804 end_property: str,
1805) -> None:
1806 """
1807 Set the end with explicit locking behavior for Event and Todo components.
1809 Args:
1810 component: The component to modify, either ``Event`` or ``Todo``.
1811 end: The end time to set.
1812 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1813 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1814 for ``Todo``.
1816 """
1817 if locked == "start":
1818 # Keep start locked, adjust duration
1819 component.pop("DURATION", None) # Remove duration property
1820 setattr(component, end_property, end)
1821 elif locked == "duration":
1822 # Keep duration locked, adjust start
1823 current_duration = component.duration
1824 component.DTSTART = end - current_duration
1825 component.pop(end_property, None) # Remove end property
1826 component.DURATION = current_duration
1827 else:
1828 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1829 raise ValueError(msg)
1832def _get_images(self: Component) -> list[Image]:
1833 """IMAGE specifies an image associated with the calendar or a calendar component.
1835 Description:
1836 This property specifies an image for an iCalendar
1837 object or a calendar component via a URI or directly with inline
1838 data that can be used by calendar user agents when presenting the
1839 calendar data to a user. Multiple properties MAY be used to
1840 specify alternative sets of images with, for example, varying
1841 media subtypes, resolutions, or sizes. When multiple properties
1842 are present, calendar user agents SHOULD display only one of them,
1843 picking one that provides the most appropriate image quality, or
1844 display none. The "DISPLAY" parameter is used to indicate the
1845 intended display mode for the image. The "ALTREP" parameter,
1846 defined in :rfc:`5545`, can be used to provide a "clickable" image
1847 where the URI in the parameter value can be "launched" by a click
1848 on the image in the calendar user agent.
1850 Conformance:
1851 This property can be specified multiple times in an
1852 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1853 components.
1855 .. note::
1857 At the present moment, this property is read-only. If you require a setter,
1858 please open an issue or a pull request.
1859 """
1860 images = self.get("IMAGE", [])
1861 if not isinstance(images, SEQUENCE_TYPES):
1862 images = [images]
1863 return [Image.from_property_value(img) for img in images]
1866images_property = property(_get_images)
1869def _get_conferences(self: Component) -> list[Conference]:
1870 """Return the CONFERENCE properties as a list.
1872 Purpose:
1873 This property specifies information for accessing a conferencing system.
1875 Conformance:
1876 This property can be specified multiple times in a
1877 "VEVENT" or "VTODO" calendar component.
1879 Description:
1880 This property specifies information for accessing a
1881 conferencing system for attendees of a meeting or task. This
1882 might be for a telephone-based conference number dial-in with
1883 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1884 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1885 or https: URI :rfc:`7230`), or for an instant messaging group chat
1886 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1887 conferencing system is not available, a data: URI :rfc:`2397`
1888 containing a text description can be used.
1890 A conference system can be a bidirectional communication channel
1891 or a uni-directional "broadcast feed".
1893 The "FEATURE" property parameter is used to describe the key
1894 capabilities of the conference system to allow a client to choose
1895 the ones that give the required level of interaction from a set of
1896 multiple properties.
1898 The "LABEL" property parameter is used to convey additional
1899 details on the use of the URI. For example, the URIs or access
1900 codes for the moderator and attendee of a teleconference system
1901 could be different, and the "LABEL" property parameter could be
1902 used to "tag" each "CONFERENCE" property to indicate which is
1903 which.
1905 The "LANGUAGE" property parameter can be used to specify the
1906 language used for text values used with this property (as per
1907 Section 3.2.10 of :rfc:`5545`).
1909 Example:
1910 The following are examples of this property:
1912 .. code-block:: text
1914 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1915 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1916 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1917 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1918 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1919 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1920 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1921 LABEL=Chat room:xmpp:chat-123@conference.example.com
1922 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1923 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1925 Get all conferences:
1927 .. code-block:: pycon
1929 >>> from icalendar import Event
1930 >>> event = Event()
1931 >>> event.conferences
1932 []
1934 Set a conference:
1936 .. code-block:: pycon
1938 >>> from icalendar import Event, Conference
1939 >>> event = Event()
1940 >>> event.conferences = [
1941 ... Conference(
1942 ... "tel:+1-412-555-0123,,,654321",
1943 ... feature="PHONE,MODERATOR",
1944 ... label="Moderator dial-in",
1945 ... language="EN",
1946 ... )
1947 ... ]
1948 >>> print(event.to_ical())
1949 BEGIN:VEVENT
1950 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN:t
1951 el:+1-412-555-0123,,,654321
1952 END:VEVENT
1954 """
1955 conferences = self.get("CONFERENCE", [])
1956 if not isinstance(conferences, SEQUENCE_TYPES):
1957 conferences = [conferences]
1958 return [Conference.from_uri(conference) for conference in conferences]
1961def _set_conferences(self: Component, conferences: list[Conference] | None):
1962 """Set the conferences."""
1963 _del_conferences(self)
1964 for conference in conferences or []:
1965 self.add("CONFERENCE", conference.to_uri())
1968def _del_conferences(self: Component):
1969 """Delete all conferences."""
1970 self.pop("CONFERENCE")
1973conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
1975__all__ = [
1976 "attendees_property",
1977 "busy_type_property",
1978 "categories_property",
1979 "class_property",
1980 "color_property",
1981 "comments_property",
1982 "conferences_property",
1983 "contacts_property",
1984 "create_single_property",
1985 "description_property",
1986 "descriptions_property",
1987 "duration_property",
1988 "exdates_property",
1989 "get_duration_property",
1990 "get_end_property",
1991 "get_start_end_duration_with_validation",
1992 "get_start_property",
1993 "images_property",
1994 "location_property",
1995 "multi_language_text_property",
1996 "organizer_property",
1997 "priority_property",
1998 "property_del_duration",
1999 "property_doc_duration_template",
2000 "property_get_duration",
2001 "property_set_duration",
2002 "rdates_property",
2003 "rfc_7953_dtend_property",
2004 "rfc_7953_dtstart_property",
2005 "rfc_7953_duration_property",
2006 "rfc_7953_end_property",
2007 "rrules_property",
2008 "sequence_property",
2009 "set_duration_with_locking",
2010 "set_end_with_locking",
2011 "set_start_with_locking",
2012 "single_int_property",
2013 "single_utc_property",
2014 "source_property",
2015 "status_property",
2016 "summary_property",
2017 "transparency_property",
2018 "uid_property",
2019 "url_property",
2020]