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.
619The categories property is used to specify categories or subtypes of the
620calendar component. The categories are useful to search for a calendar
621component of a particular type and category.
623Within the calendar components, specify categories as a list of strings.
624You can get, set, and delete categories for a component.
626This property can be used in icalendar through its Python attributes of:
628- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>`
629- :attr:`Event.categories <icalendar.cal.event.Event.categories>`
630- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>`
631- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>`
633The categories property for ``Event``, ``Journal``, and ``Todo`` complies
634with :rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`.
636Note:
637 At present, icalendar doesn't take the LANGUAGE parameter as defined
638 in :rfc:`5545#section-3.2.10` into account.
640Parameters:
641 categories(list[str]): A list of categories as strings.
643Example:
644 Create an event, add categories to it, print its ical representation,
645 append another category, and finally compare the result
646 against its expected value.
648 .. code-block:: pycon
650 >>> from icalendar import Event
651 >>> event = Event()
652 >>> event.categories = ["Work", "Meeting"]
653 >>> print(event.to_ical())
654 BEGIN:VEVENT
655 CATEGORIES:Work,Meeting
656 END:VEVENT
657 >>> event.categories.append("Lecture")
658 >>> event.categories == ["Work", "Meeting", "Lecture"]
659 True
661See also:
662 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>`
663""",
664)
667def _get_attendees(self: Component) -> list[vCalAddress]:
668 """Get attendees."""
669 value = self.get("ATTENDEE")
670 if value is None:
671 value = []
672 self["ATTENDEE"] = value
673 return value
674 if isinstance(value, vCalAddress):
675 return [value]
676 return value
679def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
680 """Set attendees."""
681 _del_attendees(self)
682 if value is None:
683 return
684 if not isinstance(value, list):
685 value = [value]
686 self["ATTENDEE"] = value
689def _del_attendees(self: Component):
690 """Delete all attendees."""
691 self.pop("ATTENDEE", None)
694attendees_property = property(
695 _get_attendees,
696 _set_attendees,
697 _del_attendees,
698 """ATTENDEE defines one or more "Attendees" within a calendar component.
700Conformance:
701 This property MUST be specified in an iCalendar object
702 that specifies a group-scheduled calendar entity. This property
703 MUST NOT be specified in an iCalendar object when publishing the
704 calendar information (e.g., NOT in an iCalendar object that
705 specifies the publication of a calendar user's busy time, event,
706 to-do, or journal). This property is not specified in an
707 iCalendar object that specifies only a time zone definition or
708 that defines calendar components that are not group-scheduled
709 components, but are components only on a single user's calendar.
711Description:
712 This property MUST only be specified within calendar
713 components to specify participants, non-participants, and the
714 chair of a group-scheduled calendar entity. The property is
715 specified within an "EMAIL" category of the "VALARM" calendar
716 component to specify an email address that is to receive the email
717 type of iCalendar alarm.
719Examples:
720 Add a new attendee to an existing event.
722 .. code-block:: pycon
724 >>> from icalendar import Event, vCalAddress
725 >>> event = Event()
726 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
727 >>> print(event.to_ical())
728 BEGIN:VEVENT
729 ATTENDEE:mailto:me@my-domain.com
730 END:VEVENT
732 Create an email alarm with several attendees:
734 >>> from icalendar import Alarm, vCalAddress
735 >>> alarm = Alarm.new(attendees = [
736 ... vCalAddress("mailto:me@my-domain.com"),
737 ... vCalAddress("mailto:you@my-domain.com"),
738 ... ], summary = "Email alarm")
739 >>> print(alarm.to_ical())
740 BEGIN:VALARM
741 ATTENDEE:mailto:me@my-domain.com
742 ATTENDEE:mailto:you@my-domain.com
743 SUMMARY:Email alarm
744 END:VALARM
745""",
746)
748uid_property = single_string_property(
749 "UID",
750 """UID specifies the persistent, globally unique identifier for a component.
752We recommend using :func:`uuid.uuid4` to generate new values.
754Returns:
755 The value of the UID property as a string or ``""`` if no value is set.
757Description:
758 The "UID" itself MUST be a globally unique identifier.
759 The generator of the identifier MUST guarantee that the identifier
760 is unique.
762 This is the method for correlating scheduling messages with the
763 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
764 The full range of calendar components specified by a recurrence
765 set is referenced by referring to just the "UID" property value
766 corresponding to the calendar component. The "RECURRENCE-ID"
767 property allows the reference to an individual instance within the
768 recurrence set.
770 This property is an important method for group-scheduling
771 applications to match requests with later replies, modifications,
772 or deletion requests. Calendaring and scheduling applications
773 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
774 calendar components to assure interoperability with other group-
775 scheduling applications. This identifier is created by the
776 calendar system that generates an iCalendar object.
778 Implementations MUST be able to receive and persist values of at
779 least 255 octets for this property, but they MUST NOT truncate
780 values in the middle of a UTF-8 multi-octet sequence.
782 :rfc:`7986` states that UID can be used, for
783 example, to identify duplicate calendar streams that a client may
784 have been given access to. It can be used in conjunction with the
785 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
786 to identify the most recent version of a calendar.
788Conformance:
789 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
790 and "VJOURNAL" calendar components.
791 :rfc:`7986` modifies the definition of the "UID" property to
792 allow it to be defined in an iCalendar object.
793 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
794 identifier to be specified. The value of this property can then be used
795 to refer uniquely to the "VALARM" component.
797 This property can be specified once only.
799Security:
800 :rfc:`7986` states that UID values MUST NOT include any data that
801 might identify a user, host, domain, or any other security- or
802 privacy-sensitive information. It is RECOMMENDED that calendar user
803 agents now generate "UID" values that are hex-encoded random
804 Universally Unique Identifier (UUID) values as defined in
805 Sections 4.4 and 4.5 of :rfc:`4122`.
806 You can use the :mod:`uuid` module to generate new UUIDs.
808Compatibility:
809 For Alarms, ``X-ALARMUID`` is also considered.
811Examples:
812 The following is an example of such a property value:
813 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
815 Set the UID of a calendar:
817 .. code-block:: pycon
819 >>> from icalendar import Calendar
820 >>> from uuid import uuid4
821 >>> calendar = Calendar()
822 >>> calendar.uid = uuid4()
823 >>> print(calendar.to_ical())
824 BEGIN:VCALENDAR
825 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
826 END:VCALENDAR
828""",
829)
831summary_property = multi_language_text_property(
832 "SUMMARY",
833 None,
834 """SUMMARY defines a short summary or subject for the calendar component.
836Property Parameters:
837 IANA, non-standard, alternate text
838 representation, and language property parameters can be specified
839 on this property.
841Conformance:
842 The property can be specified in "VEVENT", "VTODO",
843 "VJOURNAL", or "VALARM" calendar components.
845Description:
846 This property is used in the "VEVENT", "VTODO", and
847 "VJOURNAL" calendar components to capture a short, one-line
848 summary about the activity or journal entry.
850 This property is used in the "VALARM" calendar component to
851 capture the subject of an EMAIL category of alarm.
853Examples:
854 The following is an example of this property:
856 .. code-block:: pycon
858 SUMMARY:Department Party
859""",
860)
862description_property = multi_language_text_property(
863 "DESCRIPTION",
864 None,
865 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
867Property Parameters:
868 IANA, non-standard, alternate text
869 representation, and language property parameters can be specified
870 on this property.
872Conformance:
873 The property can be specified in the "VEVENT", "VTODO",
874 "VJOURNAL", or "VALARM" calendar components. The property can be
875 specified multiple times only within a "VJOURNAL" calendar
876 component.
878Description:
879 This property is used in the "VEVENT" and "VTODO" to
880 capture lengthy textual descriptions associated with the activity.
882 This property is used in the "VALARM" calendar component to
883 capture the display text for a DISPLAY category of alarm, and to
884 capture the body text for an EMAIL category of alarm.
886Examples:
887 The following is an example of this property with formatted
888 line breaks in the property value:
890 .. code-block:: pycon
892 DESCRIPTION:Meeting to provide technical review for "Phoenix"
893 design.\\nHappy Face Conference Room. Phoenix design team
894 MUST attend this meeting.\\nRSVP to team leader.
896 """, # noqa: E501
897)
900def create_single_property(
901 prop: str,
902 value_attr: str | None,
903 value_type: tuple[type],
904 type_def: type,
905 doc: str,
906 vProp: type = vDDDTypes, # noqa: N803
907 convert: Callable[[object], object] | None = None,
908):
909 """Create a single property getter and setter.
911 Parameters:
912 prop: The name of the property.
913 value_attr: The name of the attribute to get the value from.
914 value_type: The type of the value.
915 type_def: The type of the property.
916 doc: The docstring of the property.
917 vProp: The type of the property from :mod:`icalendar.prop`.
918 """
920 def p_get(self: Component):
921 default = object()
922 result = self.get(prop, default)
923 if result is default:
924 return None
925 if isinstance(result, list):
926 raise InvalidCalendar(f"Multiple {prop} defined.")
927 value = result if value_attr is None else getattr(result, value_attr, result)
928 value = value if convert is None else convert(value)
929 if not isinstance(value, value_type):
930 raise InvalidCalendar(
931 f"{prop} must be either a "
932 f"{' or '.join(t.__name__ for t in value_type)},"
933 f" not {value}."
934 )
935 return value
937 def p_set(self: Component, value) -> None:
938 if value is None:
939 p_del(self)
940 return
941 value = convert(value) if convert is not None else value
942 if not isinstance(value, value_type):
943 raise TypeError(
944 f"Use {' or '.join(t.__name__ for t in value_type)}, "
945 f"not {type(value).__name__}."
946 )
947 self[prop] = vProp(value)
948 if prop in self.exclusive:
949 for other_prop in self.exclusive:
950 if other_prop != prop:
951 self.pop(other_prop, None)
953 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
955 def p_del(self: Component):
956 self.pop(prop)
958 p_doc = f"""The {prop} property.
960 {doc}
962 Accepted values: {", ".join(t.__name__ for t in value_type)}.
963 If the attribute has invalid values, we raise InvalidCalendar.
964 If the value is absent, we return None.
965 You can also delete the value with del or by setting it to None.
966 """
967 return property(p_get, p_set, p_del, p_doc)
970X_MOZ_SNOOZE_TIME_property = single_utc_property(
971 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
972)
973X_MOZ_LASTACK_property = single_utc_property(
974 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
975)
978def property_get_duration(self: Component) -> timedelta | None:
979 """Getter for property DURATION."""
980 default = object()
981 duration = self.get("duration", default)
982 if duration is default:
983 return None
984 result = getattr(duration, "td", None)
985 if result is None:
986 raise InvalidCalendar(
987 f"DURATION must be a timedelta, not {type(duration).__name__}."
988 )
989 return result
992def property_set_duration(self: Component, value: timedelta | None):
993 """Setter for property DURATION."""
994 if value is None:
995 self.pop("duration", None)
996 return
997 if not isinstance(value, timedelta):
998 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
999 self["duration"] = vDuration(value)
1000 self.pop("DTEND")
1001 self.pop("DUE")
1004def property_del_duration(self: Component):
1005 """Delete property DURATION."""
1006 self.pop("DURATION")
1009property_doc_duration_template = """The DURATION property.
1011The "DTSTART" property for a "{component}" specifies the inclusive
1012start of the {component}.
1013The "DURATION" property in conjunction with the DTSTART property
1014for a "{component}" calendar component specifies the non-inclusive end
1015of the event.
1017If you would like to calculate the duration of a {component}, do not use this.
1018Instead use the duration property (lower case).
1019"""
1022def duration_property(component: str) -> property:
1023 """Return the duration property."""
1024 return property(
1025 property_get_duration,
1026 property_set_duration,
1027 property_del_duration,
1028 property_doc_duration_template.format(component=component),
1029 )
1032def multi_text_property(name: str, docs: str) -> property:
1033 """Get a property that can occur several times and is text.
1035 Examples: Journal.descriptions, Event.comments
1036 """
1038 def fget(self: Component) -> list[str]:
1039 """Get the values."""
1040 descriptions = self.get(name)
1041 if descriptions is None:
1042 return []
1043 if not isinstance(descriptions, SEQUENCE_TYPES):
1044 return [descriptions]
1045 return descriptions
1047 def fset(self: Component, values: str | Sequence[str] | None):
1048 """Set the values."""
1049 fdel(self)
1050 if values is None:
1051 return
1052 if isinstance(values, str):
1053 self.add(name, values)
1054 else:
1055 for description in values:
1056 self.add(name, description)
1058 def fdel(self: Component):
1059 """Delete the values."""
1060 self.pop(name)
1062 return property(fget, fset, fdel, docs)
1065descriptions_property = multi_text_property(
1066 "DESCRIPTION",
1067 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1069Property Parameters:
1070 IANA, non-standard, alternate text
1071 representation, and language property parameters can be specified
1072 on this property.
1074Conformance:
1075 The property can be
1076 specified multiple times only within a "VJOURNAL" calendar component.
1078Description:
1079 This property is used in the "VJOURNAL" calendar component to
1080 capture one or more textual journal entries.
1082Examples:
1083 The following is an example of this property with formatted
1084 line breaks in the property value:
1086 .. code-block:: pycon
1088 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1089 design.\\nHappy Face Conference Room. Phoenix design team
1090 MUST attend this meeting.\\nRSVP to team leader.
1092""", # noqa: E501
1093)
1095comments_property = multi_text_property(
1096 "COMMENT",
1097 """COMMENT is used to specify a comment to the calendar user.
1099Purpose:
1100 This property specifies non-processing information intended
1101 to provide a comment to the calendar user.
1103Conformance:
1104 In :rfc:`5545`, this property can be specified multiple times in
1105 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1106 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1107 In :rfc:`7953`, this property can be specified multiple times in
1108 "VAVAILABILITY" and "VAVAILABLE".
1110Property Parameters:
1111 IANA, non-standard, alternate text
1112 representation, and language property parameters can be specified
1113 on this property.
1115""",
1116)
1118RECURRENCE_ID = create_single_property(
1119 "RECURRENCE-ID",
1120 "dt",
1121 (date, datetime),
1122 date | datetime,
1123 """
1124Identify a specific occurrence of a recurring calendar object.
1126This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1127particular instance in a recurrence set. The value is the original start
1128date or date-time of that instance, not the rescheduled time.
1130The value is usually a DATE-TIME and must use the same value type as the
1131``DTSTART`` property in the same component. A DATE value may be used for
1132all-day items instead.
1134This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1135may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1136""",
1137 vDDDTypes,
1138)
1141def _get_organizer(self: Component) -> vCalAddress | None:
1142 """ORGANIZER defines the organizer for a calendar component.
1144 Property Parameters:
1145 IANA, non-standard, language, common name,
1146 directory entry reference, and sent-by property parameters can be
1147 specified on this property.
1149 Conformance:
1150 This property MUST be specified in an iCalendar object
1151 that specifies a group-scheduled calendar entity. This property
1152 MUST be specified in an iCalendar object that specifies the
1153 publication of a calendar user's busy time. This property MUST
1154 NOT be specified in an iCalendar object that specifies only a time
1155 zone definition or that defines calendar components that are not
1156 group-scheduled components, but are components only on a single
1157 user's calendar.
1159 Description:
1160 This property is specified within the "VEVENT",
1161 "VTODO", and "VJOURNAL" calendar components to specify the
1162 organizer of a group-scheduled calendar entity. The property is
1163 specified within the "VFREEBUSY" calendar component to specify the
1164 calendar user requesting the free or busy time. When publishing a
1165 "VFREEBUSY" calendar component, the property is used to specify
1166 the calendar that the published busy time came from.
1168 The property has the property parameters "CN", for specifying the
1169 common or display name associated with the "Organizer", "DIR", for
1170 specifying a pointer to the directory information associated with
1171 the "Organizer", "SENT-BY", for specifying another calendar user
1172 that is acting on behalf of the "Organizer". The non-standard
1173 parameters may also be specified on this property. If the
1174 "LANGUAGE" property parameter is specified, the identified
1175 language applies to the "CN" parameter value.
1176 """
1177 return self.get("ORGANIZER")
1180def _set_organizer(self: Component, value: vCalAddress | str | None):
1181 """Set the value."""
1182 _del_organizer(self)
1183 if value is not None:
1184 self.add("ORGANIZER", value)
1187def _del_organizer(self: Component):
1188 """Delete the value."""
1189 self.pop("ORGANIZER")
1192organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1195def single_string_enum_property(
1196 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1197) -> property:
1198 """Create a property to access a single string value and convert it to an enum."""
1199 prop = single_string_property(name, docs, default=default)
1201 def fget(self: Component) -> StrEnum:
1202 """Get the value."""
1203 value = prop.fget(self)
1204 if value == default:
1205 return default
1206 return enum(str(value))
1208 def fset(self: Component, value: str | StrEnum | None) -> None:
1209 """Set the value."""
1210 if value == "":
1211 value = None
1212 prop.fset(self, value)
1214 return property(fget, fset, prop.fdel, doc=docs)
1217busy_type_property = single_string_enum_property(
1218 "BUSYTYPE",
1219 BUSYTYPE,
1220 BUSYTYPE.BUSY_UNAVAILABLE,
1221 """BUSYTYPE specifies the default busy time type.
1223Returns:
1224 :class:`icalendar.enums.BUSYTYPE`
1226Description:
1227 This property is used to specify the default busy time
1228 type. The values correspond to those used by the FBTYPE"
1229 parameter used on a "FREEBUSY" property, with the exception that
1230 the "FREE" value is not used in this property. If not specified
1231 on a component that allows this property, the default is "BUSY-
1232 UNAVAILABLE".
1233""",
1234)
1236priority_property = single_int_property(
1237 "PRIORITY",
1238 0,
1239 """
1241Conformance:
1242 This property can be specified in "VEVENT" and "VTODO" calendar components
1243 according to :rfc:`5545`.
1244 :rfc:`7953` adds this property to "VAVAILABILITY".
1246Description:
1247 This priority is specified as an integer in the range 0
1248 to 9. A value of 0 specifies an undefined priority. A value of 1
1249 is the highest priority. A value of 2 is the second highest
1250 priority. Subsequent numbers specify a decreasing ordinal
1251 priority. A value of 9 is the lowest priority.
1253 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1254 "LOW" is mapped into this property such that a property value in
1255 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1256 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1257 is "LOW" priority.
1259 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1260 "C3" is mapped into this property such that a property value of 1
1261 specifies "A1", a property value of 2 specifies "A2", a property
1262 value of 3 specifies "A3", and so forth up to a property value of
1263 9 specifies "C3".
1265 Other integer values are reserved for future use.
1267 Within a "VEVENT" calendar component, this property specifies a
1268 priority for the event. This property may be useful when more
1269 than one event is scheduled for a given time period.
1271 Within a "VTODO" calendar component, this property specified a
1272 priority for the to-do. This property is useful in prioritizing
1273 multiple action items for a given time period.
1274""",
1275)
1277class_property = single_string_enum_property(
1278 "CLASS",
1279 CLASS,
1280 CLASS.PUBLIC,
1281 """CLASS specifies the class of the calendar component.
1283Returns:
1284 :class:`icalendar.enums.CLASS`
1286Description:
1287 An access classification is only one component of the
1288 general security system within a calendar application. It
1289 provides a method of capturing the scope of the access the
1290 calendar owner intends for information within an individual
1291 calendar entry. The access classification of an individual
1292 iCalendar component is useful when measured along with the other
1293 security components of a calendar system (e.g., calendar user
1294 authentication, authorization, access rights, access role, etc.).
1295 Hence, the semantics of the individual access classifications
1296 cannot be completely defined by this memo alone. Additionally,
1297 due to the "blind" nature of most exchange processes using this
1298 memo, these access classifications cannot serve as an enforcement
1299 statement for a system receiving an iCalendar object. Rather,
1300 they provide a method for capturing the intention of the calendar
1301 owner for the access to the calendar component. If not specified
1302 in a component that allows this property, the default value is
1303 PUBLIC. Applications MUST treat x-name and iana-token values they
1304 don't recognize the same way as they would the PRIVATE value.
1305""",
1306)
1308transparency_property = single_string_enum_property(
1309 "TRANSP",
1310 TRANSP,
1311 TRANSP.OPAQUE,
1312 """TRANSP defines whether or not an event is transparent to busy time searches.
1314Returns:
1315 :class:`icalendar.enums.TRANSP`
1317Description:
1318 Time Transparency is the characteristic of an event
1319 that determines whether it appears to consume time on a calendar.
1320 Events that consume actual time for the individual or resource
1321 associated with the calendar SHOULD be recorded as OPAQUE,
1322 allowing them to be detected by free/busy time searches. Other
1323 events, which do not take up the individual's (or resource's) time
1324 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1325 busy time searches.
1326""",
1327)
1328status_property = single_string_enum_property(
1329 "STATUS",
1330 STATUS,
1331 "",
1332 """STATUS defines the overall status or confirmation for the calendar component.
1334Returns:
1335 :class:`icalendar.enums.STATUS`
1337The default value is ``""``.
1339Description:
1340 In a group-scheduled calendar component, the property
1341 is used by the "Organizer" to provide a confirmation of the event
1342 to the "Attendees". For example in a "VEVENT" calendar component,
1343 the "Organizer" can indicate that a meeting is tentative,
1344 confirmed, or cancelled. In a "VTODO" calendar component, the
1345 "Organizer" can indicate that an action item needs action, is
1346 completed, is in process or being worked on, or has been
1347 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1348 can indicate that a journal entry is draft, final, or has been
1349 cancelled or removed.
1350""",
1351)
1353url_property = single_string_property(
1354 "URL",
1355 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1357Description:
1358 This property may be used in a calendar component to
1359 convey a location where a more dynamic rendition of the calendar
1360 information associated with the calendar component can be found.
1361 This memo does not attempt to standardize the form of the URI, nor
1362 the format of the resource pointed to by the property value. If
1363 the URL property and Content-Location MIME header are both
1364 specified, they MUST point to the same resource.
1366Conformance:
1367 This property can be specified once in the "VEVENT",
1368 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1369 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1371Example:
1372 The following is an example of this property:
1374 .. code-block:: ics
1376 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1378""",
1379)
1381source_property = single_string_property(
1382 "SOURCE",
1383 """A URI from where calendar data can be refreshed.
1385Description:
1386 This property identifies a location where a client can
1387 retrieve updated data for the calendar. Clients SHOULD honor any
1388 specified "REFRESH-INTERVAL" value when periodically retrieving
1389 data. Note that this property differs from the "URL" property in
1390 that "URL" is meant to provide an alternative representation of
1391 the calendar data rather than the original location of the data.
1393Conformance:
1394 This property can be specified once in an iCalendar object.
1396Example:
1397 The following is an example of this property:
1399 .. code-block:: ics
1401 SOURCE;VALUE=URI:https://example.com/holidays.ics
1403""",
1404)
1406location_property = multi_language_text_property(
1407 "LOCATION",
1408 None,
1409 """The intended venue for the activity defined by a calendar component.
1411Property Parameters:
1412 IANA, non-standard, alternate text
1413 representation, and language property parameters can be specified
1414 on this property.
1416Conformance:
1417 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1418 calendar component.
1419 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1421Description:
1422 Specific venues such as conference or meeting rooms may
1423 be explicitly specified using this property. An alternate
1424 representation may be specified that is a URI that points to
1425 directory information with more structured specification of the
1426 location. For example, the alternate representation may specify
1427 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1428 CID URL :rfc:`2392` pointing to a MIME body part containing a
1429 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1431""",
1432)
1434contacts_property = multi_text_property(
1435 "CONTACT",
1436 """Contact information associated with the calendar component.
1438Purpose:
1439 This property is used to represent contact information or
1440 alternately a reference to contact information associated with the
1441 calendar component.
1443Property Parameters:
1444 IANA, non-standard, alternate text
1445 representation, and language property parameters can be specified
1446 on this property.
1448Conformance:
1449 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1450 "VJOURNAL", or "VFREEBUSY" calendar component.
1451 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1452 amd "VAVAILABLE" calendar component.
1454Description:
1455 The property value consists of textual contact
1456 information. An alternative representation for the property value
1457 can also be specified that refers to a URI pointing to an
1458 alternate form, such as a vCard :rfc:`2426`, for the contact
1459 information.
1461Example:
1462 The following is an example of this property referencing
1463 textual contact information:
1465 .. code-block:: ics
1467 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1469 The following is an example of this property with an alternate
1470 representation of an LDAP URI to a directory entry containing the
1471 contact information:
1473 .. code-block:: ics
1475 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1476 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1477 +1-919-555-1234
1479 The following is an example of this property with an alternate
1480 representation of a MIME body part containing the contact
1481 information, such as a vCard :rfc:`2426` embedded in a text/
1482 directory media type :rfc:`2425`:
1484 .. code-block:: ics
1486 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1487 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1489 The following is an example of this property referencing a network
1490 resource, such as a vCard :rfc:`2426` object containing the contact
1491 information:
1493 .. code-block:: ics
1495 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1496 Dolittle\\, ABC Industries\\, +1-919-555-1234
1497""",
1498)
1501def timezone_datetime_property(name: str, docs: str):
1502 """Create a property to access the values with a proper timezone."""
1504 return single_utc_property(name, docs)
1507rfc_7953_dtstart_property = timezone_datetime_property(
1508 "DTSTART",
1509 """Start of the component.
1511 This is almost the same as :attr:`Event.DTSTART` with one exception:
1512 The values MUST have a timezone and DATE is not allowed.
1514 Description:
1515 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1516 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1517 "DATE-TIME" values specified as either the date with UTC time or
1518 the date with local time and a time zone reference.
1520 """,
1521)
1523rfc_7953_dtend_property = timezone_datetime_property(
1524 "DTEND",
1525 """Start of the component.
1527 This is almost the same as :attr:`Event.DTEND` with one exception:
1528 The values MUST have a timezone and DATE is not allowed.
1530 Description:
1531 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1532 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1533 "DATE-TIME" values specified as either the date with UTC time or
1534 the date with local time and a time zone reference.
1535 """,
1536)
1539@property
1540def rfc_7953_duration_property(self) -> timedelta | None:
1541 """Compute the duration of this component.
1543 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1544 Otherwise, the duration is calculated from :attr:`DTSTART` and
1545 :attr:`DTEND`/:attr:`DURATION`.
1547 This is in accordance with :rfc:`7953`:
1548 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1549 """
1550 duration = self.DURATION
1551 if duration:
1552 return duration
1553 end = self.DTEND
1554 if end is None:
1555 return None
1556 start = self.DTSTART
1557 if start is None:
1558 raise IncompleteComponent("Cannot compute duration without start.")
1559 return end - start
1562@property
1563def rfc_7953_end_property(self) -> timedelta | None:
1564 """Compute the duration of this component.
1566 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1567 Otherwise, the duration is calculated from :attr:`DTSTART` and
1568 :attr:`DTEND`/:attr:`DURATION`.
1570 This is in accordance with :rfc:`7953`:
1571 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1572 """
1573 duration = self.DURATION
1574 if duration:
1575 start = self.DTSTART
1576 if start is None:
1577 raise IncompleteComponent("Cannot compute end without start.")
1578 return start + duration
1579 end = self.DTEND
1580 if end is None:
1581 return None
1582 return end
1585@rfc_7953_end_property.setter
1586def rfc_7953_end_property(self, value: datetime):
1587 self.DTEND = value
1590@rfc_7953_end_property.deleter
1591def rfc_7953_end_property(self):
1592 del self.DTEND
1595def get_start_end_duration_with_validation(
1596 component: Component,
1597 start_property: str,
1598 end_property: str,
1599 component_name: str,
1600) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1601 """
1602 Validate the component and return start, end, and duration.
1604 This tests validity according to :rfc:`5545` rules
1605 for ``Event`` and ``Todo`` components.
1607 Parameters:
1608 component: The component to validate, either ``Event`` or ``Todo``.
1609 start_property: The start property name, ``DTSTART``.
1610 end_property: The end property name, either ``DTEND`` for ``Event`` or
1611 ``DUE`` for ``Todo``.
1612 component_name: The component name for error messages,
1613 either ``VEVENT`` or ``VTODO``.
1615 Returns:
1616 tuple: (start, end, duration) values from the component.
1618 Raises:
1619 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1621 """
1622 start = getattr(component, start_property, None)
1623 end = getattr(component, end_property, None)
1624 duration = component.DURATION
1626 # RFC 5545: Only one of end property and DURATION may be present
1627 if duration is not None and end is not None:
1628 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1629 msg = (
1630 f"Only one of {end_name} and DURATION "
1631 f"may be in a {component_name}, not both."
1632 )
1633 raise InvalidCalendar(msg)
1635 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1636 if (
1637 start is not None
1638 and is_date(start)
1639 and duration is not None
1640 and duration.seconds != 0
1641 ):
1642 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1643 raise InvalidCalendar(msg)
1645 # RFC 5545: DTSTART and end property must be of the same type
1646 if start is not None and end is not None and is_date(start) != is_date(end):
1647 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1648 msg = (
1649 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1650 )
1651 raise InvalidCalendar(msg)
1653 return start, end, duration
1656def get_start_property(component: Component) -> date | datetime:
1657 """
1658 Get the start property with validation.
1660 Parameters:
1661 component: The component from which to get its start property.
1663 Returns:
1664 The ``DTSTART`` value.
1666 Raises:
1667 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1669 """
1670 # Trigger validation by calling _get_start_end_duration
1671 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1672 if start is None:
1673 msg = "No DTSTART given."
1674 raise IncompleteComponent(msg)
1675 return start
1678def get_end_property(component: Component, end_property: str) -> date | datetime:
1679 """
1680 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1682 Parameters:
1683 component: The component to get end from
1684 end_property: The end property name, either ``DTEND`` for ``Event`` or
1685 ``DUE`` for ``Todo``.
1687 Returns:
1688 The computed end value.
1690 Raises:
1691 ~error.IncompleteComponent: If the provided information is incomplete
1692 to compute the end property.
1694 """
1695 # Trigger validation by calling _get_start_end_duration
1696 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1698 if end is None and duration is None:
1699 if start is None:
1700 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1701 msg = f"No {end_name} or DURATION+DTSTART given."
1702 raise IncompleteComponent(msg)
1704 # Default behavior differs for Event vs Todo:
1705 # Event: date gets +1 day, datetime gets same time
1706 # Todo: both date and datetime get same time (issue #898)
1707 if end_property == "DTEND" and is_date(start):
1708 return start + timedelta(days=1)
1709 return start
1711 if duration is not None:
1712 if start is not None:
1713 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1714 return start
1715 return start + duration
1716 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1717 msg = f"No {end_name} or DURATION+DTSTART given."
1718 raise IncompleteComponent(msg)
1720 return end
1723def get_duration_property(component: Component) -> timedelta:
1724 """
1725 Get the duration property with fallback calculation from start and end.
1727 Parameters:
1728 component: The component from which to get its duration property.
1730 Returns:
1731 The duration as a timedelta.
1733 """
1734 # First check if DURATION property is explicitly set
1735 if "DURATION" in component:
1736 return component["DURATION"].dt
1738 # Fall back to calculated duration from start and end
1739 return component.end - component.start
1742def set_duration_with_locking(
1743 component: Component,
1744 duration: timedelta | None,
1745 locked: Literal["start", "end"],
1746 end_property: str,
1747) -> None:
1748 """
1749 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1751 Parameters:
1752 component: The component to modify, either ``Event`` or ``Todo``.
1753 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1754 locked: Which property to keep unchanged, either ``start`` or ``end``.
1755 end_property: The end property name, either ``DTEND`` for ``Event`` or
1756 ``DUE`` for ``Todo``.
1758 """
1759 # Convert to DURATION property if duration is None
1760 if duration is None:
1761 if "DURATION" in component:
1762 return # Already has DURATION property
1763 current_duration = component.duration
1764 component.DURATION = current_duration
1765 return
1767 if not isinstance(duration, timedelta):
1768 msg = f"Use timedelta, not {type(duration).__name__}."
1769 raise TypeError(msg)
1771 # Validate date/duration compatibility
1772 start = component.DTSTART
1773 if start is not None and is_date(start) and duration.seconds != 0:
1774 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1775 raise InvalidCalendar(msg)
1777 if locked == "start":
1778 # Keep start locked, adjust end
1779 if start is None:
1780 msg = "Cannot set duration without DTSTART. Set start time first."
1781 raise IncompleteComponent(msg)
1782 component.pop(end_property, None) # Remove end property
1783 component.DURATION = duration
1784 elif locked == "end":
1785 # Keep end locked, adjust start
1786 current_end = component.end
1787 component.DTSTART = current_end - duration
1788 component.pop(end_property, None) # Remove end property
1789 component.DURATION = duration
1790 else:
1791 msg = f"locked must be 'start' or 'end', not {locked!r}"
1792 raise ValueError(msg)
1795def set_start_with_locking(
1796 component: Component,
1797 start: date | datetime,
1798 locked: Literal["duration", "end"] | None,
1799 end_property: str,
1800) -> None:
1801 """
1802 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1804 Parameters:
1805 component: The component to modify, either ``Event`` or ``Todo``.
1806 start: The start time to set.
1807 locked: Which property to keep unchanged, either ``duration``, ``end``,
1808 or ``None`` for auto-detect.
1809 end_property: The end property name, either ``DTEND`` for ``Event`` or
1810 ``DUE`` for ``Todo``.
1812 """
1813 if locked is None:
1814 # Auto-detect based on existing properties
1815 if "DURATION" in component:
1816 locked = "duration"
1817 elif end_property in component:
1818 locked = "end"
1819 else:
1820 # Default to duration if no existing properties
1821 locked = "duration"
1823 if locked == "duration":
1824 # Keep duration locked, adjust end
1825 current_duration = (
1826 component.duration
1827 if "DURATION" in component or end_property in component
1828 else None
1829 )
1830 component.DTSTART = start
1831 if current_duration is not None:
1832 component.pop(end_property, None) # Remove end property
1833 component.DURATION = current_duration
1834 elif locked == "end":
1835 # Keep end locked, adjust duration
1836 current_end = component.end
1837 component.DTSTART = start
1838 component.pop("DURATION", None) # Remove duration property
1839 setattr(component, end_property, current_end)
1840 else:
1841 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1842 raise ValueError(msg)
1845def set_end_with_locking(
1846 component: Component,
1847 end: date | datetime,
1848 locked: Literal["start", "duration"],
1849 end_property: str,
1850) -> None:
1851 """
1852 Set the end with explicit locking behavior for Event and Todo components.
1854 Parameters:
1855 component: The component to modify, either ``Event`` or ``Todo``.
1856 end: The end time to set.
1857 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1858 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1859 for ``Todo``.
1861 """
1862 if locked == "start":
1863 # Keep start locked, adjust duration
1864 component.pop("DURATION", None) # Remove duration property
1865 setattr(component, end_property, end)
1866 elif locked == "duration":
1867 # Keep duration locked, adjust start
1868 current_duration = component.duration
1869 component.DTSTART = end - current_duration
1870 component.pop(end_property, None) # Remove end property
1871 component.DURATION = current_duration
1872 else:
1873 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1874 raise ValueError(msg)
1877def _get_images(self: Component) -> list[Image]:
1878 """IMAGE specifies an image associated with the calendar or a calendar component.
1880 Description:
1881 This property specifies an image for an iCalendar
1882 object or a calendar component via a URI or directly with inline
1883 data that can be used by calendar user agents when presenting the
1884 calendar data to a user. Multiple properties MAY be used to
1885 specify alternative sets of images with, for example, varying
1886 media subtypes, resolutions, or sizes. When multiple properties
1887 are present, calendar user agents SHOULD display only one of them,
1888 picking one that provides the most appropriate image quality, or
1889 display none. The "DISPLAY" parameter is used to indicate the
1890 intended display mode for the image. The "ALTREP" parameter,
1891 defined in :rfc:`5545`, can be used to provide a "clickable" image
1892 where the URI in the parameter value can be "launched" by a click
1893 on the image in the calendar user agent.
1895 Conformance:
1896 This property can be specified multiple times in an
1897 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1898 components.
1900 .. note::
1902 At the present moment, this property is read-only. If you require a setter,
1903 please open an issue or a pull request.
1904 """
1905 images = self.get("IMAGE", [])
1906 if not isinstance(images, SEQUENCE_TYPES):
1907 images = [images]
1908 return [Image.from_property_value(img) for img in images]
1911images_property = property(_get_images)
1914def _get_conferences(self: Component) -> list[Conference]:
1915 """Return the CONFERENCE properties as a list.
1917 Purpose:
1918 This property specifies information for accessing a conferencing system.
1920 Conformance:
1921 This property can be specified multiple times in a
1922 "VEVENT" or "VTODO" calendar component.
1924 Description:
1925 This property specifies information for accessing a
1926 conferencing system for attendees of a meeting or task. This
1927 might be for a telephone-based conference number dial-in with
1928 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1929 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1930 or https: URI :rfc:`7230`), or for an instant messaging group chat
1931 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1932 conferencing system is not available, a data: URI :rfc:`2397`
1933 containing a text description can be used.
1935 A conference system can be a bidirectional communication channel
1936 or a uni-directional "broadcast feed".
1938 The "FEATURE" property parameter is used to describe the key
1939 capabilities of the conference system to allow a client to choose
1940 the ones that give the required level of interaction from a set of
1941 multiple properties.
1943 The "LABEL" property parameter is used to convey additional
1944 details on the use of the URI. For example, the URIs or access
1945 codes for the moderator and attendee of a teleconference system
1946 could be different, and the "LABEL" property parameter could be
1947 used to "tag" each "CONFERENCE" property to indicate which is
1948 which.
1950 The "LANGUAGE" property parameter can be used to specify the
1951 language used for text values used with this property (as per
1952 Section 3.2.10 of :rfc:`5545`).
1954 Example:
1955 The following are examples of this property:
1957 .. code-block:: ics
1959 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1960 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1961 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1962 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1963 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1964 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1965 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1966 LABEL=Chat room:xmpp:chat-123@conference.example.com
1967 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1968 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1970 Get all conferences:
1972 .. code-block:: pycon
1974 >>> from icalendar import Event
1975 >>> event = Event()
1976 >>> event.conferences
1977 []
1979 Set a conference:
1981 .. code-block:: pycon
1983 >>> from icalendar import Event, Conference
1984 >>> event = Event()
1985 >>> event.conferences = [
1986 ... Conference(
1987 ... "tel:+1-412-555-0123,,,654321",
1988 ... feature="PHONE,MODERATOR",
1989 ... label="Moderator dial-in",
1990 ... language="EN",
1991 ... )
1992 ... ]
1993 >>> print(event.to_ical())
1994 BEGIN:VEVENT
1995 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
1996 ALUE=URI:tel:+1-412-555-0123,,,654321
1997 END:VEVENT
1999 """
2000 conferences = self.get("CONFERENCE", [])
2001 if not isinstance(conferences, SEQUENCE_TYPES):
2002 conferences = [conferences]
2003 return [Conference.from_uri(conference) for conference in conferences]
2006def _set_conferences(self: Component, conferences: list[Conference] | None):
2007 """Set the conferences."""
2008 _del_conferences(self)
2009 for conference in conferences or []:
2010 self.add("CONFERENCE", conference.to_uri())
2013def _del_conferences(self: Component):
2014 """Delete all conferences."""
2015 self.pop("CONFERENCE")
2018conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2021def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2022 """LINK properties as a list.
2024 Purpose:
2025 LINK provides a reference to external information related to a component.
2027 Property Parameters:
2028 The VALUE parameter is required.
2029 Non-standard, link relation type, format type, label, and language parameters
2030 can also be specified on this property.
2031 The LABEL parameter is defined in :rfc:`7986`.
2033 Conformance:
2034 This property can be specified zero or more times in any iCalendar component.
2035 LINK is specified in :rfc:`9253`.
2036 The LINKREL parameter is required.
2038 Description:
2039 When used in a component, the value of this property points to
2040 additional information related to the component.
2041 For example, it may reference the originating web server.
2043 This property is a serialization of the model in :rfc:`8288`,
2044 where the link target is carried in the property value,
2045 the link context is the containing calendar entity,
2046 and the link relation type and any target attributes
2047 are carried in iCalendar property parameters.
2049 The LINK property parameters map to :rfc:`8288` attributes as follows:
2051 LABEL
2052 This parameter maps to the "title"
2053 attribute defined in Section 3.4.1 of :rfc:`8288`.
2054 LABEL is used to label the destination
2055 of a link such that it can be used as a human-readable identifier
2056 (e.g., a menu entry) in the language indicated by the LANGUAGE
2057 (if present).
2058 LANGUAGE
2059 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2060 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2061 LINKREL
2062 This parameter maps to the link relation type defined in Section 2.1 of
2063 :rfc:`8288`. See `Registered Link Relation Types
2064 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2065 FMTTYPE
2066 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2067 :rfc:`8288`.
2069 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2071 Examples:
2072 The following is an example of this property,
2073 which provides a reference to the source for the calendar object.
2075 .. code-block:: ics
2077 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2078 https://example.com/events
2080 The following is an example of this property,
2081 which provides a reference to an entity from which this one was derived.
2082 The link relation is a vendor-defined value.
2084 .. code-block:: ics
2086 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2087 VALUE=URI:
2088 https://example.com/tasks/01234567-abcd1234.ics
2090 The following is an example of this property,
2091 which provides a reference to a fragment of an XML document.
2092 The link relation is a vendor-defined value.
2094 .. code-block:: ics
2096 LINK;LINKREL="https://example.com/linkrel/costStructure";
2097 VALUE=XML-REFERENCE:
2098 https://example.com/xmlDocs/bidFramework.xml
2099 #xpointer(descendant::CostStruc/range-to(
2100 following::CostStrucEND[1]))
2102 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2104 .. code-block:: pycon
2106 >>> from icalendar import Event, vUri
2107 >>> from datetime import datetime
2108 >>> link = vUri(
2109 ... "http://example.com/event-page",
2110 ... params={"LINKREL":"SOURCE"}
2111 ... )
2112 >>> event = Event.new(
2113 ... start=datetime(2025, 9, 17, 12, 0),
2114 ... summary="An Example Event with a page"
2115 ... )
2116 >>> event.links = [link]
2117 >>> print(event.to_ical())
2118 BEGIN:VEVENT
2119 SUMMARY:An Example Event with a page
2120 DTSTART:20250917T120000
2121 DTSTAMP:20250517T080612Z
2122 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2123 LINK;LINKREL="SOURCE":http://example.com/event-page
2124 END:VEVENT
2126 """
2127 links = self.get("LINK", [])
2128 if not isinstance(links, list):
2129 links = [links]
2130 return links
2133LINKS_TYPE_SETTER: TypeAlias = (
2134 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2135)
2138def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2139 """Set the LINKs."""
2140 _del_links(self)
2141 if links is None:
2142 return
2143 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2144 links = [links]
2145 for link in links:
2146 if type(link) is str:
2147 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2148 self.add("LINK", link)
2151def _del_links(self: Component) -> None:
2152 """Delete all links."""
2153 self.pop("LINK")
2156links_property = property(_get_links, _set_links, _del_links)
2158RELATED_TO_TYPE_SETTER: TypeAlias = (
2159 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2160)
2163def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2164 """RELATED-TO properties as a list.
2166 Purpose:
2167 This property is used to represent a relationship or reference
2168 between one calendar component and another.
2169 :rfc:`9523` allows URI or UID values and a GAP parameter.
2171 Value Type:
2172 :rfc:`5545`: TEXT
2173 :rfc:`9253`: URI, UID
2175 Conformance:
2176 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2177 "VTODO", and "VJOURNAL" calendar components.
2178 Since :rfc:`9523`, this property MAY be specified in any
2179 iCalendar component.
2181 Description (:rfc:`5545`):
2182 The property value consists of the persistent, globally
2183 unique identifier of another calendar component. This value would
2184 be represented in a calendar component by the "UID" property.
2186 By default, the property value points to another calendar
2187 component that has a PARENT relationship to the referencing
2188 object. The "RELTYPE" property parameter is used to either
2189 explicitly state the default PARENT relationship type to the
2190 referenced calendar component or to override the default PARENT
2191 relationship type and specify either a CHILD or SIBLING
2192 relationship. The PARENT relationship indicates that the calendar
2193 component is a subordinate of the referenced calendar component.
2194 The CHILD relationship indicates that the calendar component is a
2195 superior of the referenced calendar component. The SIBLING
2196 relationship indicates that the calendar component is a peer of
2197 the referenced calendar component.
2199 Changes to a calendar component referenced by this property can
2200 have an implicit impact on the related calendar component. For
2201 example, if a group event changes its start or end date or time,
2202 then the related, dependent events will need to have their start
2203 and end dates changed in a corresponding way. Similarly, if a
2204 PARENT calendar component is cancelled or deleted, then there is
2205 an implied impact to the related CHILD calendar components. This
2206 property is intended only to provide information on the
2207 relationship of calendar components. It is up to the target
2208 calendar system to maintain any property implications of this
2209 relationship.
2211 Description (:rfc:`9253`):
2212 By default or when VALUE=UID is specified, the property value
2213 consists of the persistent, globally unique identifier of another
2214 calendar component. This value would be represented in a calendar
2215 component by the UID property.
2217 By default, the property value
2218 points to another calendar component that has a PARENT relationship
2219 to the referencing object. The RELTYPE property parameter is used
2220 to either explicitly state the default PARENT relationship type to
2221 the referenced calendar component or to override the default
2222 PARENT relationship type and specify either a CHILD or SIBLING
2223 relationship or a temporal relationship.
2225 The PARENT relationship
2226 indicates that the calendar component is a subordinate of the
2227 referenced calendar component. The CHILD relationship indicates
2228 that the calendar component is a superior of the referenced calendar
2229 component. The SIBLING relationship indicates that the calendar
2230 component is a peer of the referenced calendar component.
2232 To preserve backwards compatibility, the value type MUST
2233 be UID when the PARENT, SIBLING, or CHILD relationships
2234 are specified.
2236 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2237 or STARTTOSTART relationships define temporal relationships, as
2238 specified in the RELTYPE parameter definition.
2240 The FIRST and NEXT
2241 define ordering relationships between calendar components.
2243 The DEPENDS-ON relationship indicates that the current calendar
2244 component depends on the referenced calendar component in some manner.
2245 For example, a task may be blocked waiting on the other,
2246 referenced, task.
2248 The REFID and CONCEPT relationships establish
2249 a reference from the current component to the referenced component.
2250 Changes to a calendar component referenced by this property
2251 can have an implicit impact on the related calendar component.
2252 For example, if a group event changes its start or end date or
2253 time, then the related, dependent events will need to have their
2254 start and end dates and times changed in a corresponding way.
2255 Similarly, if a PARENT calendar component is canceled or deleted,
2256 then there is an implied impact to the related CHILD calendar
2257 components. This property is intended only to provide information
2258 on the relationship of calendar components.
2260 Deletion of the target component, for example, the target of a
2261 FIRST, NEXT, or temporal relationship, can result in broken links.
2263 It is up to the target calendar system to maintain any property
2264 implications of these relationships.
2266 Examples:
2267 :rfc:`5545` examples of this property:
2269 .. code-block:: ics
2271 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2273 .. code-block:: ics
2275 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2277 :rfc:`9253` examples of this property:
2279 .. code-block:: ics
2281 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2282 https://example.com/caldav/user/jb/cal/
2283 19960401-080045-4000F192713.ics
2285 See also :class:`icalendar.enums.RELTYPE`.
2287 """
2288 result = self.get("RELATED-TO", [])
2289 if not isinstance(result, list):
2290 return [result]
2291 return result
2294def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2295 """Set the RELATED-TO properties."""
2296 _del_related_to(self)
2297 if values is None:
2298 return
2299 if not isinstance(values, list):
2300 values = [values]
2301 for value in values:
2302 self.add("RELATED-TO", value)
2305def _del_related_to(self: Component):
2306 """Delete the RELATED-TO properties."""
2307 self.pop("RELATED-TO", None)
2310related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2313def _get_concepts(self: Component) -> list[vUri]:
2314 """CONCEPT
2316 Purpose:
2317 CONCEPT defines the formal categories for a calendar component.
2319 Conformance:
2320 Since :rfc:`9253`,
2321 this property can be specified zero or more times in any iCalendar component.
2323 Description:
2324 This property is used to specify formal categories or classifications of
2325 the calendar component. The values are useful in searching for a calendar
2326 component of a particular type and category.
2328 This categorization is distinct from the more informal "tagging" of components
2329 provided by the existing CATEGORIES property. It is expected that the value of
2330 the CONCEPT property will reference an external resource that provides
2331 information about the categorization.
2333 In addition, a structured URI value allows for hierarchical categorization of
2334 events.
2336 Possible category resources are the various proprietary systems, for example,
2337 the Library of Congress, or an open source of categorization data.
2339 Examples:
2340 The following is an example of this property.
2341 It points to a server acting as the source for the calendar object.
2343 .. code-block:: ics
2345 CONCEPT:https://example.com/event-types/arts/music
2347 .. seealso::
2349 :attr:`icalendar.prop.categories.vCategory`
2350 """
2351 concepts = self.get("CONCEPT", [])
2352 if not isinstance(concepts, list):
2353 concepts = [concepts]
2354 return concepts
2357CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2360def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2361 """Set the concepts."""
2362 _del_concepts(self)
2363 if concepts is None:
2364 return
2365 if not isinstance(concepts, list):
2366 concepts = [concepts]
2367 for value in concepts:
2368 self.add("CONCEPT", value)
2371def _del_concepts(self: Component):
2372 """Delete the concepts."""
2373 self.pop("CONCEPT", None)
2376concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2379def multi_string_property(name: str, doc: str):
2380 """A property for an iCalendar Property that can occur multiple times."""
2382 def fget(self: Component) -> list[str]:
2383 """Get the values of a multi-string property."""
2384 value = self.get(name, [])
2385 if not isinstance(value, list):
2386 value = [value]
2387 return value
2389 def fset(self: Component, value: list[str] | str | None) -> None:
2390 """Set the values of a multi-string property."""
2391 fdel(self)
2392 if value is None:
2393 return
2394 if not isinstance(value, list):
2395 value = [value]
2396 for value in value:
2397 self.add(name, value)
2399 def fdel(self: Component):
2400 """Delete the values of a multi-string property."""
2401 self.pop(name, None)
2403 return property(fget, fset, fdel, doc=doc)
2406refids_property = multi_string_property(
2407 "REFID",
2408 """REFID
2410Purpose:
2411 REFID acts as a key for associated iCalendar entities.
2413Conformance:
2414 Since :rfc:`9253`,
2415 this property can be specified zero or more times in any iCalendar component.
2417Description:
2418 The value of this property is free-form text that creates an
2419 identifier for associated components.
2420 All components that use the same REFID value are associated through
2421 that value and can be located or retrieved as a group.
2422 For example, all of the events in a travel itinerary
2423 would have the same REFID value, so as to be grouped together.
2425Examples:
2426 The following is an example of this property.
2428 .. code-block:: ics
2430 REFID:itinerary-2014-11-17
2432 Use a REFID to associate several VTODOs:
2434 .. code-block:: pycon
2436 >>> from icalendar import Todo
2437 >>> todo_1 = Todo.new(
2438 ... summary="turn off stove",
2439 ... refids=["travel", "alps"]
2440 ... )
2441 >>> todo_2 = Todo.new(
2442 ... summary="pack backpack",
2443 ... refids=["travel", "alps"]
2444 ... )
2445 >>> todo_1.refids == todo_2.refids
2446 True
2448.. note::
2450 List modifications do not modify the component.
2451""",
2452)
2455__all__ = [
2456 "CONCEPTS_TYPE_SETTER",
2457 "LINKS_TYPE_SETTER",
2458 "RECURRENCE_ID",
2459 "RELATED_TO_TYPE_SETTER",
2460 "attendees_property",
2461 "busy_type_property",
2462 "categories_property",
2463 "class_property",
2464 "color_property",
2465 "comments_property",
2466 "concepts_property",
2467 "conferences_property",
2468 "contacts_property",
2469 "create_single_property",
2470 "description_property",
2471 "descriptions_property",
2472 "duration_property",
2473 "exdates_property",
2474 "get_duration_property",
2475 "get_end_property",
2476 "get_start_end_duration_with_validation",
2477 "get_start_property",
2478 "images_property",
2479 "links_property",
2480 "location_property",
2481 "multi_language_text_property",
2482 "multi_string_property",
2483 "organizer_property",
2484 "priority_property",
2485 "property_del_duration",
2486 "property_doc_duration_template",
2487 "property_get_duration",
2488 "property_set_duration",
2489 "rdates_property",
2490 "refids_property",
2491 "related_to_property",
2492 "rfc_7953_dtend_property",
2493 "rfc_7953_dtstart_property",
2494 "rfc_7953_duration_property",
2495 "rfc_7953_end_property",
2496 "rrules_property",
2497 "sequence_property",
2498 "set_duration_with_locking",
2499 "set_end_with_locking",
2500 "set_start_with_locking",
2501 "single_int_property",
2502 "single_utc_property",
2503 "source_property",
2504 "status_property",
2505 "summary_property",
2506 "transparency_property",
2507 "uid_property",
2508 "url_property",
2509]