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
964 :exc:`~icalendar.error.InvalidCalendar`.
965 If the value is absent, we return None.
966 You can also delete the value with del or by setting it to None.
967 """
968 return property(p_get, p_set, p_del, p_doc)
971X_MOZ_SNOOZE_TIME_property = single_utc_property(
972 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
973)
974X_MOZ_LASTACK_property = single_utc_property(
975 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
976)
979def property_get_duration(self: Component) -> timedelta | None:
980 """Getter for property DURATION."""
981 default = object()
982 duration = self.get("duration", default)
983 if duration is default:
984 return None
985 result = getattr(duration, "td", None)
986 if result is None:
987 raise InvalidCalendar(
988 f"DURATION must be a timedelta, not {type(duration).__name__}."
989 )
990 return result
993def property_set_duration(self: Component, value: timedelta | None):
994 """Setter for property DURATION."""
995 if value is None:
996 self.pop("duration", None)
997 return
998 if not isinstance(value, timedelta):
999 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
1000 self["duration"] = vDuration(value)
1001 self.pop("DTEND")
1002 self.pop("DUE")
1005def property_del_duration(self: Component):
1006 """Delete property DURATION."""
1007 self.pop("DURATION")
1010property_doc_duration_template = """The DURATION property.
1012The "DTSTART" property for a "{component}" specifies the inclusive
1013start of the {component}.
1014The "DURATION" property in conjunction with the DTSTART property
1015for a "{component}" calendar component specifies the non-inclusive end
1016of the event.
1018If you would like to calculate the duration of a {component}, do not use this.
1019Instead use the duration property (lower case).
1020"""
1023def duration_property(component: str) -> property:
1024 """Return the duration property."""
1025 return property(
1026 property_get_duration,
1027 property_set_duration,
1028 property_del_duration,
1029 property_doc_duration_template.format(component=component),
1030 )
1033def multi_text_property(name: str, docs: str) -> property:
1034 """Get a property that can occur several times and is text.
1036 Examples: Journal.descriptions, Event.comments
1037 """
1039 def fget(self: Component) -> list[str]:
1040 """Get the values."""
1041 descriptions = self.get(name)
1042 if descriptions is None:
1043 return []
1044 if not isinstance(descriptions, SEQUENCE_TYPES):
1045 return [descriptions]
1046 return descriptions
1048 def fset(self: Component, values: str | Sequence[str] | None):
1049 """Set the values."""
1050 fdel(self)
1051 if values is None:
1052 return
1053 if isinstance(values, str):
1054 self.add(name, values)
1055 else:
1056 for description in values:
1057 self.add(name, description)
1059 def fdel(self: Component):
1060 """Delete the values."""
1061 self.pop(name)
1063 return property(fget, fset, fdel, docs)
1066descriptions_property = multi_text_property(
1067 "DESCRIPTION",
1068 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1070Property Parameters:
1071 IANA, non-standard, alternate text
1072 representation, and language property parameters can be specified
1073 on this property.
1075Conformance:
1076 The property can be
1077 specified multiple times only within a "VJOURNAL" calendar component.
1079Description:
1080 This property is used in the "VJOURNAL" calendar component to
1081 capture one or more textual journal entries.
1083Examples:
1084 The following is an example of this property with formatted
1085 line breaks in the property value:
1087 .. code-block:: pycon
1089 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1090 design.\\nHappy Face Conference Room. Phoenix design team
1091 MUST attend this meeting.\\nRSVP to team leader.
1093""", # noqa: E501
1094)
1096comments_property = multi_text_property(
1097 "COMMENT",
1098 """COMMENT is used to specify a comment to the calendar user.
1100Purpose:
1101 This property specifies non-processing information intended
1102 to provide a comment to the calendar user.
1104Conformance:
1105 In :rfc:`5545`, this property can be specified multiple times in
1106 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1107 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1108 In :rfc:`7953`, this property can be specified multiple times in
1109 "VAVAILABILITY" and "VAVAILABLE".
1111Property Parameters:
1112 IANA, non-standard, alternate text
1113 representation, and language property parameters can be specified
1114 on this property.
1116""",
1117)
1119RECURRENCE_ID = create_single_property(
1120 "RECURRENCE-ID",
1121 "dt",
1122 (date, datetime),
1123 date | datetime,
1124 """
1125Identify a specific occurrence of a recurring calendar object.
1127This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1128particular instance in a recurrence set. The value is the original start
1129date or date-time of that instance, not the rescheduled time.
1131The value is usually a DATE-TIME and must use the same value type as the
1132``DTSTART`` property in the same component. A DATE value may be used for
1133all-day items instead.
1135This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1136may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1137""",
1138 vDDDTypes,
1139)
1142def _get_organizer(self: Component) -> vCalAddress | None:
1143 """ORGANIZER defines the organizer for a calendar component.
1145 Property Parameters:
1146 IANA, non-standard, language, common name,
1147 directory entry reference, and sent-by property parameters can be
1148 specified on this property.
1150 Conformance:
1151 This property MUST be specified in an iCalendar object
1152 that specifies a group-scheduled calendar entity. This property
1153 MUST be specified in an iCalendar object that specifies the
1154 publication of a calendar user's busy time. This property MUST
1155 NOT be specified in an iCalendar object that specifies only a time
1156 zone definition or that defines calendar components that are not
1157 group-scheduled components, but are components only on a single
1158 user's calendar.
1160 Description:
1161 This property is specified within the "VEVENT",
1162 "VTODO", and "VJOURNAL" calendar components to specify the
1163 organizer of a group-scheduled calendar entity. The property is
1164 specified within the "VFREEBUSY" calendar component to specify the
1165 calendar user requesting the free or busy time. When publishing a
1166 "VFREEBUSY" calendar component, the property is used to specify
1167 the calendar that the published busy time came from.
1169 The property has the property parameters "CN", for specifying the
1170 common or display name associated with the "Organizer", "DIR", for
1171 specifying a pointer to the directory information associated with
1172 the "Organizer", "SENT-BY", for specifying another calendar user
1173 that is acting on behalf of the "Organizer". The non-standard
1174 parameters may also be specified on this property. If the
1175 "LANGUAGE" property parameter is specified, the identified
1176 language applies to the "CN" parameter value.
1177 """
1178 return self.get("ORGANIZER")
1181def _set_organizer(self: Component, value: vCalAddress | str | None):
1182 """Set the value."""
1183 _del_organizer(self)
1184 if value is not None:
1185 self.add("ORGANIZER", value)
1188def _del_organizer(self: Component):
1189 """Delete the value."""
1190 self.pop("ORGANIZER")
1193organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1196def single_string_enum_property(
1197 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1198) -> property:
1199 """Create a property to access a single string value and convert it to an enum."""
1200 prop = single_string_property(name, docs, default=default)
1202 def fget(self: Component) -> StrEnum:
1203 """Get the value."""
1204 value = prop.fget(self)
1205 if value == default:
1206 return default
1207 return enum(str(value))
1209 def fset(self: Component, value: str | StrEnum | None) -> None:
1210 """Set the value."""
1211 if value == "":
1212 value = None
1213 prop.fset(self, value)
1215 return property(fget, fset, prop.fdel, doc=docs)
1218busy_type_property = single_string_enum_property(
1219 "BUSYTYPE",
1220 BUSYTYPE,
1221 BUSYTYPE.BUSY_UNAVAILABLE,
1222 """BUSYTYPE specifies the default busy time type.
1224Returns:
1225 :class:`icalendar.enums.BUSYTYPE`
1227Description:
1228 This property is used to specify the default busy time
1229 type. The values correspond to those used by the FBTYPE"
1230 parameter used on a "FREEBUSY" property, with the exception that
1231 the "FREE" value is not used in this property. If not specified
1232 on a component that allows this property, the default is "BUSY-
1233 UNAVAILABLE".
1234""",
1235)
1237priority_property = single_int_property(
1238 "PRIORITY",
1239 0,
1240 """
1242Conformance:
1243 This property can be specified in "VEVENT" and "VTODO" calendar components
1244 according to :rfc:`5545`.
1245 :rfc:`7953` adds this property to "VAVAILABILITY".
1247Description:
1248 This priority is specified as an integer in the range 0
1249 to 9. A value of 0 specifies an undefined priority. A value of 1
1250 is the highest priority. A value of 2 is the second highest
1251 priority. Subsequent numbers specify a decreasing ordinal
1252 priority. A value of 9 is the lowest priority.
1254 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1255 "LOW" is mapped into this property such that a property value in
1256 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1257 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1258 is "LOW" priority.
1260 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1261 "C3" is mapped into this property such that a property value of 1
1262 specifies "A1", a property value of 2 specifies "A2", a property
1263 value of 3 specifies "A3", and so forth up to a property value of
1264 9 specifies "C3".
1266 Other integer values are reserved for future use.
1268 Within a "VEVENT" calendar component, this property specifies a
1269 priority for the event. This property may be useful when more
1270 than one event is scheduled for a given time period.
1272 Within a "VTODO" calendar component, this property specified a
1273 priority for the to-do. This property is useful in prioritizing
1274 multiple action items for a given time period.
1275""",
1276)
1278class_property = single_string_enum_property(
1279 "CLASS",
1280 CLASS,
1281 CLASS.PUBLIC,
1282 """CLASS specifies the class of the calendar component.
1284Returns:
1285 :class:`icalendar.enums.CLASS`
1287Description:
1288 An access classification is only one component of the
1289 general security system within a calendar application. It
1290 provides a method of capturing the scope of the access the
1291 calendar owner intends for information within an individual
1292 calendar entry. The access classification of an individual
1293 iCalendar component is useful when measured along with the other
1294 security components of a calendar system (e.g., calendar user
1295 authentication, authorization, access rights, access role, etc.).
1296 Hence, the semantics of the individual access classifications
1297 cannot be completely defined by this memo alone. Additionally,
1298 due to the "blind" nature of most exchange processes using this
1299 memo, these access classifications cannot serve as an enforcement
1300 statement for a system receiving an iCalendar object. Rather,
1301 they provide a method for capturing the intention of the calendar
1302 owner for the access to the calendar component. If not specified
1303 in a component that allows this property, the default value is
1304 PUBLIC. Applications MUST treat x-name and iana-token values they
1305 don't recognize the same way as they would the PRIVATE value.
1306""",
1307)
1309transparency_property = single_string_enum_property(
1310 "TRANSP",
1311 TRANSP,
1312 TRANSP.OPAQUE,
1313 """TRANSP defines whether or not an event is transparent to busy time searches.
1315Returns:
1316 :class:`icalendar.enums.TRANSP`
1318Description:
1319 Time Transparency is the characteristic of an event
1320 that determines whether it appears to consume time on a calendar.
1321 Events that consume actual time for the individual or resource
1322 associated with the calendar SHOULD be recorded as OPAQUE,
1323 allowing them to be detected by free/busy time searches. Other
1324 events, which do not take up the individual's (or resource's) time
1325 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1326 busy time searches.
1327""",
1328)
1329status_property = single_string_enum_property(
1330 "STATUS",
1331 STATUS,
1332 "",
1333 """STATUS defines the overall status or confirmation for the calendar component.
1335Returns:
1336 :class:`icalendar.enums.STATUS`
1338The default value is ``""``.
1340Description:
1341 In a group-scheduled calendar component, the property
1342 is used by the "Organizer" to provide a confirmation of the event
1343 to the "Attendees". For example in a "VEVENT" calendar component,
1344 the "Organizer" can indicate that a meeting is tentative,
1345 confirmed, or cancelled. In a "VTODO" calendar component, the
1346 "Organizer" can indicate that an action item needs action, is
1347 completed, is in process or being worked on, or has been
1348 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1349 can indicate that a journal entry is draft, final, or has been
1350 cancelled or removed.
1351""",
1352)
1354url_property = single_string_property(
1355 "URL",
1356 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1358Description:
1359 This property may be used in a calendar component to
1360 convey a location where a more dynamic rendition of the calendar
1361 information associated with the calendar component can be found.
1362 This memo does not attempt to standardize the form of the URI, nor
1363 the format of the resource pointed to by the property value. If
1364 the URL property and Content-Location MIME header are both
1365 specified, they MUST point to the same resource.
1367Conformance:
1368 This property can be specified once in the "VEVENT",
1369 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1370 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1372Example:
1373 The following is an example of this property:
1375 .. code-block:: ics
1377 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1379""",
1380)
1382source_property = single_string_property(
1383 "SOURCE",
1384 """A URI from where calendar data can be refreshed.
1386Description:
1387 This property identifies a location where a client can
1388 retrieve updated data for the calendar. Clients SHOULD honor any
1389 specified "REFRESH-INTERVAL" value when periodically retrieving
1390 data. Note that this property differs from the "URL" property in
1391 that "URL" is meant to provide an alternative representation of
1392 the calendar data rather than the original location of the data.
1394Conformance:
1395 This property can be specified once in an iCalendar object.
1397Example:
1398 The following is an example of this property:
1400 .. code-block:: ics
1402 SOURCE;VALUE=URI:https://example.com/holidays.ics
1404""",
1405)
1407location_property = multi_language_text_property(
1408 "LOCATION",
1409 None,
1410 """The intended venue for the activity defined by a calendar component.
1412Property Parameters:
1413 IANA, non-standard, alternate text
1414 representation, and language property parameters can be specified
1415 on this property.
1417Conformance:
1418 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1419 calendar component.
1420 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1422Description:
1423 Specific venues such as conference or meeting rooms may
1424 be explicitly specified using this property. An alternate
1425 representation may be specified that is a URI that points to
1426 directory information with more structured specification of the
1427 location. For example, the alternate representation may specify
1428 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1429 CID URL :rfc:`2392` pointing to a MIME body part containing a
1430 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1432""",
1433)
1435contacts_property = multi_text_property(
1436 "CONTACT",
1437 """Contact information associated with the calendar component.
1439Purpose:
1440 This property is used to represent contact information or
1441 alternately a reference to contact information associated with the
1442 calendar component.
1444Property Parameters:
1445 IANA, non-standard, alternate text
1446 representation, and language property parameters can be specified
1447 on this property.
1449Conformance:
1450 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1451 "VJOURNAL", or "VFREEBUSY" calendar component.
1452 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1453 amd "VAVAILABLE" calendar component.
1455Description:
1456 The property value consists of textual contact
1457 information. An alternative representation for the property value
1458 can also be specified that refers to a URI pointing to an
1459 alternate form, such as a vCard :rfc:`2426`, for the contact
1460 information.
1462Example:
1463 The following is an example of this property referencing
1464 textual contact information:
1466 .. code-block:: ics
1468 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1470 The following is an example of this property with an alternate
1471 representation of an LDAP URI to a directory entry containing the
1472 contact information:
1474 .. code-block:: ics
1476 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1477 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1478 +1-919-555-1234
1480 The following is an example of this property with an alternate
1481 representation of a MIME body part containing the contact
1482 information, such as a vCard :rfc:`2426` embedded in a text/
1483 directory media type :rfc:`2425`:
1485 .. code-block:: ics
1487 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1488 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1490 The following is an example of this property referencing a network
1491 resource, such as a vCard :rfc:`2426` object containing the contact
1492 information:
1494 .. code-block:: ics
1496 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1497 Dolittle\\, ABC Industries\\, +1-919-555-1234
1498""",
1499)
1502def timezone_datetime_property(name: str, docs: str):
1503 """Create a property to access the values with a proper timezone."""
1505 return single_utc_property(name, docs)
1508rfc_7953_dtstart_property = timezone_datetime_property(
1509 "DTSTART",
1510 """Start of the component.
1512 This is almost the same as :attr:`Event.DTSTART` with one exception:
1513 The values MUST have a timezone and DATE is not allowed.
1515 Description:
1516 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1517 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1518 "DATE-TIME" values specified as either the date with UTC time or
1519 the date with local time and a time zone reference.
1521 """,
1522)
1524rfc_7953_dtend_property = timezone_datetime_property(
1525 "DTEND",
1526 """Start of the component.
1528 This is almost the same as :attr:`Event.DTEND` with one exception:
1529 The values MUST have a timezone and DATE is not allowed.
1531 Description:
1532 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1533 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1534 "DATE-TIME" values specified as either the date with UTC time or
1535 the date with local time and a time zone reference.
1536 """,
1537)
1540@property
1541def rfc_7953_duration_property(self) -> timedelta | None:
1542 """Compute the duration of this component.
1544 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1545 Otherwise, the duration is calculated from :attr:`DTSTART` and
1546 :attr:`DTEND`/:attr:`DURATION`.
1548 This is in accordance with :rfc:`7953`:
1549 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1550 """
1551 duration = self.DURATION
1552 if duration:
1553 return duration
1554 end = self.DTEND
1555 if end is None:
1556 return None
1557 start = self.DTSTART
1558 if start is None:
1559 raise IncompleteComponent("Cannot compute duration without start.")
1560 return end - start
1563@property
1564def rfc_7953_end_property(self) -> timedelta | None:
1565 """Compute the duration of this component.
1567 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1568 Otherwise, the duration is calculated from :attr:`DTSTART` and
1569 :attr:`DTEND`/:attr:`DURATION`.
1571 This is in accordance with :rfc:`7953`:
1572 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1573 """
1574 duration = self.DURATION
1575 if duration:
1576 start = self.DTSTART
1577 if start is None:
1578 raise IncompleteComponent("Cannot compute end without start.")
1579 return start + duration
1580 end = self.DTEND
1581 if end is None:
1582 return None
1583 return end
1586@rfc_7953_end_property.setter
1587def rfc_7953_end_property(self, value: datetime):
1588 self.DTEND = value
1591@rfc_7953_end_property.deleter
1592def rfc_7953_end_property(self):
1593 del self.DTEND
1596def get_start_end_duration_with_validation(
1597 component: Component,
1598 start_property: str,
1599 end_property: str,
1600 component_name: str,
1601) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1602 """
1603 Validate the component and return start, end, and duration.
1605 This tests validity according to :rfc:`5545` rules
1606 for ``Event`` and ``Todo`` components.
1608 Parameters:
1609 component: The component to validate, either ``Event`` or ``Todo``.
1610 start_property: The start property name, ``DTSTART``.
1611 end_property: The end property name, either ``DTEND`` for ``Event`` or
1612 ``DUE`` for ``Todo``.
1613 component_name: The component name for error messages,
1614 either ``VEVENT`` or ``VTODO``.
1616 Returns:
1617 tuple: (start, end, duration) values from the component.
1619 Raises:
1620 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1622 """
1623 start = getattr(component, start_property, None)
1624 end = getattr(component, end_property, None)
1625 duration = component.DURATION
1627 # RFC 5545: Only one of end property and DURATION may be present
1628 if duration is not None and end is not None:
1629 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1630 msg = (
1631 f"Only one of {end_name} and DURATION "
1632 f"may be in a {component_name}, not both."
1633 )
1634 raise InvalidCalendar(msg)
1636 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1637 if (
1638 start is not None
1639 and is_date(start)
1640 and duration is not None
1641 and duration.seconds != 0
1642 ):
1643 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1644 raise InvalidCalendar(msg)
1646 # RFC 5545: DTSTART and end property must be of the same type
1647 if start is not None and end is not None and is_date(start) != is_date(end):
1648 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1649 msg = (
1650 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1651 )
1652 raise InvalidCalendar(msg)
1654 return start, end, duration
1657def get_start_property(component: Component) -> date | datetime:
1658 """
1659 Get the start property with validation.
1661 Parameters:
1662 component: The component from which to get its start property.
1664 Returns:
1665 The ``DTSTART`` value.
1667 Raises:
1668 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1670 """
1671 # Trigger validation by calling _get_start_end_duration
1672 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1673 if start is None:
1674 msg = "No DTSTART given."
1675 raise IncompleteComponent(msg)
1676 return start
1679def get_end_property(component: Component, end_property: str) -> date | datetime:
1680 """
1681 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1683 Parameters:
1684 component: The component to get end from
1685 end_property: The end property name, either ``DTEND`` for ``Event`` or
1686 ``DUE`` for ``Todo``.
1688 Returns:
1689 The computed end value.
1691 Raises:
1692 ~error.IncompleteComponent: If the provided information is incomplete
1693 to compute the end property.
1695 """
1696 # Trigger validation by calling _get_start_end_duration
1697 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1699 if end is None and duration is None:
1700 if start is None:
1701 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1702 msg = f"No {end_name} or DURATION+DTSTART given."
1703 raise IncompleteComponent(msg)
1705 # Default behavior differs for Event vs Todo:
1706 # Event: date gets +1 day, datetime gets same time
1707 # Todo: both date and datetime get same time (issue #898)
1708 if end_property == "DTEND" and is_date(start):
1709 return start + timedelta(days=1)
1710 return start
1712 if duration is not None:
1713 if start is not None:
1714 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1715 return start
1716 return start + duration
1717 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1718 msg = f"No {end_name} or DURATION+DTSTART given."
1719 raise IncompleteComponent(msg)
1721 return end
1724def get_duration_property(component: Component) -> timedelta:
1725 """
1726 Get the duration property with fallback calculation from start and end.
1728 Parameters:
1729 component: The component from which to get its duration property.
1731 Returns:
1732 The duration as a timedelta.
1734 """
1735 # First check if DURATION property is explicitly set
1736 if "DURATION" in component:
1737 return component["DURATION"].dt
1739 # Fall back to calculated duration from start and end
1740 return component.end - component.start
1743def set_duration_with_locking(
1744 component: Component,
1745 duration: timedelta | None,
1746 locked: Literal["start", "end"],
1747 end_property: str,
1748) -> None:
1749 """
1750 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1752 Parameters:
1753 component: The component to modify, either ``Event`` or ``Todo``.
1754 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1755 locked: Which property to keep unchanged, either ``start`` or ``end``.
1756 end_property: The end property name, either ``DTEND`` for ``Event`` or
1757 ``DUE`` for ``Todo``.
1759 """
1760 # Convert to DURATION property if duration is None
1761 if duration is None:
1762 if "DURATION" in component:
1763 return # Already has DURATION property
1764 current_duration = component.duration
1765 component.DURATION = current_duration
1766 return
1768 if not isinstance(duration, timedelta):
1769 msg = f"Use timedelta, not {type(duration).__name__}."
1770 raise TypeError(msg)
1772 # Validate date/duration compatibility
1773 start = component.DTSTART
1774 if start is not None and is_date(start) and duration.seconds != 0:
1775 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1776 raise InvalidCalendar(msg)
1778 if locked == "start":
1779 # Keep start locked, adjust end
1780 if start is None:
1781 msg = "Cannot set duration without DTSTART. Set start time first."
1782 raise IncompleteComponent(msg)
1783 component.pop(end_property, None) # Remove end property
1784 component.DURATION = duration
1785 elif locked == "end":
1786 # Keep end locked, adjust start
1787 current_end = component.end
1788 component.DTSTART = current_end - duration
1789 component.pop(end_property, None) # Remove end property
1790 component.DURATION = duration
1791 else:
1792 msg = f"locked must be 'start' or 'end', not {locked!r}"
1793 raise ValueError(msg)
1796def set_start_with_locking(
1797 component: Component,
1798 start: date | datetime,
1799 locked: Literal["duration", "end"] | None,
1800 end_property: str,
1801) -> None:
1802 """
1803 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1805 Parameters:
1806 component: The component to modify, either ``Event`` or ``Todo``.
1807 start: The start time to set.
1808 locked: Which property to keep unchanged, either ``duration``, ``end``,
1809 or ``None`` for auto-detect.
1810 end_property: The end property name, either ``DTEND`` for ``Event`` or
1811 ``DUE`` for ``Todo``.
1813 """
1814 if locked is None:
1815 # Auto-detect based on existing properties
1816 if "DURATION" in component:
1817 locked = "duration"
1818 elif end_property in component:
1819 locked = "end"
1820 else:
1821 # Default to duration if no existing properties
1822 locked = "duration"
1824 if locked == "duration":
1825 # Keep duration locked, adjust end
1826 current_duration = (
1827 component.duration
1828 if "DURATION" in component or end_property in component
1829 else None
1830 )
1831 component.DTSTART = start
1832 if current_duration is not None:
1833 component.pop(end_property, None) # Remove end property
1834 component.DURATION = current_duration
1835 elif locked == "end":
1836 # Keep end locked, adjust duration
1837 current_end = component.end
1838 component.DTSTART = start
1839 component.pop("DURATION", None) # Remove duration property
1840 setattr(component, end_property, current_end)
1841 else:
1842 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1843 raise ValueError(msg)
1846def set_end_with_locking(
1847 component: Component,
1848 end: date | datetime,
1849 locked: Literal["start", "duration"],
1850 end_property: str,
1851) -> None:
1852 """
1853 Set the end with explicit locking behavior for Event and Todo components.
1855 Parameters:
1856 component: The component to modify, either ``Event`` or ``Todo``.
1857 end: The end time to set.
1858 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1859 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1860 for ``Todo``.
1862 """
1863 if locked == "start":
1864 # Keep start locked, adjust duration
1865 component.pop("DURATION", None) # Remove duration property
1866 setattr(component, end_property, end)
1867 elif locked == "duration":
1868 # Keep duration locked, adjust start
1869 current_duration = component.duration
1870 component.DTSTART = end - current_duration
1871 component.pop(end_property, None) # Remove end property
1872 component.DURATION = current_duration
1873 else:
1874 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1875 raise ValueError(msg)
1878def _get_images(self: Component) -> list[Image]:
1879 """IMAGE specifies an image associated with the calendar or a calendar component.
1881 Description:
1882 This property specifies an image for an iCalendar
1883 object or a calendar component via a URI or directly with inline
1884 data that can be used by calendar user agents when presenting the
1885 calendar data to a user. Multiple properties MAY be used to
1886 specify alternative sets of images with, for example, varying
1887 media subtypes, resolutions, or sizes. When multiple properties
1888 are present, calendar user agents SHOULD display only one of them,
1889 picking one that provides the most appropriate image quality, or
1890 display none. The "DISPLAY" parameter is used to indicate the
1891 intended display mode for the image. The "ALTREP" parameter,
1892 defined in :rfc:`5545`, can be used to provide a "clickable" image
1893 where the URI in the parameter value can be "launched" by a click
1894 on the image in the calendar user agent.
1896 Conformance:
1897 This property can be specified multiple times in an
1898 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1899 components.
1901 .. note::
1903 At the present moment, this property is read-only. If you require a setter,
1904 please open an issue or a pull request.
1905 """
1906 images = self.get("IMAGE", [])
1907 if not isinstance(images, SEQUENCE_TYPES):
1908 images = [images]
1909 return [Image.from_property_value(img) for img in images]
1912images_property = property(_get_images)
1915def _get_conferences(self: Component) -> list[Conference]:
1916 """Return the CONFERENCE properties as a list.
1918 Purpose:
1919 This property specifies information for accessing a conferencing system.
1921 Conformance:
1922 This property can be specified multiple times in a
1923 "VEVENT" or "VTODO" calendar component.
1925 Description:
1926 This property specifies information for accessing a
1927 conferencing system for attendees of a meeting or task. This
1928 might be for a telephone-based conference number dial-in with
1929 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1930 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1931 or https: URI :rfc:`7230`), or for an instant messaging group chat
1932 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1933 conferencing system is not available, a data: URI :rfc:`2397`
1934 containing a text description can be used.
1936 A conference system can be a bidirectional communication channel
1937 or a uni-directional "broadcast feed".
1939 The "FEATURE" property parameter is used to describe the key
1940 capabilities of the conference system to allow a client to choose
1941 the ones that give the required level of interaction from a set of
1942 multiple properties.
1944 The "LABEL" property parameter is used to convey additional
1945 details on the use of the URI. For example, the URIs or access
1946 codes for the moderator and attendee of a teleconference system
1947 could be different, and the "LABEL" property parameter could be
1948 used to "tag" each "CONFERENCE" property to indicate which is
1949 which.
1951 The "LANGUAGE" property parameter can be used to specify the
1952 language used for text values used with this property (as per
1953 Section 3.2.10 of :rfc:`5545`).
1955 Example:
1956 The following are examples of this property:
1958 .. code-block:: ics
1960 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1961 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1962 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1963 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1964 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1965 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1966 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1967 LABEL=Chat room:xmpp:chat-123@conference.example.com
1968 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1969 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1971 Get all conferences:
1973 .. code-block:: pycon
1975 >>> from icalendar import Event
1976 >>> event = Event()
1977 >>> event.conferences
1978 []
1980 Set a conference:
1982 .. code-block:: pycon
1984 >>> from icalendar import Event, Conference
1985 >>> event = Event()
1986 >>> event.conferences = [
1987 ... Conference(
1988 ... "tel:+1-412-555-0123,,,654321",
1989 ... feature="PHONE,MODERATOR",
1990 ... label="Moderator dial-in",
1991 ... language="EN",
1992 ... )
1993 ... ]
1994 >>> print(event.to_ical())
1995 BEGIN:VEVENT
1996 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
1997 ALUE=URI:tel:+1-412-555-0123,,,654321
1998 END:VEVENT
2000 """
2001 conferences = self.get("CONFERENCE", [])
2002 if not isinstance(conferences, SEQUENCE_TYPES):
2003 conferences = [conferences]
2004 return [Conference.from_uri(conference) for conference in conferences]
2007def _set_conferences(self: Component, conferences: list[Conference] | None):
2008 """Set the conferences."""
2009 _del_conferences(self)
2010 for conference in conferences or []:
2011 self.add("CONFERENCE", conference.to_uri())
2014def _del_conferences(self: Component):
2015 """Delete all conferences."""
2016 self.pop("CONFERENCE")
2019conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2022def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2023 """LINK properties as a list.
2025 Purpose:
2026 LINK provides a reference to external information related to a component.
2028 Property Parameters:
2029 The VALUE parameter is required.
2030 Non-standard, link relation type, format type, label, and language parameters
2031 can also be specified on this property.
2032 The LABEL parameter is defined in :rfc:`7986`.
2034 Conformance:
2035 This property can be specified zero or more times in any iCalendar component.
2036 LINK is specified in :rfc:`9253`.
2037 The LINKREL parameter is required.
2039 Description:
2040 When used in a component, the value of this property points to
2041 additional information related to the component.
2042 For example, it may reference the originating web server.
2044 This property is a serialization of the model in :rfc:`8288`,
2045 where the link target is carried in the property value,
2046 the link context is the containing calendar entity,
2047 and the link relation type and any target attributes
2048 are carried in iCalendar property parameters.
2050 The LINK property parameters map to :rfc:`8288` attributes as follows:
2052 LABEL
2053 This parameter maps to the "title"
2054 attribute defined in Section 3.4.1 of :rfc:`8288`.
2055 LABEL is used to label the destination
2056 of a link such that it can be used as a human-readable identifier
2057 (e.g., a menu entry) in the language indicated by the LANGUAGE
2058 (if present).
2059 LANGUAGE
2060 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2061 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2062 LINKREL
2063 This parameter maps to the link relation type defined in Section 2.1 of
2064 :rfc:`8288`. See `Registered Link Relation Types
2065 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2066 FMTTYPE
2067 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2068 :rfc:`8288`.
2070 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2072 Examples:
2073 The following is an example of this property,
2074 which provides a reference to the source for the calendar object.
2076 .. code-block:: ics
2078 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2079 https://example.com/events
2081 The following is an example of this property,
2082 which provides a reference to an entity from which this one was derived.
2083 The link relation is a vendor-defined value.
2085 .. code-block:: ics
2087 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2088 VALUE=URI:
2089 https://example.com/tasks/01234567-abcd1234.ics
2091 The following is an example of this property,
2092 which provides a reference to a fragment of an XML document.
2093 The link relation is a vendor-defined value.
2095 .. code-block:: ics
2097 LINK;LINKREL="https://example.com/linkrel/costStructure";
2098 VALUE=XML-REFERENCE:
2099 https://example.com/xmlDocs/bidFramework.xml
2100 #xpointer(descendant::CostStruc/range-to(
2101 following::CostStrucEND[1]))
2103 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2105 .. code-block:: pycon
2107 >>> from icalendar import Event, vUri
2108 >>> from datetime import datetime
2109 >>> link = vUri(
2110 ... "http://example.com/event-page",
2111 ... params={"LINKREL":"SOURCE"}
2112 ... )
2113 >>> event = Event.new(
2114 ... start=datetime(2025, 9, 17, 12, 0),
2115 ... summary="An Example Event with a page"
2116 ... )
2117 >>> event.links = [link]
2118 >>> print(event.to_ical())
2119 BEGIN:VEVENT
2120 SUMMARY:An Example Event with a page
2121 DTSTART:20250917T120000
2122 DTSTAMP:20250517T080612Z
2123 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2124 LINK;LINKREL="SOURCE":http://example.com/event-page
2125 END:VEVENT
2127 """
2128 links = self.get("LINK", [])
2129 if not isinstance(links, list):
2130 links = [links]
2131 return links
2134LINKS_TYPE_SETTER: TypeAlias = (
2135 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2136)
2139def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2140 """Set the LINKs."""
2141 _del_links(self)
2142 if links is None:
2143 return
2144 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2145 links = [links]
2146 for link in links:
2147 if type(link) is str:
2148 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2149 self.add("LINK", link)
2152def _del_links(self: Component) -> None:
2153 """Delete all links."""
2154 self.pop("LINK")
2157links_property = property(_get_links, _set_links, _del_links)
2159RELATED_TO_TYPE_SETTER: TypeAlias = (
2160 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2161)
2164def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2165 """RELATED-TO properties as a list.
2167 Purpose:
2168 This property is used to represent a relationship or reference
2169 between one calendar component and another.
2170 :rfc:`9523` allows URI or UID values and a GAP parameter.
2172 Value Type:
2173 :rfc:`5545`: TEXT
2174 :rfc:`9253`: URI, UID
2176 Conformance:
2177 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2178 "VTODO", and "VJOURNAL" calendar components.
2179 Since :rfc:`9523`, this property MAY be specified in any
2180 iCalendar component.
2182 Description (:rfc:`5545`):
2183 The property value consists of the persistent, globally
2184 unique identifier of another calendar component. This value would
2185 be represented in a calendar component by the "UID" property.
2187 By default, the property value points to another calendar
2188 component that has a PARENT relationship to the referencing
2189 object. The "RELTYPE" property parameter is used to either
2190 explicitly state the default PARENT relationship type to the
2191 referenced calendar component or to override the default PARENT
2192 relationship type and specify either a CHILD or SIBLING
2193 relationship. The PARENT relationship indicates that the calendar
2194 component is a subordinate of the referenced calendar component.
2195 The CHILD relationship indicates that the calendar component is a
2196 superior of the referenced calendar component. The SIBLING
2197 relationship indicates that the calendar component is a peer of
2198 the referenced calendar component.
2200 Changes to a calendar component referenced by this property can
2201 have an implicit impact on the related calendar component. For
2202 example, if a group event changes its start or end date or time,
2203 then the related, dependent events will need to have their start
2204 and end dates changed in a corresponding way. Similarly, if a
2205 PARENT calendar component is cancelled or deleted, then there is
2206 an implied impact to the related CHILD calendar components. This
2207 property is intended only to provide information on the
2208 relationship of calendar components. It is up to the target
2209 calendar system to maintain any property implications of this
2210 relationship.
2212 Description (:rfc:`9253`):
2213 By default or when VALUE=UID is specified, the property value
2214 consists of the persistent, globally unique identifier of another
2215 calendar component. This value would be represented in a calendar
2216 component by the UID property.
2218 By default, the property value
2219 points to another calendar component that has a PARENT relationship
2220 to the referencing object. The RELTYPE property parameter is used
2221 to either explicitly state the default PARENT relationship type to
2222 the referenced calendar component or to override the default
2223 PARENT relationship type and specify either a CHILD or SIBLING
2224 relationship or a temporal relationship.
2226 The PARENT relationship
2227 indicates that the calendar component is a subordinate of the
2228 referenced calendar component. The CHILD relationship indicates
2229 that the calendar component is a superior of the referenced calendar
2230 component. The SIBLING relationship indicates that the calendar
2231 component is a peer of the referenced calendar component.
2233 To preserve backwards compatibility, the value type MUST
2234 be UID when the PARENT, SIBLING, or CHILD relationships
2235 are specified.
2237 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2238 or STARTTOSTART relationships define temporal relationships, as
2239 specified in the RELTYPE parameter definition.
2241 The FIRST and NEXT
2242 define ordering relationships between calendar components.
2244 The DEPENDS-ON relationship indicates that the current calendar
2245 component depends on the referenced calendar component in some manner.
2246 For example, a task may be blocked waiting on the other,
2247 referenced, task.
2249 The REFID and CONCEPT relationships establish
2250 a reference from the current component to the referenced component.
2251 Changes to a calendar component referenced by this property
2252 can have an implicit impact on the related calendar component.
2253 For example, if a group event changes its start or end date or
2254 time, then the related, dependent events will need to have their
2255 start and end dates and times changed in a corresponding way.
2256 Similarly, if a PARENT calendar component is canceled or deleted,
2257 then there is an implied impact to the related CHILD calendar
2258 components. This property is intended only to provide information
2259 on the relationship of calendar components.
2261 Deletion of the target component, for example, the target of a
2262 FIRST, NEXT, or temporal relationship, can result in broken links.
2264 It is up to the target calendar system to maintain any property
2265 implications of these relationships.
2267 Examples:
2268 :rfc:`5545` examples of this property:
2270 .. code-block:: ics
2272 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2274 .. code-block:: ics
2276 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2278 :rfc:`9253` examples of this property:
2280 .. code-block:: ics
2282 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2283 https://example.com/caldav/user/jb/cal/
2284 19960401-080045-4000F192713.ics
2286 See also :class:`icalendar.enums.RELTYPE`.
2288 """
2289 result = self.get("RELATED-TO", [])
2290 if not isinstance(result, list):
2291 return [result]
2292 return result
2295def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2296 """Set the RELATED-TO properties."""
2297 _del_related_to(self)
2298 if values is None:
2299 return
2300 if not isinstance(values, list):
2301 values = [values]
2302 for value in values:
2303 self.add("RELATED-TO", value)
2306def _del_related_to(self: Component):
2307 """Delete the RELATED-TO properties."""
2308 self.pop("RELATED-TO", None)
2311related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2314def _get_concepts(self: Component) -> list[vUri]:
2315 """CONCEPT
2317 Purpose:
2318 CONCEPT defines the formal categories for a calendar component.
2320 Conformance:
2321 Since :rfc:`9253`,
2322 this property can be specified zero or more times in any iCalendar component.
2324 Description:
2325 This property is used to specify formal categories or classifications of
2326 the calendar component. The values are useful in searching for a calendar
2327 component of a particular type and category.
2329 This categorization is distinct from the more informal "tagging" of components
2330 provided by the existing CATEGORIES property. It is expected that the value of
2331 the CONCEPT property will reference an external resource that provides
2332 information about the categorization.
2334 In addition, a structured URI value allows for hierarchical categorization of
2335 events.
2337 Possible category resources are the various proprietary systems, for example,
2338 the Library of Congress, or an open source of categorization data.
2340 Examples:
2341 The following is an example of this property.
2342 It points to a server acting as the source for the calendar object.
2344 .. code-block:: ics
2346 CONCEPT:https://example.com/event-types/arts/music
2348 .. seealso::
2350 :attr:`icalendar.prop.categories.vCategory`
2351 """
2352 concepts = self.get("CONCEPT", [])
2353 if not isinstance(concepts, list):
2354 concepts = [concepts]
2355 return concepts
2358CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2361def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2362 """Set the concepts."""
2363 _del_concepts(self)
2364 if concepts is None:
2365 return
2366 if not isinstance(concepts, list):
2367 concepts = [concepts]
2368 for value in concepts:
2369 self.add("CONCEPT", value)
2372def _del_concepts(self: Component):
2373 """Delete the concepts."""
2374 self.pop("CONCEPT", None)
2377concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2380def multi_string_property(name: str, doc: str):
2381 """A property for an iCalendar Property that can occur multiple times."""
2383 def fget(self: Component) -> list[str]:
2384 """Get the values of a multi-string property."""
2385 value = self.get(name, [])
2386 if not isinstance(value, list):
2387 value = [value]
2388 return value
2390 def fset(self: Component, value: list[str] | str | None) -> None:
2391 """Set the values of a multi-string property."""
2392 fdel(self)
2393 if value is None:
2394 return
2395 if not isinstance(value, list):
2396 value = [value]
2397 for value in value:
2398 self.add(name, value)
2400 def fdel(self: Component):
2401 """Delete the values of a multi-string property."""
2402 self.pop(name, None)
2404 return property(fget, fset, fdel, doc=doc)
2407refids_property = multi_string_property(
2408 "REFID",
2409 """REFID
2411Purpose:
2412 REFID acts as a key for associated iCalendar entities.
2414Conformance:
2415 Since :rfc:`9253`,
2416 this property can be specified zero or more times in any iCalendar component.
2418Description:
2419 The value of this property is free-form text that creates an
2420 identifier for associated components.
2421 All components that use the same REFID value are associated through
2422 that value and can be located or retrieved as a group.
2423 For example, all of the events in a travel itinerary
2424 would have the same REFID value, so as to be grouped together.
2426Examples:
2427 The following is an example of this property.
2429 .. code-block:: ics
2431 REFID:itinerary-2014-11-17
2433 Use a REFID to associate several VTODOs:
2435 .. code-block:: pycon
2437 >>> from icalendar import Todo
2438 >>> todo_1 = Todo.new(
2439 ... summary="turn off stove",
2440 ... refids=["travel", "alps"]
2441 ... )
2442 >>> todo_2 = Todo.new(
2443 ... summary="pack backpack",
2444 ... refids=["travel", "alps"]
2445 ... )
2446 >>> todo_1.refids == todo_2.refids
2447 True
2449.. note::
2451 List modifications do not modify the component.
2452""",
2453)
2456__all__ = [
2457 "CONCEPTS_TYPE_SETTER",
2458 "LINKS_TYPE_SETTER",
2459 "RECURRENCE_ID",
2460 "RELATED_TO_TYPE_SETTER",
2461 "attendees_property",
2462 "busy_type_property",
2463 "categories_property",
2464 "class_property",
2465 "color_property",
2466 "comments_property",
2467 "concepts_property",
2468 "conferences_property",
2469 "contacts_property",
2470 "create_single_property",
2471 "description_property",
2472 "descriptions_property",
2473 "duration_property",
2474 "exdates_property",
2475 "get_duration_property",
2476 "get_end_property",
2477 "get_start_end_duration_with_validation",
2478 "get_start_property",
2479 "images_property",
2480 "links_property",
2481 "location_property",
2482 "multi_language_text_property",
2483 "multi_string_property",
2484 "organizer_property",
2485 "priority_property",
2486 "property_del_duration",
2487 "property_doc_duration_template",
2488 "property_get_duration",
2489 "property_set_duration",
2490 "rdates_property",
2491 "refids_property",
2492 "related_to_property",
2493 "rfc_7953_dtend_property",
2494 "rfc_7953_dtstart_property",
2495 "rfc_7953_duration_property",
2496 "rfc_7953_end_property",
2497 "rrules_property",
2498 "sequence_property",
2499 "set_duration_with_locking",
2500 "set_end_with_locking",
2501 "set_start_with_locking",
2502 "single_int_property",
2503 "single_utc_property",
2504 "source_property",
2505 "status_property",
2506 "summary_property",
2507 "transparency_property",
2508 "uid_property",
2509 "url_property",
2510]