Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 30%
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 collections.abc import Sequence
7from datetime import date, datetime, timedelta
8from typing import TYPE_CHECKING, Literal, TypeAlias
10from icalendar.enums import BUSYTYPE, CLASS, STATUS, TRANSP, StrEnum
11from icalendar.error import IncompleteComponent, InvalidCalendar
12from icalendar.parser_tools import SEQUENCE_TYPES
13from icalendar.prop import (
14 vBinary,
15 vCalAddress,
16 vCategory,
17 vDDDTypes,
18 vDuration,
19 vRecur,
20 vText,
21 vUid,
22 vUnknown,
23 vUri,
24 vXmlReference,
25)
26from icalendar.prop.conference import Conference
27from icalendar.prop.image import Image
28from icalendar.timezone import tzp
29from icalendar.tools import is_date
31if TYPE_CHECKING:
32 from collections.abc import Callable
34 from icalendar.cal import Component
35 from icalendar.cal.event import Event
36 from icalendar.cal.todo import Todo
39def _get_rdates(
40 self: Component,
41) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]:
42 """The RDATE property defines the list of DATE-TIME values for recurring components.
44 RDATE is defined in :rfc:`5545`.
45 The return value is a list of tuples ``(start, end)``.
47 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
48 with and without timezone.
50 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
51 if the end is specified.
53 Value Type:
54 The default value type for this property is DATE-TIME.
55 The value type can be set to DATE or PERIOD.
57 Property Parameters:
58 IANA, non-standard, value data type, and time
59 zone identifier property parameters can be specified on this
60 property.
62 Conformance:
63 This property can be specified in recurring "VEVENT",
64 "VTODO", and "VJOURNAL" calendar components as well as in the
65 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
66 calendar component.
68 Description:
69 This property can appear along with the "RRULE"
70 property to define an aggregate set of repeating occurrences.
71 When they both appear in a recurring component, the recurrence
72 instances are defined by the union of occurrences defined by both
73 the "RDATE" and "RRULE".
75 The recurrence dates, if specified, are used in computing the
76 recurrence set. The recurrence set is the complete set of
77 recurrence instances for a calendar component. The recurrence set
78 is generated by considering the initial "DTSTART" property along
79 with the "RRULE", "RDATE", and "EXDATE" properties contained
80 within the recurring component. The "DTSTART" property defines
81 the first instance in the recurrence set. The "DTSTART" property
82 value SHOULD match the pattern of the recurrence rule, if
83 specified. The recurrence set generated with a "DTSTART" property
84 value that doesn't match the pattern of the rule is undefined.
85 The final recurrence set is generated by gathering all of the
86 start DATE-TIME values generated by any of the specified "RRULE"
87 and "RDATE" properties, and then excluding any start DATE-TIME
88 values specified by "EXDATE" properties. This implies that start
89 DATE-TIME values specified by "EXDATE" properties take precedence
90 over those specified by inclusion properties (i.e., "RDATE" and
91 "RRULE"). Where duplicate instances are generated by the "RRULE"
92 and "RDATE" properties, only one recurrence is considered.
93 Duplicate instances are ignored.
95 Example:
96 Below, we set one RDATE in a list and get the resulting tuple of start and end.
98 .. code-block:: pycon
100 >>> from icalendar import Event
101 >>> from datetime import datetime
102 >>> event = Event()
104 # Add a list of recurrence dates
105 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
106 >>> event.rdates
107 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
109 .. note::
111 Modifying the returned list does not change the RDATE value. Assign to
112 ``rdates`` for the relevant component or use
113 :meth:`Component.add <icalendar.cal.component.Component.add>` instead.
114 If you want to compute recurrences, have a look at
115 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
117 """
118 result = []
119 rdates = self.get("RDATE", [])
120 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
121 for dts in rdates.dts:
122 rdate = dts.dt
123 if isinstance(rdate, tuple):
124 # we have a period as rdate
125 if isinstance(rdate[1], timedelta):
126 result.append((rdate[0], rdate[0] + rdate[1]))
127 else:
128 result.append(rdate)
129 else:
130 # we have a date/datetime
131 result.append((rdate, None))
132 return result
135def _set_rdates(self: Component, value) -> None:
136 """Set the RDATE values, replacing any existing ones.
138 ``value`` is a list as returned by :attr:`rdates` (each item a date, a
139 datetime, or a ``(start, end)`` period tuple). Setting an empty list or
140 :obj:`None` removes the RDATE property.
141 """
142 _del_rdates(self)
143 if value:
144 self.add("RDATE", value)
147def _del_rdates(self: Component) -> None:
148 """Delete all RDATE values."""
149 self.pop("RDATE", None)
152rdates_property = property(_get_rdates, _set_rdates, _del_rdates)
155def _get_exdates(self: Component) -> list[date | datetime]:
156 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
158 EXDATE is defined in :rfc:`5545`.
160 Value Type:
161 The default value type for this property is DATE-TIME.
162 The value type can be set to DATE.
164 Property Parameters:
165 IANA, non-standard, value data type, and time
166 zone identifier property parameters can be specified on this
167 property.
169 Conformance:
170 This property can be specified in recurring "VEVENT",
171 "VTODO", and "VJOURNAL" calendar components as well as in the
172 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
173 calendar component.
175 Description:
176 The exception dates, if specified, are used in
177 computing the recurrence set. The recurrence set is the complete
178 set of recurrence instances for a calendar component. The
179 recurrence set is generated by considering the initial "DTSTART"
180 property along with the "RRULE", "RDATE", and "EXDATE" properties
181 contained within the recurring component. The "DTSTART" property
182 defines the first instance in the recurrence set. The "DTSTART"
183 property value SHOULD match the pattern of the recurrence rule, if
184 specified. The recurrence set generated with a "DTSTART" property
185 value that doesn't match the pattern of the rule is undefined.
186 The final recurrence set is generated by gathering all of the
187 start DATE-TIME values generated by any of the specified "RRULE"
188 and "RDATE" properties, and then excluding any start DATE-TIME
189 values specified by "EXDATE" properties. This implies that start
190 DATE-TIME values specified by "EXDATE" properties take precedence
191 over those specified by inclusion properties (i.e., "RDATE" and
192 "RRULE"). When duplicate instances are generated by the "RRULE"
193 and "RDATE" properties, only one recurrence is considered.
194 Duplicate instances are ignored.
196 The "EXDATE" property can be used to exclude the value specified
197 in "DTSTART". However, in such cases, the original "DTSTART" date
198 MUST still be maintained by the calendaring and scheduling system
199 because the original "DTSTART" value has inherent usage
200 dependencies by other properties such as the "RECURRENCE-ID".
202 Example:
203 Below, we add an exdate in a list and get the resulting list of exdates.
205 .. code-block:: pycon
207 >>> from icalendar import Event
208 >>> from datetime import datetime
209 >>> event = Event()
211 # Add a list of excluded dates
212 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
213 >>> event.exdates
214 [datetime.datetime(2025, 4, 28, 16, 5)]
216 .. note::
218 Modifying the returned list does not change the EXDATE value. Assign to
219 ``exdates`` for the relevant component or use
220 :meth:`Component.add <icalendar.cal.component.Component.add>` instead.
221 If you want to compute recurrences, have a look at
222 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
224 """
225 result = []
226 exdates = self.get("EXDATE", [])
227 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
228 for dts in exdates.dts:
229 exdate = dts.dt
230 # we have a date/datetime
231 result.append(exdate)
232 return result
235def _set_exdates(self: Component, value) -> None:
236 """Set the EXDATE values, replacing any existing ones.
238 ``value`` is a list as returned by :attr:`exdates` (each item a date or a
239 datetime). Setting an empty list or :obj:`None` removes the EXDATE property.
240 """
241 _del_exdates(self)
242 if value:
243 self.add("EXDATE", value)
246def _del_exdates(self: Component) -> None:
247 """Delete all EXDATE values."""
248 self.pop("EXDATE", None)
251exdates_property = property(_get_exdates, _set_exdates, _del_exdates)
254def _get_rrules(self: Component) -> list[vRecur]:
255 """RRULE defines a rule or repeating pattern for recurring components.
257 RRULE is defined in :rfc:`5545`.
258 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
260 Property Parameters:
261 IANA and non-standard property parameters can
262 be specified on this property.
264 Conformance:
265 This property can be specified in recurring "VEVENT",
266 "VTODO", and "VJOURNAL" calendar components as well as in the
267 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
268 calendar component, but it SHOULD NOT be specified more than once.
269 The recurrence set generated with multiple "RRULE" properties is
270 undefined.
272 Description:
273 The recurrence rule, if specified, is used in computing
274 the recurrence set. The recurrence set is the complete set of
275 recurrence instances for a calendar component. The recurrence set
276 is generated by considering the initial "DTSTART" property along
277 with the "RRULE", "RDATE", and "EXDATE" properties contained
278 within the recurring component. The "DTSTART" property defines
279 the first instance in the recurrence set. The "DTSTART" property
280 value SHOULD be synchronized with the recurrence rule, if
281 specified. The recurrence set generated with a "DTSTART" property
282 value not synchronized with the recurrence rule is undefined. The
283 final recurrence set is generated by gathering all of the start
284 DATE-TIME values generated by any of the specified "RRULE" and
285 "RDATE" properties, and then excluding any start DATE-TIME values
286 specified by "EXDATE" properties. This implies that start DATE-
287 TIME values specified by "EXDATE" properties take precedence over
288 those specified by inclusion properties (i.e., "RDATE" and
289 "RRULE"). Where duplicate instances are generated by the "RRULE"
290 and "RDATE" properties, only one recurrence is considered.
291 Duplicate instances are ignored.
293 The "DTSTART" property specified within the iCalendar object
294 defines the first instance of the recurrence. In most cases, a
295 "DTSTART" property of DATE-TIME value type used with a recurrence
296 rule, should be specified as a date with local time and time zone
297 reference to make sure all the recurrence instances start at the
298 same local time regardless of time zone changes.
300 If the duration of the recurring component is specified with the
301 "DTEND" or "DUE" property, then the same exact duration will apply
302 to all the members of the generated recurrence set. Else, if the
303 duration of the recurring component is specified with the
304 "DURATION" property, then the same nominal duration will apply to
305 all the members of the generated recurrence set and the exact
306 duration of each recurrence instance will depend on its specific
307 start time. For example, recurrence instances of a nominal
308 duration of one day will have an exact duration of more or less
309 than 24 hours on a day where a time zone shift occurs. The
310 duration of a specific recurrence may be modified in an exception
311 component or simply by using an "RDATE" property of PERIOD value
312 type.
314 Examples:
315 Daily for 10 occurrences:
317 .. code-block:: pycon
319 >>> from icalendar import Event
320 >>> from datetime import datetime
321 >>> from zoneinfo import ZoneInfo
322 >>> event = Event()
323 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
324 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
325 >>> print(event.to_ical())
326 BEGIN:VEVENT
327 DTSTART;TZID=America/New_York:19970902T090000
328 RRULE:FREQ=DAILY;COUNT=10
329 END:VEVENT
330 >>> event.rrules
331 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
333 Daily until December 24, 1997:
335 .. code-block:: pycon
337 >>> from icalendar import Event, vRecur
338 >>> from datetime import datetime
339 >>> from zoneinfo import ZoneInfo
340 >>> event = Event()
341 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
342 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
343 >>> print(event.to_ical())
344 BEGIN:VEVENT
345 DTSTART;TZID=America/New_York:19970902T090000
346 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
347 END:VEVENT
348 >>> event.rrules
349 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
351 .. note::
353 You cannot modify the RRULE value by modifying the result.
354 Use :meth:`Component.add <icalendar.cal.component.Component.add>` to add values.
356 If you want to compute recurrences, have a look at
357 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
359 """ # noqa: E501
360 rrules = self.get("RRULE", [])
361 if not isinstance(rrules, list):
362 return [rrules]
363 return rrules
366rrules_property = property(_get_rrules)
369def multi_language_text_property(
370 main_prop: str, compatibility_prop: str | None, doc: str
371) -> property:
372 """This creates a text property.
374 This property can be defined several times with different ``LANGUAGE`` parameters.
376 Parameters:
377 main_prop (str): The property to set and get, such as ``NAME``
378 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
379 doc (str): The documentation string
380 """
382 def fget(self: Component) -> str | None:
383 """Get the property"""
384 result = self.get(main_prop)
385 if result is None and compatibility_prop is not None:
386 result = self.get(compatibility_prop)
387 if isinstance(result, list):
388 for item in result:
389 if "LANGUAGE" not in item.params:
390 return item
391 return result
393 def fset(self: Component, value: str | None):
394 """Set the property."""
395 fdel(self)
396 if value is not None:
397 self.add(main_prop, value)
398 if compatibility_prop is not None:
399 self.add(compatibility_prop, value)
401 def fdel(self: Component):
402 """Delete the property."""
403 self.pop(main_prop, None)
404 if compatibility_prop is not None:
405 self.pop(compatibility_prop, None)
407 return property(fget, fset, fdel, doc)
410def single_int_property(
411 prop: str, default: int, doc: str, *, min_value: int | None = None
412) -> property:
413 """Create a property for an int value that exists only once.
415 Parameters:
416 default: Required. The default value.
417 doc: Required. The documentation string.
418 prop: Required. The name of the property.
419 min_value: If set, the value must be greater than or equal to this minimum.
421 .. versionadded:: 7.3.0
422 Added the ``min_value`` parameter.
423 """
425 def fget(self: Component) -> int:
426 """Get the property"""
427 try:
428 return int(self.get(prop, default))
429 except ValueError as e:
430 raise InvalidCalendar(f"{prop} must be an int") from e
432 def fset(self: Component, value: int | None):
433 """Set the property."""
434 if value is not None:
435 if not isinstance(value, int) or isinstance(value, bool):
436 raise TypeError(f"{prop} must be an int, got {value!r}")
437 if min_value is not None and value < min_value:
438 raise InvalidCalendar(f"{prop} must be >= {min_value}, got {value}")
439 fdel(self)
440 if value is not None:
441 self.add(prop, value)
443 def fdel(self: Component):
444 """Delete the property."""
445 self.pop(prop, None)
447 return property(fget, fset, fdel, doc)
450def single_utc_property(name: str, docs: str) -> property:
451 """Create a property to access a value of datetime in UTC timezone.
453 Parameters:
454 name: name of the property
455 docs: documentation string
456 """
458 def fget(self: Component) -> datetime | None:
459 """Get the value."""
460 if name not in self:
461 return None
462 dt = self.get(name)
463 if isinstance(dt, (vText, vUnknown)):
464 # we might be in an attribute that is not typed
465 value = vDDDTypes.from_ical(dt)
466 else:
467 value = getattr(dt, "dt", dt)
468 if value is None or not isinstance(value, date):
469 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
470 return tzp.localize_utc(value)
472 def fset(self: Component, value: datetime | None):
473 """Set the value"""
474 if value is None:
475 fdel(self)
476 return
477 if not isinstance(value, date):
478 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
479 fdel(self)
480 self.add(name, tzp.localize_utc(value))
482 def fdel(self: Component):
483 """Delete the property."""
484 self.pop(name, None)
486 return property(fget, fset, fdel, doc=docs)
489def single_string_property(
490 name: str, docs: str, other_name: str | list[str] | None = None, default: str = ""
491) -> property:
492 """Create a property to access a single string value."""
493 other_names = (
494 []
495 if other_name is None
496 else [other_name]
497 if isinstance(other_name, str)
498 else list(other_name)
499 )
501 def fget(self: Component) -> str:
502 """Get the value."""
503 result = self.get(name, None)
504 if result is None:
505 for alias in other_names:
506 result = self.get(alias, None)
507 if result is not None:
508 break
509 if result is None or result == []:
510 return default
511 if isinstance(result, list):
512 return result[0]
513 return result
515 def fset(self: Component, value: str | None):
516 """Set the value.
518 Setting the value to None will delete it.
519 """
520 fdel(self)
521 if value is not None:
522 self.add(name, value)
524 def fdel(self: Component):
525 """Delete the property."""
526 self.pop(name, None)
527 for alias in other_names:
528 self.pop(alias, None)
530 return property(fget, fset, fdel, doc=docs)
533color_property = single_string_property(
534 "COLOR",
535 """This property specifies a color used for displaying the component.
537 This implements :rfc:`7986` ``COLOR`` property.
539 Property Parameters:
540 IANA and non-standard property parameters can
541 be specified on this property.
543 Conformance:
544 This property can be specified once in an iCalendar
545 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
547 Description:
548 This property specifies a color that clients MAY use
549 when presenting the relevant data to a user. Typically, this
550 would appear as the "background" color of events or tasks. The
551 value is a case-insensitive color name taken from the CSS3 set of
552 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
554 Example:
555 ``"turquoise"``, ``"#ffffff"``
557 .. code-block:: pycon
559 >>> from icalendar import Todo
560 >>> todo = Todo()
561 >>> todo.color = "green"
562 >>> print(todo.to_ical())
563 BEGIN:VTODO
564 COLOR:green
565 END:VTODO
566 """,
567)
569sequence_property = single_int_property(
570 "SEQUENCE",
571 0,
572 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
574Value Type:
575 INTEGER
577Property Parameters:
578 IANA and non-standard property parameters can be specified on this property.
580Conformance:
581 The property can be specified in "VEVENT", "VTODO", or
582 "VJOURNAL" calendar component.
584Description:
585 When a calendar component is created, its sequence
586 number is 0. It is monotonically incremented by the "Organizer's"
587 CUA each time the "Organizer" makes a significant revision to the
588 calendar component.
590 The "Organizer" includes this property in an iCalendar object that
591 it sends to an "Attendee" to specify the current version of the
592 calendar component.
594 The "Attendee" includes this property in an iCalendar object that
595 it sends to the "Organizer" to specify the version of the calendar
596 component to which the "Attendee" is referring.
598 A change to the sequence number is not the mechanism that an
599 "Organizer" uses to request a response from the "Attendees". The
600 "RSVP" parameter on the "ATTENDEE" property is used by the
601 "Organizer" to indicate that a response from the "Attendees" is
602 requested.
604 Recurrence instances of a recurring component MAY have different
605 sequence numbers.
607Examples:
608 The following is an example of this property for a calendar
609 component that was just created by the "Organizer":
611 .. code-block:: pycon
613 >>> from icalendar import Event
614 >>> event = Event()
615 >>> event.sequence
616 0
618 The following is an example of this property for a calendar
619 component that has been revised 10 different times by the
620 "Organizer":
622 .. code-block:: pycon
624 >>> from icalendar import Calendar
625 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
626 >>> event = calendar.events[0]
627 >>> event.sequence
628 10
630 Raises:
631 TypeError: If the value is not an ``int``. Booleans are rejected, too,
632 even though ``bool`` subclasses ``int``.
634 ~icalendar.error.InvalidCalendar: If the value is negative.
636 .. versionchanged:: 7.3.0
637 Negative values are no longer accepted.
638 """, # noqa: E501
639 min_value=0,
640)
643def _get_categories(component: Component) -> list[str]:
644 """Get all the categories."""
645 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES")
646 if isinstance(categories, list):
647 _set_categories(
648 component,
649 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
650 )
651 return _get_categories(component)
652 if categories is None:
653 categories = vCategory([])
654 component.add("CATEGORIES", categories)
655 return categories.cats
658def _set_categories(component: Component, cats: Sequence[str] | None) -> None:
659 """Set the categories."""
660 if not cats and cats != []:
661 _del_categories(component)
662 return
663 component["CATEGORIES"] = categories = vCategory(cats)
664 if isinstance(cats, list):
665 cats.clear()
666 cats.extend(categories.cats)
667 categories.cats = cats
670def _del_categories(component: Component) -> None:
671 """Delete the categories."""
672 component.pop("CATEGORIES", None)
675categories_property = property(
676 _get_categories,
677 _set_categories,
678 _del_categories,
679 """This property defines the categories for a component.
681The categories property is used to specify categories or subtypes of the
682calendar component. The categories are useful to search for a calendar
683component of a particular type and category.
685Within the calendar components, specify categories as a list of strings.
686You can get, set, and delete categories for a component.
688This property can be used in icalendar through its Python attributes of:
690- :attr:`Available.categories <icalendar.cal.available.Available.categories>`
691- :attr:`Availability.categories <icalendar.cal.availability.Availability.categories>`
692- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>`
693- :attr:`Event.categories <icalendar.cal.event.Event.categories>`
694- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>`
695- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>`
697The categories property for ``Available`` and ``Availability`` complies with
698:rfc:`7953#section-3.1`, for ``Event``, ``Journal``, and ``Todo`` with
699:rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`.
701Note:
702 At present, icalendar doesn't take the LANGUAGE parameter as defined
703 in :rfc:`5545#section-3.2.10` into account.
705Parameters:
706 categories(list[str]): A list of categories as strings.
708Example:
709 Create an event, add categories to it, print its ical representation,
710 append another category, and finally compare the result
711 against its expected value.
713 .. code-block:: pycon
715 >>> from icalendar import Event
716 >>> event = Event()
717 >>> event.categories = ["Work", "Meeting"]
718 >>> print(event.to_ical())
719 BEGIN:VEVENT
720 CATEGORIES:Work,Meeting
721 END:VEVENT
722 >>> event.categories.append("Lecture")
723 >>> event.categories == ["Work", "Meeting", "Lecture"]
724 True
726See also:
727 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>`
728""",
729)
732def _get_attendees(self: Component) -> list[vCalAddress]:
733 """Get attendees."""
734 value = self.get("ATTENDEE")
735 if value is None:
736 value = []
737 self["ATTENDEE"] = value
738 return value
739 if isinstance(value, vCalAddress):
740 return [value]
741 return value
744ATTENDEE_TYPE_SETTER: TypeAlias = Sequence[vCalAddress | str] | vCalAddress | str | None
747def _set_attendees(self: Component, value: ATTENDEE_TYPE_SETTER):
748 """Set attendees."""
749 _del_attendees(self)
750 if value is None:
751 return
752 if isinstance(value, (vCalAddress, str)):
753 value = [value]
754 elif not isinstance(value, list):
755 value = list(value) if isinstance(value, Sequence) else [value]
756 for index, attendee in enumerate(value):
757 # vCalAddress subclasses str, so exclude it before normalizing strings
758 if not isinstance(attendee, vCalAddress) and isinstance(attendee, str):
759 value[index] = vCalAddress.new(attendee)
760 self["ATTENDEE"] = value
763def _del_attendees(self: Component):
764 """Delete all attendees."""
765 self.pop("ATTENDEE", None)
768attendees_property = property(
769 _get_attendees,
770 _set_attendees,
771 _del_attendees,
772 """ATTENDEE defines one or more "Attendees" within a calendar component.
774Conformance:
775 This property MUST be specified in an iCalendar object
776 that specifies a group-scheduled calendar entity. This property
777 MUST NOT be specified in an iCalendar object when publishing the
778 calendar information (e.g., NOT in an iCalendar object that
779 specifies the publication of a calendar user's busy time, event,
780 to-do, or journal). This property is not specified in an
781 iCalendar object that specifies only a time zone definition or
782 that defines calendar components that are not group-scheduled
783 components, but are components only on a single user's calendar.
785Description:
786 This property MUST only be specified within calendar
787 components to specify participants, non-participants, and the
788 chair of a group-scheduled calendar entity. The property is
789 specified within an "EMAIL" category of the "VALARM" calendar
790 component to specify an email address that is to receive the email
791 type of iCalendar alarm.
793Examples:
794 Assign one or more attendee email addresses directly. Strings are
795 converted to :class:`~icalendar.prop.cal_address.vCalAddress` objects
796 and receive a ``mailto:`` prefix when needed.
798 .. code-block:: pycon
800 >>> from icalendar import Event
801 >>> event = Event()
802 >>> event.attendees = [
803 ... "me@my-domain.com",
804 ... "mailto:you@my-domain.com",
805 ... ]
806 >>> event.attendees[0]
807 vCalAddress('mailto:me@my-domain.com')
808 >>> event.attendees[1]
809 vCalAddress('mailto:you@my-domain.com')
810 >>> print(event.to_ical())
811 BEGIN:VEVENT
812 ATTENDEE:mailto:me@my-domain.com
813 ATTENDEE:mailto:you@my-domain.com
814 END:VEVENT
816 Use :meth:`vCalAddress.new
817 <icalendar.prop.cal_address.vCalAddress.new>` when an attendee needs
818 parameters such as ``CN``, ``ROLE``, or ``RSVP``.
820 .. code-block:: pycon
822 >>> from icalendar import vCalAddress
823 >>> event.attendees = [
824 ... vCalAddress.new(
825 ... "chair@example.com",
826 ... cn="Meeting Chair",
827 ... role="CHAIR",
828 ... rsvp=True,
829 ... )
830 ... ]
831""",
832)
834uid_property = single_string_property(
835 "UID",
836 """UID specifies the persistent, globally unique identifier for a component.
838We recommend using :func:`uuid.uuid4` to generate new values.
840Returns:
841 The value of the UID property as a string or ``""`` if no value is set.
843Description:
844 The "UID" itself MUST be a globally unique identifier.
845 The generator of the identifier MUST guarantee that the identifier
846 is unique.
848 This is the method for correlating scheduling messages with the
849 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
850 The full range of calendar components specified by a recurrence
851 set is referenced by referring to just the "UID" property value
852 corresponding to the calendar component. The "RECURRENCE-ID"
853 property allows the reference to an individual instance within the
854 recurrence set.
856 This property is an important method for group-scheduling
857 applications to match requests with later replies, modifications,
858 or deletion requests. Calendaring and scheduling applications
859 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
860 calendar components to assure interoperability with other group-
861 scheduling applications. This identifier is created by the
862 calendar system that generates an iCalendar object.
864 Implementations MUST be able to receive and persist values of at
865 least 255 octets for this property, but they MUST NOT truncate
866 values in the middle of a UTF-8 multi-octet sequence.
868 :rfc:`7986` states that UID can be used, for
869 example, to identify duplicate calendar streams that a client may
870 have been given access to. It can be used in conjunction with the
871 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
872 to identify the most recent version of a calendar.
874Conformance:
875 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
876 and "VJOURNAL" calendar components.
877 :rfc:`7986` modifies the definition of the "UID" property to
878 allow it to be defined in an iCalendar object.
879 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
880 identifier to be specified. The value of this property can then be used
881 to refer uniquely to the "VALARM" component.
883 This property can be specified once only.
885Security:
886 :rfc:`7986` states that UID values MUST NOT include any data that
887 might identify a user, host, domain, or any other security- or
888 privacy-sensitive information. It is RECOMMENDED that calendar user
889 agents now generate "UID" values that are hex-encoded random
890 Universally Unique Identifier (UUID) values as defined in
891 Sections 4.4 and 4.5 of :rfc:`4122`.
892 You can use the :mod:`uuid` module to generate new UUIDs.
894Compatibility:
895 For Alarms, ``X-ALARMUID`` is also considered.
897Examples:
898 The following is an example of such a property value:
899 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
901 Set the UID of a calendar:
903 .. code-block:: pycon
905 >>> from icalendar import Calendar
906 >>> from uuid import uuid4
907 >>> calendar = Calendar()
908 >>> calendar.uid = uuid4()
909 >>> print(calendar.to_ical())
910 BEGIN:VCALENDAR
911 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
912 END:VCALENDAR
914""",
915)
917summary_property = multi_language_text_property(
918 "SUMMARY",
919 None,
920 """SUMMARY defines a short summary or subject for the calendar component.
922Property Parameters:
923 IANA, non-standard, alternate text
924 representation, and language property parameters can be specified
925 on this property.
927Conformance:
928 The property can be specified in "VEVENT", "VTODO",
929 "VJOURNAL", or "VALARM" calendar components.
931Description:
932 This property is used in the "VEVENT", "VTODO", and
933 "VJOURNAL" calendar components to capture a short, one-line
934 summary about the activity or journal entry.
936 This property is used in the "VALARM" calendar component to
937 capture the subject of an EMAIL category of alarm.
939Examples:
940 The following is an example of this property:
942 .. code-block:: pycon
944 SUMMARY:Department Party
945""",
946)
948description_property = multi_language_text_property(
949 "DESCRIPTION",
950 None,
951 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
953Property Parameters:
954 IANA, non-standard, alternate text
955 representation, and language property parameters can be specified
956 on this property.
958Conformance:
959 The property can be specified in the "VEVENT", "VTODO",
960 "VJOURNAL", or "VALARM" calendar components. The property can be
961 specified multiple times only within a "VJOURNAL" calendar
962 component.
964Description:
965 This property is used in the "VEVENT" and "VTODO" to
966 capture lengthy textual descriptions associated with the activity.
968 This property is used in the "VALARM" calendar component to
969 capture the display text for a DISPLAY category of alarm, and to
970 capture the body text for an EMAIL category of alarm.
972Examples:
973 The following is an example of this property with formatted
974 line breaks in the property value:
976 .. code-block:: pycon
978 DESCRIPTION:Meeting to provide technical review for "Phoenix"
979 design.\\nHappy Face Conference Room. Phoenix design team
980 MUST attend this meeting.\\nRSVP to team leader.
982 """, # noqa: E501
983)
986def create_single_property(
987 prop: str,
988 value_attr: str | None,
989 value_type: tuple[type],
990 type_def: type,
991 doc: str,
992 vProp: type = vDDDTypes, # noqa: N803
993 convert: Callable[[object], object] | None = None,
994):
995 """Create a single property getter and setter.
997 Parameters:
998 prop: The name of the property.
999 value_attr: The name of the attribute to get the value from.
1000 value_type: The type of the value.
1001 type_def: The type of the property.
1002 doc: The docstring of the property.
1003 vProp: The type of the property from :mod:`icalendar.prop`.
1004 """
1006 def p_get(self: Component):
1007 default = object()
1008 result = self.get(prop, default)
1009 if result is default:
1010 return None
1011 if isinstance(result, list):
1012 raise InvalidCalendar(f"Multiple {prop} defined.")
1013 value = result if value_attr is None else getattr(result, value_attr, result)
1014 value = value if convert is None else convert(value)
1015 if not isinstance(value, value_type):
1016 raise InvalidCalendar(
1017 f"{prop} must be either a "
1018 f"{' or '.join(t.__name__ for t in value_type)},"
1019 f" not {value}."
1020 )
1021 return value
1023 def p_set(self: Component, value) -> None:
1024 if value is None:
1025 p_del(self)
1026 return
1027 value = convert(value) if convert is not None else value
1028 if not isinstance(value, value_type):
1029 raise TypeError(
1030 f"Use {' or '.join(t.__name__ for t in value_type)}, "
1031 f"not {type(value).__name__}."
1032 )
1033 self[prop] = vProp(value)
1034 if prop in self.exclusive:
1035 for other_prop in self.exclusive:
1036 if other_prop != prop:
1037 self.pop(other_prop, None)
1039 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
1041 def p_del(self: Component):
1042 self.pop(prop)
1044 p_doc = f"""The {prop} property.
1046 {doc}
1048 To delete the value, either use ``del`` or set it to ``None``.
1050 Raises:
1051 InvalidCalendar: if the attribute has invalid values.
1052 """
1053 return property(p_get, p_set, p_del, p_doc)
1056X_MOZ_SNOOZE_TIME_property = single_utc_property(
1057 "X-MOZ-SNOOZE-TIME",
1058 """The ``X-MOZ-SNOOZE-TIME`` property as a :class:`~datetime.datetime` in UTC.
1060 Thunderbird: Alarms before this time are snoozed.
1061""",
1062)
1063X_MOZ_LASTACK_property = single_utc_property(
1064 "X-MOZ-LASTACK",
1065 """The ``X-MOZ-LASTACK`` property as a :class:`~datetime.datetime` in UTC.
1067 Thunderbird: Alarms before this time are acknowledged.
1068""",
1069)
1072def property_get_duration(self: Component) -> timedelta | None:
1073 """Getter for property DURATION."""
1074 default = object()
1075 duration = self.get("duration", default)
1076 if duration is default:
1077 return None
1078 result = getattr(duration, "td", None)
1079 if result is None:
1080 raise InvalidCalendar(
1081 f"DURATION must be a timedelta, not {type(duration).__name__}."
1082 )
1083 return result
1086def property_set_duration(self: Component, value: timedelta | None):
1087 """Setter for property DURATION."""
1088 if value is None:
1089 self.pop("duration", None)
1090 return
1091 if not isinstance(value, timedelta):
1092 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
1093 self["duration"] = vDuration(value)
1094 self.pop("DTEND")
1095 self.pop("DUE")
1098def property_del_duration(self: Component):
1099 """Delete property DURATION."""
1100 self.pop("DURATION")
1103property_doc_duration_template = """The DURATION property.
1105The "DTSTART" property for a "{component}" specifies the inclusive
1106start of the {component}.
1107The "DURATION" property in conjunction with the DTSTART property
1108for a "{component}" calendar component specifies the non-inclusive end
1109of the event.
1111If you would like to calculate the duration of a {component}, do not use this.
1112Instead use the duration property (lower case).
1113"""
1116def duration_property(component: str) -> property:
1117 """Return the duration property."""
1118 return property(
1119 property_get_duration,
1120 property_set_duration,
1121 property_del_duration,
1122 property_doc_duration_template.format(component=component),
1123 )
1126def multi_text_property(name: str, docs: str) -> property:
1127 """Get a property that can occur several times and is text.
1129 Examples: Journal.descriptions, Event.comments
1130 """
1132 def fget(self: Component) -> list[str]:
1133 """Get the values."""
1134 descriptions = self.get(name)
1135 if descriptions is None:
1136 return []
1137 if not isinstance(descriptions, SEQUENCE_TYPES):
1138 return [descriptions]
1139 return descriptions
1141 def fset(self: Component, values: str | Sequence[str] | None):
1142 """Set the values."""
1143 fdel(self)
1144 if values is None:
1145 return
1146 if isinstance(values, str):
1147 self.add(name, values)
1148 else:
1149 for description in values:
1150 self.add(name, description)
1152 def fdel(self: Component):
1153 """Delete the values."""
1154 self.pop(name)
1156 return property(fget, fset, fdel, docs)
1159descriptions_property = multi_text_property(
1160 "DESCRIPTION",
1161 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1163Property Parameters:
1164 IANA, non-standard, alternate text
1165 representation, and language property parameters can be specified
1166 on this property.
1168Conformance:
1169 The property can be
1170 specified multiple times only within a "VJOURNAL" calendar component.
1172Description:
1173 This property is used in the "VJOURNAL" calendar component to
1174 capture one or more textual journal entries.
1176Examples:
1177 The following is an example of this property with formatted
1178 line breaks in the property value:
1180 .. code-block:: pycon
1182 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1183 design.\\nHappy Face Conference Room. Phoenix design team
1184 MUST attend this meeting.\\nRSVP to team leader.
1186""", # noqa: E501
1187)
1189comments_property = multi_text_property(
1190 "COMMENT",
1191 """Specifies a comment to the calendar user.
1193This property holds free-text notes attached to a component; calendar clients
1194display it but never act on it. It is defined in :rfc:`5545#section-3.8.1.4`
1195and may appear multiple times on ``VEVENT``, ``VTODO``, ``VJOURNAL``, and
1196``VFREEBUSY`` components as well as on their ``STANDARD`` and ``DAYLIGHT``
1197sub-components. For availability components, it is defined in :rfc:`7953`, and
1198may appear multiple times on ``VAVAILABILITY`` and ``VAVAILABLE``.
1200You can get, set, append, and delete comments on a component. The value is a
1201list of strings; each string is one comment.
1203Example:
1204 Add two comments to an event and then read them back.
1206 .. code-block:: pycon
1208 >>> from icalendar import Event
1209 >>> event = Event()
1210 >>> event.add("COMMENT", "Moved from the planning board.")
1211 >>> event.add("COMMENT", "Confirmed by phone.")
1212 >>> [str(c) for c in event.comments]
1213 ['Moved from the planning board.', 'Confirmed by phone.']
1214 >>> str(event.comments[0])
1215 'Moved from the planning board.'
1216""",
1217)
1219RECURRENCE_ID = create_single_property(
1220 "RECURRENCE-ID",
1221 "dt",
1222 (date, datetime),
1223 date | datetime,
1224 """
1225Identify a specific occurrence of a recurring calendar object.
1227This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1228particular instance in a recurrence set. The value is the original start
1229date or datetime of that instance, not the rescheduled time.
1231The value is usually a DATE-TIME and must use the same value type as the
1232``DTSTART`` property in the same component. A DATE value may be used for
1233all-day items instead.
1235This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1236may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1237""",
1238 vDDDTypes,
1239)
1242def _get_organizer(self: Component) -> vCalAddress | None:
1243 """ORGANIZER defines the organizer for a calendar component.
1245 Property Parameters:
1246 IANA, non-standard, language, common name,
1247 directory entry reference, and sent-by property parameters can be
1248 specified on this property.
1250 Conformance:
1251 This property MUST be specified in an iCalendar object
1252 that specifies a group-scheduled calendar entity. This property
1253 MUST be specified in an iCalendar object that specifies the
1254 publication of a calendar user's busy time. This property MUST
1255 NOT be specified in an iCalendar object that specifies only a time
1256 zone definition or that defines calendar components that are not
1257 group-scheduled components, but are components only on a single
1258 user's calendar.
1260 Description:
1261 This property is specified within the "VEVENT",
1262 "VTODO", and "VJOURNAL" calendar components to specify the
1263 organizer of a group-scheduled calendar entity. The property is
1264 specified within the "VFREEBUSY" calendar component to specify the
1265 calendar user requesting the free or busy time. When publishing a
1266 "VFREEBUSY" calendar component, the property is used to specify
1267 the calendar that the published busy time came from.
1269 The property has the property parameters "CN", for specifying the
1270 common or display name associated with the "Organizer", "DIR", for
1271 specifying a pointer to the directory information associated with
1272 the "Organizer", "SENT-BY", for specifying another calendar user
1273 that is acting on behalf of the "Organizer". The non-standard
1274 parameters may also be specified on this property. If the
1275 "LANGUAGE" property parameter is specified, the identified
1276 language applies to the "CN" parameter value.
1277 """
1278 return self.get("ORGANIZER")
1281def _set_organizer(self: Component, value: vCalAddress | str | None):
1282 """Set the value."""
1283 _del_organizer(self)
1284 if value is not None:
1285 self.add("ORGANIZER", value)
1288def _del_organizer(self: Component):
1289 """Delete the value."""
1290 self.pop("ORGANIZER")
1293organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1296def single_string_enum_property(
1297 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1298) -> property:
1299 """Create a property to access a single string value and convert it to an enum."""
1300 prop = single_string_property(name, docs, default=default)
1302 def fget(self: Component) -> StrEnum:
1303 """Get the value."""
1304 value = prop.fget(self)
1305 if value == default:
1306 return default
1307 return enum(str(value))
1309 def fset(self: Component, value: str | StrEnum | None) -> None:
1310 """Set the value."""
1311 if value == "":
1312 value = None
1313 prop.fset(self, value)
1315 return property(fget, fset, prop.fdel, doc=docs)
1318busy_type_property = single_string_enum_property(
1319 "BUSYTYPE",
1320 BUSYTYPE,
1321 BUSYTYPE.BUSY_UNAVAILABLE,
1322 """BUSYTYPE specifies the default busy time type.
1324Returns:
1325 :class:`icalendar.enums.BUSYTYPE`
1327Description:
1328 This property is used to specify the default busy time
1329 type. The values correspond to those used by the "FBTYPE"
1330 parameter used on a "FREEBUSY" property, with the exception that
1331 the "FREE" value is not used in this property. If not specified
1332 on a component that allows this property, the default is "BUSY-
1333 UNAVAILABLE".
1334""",
1335)
1338def _make_repeat_property() -> property:
1339 from icalendar.config import _clamp_repeat
1341 _base = single_int_property(
1342 "REPEAT",
1343 0,
1344 """The number of additional times the alarm is triggered after the
1345initial trigger.
1347Defaults to ``0``, meaning the alarm fires once. Must be paired with
1348:attr:`~icalendar.cal.alarm.Alarm.DURATION`. Conforms with :rfc:`5545#section-3.8.6.2`.
1349The value is capped at :data:`icalendar.config.MAX_ALARM_REPEAT` on read.
1351Raises:
1352 TypeError: If the value is not an ``int``. Booleans are rejected, too,
1353 even though ``bool`` subclasses ``int``.
1355 ~icalendar.error.InvalidCalendar: If the value is negative.
1357.. versionchanged:: 7.3.0
1358 Negative values are no longer accepted.
1359""",
1360 min_value=0,
1361 )
1363 def fget(self):
1364 return _clamp_repeat(_base.fget(self))
1366 return property(fget, _base.fset, _base.fdel, _base.__doc__)
1369repeat_property = _make_repeat_property()
1371priority_property = single_int_property(
1372 "PRIORITY",
1373 0,
1374 """
1376Conformance:
1377 This property can be specified in "VEVENT" and "VTODO" calendar components
1378 according to :rfc:`5545`.
1379 :rfc:`7953` adds this property to "VAVAILABILITY".
1381Description:
1382 This priority is specified as an integer in the range 0
1383 to 9. A value of 0 specifies an undefined priority. A value of 1
1384 is the highest priority. A value of 2 is the second highest
1385 priority. Subsequent numbers specify a decreasing ordinal
1386 priority. A value of 9 is the lowest priority.
1388 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1389 "LOW" is mapped into this property such that a property value in
1390 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1391 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1392 is "LOW" priority.
1394 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1395 "C3" is mapped into this property such that a property value of 1
1396 specifies "A1", a property value of 2 specifies "A2", a property
1397 value of 3 specifies "A3", and so forth up to a property value of
1398 9 specifies "C3".
1400 Other integer values are reserved for future use.
1402 Within a "VEVENT" calendar component, this property specifies a
1403 priority for the event. This property may be useful when more
1404 than one event is scheduled for a given time period.
1406 Within a "VTODO" calendar component, this property specified a
1407 priority for the to-do. This property is useful in prioritizing
1408 multiple action items for a given time period.
1410 Raises:
1411 TypeError: If the value is not an ``int``. Booleans are rejected, too,
1412 even though ``bool`` subclasses ``int``.
1414 ~icalendar.error.InvalidCalendar: If the value is negative.
1416 .. versionchanged:: 7.3.0
1417 Negative values are no longer accepted.
1418""",
1419 min_value=0,
1420)
1422class_property = single_string_enum_property(
1423 "CLASS",
1424 CLASS,
1425 CLASS.PUBLIC,
1426 """CLASS specifies the class of the calendar component.
1428Returns:
1429 :class:`icalendar.enums.CLASS`
1431Description:
1432 An access classification is only one component of the
1433 general security system within a calendar application. It
1434 provides a method of capturing the scope of the access the
1435 calendar owner intends for information within an individual
1436 calendar entry. The access classification of an individual
1437 iCalendar component is useful when measured along with the other
1438 security components of a calendar system (e.g., calendar user
1439 authentication, authorization, access rights, access role, etc.).
1440 Hence, the semantics of the individual access classifications
1441 cannot be completely defined by this memo alone. Additionally,
1442 due to the "blind" nature of most exchange processes using this
1443 memo, these access classifications cannot serve as an enforcement
1444 statement for a system receiving an iCalendar object. Rather,
1445 they provide a method for capturing the intention of the calendar
1446 owner for the access to the calendar component. If not specified
1447 in a component that allows this property, the default value is
1448 PUBLIC. Applications MUST treat x-name and iana-token values they
1449 don't recognize the same way as they would the PRIVATE value.
1450""",
1451)
1453transparency_property = single_string_enum_property(
1454 "TRANSP",
1455 TRANSP,
1456 TRANSP.OPAQUE,
1457 """TRANSP defines whether or not an event is transparent to busy time searches.
1459Returns:
1460 :class:`icalendar.enums.TRANSP`
1462Description:
1463 Time Transparency is the characteristic of an event
1464 that determines whether it appears to consume time on a calendar.
1465 Events that consume actual time for the individual or resource
1466 associated with the calendar SHOULD be recorded as OPAQUE,
1467 allowing them to be detected by free/busy time searches. Other
1468 events, which do not take up the individual's (or resource's) time
1469 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1470 busy time searches.
1471""",
1472)
1473status_property = single_string_enum_property(
1474 "STATUS",
1475 STATUS,
1476 "",
1477 """STATUS defines the overall status or confirmation for the calendar component.
1479Returns:
1480 :class:`icalendar.enums.STATUS`
1482The default value is ``""``.
1484Description:
1485 In a group-scheduled calendar component, the property
1486 is used by the "Organizer" to provide a confirmation of the event
1487 to the "Attendees". For example in a "VEVENT" calendar component,
1488 the "Organizer" can indicate that a meeting is tentative,
1489 confirmed, or cancelled. In a "VTODO" calendar component, the
1490 "Organizer" can indicate that an action item needs action, is
1491 completed, is in process or being worked on, or has been
1492 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1493 can indicate that a journal entry is draft, final, or has been
1494 cancelled or removed.
1495""",
1496)
1498url_property = single_string_property(
1499 "URL",
1500 """A Uniform Resource Locator (URL) associated with a calendar component.
1502This property specifies a URI where a more dynamic rendition of the calendar
1503information can be found. It is commonly used to reference related resources
1504or provide additional information about the component.
1506According to :rfc:`5545#section-3.8.4.6`, this property can be specified
1507once in "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1508Since :rfc:`7986#section-5.5`, this property can also be defined on a
1509"VCALENDAR". :rfc:`7953#section-3.1` allows this property in "VAVAILABILITY" components.
1511This property may be used in a calendar component to convey a location
1512where a more dynamic rendition of the calendar information can be found.
1513If both the URL property and Content-Location MIME header are specified,
1514they MUST point to the same resource.
1516This differs from the SOURCE property, which identifies where calendar
1517data can be refreshed from, whereas URL provides an alternative
1518representation of the current calendar data.
1520Examples:
1522 Set a URL for an event that references additional information:
1524 .. code-block:: pycon
1526 >>> from icalendar import Event
1527 >>> event = Event()
1528 >>> event.add('url', 'http://example.com/events/meeting-2025')
1529 >>> print(event.to_ical().decode('utf-8'))
1530 BEGIN:VEVENT
1531 URL:http://example.com/events/meeting-2025
1532 END:VEVENT
1534 Set a URL for a calendar:
1536 .. code-block:: pycon
1538 >>> from icalendar import Calendar
1539 >>> calendar = Calendar()
1540 >>> calendar.add('url', 'http://example.com/pub/calendars/jsmith/mytime.ics')
1541 >>> print(calendar.to_ical().decode('utf-8'))
1542 BEGIN:VCALENDAR
1543 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1544 END:VCALENDAR
1546See also:
1547 :attr:`~icalendar.cal.calendar.Calendar.source` for specifying from where
1548 calendar data can be refreshed.
1550 icalendar implementations:
1552 - :attr:`Availability.url <icalendar.cal.availability.Availability.url>`
1553 - :attr:`Calendar.url <icalendar.cal.calendar.Calendar.url>`
1554 - :attr:`Event.url <icalendar.cal.event.Event.url>`
1555 - :attr:`FreeBusy.url <icalendar.cal.free_busy.FreeBusy.url>`
1556 - :attr:`Journal.url <icalendar.cal.journal.Journal.url>`
1557 - :attr:`Todo.url <icalendar.cal.todo.Todo.url>`
1560""",
1561)
1563source_property = single_string_property(
1564 "SOURCE",
1565 """A URI from where calendar data can be refreshed.
1567Description:
1568 This property identifies a location where a client can
1569 retrieve updated data for the calendar. Clients SHOULD honor any
1570 specified "REFRESH-INTERVAL" value when periodically retrieving
1571 data. Note that this property differs from the "URL" property in
1572 that "URL" is meant to provide an alternative representation of
1573 the calendar data rather than the original location of the data.
1575Conformance:
1576 This property can be specified once in an iCalendar object.
1578Example:
1579 The following is an example of this property:
1581 .. code-block:: ics
1583 SOURCE;VALUE=URI:https://example.com/holidays.ics
1585""",
1586)
1588location_property = multi_language_text_property(
1589 "LOCATION",
1590 None,
1591 """The intended venue for the activity defined by a calendar component.
1593Property Parameters:
1594 IANA, non-standard, alternate text
1595 representation, and language property parameters can be specified
1596 on this property.
1598Conformance:
1599 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1600 calendar component.
1601 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1603Description:
1604 Specific venues such as conference or meeting rooms may
1605 be explicitly specified using this property. An alternate
1606 representation may be specified that is a URI that points to
1607 directory information with more structured specification of the
1608 location. For example, the alternate representation may specify
1609 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1610 CID URL :rfc:`2392` pointing to a MIME body part containing a
1611 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1613""",
1614)
1616contacts_property = multi_text_property(
1617 "CONTACT",
1618 """Contact information associated with a calendar component.
1620The contact property holds free-text information for reaching the person or
1621organization responsible in a component, such as a name, phone number, or a
1622reference to more detailed contact data. Calendar clients surface it so
1623attendees know who to contact about the event or task.
1625This property is defined in :rfc:`5545#section-3.8.4.2` and may appear on a
1626``VEVENT``, ``VTODO``, ``VJOURNAL``, or ``VFREEBUSY`` component. For
1627availability components it is defined in :rfc:`7953` and may appear on a
1628``VAVAILABILITY`` or ``VAVAILABLE`` component. An alternate representation may
1629point to a URI, for example, a vCard per :rfc:`2426`, via the ``ALTREP`` parameter.
1631You can get, set, append, and delete contacts on a component. The value is a
1632list of strings; each string is one contact entry.
1634Example:
1635 Add a contact to an event and then read it back.
1637 .. code-block:: pycon
1639 >>> from icalendar import Event
1640 >>> event = Event()
1641 >>> event.add("CONTACT", "Jim Dolittle, ABC Industries, +1-919-555-1234")
1642 >>> [str(c) for c in event.contacts]
1643 ['Jim Dolittle, ABC Industries, +1-919-555-1234']
1644 >>> str(event.contacts[0])
1645 'Jim Dolittle, ABC Industries, +1-919-555-1234'
1646""",
1647)
1650def _timezone_datetime_property(name: str, docs: str):
1651 """Create a property to access the values with a proper timezone."""
1653 return single_utc_property(name, docs)
1656rfc_7953_dtstart_property = _timezone_datetime_property(
1657 "DTSTART",
1658 """Start of the component as a :class:`~datetime.datetime` in UTC.
1660 This is almost the same as
1661 :attr:`Event.DTSTART <icalendar.cal.event.Event.DTSTART>` with one exception:
1662 The values MUST have a timezone and DATE is not allowed.
1664 Description:
1665 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1666 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1667 "DATE-TIME" values specified as either the date with UTC time or
1668 the date with local time and a time zone reference.
1670 """,
1671)
1673rfc_7953_dtend_property = _timezone_datetime_property(
1674 "DTEND",
1675 """End of the component as a :class:`~datetime.datetime` in UTC.
1677 This is almost the same as
1678 :attr:`Event.DTEND <icalendar.cal.event.Event.DTEND>` with one exception:
1679 The values MUST have a timezone and DATE is not allowed.
1681 Description:
1682 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1683 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1684 "DATE-TIME" values specified as either the date with UTC time or
1685 the date with local time and a time zone reference.
1686 """,
1687)
1690@property
1691def rfc_7953_duration_property(self) -> timedelta | None:
1692 """Compute the duration of this component.
1694 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1695 Otherwise, the duration is calculated from :attr:`DTSTART` and
1696 :attr:`DTEND`/:attr:`DURATION`.
1698 This is in accordance with :rfc:`7953`:
1699 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1700 """
1701 duration = self.DURATION
1702 if duration:
1703 return duration
1704 end = self.DTEND
1705 if end is None:
1706 return None
1707 start = self.DTSTART
1708 if start is None:
1709 raise IncompleteComponent("Cannot compute duration without start.")
1710 return end - start
1713@property
1714def rfc_7953_end_property(self) -> timedelta | None:
1715 """Compute the duration of this component.
1717 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1718 Otherwise, the duration is calculated from :attr:`DTSTART` and
1719 :attr:`DTEND`/:attr:`DURATION`.
1721 This is in accordance with :rfc:`7953`:
1722 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1723 """
1724 duration = self.DURATION
1725 if duration:
1726 start = self.DTSTART
1727 if start is None:
1728 raise IncompleteComponent("Cannot compute end without start.")
1729 return start + duration
1730 end = self.DTEND
1731 if end is None:
1732 return None
1733 return end
1736@rfc_7953_end_property.setter
1737def rfc_7953_end_property(self, value: datetime):
1738 self.DTEND = value
1741@rfc_7953_end_property.deleter
1742def rfc_7953_end_property(self):
1743 del self.DTEND
1746def get_start_end_duration_with_validation(
1747 component: Event | Todo,
1748 start_property: Literal["DTSTART"],
1749 end_property: Literal["DTEND", "DUE"],
1750 component_name: Literal["VEVENT", "VTODO"],
1751) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1752 """
1753 Validate the component and return start, end, and duration.
1755 This tests validity according to :rfc:`5545` rules
1756 for ``Event`` and ``Todo`` components.
1758 Parameters:
1759 component: The component to validate, either ``Event`` or ``Todo``.
1760 start_property: The start property name, ``DTSTART``.
1761 end_property: The end property name, either ``DTEND`` for ``Event`` or
1762 ``DUE`` for ``Todo``.
1763 component_name: The component name for error messages,
1764 either ``VEVENT`` or ``VTODO``.
1766 Returns:
1767 tuple: (start, end, duration) values from the component.
1769 Raises:
1770 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1772 """
1773 start = getattr(component, start_property, None)
1774 end = getattr(component, end_property, None)
1775 duration = component.DURATION
1777 # RFC 5545: Only one of end property and DURATION may be present
1778 if duration is not None and end is not None:
1779 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1780 msg = (
1781 f"Only one of {end_name} and DURATION "
1782 f"may be in a {component_name}, not both."
1783 )
1784 raise InvalidCalendar(msg)
1786 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1787 if (
1788 start is not None
1789 and is_date(start)
1790 and duration is not None
1791 and duration.seconds != 0
1792 ):
1793 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1794 raise InvalidCalendar(msg)
1796 # RFC 5545: DTSTART and end property must be of the same type
1797 if start is not None and end is not None and is_date(start) != is_date(end):
1798 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1799 msg = (
1800 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1801 )
1802 raise InvalidCalendar(msg)
1804 return start, end, duration
1807def get_start_property(component: Event | Todo) -> date | datetime:
1808 """
1809 Get the start property with validation.
1811 Parameters:
1812 component: The component from which to get its start property.
1814 Returns:
1815 The ``DTSTART`` value.
1817 Raises:
1818 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1820 """
1821 # Trigger validation by calling _get_start_end_duration
1822 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1823 if start is None:
1824 msg = "No DTSTART given."
1825 raise IncompleteComponent(msg)
1826 return start
1829def get_end_property(
1830 component: Event | Todo, end_property: Literal["DTEND", "DUE"]
1831) -> date | datetime:
1832 """
1833 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1835 Parameters:
1836 component: The component to get end from
1837 end_property: The end property name, either ``DTEND`` for ``Event`` or
1838 ``DUE`` for ``Todo``.
1840 Returns:
1841 The computed end value.
1843 Raises:
1844 ~error.IncompleteComponent: If the provided information is incomplete
1845 to compute the end property.
1847 """
1848 # Trigger validation by calling _get_start_end_duration
1849 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1851 if end is None and duration is None:
1852 if start is None:
1853 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1854 msg = f"No {end_name} or DURATION+DTSTART given."
1855 raise IncompleteComponent(msg)
1857 # Default behavior differs for Event vs Todo:
1858 # Event: date gets +1 day, datetime gets same time
1859 # Todo: both date and datetime get same time (issue #898)
1860 if end_property == "DTEND" and is_date(start):
1861 return start + timedelta(days=1)
1862 return start
1864 if duration is not None:
1865 if start is not None:
1866 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1867 return start
1868 return start + duration
1869 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1870 msg = f"No {end_name} or DURATION+DTSTART given."
1871 raise IncompleteComponent(msg)
1873 return end
1876def get_duration_property(component: Event | Todo) -> timedelta:
1877 """
1878 Get the duration property with fallback calculation from start and end.
1880 Parameters:
1881 component: The component from which to get its duration property.
1883 Returns:
1884 The duration as a timedelta.
1886 """
1887 # First check if DURATION property is explicitly set
1888 if "DURATION" in component:
1889 return component["DURATION"].dt
1891 # Fall back to calculated duration from start and end
1892 return component.end - component.start
1895def set_duration_with_locking(
1896 component: Event | Todo,
1897 duration: timedelta | None,
1898 locked: Literal["start", "end"],
1899 end_property: Literal["DTEND", "DUE"],
1900) -> None:
1901 """
1902 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1904 Parameters:
1905 component: The component to modify, either ``Event`` or ``Todo``.
1906 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1907 locked: Which property to keep unchanged, either ``start`` or ``end``.
1908 end_property: The end property name, either ``DTEND`` for ``Event`` or
1909 ``DUE`` for ``Todo``.
1911 """
1912 # Convert to DURATION property if duration is None
1913 if duration is None:
1914 if "DURATION" in component:
1915 return # Already has DURATION property
1916 current_duration = component.duration
1917 component.DURATION = current_duration
1918 return
1920 if not isinstance(duration, timedelta):
1921 msg = f"Use timedelta, not {type(duration).__name__}."
1922 raise TypeError(msg)
1924 # Validate date/duration compatibility
1925 start = component.DTSTART
1926 if start is not None and is_date(start) and duration.seconds != 0:
1927 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1928 raise InvalidCalendar(msg)
1930 if locked == "start":
1931 # Keep start locked, adjust end
1932 if start is None:
1933 msg = "Cannot set duration without DTSTART. Set start time first."
1934 raise IncompleteComponent(msg)
1935 component.pop(end_property, None) # Remove end property
1936 component.DURATION = duration
1937 elif locked == "end":
1938 # Keep end locked, adjust start
1939 current_end = component.end
1940 component.DTSTART = current_end - duration
1941 component.pop(end_property, None) # Remove end property
1942 component.DURATION = duration
1943 else:
1944 msg = f"locked must be 'start' or 'end', not {locked!r}"
1945 raise ValueError(msg)
1948def set_start_with_locking(
1949 component: Event | Todo,
1950 start: date | datetime,
1951 locked: Literal["duration", "end"] | None,
1952 end_property: Literal["DTEND", "DUE"],
1953) -> None:
1954 """
1955 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1957 Parameters:
1958 component: The component to modify, either ``Event`` or ``Todo``.
1959 start: The start time to set.
1960 locked: Which property to keep unchanged, either ``duration``, ``end``,
1961 or ``None`` for auto-detect.
1962 end_property: The end property name, either ``DTEND`` for ``Event`` or
1963 ``DUE`` for ``Todo``.
1965 """
1966 if locked is None:
1967 # Auto-detect based on existing properties
1968 if "DURATION" in component:
1969 locked = "duration"
1970 elif end_property in component:
1971 locked = "end"
1972 else:
1973 # Default to duration if no existing properties
1974 locked = "duration"
1976 if locked == "duration":
1977 # Keep duration locked, adjust end
1978 current_duration = (
1979 component.duration
1980 if "DURATION" in component or end_property in component
1981 else None
1982 )
1983 component.DTSTART = start
1984 if current_duration is not None:
1985 component.pop(end_property, None) # Remove end property
1986 component.DURATION = current_duration
1987 elif locked == "end":
1988 # Keep end locked, adjust duration
1989 current_end = component.end
1990 component.DTSTART = start
1991 component.pop("DURATION", None) # Remove duration property
1992 setattr(component, end_property, current_end)
1993 else:
1994 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1995 raise ValueError(msg)
1998def set_end_with_locking(
1999 component: Event | Todo,
2000 end: date | datetime,
2001 locked: Literal["start", "duration"],
2002 end_property: Literal["DTEND", "DUE"],
2003) -> None:
2004 """
2005 Set the end with explicit locking behavior for Event and Todo components.
2007 Parameters:
2008 component: The component to modify, either ``Event`` or ``Todo``.
2009 end: The end time to set.
2010 locked: Which property to keep unchanged, either ``start`` or ``duration``.
2011 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
2012 for ``Todo``.
2014 """
2015 if locked == "start":
2016 # Keep start locked, adjust duration
2017 component.pop("DURATION", None) # Remove duration property
2018 setattr(component, end_property, end)
2019 elif locked == "duration":
2020 # Keep duration locked, adjust start
2021 current_duration = component.duration
2022 component.DTSTART = end - current_duration
2023 component.pop(end_property, None) # Remove end property
2024 component.DURATION = current_duration
2025 else:
2026 msg = f"locked must be 'start' or 'duration', not {locked!r}"
2027 raise ValueError(msg)
2030def _get_images(self: Component) -> list[Image]:
2031 """IMAGE specifies an image associated with the calendar or a calendar component.
2033 Description:
2034 This property specifies an image for an iCalendar
2035 object or a calendar component via a URI or directly with inline
2036 data that can be used by calendar user agents when presenting the
2037 calendar data to a user. Multiple properties MAY be used to
2038 specify alternative sets of images with, for example, varying
2039 media subtypes, resolutions, or sizes. When multiple properties
2040 are present, calendar user agents SHOULD display only one of them,
2041 picking one that provides the most appropriate image quality, or
2042 display none. The "DISPLAY" parameter is used to indicate the
2043 intended display mode for the image. The "ALTREP" parameter,
2044 defined in :rfc:`5545`, can be used to provide a "clickable" image
2045 where the URI in the parameter value can be "launched" by a click
2046 on the image in the calendar user agent.
2048 Conformance:
2049 This property can be specified multiple times in an
2050 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
2051 components.
2053 .. note::
2055 At the present moment, this property is read-only. If you require a setter,
2056 please open an issue or a pull request.
2057 """
2058 images = self.get("IMAGE", [])
2059 if not isinstance(images, SEQUENCE_TYPES):
2060 images = [images]
2061 return [Image.from_property_value(img) for img in images]
2064images_property = property(_get_images)
2067def _get_conferences(self: Component) -> list[Conference]:
2068 """Return the CONFERENCE properties as a list.
2070 Purpose:
2071 This property specifies information for accessing a conferencing system.
2073 Conformance:
2074 This property can be specified multiple times in a
2075 "VEVENT" or "VTODO" calendar component.
2077 Description:
2078 This property specifies information for accessing a
2079 conferencing system for attendees of a meeting or task. This
2080 might be for a telephone-based conference number dial-in with
2081 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
2082 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
2083 or https: URI :rfc:`7230`), or for an instant messaging group chat
2084 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
2085 conferencing system is not available, a data: URI :rfc:`2397`
2086 containing a text description can be used.
2088 A conference system can be a bidirectional communication channel
2089 or a uni-directional "broadcast feed".
2091 The "FEATURE" property parameter is used to describe the key
2092 capabilities of the conference system to allow a client to choose
2093 the ones that give the required level of interaction from a set of
2094 multiple properties.
2096 The "LABEL" property parameter is used to convey additional
2097 details on the use of the URI. For example, the URIs or access
2098 codes for the moderator and attendee of a teleconference system
2099 could be different, and the "LABEL" property parameter could be
2100 used to "tag" each "CONFERENCE" property to indicate which is
2101 which.
2103 The "LANGUAGE" property parameter can be used to specify the
2104 language used for text values used with this property (as per
2105 Section 3.2.10 of :rfc:`5545`).
2107 Example:
2108 The following are examples of this property:
2110 .. code-block:: ics
2112 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
2113 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
2114 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2115 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
2116 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2117 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
2118 CONFERENCE;VALUE=URI;FEATURE=CHAT;
2119 LABEL=Chat room:xmpp:chat-123@conference.example.com
2120 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
2121 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
2123 Get all conferences:
2125 .. code-block:: pycon
2127 >>> from icalendar import Event
2128 >>> event = Event()
2129 >>> event.conferences
2130 []
2132 Set a conference:
2134 .. code-block:: pycon
2136 >>> from icalendar import Event, Conference
2137 >>> event = Event()
2138 >>> event.conferences = [
2139 ... Conference(
2140 ... "tel:+1-412-555-0123,,,654321",
2141 ... feature="PHONE,MODERATOR",
2142 ... label="Moderator dial-in",
2143 ... language="EN",
2144 ... )
2145 ... ]
2146 >>> print(event.to_ical())
2147 BEGIN:VEVENT
2148 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
2149 ALUE=URI:tel:+1-412-555-0123,,,654321
2150 END:VEVENT
2152 """
2153 conferences = self.get("CONFERENCE", [])
2154 if not isinstance(conferences, SEQUENCE_TYPES):
2155 conferences = [conferences]
2156 return [Conference.from_uri(conference) for conference in conferences]
2159def _set_conferences(self: Component, conferences: list[Conference] | None):
2160 """Set the conferences."""
2161 _del_conferences(self)
2162 for conference in conferences or []:
2163 self.add("CONFERENCE", conference.to_uri())
2166def _del_conferences(self: Component):
2167 """Delete all conferences."""
2168 self.pop("CONFERENCE")
2171conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2174def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2175 """LINK properties as a list.
2177 Purpose:
2178 LINK provides a reference to external information related to a component.
2180 Property Parameters:
2181 The VALUE parameter is required.
2182 Non-standard, link relation type, format type, label, and language parameters
2183 can also be specified on this property.
2184 The LABEL parameter is defined in :rfc:`7986`.
2186 Conformance:
2187 This property can be specified zero or more times in any iCalendar component.
2188 LINK is specified in :rfc:`9253`.
2189 The LINKREL parameter is required.
2191 Description:
2192 When used in a component, the value of this property points to
2193 additional information related to the component.
2194 For example, it may reference the originating web server.
2196 This property is a serialization of the model in :rfc:`8288`,
2197 where the link target is carried in the property value,
2198 the link context is the containing calendar entity,
2199 and the link relation type and any target attributes
2200 are carried in iCalendar property parameters.
2202 The LINK property parameters map to :rfc:`8288` attributes as follows:
2204 LABEL
2205 This parameter maps to the "title"
2206 attribute defined in Section 3.4.1 of :rfc:`8288`.
2207 LABEL is used to label the destination
2208 of a link such that it can be used as a human-readable identifier
2209 (e.g., a menu entry) in the language indicated by the LANGUAGE
2210 (if present).
2211 LANGUAGE
2212 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2213 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2214 LINKREL
2215 This parameter maps to the link relation type defined in Section 2.1 of
2216 :rfc:`8288`. See `Registered Link Relation Types
2217 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2218 FMTTYPE
2219 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2220 :rfc:`8288`.
2222 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2224 Examples:
2225 The following is an example of this property,
2226 which provides a reference to the source for the calendar object.
2228 .. code-block:: ics
2230 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2231 https://example.com/events
2233 The following is an example of this property,
2234 which provides a reference to an entity from which this one was derived.
2235 The link relation is a vendor-defined value.
2237 .. code-block:: ics
2239 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2240 VALUE=URI:
2241 https://example.com/tasks/01234567-abcd1234.ics
2243 The following is an example of this property,
2244 which provides a reference to a fragment of an XML document.
2245 The link relation is a vendor-defined value.
2247 .. code-block:: ics
2249 LINK;LINKREL="https://example.com/linkrel/costStructure";
2250 VALUE=XML-REFERENCE:
2251 https://example.com/xmlDocs/bidFramework.xml
2252 #xpointer(descendant::CostStruc/range-to(
2253 following::CostStrucEND[1]))
2255 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2257 .. code-block:: pycon
2259 >>> from icalendar import Event, vUri
2260 >>> from datetime import datetime
2261 >>> link = vUri(
2262 ... "http://example.com/event-page",
2263 ... params={"LINKREL":"SOURCE"}
2264 ... )
2265 >>> event = Event.new(
2266 ... start=datetime(2025, 9, 17, 12, 0),
2267 ... summary="An Example Event with a page"
2268 ... )
2269 >>> event.links = [link]
2270 >>> print(event.to_ical())
2271 BEGIN:VEVENT
2272 SUMMARY:An Example Event with a page
2273 DTSTART:20250917T120000
2274 DTSTAMP:20250517T080612Z
2275 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2276 LINK;LINKREL="SOURCE":http://example.com/event-page
2277 END:VEVENT
2279 """
2280 links = self.get("LINK", [])
2281 if not isinstance(links, list):
2282 links = [links]
2283 return links
2286LINKS_TYPE_SETTER: TypeAlias = (
2287 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2288)
2291def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2292 """Set the LINKs."""
2293 _del_links(self)
2294 if links is None:
2295 return
2296 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2297 links = [links]
2298 for link in links:
2299 if type(link) is str:
2300 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2301 self.add("LINK", link)
2304def _del_links(self: Component) -> None:
2305 """Delete all links."""
2306 self.pop("LINK")
2309links_property = property(_get_links, _set_links, _del_links)
2311RELATED_TO_TYPE_SETTER: TypeAlias = (
2312 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2313)
2316def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2317 """RELATED-TO properties as a list.
2319 Purpose:
2320 This property is used to represent a relationship or reference
2321 between one calendar component and another.
2322 :rfc:`9523` allows URI or UID values and a GAP parameter.
2324 Value Type:
2325 :rfc:`5545`: TEXT
2326 :rfc:`9253`: URI, UID
2328 Conformance:
2329 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2330 "VTODO", and "VJOURNAL" calendar components.
2331 Since :rfc:`9523`, this property MAY be specified in any
2332 iCalendar component.
2334 Description (:rfc:`5545`):
2335 The property value consists of the persistent, globally
2336 unique identifier of another calendar component. This value would
2337 be represented in a calendar component by the "UID" property.
2339 By default, the property value points to another calendar
2340 component that has a PARENT relationship to the referencing
2341 object. The "RELTYPE" property parameter is used to either
2342 explicitly state the default PARENT relationship type to the
2343 referenced calendar component or to override the default PARENT
2344 relationship type and specify either a CHILD or SIBLING
2345 relationship. The PARENT relationship indicates that the calendar
2346 component is a subordinate of the referenced calendar component.
2347 The CHILD relationship indicates that the calendar component is a
2348 superior of the referenced calendar component. The SIBLING
2349 relationship indicates that the calendar component is a peer of
2350 the referenced calendar component.
2352 Changes to a calendar component referenced by this property can
2353 have an implicit impact on the related calendar component. For
2354 example, if a group event changes its start or end date or time,
2355 then the related, dependent events will need to have their start
2356 and end dates changed in a corresponding way. Similarly, if a
2357 PARENT calendar component is cancelled or deleted, then there is
2358 an implied impact to the related CHILD calendar components. This
2359 property is intended only to provide information on the
2360 relationship of calendar components. It is up to the target
2361 calendar system to maintain any property implications of this
2362 relationship.
2364 Description (:rfc:`9253`):
2365 By default or when VALUE=UID is specified, the property value
2366 consists of the persistent, globally unique identifier of another
2367 calendar component. This value would be represented in a calendar
2368 component by the UID property.
2370 By default, the property value
2371 points to another calendar component that has a PARENT relationship
2372 to the referencing object. The RELTYPE property parameter is used
2373 to either explicitly state the default PARENT relationship type to
2374 the referenced calendar component or to override the default
2375 PARENT relationship type and specify either a CHILD or SIBLING
2376 relationship or a temporal relationship.
2378 The PARENT relationship
2379 indicates that the calendar component is a subordinate of the
2380 referenced calendar component. The CHILD relationship indicates
2381 that the calendar component is a superior of the referenced calendar
2382 component. The SIBLING relationship indicates that the calendar
2383 component is a peer of the referenced calendar component.
2385 To preserve backwards compatibility, the value type MUST
2386 be UID when the PARENT, SIBLING, or CHILD relationships
2387 are specified.
2389 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2390 or STARTTOSTART relationships define temporal relationships, as
2391 specified in the RELTYPE parameter definition.
2393 The FIRST and NEXT
2394 define ordering relationships between calendar components.
2396 The DEPENDS-ON relationship indicates that the current calendar
2397 component depends on the referenced calendar component in some manner.
2398 For example, a task may be blocked waiting on the other,
2399 referenced, task.
2401 The REFID and CONCEPT relationships establish
2402 a reference from the current component to the referenced component.
2403 Changes to a calendar component referenced by this property
2404 can have an implicit impact on the related calendar component.
2405 For example, if a group event changes its start or end date or
2406 time, then the related, dependent events will need to have their
2407 start and end dates and times changed in a corresponding way.
2408 Similarly, if a PARENT calendar component is canceled or deleted,
2409 then there is an implied impact to the related CHILD calendar
2410 components. This property is intended only to provide information
2411 on the relationship of calendar components.
2413 Deletion of the target component, for example, the target of a
2414 FIRST, NEXT, or temporal relationship, can result in broken links.
2416 It is up to the target calendar system to maintain any property
2417 implications of these relationships.
2419 Examples:
2420 :rfc:`5545` examples of this property:
2422 .. code-block:: ics
2424 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2426 .. code-block:: ics
2428 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2430 :rfc:`9253` examples of this property:
2432 .. code-block:: ics
2434 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2435 https://example.com/caldav/user/jb/cal/
2436 19960401-080045-4000F192713.ics
2438 See also :class:`icalendar.enums.RELTYPE`.
2440 """
2441 result = self.get("RELATED-TO", [])
2442 if not isinstance(result, list):
2443 return [result]
2444 return result
2447def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2448 """Set the RELATED-TO properties."""
2449 _del_related_to(self)
2450 if values is None:
2451 return
2452 if not isinstance(values, list):
2453 values = [values]
2454 for value in values:
2455 self.add("RELATED-TO", value)
2458def _del_related_to(self: Component):
2459 """Delete the RELATED-TO properties."""
2460 self.pop("RELATED-TO", None)
2463related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2466def _get_concepts(self: Component) -> list[vUri]:
2467 """CONCEPT
2469 Purpose:
2470 CONCEPT defines the formal categories for a calendar component.
2472 Conformance:
2473 Since :rfc:`9253`,
2474 this property can be specified zero or more times in any iCalendar component.
2476 Description:
2477 This property is used to specify formal categories or classifications of
2478 the calendar component. The values are useful in searching for a calendar
2479 component of a particular type and category.
2481 This categorization is distinct from the more informal "tagging" of components
2482 provided by the existing CATEGORIES property. It is expected that the value of
2483 the CONCEPT property will reference an external resource that provides
2484 information about the categorization.
2486 In addition, a structured URI value allows for hierarchical categorization of
2487 events.
2489 Possible category resources are the various proprietary systems, for example,
2490 the Library of Congress, or an open source of categorization data.
2492 Examples:
2493 The following is an example of this property.
2494 It points to a server acting as the source for the calendar object.
2496 .. code-block:: ics
2498 CONCEPT:https://example.com/event-types/arts/music
2500 .. seealso::
2502 :attr:`icalendar.prop.categories.vCategory`
2503 """
2504 concepts = self.get("CONCEPT", [])
2505 if not isinstance(concepts, list):
2506 concepts = [concepts]
2507 return concepts
2510CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2513def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2514 """Set the concepts."""
2515 _del_concepts(self)
2516 if concepts is None:
2517 return
2518 if not isinstance(concepts, list):
2519 concepts = [concepts]
2520 for value in concepts:
2521 self.add("CONCEPT", value)
2524def _del_concepts(self: Component):
2525 """Delete the concepts."""
2526 self.pop("CONCEPT", None)
2529concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2532def multi_string_property(name: str, doc: str):
2533 """A property for an iCalendar Property that can occur multiple times."""
2535 def fget(self: Component) -> list[str]:
2536 """Get the values of a multi-string property."""
2537 value = self.get(name, [])
2538 if not isinstance(value, list):
2539 value = [value]
2540 return value
2542 def fset(self: Component, value: list[str] | str | None) -> None:
2543 """Set the values of a multi-string property."""
2544 fdel(self)
2545 if value is None:
2546 return
2547 if not isinstance(value, list):
2548 value = [value]
2549 for value in value:
2550 self.add(name, value)
2552 def fdel(self: Component):
2553 """Delete the values of a multi-string property."""
2554 self.pop(name, None)
2556 return property(fget, fset, fdel, doc=doc)
2559refids_property = multi_string_property(
2560 "REFID",
2561 """REFID
2563Purpose:
2564 REFID acts as a key for associated iCalendar entities.
2566Conformance:
2567 Since :rfc:`9253`,
2568 this property can be specified zero or more times in any iCalendar component.
2570Description:
2571 The value of this property is free-form text that creates an
2572 identifier for associated components.
2573 All components that use the same REFID value are associated through
2574 that value and can be located or retrieved as a group.
2575 For example, all of the events in a travel itinerary
2576 would have the same REFID value, so as to be grouped together.
2578Examples:
2579 The following is an example of this property.
2581 .. code-block:: ics
2583 REFID:itinerary-2014-11-17
2585 Use a REFID to associate several VTODOs:
2587 .. code-block:: pycon
2589 >>> from icalendar import Todo
2590 >>> todo_1 = Todo.new(
2591 ... summary="turn off stove",
2592 ... refids=["travel", "alps"]
2593 ... )
2594 >>> todo_2 = Todo.new(
2595 ... summary="pack backpack",
2596 ... refids=["travel", "alps"]
2597 ... )
2598 >>> todo_1.refids == todo_2.refids
2599 True
2601.. note::
2603 When you assign a list to this property, the returned list
2604 is the same object stored in the component. Modifying it (``append()``,
2605 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2606 the component stores.
2608 However, if you assign a single string value to this property, the returned
2609 list is a temporary copy, and changes to this list don't affect the component.
2610""",
2611)
2613REQUEST_STATUS_property = multi_string_property(
2614 "REQUEST-STATUS",
2615 """This property defines the status code returned for a scheduling request.
2617You can assign a single string, a list of strings, or ``None`` to this
2618property. The property stores a :class:`str` as-is. Assigning ``None`` or
2619an empty list removes all REQUEST-STATUS values, as does deleting the
2620property.
2622The value consists of a short return status code component, a longer
2623return status description component, and optionally a status-specific
2624data component, separated by semicolons (statcode;statdesc[;extdata]).
2625The return status components are defined in :rfc:`5545#section-3.8.8.3`.
2627The REQUEST-STATUS property can be specified in the following
2628icalendar components as ``REQUEST_STATUS``.
2630- :attr:`Event.REQUEST_STATUS <icalendar.cal.event.Event.REQUEST_STATUS>`
2631- :attr:`FreeBusy.REQUEST_STATUS <icalendar.cal.free_busy.FreeBusy.REQUEST_STATUS>`
2632- :attr:`Journal.REQUEST_STATUS <icalendar.cal.journal.Journal.REQUEST_STATUS>`
2633- :attr:`Todo.REQUEST_STATUS <icalendar.cal.todo.Todo.REQUEST_STATUS>`
2635Note:
2636 When you assign a list to this property, the returned list
2637 is the same object stored in the component. Modifying it (``append()``,
2638 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2639 the component stores.
2641 However, if you assign a single string value to this property, the returned
2642 list is a temporary copy, and changes to this list don't affect the component.
2644Parameters:
2645 request_status(str | list[str] | None):
2646 Either a single status string, a list of status strings, or
2647 ``None`` to set the component's status code returned for a
2648 scheduling request.
2650Example:
2651 Add a request status to an event:
2653 .. code-block:: pycon
2655 >>> from icalendar import Event
2656 >>> event = Event.new(request_status="2.0;Success")
2657 >>> event.REQUEST_STATUS == ["2.0;Success"]
2658 True
2659""",
2660)
2662RESOURCES_property = multi_string_property(
2663 "RESOURCES",
2664 """This property defines resources for a calendar component.
2666You can assign a single string, a list of strings, or ``None`` to this
2667property. The property stores a :class:`str` as-is. Assigning ``None`` or
2668an empty list removes all RESOURCES values, as does deleting the property.
2670The value is a comma-separated list of resources, such as equipment,
2671facilities, or other things that the component needs. Each item of the
2672list becomes its own RESOURCES property value. See
2673:rfc:`5545#section-3.8.1.10` for the specification.
2675The RESOURCES property can be specified in the following
2676icalendar components as ``resources`` using the component's
2677``new()`` constructor.
2679- :attr:`Event.RESOURCES <icalendar.cal.event.Event.RESOURCES>`
2680- :attr:`Todo.RESOURCES <icalendar.cal.todo.Todo.RESOURCES>`
2682Parameters:
2683 resources(str | list[str] | None):
2684 Either a single resource string, a list of resource strings,
2685 or ``None`` to set the component's resources.
2687Note:
2688 When you assign a list to this property, the returned list
2689 is the same object stored in the component. Modifying it (``append()``,
2690 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2691 the component stores.
2693 However, if you assign a single string value to this property, the returned
2694 list is a temporary copy, and changes to this list don't affect the component.
2696Example:
2697 Add resources to an event:
2699 .. code-block:: pycon
2701 >>> from icalendar import Event
2702 >>> event = Event.new(resources=["EASEL", "PROJECTOR", "VCR"])
2703 >>> event.RESOURCES == ["EASEL", "PROJECTOR", "VCR"]
2704 True
2705""",
2706)
2709ATTACHMENTS_TYPE_SETTER: TypeAlias = (
2710 str | bytes | vUri | vBinary | None | list[str | bytes | vUri | vBinary]
2711)
2714def _normalize_attachment(value: str | bytes | vUri | vBinary) -> vUri | vBinary:
2715 """Convert one attachment value."""
2716 if isinstance(value, (vUri, vBinary)):
2717 return value
2718 if isinstance(value, str):
2719 return vUri(value)
2720 if isinstance(value, bytes):
2721 return vBinary(value)
2722 raise TypeError(
2723 f"Attachments must be str, bytes, vUri, or vBinary, not {type(value).__name__}."
2724 )
2727def _get_attachments(self: Component) -> list[vUri | vBinary]:
2728 """Get all the attachments"""
2729 attachments = self.get("ATTACH", [])
2730 if not isinstance(attachments, SEQUENCE_TYPES):
2731 return [attachments]
2732 return list(attachments)
2735def _set_attachments(self: Component, value: ATTACHMENTS_TYPE_SETTER) -> None:
2736 """Set attachments properties"""
2737 if value is None:
2738 _del_attachments(self)
2739 return
2740 if not isinstance(value, list):
2741 value = [value]
2742 attachments = [_normalize_attachment(attachment) for attachment in value]
2743 _del_attachments(self)
2744 for attachment in attachments:
2745 self.add("ATTACH", attachment)
2748def _del_attachments(self: Component) -> None:
2749 """Delete all attachments"""
2750 self.pop("ATTACH", None)
2753attachments_property = property(
2754 _get_attachments,
2755 _set_attachments,
2756 _del_attachments,
2757 """This property defines the attachments for a component.
2759Setting this property replaces all existing attachments. A :class:`str`
2760is converted to :class:`~icalendar.prop.uri.vUri`, and :class:`bytes` is
2761converted to :class:`~icalendar.prop.binary.vBinary`. Values that are
2762already :class:`~icalendar.prop.uri.vUri` or
2763:class:`~icalendar.prop.binary.vBinary` are stored unchanged, so their
2764parameters are preserved. Setting ``None`` or an empty list removes all
2765attachments, as does deleting the property.
2767Parameters:
2768 attachments(str | bytes | vUri | vBinary | list | None):
2769 A single attachment, or a list of attachments to set. Accepts
2770 :class:`str`, :class:`bytes`, :class:`~icalendar.prop.uri.vUri`,
2771 and :class:`~icalendar.prop.binary.vBinary`, individually or
2772 mixed together in a list.
2774Example:
2775 Attach a URI to an event, then replace it with a URI and inline
2776 binary data together:
2778 .. code-block:: pycon
2780 >>> from icalendar import Event, vUri, vBinary
2781 >>> event = Event()
2782 >>> event.attachments
2783 []
2784 >>> event.attachments = ["https://example.com/agenda.pdf"]
2785 >>> print(event.to_ical().decode())
2786 BEGIN:VEVENT
2787 ATTACH:https://example.com/agenda.pdf
2788 END:VEVENT
2789 >>> event.attachments = [
2790 ... vUri(
2791 ... "https://example.com/agenda.pdf",
2792 ... params={"FMTTYPE": "application/pdf"},
2793 ... ),
2794 ... vBinary(b"image-data", params={"FMTTYPE": "image/png"},),
2795 ... ]
2796 >>> len(event.attachments)
2797 2
2799.. note::
2801 An alarm as an audio action must not contain more than one attachment.
2803 List modifications do not modify the component. Methods such as
2804 ``append()``, ``extend()``, and ``remove()``, as well as item
2805 assignment, act on a copy. Assign the list back to the property, or
2806 use :meth:`Component.add <icalendar.cal.component.Component.add>`
2807 with a typed value instead.
2809.. seealso::
2811 :rfc:`5545#section-3.8.1.1` for the definition of the ``ATTACH``
2812 property.
2813""",
2814)
2817__all__ = [
2818 "ATTACHMENTS_TYPE_SETTER",
2819 "ATTENDEE_TYPE_SETTER",
2820 "CONCEPTS_TYPE_SETTER",
2821 "LINKS_TYPE_SETTER",
2822 "RECURRENCE_ID",
2823 "RELATED_TO_TYPE_SETTER",
2824 "REQUEST_STATUS_property",
2825 "RESOURCES_property",
2826 "attachments_property",
2827 "attendees_property",
2828 "busy_type_property",
2829 "categories_property",
2830 "class_property",
2831 "color_property",
2832 "comments_property",
2833 "concepts_property",
2834 "conferences_property",
2835 "contacts_property",
2836 "create_single_property",
2837 "description_property",
2838 "descriptions_property",
2839 "duration_property",
2840 "exdates_property",
2841 "get_duration_property",
2842 "get_end_property",
2843 "get_start_end_duration_with_validation",
2844 "get_start_property",
2845 "images_property",
2846 "links_property",
2847 "location_property",
2848 "multi_language_text_property",
2849 "multi_string_property",
2850 "organizer_property",
2851 "priority_property",
2852 "property_del_duration",
2853 "property_doc_duration_template",
2854 "property_get_duration",
2855 "property_set_duration",
2856 "rdates_property",
2857 "refids_property",
2858 "related_to_property",
2859 "repeat_property",
2860 "rfc_7953_dtend_property",
2861 "rfc_7953_dtstart_property",
2862 "rfc_7953_duration_property",
2863 "rfc_7953_end_property",
2864 "rrules_property",
2865 "sequence_property",
2866 "set_duration_with_locking",
2867 "set_end_with_locking",
2868 "set_start_with_locking",
2869 "single_int_property",
2870 "single_utc_property",
2871 "source_property",
2872 "status_property",
2873 "summary_property",
2874 "transparency_property",
2875 "uid_property",
2876 "url_property",
2877]