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, TypeAlias
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 collections.abc import Sequence
31 from icalendar.cal import Component
34def _get_rdates(
35 self: Component,
36) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]:
37 """The RDATE property defines the list of DATE-TIME values for recurring components.
39 RDATE is defined in :rfc:`5545`.
40 The return value is a list of tuples ``(start, end)``.
42 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
43 with and without timezone.
45 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
46 if the end is specified.
48 Value Type:
49 The default value type for this property is DATE-TIME.
50 The value type can be set to DATE or PERIOD.
52 Property Parameters:
53 IANA, non-standard, value data type, and time
54 zone identifier property parameters can be specified on this
55 property.
57 Conformance:
58 This property can be specified in recurring "VEVENT",
59 "VTODO", and "VJOURNAL" calendar components as well as in the
60 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
61 calendar component.
63 Description:
64 This property can appear along with the "RRULE"
65 property to define an aggregate set of repeating occurrences.
66 When they both appear in a recurring component, the recurrence
67 instances are defined by the union of occurrences defined by both
68 the "RDATE" and "RRULE".
70 The recurrence dates, if specified, are used in computing the
71 recurrence set. The recurrence set is the complete set of
72 recurrence instances for a calendar component. The recurrence set
73 is generated by considering the initial "DTSTART" property along
74 with the "RRULE", "RDATE", and "EXDATE" properties contained
75 within the recurring component. The "DTSTART" property defines
76 the first instance in the recurrence set. The "DTSTART" property
77 value SHOULD match the pattern of the recurrence rule, if
78 specified. The recurrence set generated with a "DTSTART" property
79 value that doesn't match the pattern of the rule is undefined.
80 The final recurrence set is generated by gathering all of the
81 start DATE-TIME values generated by any of the specified "RRULE"
82 and "RDATE" properties, and then excluding any start DATE-TIME
83 values specified by "EXDATE" properties. This implies that start
84 DATE-TIME values specified by "EXDATE" properties take precedence
85 over those specified by inclusion properties (i.e., "RDATE" and
86 "RRULE"). Where duplicate instances are generated by the "RRULE"
87 and "RDATE" properties, only one recurrence is considered.
88 Duplicate instances are ignored.
90 Example:
91 Below, we set one RDATE in a list and get the resulting tuple of start and end.
93 .. code-block:: pycon
95 >>> from icalendar import Event
96 >>> from datetime import datetime
97 >>> event = Event()
99 # Add a list of recurrence dates
100 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
101 >>> event.rdates
102 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
104 .. note::
106 You cannot modify the RDATE value by modifying the result.
107 Use :func:`icalendar.cal.Component.add` to add values.
109 If you want to compute recurrences, have a look at
110 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
112 """
113 result = []
114 rdates = self.get("RDATE", [])
115 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
116 for dts in rdates.dts:
117 rdate = dts.dt
118 if isinstance(rdate, tuple):
119 # we have a period as rdate
120 if isinstance(rdate[1], timedelta):
121 result.append((rdate[0], rdate[0] + rdate[1]))
122 else:
123 result.append(rdate)
124 else:
125 # we have a date/datetime
126 result.append((rdate, None))
127 return result
130rdates_property = property(_get_rdates)
133def _get_exdates(self: Component) -> list[date | datetime]:
134 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
136 EXDATE is defined in :rfc:`5545`.
138 Value Type:
139 The default value type for this property is DATE-TIME.
140 The value type can be set to DATE.
142 Property Parameters:
143 IANA, non-standard, value data type, and time
144 zone identifier property parameters can be specified on this
145 property.
147 Conformance:
148 This property can be specified in recurring "VEVENT",
149 "VTODO", and "VJOURNAL" calendar components as well as in the
150 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
151 calendar component.
153 Description:
154 The exception dates, if specified, are used in
155 computing the recurrence set. The recurrence set is the complete
156 set of recurrence instances for a calendar component. The
157 recurrence set is generated by considering the initial "DTSTART"
158 property along with the "RRULE", "RDATE", and "EXDATE" properties
159 contained within the recurring component. The "DTSTART" property
160 defines the first instance in the recurrence set. The "DTSTART"
161 property value SHOULD match the pattern of the recurrence rule, if
162 specified. The recurrence set generated with a "DTSTART" property
163 value that doesn't match the pattern of the rule is undefined.
164 The final recurrence set is generated by gathering all of the
165 start DATE-TIME values generated by any of the specified "RRULE"
166 and "RDATE" properties, and then excluding any start DATE-TIME
167 values specified by "EXDATE" properties. This implies that start
168 DATE-TIME values specified by "EXDATE" properties take precedence
169 over those specified by inclusion properties (i.e., "RDATE" and
170 "RRULE"). When duplicate instances are generated by the "RRULE"
171 and "RDATE" properties, only one recurrence is considered.
172 Duplicate instances are ignored.
174 The "EXDATE" property can be used to exclude the value specified
175 in "DTSTART". However, in such cases, the original "DTSTART" date
176 MUST still be maintained by the calendaring and scheduling system
177 because the original "DTSTART" value has inherent usage
178 dependencies by other properties such as the "RECURRENCE-ID".
180 Example:
181 Below, we add an exdate in a list and get the resulting list of exdates.
183 .. code-block:: pycon
185 >>> from icalendar import Event
186 >>> from datetime import datetime
187 >>> event = Event()
189 # Add a list of excluded dates
190 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
191 >>> event.exdates
192 [datetime.datetime(2025, 4, 28, 16, 5)]
194 .. note::
196 You cannot modify the EXDATE value by modifying the result.
197 Use :func:`icalendar.cal.Component.add` to add values.
199 If you want to compute recurrences, have a look at
200 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
202 """
203 result = []
204 exdates = self.get("EXDATE", [])
205 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
206 for dts in exdates.dts:
207 exdate = dts.dt
208 # we have a date/datetime
209 result.append(exdate)
210 return result
213exdates_property = property(_get_exdates)
216def _get_rrules(self: Component) -> list[vRecur]:
217 """RRULE defines a rule or repeating pattern for recurring components.
219 RRULE is defined in :rfc:`5545`.
220 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
222 Property Parameters:
223 IANA and non-standard property parameters can
224 be specified on this property.
226 Conformance:
227 This property can be specified in recurring "VEVENT",
228 "VTODO", and "VJOURNAL" calendar components as well as in the
229 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
230 calendar component, but it SHOULD NOT be specified more than once.
231 The recurrence set generated with multiple "RRULE" properties is
232 undefined.
234 Description:
235 The recurrence rule, if specified, is used in computing
236 the recurrence set. The recurrence set is the complete set of
237 recurrence instances for a calendar component. The recurrence set
238 is generated by considering the initial "DTSTART" property along
239 with the "RRULE", "RDATE", and "EXDATE" properties contained
240 within the recurring component. The "DTSTART" property defines
241 the first instance in the recurrence set. The "DTSTART" property
242 value SHOULD be synchronized with the recurrence rule, if
243 specified. The recurrence set generated with a "DTSTART" property
244 value not synchronized with the recurrence rule is undefined. The
245 final recurrence set is generated by gathering all of the start
246 DATE-TIME values generated by any of the specified "RRULE" and
247 "RDATE" properties, and then excluding any start DATE-TIME values
248 specified by "EXDATE" properties. This implies that start DATE-
249 TIME values specified by "EXDATE" properties take precedence over
250 those specified by inclusion properties (i.e., "RDATE" and
251 "RRULE"). Where duplicate instances are generated by the "RRULE"
252 and "RDATE" properties, only one recurrence is considered.
253 Duplicate instances are ignored.
255 The "DTSTART" property specified within the iCalendar object
256 defines the first instance of the recurrence. In most cases, a
257 "DTSTART" property of DATE-TIME value type used with a recurrence
258 rule, should be specified as a date with local time and time zone
259 reference to make sure all the recurrence instances start at the
260 same local time regardless of time zone changes.
262 If the duration of the recurring component is specified with the
263 "DTEND" or "DUE" property, then the same exact duration will apply
264 to all the members of the generated recurrence set. Else, if the
265 duration of the recurring component is specified with the
266 "DURATION" property, then the same nominal duration will apply to
267 all the members of the generated recurrence set and the exact
268 duration of each recurrence instance will depend on its specific
269 start time. For example, recurrence instances of a nominal
270 duration of one day will have an exact duration of more or less
271 than 24 hours on a day where a time zone shift occurs. The
272 duration of a specific recurrence may be modified in an exception
273 component or simply by using an "RDATE" property of PERIOD value
274 type.
276 Examples:
277 Daily for 10 occurrences:
279 .. code-block:: pycon
281 >>> from icalendar import Event
282 >>> from datetime import datetime
283 >>> from zoneinfo import ZoneInfo
284 >>> event = Event()
285 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
286 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
287 >>> print(event.to_ical())
288 BEGIN:VEVENT
289 DTSTART;TZID=America/New_York:19970902T090000
290 RRULE:FREQ=DAILY;COUNT=10
291 END:VEVENT
292 >>> event.rrules
293 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
295 Daily until December 24, 1997:
297 .. code-block:: pycon
299 >>> from icalendar import Event, vRecur
300 >>> from datetime import datetime
301 >>> from zoneinfo import ZoneInfo
302 >>> event = Event()
303 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
304 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
305 >>> print(event.to_ical())
306 BEGIN:VEVENT
307 DTSTART;TZID=America/New_York:19970902T090000
308 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
309 END:VEVENT
310 >>> event.rrules
311 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
313 .. note::
315 You cannot modify the RRULE value by modifying the result.
316 Use :func:`icalendar.cal.Component.add` to add values.
318 If you want to compute recurrences, have a look at
319 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
321 """ # noqa: E501
322 rrules = self.get("RRULE", [])
323 if not isinstance(rrules, list):
324 return [rrules]
325 return rrules
328rrules_property = property(_get_rrules)
331def multi_language_text_property(
332 main_prop: str, compatibility_prop: str | None, doc: str
333) -> property:
334 """This creates a text property.
336 This property can be defined several times with different ``LANGUAGE`` parameters.
338 Parameters:
339 main_prop (str): The property to set and get, such as ``NAME``
340 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
341 doc (str): The documentation string
342 """
344 def fget(self: Component) -> str | None:
345 """Get the property"""
346 result = self.get(main_prop)
347 if result is None and compatibility_prop is not None:
348 result = self.get(compatibility_prop)
349 if isinstance(result, list):
350 for item in result:
351 if "LANGUAGE" not in item.params:
352 return item
353 return result
355 def fset(self: Component, value: str | None):
356 """Set the property."""
357 fdel(self)
358 if value is not None:
359 self.add(main_prop, value)
361 def fdel(self: Component):
362 """Delete the property."""
363 self.pop(main_prop, None)
364 if compatibility_prop is not None:
365 self.pop(compatibility_prop, None)
367 return property(fget, fset, fdel, doc)
370def single_int_property(prop: str, default: int, doc: str) -> property:
371 """Create a property for an int value that exists only once.
373 Parameters:
374 prop: The name of the property
375 default: The default value
376 doc: The documentation string
377 """
379 def fget(self: Component) -> int:
380 """Get the property"""
381 try:
382 return int(self.get(prop, default))
383 except ValueError as e:
384 raise InvalidCalendar(f"{prop} must be an int") from e
386 def fset(self: Component, value: int | None):
387 """Set the property."""
388 fdel(self)
389 if value is not None:
390 self.add(prop, value)
392 def fdel(self: Component):
393 """Delete the property."""
394 self.pop(prop, None)
396 return property(fget, fset, fdel, doc)
399def single_utc_property(name: str, docs: str) -> property:
400 """Create a property to access a value of datetime in UTC timezone.
402 Parameters:
403 name: name of the property
404 docs: documentation string
405 """
406 docs = (
407 f"""The {name} property. datetime in UTC
409 All values will be converted to a datetime in UTC.
410 """
411 + docs
412 )
414 def fget(self: Component) -> datetime | None:
415 """Get the value."""
416 if name not in self:
417 return None
418 dt = self.get(name)
419 if isinstance(dt, vText):
420 # we might be in an attribute that is not typed
421 value = vDDDTypes.from_ical(dt)
422 else:
423 value = getattr(dt, "dt", dt)
424 if value is None or not isinstance(value, date):
425 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
426 return tzp.localize_utc(value)
428 def fset(self: Component, value: datetime | None):
429 """Set the value"""
430 if value is None:
431 fdel(self)
432 return
433 if not isinstance(value, date):
434 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
435 fdel(self)
436 self.add(name, tzp.localize_utc(value))
438 def fdel(self: Component):
439 """Delete the property."""
440 self.pop(name, None)
442 return property(fget, fset, fdel, doc=docs)
445def single_string_property(
446 name: str, docs: str, other_name: str | None = None, default: str = ""
447) -> property:
448 """Create a property to access a single string value."""
450 def fget(self: Component) -> str:
451 """Get the value."""
452 result = self.get(
453 name, None if other_name is None else self.get(other_name, None)
454 )
455 if result is None or result == []:
456 return default
457 if isinstance(result, list):
458 return result[0]
459 return result
461 def fset(self: Component, value: str | None):
462 """Set the value.
464 Setting the value to None will delete it.
465 """
466 fdel(self)
467 if value is not None:
468 self.add(name, value)
470 def fdel(self: Component):
471 """Delete the property."""
472 self.pop(name, None)
473 if other_name is not None:
474 self.pop(other_name, None)
476 return property(fget, fset, fdel, doc=docs)
479color_property = single_string_property(
480 "COLOR",
481 """This property specifies a color used for displaying the component.
483 This implements :rfc:`7986` ``COLOR`` property.
485 Property Parameters:
486 IANA and non-standard property parameters can
487 be specified on this property.
489 Conformance:
490 This property can be specified once in an iCalendar
491 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
493 Description:
494 This property specifies a color that clients MAY use
495 when presenting the relevant data to a user. Typically, this
496 would appear as the "background" color of events or tasks. The
497 value is a case-insensitive color name taken from the CSS3 set of
498 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
500 Example:
501 ``"turquoise"``, ``"#ffffff"``
503 .. code-block:: pycon
505 >>> from icalendar import Todo
506 >>> todo = Todo()
507 >>> todo.color = "green"
508 >>> print(todo.to_ical())
509 BEGIN:VTODO
510 COLOR:green
511 END:VTODO
512 """,
513)
515sequence_property = single_int_property(
516 "SEQUENCE",
517 0,
518 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
520Value Type:
521 INTEGER
523Property Parameters:
524 IANA and non-standard property parameters can be specified on this property.
526Conformance:
527 The property can be specified in "VEVENT", "VTODO", or
528 "VJOURNAL" calendar component.
530Description:
531 When a calendar component is created, its sequence
532 number is 0. It is monotonically incremented by the "Organizer's"
533 CUA each time the "Organizer" makes a significant revision to the
534 calendar component.
536 The "Organizer" includes this property in an iCalendar object that
537 it sends to an "Attendee" to specify the current version of the
538 calendar component.
540 The "Attendee" includes this property in an iCalendar object that
541 it sends to the "Organizer" to specify the version of the calendar
542 component to which the "Attendee" is referring.
544 A change to the sequence number is not the mechanism that an
545 "Organizer" uses to request a response from the "Attendees". The
546 "RSVP" parameter on the "ATTENDEE" property is used by the
547 "Organizer" to indicate that a response from the "Attendees" is
548 requested.
550 Recurrence instances of a recurring component MAY have different
551 sequence numbers.
553Examples:
554 The following is an example of this property for a calendar
555 component that was just created by the "Organizer":
557 .. code-block:: pycon
559 >>> from icalendar import Event
560 >>> event = Event()
561 >>> event.sequence
562 0
564 The following is an example of this property for a calendar
565 component that has been revised 10 different times by the
566 "Organizer":
568 .. code-block:: pycon
570 >>> from icalendar import Calendar
571 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
572 >>> event = calendar.events[0]
573 >>> event.sequence
574 10
575 """, # noqa: E501
576)
579def _get_categories(component: Component) -> list[str]:
580 """Get all the categories."""
581 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES")
582 if isinstance(categories, list):
583 _set_categories(
584 component,
585 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
586 )
587 return _get_categories(component)
588 if categories is None:
589 categories = vCategory([])
590 component.add("CATEGORIES", categories)
591 return categories.cats
594def _set_categories(component: Component, cats: Sequence[str] | None) -> None:
595 """Set the categories."""
596 if not cats and cats != []:
597 _del_categories(component)
598 return
599 component["CATEGORIES"] = categories = vCategory(cats)
600 if isinstance(cats, list):
601 cats.clear()
602 cats.extend(categories.cats)
603 categories.cats = cats
606def _del_categories(component: Component) -> None:
607 """Delete the categories."""
608 component.pop("CATEGORIES", None)
611categories_property = property(
612 _get_categories,
613 _set_categories,
614 _del_categories,
615 """This property defines the categories for a component.
617Property Parameters:
618 IANA, non-standard, and language property parameters can be specified on this
619 property.
621Conformance:
622 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
623 components.
624 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
626Description:
627 This property is used to specify categories or subtypes
628 of the calendar component. The categories are useful in searching
629 for a calendar component of a particular type and category.
630 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
631 more than one category can be specified as a COMMA-separated list
632 of categories.
634Example:
635 Below, we add the categories to an event:
637 .. code-block:: pycon
639 >>> from icalendar import Event
640 >>> event = Event()
641 >>> event.categories = ["Work", "Meeting"]
642 >>> print(event.to_ical())
643 BEGIN:VEVENT
644 CATEGORIES:Work,Meeting
645 END:VEVENT
646 >>> event.categories.append("Lecture")
647 >>> event.categories == ["Work", "Meeting", "Lecture"]
648 True
650.. note::
652 At present, we do not take the LANGUAGE parameter into account.
654.. seealso::
656 :attr:`Component.concepts`
657""",
658)
661def _get_attendees(self: Component) -> list[vCalAddress]:
662 """Get attendees."""
663 value = self.get("ATTENDEE")
664 if value is None:
665 value = []
666 self["ATTENDEE"] = value
667 return value
668 if isinstance(value, vCalAddress):
669 return [value]
670 return value
673def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
674 """Set attendees."""
675 _del_attendees(self)
676 if value is None:
677 return
678 if not isinstance(value, list):
679 value = [value]
680 self["ATTENDEE"] = value
683def _del_attendees(self: Component):
684 """Delete all attendees."""
685 self.pop("ATTENDEE", None)
688attendees_property = property(
689 _get_attendees,
690 _set_attendees,
691 _del_attendees,
692 """ATTENDEE defines one or more "Attendees" within a calendar component.
694Conformance:
695 This property MUST be specified in an iCalendar object
696 that specifies a group-scheduled calendar entity. This property
697 MUST NOT be specified in an iCalendar object when publishing the
698 calendar information (e.g., NOT in an iCalendar object that
699 specifies the publication of a calendar user's busy time, event,
700 to-do, or journal). This property is not specified in an
701 iCalendar object that specifies only a time zone definition or
702 that defines calendar components that are not group-scheduled
703 components, but are components only on a single user's calendar.
705Description:
706 This property MUST only be specified within calendar
707 components to specify participants, non-participants, and the
708 chair of a group-scheduled calendar entity. The property is
709 specified within an "EMAIL" category of the "VALARM" calendar
710 component to specify an email address that is to receive the email
711 type of iCalendar alarm.
713Examples:
714 Add a new attendee to an existing event.
716 .. code-block:: pycon
718 >>> from icalendar import Event, vCalAddress
719 >>> event = Event()
720 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
721 >>> print(event.to_ical())
722 BEGIN:VEVENT
723 ATTENDEE:mailto:me@my-domain.com
724 END:VEVENT
726 Create an email alarm with several attendees:
728 >>> from icalendar import Alarm, vCalAddress
729 >>> alarm = Alarm.new(attendees = [
730 ... vCalAddress("mailto:me@my-domain.com"),
731 ... vCalAddress("mailto:you@my-domain.com"),
732 ... ], summary = "Email alarm")
733 >>> print(alarm.to_ical())
734 BEGIN:VALARM
735 ATTENDEE:mailto:me@my-domain.com
736 ATTENDEE:mailto:you@my-domain.com
737 SUMMARY:Email alarm
738 END:VALARM
739""",
740)
742uid_property = single_string_property(
743 "UID",
744 """UID specifies the persistent, globally unique identifier for a component.
746We recommend using :func:`uuid.uuid4` to generate new values.
748Returns:
749 The value of the UID property as a string or ``""`` if no value is set.
751Description:
752 The "UID" itself MUST be a globally unique identifier.
753 The generator of the identifier MUST guarantee that the identifier
754 is unique.
756 This is the method for correlating scheduling messages with the
757 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
758 The full range of calendar components specified by a recurrence
759 set is referenced by referring to just the "UID" property value
760 corresponding to the calendar component. The "RECURRENCE-ID"
761 property allows the reference to an individual instance within the
762 recurrence set.
764 This property is an important method for group-scheduling
765 applications to match requests with later replies, modifications,
766 or deletion requests. Calendaring and scheduling applications
767 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
768 calendar components to assure interoperability with other group-
769 scheduling applications. This identifier is created by the
770 calendar system that generates an iCalendar object.
772 Implementations MUST be able to receive and persist values of at
773 least 255 octets for this property, but they MUST NOT truncate
774 values in the middle of a UTF-8 multi-octet sequence.
776 :rfc:`7986` states that UID can be used, for
777 example, to identify duplicate calendar streams that a client may
778 have been given access to. It can be used in conjunction with the
779 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
780 to identify the most recent version of a calendar.
782Conformance:
783 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
784 and "VJOURNAL" calendar components.
785 :rfc:`7986` modifies the definition of the "UID" property to
786 allow it to be defined in an iCalendar object.
787 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
788 identifier to be specified. The value of this property can then be used
789 to refer uniquely to the "VALARM" component.
791 This property can be specified once only.
793Security:
794 :rfc:`7986` states that UID values MUST NOT include any data that
795 might identify a user, host, domain, or any other security- or
796 privacy-sensitive information. It is RECOMMENDED that calendar user
797 agents now generate "UID" values that are hex-encoded random
798 Universally Unique Identifier (UUID) values as defined in
799 Sections 4.4 and 4.5 of :rfc:`4122`.
800 You can use the :mod:`uuid` module to generate new UUIDs.
802Compatibility:
803 For Alarms, ``X-ALARMUID`` is also considered.
805Examples:
806 The following is an example of such a property value:
807 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
809 Set the UID of a calendar:
811 .. code-block:: pycon
813 >>> from icalendar import Calendar
814 >>> from uuid import uuid4
815 >>> calendar = Calendar()
816 >>> calendar.uid = uuid4()
817 >>> print(calendar.to_ical())
818 BEGIN:VCALENDAR
819 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
820 END:VCALENDAR
822""",
823)
825summary_property = multi_language_text_property(
826 "SUMMARY",
827 None,
828 """SUMMARY defines a short summary or subject for the calendar component.
830Property Parameters:
831 IANA, non-standard, alternate text
832 representation, and language property parameters can be specified
833 on this property.
835Conformance:
836 The property can be specified in "VEVENT", "VTODO",
837 "VJOURNAL", or "VALARM" calendar components.
839Description:
840 This property is used in the "VEVENT", "VTODO", and
841 "VJOURNAL" calendar components to capture a short, one-line
842 summary about the activity or journal entry.
844 This property is used in the "VALARM" calendar component to
845 capture the subject of an EMAIL category of alarm.
847Examples:
848 The following is an example of this property:
850 .. code-block:: pycon
852 SUMMARY:Department Party
853""",
854)
856description_property = multi_language_text_property(
857 "DESCRIPTION",
858 None,
859 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
861Property Parameters:
862 IANA, non-standard, alternate text
863 representation, and language property parameters can be specified
864 on this property.
866Conformance:
867 The property can be specified in the "VEVENT", "VTODO",
868 "VJOURNAL", or "VALARM" calendar components. The property can be
869 specified multiple times only within a "VJOURNAL" calendar
870 component.
872Description:
873 This property is used in the "VEVENT" and "VTODO" to
874 capture lengthy textual descriptions associated with the activity.
876 This property is used in the "VALARM" calendar component to
877 capture the display text for a DISPLAY category of alarm, and to
878 capture the body text for an EMAIL category of alarm.
880Examples:
881 The following is an example of this property with formatted
882 line breaks in the property value:
884 .. code-block:: pycon
886 DESCRIPTION:Meeting to provide technical review for "Phoenix"
887 design.\\nHappy Face Conference Room. Phoenix design team
888 MUST attend this meeting.\\nRSVP to team leader.
890 """, # noqa: E501
891)
894def create_single_property(
895 prop: str,
896 value_attr: str | None,
897 value_type: tuple[type],
898 type_def: type,
899 doc: str,
900 vProp: type = vDDDTypes, # noqa: N803
901):
902 """Create a single property getter and setter.
904 :param prop: The name of the property.
905 :param value_attr: The name of the attribute to get the value from.
906 :param value_type: The type of the value.
907 :param type_def: The type of the property.
908 :param doc: The docstring of the property.
909 :param vProp: The type of the property from :mod:`icalendar.prop`.
910 """
912 def p_get(self: Component):
913 default = object()
914 result = self.get(prop, default)
915 if result is default:
916 return None
917 if isinstance(result, list):
918 raise InvalidCalendar(f"Multiple {prop} defined.")
919 value = result if value_attr is None else getattr(result, value_attr, result)
920 if not isinstance(value, value_type):
921 raise InvalidCalendar(
922 f"{prop} must be either a "
923 f"{' or '.join(t.__name__ for t in value_type)},"
924 f" not {value}."
925 )
926 return value
928 def p_set(self: Component, value) -> None:
929 if value is None:
930 p_del(self)
931 return
932 if not isinstance(value, value_type):
933 raise TypeError(
934 f"Use {' or '.join(t.__name__ for t in value_type)}, "
935 f"not {type(value).__name__}."
936 )
937 self[prop] = vProp(value)
938 if prop in self.exclusive:
939 for other_prop in self.exclusive:
940 if other_prop != prop:
941 self.pop(other_prop, None)
943 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
945 def p_del(self: Component):
946 self.pop(prop)
948 p_doc = f"""The {prop} property.
950 {doc}
952 Accepted values: {", ".join(t.__name__ for t in value_type)}.
953 If the attribute has invalid values, we raise InvalidCalendar.
954 If the value is absent, we return None.
955 You can also delete the value with del or by setting it to None.
956 """
957 return property(p_get, p_set, p_del, p_doc)
960X_MOZ_SNOOZE_TIME_property = single_utc_property(
961 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
962)
963X_MOZ_LASTACK_property = single_utc_property(
964 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
965)
968def property_get_duration(self: Component) -> timedelta | None:
969 """Getter for property DURATION."""
970 default = object()
971 duration = self.get("duration", default)
972 if isinstance(duration, vDDDTypes):
973 return duration.dt
974 if isinstance(duration, vDuration):
975 return duration.td
976 if duration is not default and not isinstance(duration, timedelta):
977 raise InvalidCalendar(
978 f"DURATION must be a timedelta, not {type(duration).__name__}."
979 )
980 return None
983def property_set_duration(self: Component, value: timedelta | None):
984 """Setter for property DURATION."""
985 if value is None:
986 self.pop("duration", None)
987 return
988 if not isinstance(value, timedelta):
989 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
990 self["duration"] = vDuration(value)
991 self.pop("DTEND")
992 self.pop("DUE")
995def property_del_duration(self: Component):
996 """Delete property DURATION."""
997 self.pop("DURATION")
1000property_doc_duration_template = """The DURATION property.
1002The "DTSTART" property for a "{component}" specifies the inclusive
1003start of the {component}.
1004The "DURATION" property in conjunction with the DTSTART property
1005for a "{component}" calendar component specifies the non-inclusive end
1006of the event.
1008If you would like to calculate the duration of a {component}, do not use this.
1009Instead use the duration property (lower case).
1010"""
1013def duration_property(component: str) -> property:
1014 """Return the duration property."""
1015 return property(
1016 property_get_duration,
1017 property_set_duration,
1018 property_del_duration,
1019 property_doc_duration_template.format(component=component),
1020 )
1023def multi_text_property(name: str, docs: str) -> property:
1024 """Get a property that can occur several times and is text.
1026 Examples: Journal.descriptions, Event.comments
1027 """
1029 def fget(self: Component) -> list[str]:
1030 """Get the values."""
1031 descriptions = self.get(name)
1032 if descriptions is None:
1033 return []
1034 if not isinstance(descriptions, SEQUENCE_TYPES):
1035 return [descriptions]
1036 return descriptions
1038 def fset(self: Component, values: str | Sequence[str] | None):
1039 """Set the values."""
1040 fdel(self)
1041 if values is None:
1042 return
1043 if isinstance(values, str):
1044 self.add(name, values)
1045 else:
1046 for description in values:
1047 self.add(name, description)
1049 def fdel(self: Component):
1050 """Delete the values."""
1051 self.pop(name)
1053 return property(fget, fset, fdel, docs)
1056descriptions_property = multi_text_property(
1057 "DESCRIPTION",
1058 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1060Property Parameters:
1061 IANA, non-standard, alternate text
1062 representation, and language property parameters can be specified
1063 on this property.
1065Conformance:
1066 The property can be
1067 specified multiple times only within a "VJOURNAL" calendar component.
1069Description:
1070 This property is used in the "VJOURNAL" calendar component to
1071 capture one or more textual journal entries.
1073Examples:
1074 The following is an example of this property with formatted
1075 line breaks in the property value:
1077 .. code-block:: pycon
1079 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1080 design.\\nHappy Face Conference Room. Phoenix design team
1081 MUST attend this meeting.\\nRSVP to team leader.
1083""", # noqa: E501
1084)
1086comments_property = multi_text_property(
1087 "COMMENT",
1088 """COMMENT is used to specify a comment to the calendar user.
1090Purpose:
1091 This property specifies non-processing information intended
1092 to provide a comment to the calendar user.
1094Conformance:
1095 In :rfc:`5545`, this property can be specified multiple times in
1096 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1097 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1098 In :rfc:`7953`, this property can be specified multiple times in
1099 "VAVAILABILITY" and "VAVAILABLE".
1101Property Parameters:
1102 IANA, non-standard, alternate text
1103 representation, and language property parameters can be specified
1104 on this property.
1106""",
1107)
1110def _get_organizer(self: Component) -> vCalAddress | None:
1111 """ORGANIZER defines the organizer for a calendar component.
1113 Property Parameters:
1114 IANA, non-standard, language, common name,
1115 directory entry reference, and sent-by property parameters can be
1116 specified on this property.
1118 Conformance:
1119 This property MUST be specified in an iCalendar object
1120 that specifies a group-scheduled calendar entity. This property
1121 MUST be specified in an iCalendar object that specifies the
1122 publication of a calendar user's busy time. This property MUST
1123 NOT be specified in an iCalendar object that specifies only a time
1124 zone definition or that defines calendar components that are not
1125 group-scheduled components, but are components only on a single
1126 user's calendar.
1128 Description:
1129 This property is specified within the "VEVENT",
1130 "VTODO", and "VJOURNAL" calendar components to specify the
1131 organizer of a group-scheduled calendar entity. The property is
1132 specified within the "VFREEBUSY" calendar component to specify the
1133 calendar user requesting the free or busy time. When publishing a
1134 "VFREEBUSY" calendar component, the property is used to specify
1135 the calendar that the published busy time came from.
1137 The property has the property parameters "CN", for specifying the
1138 common or display name associated with the "Organizer", "DIR", for
1139 specifying a pointer to the directory information associated with
1140 the "Organizer", "SENT-BY", for specifying another calendar user
1141 that is acting on behalf of the "Organizer". The non-standard
1142 parameters may also be specified on this property. If the
1143 "LANGUAGE" property parameter is specified, the identified
1144 language applies to the "CN" parameter value.
1145 """
1146 return self.get("ORGANIZER")
1149def _set_organizer(self: Component, value: vCalAddress | str | None):
1150 """Set the value."""
1151 _del_organizer(self)
1152 if value is not None:
1153 self.add("ORGANIZER", value)
1156def _del_organizer(self: Component):
1157 """Delete the value."""
1158 self.pop("ORGANIZER")
1161organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1164def single_string_enum_property(
1165 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1166) -> property:
1167 """Create a property to access a single string value and convert it to an enum."""
1168 prop = single_string_property(name, docs, default=default)
1170 def fget(self: Component) -> StrEnum:
1171 """Get the value."""
1172 value = prop.fget(self)
1173 if value == default:
1174 return default
1175 return enum(str(value))
1177 def fset(self: Component, value: str | StrEnum | None) -> None:
1178 """Set the value."""
1179 if value == "":
1180 value = None
1181 prop.fset(self, value)
1183 return property(fget, fset, prop.fdel, doc=docs)
1186busy_type_property = single_string_enum_property(
1187 "BUSYTYPE",
1188 BUSYTYPE,
1189 BUSYTYPE.BUSY_UNAVAILABLE,
1190 """BUSYTYPE specifies the default busy time type.
1192Returns:
1193 :class:`icalendar.enums.BUSYTYPE`
1195Description:
1196 This property is used to specify the default busy time
1197 type. The values correspond to those used by the FBTYPE"
1198 parameter used on a "FREEBUSY" property, with the exception that
1199 the "FREE" value is not used in this property. If not specified
1200 on a component that allows this property, the default is "BUSY-
1201 UNAVAILABLE".
1202""",
1203)
1205priority_property = single_int_property(
1206 "PRIORITY",
1207 0,
1208 """
1210Conformance:
1211 This property can be specified in "VEVENT" and "VTODO" calendar components
1212 according to :rfc:`5545`.
1213 :rfc:`7953` adds this property to "VAVAILABILITY".
1215Description:
1216 This priority is specified as an integer in the range 0
1217 to 9. A value of 0 specifies an undefined priority. A value of 1
1218 is the highest priority. A value of 2 is the second highest
1219 priority. Subsequent numbers specify a decreasing ordinal
1220 priority. A value of 9 is the lowest priority.
1222 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1223 "LOW" is mapped into this property such that a property value in
1224 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1225 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1226 is "LOW" priority.
1228 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1229 "C3" is mapped into this property such that a property value of 1
1230 specifies "A1", a property value of 2 specifies "A2", a property
1231 value of 3 specifies "A3", and so forth up to a property value of
1232 9 specifies "C3".
1234 Other integer values are reserved for future use.
1236 Within a "VEVENT" calendar component, this property specifies a
1237 priority for the event. This property may be useful when more
1238 than one event is scheduled for a given time period.
1240 Within a "VTODO" calendar component, this property specified a
1241 priority for the to-do. This property is useful in prioritizing
1242 multiple action items for a given time period.
1243""",
1244)
1246class_property = single_string_enum_property(
1247 "CLASS",
1248 CLASS,
1249 CLASS.PUBLIC,
1250 """CLASS specifies the class of the calendar component.
1252Returns:
1253 :class:`icalendar.enums.CLASS`
1255Description:
1256 An access classification is only one component of the
1257 general security system within a calendar application. It
1258 provides a method of capturing the scope of the access the
1259 calendar owner intends for information within an individual
1260 calendar entry. The access classification of an individual
1261 iCalendar component is useful when measured along with the other
1262 security components of a calendar system (e.g., calendar user
1263 authentication, authorization, access rights, access role, etc.).
1264 Hence, the semantics of the individual access classifications
1265 cannot be completely defined by this memo alone. Additionally,
1266 due to the "blind" nature of most exchange processes using this
1267 memo, these access classifications cannot serve as an enforcement
1268 statement for a system receiving an iCalendar object. Rather,
1269 they provide a method for capturing the intention of the calendar
1270 owner for the access to the calendar component. If not specified
1271 in a component that allows this property, the default value is
1272 PUBLIC. Applications MUST treat x-name and iana-token values they
1273 don't recognize the same way as they would the PRIVATE value.
1274""",
1275)
1277transparency_property = single_string_enum_property(
1278 "TRANSP",
1279 TRANSP,
1280 TRANSP.OPAQUE,
1281 """TRANSP defines whether or not an event is transparent to busy time searches.
1283Returns:
1284 :class:`icalendar.enums.TRANSP`
1286Description:
1287 Time Transparency is the characteristic of an event
1288 that determines whether it appears to consume time on a calendar.
1289 Events that consume actual time for the individual or resource
1290 associated with the calendar SHOULD be recorded as OPAQUE,
1291 allowing them to be detected by free/busy time searches. Other
1292 events, which do not take up the individual's (or resource's) time
1293 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1294 busy time searches.
1295""",
1296)
1297status_property = single_string_enum_property(
1298 "STATUS",
1299 STATUS,
1300 "",
1301 """STATUS defines the overall status or confirmation for the calendar component.
1303Returns:
1304 :class:`icalendar.enums.STATUS`
1306The default value is ``""``.
1308Description:
1309 In a group-scheduled calendar component, the property
1310 is used by the "Organizer" to provide a confirmation of the event
1311 to the "Attendees". For example in a "VEVENT" calendar component,
1312 the "Organizer" can indicate that a meeting is tentative,
1313 confirmed, or cancelled. In a "VTODO" calendar component, the
1314 "Organizer" can indicate that an action item needs action, is
1315 completed, is in process or being worked on, or has been
1316 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1317 can indicate that a journal entry is draft, final, or has been
1318 cancelled or removed.
1319""",
1320)
1322url_property = single_string_property(
1323 "URL",
1324 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1326Description:
1327 This property may be used in a calendar component to
1328 convey a location where a more dynamic rendition of the calendar
1329 information associated with the calendar component can be found.
1330 This memo does not attempt to standardize the form of the URI, nor
1331 the format of the resource pointed to by the property value. If
1332 the URL property and Content-Location MIME header are both
1333 specified, they MUST point to the same resource.
1335Conformance:
1336 This property can be specified once in the "VEVENT",
1337 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1338 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1340Example:
1341 The following is an example of this property:
1343 .. code-block:: text
1345 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1347""",
1348)
1350source_property = single_string_property(
1351 "SOURCE",
1352 """A URI from where calendar data can be refreshed.
1354Description:
1355 This property identifies a location where a client can
1356 retrieve updated data for the calendar. Clients SHOULD honor any
1357 specified "REFRESH-INTERVAL" value when periodically retrieving
1358 data. Note that this property differs from the "URL" property in
1359 that "URL" is meant to provide an alternative representation of
1360 the calendar data rather than the original location of the data.
1362Conformance:
1363 This property can be specified once in an iCalendar object.
1365Example:
1366 The following is an example of this property:
1368 .. code-block:: text
1370 SOURCE;VALUE=URI:https://example.com/holidays.ics
1372""",
1373)
1375location_property = multi_language_text_property(
1376 "LOCATION",
1377 None,
1378 """The intended venue for the activity defined by a calendar component.
1380Property Parameters:
1381 IANA, non-standard, alternate text
1382 representation, and language property parameters can be specified
1383 on this property.
1385Conformance:
1386 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1387 calendar component.
1388 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1390Description:
1391 Specific venues such as conference or meeting rooms may
1392 be explicitly specified using this property. An alternate
1393 representation may be specified that is a URI that points to
1394 directory information with more structured specification of the
1395 location. For example, the alternate representation may specify
1396 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1397 CID URL :rfc:`2392` pointing to a MIME body part containing a
1398 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1400""",
1401)
1403contacts_property = multi_text_property(
1404 "CONTACT",
1405 """Contact information associated with the calendar component.
1407Purpose:
1408 This property is used to represent contact information or
1409 alternately a reference to contact information associated with the
1410 calendar component.
1412Property Parameters:
1413 IANA, non-standard, alternate text
1414 representation, and language property parameters can be specified
1415 on this property.
1417Conformance:
1418 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1419 "VJOURNAL", or "VFREEBUSY" calendar component.
1420 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1421 amd "VAVAILABLE" calendar component.
1423Description:
1424 The property value consists of textual contact
1425 information. An alternative representation for the property value
1426 can also be specified that refers to a URI pointing to an
1427 alternate form, such as a vCard :rfc:`2426`, for the contact
1428 information.
1430Example:
1431 The following is an example of this property referencing
1432 textual contact information:
1434 .. code-block:: text
1436 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1438 The following is an example of this property with an alternate
1439 representation of an LDAP URI to a directory entry containing the
1440 contact information:
1442 .. code-block:: text
1444 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1445 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1446 +1-919-555-1234
1448 The following is an example of this property with an alternate
1449 representation of a MIME body part containing the contact
1450 information, such as a vCard :rfc:`2426` embedded in a text/
1451 directory media type :rfc:`2425`:
1453 .. code-block:: text
1455 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1456 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1458 The following is an example of this property referencing a network
1459 resource, such as a vCard :rfc:`2426` object containing the contact
1460 information:
1462 .. code-block:: text
1464 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1465 Dolittle\\, ABC Industries\\, +1-919-555-1234
1466""",
1467)
1470def timezone_datetime_property(name: str, docs: str):
1471 """Create a property to access the values with a proper timezone."""
1473 return single_utc_property(name, docs)
1476rfc_7953_dtstart_property = timezone_datetime_property(
1477 "DTSTART",
1478 """Start of the component.
1480 This is almost the same as :attr:`Event.DTSTART` with one exception:
1481 The values MUST have a timezone and DATE is not allowed.
1483 Description:
1484 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1485 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1486 "DATE-TIME" values specified as either the date with UTC time or
1487 the date with local time and a time zone reference.
1489 """,
1490)
1492rfc_7953_dtend_property = timezone_datetime_property(
1493 "DTEND",
1494 """Start of the component.
1496 This is almost the same as :attr:`Event.DTEND` with one exception:
1497 The values MUST have a timezone and DATE is not allowed.
1499 Description:
1500 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1501 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1502 "DATE-TIME" values specified as either the date with UTC time or
1503 the date with local time and a time zone reference.
1504 """,
1505)
1508@property
1509def rfc_7953_duration_property(self) -> timedelta | None:
1510 """Compute the duration of this component.
1512 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1513 Otherwise, the duration is calculated from :attr:`DTSTART` and
1514 :attr:`DTEND`/:attr:`DURATION`.
1516 This is in accordance with :rfc:`7953`:
1517 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1518 """
1519 duration = self.DURATION
1520 if duration:
1521 return duration
1522 end = self.DTEND
1523 if end is None:
1524 return None
1525 start = self.DTSTART
1526 if start is None:
1527 raise IncompleteComponent("Cannot compute duration without start.")
1528 return end - start
1531@property
1532def rfc_7953_end_property(self) -> timedelta | None:
1533 """Compute the duration of this component.
1535 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1536 Otherwise, the duration is calculated from :attr:`DTSTART` and
1537 :attr:`DTEND`/:attr:`DURATION`.
1539 This is in accordance with :rfc:`7953`:
1540 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1541 """
1542 duration = self.DURATION
1543 if duration:
1544 start = self.DTSTART
1545 if start is None:
1546 raise IncompleteComponent("Cannot compute end without start.")
1547 return start + duration
1548 end = self.DTEND
1549 if end is None:
1550 return None
1551 return end
1554@rfc_7953_end_property.setter
1555def rfc_7953_end_property(self, value: datetime):
1556 self.DTEND = value
1559@rfc_7953_end_property.deleter
1560def rfc_7953_end_property(self):
1561 del self.DTEND
1564def get_start_end_duration_with_validation(
1565 component: Component,
1566 start_property: str,
1567 end_property: str,
1568 component_name: str,
1569) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1570 """
1571 Validate the component and return start, end, and duration.
1573 This tests validity according to :rfc:`5545` rules
1574 for ``Event`` and ``Todo`` components.
1576 Parameters:
1577 component: The component to validate, either ``Event`` or ``Todo``.
1578 start_property: The start property name, ``DTSTART``.
1579 end_property: The end property name, either ``DTEND`` for ``Event`` or
1580 ``DUE`` for ``Todo``.
1581 component_name: The component name for error messages,
1582 either ``VEVENT`` or ``VTODO``.
1584 Returns:
1585 tuple: (start, end, duration) values from the component.
1587 Raises:
1588 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1590 """
1591 start = getattr(component, start_property, None)
1592 end = getattr(component, end_property, None)
1593 duration = component.DURATION
1595 # RFC 5545: Only one of end property and DURATION may be present
1596 if duration is not None and end is not None:
1597 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1598 msg = (
1599 f"Only one of {end_name} and DURATION "
1600 f"may be in a {component_name}, not both."
1601 )
1602 raise InvalidCalendar(msg)
1604 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1605 if (
1606 start is not None
1607 and is_date(start)
1608 and duration is not None
1609 and duration.seconds != 0
1610 ):
1611 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1612 raise InvalidCalendar(msg)
1614 # RFC 5545: DTSTART and end property must be of the same type
1615 if start is not None and end is not None and is_date(start) != is_date(end):
1616 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1617 msg = (
1618 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1619 )
1620 raise InvalidCalendar(msg)
1622 return start, end, duration
1625def get_start_property(component: Component) -> date | datetime:
1626 """
1627 Get the start property with validation.
1629 Parameters:
1630 component: The component from which to get its start property.
1632 Returns:
1633 The ``DTSTART`` value.
1635 Raises:
1636 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1638 """
1639 # Trigger validation by calling _get_start_end_duration
1640 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1641 if start is None:
1642 msg = "No DTSTART given."
1643 raise IncompleteComponent(msg)
1644 return start
1647def get_end_property(component: Component, end_property: str) -> date | datetime:
1648 """
1649 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1651 Parameters:
1652 component: The component to get end from
1653 end_property: The end property name, either ``DTEND`` for ``Event`` or
1654 ``DUE`` for ``Todo``.
1656 Returns:
1657 The computed end value.
1659 Raises:
1660 ~error.IncompleteComponent: If the provided information is incomplete
1661 to compute the end property.
1663 """
1664 # Trigger validation by calling _get_start_end_duration
1665 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1667 if end is None and duration is None:
1668 if start is None:
1669 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1670 msg = f"No {end_name} or DURATION+DTSTART given."
1671 raise IncompleteComponent(msg)
1673 # Default behavior differs for Event vs Todo:
1674 # Event: date gets +1 day, datetime gets same time
1675 # Todo: both date and datetime get same time (issue #898)
1676 if end_property == "DTEND" and is_date(start):
1677 return start + timedelta(days=1)
1678 return start
1680 if duration is not None:
1681 if start is not None:
1682 return start + duration
1683 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1684 msg = f"No {end_name} or DURATION+DTSTART given."
1685 raise IncompleteComponent(msg)
1687 return end
1690def get_duration_property(component: Component) -> timedelta:
1691 """
1692 Get the duration property with fallback calculation from start and end.
1694 Parameters:
1695 component: The component from which to get its duration property.
1697 Returns:
1698 The duration as a timedelta.
1700 """
1701 # First check if DURATION property is explicitly set
1702 if "DURATION" in component:
1703 return component["DURATION"].dt
1705 # Fall back to calculated duration from start and end
1706 return component.end - component.start
1709def set_duration_with_locking(
1710 component: Component,
1711 duration: timedelta | None,
1712 locked: Literal["start", "end"],
1713 end_property: str,
1714) -> None:
1715 """
1716 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1718 Parameters:
1719 component: The component to modify, either ``Event`` or ``Todo``.
1720 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1721 locked: Which property to keep unchanged, either ``start`` or ``end``.
1722 end_property: The end property name, either ``DTEND`` for ``Event`` or
1723 ``DUE`` for ``Todo``.
1725 """
1726 # Convert to DURATION property if duration is None
1727 if duration is None:
1728 if "DURATION" in component:
1729 return # Already has DURATION property
1730 current_duration = component.duration
1731 component.DURATION = current_duration
1732 return
1734 if not isinstance(duration, timedelta):
1735 msg = f"Use timedelta, not {type(duration).__name__}."
1736 raise TypeError(msg)
1738 # Validate date/duration compatibility
1739 start = component.DTSTART
1740 if start is not None and is_date(start) and duration.seconds != 0:
1741 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1742 raise InvalidCalendar(msg)
1744 if locked == "start":
1745 # Keep start locked, adjust end
1746 if start is None:
1747 msg = "Cannot set duration without DTSTART. Set start time first."
1748 raise IncompleteComponent(msg)
1749 component.pop(end_property, None) # Remove end property
1750 component.DURATION = duration
1751 elif locked == "end":
1752 # Keep end locked, adjust start
1753 current_end = component.end
1754 component.DTSTART = current_end - duration
1755 component.pop(end_property, None) # Remove end property
1756 component.DURATION = duration
1757 else:
1758 msg = f"locked must be 'start' or 'end', not {locked!r}"
1759 raise ValueError(msg)
1762def set_start_with_locking(
1763 component: Component,
1764 start: date | datetime,
1765 locked: Literal["duration", "end"] | None,
1766 end_property: str,
1767) -> None:
1768 """
1769 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1771 Parameters:
1772 component: The component to modify, either ``Event`` or ``Todo``.
1773 start: The start time to set.
1774 locked: Which property to keep unchanged, either ``duration``, ``end``,
1775 or ``None`` for auto-detect.
1776 end_property: The end property name, either ``DTEND`` for ``Event`` or
1777 ``DUE`` for ``Todo``.
1779 """
1780 if locked is None:
1781 # Auto-detect based on existing properties
1782 if "DURATION" in component:
1783 locked = "duration"
1784 elif end_property in component:
1785 locked = "end"
1786 else:
1787 # Default to duration if no existing properties
1788 locked = "duration"
1790 if locked == "duration":
1791 # Keep duration locked, adjust end
1792 current_duration = (
1793 component.duration
1794 if "DURATION" in component or end_property in component
1795 else None
1796 )
1797 component.DTSTART = start
1798 if current_duration is not None:
1799 component.pop(end_property, None) # Remove end property
1800 component.DURATION = current_duration
1801 elif locked == "end":
1802 # Keep end locked, adjust duration
1803 current_end = component.end
1804 component.DTSTART = start
1805 component.pop("DURATION", None) # Remove duration property
1806 setattr(component, end_property, current_end)
1807 else:
1808 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1809 raise ValueError(msg)
1812def set_end_with_locking(
1813 component: Component,
1814 end: date | datetime,
1815 locked: Literal["start", "duration"],
1816 end_property: str,
1817) -> None:
1818 """
1819 Set the end with explicit locking behavior for Event and Todo components.
1821 Parameters:
1822 component: The component to modify, either ``Event`` or ``Todo``.
1823 end: The end time to set.
1824 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1825 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1826 for ``Todo``.
1828 """
1829 if locked == "start":
1830 # Keep start locked, adjust duration
1831 component.pop("DURATION", None) # Remove duration property
1832 setattr(component, end_property, end)
1833 elif locked == "duration":
1834 # Keep duration locked, adjust start
1835 current_duration = component.duration
1836 component.DTSTART = end - current_duration
1837 component.pop(end_property, None) # Remove end property
1838 component.DURATION = current_duration
1839 else:
1840 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1841 raise ValueError(msg)
1844def _get_images(self: Component) -> list[Image]:
1845 """IMAGE specifies an image associated with the calendar or a calendar component.
1847 Description:
1848 This property specifies an image for an iCalendar
1849 object or a calendar component via a URI or directly with inline
1850 data that can be used by calendar user agents when presenting the
1851 calendar data to a user. Multiple properties MAY be used to
1852 specify alternative sets of images with, for example, varying
1853 media subtypes, resolutions, or sizes. When multiple properties
1854 are present, calendar user agents SHOULD display only one of them,
1855 picking one that provides the most appropriate image quality, or
1856 display none. The "DISPLAY" parameter is used to indicate the
1857 intended display mode for the image. The "ALTREP" parameter,
1858 defined in :rfc:`5545`, can be used to provide a "clickable" image
1859 where the URI in the parameter value can be "launched" by a click
1860 on the image in the calendar user agent.
1862 Conformance:
1863 This property can be specified multiple times in an
1864 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1865 components.
1867 .. note::
1869 At the present moment, this property is read-only. If you require a setter,
1870 please open an issue or a pull request.
1871 """
1872 images = self.get("IMAGE", [])
1873 if not isinstance(images, SEQUENCE_TYPES):
1874 images = [images]
1875 return [Image.from_property_value(img) for img in images]
1878images_property = property(_get_images)
1881def _get_conferences(self: Component) -> list[Conference]:
1882 """Return the CONFERENCE properties as a list.
1884 Purpose:
1885 This property specifies information for accessing a conferencing system.
1887 Conformance:
1888 This property can be specified multiple times in a
1889 "VEVENT" or "VTODO" calendar component.
1891 Description:
1892 This property specifies information for accessing a
1893 conferencing system for attendees of a meeting or task. This
1894 might be for a telephone-based conference number dial-in with
1895 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1896 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1897 or https: URI :rfc:`7230`), or for an instant messaging group chat
1898 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1899 conferencing system is not available, a data: URI :rfc:`2397`
1900 containing a text description can be used.
1902 A conference system can be a bidirectional communication channel
1903 or a uni-directional "broadcast feed".
1905 The "FEATURE" property parameter is used to describe the key
1906 capabilities of the conference system to allow a client to choose
1907 the ones that give the required level of interaction from a set of
1908 multiple properties.
1910 The "LABEL" property parameter is used to convey additional
1911 details on the use of the URI. For example, the URIs or access
1912 codes for the moderator and attendee of a teleconference system
1913 could be different, and the "LABEL" property parameter could be
1914 used to "tag" each "CONFERENCE" property to indicate which is
1915 which.
1917 The "LANGUAGE" property parameter can be used to specify the
1918 language used for text values used with this property (as per
1919 Section 3.2.10 of :rfc:`5545`).
1921 Example:
1922 The following are examples of this property:
1924 .. code-block:: text
1926 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1927 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1928 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1929 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1930 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1931 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1932 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1933 LABEL=Chat room:xmpp:chat-123@conference.example.com
1934 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1935 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1937 Get all conferences:
1939 .. code-block:: pycon
1941 >>> from icalendar import Event
1942 >>> event = Event()
1943 >>> event.conferences
1944 []
1946 Set a conference:
1948 .. code-block:: pycon
1950 >>> from icalendar import Event, Conference
1951 >>> event = Event()
1952 >>> event.conferences = [
1953 ... Conference(
1954 ... "tel:+1-412-555-0123,,,654321",
1955 ... feature="PHONE,MODERATOR",
1956 ... label="Moderator dial-in",
1957 ... language="EN",
1958 ... )
1959 ... ]
1960 >>> print(event.to_ical())
1961 BEGIN:VEVENT
1962 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
1963 ALUE=URI:tel:+1-412-555-0123,,,654321
1964 END:VEVENT
1966 """
1967 conferences = self.get("CONFERENCE", [])
1968 if not isinstance(conferences, SEQUENCE_TYPES):
1969 conferences = [conferences]
1970 return [Conference.from_uri(conference) for conference in conferences]
1973def _set_conferences(self: Component, conferences: list[Conference] | None):
1974 """Set the conferences."""
1975 _del_conferences(self)
1976 for conference in conferences or []:
1977 self.add("CONFERENCE", conference.to_uri())
1980def _del_conferences(self: Component):
1981 """Delete all conferences."""
1982 self.pop("CONFERENCE")
1985conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
1988def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
1989 """LINK properties as a list.
1991 Purpose:
1992 LINK provides a reference to external information related to a component.
1994 Property Parameters:
1995 The VALUE parameter is required.
1996 Non-standard, link relation type, format type, label, and language parameters
1997 can also be specified on this property.
1998 The LABEL parameter is defined in :rfc:`7986`.
2000 Conformance:
2001 This property can be specified zero or more times in any iCalendar component.
2002 LINK is specified in :rfc:`9253`.
2003 The LINKREL parameter is required.
2005 Description:
2006 When used in a component, the value of this property points to
2007 additional information related to the component.
2008 For example, it may reference the originating web server.
2010 This property is a serialization of the model in :rfc:`8288`,
2011 where the link target is carried in the property value,
2012 the link context is the containing calendar entity,
2013 and the link relation type and any target attributes
2014 are carried in iCalendar property parameters.
2016 The LINK property parameters map to :rfc:`8288` attributes as follows:
2018 LABEL
2019 This parameter maps to the "title"
2020 attribute defined in Section 3.4.1 of :rfc:`8288`.
2021 LABEL is used to label the destination
2022 of a link such that it can be used as a human-readable identifier
2023 (e.g., a menu entry) in the language indicated by the LANGUAGE
2024 (if present).
2025 LANGUAGE
2026 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2027 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2028 LINKREL
2029 This parameter maps to the link relation type defined in Section 2.1 of
2030 :rfc:`8288`. See `Registered Link Relation Types
2031 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2032 FMTTYPE
2033 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2034 :rfc:`8288`.
2036 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2038 Examples:
2039 The following is an example of this property,
2040 which provides a reference to the source for the calendar object.
2042 .. code-block:: text
2044 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2045 https://example.com/events
2047 The following is an example of this property,
2048 which provides a reference to an entity from which this one was derived.
2049 The link relation is a vendor-defined value.
2051 .. code-block:: text
2053 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2054 VALUE=URI:
2055 https://example.com/tasks/01234567-abcd1234.ics
2057 The following is an example of this property,
2058 which provides a reference to a fragment of an XML document.
2059 The link relation is a vendor-defined value.
2061 .. code-block:: text
2063 LINK;LINKREL="https://example.com/linkrel/costStructure";
2064 VALUE=XML-REFERENCE:
2065 https://example.com/xmlDocs/bidFramework.xml
2066 #xpointer(descendant::CostStruc/range-to(
2067 following::CostStrucEND[1]))
2069 Set a link :class:`icalendar.vUri` to the event page:
2071 .. code-block:: pycon
2073 >>> from icalendar import Event, vUri
2074 >>> from datetime import datetime
2075 >>> link = vUri(
2076 ... "http://example.com/event-page",
2077 ... params={"LINKREL":"SOURCE"}
2078 ... )
2079 >>> event = Event.new(
2080 ... start=datetime(2025, 9, 17, 12, 0),
2081 ... summary="An Example Event with a page"
2082 ... )
2083 >>> event.links = [link]
2084 >>> print(event.to_ical())
2085 BEGIN:VEVENT
2086 SUMMARY:An Example Event with a page
2087 DTSTART:20250917T120000
2088 DTSTAMP:20250517T080612Z
2089 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2090 LINK;LINKREL="SOURCE":http://example.com/event-page
2091 END:VEVENT
2093 """
2094 links = self.get("LINK", [])
2095 if not isinstance(links, list):
2096 links = [links]
2097 return links
2100LINKS_TYPE_SETTER: TypeAlias = (
2101 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2102)
2105def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2106 """Set the LINKs."""
2107 _del_links(self)
2108 if links is None:
2109 return
2110 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2111 links = [links]
2112 for link in links:
2113 if type(link) is str:
2114 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2115 self.add("LINK", link)
2118def _del_links(self: Component) -> None:
2119 """Delete all links."""
2120 self.pop("LINK")
2123links_property = property(_get_links, _set_links, _del_links)
2125RELATED_TO_TYPE_SETTER: TypeAlias = (
2126 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2127)
2130def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2131 """RELATED-TO properties as a list.
2133 Purpose:
2134 This property is used to represent a relationship or reference
2135 between one calendar component and another.
2136 :rfc:`9523` allows URI or UID values and a GAP parameter.
2138 Value Type:
2139 :rfc:`5545`: TEXT
2140 :rfc:`9253`: URI, UID
2142 Conformance:
2143 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2144 "VTODO", and "VJOURNAL" calendar components.
2145 Since :rfc:`9523`, this property MAY be specified in any
2146 iCalendar component.
2148 Description (:rfc:`5545`):
2149 The property value consists of the persistent, globally
2150 unique identifier of another calendar component. This value would
2151 be represented in a calendar component by the "UID" property.
2153 By default, the property value points to another calendar
2154 component that has a PARENT relationship to the referencing
2155 object. The "RELTYPE" property parameter is used to either
2156 explicitly state the default PARENT relationship type to the
2157 referenced calendar component or to override the default PARENT
2158 relationship type and specify either a CHILD or SIBLING
2159 relationship. The PARENT relationship indicates that the calendar
2160 component is a subordinate of the referenced calendar component.
2161 The CHILD relationship indicates that the calendar component is a
2162 superior of the referenced calendar component. The SIBLING
2163 relationship indicates that the calendar component is a peer of
2164 the referenced calendar component.
2166 Changes to a calendar component referenced by this property can
2167 have an implicit impact on the related calendar component. For
2168 example, if a group event changes its start or end date or time,
2169 then the related, dependent events will need to have their start
2170 and end dates changed in a corresponding way. Similarly, if a
2171 PARENT calendar component is cancelled or deleted, then there is
2172 an implied impact to the related CHILD calendar components. This
2173 property is intended only to provide information on the
2174 relationship of calendar components. It is up to the target
2175 calendar system to maintain any property implications of this
2176 relationship.
2178 Description (:rfc:`9253`):
2179 By default or when VALUE=UID is specified, the property value
2180 consists of the persistent, globally unique identifier of another
2181 calendar component. This value would be represented in a calendar
2182 component by the UID property.
2184 By default, the property value
2185 points to another calendar component that has a PARENT relationship
2186 to the referencing object. The RELTYPE property parameter is used
2187 to either explicitly state the default PARENT relationship type to
2188 the referenced calendar component or to override the default
2189 PARENT relationship type and specify either a CHILD or SIBLING
2190 relationship or a temporal relationship.
2192 The PARENT relationship
2193 indicates that the calendar component is a subordinate of the
2194 referenced calendar component. The CHILD relationship indicates
2195 that the calendar component is a superior of the referenced calendar
2196 component. The SIBLING relationship indicates that the calendar
2197 component is a peer of the referenced calendar component.
2199 To preserve backwards compatibility, the value type MUST
2200 be UID when the PARENT, SIBLING, or CHILD relationships
2201 are specified.
2203 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2204 or STARTTOSTART relationships define temporal relationships, as
2205 specified in the RELTYPE parameter definition.
2207 The FIRST and NEXT
2208 define ordering relationships between calendar components.
2210 The DEPENDS-ON relationship indicates that the current calendar
2211 component depends on the referenced calendar component in some manner.
2212 For example, a task may be blocked waiting on the other,
2213 referenced, task.
2215 The REFID and CONCEPT relationships establish
2216 a reference from the current component to the referenced component.
2217 Changes to a calendar component referenced by this property
2218 can have an implicit impact on the related calendar component.
2219 For example, if a group event changes its start or end date or
2220 time, then the related, dependent events will need to have their
2221 start and end dates and times changed in a corresponding way.
2222 Similarly, if a PARENT calendar component is canceled or deleted,
2223 then there is an implied impact to the related CHILD calendar
2224 components. This property is intended only to provide information
2225 on the relationship of calendar components.
2227 Deletion of the target component, for example, the target of a
2228 FIRST, NEXT, or temporal relationship, can result in broken links.
2230 It is up to the target calendar system to maintain any property
2231 implications of these relationships.
2233 Examples:
2234 :rfc:`5545` examples of this property:
2236 .. code-block:: text
2238 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2240 .. code-block:: text
2242 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2244 :rfc:`9253` examples of this property:
2246 .. code-block:: text
2248 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2249 https://example.com/caldav/user/jb/cal/
2250 19960401-080045-4000F192713.ics
2252 See also :class:`icalendar.enum.RELTYPE`.
2254 """
2255 result = self.get("RELATED-TO", [])
2256 if not isinstance(result, list):
2257 return [result]
2258 return result
2261def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2262 """Set the RELATED-TO properties."""
2263 _del_related_to(self)
2264 if values is None:
2265 return
2266 if not isinstance(values, list):
2267 values = [values]
2268 for value in values:
2269 self.add("RELATED-TO", value)
2272def _del_related_to(self: Component):
2273 """Delete the RELATED-TO properties."""
2274 self.pop("RELATED-TO", None)
2277related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2280def _get_concepts(self: Component) -> list[vUri]:
2281 """CONCEPT
2283 Purpose:
2284 CONCEPT defines the formal categories for a calendar component.
2286 Conformance:
2287 Since :rfc:`9253`,
2288 this property can be specified zero or more times in any iCalendar component.
2290 Description:
2291 This property is used to specify formal categories or classifications of
2292 the calendar component. The values are useful in searching for a calendar
2293 component of a particular type and category.
2295 This categorization is distinct from the more informal "tagging" of components
2296 provided by the existing CATEGORIES property. It is expected that the value of
2297 the CONCEPT property will reference an external resource that provides
2298 information about the categorization.
2300 In addition, a structured URI value allows for hierarchical categorization of
2301 events.
2303 Possible category resources are the various proprietary systems, for example,
2304 the Library of Congress, or an open source of categorization data.
2306 Examples:
2307 The following is an example of this property.
2308 It points to a server acting as the source for the calendar object.
2310 .. code-block:: text
2312 CONCEPT:https://example.com/event-types/arts/music
2314 .. seealso::
2316 :attr:`Component.categories`
2317 """
2318 concepts = self.get("CONCEPT", [])
2319 if not isinstance(concepts, list):
2320 concepts = [concepts]
2321 return concepts
2324CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2327def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2328 """Set the concepts."""
2329 _del_concepts(self)
2330 if concepts is None:
2331 return
2332 if not isinstance(concepts, list):
2333 concepts = [concepts]
2334 for value in concepts:
2335 self.add("CONCEPT", value)
2338def _del_concepts(self: Component):
2339 """Delete the concepts."""
2340 self.pop("CONCEPT", None)
2343concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2346def multi_string_property(name: str, doc: str):
2347 """A property for an iCalendar Property that can occur multiple times."""
2349 def fget(self: Component) -> list[str]:
2350 """Get the values of a multi-string property."""
2351 value = self.get(name, [])
2352 if not isinstance(value, list):
2353 value = [value]
2354 return value
2356 def fset(self: Component, value: list[str] | str | None) -> None:
2357 """Set the values of a multi-string property."""
2358 fdel(self)
2359 if value is None:
2360 return
2361 if not isinstance(value, list):
2362 value = [value]
2363 for value in value:
2364 self.add(name, value)
2366 def fdel(self: Component):
2367 """Delete the values of a multi-string property."""
2368 self.pop(name, None)
2370 return property(fget, fset, fdel, doc=doc)
2373refids_property = multi_string_property(
2374 "REFID",
2375 """REFID
2377Purpose:
2378 REFID acts as a key for associated iCalendar entities.
2380Conformance:
2381 Since :rfc:`9253`,
2382 this property can be specified zero or more times in any iCalendar component.
2384Description:
2385 The value of this property is free-form text that creates an
2386 identifier for associated components.
2387 All components that use the same REFID value are associated through
2388 that value and can be located or retrieved as a group.
2389 For example, all of the events in a travel itinerary
2390 would have the same REFID value, so as to be grouped together.
2392Examples:
2393 The following is an example of this property.
2395 .. code-block:: text
2397 REFID:itinerary-2014-11-17
2399 Use a REFID to associate several VTODOs:
2401 .. code-block:: pycon
2403 >>> from icalendar import Todo
2404 >>> todo_1 = Todo.new(
2405 ... summary="turn off stove",
2406 ... refids=["travel", "alps"]
2407 ... )
2408 >>> todo_2 = Todo.new(
2409 ... summary="pack backpack",
2410 ... refids=["travel", "alps"]
2411 ... )
2412 >>> todo_1.refids == todo_2.refids
2413 True
2415.. note::
2417 List modifications do not modify the component.
2418""",
2419)
2422__all__ = [
2423 "CONCEPTS_TYPE_SETTER",
2424 "LINKS_TYPE_SETTER",
2425 "RELATED_TO_TYPE_SETTER",
2426 "attendees_property",
2427 "busy_type_property",
2428 "categories_property",
2429 "class_property",
2430 "color_property",
2431 "comments_property",
2432 "concepts_property",
2433 "conferences_property",
2434 "contacts_property",
2435 "create_single_property",
2436 "description_property",
2437 "descriptions_property",
2438 "duration_property",
2439 "exdates_property",
2440 "get_duration_property",
2441 "get_end_property",
2442 "get_start_end_duration_with_validation",
2443 "get_start_property",
2444 "images_property",
2445 "links_property",
2446 "location_property",
2447 "multi_language_text_property",
2448 "multi_string_property",
2449 "organizer_property",
2450 "priority_property",
2451 "property_del_duration",
2452 "property_doc_duration_template",
2453 "property_get_duration",
2454 "property_set_duration",
2455 "rdates_property",
2456 "refids_property",
2457 "related_to_property",
2458 "rfc_7953_dtend_property",
2459 "rfc_7953_dtstart_property",
2460 "rfc_7953_duration_property",
2461 "rfc_7953_end_property",
2462 "rrules_property",
2463 "sequence_property",
2464 "set_duration_with_locking",
2465 "set_end_with_locking",
2466 "set_start_with_locking",
2467 "single_int_property",
2468 "single_utc_property",
2469 "source_property",
2470 "status_property",
2471 "summary_property",
2472 "transparency_property",
2473 "uid_property",
2474 "url_property",
2475]