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, List, 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 vUid,
20 vUri,
21 vXmlReference,
22)
23from icalendar.prop.conference import Conference
24from icalendar.prop.image import Image
25from icalendar.timezone import tzp
26from icalendar.tools import is_date
28if TYPE_CHECKING:
29 from icalendar.cal import Component
31try:
32 from typing import TypeAlias
33except ImportError:
34 from typing_extensions import TypeAlias
37def _get_rdates(
38 self: Component,
39) -> list[Union[tuple[date, None], tuple[datetime, None], tuple[datetime, datetime]]]:
40 """The RDATE property defines the list of DATE-TIME values for recurring components.
42 RDATE is defined in :rfc:`5545`.
43 The return value is a list of tuples ``(start, end)``.
45 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
46 with and without timezone.
48 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
49 if the end is specified.
51 Value Type:
52 The default value type for this property is DATE-TIME.
53 The value type can be set to DATE or PERIOD.
55 Property Parameters:
56 IANA, non-standard, value data type, and time
57 zone identifier property parameters can be specified on this
58 property.
60 Conformance:
61 This property can be specified in recurring "VEVENT",
62 "VTODO", and "VJOURNAL" calendar components as well as in the
63 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
64 calendar component.
66 Description:
67 This property can appear along with the "RRULE"
68 property to define an aggregate set of repeating occurrences.
69 When they both appear in a recurring component, the recurrence
70 instances are defined by the union of occurrences defined by both
71 the "RDATE" and "RRULE".
73 The recurrence dates, if specified, are used in computing the
74 recurrence set. The recurrence set is the complete set of
75 recurrence instances for a calendar component. The recurrence set
76 is generated by considering the initial "DTSTART" property along
77 with the "RRULE", "RDATE", and "EXDATE" properties contained
78 within the recurring component. The "DTSTART" property defines
79 the first instance in the recurrence set. The "DTSTART" property
80 value SHOULD match the pattern of the recurrence rule, if
81 specified. The recurrence set generated with a "DTSTART" property
82 value that doesn't match the pattern of the rule is undefined.
83 The final recurrence set is generated by gathering all of the
84 start DATE-TIME values generated by any of the specified "RRULE"
85 and "RDATE" properties, and then excluding any start DATE-TIME
86 values specified by "EXDATE" properties. This implies that start
87 DATE-TIME values specified by "EXDATE" properties take precedence
88 over those specified by inclusion properties (i.e., "RDATE" and
89 "RRULE"). Where duplicate instances are generated by the "RRULE"
90 and "RDATE" properties, only one recurrence is considered.
91 Duplicate instances are ignored.
93 Example:
94 Below, we set one RDATE in a list and get the resulting tuple of start and end.
96 .. code-block:: pycon
98 >>> from icalendar import Event
99 >>> from datetime import datetime
100 >>> event = Event()
102 # Add a list of recurrence dates
103 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
104 >>> event.rdates
105 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
107 .. note::
109 You cannot modify the RDATE value by modifying the result.
110 Use :func:`icalendar.cal.Component.add` to add values.
112 If you want to compute recurrences, have a look at
113 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
115 """
116 result = []
117 rdates = self.get("RDATE", [])
118 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
119 for dts in rdates.dts:
120 rdate = dts.dt
121 if isinstance(rdate, tuple):
122 # we have a period as rdate
123 if isinstance(rdate[1], timedelta):
124 result.append((rdate[0], rdate[0] + rdate[1]))
125 else:
126 result.append(rdate)
127 else:
128 # we have a date/datetime
129 result.append((rdate, None))
130 return result
133rdates_property = property(_get_rdates)
136def _get_exdates(self: Component) -> list[date | datetime]:
137 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
139 EXDATE is defined in :rfc:`5545`.
141 Value Type:
142 The default value type for this property is DATE-TIME.
143 The value type can be set to DATE.
145 Property Parameters:
146 IANA, non-standard, value data type, and time
147 zone identifier property parameters can be specified on this
148 property.
150 Conformance:
151 This property can be specified in recurring "VEVENT",
152 "VTODO", and "VJOURNAL" calendar components as well as in the
153 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
154 calendar component.
156 Description:
157 The exception dates, if specified, are used in
158 computing the recurrence set. The recurrence set is the complete
159 set of recurrence instances for a calendar component. The
160 recurrence set is generated by considering the initial "DTSTART"
161 property along with the "RRULE", "RDATE", and "EXDATE" properties
162 contained within the recurring component. The "DTSTART" property
163 defines the first instance in the recurrence set. The "DTSTART"
164 property value SHOULD match the pattern of the recurrence rule, if
165 specified. The recurrence set generated with a "DTSTART" property
166 value that doesn't match the pattern of the rule is undefined.
167 The final recurrence set is generated by gathering all of the
168 start DATE-TIME values generated by any of the specified "RRULE"
169 and "RDATE" properties, and then excluding any start DATE-TIME
170 values specified by "EXDATE" properties. This implies that start
171 DATE-TIME values specified by "EXDATE" properties take precedence
172 over those specified by inclusion properties (i.e., "RDATE" and
173 "RRULE"). When duplicate instances are generated by the "RRULE"
174 and "RDATE" properties, only one recurrence is considered.
175 Duplicate instances are ignored.
177 The "EXDATE" property can be used to exclude the value specified
178 in "DTSTART". However, in such cases, the original "DTSTART" date
179 MUST still be maintained by the calendaring and scheduling system
180 because the original "DTSTART" value has inherent usage
181 dependencies by other properties such as the "RECURRENCE-ID".
183 Example:
184 Below, we add an exdate in a list and get the resulting list of exdates.
186 .. code-block:: pycon
188 >>> from icalendar import Event
189 >>> from datetime import datetime
190 >>> event = Event()
192 # Add a list of excluded dates
193 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
194 >>> event.exdates
195 [datetime.datetime(2025, 4, 28, 16, 5)]
197 .. note::
199 You cannot modify the EXDATE value by modifying the result.
200 Use :func:`icalendar.cal.Component.add` to add values.
202 If you want to compute recurrences, have a look at
203 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
205 """
206 result = []
207 exdates = self.get("EXDATE", [])
208 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
209 for dts in exdates.dts:
210 exdate = dts.dt
211 # we have a date/datetime
212 result.append(exdate)
213 return result
216exdates_property = property(_get_exdates)
219def _get_rrules(self: Component) -> list[vRecur]:
220 """RRULE defines a rule or repeating pattern for recurring components.
222 RRULE is defined in :rfc:`5545`.
223 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
225 Property Parameters:
226 IANA and non-standard property parameters can
227 be specified on this property.
229 Conformance:
230 This property can be specified in recurring "VEVENT",
231 "VTODO", and "VJOURNAL" calendar components as well as in the
232 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
233 calendar component, but it SHOULD NOT be specified more than once.
234 The recurrence set generated with multiple "RRULE" properties is
235 undefined.
237 Description:
238 The recurrence rule, if specified, is used in computing
239 the recurrence set. The recurrence set is the complete set of
240 recurrence instances for a calendar component. The recurrence set
241 is generated by considering the initial "DTSTART" property along
242 with the "RRULE", "RDATE", and "EXDATE" properties contained
243 within the recurring component. The "DTSTART" property defines
244 the first instance in the recurrence set. The "DTSTART" property
245 value SHOULD be synchronized with the recurrence rule, if
246 specified. The recurrence set generated with a "DTSTART" property
247 value not synchronized with the recurrence rule is undefined. The
248 final recurrence set is generated by gathering all of the start
249 DATE-TIME values generated by any of the specified "RRULE" and
250 "RDATE" properties, and then excluding any start DATE-TIME values
251 specified by "EXDATE" properties. This implies that start DATE-
252 TIME values specified by "EXDATE" properties take precedence over
253 those specified by inclusion properties (i.e., "RDATE" and
254 "RRULE"). Where duplicate instances are generated by the "RRULE"
255 and "RDATE" properties, only one recurrence is considered.
256 Duplicate instances are ignored.
258 The "DTSTART" property specified within the iCalendar object
259 defines the first instance of the recurrence. In most cases, a
260 "DTSTART" property of DATE-TIME value type used with a recurrence
261 rule, should be specified as a date with local time and time zone
262 reference to make sure all the recurrence instances start at the
263 same local time regardless of time zone changes.
265 If the duration of the recurring component is specified with the
266 "DTEND" or "DUE" property, then the same exact duration will apply
267 to all the members of the generated recurrence set. Else, if the
268 duration of the recurring component is specified with the
269 "DURATION" property, then the same nominal duration will apply to
270 all the members of the generated recurrence set and the exact
271 duration of each recurrence instance will depend on its specific
272 start time. For example, recurrence instances of a nominal
273 duration of one day will have an exact duration of more or less
274 than 24 hours on a day where a time zone shift occurs. The
275 duration of a specific recurrence may be modified in an exception
276 component or simply by using an "RDATE" property of PERIOD value
277 type.
279 Examples:
280 Daily for 10 occurrences:
282 .. code-block:: pycon
284 >>> from icalendar import Event
285 >>> from datetime import datetime
286 >>> from zoneinfo import ZoneInfo
287 >>> event = Event()
288 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
289 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
290 >>> print(event.to_ical())
291 BEGIN:VEVENT
292 DTSTART;TZID=America/New_York:19970902T090000
293 RRULE:FREQ=DAILY;COUNT=10
294 END:VEVENT
295 >>> event.rrules
296 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
298 Daily until December 24, 1997:
300 .. code-block:: pycon
302 >>> from icalendar import Event, vRecur
303 >>> from datetime import datetime
304 >>> from zoneinfo import ZoneInfo
305 >>> event = Event()
306 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
307 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
308 >>> print(event.to_ical())
309 BEGIN:VEVENT
310 DTSTART;TZID=America/New_York:19970902T090000
311 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
312 END:VEVENT
313 >>> event.rrules
314 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
316 .. note::
318 You cannot modify the RRULE value by modifying the result.
319 Use :func:`icalendar.cal.Component.add` to add values.
321 If you want to compute recurrences, have a look at
322 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
324 """ # noqa: E501
325 rrules = self.get("RRULE", [])
326 if not isinstance(rrules, list):
327 return [rrules]
328 return rrules
331rrules_property = property(_get_rrules)
334def multi_language_text_property(
335 main_prop: str, compatibility_prop: Optional[str], doc: str
336) -> property:
337 """This creates a text property.
339 This property can be defined several times with different ``LANGUAGE`` parameters.
341 Args:
342 main_prop (str): The property to set and get, such as ``NAME``
343 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
344 doc (str): The documentation string
345 """
347 def fget(self: Component) -> Optional[str]:
348 """Get the property"""
349 result = self.get(main_prop)
350 if result is None and compatibility_prop is not None:
351 result = self.get(compatibility_prop)
352 if isinstance(result, list):
353 for item in result:
354 if "LANGUAGE" not in item.params:
355 return item
356 return result
358 def fset(self: Component, value: Optional[str]):
359 """Set the property."""
360 fdel(self)
361 if value is not None:
362 self.add(main_prop, value)
364 def fdel(self: Component):
365 """Delete the property."""
366 self.pop(main_prop, None)
367 if compatibility_prop is not None:
368 self.pop(compatibility_prop, None)
370 return property(fget, fset, fdel, doc)
373def single_int_property(prop: str, default: int, doc: str) -> property:
374 """Create a property for an int value that exists only once.
376 Args:
377 prop: The name of the property
378 default: The default value
379 doc: The documentation string
380 """
382 def fget(self: Component) -> int:
383 """Get the property"""
384 try:
385 return int(self.get(prop, default))
386 except ValueError as e:
387 raise InvalidCalendar(f"{prop} must be an int") from e
389 def fset(self: Component, value: Optional[int]):
390 """Set the property."""
391 fdel(self)
392 if value is not None:
393 self.add(prop, value)
395 def fdel(self: Component):
396 """Delete the property."""
397 self.pop(prop, None)
399 return property(fget, fset, fdel, doc)
402def single_utc_property(name: str, docs: str) -> property:
403 """Create a property to access a value of datetime in UTC timezone.
405 Args:
406 name: name of the property
407 docs: documentation string
408 """
409 docs = (
410 f"""The {name} property. datetime in UTC
412 All values will be converted to a datetime in UTC.
413 """
414 + docs
415 )
417 def fget(self: Component) -> Optional[datetime]:
418 """Get the value."""
419 if name not in self:
420 return None
421 dt = self.get(name)
422 if isinstance(dt, vText):
423 # we might be in an attribute that is not typed
424 value = vDDDTypes.from_ical(dt)
425 else:
426 value = getattr(dt, "dt", dt)
427 if value is None or not isinstance(value, date):
428 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
429 return tzp.localize_utc(value)
431 def fset(self: Component, value: Optional[datetime]):
432 """Set the value"""
433 if value is None:
434 fdel(self)
435 return
436 if not isinstance(value, date):
437 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
438 fdel(self)
439 self.add(name, tzp.localize_utc(value))
441 def fdel(self: Component):
442 """Delete the property."""
443 self.pop(name, None)
445 return property(fget, fset, fdel, doc=docs)
448def single_string_property(
449 name: str, docs: str, other_name: Optional[str] = None, default: str = ""
450) -> property:
451 """Create a property to access a single string value."""
453 def fget(self: Component) -> str:
454 """Get the value."""
455 result = self.get(
456 name, None if other_name is None else self.get(other_name, None)
457 )
458 if result is None or result == []:
459 return default
460 if isinstance(result, list):
461 return result[0]
462 return result
464 def fset(self: Component, value: Optional[str]):
465 """Set the value.
467 Setting the value to None will delete it.
468 """
469 fdel(self)
470 if value is not None:
471 self.add(name, value)
473 def fdel(self: Component):
474 """Delete the property."""
475 self.pop(name, None)
476 if other_name is not None:
477 self.pop(other_name, None)
479 return property(fget, fset, fdel, doc=docs)
482color_property = single_string_property(
483 "COLOR",
484 """This property specifies a color used for displaying the component.
486 This implements :rfc:`7986` ``COLOR`` property.
488 Property Parameters:
489 IANA and non-standard property parameters can
490 be specified on this property.
492 Conformance:
493 This property can be specified once in an iCalendar
494 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
496 Description:
497 This property specifies a color that clients MAY use
498 when presenting the relevant data to a user. Typically, this
499 would appear as the "background" color of events or tasks. The
500 value is a case-insensitive color name taken from the CSS3 set of
501 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
503 Example:
504 ``"turquoise"``, ``"#ffffff"``
506 .. code-block:: pycon
508 >>> from icalendar import Todo
509 >>> todo = Todo()
510 >>> todo.color = "green"
511 >>> print(todo.to_ical())
512 BEGIN:VTODO
513 COLOR:green
514 END:VTODO
515 """,
516)
518sequence_property = single_int_property(
519 "SEQUENCE",
520 0,
521 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
523Value Type:
524 INTEGER
526Property Parameters:
527 IANA and non-standard property parameters can be specified on this property.
529Conformance:
530 The property can be specified in "VEVENT", "VTODO", or
531 "VJOURNAL" calendar component.
533Description:
534 When a calendar component is created, its sequence
535 number is 0. It is monotonically incremented by the "Organizer's"
536 CUA each time the "Organizer" makes a significant revision to the
537 calendar component.
539 The "Organizer" includes this property in an iCalendar object that
540 it sends to an "Attendee" to specify the current version of the
541 calendar component.
543 The "Attendee" includes this property in an iCalendar object that
544 it sends to the "Organizer" to specify the version of the calendar
545 component to which the "Attendee" is referring.
547 A change to the sequence number is not the mechanism that an
548 "Organizer" uses to request a response from the "Attendees". The
549 "RSVP" parameter on the "ATTENDEE" property is used by the
550 "Organizer" to indicate that a response from the "Attendees" is
551 requested.
553 Recurrence instances of a recurring component MAY have different
554 sequence numbers.
556Examples:
557 The following is an example of this property for a calendar
558 component that was just created by the "Organizer":
560 .. code-block:: pycon
562 >>> from icalendar import Event
563 >>> event = Event()
564 >>> event.sequence
565 0
567 The following is an example of this property for a calendar
568 component that has been revised 10 different times by the
569 "Organizer":
571 .. code-block:: pycon
573 >>> from icalendar import Calendar
574 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
575 >>> event = calendar.events[0]
576 >>> event.sequence
577 10
578 """, # noqa: E501
579)
582def _get_categories(component: Component) -> list[str]:
583 """Get all the categories."""
584 categories: Optional[vCategory | list[vCategory]] = component.get("CATEGORIES")
585 if isinstance(categories, list):
586 _set_categories(
587 component,
588 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
589 )
590 return _get_categories(component)
591 if categories is None:
592 categories = vCategory([])
593 component.add("CATEGORIES", categories)
594 return categories.cats
597def _set_categories(component: Component, cats: Optional[Sequence[str]]) -> None:
598 """Set the categories."""
599 if not cats and cats != []:
600 _del_categories(component)
601 return
602 component["CATEGORIES"] = categories = vCategory(cats)
603 if isinstance(cats, list):
604 cats.clear()
605 cats.extend(categories.cats)
606 categories.cats = cats
609def _del_categories(component: Component) -> None:
610 """Delete the categories."""
611 component.pop("CATEGORIES", None)
614categories_property = property(
615 _get_categories,
616 _set_categories,
617 _del_categories,
618 """This property defines the categories for a component.
620Property Parameters:
621 IANA, non-standard, and language property parameters can be specified on this
622 property.
624Conformance:
625 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
626 components.
627 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
629Description:
630 This property is used to specify categories or subtypes
631 of the calendar component. The categories are useful in searching
632 for a calendar component of a particular type and category.
633 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
634 more than one category can be specified as a COMMA-separated list
635 of categories.
637Example:
638 Below, we add the categories to an event:
640 .. code-block:: pycon
642 >>> from icalendar import Event
643 >>> event = Event()
644 >>> event.categories = ["Work", "Meeting"]
645 >>> print(event.to_ical())
646 BEGIN:VEVENT
647 CATEGORIES:Work,Meeting
648 END:VEVENT
649 >>> event.categories.append("Lecture")
650 >>> event.categories == ["Work", "Meeting", "Lecture"]
651 True
653.. note::
655 At present, we do not take the LANGUAGE parameter into account.
657.. seealso::
659 :attr:`Component.concepts`
660""",
661)
664def _get_attendees(self: Component) -> list[vCalAddress]:
665 """Get attendees."""
666 value = self.get("ATTENDEE")
667 if value is None:
668 value = []
669 self["ATTENDEE"] = value
670 return value
671 if isinstance(value, vCalAddress):
672 return [value]
673 return value
676def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
677 """Set attendees."""
678 _del_attendees(self)
679 if value is None:
680 return
681 if not isinstance(value, list):
682 value = [value]
683 self["ATTENDEE"] = value
686def _del_attendees(self: Component):
687 """Delete all attendees."""
688 self.pop("ATTENDEE", None)
691attendees_property = property(
692 _get_attendees,
693 _set_attendees,
694 _del_attendees,
695 """ATTENDEE defines one or more "Attendees" within a calendar component.
697Conformance:
698 This property MUST be specified in an iCalendar object
699 that specifies a group-scheduled calendar entity. This property
700 MUST NOT be specified in an iCalendar object when publishing the
701 calendar information (e.g., NOT in an iCalendar object that
702 specifies the publication of a calendar user's busy time, event,
703 to-do, or journal). This property is not specified in an
704 iCalendar object that specifies only a time zone definition or
705 that defines calendar components that are not group-scheduled
706 components, but are components only on a single user's calendar.
708Description:
709 This property MUST only be specified within calendar
710 components to specify participants, non-participants, and the
711 chair of a group-scheduled calendar entity. The property is
712 specified within an "EMAIL" category of the "VALARM" calendar
713 component to specify an email address that is to receive the email
714 type of iCalendar alarm.
716Examples:
717 Add a new attendee to an existing event.
719 .. code-block:: pycon
721 >>> from icalendar import Event, vCalAddress
722 >>> event = Event()
723 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
724 >>> print(event.to_ical())
725 BEGIN:VEVENT
726 ATTENDEE:mailto:me@my-domain.com
727 END:VEVENT
729 Create an email alarm with several attendees:
731 >>> from icalendar import Alarm, vCalAddress
732 >>> alarm = Alarm.new(attendees = [
733 ... vCalAddress("mailto:me@my-domain.com"),
734 ... vCalAddress("mailto:you@my-domain.com"),
735 ... ], summary = "Email alarm")
736 >>> print(alarm.to_ical())
737 BEGIN:VALARM
738 ATTENDEE:mailto:me@my-domain.com
739 ATTENDEE:mailto:you@my-domain.com
740 SUMMARY:Email alarm
741 END:VALARM
742""",
743)
745uid_property = single_string_property(
746 "UID",
747 """UID specifies the persistent, globally unique identifier for a component.
749We recommend using :func:`uuid.uuid4` to generate new values.
751Returns:
752 The value of the UID property as a string or ``""`` if no value is set.
754Description:
755 The "UID" itself MUST be a globally unique identifier.
756 The generator of the identifier MUST guarantee that the identifier
757 is unique.
759 This is the method for correlating scheduling messages with the
760 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
761 The full range of calendar components specified by a recurrence
762 set is referenced by referring to just the "UID" property value
763 corresponding to the calendar component. The "RECURRENCE-ID"
764 property allows the reference to an individual instance within the
765 recurrence set.
767 This property is an important method for group-scheduling
768 applications to match requests with later replies, modifications,
769 or deletion requests. Calendaring and scheduling applications
770 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
771 calendar components to assure interoperability with other group-
772 scheduling applications. This identifier is created by the
773 calendar system that generates an iCalendar object.
775 Implementations MUST be able to receive and persist values of at
776 least 255 octets for this property, but they MUST NOT truncate
777 values in the middle of a UTF-8 multi-octet sequence.
779 :rfc:`7986` states that UID can be used, for
780 example, to identify duplicate calendar streams that a client may
781 have been given access to. It can be used in conjunction with the
782 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
783 to identify the most recent version of a calendar.
785Conformance:
786 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
787 and "VJOURNAL" calendar components.
788 :rfc:`7986` modifies the definition of the "UID" property to
789 allow it to be defined in an iCalendar object.
790 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
791 identifier to be specified. The value of this property can then be used
792 to refer uniquely to the "VALARM" component.
794 This property can be specified once only.
796Security:
797 :rfc:`7986` states that UID values MUST NOT include any data that
798 might identify a user, host, domain, or any other security- or
799 privacy-sensitive information. It is RECOMMENDED that calendar user
800 agents now generate "UID" values that are hex-encoded random
801 Universally Unique Identifier (UUID) values as defined in
802 Sections 4.4 and 4.5 of :rfc:`4122`.
803 You can use the :mod:`uuid` module to generate new UUIDs.
805Compatibility:
806 For Alarms, ``X-ALARMUID`` is also considered.
808Examples:
809 The following is an example of such a property value:
810 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
812 Set the UID of a calendar:
814 .. code-block:: pycon
816 >>> from icalendar import Calendar
817 >>> from uuid import uuid4
818 >>> calendar = Calendar()
819 >>> calendar.uid = uuid4()
820 >>> print(calendar.to_ical())
821 BEGIN:VCALENDAR
822 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
823 END:VCALENDAR
825""",
826)
828summary_property = multi_language_text_property(
829 "SUMMARY",
830 None,
831 """SUMMARY defines a short summary or subject for the calendar component.
833Property Parameters:
834 IANA, non-standard, alternate text
835 representation, and language property parameters can be specified
836 on this property.
838Conformance:
839 The property can be specified in "VEVENT", "VTODO",
840 "VJOURNAL", or "VALARM" calendar components.
842Description:
843 This property is used in the "VEVENT", "VTODO", and
844 "VJOURNAL" calendar components to capture a short, one-line
845 summary about the activity or journal entry.
847 This property is used in the "VALARM" calendar component to
848 capture the subject of an EMAIL category of alarm.
850Examples:
851 The following is an example of this property:
853 .. code-block:: pycon
855 SUMMARY:Department Party
856""",
857)
859description_property = multi_language_text_property(
860 "DESCRIPTION",
861 None,
862 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
864Property Parameters:
865 IANA, non-standard, alternate text
866 representation, and language property parameters can be specified
867 on this property.
869Conformance:
870 The property can be specified in the "VEVENT", "VTODO",
871 "VJOURNAL", or "VALARM" calendar components. The property can be
872 specified multiple times only within a "VJOURNAL" calendar
873 component.
875Description:
876 This property is used in the "VEVENT" and "VTODO" to
877 capture lengthy textual descriptions associated with the activity.
879 This property is used in the "VALARM" calendar component to
880 capture the display text for a DISPLAY category of alarm, and to
881 capture the body text for an EMAIL category of alarm.
883Examples:
884 The following is an example of this property with formatted
885 line breaks in the property value:
887 .. code-block:: pycon
889 DESCRIPTION:Meeting to provide technical review for "Phoenix"
890 design.\\nHappy Face Conference Room. Phoenix design team
891 MUST attend this meeting.\\nRSVP to team leader.
893 """, # noqa: E501
894)
897def create_single_property(
898 prop: str,
899 value_attr: Optional[str],
900 value_type: tuple[type],
901 type_def: type,
902 doc: str,
903 vProp: type = vDDDTypes, # noqa: N803
904):
905 """Create a single property getter and setter.
907 :param prop: The name of the property.
908 :param value_attr: The name of the attribute to get the value from.
909 :param value_type: The type of the value.
910 :param type_def: The type of the property.
911 :param doc: The docstring of the property.
912 :param vProp: The type of the property from :mod:`icalendar.prop`.
913 """
915 def p_get(self: Component):
916 default = object()
917 result = self.get(prop, default)
918 if result is default:
919 return None
920 if isinstance(result, list):
921 raise InvalidCalendar(f"Multiple {prop} defined.")
922 value = result if value_attr is None else getattr(result, value_attr, result)
923 if not isinstance(value, value_type):
924 raise InvalidCalendar(
925 f"{prop} must be either a "
926 f"{' or '.join(t.__name__ for t in value_type)},"
927 f" not {value}."
928 )
929 return value
931 def p_set(self: Component, value) -> None:
932 if value is None:
933 p_del(self)
934 return
935 if not isinstance(value, value_type):
936 raise TypeError(
937 f"Use {' or '.join(t.__name__ for t in value_type)}, "
938 f"not {type(value).__name__}."
939 )
940 self[prop] = vProp(value)
941 if prop in self.exclusive:
942 for other_prop in self.exclusive:
943 if other_prop != prop:
944 self.pop(other_prop, None)
946 p_set.__annotations__["value"] = p_get.__annotations__["return"] = Optional[
947 type_def
948 ]
950 def p_del(self: Component):
951 self.pop(prop)
953 p_doc = f"""The {prop} property.
955 {doc}
957 Accepted values: {", ".join(t.__name__ for t in value_type)}.
958 If the attribute has invalid values, we raise InvalidCalendar.
959 If the value is absent, we return None.
960 You can also delete the value with del or by setting it to None.
961 """
962 return property(p_get, p_set, p_del, p_doc)
965X_MOZ_SNOOZE_TIME_property = single_utc_property(
966 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
967)
968X_MOZ_LASTACK_property = single_utc_property(
969 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
970)
973def property_get_duration(self: Component) -> Optional[timedelta]:
974 """Getter for property DURATION."""
975 default = object()
976 duration = self.get("duration", default)
977 if isinstance(duration, vDDDTypes):
978 return duration.dt
979 if isinstance(duration, vDuration):
980 return duration.td
981 if duration is not default and not isinstance(duration, timedelta):
982 raise InvalidCalendar(
983 f"DURATION must be a timedelta, not {type(duration).__name__}."
984 )
985 return None
988def property_set_duration(self: Component, value: Optional[timedelta]):
989 """Setter for property DURATION."""
990 if value is None:
991 self.pop("duration", None)
992 return
993 if not isinstance(value, timedelta):
994 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
995 self["duration"] = vDuration(value)
996 self.pop("DTEND")
997 self.pop("DUE")
1000def property_del_duration(self: Component):
1001 """Delete property DURATION."""
1002 self.pop("DURATION")
1005property_doc_duration_template = """The DURATION property.
1007The "DTSTART" property for a "{component}" specifies the inclusive
1008start of the {component}.
1009The "DURATION" property in conjunction with the DTSTART property
1010for a "{component}" calendar component specifies the non-inclusive end
1011of the event.
1013If you would like to calculate the duration of a {component}, do not use this.
1014Instead use the duration property (lower case).
1015"""
1018def duration_property(component: str) -> property:
1019 """Return the duration property."""
1020 return property(
1021 property_get_duration,
1022 property_set_duration,
1023 property_del_duration,
1024 property_doc_duration_template.format(component=component),
1025 )
1028def multi_text_property(name: str, docs: str) -> property:
1029 """Get a property that can occur several times and is text.
1031 Examples: Journal.descriptions, Event.comments
1032 """
1034 def fget(self: Component) -> list[str]:
1035 """Get the values."""
1036 descriptions = self.get(name)
1037 if descriptions is None:
1038 return []
1039 if not isinstance(descriptions, SEQUENCE_TYPES):
1040 return [descriptions]
1041 return descriptions
1043 def fset(self: Component, values: Optional[str | Sequence[str]]):
1044 """Set the values."""
1045 fdel(self)
1046 if values is None:
1047 return
1048 if isinstance(values, str):
1049 self.add(name, values)
1050 else:
1051 for description in values:
1052 self.add(name, description)
1054 def fdel(self: Component):
1055 """Delete the values."""
1056 self.pop(name)
1058 return property(fget, fset, fdel, docs)
1061descriptions_property = multi_text_property(
1062 "DESCRIPTION",
1063 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1065Property Parameters:
1066 IANA, non-standard, alternate text
1067 representation, and language property parameters can be specified
1068 on this property.
1070Conformance:
1071 The property can be
1072 specified multiple times only within a "VJOURNAL" calendar component.
1074Description:
1075 This property is used in the "VJOURNAL" calendar component to
1076 capture one or more textual journal entries.
1078Examples:
1079 The following is an example of this property with formatted
1080 line breaks in the property value:
1082 .. code-block:: pycon
1084 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1085 design.\\nHappy Face Conference Room. Phoenix design team
1086 MUST attend this meeting.\\nRSVP to team leader.
1088""", # noqa: E501
1089)
1091comments_property = multi_text_property(
1092 "COMMENT",
1093 """COMMENT is used to specify a comment to the calendar user.
1095Purpose:
1096 This property specifies non-processing information intended
1097 to provide a comment to the calendar user.
1099Conformance:
1100 In :rfc:`5545`, this property can be specified multiple times in
1101 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1102 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1103 In :rfc:`7953`, this property can be specified multiple times in
1104 "VAVAILABILITY" and "VAVAILABLE".
1106Property Parameters:
1107 IANA, non-standard, alternate text
1108 representation, and language property parameters can be specified
1109 on this property.
1111""",
1112)
1115def _get_organizer(self: Component) -> Optional[vCalAddress]:
1116 """ORGANIZER defines the organizer for a calendar component.
1118 Property Parameters:
1119 IANA, non-standard, language, common name,
1120 directory entry reference, and sent-by property parameters can be
1121 specified on this property.
1123 Conformance:
1124 This property MUST be specified in an iCalendar object
1125 that specifies a group-scheduled calendar entity. This property
1126 MUST be specified in an iCalendar object that specifies the
1127 publication of a calendar user's busy time. This property MUST
1128 NOT be specified in an iCalendar object that specifies only a time
1129 zone definition or that defines calendar components that are not
1130 group-scheduled components, but are components only on a single
1131 user's calendar.
1133 Description:
1134 This property is specified within the "VEVENT",
1135 "VTODO", and "VJOURNAL" calendar components to specify the
1136 organizer of a group-scheduled calendar entity. The property is
1137 specified within the "VFREEBUSY" calendar component to specify the
1138 calendar user requesting the free or busy time. When publishing a
1139 "VFREEBUSY" calendar component, the property is used to specify
1140 the calendar that the published busy time came from.
1142 The property has the property parameters "CN", for specifying the
1143 common or display name associated with the "Organizer", "DIR", for
1144 specifying a pointer to the directory information associated with
1145 the "Organizer", "SENT-BY", for specifying another calendar user
1146 that is acting on behalf of the "Organizer". The non-standard
1147 parameters may also be specified on this property. If the
1148 "LANGUAGE" property parameter is specified, the identified
1149 language applies to the "CN" parameter value.
1150 """
1151 return self.get("ORGANIZER")
1154def _set_organizer(self: Component, value: Optional[vCalAddress | str]):
1155 """Set the value."""
1156 _del_organizer(self)
1157 if value is not None:
1158 self.add("ORGANIZER", value)
1161def _del_organizer(self: Component):
1162 """Delete the value."""
1163 self.pop("ORGANIZER")
1166organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1169def single_string_enum_property(
1170 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1171) -> property:
1172 """Create a property to access a single string value and convert it to an enum."""
1173 prop = single_string_property(name, docs, default=default)
1175 def fget(self: Component) -> StrEnum:
1176 """Get the value."""
1177 value = prop.fget(self)
1178 if value == default:
1179 return default
1180 return enum(str(value))
1182 def fset(self: Component, value: str | StrEnum | None) -> None:
1183 """Set the value."""
1184 if value == "":
1185 value = None
1186 prop.fset(self, value)
1188 return property(fget, fset, prop.fdel, doc=docs)
1191busy_type_property = single_string_enum_property(
1192 "BUSYTYPE",
1193 BUSYTYPE,
1194 BUSYTYPE.BUSY_UNAVAILABLE,
1195 """BUSYTYPE specifies the default busy time type.
1197Returns:
1198 :class:`icalendar.enums.BUSYTYPE`
1200Description:
1201 This property is used to specify the default busy time
1202 type. The values correspond to those used by the FBTYPE"
1203 parameter used on a "FREEBUSY" property, with the exception that
1204 the "FREE" value is not used in this property. If not specified
1205 on a component that allows this property, the default is "BUSY-
1206 UNAVAILABLE".
1207""",
1208)
1210priority_property = single_int_property(
1211 "PRIORITY",
1212 0,
1213 """
1215Conformance:
1216 This property can be specified in "VEVENT" and "VTODO" calendar components
1217 according to :rfc:`5545`.
1218 :rfc:`7953` adds this property to "VAVAILABILITY".
1220Description:
1221 This priority is specified as an integer in the range 0
1222 to 9. A value of 0 specifies an undefined priority. A value of 1
1223 is the highest priority. A value of 2 is the second highest
1224 priority. Subsequent numbers specify a decreasing ordinal
1225 priority. A value of 9 is the lowest priority.
1227 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1228 "LOW" is mapped into this property such that a property value in
1229 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1230 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1231 is "LOW" priority.
1233 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1234 "C3" is mapped into this property such that a property value of 1
1235 specifies "A1", a property value of 2 specifies "A2", a property
1236 value of 3 specifies "A3", and so forth up to a property value of
1237 9 specifies "C3".
1239 Other integer values are reserved for future use.
1241 Within a "VEVENT" calendar component, this property specifies a
1242 priority for the event. This property may be useful when more
1243 than one event is scheduled for a given time period.
1245 Within a "VTODO" calendar component, this property specified a
1246 priority for the to-do. This property is useful in prioritizing
1247 multiple action items for a given time period.
1248""",
1249)
1251class_property = single_string_enum_property(
1252 "CLASS",
1253 CLASS,
1254 CLASS.PUBLIC,
1255 """CLASS specifies the class of the calendar component.
1257Returns:
1258 :class:`icalendar.enums.CLASS`
1260Description:
1261 An access classification is only one component of the
1262 general security system within a calendar application. It
1263 provides a method of capturing the scope of the access the
1264 calendar owner intends for information within an individual
1265 calendar entry. The access classification of an individual
1266 iCalendar component is useful when measured along with the other
1267 security components of a calendar system (e.g., calendar user
1268 authentication, authorization, access rights, access role, etc.).
1269 Hence, the semantics of the individual access classifications
1270 cannot be completely defined by this memo alone. Additionally,
1271 due to the "blind" nature of most exchange processes using this
1272 memo, these access classifications cannot serve as an enforcement
1273 statement for a system receiving an iCalendar object. Rather,
1274 they provide a method for capturing the intention of the calendar
1275 owner for the access to the calendar component. If not specified
1276 in a component that allows this property, the default value is
1277 PUBLIC. Applications MUST treat x-name and iana-token values they
1278 don't recognize the same way as they would the PRIVATE value.
1279""",
1280)
1282transparency_property = single_string_enum_property(
1283 "TRANSP",
1284 TRANSP,
1285 TRANSP.OPAQUE,
1286 """TRANSP defines whether or not an event is transparent to busy time searches.
1288Returns:
1289 :class:`icalendar.enums.TRANSP`
1291Description:
1292 Time Transparency is the characteristic of an event
1293 that determines whether it appears to consume time on a calendar.
1294 Events that consume actual time for the individual or resource
1295 associated with the calendar SHOULD be recorded as OPAQUE,
1296 allowing them to be detected by free/busy time searches. Other
1297 events, which do not take up the individual's (or resource's) time
1298 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1299 busy time searches.
1300""",
1301)
1302status_property = single_string_enum_property(
1303 "STATUS",
1304 STATUS,
1305 "",
1306 """STATUS defines the overall status or confirmation for the calendar component.
1308Returns:
1309 :class:`icalendar.enums.STATUS`
1311The default value is ``""``.
1313Description:
1314 In a group-scheduled calendar component, the property
1315 is used by the "Organizer" to provide a confirmation of the event
1316 to the "Attendees". For example in a "VEVENT" calendar component,
1317 the "Organizer" can indicate that a meeting is tentative,
1318 confirmed, or cancelled. In a "VTODO" calendar component, the
1319 "Organizer" can indicate that an action item needs action, is
1320 completed, is in process or being worked on, or has been
1321 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1322 can indicate that a journal entry is draft, final, or has been
1323 cancelled or removed.
1324""",
1325)
1327url_property = single_string_property(
1328 "URL",
1329 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1331Description:
1332 This property may be used in a calendar component to
1333 convey a location where a more dynamic rendition of the calendar
1334 information associated with the calendar component can be found.
1335 This memo does not attempt to standardize the form of the URI, nor
1336 the format of the resource pointed to by the property value. If
1337 the URL property and Content-Location MIME header are both
1338 specified, they MUST point to the same resource.
1340Conformance:
1341 This property can be specified once in the "VEVENT",
1342 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1343 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1345Example:
1346 The following is an example of this property:
1348 .. code-block:: text
1350 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1352""",
1353)
1355source_property = single_string_property(
1356 "SOURCE",
1357 """A URI from where calendar data can be refreshed.
1359Description:
1360 This property identifies a location where a client can
1361 retrieve updated data for the calendar. Clients SHOULD honor any
1362 specified "REFRESH-INTERVAL" value when periodically retrieving
1363 data. Note that this property differs from the "URL" property in
1364 that "URL" is meant to provide an alternative representation of
1365 the calendar data rather than the original location of the data.
1367Conformance:
1368 This property can be specified once in an iCalendar object.
1370Example:
1371 The following is an example of this property:
1373 .. code-block:: text
1375 SOURCE;VALUE=URI:https://example.com/holidays.ics
1377""",
1378)
1380location_property = multi_language_text_property(
1381 "LOCATION",
1382 None,
1383 """The intended venue for the activity defined by a calendar component.
1385Property Parameters:
1386 IANA, non-standard, alternate text
1387 representation, and language property parameters can be specified
1388 on this property.
1390Conformance:
1391 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1392 calendar component.
1393 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1395Description:
1396 Specific venues such as conference or meeting rooms may
1397 be explicitly specified using this property. An alternate
1398 representation may be specified that is a URI that points to
1399 directory information with more structured specification of the
1400 location. For example, the alternate representation may specify
1401 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1402 CID URL :rfc:`2392` pointing to a MIME body part containing a
1403 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1405""",
1406)
1408contacts_property = multi_text_property(
1409 "CONTACT",
1410 """Contact information associated with the calendar component.
1412Purpose:
1413 This property is used to represent contact information or
1414 alternately a reference to contact information associated with the
1415 calendar component.
1417Property Parameters:
1418 IANA, non-standard, alternate text
1419 representation, and language property parameters can be specified
1420 on this property.
1422Conformance:
1423 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1424 "VJOURNAL", or "VFREEBUSY" calendar component.
1425 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1426 amd "VAVAILABLE" calendar component.
1428Description:
1429 The property value consists of textual contact
1430 information. An alternative representation for the property value
1431 can also be specified that refers to a URI pointing to an
1432 alternate form, such as a vCard :rfc:`2426`, for the contact
1433 information.
1435Example:
1436 The following is an example of this property referencing
1437 textual contact information:
1439 .. code-block:: text
1441 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1443 The following is an example of this property with an alternate
1444 representation of an LDAP URI to a directory entry containing the
1445 contact information:
1447 .. code-block:: text
1449 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1450 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1451 +1-919-555-1234
1453 The following is an example of this property with an alternate
1454 representation of a MIME body part containing the contact
1455 information, such as a vCard :rfc:`2426` embedded in a text/
1456 directory media type :rfc:`2425`:
1458 .. code-block:: text
1460 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1461 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1463 The following is an example of this property referencing a network
1464 resource, such as a vCard :rfc:`2426` object containing the contact
1465 information:
1467 .. code-block:: text
1469 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1470 Dolittle\\, ABC Industries\\, +1-919-555-1234
1471""",
1472)
1475def timezone_datetime_property(name: str, docs: str):
1476 """Create a property to access the values with a proper timezone."""
1478 return single_utc_property(name, docs)
1481rfc_7953_dtstart_property = timezone_datetime_property(
1482 "DTSTART",
1483 """Start of the component.
1485 This is almost the same as :attr:`Event.DTSTART` with one exception:
1486 The values MUST have a timezone and DATE is not allowed.
1488 Description:
1489 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1490 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1491 "DATE-TIME" values specified as either the date with UTC time or
1492 the date with local time and a time zone reference.
1494 """,
1495)
1497rfc_7953_dtend_property = timezone_datetime_property(
1498 "DTEND",
1499 """Start of the component.
1501 This is almost the same as :attr:`Event.DTEND` with one exception:
1502 The values MUST have a timezone and DATE is not allowed.
1504 Description:
1505 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1506 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1507 "DATE-TIME" values specified as either the date with UTC time or
1508 the date with local time and a time zone reference.
1509 """,
1510)
1513@property
1514def rfc_7953_duration_property(self) -> Optional[timedelta]:
1515 """Compute the duration of this component.
1517 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1518 Otherwise, the duration is calculated from :attr:`DTSTART` and
1519 :attr:`DTEND`/:attr:`DURATION`.
1521 This is in accordance with :rfc:`7953`:
1522 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1523 """
1524 duration = self.DURATION
1525 if duration:
1526 return duration
1527 end = self.DTEND
1528 if end is None:
1529 return None
1530 start = self.DTSTART
1531 if start is None:
1532 raise IncompleteComponent("Cannot compute duration without start.")
1533 return end - start
1536@property
1537def rfc_7953_end_property(self) -> Optional[timedelta]:
1538 """Compute the duration of this component.
1540 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1541 Otherwise, the duration is calculated from :attr:`DTSTART` and
1542 :attr:`DTEND`/:attr:`DURATION`.
1544 This is in accordance with :rfc:`7953`:
1545 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1546 """
1547 duration = self.DURATION
1548 if duration:
1549 start = self.DTSTART
1550 if start is None:
1551 raise IncompleteComponent("Cannot compute end without start.")
1552 return start + duration
1553 end = self.DTEND
1554 if end is None:
1555 return None
1556 return end
1559@rfc_7953_end_property.setter
1560def rfc_7953_end_property(self, value: datetime):
1561 self.DTEND = value
1564@rfc_7953_end_property.deleter
1565def rfc_7953_end_property(self):
1566 del self.DTEND
1569def get_start_end_duration_with_validation(
1570 component: Component,
1571 start_property: str,
1572 end_property: str,
1573 component_name: str,
1574) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1575 """
1576 Validate the component and return start, end, and duration.
1578 This tests validity according to :rfc:`5545` rules
1579 for ``Event`` and ``Todo`` components.
1581 Args:
1582 component: The component to validate, either ``Event`` or ``Todo``.
1583 start_property: The start property name, ``DTSTART``.
1584 end_property: The end property name, either ``DTEND`` for ``Event`` or
1585 ``DUE`` for ``Todo``.
1586 component_name: The component name for error messages,
1587 either ``VEVENT`` or ``VTODO``.
1589 Returns:
1590 tuple: (start, end, duration) values from the component.
1592 Raises:
1593 InvalidCalendar: If the component violates RFC 5545 constraints.
1595 """
1596 start = getattr(component, start_property, None)
1597 end = getattr(component, end_property, None)
1598 duration = component.DURATION
1600 # RFC 5545: Only one of end property and DURATION may be present
1601 if duration is not None and end is not None:
1602 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1603 msg = (
1604 f"Only one of {end_name} and DURATION "
1605 f"may be in a {component_name}, not both."
1606 )
1607 raise InvalidCalendar(msg)
1609 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1610 if (
1611 start is not None
1612 and is_date(start)
1613 and duration is not None
1614 and duration.seconds != 0
1615 ):
1616 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1617 raise InvalidCalendar(msg)
1619 # RFC 5545: DTSTART and end property must be of the same type
1620 if start is not None and end is not None and is_date(start) != is_date(end):
1621 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1622 msg = (
1623 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1624 )
1625 raise InvalidCalendar(msg)
1627 return start, end, duration
1630def get_start_property(component: Component) -> date | datetime:
1631 """
1632 Get the start property with validation.
1634 Args:
1635 component: The component from which to get its start property.
1637 Returns:
1638 The ``DTSTART`` value.
1640 Raises:
1641 IncompleteComponent: If no ``DTSTART`` is present.
1643 """
1644 # Trigger validation by calling _get_start_end_duration
1645 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1646 if start is None:
1647 msg = "No DTSTART given."
1648 raise IncompleteComponent(msg)
1649 return start
1652def get_end_property(component: Component, end_property: str) -> date | datetime:
1653 """
1654 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1656 Args:
1657 component: The component to get end from
1658 end_property: The end property name, either ``DTEND`` for ``Event`` or
1659 ``DUE`` for ``Todo``.
1661 Returns:
1662 The computed end value.
1664 Raises:
1665 IncompleteComponent: If the provided information is incomplete
1666 to compute the end property.
1668 """
1669 # Trigger validation by calling _get_start_end_duration
1670 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1672 if end is None and duration is None:
1673 if start is None:
1674 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1675 msg = f"No {end_name} or DURATION+DTSTART given."
1676 raise IncompleteComponent(msg)
1678 # Default behavior differs for Event vs Todo:
1679 # Event: date gets +1 day, datetime gets same time
1680 # Todo: both date and datetime get same time (issue #898)
1681 if end_property == "DTEND" and is_date(start):
1682 return start + timedelta(days=1)
1683 return start
1685 if duration is not None:
1686 if start is not None:
1687 return start + duration
1688 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1689 msg = f"No {end_name} or DURATION+DTSTART given."
1690 raise IncompleteComponent(msg)
1692 return end
1695def get_duration_property(component: Component) -> timedelta:
1696 """
1697 Get the duration property with fallback calculation from start and end.
1699 Args:
1700 component: The component from which to get its duration property.
1702 Returns:
1703 The duration as a timedelta.
1705 """
1706 # First check if DURATION property is explicitly set
1707 if "DURATION" in component:
1708 return component["DURATION"].dt
1710 # Fall back to calculated duration from start and end
1711 return component.end - component.start
1714def set_duration_with_locking(
1715 component: Component,
1716 duration: timedelta | None,
1717 locked: Literal["start", "end"],
1718 end_property: str,
1719) -> None:
1720 """
1721 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1723 Args:
1724 component: The component to modify, either ``Event`` or ``Todo``.
1725 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1726 locked: Which property to keep unchanged, either ``start`` or ``end``.
1727 end_property: The end property name, either ``DTEND`` for ``Event`` or
1728 ``DUE`` for ``Todo``.
1730 """
1731 # Convert to DURATION property if duration is None
1732 if duration is None:
1733 if "DURATION" in component:
1734 return # Already has DURATION property
1735 current_duration = component.duration
1736 component.DURATION = current_duration
1737 return
1739 if not isinstance(duration, timedelta):
1740 msg = f"Use timedelta, not {type(duration).__name__}."
1741 raise TypeError(msg)
1743 # Validate date/duration compatibility
1744 start = component.DTSTART
1745 if start is not None and is_date(start) and duration.seconds != 0:
1746 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1747 raise InvalidCalendar(msg)
1749 if locked == "start":
1750 # Keep start locked, adjust end
1751 if start is None:
1752 msg = "Cannot set duration without DTSTART. Set start time first."
1753 raise IncompleteComponent(msg)
1754 component.pop(end_property, None) # Remove end property
1755 component.DURATION = duration
1756 elif locked == "end":
1757 # Keep end locked, adjust start
1758 current_end = component.end
1759 component.DTSTART = current_end - duration
1760 component.pop(end_property, None) # Remove end property
1761 component.DURATION = duration
1762 else:
1763 msg = f"locked must be 'start' or 'end', not {locked!r}"
1764 raise ValueError(msg)
1767def set_start_with_locking(
1768 component: Component,
1769 start: date | datetime,
1770 locked: Literal["duration", "end"] | None,
1771 end_property: str,
1772) -> None:
1773 """
1774 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1776 Args:
1777 component: The component to modify, either ``Event`` or ``Todo``.
1778 start: The start time to set.
1779 locked: Which property to keep unchanged, either ``duration``, ``end``,
1780 or ``None`` for auto-detect.
1781 end_property: The end property name, either ``DTEND`` for ``Event`` or
1782 ``DUE`` for ``Todo``.
1784 """
1785 if locked is None:
1786 # Auto-detect based on existing properties
1787 if "DURATION" in component:
1788 locked = "duration"
1789 elif end_property in component:
1790 locked = "end"
1791 else:
1792 # Default to duration if no existing properties
1793 locked = "duration"
1795 if locked == "duration":
1796 # Keep duration locked, adjust end
1797 current_duration = (
1798 component.duration
1799 if "DURATION" in component or end_property in component
1800 else None
1801 )
1802 component.DTSTART = start
1803 if current_duration is not None:
1804 component.pop(end_property, None) # Remove end property
1805 component.DURATION = current_duration
1806 elif locked == "end":
1807 # Keep end locked, adjust duration
1808 current_end = component.end
1809 component.DTSTART = start
1810 component.pop("DURATION", None) # Remove duration property
1811 setattr(component, end_property, current_end)
1812 else:
1813 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1814 raise ValueError(msg)
1817def set_end_with_locking(
1818 component: Component,
1819 end: date | datetime,
1820 locked: Literal["start", "duration"],
1821 end_property: str,
1822) -> None:
1823 """
1824 Set the end with explicit locking behavior for Event and Todo components.
1826 Args:
1827 component: The component to modify, either ``Event`` or ``Todo``.
1828 end: The end time to set.
1829 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1830 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1831 for ``Todo``.
1833 """
1834 if locked == "start":
1835 # Keep start locked, adjust duration
1836 component.pop("DURATION", None) # Remove duration property
1837 setattr(component, end_property, end)
1838 elif locked == "duration":
1839 # Keep duration locked, adjust start
1840 current_duration = component.duration
1841 component.DTSTART = end - current_duration
1842 component.pop(end_property, None) # Remove end property
1843 component.DURATION = current_duration
1844 else:
1845 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1846 raise ValueError(msg)
1849def _get_images(self: Component) -> list[Image]:
1850 """IMAGE specifies an image associated with the calendar or a calendar component.
1852 Description:
1853 This property specifies an image for an iCalendar
1854 object or a calendar component via a URI or directly with inline
1855 data that can be used by calendar user agents when presenting the
1856 calendar data to a user. Multiple properties MAY be used to
1857 specify alternative sets of images with, for example, varying
1858 media subtypes, resolutions, or sizes. When multiple properties
1859 are present, calendar user agents SHOULD display only one of them,
1860 picking one that provides the most appropriate image quality, or
1861 display none. The "DISPLAY" parameter is used to indicate the
1862 intended display mode for the image. The "ALTREP" parameter,
1863 defined in :rfc:`5545`, can be used to provide a "clickable" image
1864 where the URI in the parameter value can be "launched" by a click
1865 on the image in the calendar user agent.
1867 Conformance:
1868 This property can be specified multiple times in an
1869 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1870 components.
1872 .. note::
1874 At the present moment, this property is read-only. If you require a setter,
1875 please open an issue or a pull request.
1876 """
1877 images = self.get("IMAGE", [])
1878 if not isinstance(images, SEQUENCE_TYPES):
1879 images = [images]
1880 return [Image.from_property_value(img) for img in images]
1883images_property = property(_get_images)
1886def _get_conferences(self: Component) -> list[Conference]:
1887 """Return the CONFERENCE properties as a list.
1889 Purpose:
1890 This property specifies information for accessing a conferencing system.
1892 Conformance:
1893 This property can be specified multiple times in a
1894 "VEVENT" or "VTODO" calendar component.
1896 Description:
1897 This property specifies information for accessing a
1898 conferencing system for attendees of a meeting or task. This
1899 might be for a telephone-based conference number dial-in with
1900 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1901 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1902 or https: URI :rfc:`7230`), or for an instant messaging group chat
1903 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1904 conferencing system is not available, a data: URI :rfc:`2397`
1905 containing a text description can be used.
1907 A conference system can be a bidirectional communication channel
1908 or a uni-directional "broadcast feed".
1910 The "FEATURE" property parameter is used to describe the key
1911 capabilities of the conference system to allow a client to choose
1912 the ones that give the required level of interaction from a set of
1913 multiple properties.
1915 The "LABEL" property parameter is used to convey additional
1916 details on the use of the URI. For example, the URIs or access
1917 codes for the moderator and attendee of a teleconference system
1918 could be different, and the "LABEL" property parameter could be
1919 used to "tag" each "CONFERENCE" property to indicate which is
1920 which.
1922 The "LANGUAGE" property parameter can be used to specify the
1923 language used for text values used with this property (as per
1924 Section 3.2.10 of :rfc:`5545`).
1926 Example:
1927 The following are examples of this property:
1929 .. code-block:: text
1931 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1932 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1933 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1934 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1935 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1936 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1937 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1938 LABEL=Chat room:xmpp:chat-123@conference.example.com
1939 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1940 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1942 Get all conferences:
1944 .. code-block:: pycon
1946 >>> from icalendar import Event
1947 >>> event = Event()
1948 >>> event.conferences
1949 []
1951 Set a conference:
1953 .. code-block:: pycon
1955 >>> from icalendar import Event, Conference
1956 >>> event = Event()
1957 >>> event.conferences = [
1958 ... Conference(
1959 ... "tel:+1-412-555-0123,,,654321",
1960 ... feature="PHONE,MODERATOR",
1961 ... label="Moderator dial-in",
1962 ... language="EN",
1963 ... )
1964 ... ]
1965 >>> print(event.to_ical())
1966 BEGIN:VEVENT
1967 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
1968 ALUE=URI:tel:+1-412-555-0123,,,654321
1969 END:VEVENT
1971 """
1972 conferences = self.get("CONFERENCE", [])
1973 if not isinstance(conferences, SEQUENCE_TYPES):
1974 conferences = [conferences]
1975 return [Conference.from_uri(conference) for conference in conferences]
1978def _set_conferences(self: Component, conferences: list[Conference] | None):
1979 """Set the conferences."""
1980 _del_conferences(self)
1981 for conference in conferences or []:
1982 self.add("CONFERENCE", conference.to_uri())
1985def _del_conferences(self: Component):
1986 """Delete all conferences."""
1987 self.pop("CONFERENCE")
1990conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
1993def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
1994 """LINK properties as a list.
1996 Purpose:
1997 LINK provides a reference to external information related to a component.
1999 Property Parameters:
2000 The VALUE parameter is required.
2001 Non-standard, link relation type, format type, label, and language parameters
2002 can also be specified on this property.
2003 The LABEL parameter is defined in :rfc:`7986`.
2005 Conformance:
2006 This property can be specified zero or more times in any iCalendar component.
2007 LINK is specified in :rfc:`9253`.
2008 The LINKREL parameter is required.
2010 Description:
2011 When used in a component, the value of this property points to
2012 additional information related to the component.
2013 For example, it may reference the originating web server.
2015 This property is a serialization of the model in :rfc:`8288`,
2016 where the link target is carried in the property value,
2017 the link context is the containing calendar entity,
2018 and the link relation type and any target attributes
2019 are carried in iCalendar property parameters.
2021 The LINK property parameters map to :rfc:`8288` attributes as follows:
2023 LABEL
2024 This parameter maps to the "title"
2025 attribute defined in Section 3.4.1 of :rfc:`8288`.
2026 LABEL is used to label the destination
2027 of a link such that it can be used as a human-readable identifier
2028 (e.g., a menu entry) in the language indicated by the LANGUAGE
2029 (if present).
2030 LANGUAGE
2031 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2032 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2033 LINKREL
2034 This parameter maps to the link relation type defined in Section 2.1 of
2035 :rfc:`8288`. See `Registered Link Relation Types
2036 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2037 FMTTYPE
2038 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2039 :rfc:`8288`.
2041 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2043 Examples:
2044 The following is an example of this property,
2045 which provides a reference to the source for the calendar object.
2047 .. code-block:: text
2049 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2050 https://example.com/events
2052 The following is an example of this property,
2053 which provides a reference to an entity from which this one was derived.
2054 The link relation is a vendor-defined value.
2056 .. code-block:: text
2058 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2059 VALUE=URI:
2060 https://example.com/tasks/01234567-abcd1234.ics
2062 The following is an example of this property,
2063 which provides a reference to a fragment of an XML document.
2064 The link relation is a vendor-defined value.
2066 .. code-block:: text
2068 LINK;LINKREL="https://example.com/linkrel/costStructure";
2069 VALUE=XML-REFERENCE:
2070 https://example.com/xmlDocs/bidFramework.xml
2071 #xpointer(descendant::CostStruc/range-to(
2072 following::CostStrucEND[1]))
2074 Set a link :class:`icalendar.vUri` to the event page:
2076 .. code-block:: pycon
2078 >>> from icalendar import Event, vUri
2079 >>> from datetime import datetime
2080 >>> link = vUri(
2081 ... "http://example.com/event-page",
2082 ... params={"LINKREL":"SOURCE"}
2083 ... )
2084 >>> event = Event.new(
2085 ... start=datetime(2025, 9, 17, 12, 0),
2086 ... summary="An Example Event with a page"
2087 ... )
2088 >>> event.links = [link]
2089 >>> print(event.to_ical())
2090 BEGIN:VEVENT
2091 SUMMARY:An Example Event with a page
2092 DTSTART:20250917T120000
2093 DTSTAMP:20250517T080612Z
2094 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2095 LINK;LINKREL="SOURCE":http://example.com/event-page
2096 END:VEVENT
2098 """
2099 links = self.get("LINK", [])
2100 if not isinstance(links, list):
2101 links = [links]
2102 return links
2105LINKS_TYPE_SETTER: TypeAlias = Union[
2106 str, vUri, vUid, vXmlReference, None, List[Union[str, vUri, vUid, vXmlReference]]
2107]
2110def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2111 """Set the LINKs."""
2112 _del_links(self)
2113 if links is None:
2114 return
2115 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2116 links = [links]
2117 for link in links:
2118 if type(link) is str:
2119 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2120 self.add("LINK", link)
2123def _del_links(self: Component) -> None:
2124 """Delete all links."""
2125 self.pop("LINK")
2128links_property = property(_get_links, _set_links, _del_links)
2130RELATED_TO_TYPE_SETTER: TypeAlias = Union[
2131 None, str, vText, vUri, vUid, List[Union[str, vText, vUri, vUid]]
2132]
2135def _get_related_to(self: Component) -> list[Union[vText, vUri, vUid]]:
2136 """RELATED-TO properties as a list.
2138 Purpose:
2139 This property is used to represent a relationship or reference
2140 between one calendar component and another.
2141 :rfc:`9523` allows URI or UID values and a GAP parameter.
2143 Value Type:
2144 :rfc:`5545`: TEXT
2145 :rfc:`9253`: URI, UID
2147 Conformance:
2148 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2149 "VTODO", and "VJOURNAL" calendar components.
2150 Since :rfc:`9523`, this property MAY be specified in any
2151 iCalendar component.
2153 Description (:rfc:`5545`):
2154 The property value consists of the persistent, globally
2155 unique identifier of another calendar component. This value would
2156 be represented in a calendar component by the "UID" property.
2158 By default, the property value points to another calendar
2159 component that has a PARENT relationship to the referencing
2160 object. The "RELTYPE" property parameter is used to either
2161 explicitly state the default PARENT relationship type to the
2162 referenced calendar component or to override the default PARENT
2163 relationship type and specify either a CHILD or SIBLING
2164 relationship. The PARENT relationship indicates that the calendar
2165 component is a subordinate of the referenced calendar component.
2166 The CHILD relationship indicates that the calendar component is a
2167 superior of the referenced calendar component. The SIBLING
2168 relationship indicates that the calendar component is a peer of
2169 the referenced calendar component.
2171 Changes to a calendar component referenced by this property can
2172 have an implicit impact on the related calendar component. For
2173 example, if a group event changes its start or end date or time,
2174 then the related, dependent events will need to have their start
2175 and end dates changed in a corresponding way. Similarly, if a
2176 PARENT calendar component is cancelled or deleted, then there is
2177 an implied impact to the related CHILD calendar components. This
2178 property is intended only to provide information on the
2179 relationship of calendar components. It is up to the target
2180 calendar system to maintain any property implications of this
2181 relationship.
2183 Description (:rfc:`9253`):
2184 By default or when VALUE=UID is specified, the property value
2185 consists of the persistent, globally unique identifier of another
2186 calendar component. This value would be represented in a calendar
2187 component by the UID property.
2189 By default, the property value
2190 points to another calendar component that has a PARENT relationship
2191 to the referencing object. The RELTYPE property parameter is used
2192 to either explicitly state the default PARENT relationship type to
2193 the referenced calendar component or to override the default
2194 PARENT relationship type and specify either a CHILD or SIBLING
2195 relationship or a temporal relationship.
2197 The PARENT relationship
2198 indicates that the calendar component is a subordinate of the
2199 referenced calendar component. The CHILD relationship indicates
2200 that the calendar component is a superior of the referenced calendar
2201 component. The SIBLING relationship indicates that the calendar
2202 component is a peer of the referenced calendar component.
2204 To preserve backwards compatibility, the value type MUST
2205 be UID when the PARENT, SIBLING, or CHILD relationships
2206 are specified.
2208 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2209 or STARTTOSTART relationships define temporal relationships, as
2210 specified in the RELTYPE parameter definition.
2212 The FIRST and NEXT
2213 define ordering relationships between calendar components.
2215 The DEPENDS-ON relationship indicates that the current calendar
2216 component depends on the referenced calendar component in some manner.
2217 For example, a task may be blocked waiting on the other,
2218 referenced, task.
2220 The REFID and CONCEPT relationships establish
2221 a reference from the current component to the referenced component.
2222 Changes to a calendar component referenced by this property
2223 can have an implicit impact on the related calendar component.
2224 For example, if a group event changes its start or end date or
2225 time, then the related, dependent events will need to have their
2226 start and end dates and times changed in a corresponding way.
2227 Similarly, if a PARENT calendar component is canceled or deleted,
2228 then there is an implied impact to the related CHILD calendar
2229 components. This property is intended only to provide information
2230 on the relationship of calendar components.
2232 Deletion of the target component, for example, the target of a
2233 FIRST, NEXT, or temporal relationship, can result in broken links.
2235 It is up to the target calendar system to maintain any property
2236 implications of these relationships.
2238 Examples:
2239 :rfc:`5545` examples of this property:
2241 .. code-block:: text
2243 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2245 .. code-block:: text
2247 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2249 :rfc:`9253` examples of this property:
2251 .. code-block:: text
2253 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2254 https://example.com/caldav/user/jb/cal/
2255 19960401-080045-4000F192713.ics
2257 See also :class:`icalendar.enum.RELTYPE`.
2259 """
2260 result = self.get("RELATED-TO", [])
2261 if not isinstance(result, list):
2262 return [result]
2263 return result
2266def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2267 """Set the RELATED-TO properties."""
2268 _del_related_to(self)
2269 if values is None:
2270 return
2271 if not isinstance(values, list):
2272 values = [values]
2273 for value in values:
2274 self.add("RELATED-TO", value)
2277def _del_related_to(self: Component):
2278 """Delete the RELATED-TO properties."""
2279 self.pop("RELATED-TO", None)
2282related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2285def _get_concepts(self: Component) -> list[vUri]:
2286 """CONCEPT
2288 Purpose:
2289 CONCEPT defines the formal categories for a calendar component.
2291 Conformance:
2292 Since :rfc:`9253`,
2293 this property can be specified zero or more times in any iCalendar component.
2295 Description:
2296 This property is used to specify formal categories or classifications of
2297 the calendar component. The values are useful in searching for a calendar
2298 component of a particular type and category.
2300 This categorization is distinct from the more informal "tagging" of components
2301 provided by the existing CATEGORIES property. It is expected that the value of
2302 the CONCEPT property will reference an external resource that provides
2303 information about the categorization.
2305 In addition, a structured URI value allows for hierarchical categorization of
2306 events.
2308 Possible category resources are the various proprietary systems, for example,
2309 the Library of Congress, or an open source of categorization data.
2311 Examples:
2312 The following is an example of this property.
2313 It points to a server acting as the source for the calendar object.
2315 .. code-block:: text
2317 CONCEPT:https://example.com/event-types/arts/music
2319 .. seealso::
2321 :attr:`Component.categories`
2322 """
2323 concepts = self.get("CONCEPT", [])
2324 if not isinstance(concepts, list):
2325 concepts = [concepts]
2326 return concepts
2329CONCEPTS_TYPE_SETTER: TypeAlias = Union[List[Union[vUri, str]], str, vUri, None]
2332def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2333 """Set the concepts."""
2334 _del_concepts(self)
2335 if concepts is None:
2336 return
2337 if not isinstance(concepts, list):
2338 concepts = [concepts]
2339 for value in concepts:
2340 self.add("CONCEPT", value)
2343def _del_concepts(self: Component):
2344 """Delete the concepts."""
2345 self.pop("CONCEPT", None)
2348concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2351def multi_string_property(name: str, doc: str):
2352 """A property for an iCalendar Property that can occur multiple times."""
2354 def fget(self: Component) -> list[str]:
2355 """Get the values of a multi-string property."""
2356 value = self.get(name, [])
2357 if not isinstance(value, list):
2358 value = [value]
2359 return value
2361 def fset(self: Component, value: list[str] | str | None) -> None:
2362 """Set the values of a multi-string property."""
2363 fdel(self)
2364 if value is None:
2365 return
2366 if not isinstance(value, list):
2367 value = [value]
2368 for value in value:
2369 self.add(name, value)
2371 def fdel(self: Component):
2372 """Delete the values of a multi-string property."""
2373 self.pop(name, None)
2375 return property(fget, fset, fdel, doc=doc)
2378refids_property = multi_string_property(
2379 "REFID",
2380 """REFID
2382Purpose:
2383 REFID acts as a key for associated iCalendar entities.
2385Conformance:
2386 Since :rfc:`9253`,
2387 this property can be specified zero or more times in any iCalendar component.
2389Description:
2390 The value of this property is free-form text that creates an
2391 identifier for associated components.
2392 All components that use the same REFID value are associated through
2393 that value and can be located or retrieved as a group.
2394 For example, all of the events in a travel itinerary
2395 would have the same REFID value, so as to be grouped together.
2397Examples:
2398 The following is an example of this property.
2400 .. code-block:: text
2402 REFID:itinerary-2014-11-17
2404 Use a REFID to associate several VTODOs:
2406 .. code-block:: pycon
2408 >>> from icalendar import Todo
2409 >>> todo_1 = Todo.new(
2410 ... summary="turn off stove",
2411 ... refids=["travel", "alps"]
2412 ... )
2413 >>> todo_2 = Todo.new(
2414 ... summary="pack backpack",
2415 ... refids=["travel", "alps"]
2416 ... )
2417 >>> todo_1.refids == todo_2.refids
2418 True
2420.. note::
2422 List modifications do not modify the component.
2423""",
2424)
2427__all__ = [
2428 "CONCEPTS_TYPE_SETTER",
2429 "LINKS_TYPE_SETTER",
2430 "RELATED_TO_TYPE_SETTER",
2431 "attendees_property",
2432 "busy_type_property",
2433 "categories_property",
2434 "class_property",
2435 "color_property",
2436 "comments_property",
2437 "concepts_property",
2438 "conferences_property",
2439 "contacts_property",
2440 "create_single_property",
2441 "description_property",
2442 "descriptions_property",
2443 "duration_property",
2444 "exdates_property",
2445 "get_duration_property",
2446 "get_end_property",
2447 "get_start_end_duration_with_validation",
2448 "get_start_property",
2449 "images_property",
2450 "links_property",
2451 "location_property",
2452 "multi_language_text_property",
2453 "multi_string_property",
2454 "organizer_property",
2455 "priority_property",
2456 "property_del_duration",
2457 "property_doc_duration_template",
2458 "property_get_duration",
2459 "property_set_duration",
2460 "rdates_property",
2461 "refids_property",
2462 "related_to_property",
2463 "rfc_7953_dtend_property",
2464 "rfc_7953_dtstart_property",
2465 "rfc_7953_duration_property",
2466 "rfc_7953_end_property",
2467 "rrules_property",
2468 "sequence_property",
2469 "set_duration_with_locking",
2470 "set_end_with_locking",
2471 "set_start_with_locking",
2472 "single_int_property",
2473 "single_utc_property",
2474 "source_property",
2475 "status_property",
2476 "summary_property",
2477 "transparency_property",
2478 "uid_property",
2479 "url_property",
2480]