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 Callable, 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)
360 if compatibility_prop is not None:
361 self.add(compatibility_prop, value)
363 def fdel(self: Component):
364 """Delete the property."""
365 self.pop(main_prop, None)
366 if compatibility_prop is not None:
367 self.pop(compatibility_prop, None)
369 return property(fget, fset, fdel, doc)
372def single_int_property(prop: str, default: int, doc: str) -> property:
373 """Create a property for an int value that exists only once.
375 Parameters:
376 prop: The name of the property
377 default: The default value
378 doc: The documentation string
379 """
381 def fget(self: Component) -> int:
382 """Get the property"""
383 try:
384 return int(self.get(prop, default))
385 except ValueError as e:
386 raise InvalidCalendar(f"{prop} must be an int") from e
388 def fset(self: Component, value: int | None):
389 """Set the property."""
390 fdel(self)
391 if value is not None:
392 self.add(prop, value)
394 def fdel(self: Component):
395 """Delete the property."""
396 self.pop(prop, None)
398 return property(fget, fset, fdel, doc)
401def single_utc_property(name: str, docs: str) -> property:
402 """Create a property to access a value of datetime in UTC timezone.
404 Parameters:
405 name: name of the property
406 docs: documentation string
407 """
408 docs = (
409 f"""The {name} property. datetime in UTC
411 All values will be converted to a datetime in UTC.
412 """
413 + docs
414 )
416 def fget(self: Component) -> datetime | None:
417 """Get the value."""
418 if name not in self:
419 return None
420 dt = self.get(name)
421 if isinstance(dt, vText):
422 # we might be in an attribute that is not typed
423 value = vDDDTypes.from_ical(dt)
424 else:
425 value = getattr(dt, "dt", dt)
426 if value is None or not isinstance(value, date):
427 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
428 return tzp.localize_utc(value)
430 def fset(self: Component, value: datetime | None):
431 """Set the value"""
432 if value is None:
433 fdel(self)
434 return
435 if not isinstance(value, date):
436 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
437 fdel(self)
438 self.add(name, tzp.localize_utc(value))
440 def fdel(self: Component):
441 """Delete the property."""
442 self.pop(name, None)
444 return property(fget, fset, fdel, doc=docs)
447def single_string_property(
448 name: str, docs: str, other_name: str | None = None, default: str = ""
449) -> property:
450 """Create a property to access a single string value."""
452 def fget(self: Component) -> str:
453 """Get the value."""
454 result = self.get(
455 name, None if other_name is None else self.get(other_name, None)
456 )
457 if result is None or result == []:
458 return default
459 if isinstance(result, list):
460 return result[0]
461 return result
463 def fset(self: Component, value: str | None):
464 """Set the value.
466 Setting the value to None will delete it.
467 """
468 fdel(self)
469 if value is not None:
470 self.add(name, value)
472 def fdel(self: Component):
473 """Delete the property."""
474 self.pop(name, None)
475 if other_name is not None:
476 self.pop(other_name, None)
478 return property(fget, fset, fdel, doc=docs)
481color_property = single_string_property(
482 "COLOR",
483 """This property specifies a color used for displaying the component.
485 This implements :rfc:`7986` ``COLOR`` property.
487 Property Parameters:
488 IANA and non-standard property parameters can
489 be specified on this property.
491 Conformance:
492 This property can be specified once in an iCalendar
493 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
495 Description:
496 This property specifies a color that clients MAY use
497 when presenting the relevant data to a user. Typically, this
498 would appear as the "background" color of events or tasks. The
499 value is a case-insensitive color name taken from the CSS3 set of
500 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
502 Example:
503 ``"turquoise"``, ``"#ffffff"``
505 .. code-block:: pycon
507 >>> from icalendar import Todo
508 >>> todo = Todo()
509 >>> todo.color = "green"
510 >>> print(todo.to_ical())
511 BEGIN:VTODO
512 COLOR:green
513 END:VTODO
514 """,
515)
517sequence_property = single_int_property(
518 "SEQUENCE",
519 0,
520 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
522Value Type:
523 INTEGER
525Property Parameters:
526 IANA and non-standard property parameters can be specified on this property.
528Conformance:
529 The property can be specified in "VEVENT", "VTODO", or
530 "VJOURNAL" calendar component.
532Description:
533 When a calendar component is created, its sequence
534 number is 0. It is monotonically incremented by the "Organizer's"
535 CUA each time the "Organizer" makes a significant revision to the
536 calendar component.
538 The "Organizer" includes this property in an iCalendar object that
539 it sends to an "Attendee" to specify the current version of the
540 calendar component.
542 The "Attendee" includes this property in an iCalendar object that
543 it sends to the "Organizer" to specify the version of the calendar
544 component to which the "Attendee" is referring.
546 A change to the sequence number is not the mechanism that an
547 "Organizer" uses to request a response from the "Attendees". The
548 "RSVP" parameter on the "ATTENDEE" property is used by the
549 "Organizer" to indicate that a response from the "Attendees" is
550 requested.
552 Recurrence instances of a recurring component MAY have different
553 sequence numbers.
555Examples:
556 The following is an example of this property for a calendar
557 component that was just created by the "Organizer":
559 .. code-block:: pycon
561 >>> from icalendar import Event
562 >>> event = Event()
563 >>> event.sequence
564 0
566 The following is an example of this property for a calendar
567 component that has been revised 10 different times by the
568 "Organizer":
570 .. code-block:: pycon
572 >>> from icalendar import Calendar
573 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
574 >>> event = calendar.events[0]
575 >>> event.sequence
576 10
577 """, # noqa: E501
578)
581def _get_categories(component: Component) -> list[str]:
582 """Get all the categories."""
583 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES")
584 if isinstance(categories, list):
585 _set_categories(
586 component,
587 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
588 )
589 return _get_categories(component)
590 if categories is None:
591 categories = vCategory([])
592 component.add("CATEGORIES", categories)
593 return categories.cats
596def _set_categories(component: Component, cats: Sequence[str] | None) -> None:
597 """Set the categories."""
598 if not cats and cats != []:
599 _del_categories(component)
600 return
601 component["CATEGORIES"] = categories = vCategory(cats)
602 if isinstance(cats, list):
603 cats.clear()
604 cats.extend(categories.cats)
605 categories.cats = cats
608def _del_categories(component: Component) -> None:
609 """Delete the categories."""
610 component.pop("CATEGORIES", None)
613categories_property = property(
614 _get_categories,
615 _set_categories,
616 _del_categories,
617 """This property defines the categories for a component.
619Property Parameters:
620 IANA, non-standard, and language property parameters can be specified on this
621 property.
623Conformance:
624 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
625 components.
626 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
628Description:
629 This property is used to specify categories or subtypes
630 of the calendar component. The categories are useful in searching
631 for a calendar component of a particular type and category.
632 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
633 more than one category can be specified as a COMMA-separated list
634 of categories.
636Example:
637 Below, we add the categories to an event:
639 .. code-block:: pycon
641 >>> from icalendar import Event
642 >>> event = Event()
643 >>> event.categories = ["Work", "Meeting"]
644 >>> print(event.to_ical())
645 BEGIN:VEVENT
646 CATEGORIES:Work,Meeting
647 END:VEVENT
648 >>> event.categories.append("Lecture")
649 >>> event.categories == ["Work", "Meeting", "Lecture"]
650 True
652.. note::
654 At present, we do not take the LANGUAGE parameter into account.
656.. seealso::
658 :attr:`Component.concepts`
659""",
660)
663def _get_attendees(self: Component) -> list[vCalAddress]:
664 """Get attendees."""
665 value = self.get("ATTENDEE")
666 if value is None:
667 value = []
668 self["ATTENDEE"] = value
669 return value
670 if isinstance(value, vCalAddress):
671 return [value]
672 return value
675def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
676 """Set attendees."""
677 _del_attendees(self)
678 if value is None:
679 return
680 if not isinstance(value, list):
681 value = [value]
682 self["ATTENDEE"] = value
685def _del_attendees(self: Component):
686 """Delete all attendees."""
687 self.pop("ATTENDEE", None)
690attendees_property = property(
691 _get_attendees,
692 _set_attendees,
693 _del_attendees,
694 """ATTENDEE defines one or more "Attendees" within a calendar component.
696Conformance:
697 This property MUST be specified in an iCalendar object
698 that specifies a group-scheduled calendar entity. This property
699 MUST NOT be specified in an iCalendar object when publishing the
700 calendar information (e.g., NOT in an iCalendar object that
701 specifies the publication of a calendar user's busy time, event,
702 to-do, or journal). This property is not specified in an
703 iCalendar object that specifies only a time zone definition or
704 that defines calendar components that are not group-scheduled
705 components, but are components only on a single user's calendar.
707Description:
708 This property MUST only be specified within calendar
709 components to specify participants, non-participants, and the
710 chair of a group-scheduled calendar entity. The property is
711 specified within an "EMAIL" category of the "VALARM" calendar
712 component to specify an email address that is to receive the email
713 type of iCalendar alarm.
715Examples:
716 Add a new attendee to an existing event.
718 .. code-block:: pycon
720 >>> from icalendar import Event, vCalAddress
721 >>> event = Event()
722 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
723 >>> print(event.to_ical())
724 BEGIN:VEVENT
725 ATTENDEE:mailto:me@my-domain.com
726 END:VEVENT
728 Create an email alarm with several attendees:
730 >>> from icalendar import Alarm, vCalAddress
731 >>> alarm = Alarm.new(attendees = [
732 ... vCalAddress("mailto:me@my-domain.com"),
733 ... vCalAddress("mailto:you@my-domain.com"),
734 ... ], summary = "Email alarm")
735 >>> print(alarm.to_ical())
736 BEGIN:VALARM
737 ATTENDEE:mailto:me@my-domain.com
738 ATTENDEE:mailto:you@my-domain.com
739 SUMMARY:Email alarm
740 END:VALARM
741""",
742)
744uid_property = single_string_property(
745 "UID",
746 """UID specifies the persistent, globally unique identifier for a component.
748We recommend using :func:`uuid.uuid4` to generate new values.
750Returns:
751 The value of the UID property as a string or ``""`` if no value is set.
753Description:
754 The "UID" itself MUST be a globally unique identifier.
755 The generator of the identifier MUST guarantee that the identifier
756 is unique.
758 This is the method for correlating scheduling messages with the
759 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
760 The full range of calendar components specified by a recurrence
761 set is referenced by referring to just the "UID" property value
762 corresponding to the calendar component. The "RECURRENCE-ID"
763 property allows the reference to an individual instance within the
764 recurrence set.
766 This property is an important method for group-scheduling
767 applications to match requests with later replies, modifications,
768 or deletion requests. Calendaring and scheduling applications
769 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
770 calendar components to assure interoperability with other group-
771 scheduling applications. This identifier is created by the
772 calendar system that generates an iCalendar object.
774 Implementations MUST be able to receive and persist values of at
775 least 255 octets for this property, but they MUST NOT truncate
776 values in the middle of a UTF-8 multi-octet sequence.
778 :rfc:`7986` states that UID can be used, for
779 example, to identify duplicate calendar streams that a client may
780 have been given access to. It can be used in conjunction with the
781 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
782 to identify the most recent version of a calendar.
784Conformance:
785 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
786 and "VJOURNAL" calendar components.
787 :rfc:`7986` modifies the definition of the "UID" property to
788 allow it to be defined in an iCalendar object.
789 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
790 identifier to be specified. The value of this property can then be used
791 to refer uniquely to the "VALARM" component.
793 This property can be specified once only.
795Security:
796 :rfc:`7986` states that UID values MUST NOT include any data that
797 might identify a user, host, domain, or any other security- or
798 privacy-sensitive information. It is RECOMMENDED that calendar user
799 agents now generate "UID" values that are hex-encoded random
800 Universally Unique Identifier (UUID) values as defined in
801 Sections 4.4 and 4.5 of :rfc:`4122`.
802 You can use the :mod:`uuid` module to generate new UUIDs.
804Compatibility:
805 For Alarms, ``X-ALARMUID`` is also considered.
807Examples:
808 The following is an example of such a property value:
809 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
811 Set the UID of a calendar:
813 .. code-block:: pycon
815 >>> from icalendar import Calendar
816 >>> from uuid import uuid4
817 >>> calendar = Calendar()
818 >>> calendar.uid = uuid4()
819 >>> print(calendar.to_ical())
820 BEGIN:VCALENDAR
821 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
822 END:VCALENDAR
824""",
825)
827summary_property = multi_language_text_property(
828 "SUMMARY",
829 None,
830 """SUMMARY defines a short summary or subject for the calendar component.
832Property Parameters:
833 IANA, non-standard, alternate text
834 representation, and language property parameters can be specified
835 on this property.
837Conformance:
838 The property can be specified in "VEVENT", "VTODO",
839 "VJOURNAL", or "VALARM" calendar components.
841Description:
842 This property is used in the "VEVENT", "VTODO", and
843 "VJOURNAL" calendar components to capture a short, one-line
844 summary about the activity or journal entry.
846 This property is used in the "VALARM" calendar component to
847 capture the subject of an EMAIL category of alarm.
849Examples:
850 The following is an example of this property:
852 .. code-block:: pycon
854 SUMMARY:Department Party
855""",
856)
858description_property = multi_language_text_property(
859 "DESCRIPTION",
860 None,
861 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
863Property Parameters:
864 IANA, non-standard, alternate text
865 representation, and language property parameters can be specified
866 on this property.
868Conformance:
869 The property can be specified in the "VEVENT", "VTODO",
870 "VJOURNAL", or "VALARM" calendar components. The property can be
871 specified multiple times only within a "VJOURNAL" calendar
872 component.
874Description:
875 This property is used in the "VEVENT" and "VTODO" to
876 capture lengthy textual descriptions associated with the activity.
878 This property is used in the "VALARM" calendar component to
879 capture the display text for a DISPLAY category of alarm, and to
880 capture the body text for an EMAIL category of alarm.
882Examples:
883 The following is an example of this property with formatted
884 line breaks in the property value:
886 .. code-block:: pycon
888 DESCRIPTION:Meeting to provide technical review for "Phoenix"
889 design.\\nHappy Face Conference Room. Phoenix design team
890 MUST attend this meeting.\\nRSVP to team leader.
892 """, # noqa: E501
893)
896def create_single_property(
897 prop: str,
898 value_attr: str | None,
899 value_type: tuple[type],
900 type_def: type,
901 doc: str,
902 vProp: type = vDDDTypes, # noqa: N803
903 convert: Callable[[object], object] | None = None,
904):
905 """Create a single property getter and setter.
907 Parameters:
908 prop: The name of the property.
909 value_attr: The name of the attribute to get the value from.
910 value_type: The type of the value.
911 type_def: The type of the property.
912 doc: The docstring of the property.
913 vProp: The type of the property from :mod:`icalendar.prop`.
914 """
916 def p_get(self: Component):
917 default = object()
918 result = self.get(prop, default)
919 if result is default:
920 return None
921 if isinstance(result, list):
922 raise InvalidCalendar(f"Multiple {prop} defined.")
923 value = result if value_attr is None else getattr(result, value_attr, result)
924 value = value if convert is None else convert(value)
925 if not isinstance(value, value_type):
926 raise InvalidCalendar(
927 f"{prop} must be either a "
928 f"{' or '.join(t.__name__ for t in value_type)},"
929 f" not {value}."
930 )
931 return value
933 def p_set(self: Component, value) -> None:
934 if value is None:
935 p_del(self)
936 return
937 value = convert(value) if convert is not None else value
938 if not isinstance(value, value_type):
939 raise TypeError(
940 f"Use {' or '.join(t.__name__ for t in value_type)}, "
941 f"not {type(value).__name__}."
942 )
943 self[prop] = vProp(value)
944 if prop in self.exclusive:
945 for other_prop in self.exclusive:
946 if other_prop != prop:
947 self.pop(other_prop, None)
949 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
951 def p_del(self: Component):
952 self.pop(prop)
954 p_doc = f"""The {prop} property.
956 {doc}
958 Accepted values: {", ".join(t.__name__ for t in value_type)}.
959 If the attribute has invalid values, we raise InvalidCalendar.
960 If the value is absent, we return None.
961 You can also delete the value with del or by setting it to None.
962 """
963 return property(p_get, p_set, p_del, p_doc)
966X_MOZ_SNOOZE_TIME_property = single_utc_property(
967 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
968)
969X_MOZ_LASTACK_property = single_utc_property(
970 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
971)
974def property_get_duration(self: Component) -> timedelta | None:
975 """Getter for property DURATION."""
976 default = object()
977 duration = self.get("duration", default)
978 if isinstance(duration, vDDDTypes):
979 return duration.dt
980 if isinstance(duration, vDuration):
981 return duration.td
982 if duration is not default and not isinstance(duration, timedelta):
983 raise InvalidCalendar(
984 f"DURATION must be a timedelta, not {type(duration).__name__}."
985 )
986 return None
989def property_set_duration(self: Component, value: timedelta | None):
990 """Setter for property DURATION."""
991 if value is None:
992 self.pop("duration", None)
993 return
994 if not isinstance(value, timedelta):
995 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
996 self["duration"] = vDuration(value)
997 self.pop("DTEND")
998 self.pop("DUE")
1001def property_del_duration(self: Component):
1002 """Delete property DURATION."""
1003 self.pop("DURATION")
1006property_doc_duration_template = """The DURATION property.
1008The "DTSTART" property for a "{component}" specifies the inclusive
1009start of the {component}.
1010The "DURATION" property in conjunction with the DTSTART property
1011for a "{component}" calendar component specifies the non-inclusive end
1012of the event.
1014If you would like to calculate the duration of a {component}, do not use this.
1015Instead use the duration property (lower case).
1016"""
1019def duration_property(component: str) -> property:
1020 """Return the duration property."""
1021 return property(
1022 property_get_duration,
1023 property_set_duration,
1024 property_del_duration,
1025 property_doc_duration_template.format(component=component),
1026 )
1029def multi_text_property(name: str, docs: str) -> property:
1030 """Get a property that can occur several times and is text.
1032 Examples: Journal.descriptions, Event.comments
1033 """
1035 def fget(self: Component) -> list[str]:
1036 """Get the values."""
1037 descriptions = self.get(name)
1038 if descriptions is None:
1039 return []
1040 if not isinstance(descriptions, SEQUENCE_TYPES):
1041 return [descriptions]
1042 return descriptions
1044 def fset(self: Component, values: str | Sequence[str] | None):
1045 """Set the values."""
1046 fdel(self)
1047 if values is None:
1048 return
1049 if isinstance(values, str):
1050 self.add(name, values)
1051 else:
1052 for description in values:
1053 self.add(name, description)
1055 def fdel(self: Component):
1056 """Delete the values."""
1057 self.pop(name)
1059 return property(fget, fset, fdel, docs)
1062descriptions_property = multi_text_property(
1063 "DESCRIPTION",
1064 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1066Property Parameters:
1067 IANA, non-standard, alternate text
1068 representation, and language property parameters can be specified
1069 on this property.
1071Conformance:
1072 The property can be
1073 specified multiple times only within a "VJOURNAL" calendar component.
1075Description:
1076 This property is used in the "VJOURNAL" calendar component to
1077 capture one or more textual journal entries.
1079Examples:
1080 The following is an example of this property with formatted
1081 line breaks in the property value:
1083 .. code-block:: pycon
1085 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1086 design.\\nHappy Face Conference Room. Phoenix design team
1087 MUST attend this meeting.\\nRSVP to team leader.
1089""", # noqa: E501
1090)
1092comments_property = multi_text_property(
1093 "COMMENT",
1094 """COMMENT is used to specify a comment to the calendar user.
1096Purpose:
1097 This property specifies non-processing information intended
1098 to provide a comment to the calendar user.
1100Conformance:
1101 In :rfc:`5545`, this property can be specified multiple times in
1102 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1103 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1104 In :rfc:`7953`, this property can be specified multiple times in
1105 "VAVAILABILITY" and "VAVAILABLE".
1107Property Parameters:
1108 IANA, non-standard, alternate text
1109 representation, and language property parameters can be specified
1110 on this property.
1112""",
1113)
1115RECURRENCE_ID = create_single_property(
1116 "RECURRENCE-ID",
1117 "dt",
1118 (date, datetime),
1119 date | datetime,
1120 """
1121Identify a specific occurrence of a recurring calendar object.
1123This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1124particular instance in a recurrence set. The value is the original start
1125date or date-time of that instance, not the rescheduled time.
1127The value is usually a DATE-TIME and must use the same value type as the
1128``DTSTART`` property in the same component. A DATE value may be used for
1129all-day items instead.
1131This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1132may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1133""",
1134 vDDDTypes,
1135)
1138def _get_organizer(self: Component) -> vCalAddress | None:
1139 """ORGANIZER defines the organizer for a calendar component.
1141 Property Parameters:
1142 IANA, non-standard, language, common name,
1143 directory entry reference, and sent-by property parameters can be
1144 specified on this property.
1146 Conformance:
1147 This property MUST be specified in an iCalendar object
1148 that specifies a group-scheduled calendar entity. This property
1149 MUST be specified in an iCalendar object that specifies the
1150 publication of a calendar user's busy time. This property MUST
1151 NOT be specified in an iCalendar object that specifies only a time
1152 zone definition or that defines calendar components that are not
1153 group-scheduled components, but are components only on a single
1154 user's calendar.
1156 Description:
1157 This property is specified within the "VEVENT",
1158 "VTODO", and "VJOURNAL" calendar components to specify the
1159 organizer of a group-scheduled calendar entity. The property is
1160 specified within the "VFREEBUSY" calendar component to specify the
1161 calendar user requesting the free or busy time. When publishing a
1162 "VFREEBUSY" calendar component, the property is used to specify
1163 the calendar that the published busy time came from.
1165 The property has the property parameters "CN", for specifying the
1166 common or display name associated with the "Organizer", "DIR", for
1167 specifying a pointer to the directory information associated with
1168 the "Organizer", "SENT-BY", for specifying another calendar user
1169 that is acting on behalf of the "Organizer". The non-standard
1170 parameters may also be specified on this property. If the
1171 "LANGUAGE" property parameter is specified, the identified
1172 language applies to the "CN" parameter value.
1173 """
1174 return self.get("ORGANIZER")
1177def _set_organizer(self: Component, value: vCalAddress | str | None):
1178 """Set the value."""
1179 _del_organizer(self)
1180 if value is not None:
1181 self.add("ORGANIZER", value)
1184def _del_organizer(self: Component):
1185 """Delete the value."""
1186 self.pop("ORGANIZER")
1189organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1192def single_string_enum_property(
1193 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1194) -> property:
1195 """Create a property to access a single string value and convert it to an enum."""
1196 prop = single_string_property(name, docs, default=default)
1198 def fget(self: Component) -> StrEnum:
1199 """Get the value."""
1200 value = prop.fget(self)
1201 if value == default:
1202 return default
1203 return enum(str(value))
1205 def fset(self: Component, value: str | StrEnum | None) -> None:
1206 """Set the value."""
1207 if value == "":
1208 value = None
1209 prop.fset(self, value)
1211 return property(fget, fset, prop.fdel, doc=docs)
1214busy_type_property = single_string_enum_property(
1215 "BUSYTYPE",
1216 BUSYTYPE,
1217 BUSYTYPE.BUSY_UNAVAILABLE,
1218 """BUSYTYPE specifies the default busy time type.
1220Returns:
1221 :class:`icalendar.enums.BUSYTYPE`
1223Description:
1224 This property is used to specify the default busy time
1225 type. The values correspond to those used by the FBTYPE"
1226 parameter used on a "FREEBUSY" property, with the exception that
1227 the "FREE" value is not used in this property. If not specified
1228 on a component that allows this property, the default is "BUSY-
1229 UNAVAILABLE".
1230""",
1231)
1233priority_property = single_int_property(
1234 "PRIORITY",
1235 0,
1236 """
1238Conformance:
1239 This property can be specified in "VEVENT" and "VTODO" calendar components
1240 according to :rfc:`5545`.
1241 :rfc:`7953` adds this property to "VAVAILABILITY".
1243Description:
1244 This priority is specified as an integer in the range 0
1245 to 9. A value of 0 specifies an undefined priority. A value of 1
1246 is the highest priority. A value of 2 is the second highest
1247 priority. Subsequent numbers specify a decreasing ordinal
1248 priority. A value of 9 is the lowest priority.
1250 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1251 "LOW" is mapped into this property such that a property value in
1252 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1253 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1254 is "LOW" priority.
1256 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1257 "C3" is mapped into this property such that a property value of 1
1258 specifies "A1", a property value of 2 specifies "A2", a property
1259 value of 3 specifies "A3", and so forth up to a property value of
1260 9 specifies "C3".
1262 Other integer values are reserved for future use.
1264 Within a "VEVENT" calendar component, this property specifies a
1265 priority for the event. This property may be useful when more
1266 than one event is scheduled for a given time period.
1268 Within a "VTODO" calendar component, this property specified a
1269 priority for the to-do. This property is useful in prioritizing
1270 multiple action items for a given time period.
1271""",
1272)
1274class_property = single_string_enum_property(
1275 "CLASS",
1276 CLASS,
1277 CLASS.PUBLIC,
1278 """CLASS specifies the class of the calendar component.
1280Returns:
1281 :class:`icalendar.enums.CLASS`
1283Description:
1284 An access classification is only one component of the
1285 general security system within a calendar application. It
1286 provides a method of capturing the scope of the access the
1287 calendar owner intends for information within an individual
1288 calendar entry. The access classification of an individual
1289 iCalendar component is useful when measured along with the other
1290 security components of a calendar system (e.g., calendar user
1291 authentication, authorization, access rights, access role, etc.).
1292 Hence, the semantics of the individual access classifications
1293 cannot be completely defined by this memo alone. Additionally,
1294 due to the "blind" nature of most exchange processes using this
1295 memo, these access classifications cannot serve as an enforcement
1296 statement for a system receiving an iCalendar object. Rather,
1297 they provide a method for capturing the intention of the calendar
1298 owner for the access to the calendar component. If not specified
1299 in a component that allows this property, the default value is
1300 PUBLIC. Applications MUST treat x-name and iana-token values they
1301 don't recognize the same way as they would the PRIVATE value.
1302""",
1303)
1305transparency_property = single_string_enum_property(
1306 "TRANSP",
1307 TRANSP,
1308 TRANSP.OPAQUE,
1309 """TRANSP defines whether or not an event is transparent to busy time searches.
1311Returns:
1312 :class:`icalendar.enums.TRANSP`
1314Description:
1315 Time Transparency is the characteristic of an event
1316 that determines whether it appears to consume time on a calendar.
1317 Events that consume actual time for the individual or resource
1318 associated with the calendar SHOULD be recorded as OPAQUE,
1319 allowing them to be detected by free/busy time searches. Other
1320 events, which do not take up the individual's (or resource's) time
1321 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1322 busy time searches.
1323""",
1324)
1325status_property = single_string_enum_property(
1326 "STATUS",
1327 STATUS,
1328 "",
1329 """STATUS defines the overall status or confirmation for the calendar component.
1331Returns:
1332 :class:`icalendar.enums.STATUS`
1334The default value is ``""``.
1336Description:
1337 In a group-scheduled calendar component, the property
1338 is used by the "Organizer" to provide a confirmation of the event
1339 to the "Attendees". For example in a "VEVENT" calendar component,
1340 the "Organizer" can indicate that a meeting is tentative,
1341 confirmed, or cancelled. In a "VTODO" calendar component, the
1342 "Organizer" can indicate that an action item needs action, is
1343 completed, is in process or being worked on, or has been
1344 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1345 can indicate that a journal entry is draft, final, or has been
1346 cancelled or removed.
1347""",
1348)
1350url_property = single_string_property(
1351 "URL",
1352 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1354Description:
1355 This property may be used in a calendar component to
1356 convey a location where a more dynamic rendition of the calendar
1357 information associated with the calendar component can be found.
1358 This memo does not attempt to standardize the form of the URI, nor
1359 the format of the resource pointed to by the property value. If
1360 the URL property and Content-Location MIME header are both
1361 specified, they MUST point to the same resource.
1363Conformance:
1364 This property can be specified once in the "VEVENT",
1365 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1366 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1368Example:
1369 The following is an example of this property:
1371 .. code-block:: ics
1373 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1375""",
1376)
1378source_property = single_string_property(
1379 "SOURCE",
1380 """A URI from where calendar data can be refreshed.
1382Description:
1383 This property identifies a location where a client can
1384 retrieve updated data for the calendar. Clients SHOULD honor any
1385 specified "REFRESH-INTERVAL" value when periodically retrieving
1386 data. Note that this property differs from the "URL" property in
1387 that "URL" is meant to provide an alternative representation of
1388 the calendar data rather than the original location of the data.
1390Conformance:
1391 This property can be specified once in an iCalendar object.
1393Example:
1394 The following is an example of this property:
1396 .. code-block:: ics
1398 SOURCE;VALUE=URI:https://example.com/holidays.ics
1400""",
1401)
1403location_property = multi_language_text_property(
1404 "LOCATION",
1405 None,
1406 """The intended venue for the activity defined by a calendar component.
1408Property Parameters:
1409 IANA, non-standard, alternate text
1410 representation, and language property parameters can be specified
1411 on this property.
1413Conformance:
1414 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1415 calendar component.
1416 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1418Description:
1419 Specific venues such as conference or meeting rooms may
1420 be explicitly specified using this property. An alternate
1421 representation may be specified that is a URI that points to
1422 directory information with more structured specification of the
1423 location. For example, the alternate representation may specify
1424 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1425 CID URL :rfc:`2392` pointing to a MIME body part containing a
1426 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1428""",
1429)
1431contacts_property = multi_text_property(
1432 "CONTACT",
1433 """Contact information associated with the calendar component.
1435Purpose:
1436 This property is used to represent contact information or
1437 alternately a reference to contact information associated with the
1438 calendar component.
1440Property Parameters:
1441 IANA, non-standard, alternate text
1442 representation, and language property parameters can be specified
1443 on this property.
1445Conformance:
1446 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1447 "VJOURNAL", or "VFREEBUSY" calendar component.
1448 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1449 amd "VAVAILABLE" calendar component.
1451Description:
1452 The property value consists of textual contact
1453 information. An alternative representation for the property value
1454 can also be specified that refers to a URI pointing to an
1455 alternate form, such as a vCard :rfc:`2426`, for the contact
1456 information.
1458Example:
1459 The following is an example of this property referencing
1460 textual contact information:
1462 .. code-block:: ics
1464 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1466 The following is an example of this property with an alternate
1467 representation of an LDAP URI to a directory entry containing the
1468 contact information:
1470 .. code-block:: ics
1472 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1473 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1474 +1-919-555-1234
1476 The following is an example of this property with an alternate
1477 representation of a MIME body part containing the contact
1478 information, such as a vCard :rfc:`2426` embedded in a text/
1479 directory media type :rfc:`2425`:
1481 .. code-block:: ics
1483 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1484 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1486 The following is an example of this property referencing a network
1487 resource, such as a vCard :rfc:`2426` object containing the contact
1488 information:
1490 .. code-block:: ics
1492 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1493 Dolittle\\, ABC Industries\\, +1-919-555-1234
1494""",
1495)
1498def timezone_datetime_property(name: str, docs: str):
1499 """Create a property to access the values with a proper timezone."""
1501 return single_utc_property(name, docs)
1504rfc_7953_dtstart_property = timezone_datetime_property(
1505 "DTSTART",
1506 """Start of the component.
1508 This is almost the same as :attr:`Event.DTSTART` with one exception:
1509 The values MUST have a timezone and DATE is not allowed.
1511 Description:
1512 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1513 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1514 "DATE-TIME" values specified as either the date with UTC time or
1515 the date with local time and a time zone reference.
1517 """,
1518)
1520rfc_7953_dtend_property = timezone_datetime_property(
1521 "DTEND",
1522 """Start of the component.
1524 This is almost the same as :attr:`Event.DTEND` with one exception:
1525 The values MUST have a timezone and DATE is not allowed.
1527 Description:
1528 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1529 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1530 "DATE-TIME" values specified as either the date with UTC time or
1531 the date with local time and a time zone reference.
1532 """,
1533)
1536@property
1537def rfc_7953_duration_property(self) -> timedelta | None:
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 return duration
1550 end = self.DTEND
1551 if end is None:
1552 return None
1553 start = self.DTSTART
1554 if start is None:
1555 raise IncompleteComponent("Cannot compute duration without start.")
1556 return end - start
1559@property
1560def rfc_7953_end_property(self) -> timedelta | None:
1561 """Compute the duration of this component.
1563 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1564 Otherwise, the duration is calculated from :attr:`DTSTART` and
1565 :attr:`DTEND`/:attr:`DURATION`.
1567 This is in accordance with :rfc:`7953`:
1568 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1569 """
1570 duration = self.DURATION
1571 if duration:
1572 start = self.DTSTART
1573 if start is None:
1574 raise IncompleteComponent("Cannot compute end without start.")
1575 return start + duration
1576 end = self.DTEND
1577 if end is None:
1578 return None
1579 return end
1582@rfc_7953_end_property.setter
1583def rfc_7953_end_property(self, value: datetime):
1584 self.DTEND = value
1587@rfc_7953_end_property.deleter
1588def rfc_7953_end_property(self):
1589 del self.DTEND
1592def get_start_end_duration_with_validation(
1593 component: Component,
1594 start_property: str,
1595 end_property: str,
1596 component_name: str,
1597) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1598 """
1599 Validate the component and return start, end, and duration.
1601 This tests validity according to :rfc:`5545` rules
1602 for ``Event`` and ``Todo`` components.
1604 Parameters:
1605 component: The component to validate, either ``Event`` or ``Todo``.
1606 start_property: The start property name, ``DTSTART``.
1607 end_property: The end property name, either ``DTEND`` for ``Event`` or
1608 ``DUE`` for ``Todo``.
1609 component_name: The component name for error messages,
1610 either ``VEVENT`` or ``VTODO``.
1612 Returns:
1613 tuple: (start, end, duration) values from the component.
1615 Raises:
1616 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1618 """
1619 start = getattr(component, start_property, None)
1620 end = getattr(component, end_property, None)
1621 duration = component.DURATION
1623 # RFC 5545: Only one of end property and DURATION may be present
1624 if duration is not None and end is not None:
1625 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1626 msg = (
1627 f"Only one of {end_name} and DURATION "
1628 f"may be in a {component_name}, not both."
1629 )
1630 raise InvalidCalendar(msg)
1632 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1633 if (
1634 start is not None
1635 and is_date(start)
1636 and duration is not None
1637 and duration.seconds != 0
1638 ):
1639 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1640 raise InvalidCalendar(msg)
1642 # RFC 5545: DTSTART and end property must be of the same type
1643 if start is not None and end is not None and is_date(start) != is_date(end):
1644 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1645 msg = (
1646 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1647 )
1648 raise InvalidCalendar(msg)
1650 return start, end, duration
1653def get_start_property(component: Component) -> date | datetime:
1654 """
1655 Get the start property with validation.
1657 Parameters:
1658 component: The component from which to get its start property.
1660 Returns:
1661 The ``DTSTART`` value.
1663 Raises:
1664 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1666 """
1667 # Trigger validation by calling _get_start_end_duration
1668 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1669 if start is None:
1670 msg = "No DTSTART given."
1671 raise IncompleteComponent(msg)
1672 return start
1675def get_end_property(component: Component, end_property: str) -> date | datetime:
1676 """
1677 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1679 Parameters:
1680 component: The component to get end from
1681 end_property: The end property name, either ``DTEND`` for ``Event`` or
1682 ``DUE`` for ``Todo``.
1684 Returns:
1685 The computed end value.
1687 Raises:
1688 ~error.IncompleteComponent: If the provided information is incomplete
1689 to compute the end property.
1691 """
1692 # Trigger validation by calling _get_start_end_duration
1693 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1695 if end is None and duration is None:
1696 if start is None:
1697 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1698 msg = f"No {end_name} or DURATION+DTSTART given."
1699 raise IncompleteComponent(msg)
1701 # Default behavior differs for Event vs Todo:
1702 # Event: date gets +1 day, datetime gets same time
1703 # Todo: both date and datetime get same time (issue #898)
1704 if end_property == "DTEND" and is_date(start):
1705 return start + timedelta(days=1)
1706 return start
1708 if duration is not None:
1709 if start is not None:
1710 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1711 return start
1712 return start + duration
1713 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1714 msg = f"No {end_name} or DURATION+DTSTART given."
1715 raise IncompleteComponent(msg)
1717 return end
1720def get_duration_property(component: Component) -> timedelta:
1721 """
1722 Get the duration property with fallback calculation from start and end.
1724 Parameters:
1725 component: The component from which to get its duration property.
1727 Returns:
1728 The duration as a timedelta.
1730 """
1731 # First check if DURATION property is explicitly set
1732 if "DURATION" in component:
1733 return component["DURATION"].dt
1735 # Fall back to calculated duration from start and end
1736 return component.end - component.start
1739def set_duration_with_locking(
1740 component: Component,
1741 duration: timedelta | None,
1742 locked: Literal["start", "end"],
1743 end_property: str,
1744) -> None:
1745 """
1746 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1748 Parameters:
1749 component: The component to modify, either ``Event`` or ``Todo``.
1750 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1751 locked: Which property to keep unchanged, either ``start`` or ``end``.
1752 end_property: The end property name, either ``DTEND`` for ``Event`` or
1753 ``DUE`` for ``Todo``.
1755 """
1756 # Convert to DURATION property if duration is None
1757 if duration is None:
1758 if "DURATION" in component:
1759 return # Already has DURATION property
1760 current_duration = component.duration
1761 component.DURATION = current_duration
1762 return
1764 if not isinstance(duration, timedelta):
1765 msg = f"Use timedelta, not {type(duration).__name__}."
1766 raise TypeError(msg)
1768 # Validate date/duration compatibility
1769 start = component.DTSTART
1770 if start is not None and is_date(start) and duration.seconds != 0:
1771 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1772 raise InvalidCalendar(msg)
1774 if locked == "start":
1775 # Keep start locked, adjust end
1776 if start is None:
1777 msg = "Cannot set duration without DTSTART. Set start time first."
1778 raise IncompleteComponent(msg)
1779 component.pop(end_property, None) # Remove end property
1780 component.DURATION = duration
1781 elif locked == "end":
1782 # Keep end locked, adjust start
1783 current_end = component.end
1784 component.DTSTART = current_end - duration
1785 component.pop(end_property, None) # Remove end property
1786 component.DURATION = duration
1787 else:
1788 msg = f"locked must be 'start' or 'end', not {locked!r}"
1789 raise ValueError(msg)
1792def set_start_with_locking(
1793 component: Component,
1794 start: date | datetime,
1795 locked: Literal["duration", "end"] | None,
1796 end_property: str,
1797) -> None:
1798 """
1799 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1801 Parameters:
1802 component: The component to modify, either ``Event`` or ``Todo``.
1803 start: The start time to set.
1804 locked: Which property to keep unchanged, either ``duration``, ``end``,
1805 or ``None`` for auto-detect.
1806 end_property: The end property name, either ``DTEND`` for ``Event`` or
1807 ``DUE`` for ``Todo``.
1809 """
1810 if locked is None:
1811 # Auto-detect based on existing properties
1812 if "DURATION" in component:
1813 locked = "duration"
1814 elif end_property in component:
1815 locked = "end"
1816 else:
1817 # Default to duration if no existing properties
1818 locked = "duration"
1820 if locked == "duration":
1821 # Keep duration locked, adjust end
1822 current_duration = (
1823 component.duration
1824 if "DURATION" in component or end_property in component
1825 else None
1826 )
1827 component.DTSTART = start
1828 if current_duration is not None:
1829 component.pop(end_property, None) # Remove end property
1830 component.DURATION = current_duration
1831 elif locked == "end":
1832 # Keep end locked, adjust duration
1833 current_end = component.end
1834 component.DTSTART = start
1835 component.pop("DURATION", None) # Remove duration property
1836 setattr(component, end_property, current_end)
1837 else:
1838 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1839 raise ValueError(msg)
1842def set_end_with_locking(
1843 component: Component,
1844 end: date | datetime,
1845 locked: Literal["start", "duration"],
1846 end_property: str,
1847) -> None:
1848 """
1849 Set the end with explicit locking behavior for Event and Todo components.
1851 Parameters:
1852 component: The component to modify, either ``Event`` or ``Todo``.
1853 end: The end time to set.
1854 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1855 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1856 for ``Todo``.
1858 """
1859 if locked == "start":
1860 # Keep start locked, adjust duration
1861 component.pop("DURATION", None) # Remove duration property
1862 setattr(component, end_property, end)
1863 elif locked == "duration":
1864 # Keep duration locked, adjust start
1865 current_duration = component.duration
1866 component.DTSTART = end - current_duration
1867 component.pop(end_property, None) # Remove end property
1868 component.DURATION = current_duration
1869 else:
1870 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1871 raise ValueError(msg)
1874def _get_images(self: Component) -> list[Image]:
1875 """IMAGE specifies an image associated with the calendar or a calendar component.
1877 Description:
1878 This property specifies an image for an iCalendar
1879 object or a calendar component via a URI or directly with inline
1880 data that can be used by calendar user agents when presenting the
1881 calendar data to a user. Multiple properties MAY be used to
1882 specify alternative sets of images with, for example, varying
1883 media subtypes, resolutions, or sizes. When multiple properties
1884 are present, calendar user agents SHOULD display only one of them,
1885 picking one that provides the most appropriate image quality, or
1886 display none. The "DISPLAY" parameter is used to indicate the
1887 intended display mode for the image. The "ALTREP" parameter,
1888 defined in :rfc:`5545`, can be used to provide a "clickable" image
1889 where the URI in the parameter value can be "launched" by a click
1890 on the image in the calendar user agent.
1892 Conformance:
1893 This property can be specified multiple times in an
1894 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1895 components.
1897 .. note::
1899 At the present moment, this property is read-only. If you require a setter,
1900 please open an issue or a pull request.
1901 """
1902 images = self.get("IMAGE", [])
1903 if not isinstance(images, SEQUENCE_TYPES):
1904 images = [images]
1905 return [Image.from_property_value(img) for img in images]
1908images_property = property(_get_images)
1911def _get_conferences(self: Component) -> list[Conference]:
1912 """Return the CONFERENCE properties as a list.
1914 Purpose:
1915 This property specifies information for accessing a conferencing system.
1917 Conformance:
1918 This property can be specified multiple times in a
1919 "VEVENT" or "VTODO" calendar component.
1921 Description:
1922 This property specifies information for accessing a
1923 conferencing system for attendees of a meeting or task. This
1924 might be for a telephone-based conference number dial-in with
1925 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1926 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1927 or https: URI :rfc:`7230`), or for an instant messaging group chat
1928 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1929 conferencing system is not available, a data: URI :rfc:`2397`
1930 containing a text description can be used.
1932 A conference system can be a bidirectional communication channel
1933 or a uni-directional "broadcast feed".
1935 The "FEATURE" property parameter is used to describe the key
1936 capabilities of the conference system to allow a client to choose
1937 the ones that give the required level of interaction from a set of
1938 multiple properties.
1940 The "LABEL" property parameter is used to convey additional
1941 details on the use of the URI. For example, the URIs or access
1942 codes for the moderator and attendee of a teleconference system
1943 could be different, and the "LABEL" property parameter could be
1944 used to "tag" each "CONFERENCE" property to indicate which is
1945 which.
1947 The "LANGUAGE" property parameter can be used to specify the
1948 language used for text values used with this property (as per
1949 Section 3.2.10 of :rfc:`5545`).
1951 Example:
1952 The following are examples of this property:
1954 .. code-block:: ics
1956 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1957 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1958 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1959 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1960 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1961 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1962 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1963 LABEL=Chat room:xmpp:chat-123@conference.example.com
1964 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1965 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1967 Get all conferences:
1969 .. code-block:: pycon
1971 >>> from icalendar import Event
1972 >>> event = Event()
1973 >>> event.conferences
1974 []
1976 Set a conference:
1978 .. code-block:: pycon
1980 >>> from icalendar import Event, Conference
1981 >>> event = Event()
1982 >>> event.conferences = [
1983 ... Conference(
1984 ... "tel:+1-412-555-0123,,,654321",
1985 ... feature="PHONE,MODERATOR",
1986 ... label="Moderator dial-in",
1987 ... language="EN",
1988 ... )
1989 ... ]
1990 >>> print(event.to_ical())
1991 BEGIN:VEVENT
1992 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
1993 ALUE=URI:tel:+1-412-555-0123,,,654321
1994 END:VEVENT
1996 """
1997 conferences = self.get("CONFERENCE", [])
1998 if not isinstance(conferences, SEQUENCE_TYPES):
1999 conferences = [conferences]
2000 return [Conference.from_uri(conference) for conference in conferences]
2003def _set_conferences(self: Component, conferences: list[Conference] | None):
2004 """Set the conferences."""
2005 _del_conferences(self)
2006 for conference in conferences or []:
2007 self.add("CONFERENCE", conference.to_uri())
2010def _del_conferences(self: Component):
2011 """Delete all conferences."""
2012 self.pop("CONFERENCE")
2015conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2018def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2019 """LINK properties as a list.
2021 Purpose:
2022 LINK provides a reference to external information related to a component.
2024 Property Parameters:
2025 The VALUE parameter is required.
2026 Non-standard, link relation type, format type, label, and language parameters
2027 can also be specified on this property.
2028 The LABEL parameter is defined in :rfc:`7986`.
2030 Conformance:
2031 This property can be specified zero or more times in any iCalendar component.
2032 LINK is specified in :rfc:`9253`.
2033 The LINKREL parameter is required.
2035 Description:
2036 When used in a component, the value of this property points to
2037 additional information related to the component.
2038 For example, it may reference the originating web server.
2040 This property is a serialization of the model in :rfc:`8288`,
2041 where the link target is carried in the property value,
2042 the link context is the containing calendar entity,
2043 and the link relation type and any target attributes
2044 are carried in iCalendar property parameters.
2046 The LINK property parameters map to :rfc:`8288` attributes as follows:
2048 LABEL
2049 This parameter maps to the "title"
2050 attribute defined in Section 3.4.1 of :rfc:`8288`.
2051 LABEL is used to label the destination
2052 of a link such that it can be used as a human-readable identifier
2053 (e.g., a menu entry) in the language indicated by the LANGUAGE
2054 (if present).
2055 LANGUAGE
2056 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2057 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2058 LINKREL
2059 This parameter maps to the link relation type defined in Section 2.1 of
2060 :rfc:`8288`. See `Registered Link Relation Types
2061 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2062 FMTTYPE
2063 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2064 :rfc:`8288`.
2066 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2068 Examples:
2069 The following is an example of this property,
2070 which provides a reference to the source for the calendar object.
2072 .. code-block:: ics
2074 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2075 https://example.com/events
2077 The following is an example of this property,
2078 which provides a reference to an entity from which this one was derived.
2079 The link relation is a vendor-defined value.
2081 .. code-block:: ics
2083 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2084 VALUE=URI:
2085 https://example.com/tasks/01234567-abcd1234.ics
2087 The following is an example of this property,
2088 which provides a reference to a fragment of an XML document.
2089 The link relation is a vendor-defined value.
2091 .. code-block:: ics
2093 LINK;LINKREL="https://example.com/linkrel/costStructure";
2094 VALUE=XML-REFERENCE:
2095 https://example.com/xmlDocs/bidFramework.xml
2096 #xpointer(descendant::CostStruc/range-to(
2097 following::CostStrucEND[1]))
2099 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2101 .. code-block:: pycon
2103 >>> from icalendar import Event, vUri
2104 >>> from datetime import datetime
2105 >>> link = vUri(
2106 ... "http://example.com/event-page",
2107 ... params={"LINKREL":"SOURCE"}
2108 ... )
2109 >>> event = Event.new(
2110 ... start=datetime(2025, 9, 17, 12, 0),
2111 ... summary="An Example Event with a page"
2112 ... )
2113 >>> event.links = [link]
2114 >>> print(event.to_ical())
2115 BEGIN:VEVENT
2116 SUMMARY:An Example Event with a page
2117 DTSTART:20250917T120000
2118 DTSTAMP:20250517T080612Z
2119 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2120 LINK;LINKREL="SOURCE":http://example.com/event-page
2121 END:VEVENT
2123 """
2124 links = self.get("LINK", [])
2125 if not isinstance(links, list):
2126 links = [links]
2127 return links
2130LINKS_TYPE_SETTER: TypeAlias = (
2131 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2132)
2135def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2136 """Set the LINKs."""
2137 _del_links(self)
2138 if links is None:
2139 return
2140 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2141 links = [links]
2142 for link in links:
2143 if type(link) is str:
2144 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2145 self.add("LINK", link)
2148def _del_links(self: Component) -> None:
2149 """Delete all links."""
2150 self.pop("LINK")
2153links_property = property(_get_links, _set_links, _del_links)
2155RELATED_TO_TYPE_SETTER: TypeAlias = (
2156 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2157)
2160def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2161 """RELATED-TO properties as a list.
2163 Purpose:
2164 This property is used to represent a relationship or reference
2165 between one calendar component and another.
2166 :rfc:`9523` allows URI or UID values and a GAP parameter.
2168 Value Type:
2169 :rfc:`5545`: TEXT
2170 :rfc:`9253`: URI, UID
2172 Conformance:
2173 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2174 "VTODO", and "VJOURNAL" calendar components.
2175 Since :rfc:`9523`, this property MAY be specified in any
2176 iCalendar component.
2178 Description (:rfc:`5545`):
2179 The property value consists of the persistent, globally
2180 unique identifier of another calendar component. This value would
2181 be represented in a calendar component by the "UID" property.
2183 By default, the property value points to another calendar
2184 component that has a PARENT relationship to the referencing
2185 object. The "RELTYPE" property parameter is used to either
2186 explicitly state the default PARENT relationship type to the
2187 referenced calendar component or to override the default PARENT
2188 relationship type and specify either a CHILD or SIBLING
2189 relationship. The PARENT relationship indicates that the calendar
2190 component is a subordinate of the referenced calendar component.
2191 The CHILD relationship indicates that the calendar component is a
2192 superior of the referenced calendar component. The SIBLING
2193 relationship indicates that the calendar component is a peer of
2194 the referenced calendar component.
2196 Changes to a calendar component referenced by this property can
2197 have an implicit impact on the related calendar component. For
2198 example, if a group event changes its start or end date or time,
2199 then the related, dependent events will need to have their start
2200 and end dates changed in a corresponding way. Similarly, if a
2201 PARENT calendar component is cancelled or deleted, then there is
2202 an implied impact to the related CHILD calendar components. This
2203 property is intended only to provide information on the
2204 relationship of calendar components. It is up to the target
2205 calendar system to maintain any property implications of this
2206 relationship.
2208 Description (:rfc:`9253`):
2209 By default or when VALUE=UID is specified, the property value
2210 consists of the persistent, globally unique identifier of another
2211 calendar component. This value would be represented in a calendar
2212 component by the UID property.
2214 By default, the property value
2215 points to another calendar component that has a PARENT relationship
2216 to the referencing object. The RELTYPE property parameter is used
2217 to either explicitly state the default PARENT relationship type to
2218 the referenced calendar component or to override the default
2219 PARENT relationship type and specify either a CHILD or SIBLING
2220 relationship or a temporal relationship.
2222 The PARENT relationship
2223 indicates that the calendar component is a subordinate of the
2224 referenced calendar component. The CHILD relationship indicates
2225 that the calendar component is a superior of the referenced calendar
2226 component. The SIBLING relationship indicates that the calendar
2227 component is a peer of the referenced calendar component.
2229 To preserve backwards compatibility, the value type MUST
2230 be UID when the PARENT, SIBLING, or CHILD relationships
2231 are specified.
2233 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2234 or STARTTOSTART relationships define temporal relationships, as
2235 specified in the RELTYPE parameter definition.
2237 The FIRST and NEXT
2238 define ordering relationships between calendar components.
2240 The DEPENDS-ON relationship indicates that the current calendar
2241 component depends on the referenced calendar component in some manner.
2242 For example, a task may be blocked waiting on the other,
2243 referenced, task.
2245 The REFID and CONCEPT relationships establish
2246 a reference from the current component to the referenced component.
2247 Changes to a calendar component referenced by this property
2248 can have an implicit impact on the related calendar component.
2249 For example, if a group event changes its start or end date or
2250 time, then the related, dependent events will need to have their
2251 start and end dates and times changed in a corresponding way.
2252 Similarly, if a PARENT calendar component is canceled or deleted,
2253 then there is an implied impact to the related CHILD calendar
2254 components. This property is intended only to provide information
2255 on the relationship of calendar components.
2257 Deletion of the target component, for example, the target of a
2258 FIRST, NEXT, or temporal relationship, can result in broken links.
2260 It is up to the target calendar system to maintain any property
2261 implications of these relationships.
2263 Examples:
2264 :rfc:`5545` examples of this property:
2266 .. code-block:: ics
2268 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2270 .. code-block:: ics
2272 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2274 :rfc:`9253` examples of this property:
2276 .. code-block:: ics
2278 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2279 https://example.com/caldav/user/jb/cal/
2280 19960401-080045-4000F192713.ics
2282 See also :class:`icalendar.enums.RELTYPE`.
2284 """
2285 result = self.get("RELATED-TO", [])
2286 if not isinstance(result, list):
2287 return [result]
2288 return result
2291def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2292 """Set the RELATED-TO properties."""
2293 _del_related_to(self)
2294 if values is None:
2295 return
2296 if not isinstance(values, list):
2297 values = [values]
2298 for value in values:
2299 self.add("RELATED-TO", value)
2302def _del_related_to(self: Component):
2303 """Delete the RELATED-TO properties."""
2304 self.pop("RELATED-TO", None)
2307related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2310def _get_concepts(self: Component) -> list[vUri]:
2311 """CONCEPT
2313 Purpose:
2314 CONCEPT defines the formal categories for a calendar component.
2316 Conformance:
2317 Since :rfc:`9253`,
2318 this property can be specified zero or more times in any iCalendar component.
2320 Description:
2321 This property is used to specify formal categories or classifications of
2322 the calendar component. The values are useful in searching for a calendar
2323 component of a particular type and category.
2325 This categorization is distinct from the more informal "tagging" of components
2326 provided by the existing CATEGORIES property. It is expected that the value of
2327 the CONCEPT property will reference an external resource that provides
2328 information about the categorization.
2330 In addition, a structured URI value allows for hierarchical categorization of
2331 events.
2333 Possible category resources are the various proprietary systems, for example,
2334 the Library of Congress, or an open source of categorization data.
2336 Examples:
2337 The following is an example of this property.
2338 It points to a server acting as the source for the calendar object.
2340 .. code-block:: ics
2342 CONCEPT:https://example.com/event-types/arts/music
2344 .. seealso::
2346 :attr:`icalendar.prop.categories.vCategory`
2347 """
2348 concepts = self.get("CONCEPT", [])
2349 if not isinstance(concepts, list):
2350 concepts = [concepts]
2351 return concepts
2354CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2357def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2358 """Set the concepts."""
2359 _del_concepts(self)
2360 if concepts is None:
2361 return
2362 if not isinstance(concepts, list):
2363 concepts = [concepts]
2364 for value in concepts:
2365 self.add("CONCEPT", value)
2368def _del_concepts(self: Component):
2369 """Delete the concepts."""
2370 self.pop("CONCEPT", None)
2373concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2376def multi_string_property(name: str, doc: str):
2377 """A property for an iCalendar Property that can occur multiple times."""
2379 def fget(self: Component) -> list[str]:
2380 """Get the values of a multi-string property."""
2381 value = self.get(name, [])
2382 if not isinstance(value, list):
2383 value = [value]
2384 return value
2386 def fset(self: Component, value: list[str] | str | None) -> None:
2387 """Set the values of a multi-string property."""
2388 fdel(self)
2389 if value is None:
2390 return
2391 if not isinstance(value, list):
2392 value = [value]
2393 for value in value:
2394 self.add(name, value)
2396 def fdel(self: Component):
2397 """Delete the values of a multi-string property."""
2398 self.pop(name, None)
2400 return property(fget, fset, fdel, doc=doc)
2403refids_property = multi_string_property(
2404 "REFID",
2405 """REFID
2407Purpose:
2408 REFID acts as a key for associated iCalendar entities.
2410Conformance:
2411 Since :rfc:`9253`,
2412 this property can be specified zero or more times in any iCalendar component.
2414Description:
2415 The value of this property is free-form text that creates an
2416 identifier for associated components.
2417 All components that use the same REFID value are associated through
2418 that value and can be located or retrieved as a group.
2419 For example, all of the events in a travel itinerary
2420 would have the same REFID value, so as to be grouped together.
2422Examples:
2423 The following is an example of this property.
2425 .. code-block:: ics
2427 REFID:itinerary-2014-11-17
2429 Use a REFID to associate several VTODOs:
2431 .. code-block:: pycon
2433 >>> from icalendar import Todo
2434 >>> todo_1 = Todo.new(
2435 ... summary="turn off stove",
2436 ... refids=["travel", "alps"]
2437 ... )
2438 >>> todo_2 = Todo.new(
2439 ... summary="pack backpack",
2440 ... refids=["travel", "alps"]
2441 ... )
2442 >>> todo_1.refids == todo_2.refids
2443 True
2445.. note::
2447 List modifications do not modify the component.
2448""",
2449)
2452__all__ = [
2453 "CONCEPTS_TYPE_SETTER",
2454 "LINKS_TYPE_SETTER",
2455 "RECURRENCE_ID",
2456 "RELATED_TO_TYPE_SETTER",
2457 "attendees_property",
2458 "busy_type_property",
2459 "categories_property",
2460 "class_property",
2461 "color_property",
2462 "comments_property",
2463 "concepts_property",
2464 "conferences_property",
2465 "contacts_property",
2466 "create_single_property",
2467 "description_property",
2468 "descriptions_property",
2469 "duration_property",
2470 "exdates_property",
2471 "get_duration_property",
2472 "get_end_property",
2473 "get_start_end_duration_with_validation",
2474 "get_start_property",
2475 "images_property",
2476 "links_property",
2477 "location_property",
2478 "multi_language_text_property",
2479 "multi_string_property",
2480 "organizer_property",
2481 "priority_property",
2482 "property_del_duration",
2483 "property_doc_duration_template",
2484 "property_get_duration",
2485 "property_set_duration",
2486 "rdates_property",
2487 "refids_property",
2488 "related_to_property",
2489 "rfc_7953_dtend_property",
2490 "rfc_7953_dtstart_property",
2491 "rfc_7953_duration_property",
2492 "rfc_7953_end_property",
2493 "rrules_property",
2494 "sequence_property",
2495 "set_duration_with_locking",
2496 "set_end_with_locking",
2497 "set_start_with_locking",
2498 "single_int_property",
2499 "single_utc_property",
2500 "source_property",
2501 "status_property",
2502 "summary_property",
2503 "transparency_property",
2504 "uid_property",
2505 "url_property",
2506]