Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 29%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Attributes of Components and properties."""
3from __future__ import annotations
5import itertools
6from 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
37def _get_rdates(
38 self: Component,
39) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]:
40 """The RDATE property defines the list of DATE-TIME values for recurring components.
42 RDATE is defined in :rfc:`5545`.
43 The return value is a list of tuples ``(start, end)``.
45 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
46 with and without timezone.
48 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
49 if the end is specified.
51 Value Type:
52 The default value type for this property is DATE-TIME.
53 The value type can be set to DATE or PERIOD.
55 Property Parameters:
56 IANA, non-standard, value data type, and time
57 zone identifier property parameters can be specified on this
58 property.
60 Conformance:
61 This property can be specified in recurring "VEVENT",
62 "VTODO", and "VJOURNAL" calendar components as well as in the
63 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
64 calendar component.
66 Description:
67 This property can appear along with the "RRULE"
68 property to define an aggregate set of repeating occurrences.
69 When they both appear in a recurring component, the recurrence
70 instances are defined by the union of occurrences defined by both
71 the "RDATE" and "RRULE".
73 The recurrence dates, if specified, are used in computing the
74 recurrence set. The recurrence set is the complete set of
75 recurrence instances for a calendar component. The recurrence set
76 is generated by considering the initial "DTSTART" property along
77 with the "RRULE", "RDATE", and "EXDATE" properties contained
78 within the recurring component. The "DTSTART" property defines
79 the first instance in the recurrence set. The "DTSTART" property
80 value SHOULD match the pattern of the recurrence rule, if
81 specified. The recurrence set generated with a "DTSTART" property
82 value that doesn't match the pattern of the rule is undefined.
83 The final recurrence set is generated by gathering all of the
84 start DATE-TIME values generated by any of the specified "RRULE"
85 and "RDATE" properties, and then excluding any start DATE-TIME
86 values specified by "EXDATE" properties. This implies that start
87 DATE-TIME values specified by "EXDATE" properties take precedence
88 over those specified by inclusion properties (i.e., "RDATE" and
89 "RRULE"). Where duplicate instances are generated by the "RRULE"
90 and "RDATE" properties, only one recurrence is considered.
91 Duplicate instances are ignored.
93 Example:
94 Below, we set one RDATE in a list and get the resulting tuple of start and end.
96 .. code-block:: pycon
98 >>> from icalendar import Event
99 >>> from datetime import datetime
100 >>> event = Event()
102 # Add a list of recurrence dates
103 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
104 >>> event.rdates
105 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
107 .. note::
109 Modifying the returned list does not change the RDATE value. Assign to
110 ``rdates`` for the relevant component or use
111 :meth:`Component.add <icalendar.cal.component.Component.add>` instead.
112 If you want to compute recurrences, have a look at
113 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
115 """
116 result = []
117 rdates = self.get("RDATE", [])
118 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
119 for dts in rdates.dts:
120 rdate = dts.dt
121 if isinstance(rdate, tuple):
122 # we have a period as rdate
123 if isinstance(rdate[1], timedelta):
124 result.append((rdate[0], rdate[0] + rdate[1]))
125 else:
126 result.append(rdate)
127 else:
128 # we have a date/datetime
129 result.append((rdate, None))
130 return result
133def _set_rdates(self: Component, value) -> None:
134 """Set the RDATE values, replacing any existing ones.
136 ``value`` is a list as returned by :attr:`rdates` (each item a date, a
137 datetime, or a ``(start, end)`` period tuple). Setting an empty list or
138 :obj:`None` removes the RDATE property.
139 """
140 _del_rdates(self)
141 if value:
142 self.add("RDATE", value)
145def _del_rdates(self: Component) -> None:
146 """Delete all RDATE values."""
147 self.pop("RDATE", None)
150rdates_property = property(_get_rdates, _set_rdates, _del_rdates)
153def _get_exdates(self: Component) -> list[date | datetime]:
154 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
156 EXDATE is defined in :rfc:`5545`.
158 Value Type:
159 The default value type for this property is DATE-TIME.
160 The value type can be set to DATE.
162 Property Parameters:
163 IANA, non-standard, value data type, and time
164 zone identifier property parameters can be specified on this
165 property.
167 Conformance:
168 This property can be specified in recurring "VEVENT",
169 "VTODO", and "VJOURNAL" calendar components as well as in the
170 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
171 calendar component.
173 Description:
174 The exception dates, if specified, are used in
175 computing the recurrence set. The recurrence set is the complete
176 set of recurrence instances for a calendar component. The
177 recurrence set is generated by considering the initial "DTSTART"
178 property along with the "RRULE", "RDATE", and "EXDATE" properties
179 contained within the recurring component. The "DTSTART" property
180 defines the first instance in the recurrence set. The "DTSTART"
181 property value SHOULD match the pattern of the recurrence rule, if
182 specified. The recurrence set generated with a "DTSTART" property
183 value that doesn't match the pattern of the rule is undefined.
184 The final recurrence set is generated by gathering all of the
185 start DATE-TIME values generated by any of the specified "RRULE"
186 and "RDATE" properties, and then excluding any start DATE-TIME
187 values specified by "EXDATE" properties. This implies that start
188 DATE-TIME values specified by "EXDATE" properties take precedence
189 over those specified by inclusion properties (i.e., "RDATE" and
190 "RRULE"). When duplicate instances are generated by the "RRULE"
191 and "RDATE" properties, only one recurrence is considered.
192 Duplicate instances are ignored.
194 The "EXDATE" property can be used to exclude the value specified
195 in "DTSTART". However, in such cases, the original "DTSTART" date
196 MUST still be maintained by the calendaring and scheduling system
197 because the original "DTSTART" value has inherent usage
198 dependencies by other properties such as the "RECURRENCE-ID".
200 Example:
201 Below, we add an exdate in a list and get the resulting list of exdates.
203 .. code-block:: pycon
205 >>> from icalendar import Event
206 >>> from datetime import datetime
207 >>> event = Event()
209 # Add a list of excluded dates
210 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
211 >>> event.exdates
212 [datetime.datetime(2025, 4, 28, 16, 5)]
214 .. note::
216 Modifying the returned list does not change the EXDATE value. Assign to
217 ``exdates`` for the relevant component or use
218 :meth:`Component.add <icalendar.cal.component.Component.add>` instead.
219 If you want to compute recurrences, have a look at
220 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
222 """
223 result = []
224 exdates = self.get("EXDATE", [])
225 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
226 for dts in exdates.dts:
227 exdate = dts.dt
228 # we have a date/datetime
229 result.append(exdate)
230 return result
233def _set_exdates(self: Component, value) -> None:
234 """Set the EXDATE values, replacing any existing ones.
236 ``value`` is a list as returned by :attr:`exdates` (each item a date or a
237 datetime). Setting an empty list or :obj:`None` removes the EXDATE property.
238 """
239 _del_exdates(self)
240 if value:
241 self.add("EXDATE", value)
244def _del_exdates(self: Component) -> None:
245 """Delete all EXDATE values."""
246 self.pop("EXDATE", None)
249exdates_property = property(_get_exdates, _set_exdates, _del_exdates)
252def _get_rrules(self: Component) -> list[vRecur]:
253 """RRULE defines a rule or repeating pattern for recurring components.
255 RRULE is defined in :rfc:`5545`.
256 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
258 Property Parameters:
259 IANA and non-standard property parameters can
260 be specified on this property.
262 Conformance:
263 This property can be specified in recurring "VEVENT",
264 "VTODO", and "VJOURNAL" calendar components as well as in the
265 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
266 calendar component, but it SHOULD NOT be specified more than once.
267 The recurrence set generated with multiple "RRULE" properties is
268 undefined.
270 Description:
271 The recurrence rule, if specified, is used in computing
272 the recurrence set. The recurrence set is the complete set of
273 recurrence instances for a calendar component. The recurrence set
274 is generated by considering the initial "DTSTART" property along
275 with the "RRULE", "RDATE", and "EXDATE" properties contained
276 within the recurring component. The "DTSTART" property defines
277 the first instance in the recurrence set. The "DTSTART" property
278 value SHOULD be synchronized with the recurrence rule, if
279 specified. The recurrence set generated with a "DTSTART" property
280 value not synchronized with the recurrence rule is undefined. The
281 final recurrence set is generated by gathering all of the start
282 DATE-TIME values generated by any of the specified "RRULE" and
283 "RDATE" properties, and then excluding any start DATE-TIME values
284 specified by "EXDATE" properties. This implies that start DATE-
285 TIME values specified by "EXDATE" properties take precedence over
286 those specified by inclusion properties (i.e., "RDATE" and
287 "RRULE"). Where duplicate instances are generated by the "RRULE"
288 and "RDATE" properties, only one recurrence is considered.
289 Duplicate instances are ignored.
291 The "DTSTART" property specified within the iCalendar object
292 defines the first instance of the recurrence. In most cases, a
293 "DTSTART" property of DATE-TIME value type used with a recurrence
294 rule, should be specified as a date with local time and time zone
295 reference to make sure all the recurrence instances start at the
296 same local time regardless of time zone changes.
298 If the duration of the recurring component is specified with the
299 "DTEND" or "DUE" property, then the same exact duration will apply
300 to all the members of the generated recurrence set. Else, if the
301 duration of the recurring component is specified with the
302 "DURATION" property, then the same nominal duration will apply to
303 all the members of the generated recurrence set and the exact
304 duration of each recurrence instance will depend on its specific
305 start time. For example, recurrence instances of a nominal
306 duration of one day will have an exact duration of more or less
307 than 24 hours on a day where a time zone shift occurs. The
308 duration of a specific recurrence may be modified in an exception
309 component or simply by using an "RDATE" property of PERIOD value
310 type.
312 Examples:
313 Daily for 10 occurrences:
315 .. code-block:: pycon
317 >>> from icalendar import Event
318 >>> from datetime import datetime
319 >>> from zoneinfo import ZoneInfo
320 >>> event = Event()
321 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
322 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
323 >>> print(event.to_ical())
324 BEGIN:VEVENT
325 DTSTART;TZID=America/New_York:19970902T090000
326 RRULE:FREQ=DAILY;COUNT=10
327 END:VEVENT
328 >>> event.rrules
329 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
331 Daily until December 24, 1997:
333 .. code-block:: pycon
335 >>> from icalendar import Event, vRecur
336 >>> from datetime import datetime
337 >>> from zoneinfo import ZoneInfo
338 >>> event = Event()
339 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
340 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
341 >>> print(event.to_ical())
342 BEGIN:VEVENT
343 DTSTART;TZID=America/New_York:19970902T090000
344 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
345 END:VEVENT
346 >>> event.rrules
347 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
349 .. note::
351 You cannot modify the RRULE value by modifying the result.
352 Use :meth:`Component.add <icalendar.cal.component.Component.add>` to add values.
354 If you want to compute recurrences, have a look at
355 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
357 """ # noqa: E501
358 rrules = self.get("RRULE", [])
359 if not isinstance(rrules, list):
360 return [rrules]
361 return rrules
364rrules_property = property(_get_rrules)
367def multi_language_text_property(
368 main_prop: str, compatibility_prop: str | None, doc: str
369) -> property:
370 """This creates a text property.
372 This property can be defined several times with different ``LANGUAGE`` parameters.
374 Parameters:
375 main_prop (str): The property to set and get, such as ``NAME``
376 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
377 doc (str): The documentation string
378 """
380 def fget(self: Component) -> str | None:
381 """Get the property"""
382 result = self.get(main_prop)
383 if result is None and compatibility_prop is not None:
384 result = self.get(compatibility_prop)
385 if isinstance(result, list):
386 for item in result:
387 if "LANGUAGE" not in item.params:
388 return item
389 return result
391 def fset(self: Component, value: str | None):
392 """Set the property."""
393 fdel(self)
394 if value is not None:
395 self.add(main_prop, value)
396 if compatibility_prop is not None:
397 self.add(compatibility_prop, value)
399 def fdel(self: Component):
400 """Delete the property."""
401 self.pop(main_prop, None)
402 if compatibility_prop is not None:
403 self.pop(compatibility_prop, None)
405 return property(fget, fset, fdel, doc)
408def single_int_property(
409 prop: str, default: int, doc: str, *, min_value: int | None = None
410) -> property:
411 """Create a property for an int value that exists only once.
413 Parameters:
414 default: Required. The default value.
415 doc: Required. The documentation string.
416 prop: Required. The name of the property.
417 min_value: If set, the value must be greater than or equal to this minimum.
419 .. versionadded:: 7.3.0
420 Added the ``min_value`` parameter.
421 """
423 def fget(self: Component) -> int:
424 """Get the property"""
425 try:
426 return int(self.get(prop, default))
427 except ValueError as e:
428 raise InvalidCalendar(f"{prop} must be an int") from e
430 def fset(self: Component, value: int | None):
431 """Set the property."""
432 if value is not None:
433 if not isinstance(value, int) or isinstance(value, bool):
434 raise TypeError(f"{prop} must be an int, got {value!r}")
435 if min_value is not None and value < min_value:
436 raise InvalidCalendar(f"{prop} must be >= {min_value}, got {value}")
437 fdel(self)
438 if value is not None:
439 self.add(prop, value)
441 def fdel(self: Component):
442 """Delete the property."""
443 self.pop(prop, None)
445 return property(fget, fset, fdel, doc)
448def single_utc_property(name: str, docs: str) -> property:
449 """Create a property to access a value of datetime in UTC timezone.
451 Parameters:
452 name: name of the property
453 docs: documentation string
454 """
456 def fget(self: Component) -> datetime | None:
457 """Get the value."""
458 if name not in self:
459 return None
460 dt = self.get(name)
461 if isinstance(dt, (vText, vUnknown)):
462 # we might be in an attribute that is not typed
463 value = vDDDTypes.from_ical(dt)
464 else:
465 value = getattr(dt, "dt", dt)
466 if value is None or not isinstance(value, date):
467 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
468 return tzp.localize_utc(value)
470 def fset(self: Component, value: datetime | None):
471 """Set the value"""
472 if value is None:
473 fdel(self)
474 return
475 if not isinstance(value, date):
476 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
477 fdel(self)
478 self.add(name, tzp.localize_utc(value))
480 def fdel(self: Component):
481 """Delete the property."""
482 self.pop(name, None)
484 return property(fget, fset, fdel, doc=docs)
487def single_string_property(
488 name: str, docs: str, other_name: str | list[str] | None = None, default: str = ""
489) -> property:
490 """Create a property to access a single string value."""
491 other_names = (
492 []
493 if other_name is None
494 else [other_name]
495 if isinstance(other_name, str)
496 else list(other_name)
497 )
499 def fget(self: Component) -> str:
500 """Get the value."""
501 result = self.get(name, None)
502 if result is None:
503 for alias in other_names:
504 result = self.get(alias, None)
505 if result is not None:
506 break
507 if result is None or result == []:
508 return default
509 if isinstance(result, list):
510 return result[0]
511 return result
513 def fset(self: Component, value: str | None):
514 """Set the value.
516 Setting the value to None will delete it.
517 """
518 fdel(self)
519 if value is not None:
520 self.add(name, value)
522 def fdel(self: Component):
523 """Delete the property."""
524 self.pop(name, None)
525 for alias in other_names:
526 self.pop(alias, None)
528 return property(fget, fset, fdel, doc=docs)
531color_property = single_string_property(
532 "COLOR",
533 """This property specifies a color used for displaying the component.
535 This implements :rfc:`7986` ``COLOR`` property.
537 Property Parameters:
538 IANA and non-standard property parameters can
539 be specified on this property.
541 Conformance:
542 This property can be specified once in an iCalendar
543 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
545 Description:
546 This property specifies a color that clients MAY use
547 when presenting the relevant data to a user. Typically, this
548 would appear as the "background" color of events or tasks. The
549 value is a case-insensitive color name taken from the CSS3 set of
550 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
552 Example:
553 ``"turquoise"``, ``"#ffffff"``
555 .. code-block:: pycon
557 >>> from icalendar import Todo
558 >>> todo = Todo()
559 >>> todo.color = "green"
560 >>> print(todo.to_ical())
561 BEGIN:VTODO
562 COLOR:green
563 END:VTODO
564 """,
565)
567sequence_property = single_int_property(
568 "SEQUENCE",
569 0,
570 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
572Value Type:
573 INTEGER
575Property Parameters:
576 IANA and non-standard property parameters can be specified on this property.
578Conformance:
579 The property can be specified in "VEVENT", "VTODO", or
580 "VJOURNAL" calendar component.
582Description:
583 When a calendar component is created, its sequence
584 number is 0. It is monotonically incremented by the "Organizer's"
585 CUA each time the "Organizer" makes a significant revision to the
586 calendar component.
588 The "Organizer" includes this property in an iCalendar object that
589 it sends to an "Attendee" to specify the current version of the
590 calendar component.
592 The "Attendee" includes this property in an iCalendar object that
593 it sends to the "Organizer" to specify the version of the calendar
594 component to which the "Attendee" is referring.
596 A change to the sequence number is not the mechanism that an
597 "Organizer" uses to request a response from the "Attendees". The
598 "RSVP" parameter on the "ATTENDEE" property is used by the
599 "Organizer" to indicate that a response from the "Attendees" is
600 requested.
602 Recurrence instances of a recurring component MAY have different
603 sequence numbers.
605Examples:
606 The following is an example of this property for a calendar
607 component that was just created by the "Organizer":
609 .. code-block:: pycon
611 >>> from icalendar import Event
612 >>> event = Event()
613 >>> event.sequence
614 0
616 The following is an example of this property for a calendar
617 component that has been revised 10 different times by the
618 "Organizer":
620 .. code-block:: pycon
622 >>> from icalendar import Calendar
623 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
624 >>> event = calendar.events[0]
625 >>> event.sequence
626 10
628 Raises:
629 TypeError: If the value is not an ``int``. Booleans are rejected, too,
630 even though ``bool`` subclasses ``int``.
632 ~icalendar.error.InvalidCalendar: If the value is negative.
634 .. versionchanged:: 7.3.0
635 Negative values are no longer accepted.
636 """, # noqa: E501
637 min_value=0,
638)
641def _get_categories(component: Component) -> list[str]:
642 """Get all the categories."""
643 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES")
644 if isinstance(categories, list):
645 _set_categories(
646 component,
647 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
648 )
649 return _get_categories(component)
650 if categories is None:
651 categories = vCategory([])
652 component.add("CATEGORIES", categories)
653 return categories.cats
656def _set_categories(component: Component, cats: Sequence[str] | None) -> None:
657 """Set the categories."""
658 if not cats and cats != []:
659 _del_categories(component)
660 return
661 component["CATEGORIES"] = categories = vCategory(cats)
662 if isinstance(cats, list):
663 cats.clear()
664 cats.extend(categories.cats)
665 categories.cats = cats
668def _del_categories(component: Component) -> None:
669 """Delete the categories."""
670 component.pop("CATEGORIES", None)
673categories_property = property(
674 _get_categories,
675 _set_categories,
676 _del_categories,
677 """This property defines the categories for a component.
679The categories property is used to specify categories or subtypes of the
680calendar component. The categories are useful to search for a calendar
681component of a particular type and category.
683Within the calendar components, specify categories as a list of strings.
684You can get, set, and delete categories for a component.
686This property can be used in icalendar through its Python attributes of:
688- :attr:`Available.categories <icalendar.cal.available.Available.categories>`
689- :attr:`Availability.categories <icalendar.cal.availability.Availability.categories>`
690- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>`
691- :attr:`Event.categories <icalendar.cal.event.Event.categories>`
692- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>`
693- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>`
695The categories property for ``Available`` and ``Availability`` complies with
696:rfc:`7953#section-3.1`, for ``Event``, ``Journal``, and ``Todo`` with
697:rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`.
699Note:
700 At present, icalendar doesn't take the LANGUAGE parameter as defined
701 in :rfc:`5545#section-3.2.10` into account.
703Parameters:
704 categories(list[str]): A list of categories as strings.
706Example:
707 Create an event, add categories to it, print its ical representation,
708 append another category, and finally compare the result
709 against its expected value.
711 .. code-block:: pycon
713 >>> from icalendar import Event
714 >>> event = Event()
715 >>> event.categories = ["Work", "Meeting"]
716 >>> print(event.to_ical())
717 BEGIN:VEVENT
718 CATEGORIES:Work,Meeting
719 END:VEVENT
720 >>> event.categories.append("Lecture")
721 >>> event.categories == ["Work", "Meeting", "Lecture"]
722 True
724See also:
725 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>`
726""",
727)
730def _get_attendees(self: Component) -> list[vCalAddress]:
731 """Get attendees."""
732 value = self.get("ATTENDEE")
733 if value is None:
734 value = []
735 self["ATTENDEE"] = value
736 return value
737 if isinstance(value, vCalAddress):
738 return [value]
739 return value
742ATTENDEE_TYPE_SETTER: TypeAlias = Sequence[vCalAddress | str] | vCalAddress | str | None
745def _set_attendees(self: Component, value: ATTENDEE_TYPE_SETTER):
746 """Set attendees."""
747 _del_attendees(self)
748 if value is None:
749 return
750 if isinstance(value, (vCalAddress, str)):
751 value = [value]
752 elif not isinstance(value, list):
753 value = list(value) if isinstance(value, Sequence) else [value]
754 for index, attendee in enumerate(value):
755 # vCalAddress subclasses str, so exclude it before normalizing strings
756 if not isinstance(attendee, vCalAddress) and isinstance(attendee, str):
757 value[index] = vCalAddress.new(attendee)
758 self["ATTENDEE"] = value
761def _del_attendees(self: Component):
762 """Delete all attendees."""
763 self.pop("ATTENDEE", None)
766attendees_property = property(
767 _get_attendees,
768 _set_attendees,
769 _del_attendees,
770 """ATTENDEE defines one or more "Attendees" within a calendar component.
772Conformance:
773 This property MUST be specified in an iCalendar object
774 that specifies a group-scheduled calendar entity. This property
775 MUST NOT be specified in an iCalendar object when publishing the
776 calendar information (e.g., NOT in an iCalendar object that
777 specifies the publication of a calendar user's busy time, event,
778 to-do, or journal). This property is not specified in an
779 iCalendar object that specifies only a time zone definition or
780 that defines calendar components that are not group-scheduled
781 components, but are components only on a single user's calendar.
783Description:
784 This property MUST only be specified within calendar
785 components to specify participants, non-participants, and the
786 chair of a group-scheduled calendar entity. The property is
787 specified within an "EMAIL" category of the "VALARM" calendar
788 component to specify an email address that is to receive the email
789 type of iCalendar alarm.
791Examples:
792 Assign one or more attendee email addresses directly. Strings are
793 converted to :class:`~icalendar.prop.cal_address.vCalAddress` objects
794 and receive a ``mailto:`` prefix when needed.
796 .. code-block:: pycon
798 >>> from icalendar import Event
799 >>> event = Event()
800 >>> event.attendees = [
801 ... "me@my-domain.com",
802 ... "mailto:you@my-domain.com",
803 ... ]
804 >>> event.attendees[0]
805 vCalAddress('mailto:me@my-domain.com')
806 >>> event.attendees[1]
807 vCalAddress('mailto:you@my-domain.com')
808 >>> print(event.to_ical())
809 BEGIN:VEVENT
810 ATTENDEE:mailto:me@my-domain.com
811 ATTENDEE:mailto:you@my-domain.com
812 END:VEVENT
814 Use :meth:`vCalAddress.new
815 <icalendar.prop.cal_address.vCalAddress.new>` when an attendee needs
816 parameters such as ``CN``, ``ROLE``, or ``RSVP``.
818 .. code-block:: pycon
820 >>> from icalendar import vCalAddress
821 >>> event.attendees = [
822 ... vCalAddress.new(
823 ... "chair@example.com",
824 ... cn="Meeting Chair",
825 ... role="CHAIR",
826 ... rsvp=True,
827 ... )
828 ... ]
829""",
830)
832uid_property = single_string_property(
833 "UID",
834 """UID specifies the persistent, globally unique identifier for a component.
836We recommend using :func:`uuid.uuid4` to generate new values.
838Returns:
839 The value of the UID property as a string or ``""`` if no value is set.
841Description:
842 The "UID" itself MUST be a globally unique identifier.
843 The generator of the identifier MUST guarantee that the identifier
844 is unique.
846 This is the method for correlating scheduling messages with the
847 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
848 The full range of calendar components specified by a recurrence
849 set is referenced by referring to just the "UID" property value
850 corresponding to the calendar component. The "RECURRENCE-ID"
851 property allows the reference to an individual instance within the
852 recurrence set.
854 This property is an important method for group-scheduling
855 applications to match requests with later replies, modifications,
856 or deletion requests. Calendaring and scheduling applications
857 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
858 calendar components to assure interoperability with other group-
859 scheduling applications. This identifier is created by the
860 calendar system that generates an iCalendar object.
862 Implementations MUST be able to receive and persist values of at
863 least 255 octets for this property, but they MUST NOT truncate
864 values in the middle of a UTF-8 multi-octet sequence.
866 :rfc:`7986` states that UID can be used, for
867 example, to identify duplicate calendar streams that a client may
868 have been given access to. It can be used in conjunction with the
869 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
870 to identify the most recent version of a calendar.
872Conformance:
873 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
874 and "VJOURNAL" calendar components.
875 :rfc:`7986` modifies the definition of the "UID" property to
876 allow it to be defined in an iCalendar object.
877 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
878 identifier to be specified. The value of this property can then be used
879 to refer uniquely to the "VALARM" component.
881 This property can be specified once only.
883Security:
884 :rfc:`7986` states that UID values MUST NOT include any data that
885 might identify a user, host, domain, or any other security- or
886 privacy-sensitive information. It is RECOMMENDED that calendar user
887 agents now generate "UID" values that are hex-encoded random
888 Universally Unique Identifier (UUID) values as defined in
889 Sections 4.4 and 4.5 of :rfc:`4122`.
890 You can use the :mod:`uuid` module to generate new UUIDs.
892Compatibility:
893 For Alarms, ``X-ALARMUID`` is also considered.
895Examples:
896 The following is an example of such a property value:
897 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
899 Set the UID of a calendar:
901 .. code-block:: pycon
903 >>> from icalendar import Calendar
904 >>> from uuid import uuid4
905 >>> calendar = Calendar()
906 >>> calendar.uid = uuid4()
907 >>> print(calendar.to_ical())
908 BEGIN:VCALENDAR
909 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
910 END:VCALENDAR
912""",
913)
915summary_property = multi_language_text_property(
916 "SUMMARY",
917 None,
918 """SUMMARY defines a short summary or subject for the calendar component.
920Property Parameters:
921 IANA, non-standard, alternate text
922 representation, and language property parameters can be specified
923 on this property.
925Conformance:
926 The property can be specified in "VEVENT", "VTODO",
927 "VJOURNAL", or "VALARM" calendar components.
929Description:
930 This property is used in the "VEVENT", "VTODO", and
931 "VJOURNAL" calendar components to capture a short, one-line
932 summary about the activity or journal entry.
934 This property is used in the "VALARM" calendar component to
935 capture the subject of an EMAIL category of alarm.
937Examples:
938 The following is an example of this property:
940 .. code-block:: pycon
942 SUMMARY:Department Party
943""",
944)
946description_property = multi_language_text_property(
947 "DESCRIPTION",
948 None,
949 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
951Property Parameters:
952 IANA, non-standard, alternate text
953 representation, and language property parameters can be specified
954 on this property.
956Conformance:
957 The property can be specified in the "VEVENT", "VTODO",
958 "VJOURNAL", or "VALARM" calendar components. The property can be
959 specified multiple times only within a "VJOURNAL" calendar
960 component.
962Description:
963 This property is used in the "VEVENT" and "VTODO" to
964 capture lengthy textual descriptions associated with the activity.
966 This property is used in the "VALARM" calendar component to
967 capture the display text for a DISPLAY category of alarm, and to
968 capture the body text for an EMAIL category of alarm.
970Examples:
971 The following is an example of this property with formatted
972 line breaks in the property value:
974 .. code-block:: pycon
976 DESCRIPTION:Meeting to provide technical review for "Phoenix"
977 design.\\nHappy Face Conference Room. Phoenix design team
978 MUST attend this meeting.\\nRSVP to team leader.
980 """, # noqa: E501
981)
984def create_single_property(
985 prop: str,
986 value_attr: str | None,
987 value_type: tuple[type],
988 type_def: type,
989 doc: str,
990 vProp: type = vDDDTypes, # noqa: N803
991 convert: Callable[[object], object] | None = None,
992):
993 """Create a single property getter and setter.
995 Parameters:
996 prop: The name of the property.
997 value_attr: The name of the attribute to get the value from.
998 value_type: The type of the value.
999 type_def: The type of the property.
1000 doc: The docstring of the property.
1001 vProp: The type of the property from :mod:`icalendar.prop`.
1002 """
1004 def p_get(self: Component):
1005 default = object()
1006 result = self.get(prop, default)
1007 if result is default:
1008 return None
1009 if isinstance(result, list):
1010 raise InvalidCalendar(f"Multiple {prop} defined.")
1011 value = result if value_attr is None else getattr(result, value_attr, result)
1012 value = value if convert is None else convert(value)
1013 if not isinstance(value, value_type):
1014 raise InvalidCalendar(
1015 f"{prop} must be either a "
1016 f"{' or '.join(t.__name__ for t in value_type)},"
1017 f" not {value}."
1018 )
1019 return value
1021 def p_set(self: Component, value) -> None:
1022 if value is None:
1023 p_del(self)
1024 return
1025 value = convert(value) if convert is not None else value
1026 if not isinstance(value, value_type):
1027 raise TypeError(
1028 f"Use {' or '.join(t.__name__ for t in value_type)}, "
1029 f"not {type(value).__name__}."
1030 )
1031 self[prop] = vProp(value)
1032 if prop in self.exclusive:
1033 for other_prop in self.exclusive:
1034 if other_prop != prop:
1035 self.pop(other_prop, None)
1037 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
1039 def p_del(self: Component):
1040 self.pop(prop)
1042 p_doc = f"""The {prop} property.
1044 {doc}
1046 To delete the value, either use ``del`` or set it to ``None``.
1048 Raises:
1049 InvalidCalendar: if the attribute has invalid values.
1050 """
1051 return property(p_get, p_set, p_del, p_doc)
1054X_MOZ_SNOOZE_TIME_property = single_utc_property(
1055 "X-MOZ-SNOOZE-TIME",
1056 """The ``X-MOZ-SNOOZE-TIME`` property as a :class:`~datetime.datetime` in UTC.
1058 Thunderbird: Alarms before this time are snoozed.
1059""",
1060)
1061X_MOZ_LASTACK_property = single_utc_property(
1062 "X-MOZ-LASTACK",
1063 """The ``X-MOZ-LASTACK`` property as a :class:`~datetime.datetime` in UTC.
1065 Thunderbird: Alarms before this time are acknowledged.
1066""",
1067)
1070def property_get_duration(self: Component) -> timedelta | None:
1071 """Getter for property DURATION."""
1072 default = object()
1073 duration = self.get("duration", default)
1074 if duration is default:
1075 return None
1076 result = getattr(duration, "td", None)
1077 if result is None:
1078 raise InvalidCalendar(
1079 f"DURATION must be a timedelta, not {type(duration).__name__}."
1080 )
1081 return result
1084def property_set_duration(self: Component, value: timedelta | None):
1085 """Setter for property DURATION."""
1086 if value is None:
1087 self.pop("duration", None)
1088 return
1089 if not isinstance(value, timedelta):
1090 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
1091 self["duration"] = vDuration(value)
1092 self.pop("DTEND")
1093 self.pop("DUE")
1096def property_del_duration(self: Component):
1097 """Delete property DURATION."""
1098 self.pop("DURATION")
1101property_doc_duration_template = """The DURATION property.
1103The "DTSTART" property for a "{component}" specifies the inclusive
1104start of the {component}.
1105The "DURATION" property in conjunction with the DTSTART property
1106for a "{component}" calendar component specifies the non-inclusive end
1107of the event.
1109If you would like to calculate the duration of a {component}, do not use this.
1110Instead use the duration property (lower case).
1111"""
1114def duration_property(component: str) -> property:
1115 """Return the duration property."""
1116 return property(
1117 property_get_duration,
1118 property_set_duration,
1119 property_del_duration,
1120 property_doc_duration_template.format(component=component),
1121 )
1124def multi_text_property(name: str, docs: str) -> property:
1125 """Get a property that can occur several times and is text.
1127 Examples: Journal.descriptions, Event.comments
1128 """
1130 def fget(self: Component) -> list[str]:
1131 """Get the values."""
1132 descriptions = self.get(name)
1133 if descriptions is None:
1134 return []
1135 if not isinstance(descriptions, SEQUENCE_TYPES):
1136 return [descriptions]
1137 return descriptions
1139 def fset(self: Component, values: str | Sequence[str] | None):
1140 """Set the values."""
1141 fdel(self)
1142 if values is None:
1143 return
1144 if isinstance(values, str):
1145 self.add(name, values)
1146 else:
1147 for description in values:
1148 self.add(name, description)
1150 def fdel(self: Component):
1151 """Delete the values."""
1152 self.pop(name)
1154 return property(fget, fset, fdel, docs)
1157descriptions_property = multi_text_property(
1158 "DESCRIPTION",
1159 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1161Property Parameters:
1162 IANA, non-standard, alternate text
1163 representation, and language property parameters can be specified
1164 on this property.
1166Conformance:
1167 The property can be
1168 specified multiple times only within a "VJOURNAL" calendar component.
1170Description:
1171 This property is used in the "VJOURNAL" calendar component to
1172 capture one or more textual journal entries.
1174Examples:
1175 The following is an example of this property with formatted
1176 line breaks in the property value:
1178 .. code-block:: pycon
1180 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1181 design.\\nHappy Face Conference Room. Phoenix design team
1182 MUST attend this meeting.\\nRSVP to team leader.
1184""", # noqa: E501
1185)
1187comments_property = multi_text_property(
1188 "COMMENT",
1189 """Specifies a comment to the calendar user.
1191This property holds free-text notes attached to a component; calendar clients
1192display it but never act on it. It is defined in :rfc:`5545#section-3.8.1.4`
1193and may appear multiple times on ``VEVENT``, ``VTODO``, ``VJOURNAL``, and
1194``VFREEBUSY`` components as well as on their ``STANDARD`` and ``DAYLIGHT``
1195sub-components. For availability components, it is defined in :rfc:`7953`, and
1196may appear multiple times on ``VAVAILABILITY`` and ``VAVAILABLE``.
1198You can get, set, append, and delete comments on a component. The value is a
1199list of strings; each string is one comment.
1201Example:
1202 Add two comments to an event and then read them back.
1204 .. code-block:: pycon
1206 >>> from icalendar import Event
1207 >>> event = Event()
1208 >>> event.add("COMMENT", "Moved from the planning board.")
1209 >>> event.add("COMMENT", "Confirmed by phone.")
1210 >>> [str(c) for c in event.comments]
1211 ['Moved from the planning board.', 'Confirmed by phone.']
1212 >>> str(event.comments[0])
1213 'Moved from the planning board.'
1214""",
1215)
1217RECURRENCE_ID = create_single_property(
1218 "RECURRENCE-ID",
1219 "dt",
1220 (date, datetime),
1221 date | datetime,
1222 """
1223Identify a specific occurrence of a recurring calendar object.
1225This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1226particular instance in a recurrence set. The value is the original start
1227date or datetime of that instance, not the rescheduled time.
1229The value is usually a DATE-TIME and must use the same value type as the
1230``DTSTART`` property in the same component. A DATE value may be used for
1231all-day items instead.
1233This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1234may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1235""",
1236 vDDDTypes,
1237)
1240def _get_organizer(self: Component) -> vCalAddress | None:
1241 """ORGANIZER defines the organizer for a calendar component.
1243 Property Parameters:
1244 IANA, non-standard, language, common name,
1245 directory entry reference, and sent-by property parameters can be
1246 specified on this property.
1248 Conformance:
1249 This property MUST be specified in an iCalendar object
1250 that specifies a group-scheduled calendar entity. This property
1251 MUST be specified in an iCalendar object that specifies the
1252 publication of a calendar user's busy time. This property MUST
1253 NOT be specified in an iCalendar object that specifies only a time
1254 zone definition or that defines calendar components that are not
1255 group-scheduled components, but are components only on a single
1256 user's calendar.
1258 Description:
1259 This property is specified within the "VEVENT",
1260 "VTODO", and "VJOURNAL" calendar components to specify the
1261 organizer of a group-scheduled calendar entity. The property is
1262 specified within the "VFREEBUSY" calendar component to specify the
1263 calendar user requesting the free or busy time. When publishing a
1264 "VFREEBUSY" calendar component, the property is used to specify
1265 the calendar that the published busy time came from.
1267 The property has the property parameters "CN", for specifying the
1268 common or display name associated with the "Organizer", "DIR", for
1269 specifying a pointer to the directory information associated with
1270 the "Organizer", "SENT-BY", for specifying another calendar user
1271 that is acting on behalf of the "Organizer". The non-standard
1272 parameters may also be specified on this property. If the
1273 "LANGUAGE" property parameter is specified, the identified
1274 language applies to the "CN" parameter value.
1275 """
1276 return self.get("ORGANIZER")
1279def _set_organizer(self: Component, value: vCalAddress | str | None):
1280 """Set the value."""
1281 _del_organizer(self)
1282 if value is not None:
1283 self.add("ORGANIZER", value)
1286def _del_organizer(self: Component):
1287 """Delete the value."""
1288 self.pop("ORGANIZER")
1291organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1294def single_string_enum_property(
1295 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1296) -> property:
1297 """Create a property to access a single string value and convert it to an enum."""
1298 prop = single_string_property(name, docs, default=default)
1300 def fget(self: Component) -> StrEnum:
1301 """Get the value."""
1302 value = prop.fget(self)
1303 if value == default:
1304 return default
1305 return enum(str(value))
1307 def fset(self: Component, value: str | StrEnum | None) -> None:
1308 """Set the value."""
1309 if value == "":
1310 value = None
1311 prop.fset(self, value)
1313 return property(fget, fset, prop.fdel, doc=docs)
1316busy_type_property = single_string_enum_property(
1317 "BUSYTYPE",
1318 BUSYTYPE,
1319 BUSYTYPE.BUSY_UNAVAILABLE,
1320 """BUSYTYPE specifies the default busy time type.
1322Returns:
1323 :class:`icalendar.enums.BUSYTYPE`
1325Description:
1326 This property is used to specify the default busy time
1327 type. The values correspond to those used by the "FBTYPE"
1328 parameter used on a "FREEBUSY" property, with the exception that
1329 the "FREE" value is not used in this property. If not specified
1330 on a component that allows this property, the default is "BUSY-
1331 UNAVAILABLE".
1332""",
1333)
1336def _make_repeat_property() -> property:
1337 from icalendar.config import _clamp_repeat
1339 _base = single_int_property(
1340 "REPEAT",
1341 0,
1342 """The number of additional times the alarm is triggered after the
1343initial trigger.
1345Defaults to ``0``, meaning the alarm fires once. Must be paired with
1346:attr:`~icalendar.cal.alarm.Alarm.DURATION`. Conforms with :rfc:`5545#section-3.8.6.2`.
1347The value is capped at :data:`icalendar.config.MAX_ALARM_REPEAT` on read.
1349Raises:
1350 TypeError: If the value is not an ``int``. Booleans are rejected, too,
1351 even though ``bool`` subclasses ``int``.
1353 ~icalendar.error.InvalidCalendar: If the value is negative.
1355.. versionchanged:: 7.3.0
1356 Negative values are no longer accepted.
1357""",
1358 min_value=0,
1359 )
1361 def fget(self):
1362 return _clamp_repeat(_base.fget(self))
1364 return property(fget, _base.fset, _base.fdel, _base.__doc__)
1367repeat_property = _make_repeat_property()
1369priority_property = single_int_property(
1370 "PRIORITY",
1371 0,
1372 """
1374Conformance:
1375 This property can be specified in "VEVENT" and "VTODO" calendar components
1376 according to :rfc:`5545`.
1377 :rfc:`7953` adds this property to "VAVAILABILITY".
1379Description:
1380 This priority is specified as an integer in the range 0
1381 to 9. A value of 0 specifies an undefined priority. A value of 1
1382 is the highest priority. A value of 2 is the second highest
1383 priority. Subsequent numbers specify a decreasing ordinal
1384 priority. A value of 9 is the lowest priority.
1386 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1387 "LOW" is mapped into this property such that a property value in
1388 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1389 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1390 is "LOW" priority.
1392 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1393 "C3" is mapped into this property such that a property value of 1
1394 specifies "A1", a property value of 2 specifies "A2", a property
1395 value of 3 specifies "A3", and so forth up to a property value of
1396 9 specifies "C3".
1398 Other integer values are reserved for future use.
1400 Within a "VEVENT" calendar component, this property specifies a
1401 priority for the event. This property may be useful when more
1402 than one event is scheduled for a given time period.
1404 Within a "VTODO" calendar component, this property specified a
1405 priority for the to-do. This property is useful in prioritizing
1406 multiple action items for a given time period.
1408 Raises:
1409 TypeError: If the value is not an ``int``. Booleans are rejected, too,
1410 even though ``bool`` subclasses ``int``.
1412 ~icalendar.error.InvalidCalendar: If the value is negative.
1414 .. versionchanged:: 7.3.0
1415 Negative values are no longer accepted.
1416""",
1417 min_value=0,
1418)
1420class_property = single_string_enum_property(
1421 "CLASS",
1422 CLASS,
1423 CLASS.PUBLIC,
1424 """CLASS specifies the class of the calendar component.
1426Returns:
1427 :class:`icalendar.enums.CLASS`
1429Description:
1430 An access classification is only one component of the
1431 general security system within a calendar application. It
1432 provides a method of capturing the scope of the access the
1433 calendar owner intends for information within an individual
1434 calendar entry. The access classification of an individual
1435 iCalendar component is useful when measured along with the other
1436 security components of a calendar system (e.g., calendar user
1437 authentication, authorization, access rights, access role, etc.).
1438 Hence, the semantics of the individual access classifications
1439 cannot be completely defined by this memo alone. Additionally,
1440 due to the "blind" nature of most exchange processes using this
1441 memo, these access classifications cannot serve as an enforcement
1442 statement for a system receiving an iCalendar object. Rather,
1443 they provide a method for capturing the intention of the calendar
1444 owner for the access to the calendar component. If not specified
1445 in a component that allows this property, the default value is
1446 PUBLIC. Applications MUST treat x-name and iana-token values they
1447 don't recognize the same way as they would the PRIVATE value.
1448""",
1449)
1451transparency_property = single_string_enum_property(
1452 "TRANSP",
1453 TRANSP,
1454 TRANSP.OPAQUE,
1455 """TRANSP defines whether or not an event is transparent to busy time searches.
1457Returns:
1458 :class:`icalendar.enums.TRANSP`
1460Description:
1461 Time Transparency is the characteristic of an event
1462 that determines whether it appears to consume time on a calendar.
1463 Events that consume actual time for the individual or resource
1464 associated with the calendar SHOULD be recorded as OPAQUE,
1465 allowing them to be detected by free/busy time searches. Other
1466 events, which do not take up the individual's (or resource's) time
1467 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1468 busy time searches.
1469""",
1470)
1471status_property = single_string_enum_property(
1472 "STATUS",
1473 STATUS,
1474 "",
1475 """STATUS defines the overall status or confirmation for the calendar component.
1477Returns:
1478 :class:`icalendar.enums.STATUS`
1480The default value is ``""``.
1482Description:
1483 In a group-scheduled calendar component, the property
1484 is used by the "Organizer" to provide a confirmation of the event
1485 to the "Attendees". For example in a "VEVENT" calendar component,
1486 the "Organizer" can indicate that a meeting is tentative,
1487 confirmed, or cancelled. In a "VTODO" calendar component, the
1488 "Organizer" can indicate that an action item needs action, is
1489 completed, is in process or being worked on, or has been
1490 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1491 can indicate that a journal entry is draft, final, or has been
1492 cancelled or removed.
1493""",
1494)
1496url_property = single_string_property(
1497 "URL",
1498 """A Uniform Resource Locator (URL) associated with a calendar component.
1500This property specifies a URI where a more dynamic rendition of the calendar
1501information can be found. It is commonly used to reference related resources
1502or provide additional information about the component.
1504According to :rfc:`5545#section-3.8.4.6`, this property can be specified
1505once in "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1506Since :rfc:`7986#section-5.5`, this property can also be defined on a
1507"VCALENDAR". :rfc:`7953#section-3.1` allows this property in "VAVAILABILITY" components.
1509This property may be used in a calendar component to convey a location
1510where a more dynamic rendition of the calendar information can be found.
1511If both the URL property and Content-Location MIME header are specified,
1512they MUST point to the same resource.
1514This differs from the SOURCE property, which identifies where calendar
1515data can be refreshed from, whereas URL provides an alternative
1516representation of the current calendar data.
1518Examples:
1520 Set a URL for an event that references additional information:
1522 .. code-block:: pycon
1524 >>> from icalendar import Event
1525 >>> event = Event()
1526 >>> event.add('url', 'http://example.com/events/meeting-2025')
1527 >>> print(event.to_ical().decode('utf-8'))
1528 BEGIN:VEVENT
1529 URL:http://example.com/events/meeting-2025
1530 END:VEVENT
1532 Set a URL for a calendar:
1534 .. code-block:: pycon
1536 >>> from icalendar import Calendar
1537 >>> calendar = Calendar()
1538 >>> calendar.add('url', 'http://example.com/pub/calendars/jsmith/mytime.ics')
1539 >>> print(calendar.to_ical().decode('utf-8'))
1540 BEGIN:VCALENDAR
1541 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1542 END:VCALENDAR
1544See also:
1545 :attr:`~icalendar.cal.calendar.Calendar.source` for specifying from where
1546 calendar data can be refreshed.
1548 icalendar implementations:
1550 - :attr:`Availability.url <icalendar.cal.availability.Availability.url>`
1551 - :attr:`Calendar.url <icalendar.cal.calendar.Calendar.url>`
1552 - :attr:`Event.url <icalendar.cal.event.Event.url>`
1553 - :attr:`FreeBusy.url <icalendar.cal.free_busy.FreeBusy.url>`
1554 - :attr:`Journal.url <icalendar.cal.journal.Journal.url>`
1555 - :attr:`Todo.url <icalendar.cal.todo.Todo.url>`
1558""",
1559)
1561source_property = single_string_property(
1562 "SOURCE",
1563 """A URI from where calendar data can be refreshed.
1565Description:
1566 This property identifies a location where a client can
1567 retrieve updated data for the calendar. Clients SHOULD honor any
1568 specified "REFRESH-INTERVAL" value when periodically retrieving
1569 data. Note that this property differs from the "URL" property in
1570 that "URL" is meant to provide an alternative representation of
1571 the calendar data rather than the original location of the data.
1573Conformance:
1574 This property can be specified once in an iCalendar object.
1576Example:
1577 The following is an example of this property:
1579 .. code-block:: ics
1581 SOURCE;VALUE=URI:https://example.com/holidays.ics
1583""",
1584)
1586location_property = multi_language_text_property(
1587 "LOCATION",
1588 None,
1589 """The intended venue for the activity defined by a calendar component.
1591Property Parameters:
1592 IANA, non-standard, alternate text
1593 representation, and language property parameters can be specified
1594 on this property.
1596Conformance:
1597 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1598 calendar component.
1599 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1601Description:
1602 Specific venues such as conference or meeting rooms may
1603 be explicitly specified using this property. An alternate
1604 representation may be specified that is a URI that points to
1605 directory information with more structured specification of the
1606 location. For example, the alternate representation may specify
1607 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1608 CID URL :rfc:`2392` pointing to a MIME body part containing a
1609 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1611""",
1612)
1614contacts_property = multi_text_property(
1615 "CONTACT",
1616 """Contact information associated with a calendar component.
1618The contact property holds free-text information for reaching the person or
1619organization responsible in a component, such as a name, phone number, or a
1620reference to more detailed contact data. Calendar clients surface it so
1621attendees know who to contact about the event or task.
1623This property is defined in :rfc:`5545#section-3.8.4.2` and may appear on a
1624``VEVENT``, ``VTODO``, ``VJOURNAL``, or ``VFREEBUSY`` component. For
1625availability components it is defined in :rfc:`7953` and may appear on a
1626``VAVAILABILITY`` or ``VAVAILABLE`` component. An alternate representation may
1627point to a URI, for example, a vCard per :rfc:`2426`, via the ``ALTREP`` parameter.
1629You can get, set, append, and delete contacts on a component. The value is a
1630list of strings; each string is one contact entry.
1632Example:
1633 Add a contact to an event and then read it back.
1635 .. code-block:: pycon
1637 >>> from icalendar import Event
1638 >>> event = Event()
1639 >>> event.add("CONTACT", "Jim Dolittle, ABC Industries, +1-919-555-1234")
1640 >>> [str(c) for c in event.contacts]
1641 ['Jim Dolittle, ABC Industries, +1-919-555-1234']
1642 >>> str(event.contacts[0])
1643 'Jim Dolittle, ABC Industries, +1-919-555-1234'
1644""",
1645)
1648def _timezone_datetime_property(name: str, docs: str):
1649 """Create a property to access the values with a proper timezone."""
1651 return single_utc_property(name, docs)
1654rfc_7953_dtstart_property = _timezone_datetime_property(
1655 "DTSTART",
1656 """Start of the component as a :class:`~datetime.datetime` in UTC.
1658 This is almost the same as
1659 :attr:`Event.DTSTART <icalendar.cal.event.Event.DTSTART>` with one exception:
1660 The values MUST have a timezone and DATE is not allowed.
1662 Description:
1663 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1664 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1665 "DATE-TIME" values specified as either the date with UTC time or
1666 the date with local time and a time zone reference.
1668 """,
1669)
1671rfc_7953_dtend_property = _timezone_datetime_property(
1672 "DTEND",
1673 """End of the component as a :class:`~datetime.datetime` in UTC.
1675 This is almost the same as
1676 :attr:`Event.DTEND <icalendar.cal.event.Event.DTEND>` with one exception:
1677 The values MUST have a timezone and DATE is not allowed.
1679 Description:
1680 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1681 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1682 "DATE-TIME" values specified as either the date with UTC time or
1683 the date with local time and a time zone reference.
1684 """,
1685)
1688@property
1689def rfc_7953_duration_property(self) -> timedelta | None:
1690 """Compute the duration of this component.
1692 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1693 Otherwise, the duration is calculated from :attr:`DTSTART` and
1694 :attr:`DTEND`/:attr:`DURATION`.
1696 This is in accordance with :rfc:`7953`:
1697 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1698 """
1699 duration = self.DURATION
1700 if duration:
1701 return duration
1702 end = self.DTEND
1703 if end is None:
1704 return None
1705 start = self.DTSTART
1706 if start is None:
1707 raise IncompleteComponent("Cannot compute duration without start.")
1708 return end - start
1711@property
1712def rfc_7953_end_property(self) -> timedelta | None:
1713 """Compute the duration of this component.
1715 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1716 Otherwise, the duration is calculated from :attr:`DTSTART` and
1717 :attr:`DTEND`/:attr:`DURATION`.
1719 This is in accordance with :rfc:`7953`:
1720 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1721 """
1722 duration = self.DURATION
1723 if duration:
1724 start = self.DTSTART
1725 if start is None:
1726 raise IncompleteComponent("Cannot compute end without start.")
1727 return start + duration
1728 end = self.DTEND
1729 if end is None:
1730 return None
1731 return end
1734@rfc_7953_end_property.setter
1735def rfc_7953_end_property(self, value: datetime):
1736 self.DTEND = value
1739@rfc_7953_end_property.deleter
1740def rfc_7953_end_property(self):
1741 del self.DTEND
1744def get_start_end_duration_with_validation(
1745 component: Component,
1746 start_property: str,
1747 end_property: str,
1748 component_name: str,
1749) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1750 """
1751 Validate the component and return start, end, and duration.
1753 This tests validity according to :rfc:`5545` rules
1754 for ``Event`` and ``Todo`` components.
1756 Parameters:
1757 component: The component to validate, either ``Event`` or ``Todo``.
1758 start_property: The start property name, ``DTSTART``.
1759 end_property: The end property name, either ``DTEND`` for ``Event`` or
1760 ``DUE`` for ``Todo``.
1761 component_name: The component name for error messages,
1762 either ``VEVENT`` or ``VTODO``.
1764 Returns:
1765 tuple: (start, end, duration) values from the component.
1767 Raises:
1768 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1770 """
1771 start = getattr(component, start_property, None)
1772 end = getattr(component, end_property, None)
1773 duration = component.DURATION
1775 # RFC 5545: Only one of end property and DURATION may be present
1776 if duration is not None and end is not None:
1777 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1778 msg = (
1779 f"Only one of {end_name} and DURATION "
1780 f"may be in a {component_name}, not both."
1781 )
1782 raise InvalidCalendar(msg)
1784 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1785 if (
1786 start is not None
1787 and is_date(start)
1788 and duration is not None
1789 and duration.seconds != 0
1790 ):
1791 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1792 raise InvalidCalendar(msg)
1794 # RFC 5545: DTSTART and end property must be of the same type
1795 if start is not None and end is not None and is_date(start) != is_date(end):
1796 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1797 msg = (
1798 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1799 )
1800 raise InvalidCalendar(msg)
1802 return start, end, duration
1805def get_start_property(component: Component) -> date | datetime:
1806 """
1807 Get the start property with validation.
1809 Parameters:
1810 component: The component from which to get its start property.
1812 Returns:
1813 The ``DTSTART`` value.
1815 Raises:
1816 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1818 """
1819 # Trigger validation by calling _get_start_end_duration
1820 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1821 if start is None:
1822 msg = "No DTSTART given."
1823 raise IncompleteComponent(msg)
1824 return start
1827def get_end_property(component: Component, end_property: str) -> date | datetime:
1828 """
1829 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1831 Parameters:
1832 component: The component to get end from
1833 end_property: The end property name, either ``DTEND`` for ``Event`` or
1834 ``DUE`` for ``Todo``.
1836 Returns:
1837 The computed end value.
1839 Raises:
1840 ~error.IncompleteComponent: If the provided information is incomplete
1841 to compute the end property.
1843 """
1844 # Trigger validation by calling _get_start_end_duration
1845 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1847 if end is None and duration is None:
1848 if start is None:
1849 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1850 msg = f"No {end_name} or DURATION+DTSTART given."
1851 raise IncompleteComponent(msg)
1853 # Default behavior differs for Event vs Todo:
1854 # Event: date gets +1 day, datetime gets same time
1855 # Todo: both date and datetime get same time (issue #898)
1856 if end_property == "DTEND" and is_date(start):
1857 return start + timedelta(days=1)
1858 return start
1860 if duration is not None:
1861 if start is not None:
1862 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1863 return start
1864 return start + duration
1865 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1866 msg = f"No {end_name} or DURATION+DTSTART given."
1867 raise IncompleteComponent(msg)
1869 return end
1872def get_duration_property(component: Component) -> timedelta:
1873 """
1874 Get the duration property with fallback calculation from start and end.
1876 Parameters:
1877 component: The component from which to get its duration property.
1879 Returns:
1880 The duration as a timedelta.
1882 """
1883 # First check if DURATION property is explicitly set
1884 if "DURATION" in component:
1885 return component["DURATION"].dt
1887 # Fall back to calculated duration from start and end
1888 return component.end - component.start
1891def set_duration_with_locking(
1892 component: Component,
1893 duration: timedelta | None,
1894 locked: Literal["start", "end"],
1895 end_property: str,
1896) -> None:
1897 """
1898 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1900 Parameters:
1901 component: The component to modify, either ``Event`` or ``Todo``.
1902 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1903 locked: Which property to keep unchanged, either ``start`` or ``end``.
1904 end_property: The end property name, either ``DTEND`` for ``Event`` or
1905 ``DUE`` for ``Todo``.
1907 """
1908 # Convert to DURATION property if duration is None
1909 if duration is None:
1910 if "DURATION" in component:
1911 return # Already has DURATION property
1912 current_duration = component.duration
1913 component.DURATION = current_duration
1914 return
1916 if not isinstance(duration, timedelta):
1917 msg = f"Use timedelta, not {type(duration).__name__}."
1918 raise TypeError(msg)
1920 # Validate date/duration compatibility
1921 start = component.DTSTART
1922 if start is not None and is_date(start) and duration.seconds != 0:
1923 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1924 raise InvalidCalendar(msg)
1926 if locked == "start":
1927 # Keep start locked, adjust end
1928 if start is None:
1929 msg = "Cannot set duration without DTSTART. Set start time first."
1930 raise IncompleteComponent(msg)
1931 component.pop(end_property, None) # Remove end property
1932 component.DURATION = duration
1933 elif locked == "end":
1934 # Keep end locked, adjust start
1935 current_end = component.end
1936 component.DTSTART = current_end - duration
1937 component.pop(end_property, None) # Remove end property
1938 component.DURATION = duration
1939 else:
1940 msg = f"locked must be 'start' or 'end', not {locked!r}"
1941 raise ValueError(msg)
1944def set_start_with_locking(
1945 component: Component,
1946 start: date | datetime,
1947 locked: Literal["duration", "end"] | None,
1948 end_property: str,
1949) -> None:
1950 """
1951 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1953 Parameters:
1954 component: The component to modify, either ``Event`` or ``Todo``.
1955 start: The start time to set.
1956 locked: Which property to keep unchanged, either ``duration``, ``end``,
1957 or ``None`` for auto-detect.
1958 end_property: The end property name, either ``DTEND`` for ``Event`` or
1959 ``DUE`` for ``Todo``.
1961 """
1962 if locked is None:
1963 # Auto-detect based on existing properties
1964 if "DURATION" in component:
1965 locked = "duration"
1966 elif end_property in component:
1967 locked = "end"
1968 else:
1969 # Default to duration if no existing properties
1970 locked = "duration"
1972 if locked == "duration":
1973 # Keep duration locked, adjust end
1974 current_duration = (
1975 component.duration
1976 if "DURATION" in component or end_property in component
1977 else None
1978 )
1979 component.DTSTART = start
1980 if current_duration is not None:
1981 component.pop(end_property, None) # Remove end property
1982 component.DURATION = current_duration
1983 elif locked == "end":
1984 # Keep end locked, adjust duration
1985 current_end = component.end
1986 component.DTSTART = start
1987 component.pop("DURATION", None) # Remove duration property
1988 setattr(component, end_property, current_end)
1989 else:
1990 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1991 raise ValueError(msg)
1994def set_end_with_locking(
1995 component: Component,
1996 end: date | datetime,
1997 locked: Literal["start", "duration"],
1998 end_property: str,
1999) -> None:
2000 """
2001 Set the end with explicit locking behavior for Event and Todo components.
2003 Parameters:
2004 component: The component to modify, either ``Event`` or ``Todo``.
2005 end: The end time to set.
2006 locked: Which property to keep unchanged, either ``start`` or ``duration``.
2007 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
2008 for ``Todo``.
2010 """
2011 if locked == "start":
2012 # Keep start locked, adjust duration
2013 component.pop("DURATION", None) # Remove duration property
2014 setattr(component, end_property, end)
2015 elif locked == "duration":
2016 # Keep duration locked, adjust start
2017 current_duration = component.duration
2018 component.DTSTART = end - current_duration
2019 component.pop(end_property, None) # Remove end property
2020 component.DURATION = current_duration
2021 else:
2022 msg = f"locked must be 'start' or 'duration', not {locked!r}"
2023 raise ValueError(msg)
2026def _get_images(self: Component) -> list[Image]:
2027 """IMAGE specifies an image associated with the calendar or a calendar component.
2029 Description:
2030 This property specifies an image for an iCalendar
2031 object or a calendar component via a URI or directly with inline
2032 data that can be used by calendar user agents when presenting the
2033 calendar data to a user. Multiple properties MAY be used to
2034 specify alternative sets of images with, for example, varying
2035 media subtypes, resolutions, or sizes. When multiple properties
2036 are present, calendar user agents SHOULD display only one of them,
2037 picking one that provides the most appropriate image quality, or
2038 display none. The "DISPLAY" parameter is used to indicate the
2039 intended display mode for the image. The "ALTREP" parameter,
2040 defined in :rfc:`5545`, can be used to provide a "clickable" image
2041 where the URI in the parameter value can be "launched" by a click
2042 on the image in the calendar user agent.
2044 Conformance:
2045 This property can be specified multiple times in an
2046 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
2047 components.
2049 .. note::
2051 At the present moment, this property is read-only. If you require a setter,
2052 please open an issue or a pull request.
2053 """
2054 images = self.get("IMAGE", [])
2055 if not isinstance(images, SEQUENCE_TYPES):
2056 images = [images]
2057 return [Image.from_property_value(img) for img in images]
2060images_property = property(_get_images)
2063def _get_conferences(self: Component) -> list[Conference]:
2064 """Return the CONFERENCE properties as a list.
2066 Purpose:
2067 This property specifies information for accessing a conferencing system.
2069 Conformance:
2070 This property can be specified multiple times in a
2071 "VEVENT" or "VTODO" calendar component.
2073 Description:
2074 This property specifies information for accessing a
2075 conferencing system for attendees of a meeting or task. This
2076 might be for a telephone-based conference number dial-in with
2077 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
2078 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
2079 or https: URI :rfc:`7230`), or for an instant messaging group chat
2080 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
2081 conferencing system is not available, a data: URI :rfc:`2397`
2082 containing a text description can be used.
2084 A conference system can be a bidirectional communication channel
2085 or a uni-directional "broadcast feed".
2087 The "FEATURE" property parameter is used to describe the key
2088 capabilities of the conference system to allow a client to choose
2089 the ones that give the required level of interaction from a set of
2090 multiple properties.
2092 The "LABEL" property parameter is used to convey additional
2093 details on the use of the URI. For example, the URIs or access
2094 codes for the moderator and attendee of a teleconference system
2095 could be different, and the "LABEL" property parameter could be
2096 used to "tag" each "CONFERENCE" property to indicate which is
2097 which.
2099 The "LANGUAGE" property parameter can be used to specify the
2100 language used for text values used with this property (as per
2101 Section 3.2.10 of :rfc:`5545`).
2103 Example:
2104 The following are examples of this property:
2106 .. code-block:: ics
2108 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
2109 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
2110 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2111 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
2112 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2113 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
2114 CONFERENCE;VALUE=URI;FEATURE=CHAT;
2115 LABEL=Chat room:xmpp:chat-123@conference.example.com
2116 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
2117 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
2119 Get all conferences:
2121 .. code-block:: pycon
2123 >>> from icalendar import Event
2124 >>> event = Event()
2125 >>> event.conferences
2126 []
2128 Set a conference:
2130 .. code-block:: pycon
2132 >>> from icalendar import Event, Conference
2133 >>> event = Event()
2134 >>> event.conferences = [
2135 ... Conference(
2136 ... "tel:+1-412-555-0123,,,654321",
2137 ... feature="PHONE,MODERATOR",
2138 ... label="Moderator dial-in",
2139 ... language="EN",
2140 ... )
2141 ... ]
2142 >>> print(event.to_ical())
2143 BEGIN:VEVENT
2144 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
2145 ALUE=URI:tel:+1-412-555-0123,,,654321
2146 END:VEVENT
2148 """
2149 conferences = self.get("CONFERENCE", [])
2150 if not isinstance(conferences, SEQUENCE_TYPES):
2151 conferences = [conferences]
2152 return [Conference.from_uri(conference) for conference in conferences]
2155def _set_conferences(self: Component, conferences: list[Conference] | None):
2156 """Set the conferences."""
2157 _del_conferences(self)
2158 for conference in conferences or []:
2159 self.add("CONFERENCE", conference.to_uri())
2162def _del_conferences(self: Component):
2163 """Delete all conferences."""
2164 self.pop("CONFERENCE")
2167conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2170def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2171 """LINK properties as a list.
2173 Purpose:
2174 LINK provides a reference to external information related to a component.
2176 Property Parameters:
2177 The VALUE parameter is required.
2178 Non-standard, link relation type, format type, label, and language parameters
2179 can also be specified on this property.
2180 The LABEL parameter is defined in :rfc:`7986`.
2182 Conformance:
2183 This property can be specified zero or more times in any iCalendar component.
2184 LINK is specified in :rfc:`9253`.
2185 The LINKREL parameter is required.
2187 Description:
2188 When used in a component, the value of this property points to
2189 additional information related to the component.
2190 For example, it may reference the originating web server.
2192 This property is a serialization of the model in :rfc:`8288`,
2193 where the link target is carried in the property value,
2194 the link context is the containing calendar entity,
2195 and the link relation type and any target attributes
2196 are carried in iCalendar property parameters.
2198 The LINK property parameters map to :rfc:`8288` attributes as follows:
2200 LABEL
2201 This parameter maps to the "title"
2202 attribute defined in Section 3.4.1 of :rfc:`8288`.
2203 LABEL is used to label the destination
2204 of a link such that it can be used as a human-readable identifier
2205 (e.g., a menu entry) in the language indicated by the LANGUAGE
2206 (if present).
2207 LANGUAGE
2208 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2209 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2210 LINKREL
2211 This parameter maps to the link relation type defined in Section 2.1 of
2212 :rfc:`8288`. See `Registered Link Relation Types
2213 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2214 FMTTYPE
2215 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2216 :rfc:`8288`.
2218 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2220 Examples:
2221 The following is an example of this property,
2222 which provides a reference to the source for the calendar object.
2224 .. code-block:: ics
2226 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2227 https://example.com/events
2229 The following is an example of this property,
2230 which provides a reference to an entity from which this one was derived.
2231 The link relation is a vendor-defined value.
2233 .. code-block:: ics
2235 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2236 VALUE=URI:
2237 https://example.com/tasks/01234567-abcd1234.ics
2239 The following is an example of this property,
2240 which provides a reference to a fragment of an XML document.
2241 The link relation is a vendor-defined value.
2243 .. code-block:: ics
2245 LINK;LINKREL="https://example.com/linkrel/costStructure";
2246 VALUE=XML-REFERENCE:
2247 https://example.com/xmlDocs/bidFramework.xml
2248 #xpointer(descendant::CostStruc/range-to(
2249 following::CostStrucEND[1]))
2251 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2253 .. code-block:: pycon
2255 >>> from icalendar import Event, vUri
2256 >>> from datetime import datetime
2257 >>> link = vUri(
2258 ... "http://example.com/event-page",
2259 ... params={"LINKREL":"SOURCE"}
2260 ... )
2261 >>> event = Event.new(
2262 ... start=datetime(2025, 9, 17, 12, 0),
2263 ... summary="An Example Event with a page"
2264 ... )
2265 >>> event.links = [link]
2266 >>> print(event.to_ical())
2267 BEGIN:VEVENT
2268 SUMMARY:An Example Event with a page
2269 DTSTART:20250917T120000
2270 DTSTAMP:20250517T080612Z
2271 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2272 LINK;LINKREL="SOURCE":http://example.com/event-page
2273 END:VEVENT
2275 """
2276 links = self.get("LINK", [])
2277 if not isinstance(links, list):
2278 links = [links]
2279 return links
2282LINKS_TYPE_SETTER: TypeAlias = (
2283 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2284)
2287def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2288 """Set the LINKs."""
2289 _del_links(self)
2290 if links is None:
2291 return
2292 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2293 links = [links]
2294 for link in links:
2295 if type(link) is str:
2296 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2297 self.add("LINK", link)
2300def _del_links(self: Component) -> None:
2301 """Delete all links."""
2302 self.pop("LINK")
2305links_property = property(_get_links, _set_links, _del_links)
2307RELATED_TO_TYPE_SETTER: TypeAlias = (
2308 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2309)
2312def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2313 """RELATED-TO properties as a list.
2315 Purpose:
2316 This property is used to represent a relationship or reference
2317 between one calendar component and another.
2318 :rfc:`9523` allows URI or UID values and a GAP parameter.
2320 Value Type:
2321 :rfc:`5545`: TEXT
2322 :rfc:`9253`: URI, UID
2324 Conformance:
2325 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2326 "VTODO", and "VJOURNAL" calendar components.
2327 Since :rfc:`9523`, this property MAY be specified in any
2328 iCalendar component.
2330 Description (:rfc:`5545`):
2331 The property value consists of the persistent, globally
2332 unique identifier of another calendar component. This value would
2333 be represented in a calendar component by the "UID" property.
2335 By default, the property value points to another calendar
2336 component that has a PARENT relationship to the referencing
2337 object. The "RELTYPE" property parameter is used to either
2338 explicitly state the default PARENT relationship type to the
2339 referenced calendar component or to override the default PARENT
2340 relationship type and specify either a CHILD or SIBLING
2341 relationship. The PARENT relationship indicates that the calendar
2342 component is a subordinate of the referenced calendar component.
2343 The CHILD relationship indicates that the calendar component is a
2344 superior of the referenced calendar component. The SIBLING
2345 relationship indicates that the calendar component is a peer of
2346 the referenced calendar component.
2348 Changes to a calendar component referenced by this property can
2349 have an implicit impact on the related calendar component. For
2350 example, if a group event changes its start or end date or time,
2351 then the related, dependent events will need to have their start
2352 and end dates changed in a corresponding way. Similarly, if a
2353 PARENT calendar component is cancelled or deleted, then there is
2354 an implied impact to the related CHILD calendar components. This
2355 property is intended only to provide information on the
2356 relationship of calendar components. It is up to the target
2357 calendar system to maintain any property implications of this
2358 relationship.
2360 Description (:rfc:`9253`):
2361 By default or when VALUE=UID is specified, the property value
2362 consists of the persistent, globally unique identifier of another
2363 calendar component. This value would be represented in a calendar
2364 component by the UID property.
2366 By default, the property value
2367 points to another calendar component that has a PARENT relationship
2368 to the referencing object. The RELTYPE property parameter is used
2369 to either explicitly state the default PARENT relationship type to
2370 the referenced calendar component or to override the default
2371 PARENT relationship type and specify either a CHILD or SIBLING
2372 relationship or a temporal relationship.
2374 The PARENT relationship
2375 indicates that the calendar component is a subordinate of the
2376 referenced calendar component. The CHILD relationship indicates
2377 that the calendar component is a superior of the referenced calendar
2378 component. The SIBLING relationship indicates that the calendar
2379 component is a peer of the referenced calendar component.
2381 To preserve backwards compatibility, the value type MUST
2382 be UID when the PARENT, SIBLING, or CHILD relationships
2383 are specified.
2385 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2386 or STARTTOSTART relationships define temporal relationships, as
2387 specified in the RELTYPE parameter definition.
2389 The FIRST and NEXT
2390 define ordering relationships between calendar components.
2392 The DEPENDS-ON relationship indicates that the current calendar
2393 component depends on the referenced calendar component in some manner.
2394 For example, a task may be blocked waiting on the other,
2395 referenced, task.
2397 The REFID and CONCEPT relationships establish
2398 a reference from the current component to the referenced component.
2399 Changes to a calendar component referenced by this property
2400 can have an implicit impact on the related calendar component.
2401 For example, if a group event changes its start or end date or
2402 time, then the related, dependent events will need to have their
2403 start and end dates and times changed in a corresponding way.
2404 Similarly, if a PARENT calendar component is canceled or deleted,
2405 then there is an implied impact to the related CHILD calendar
2406 components. This property is intended only to provide information
2407 on the relationship of calendar components.
2409 Deletion of the target component, for example, the target of a
2410 FIRST, NEXT, or temporal relationship, can result in broken links.
2412 It is up to the target calendar system to maintain any property
2413 implications of these relationships.
2415 Examples:
2416 :rfc:`5545` examples of this property:
2418 .. code-block:: ics
2420 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2422 .. code-block:: ics
2424 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2426 :rfc:`9253` examples of this property:
2428 .. code-block:: ics
2430 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2431 https://example.com/caldav/user/jb/cal/
2432 19960401-080045-4000F192713.ics
2434 See also :class:`icalendar.enums.RELTYPE`.
2436 """
2437 result = self.get("RELATED-TO", [])
2438 if not isinstance(result, list):
2439 return [result]
2440 return result
2443def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2444 """Set the RELATED-TO properties."""
2445 _del_related_to(self)
2446 if values is None:
2447 return
2448 if not isinstance(values, list):
2449 values = [values]
2450 for value in values:
2451 self.add("RELATED-TO", value)
2454def _del_related_to(self: Component):
2455 """Delete the RELATED-TO properties."""
2456 self.pop("RELATED-TO", None)
2459related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2462def _get_concepts(self: Component) -> list[vUri]:
2463 """CONCEPT
2465 Purpose:
2466 CONCEPT defines the formal categories for a calendar component.
2468 Conformance:
2469 Since :rfc:`9253`,
2470 this property can be specified zero or more times in any iCalendar component.
2472 Description:
2473 This property is used to specify formal categories or classifications of
2474 the calendar component. The values are useful in searching for a calendar
2475 component of a particular type and category.
2477 This categorization is distinct from the more informal "tagging" of components
2478 provided by the existing CATEGORIES property. It is expected that the value of
2479 the CONCEPT property will reference an external resource that provides
2480 information about the categorization.
2482 In addition, a structured URI value allows for hierarchical categorization of
2483 events.
2485 Possible category resources are the various proprietary systems, for example,
2486 the Library of Congress, or an open source of categorization data.
2488 Examples:
2489 The following is an example of this property.
2490 It points to a server acting as the source for the calendar object.
2492 .. code-block:: ics
2494 CONCEPT:https://example.com/event-types/arts/music
2496 .. seealso::
2498 :attr:`icalendar.prop.categories.vCategory`
2499 """
2500 concepts = self.get("CONCEPT", [])
2501 if not isinstance(concepts, list):
2502 concepts = [concepts]
2503 return concepts
2506CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2509def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2510 """Set the concepts."""
2511 _del_concepts(self)
2512 if concepts is None:
2513 return
2514 if not isinstance(concepts, list):
2515 concepts = [concepts]
2516 for value in concepts:
2517 self.add("CONCEPT", value)
2520def _del_concepts(self: Component):
2521 """Delete the concepts."""
2522 self.pop("CONCEPT", None)
2525concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2528def multi_string_property(name: str, doc: str):
2529 """A property for an iCalendar Property that can occur multiple times."""
2531 def fget(self: Component) -> list[str]:
2532 """Get the values of a multi-string property."""
2533 value = self.get(name, [])
2534 if not isinstance(value, list):
2535 value = [value]
2536 return value
2538 def fset(self: Component, value: list[str] | str | None) -> None:
2539 """Set the values of a multi-string property."""
2540 fdel(self)
2541 if value is None:
2542 return
2543 if not isinstance(value, list):
2544 value = [value]
2545 for value in value:
2546 self.add(name, value)
2548 def fdel(self: Component):
2549 """Delete the values of a multi-string property."""
2550 self.pop(name, None)
2552 return property(fget, fset, fdel, doc=doc)
2555refids_property = multi_string_property(
2556 "REFID",
2557 """REFID
2559Purpose:
2560 REFID acts as a key for associated iCalendar entities.
2562Conformance:
2563 Since :rfc:`9253`,
2564 this property can be specified zero or more times in any iCalendar component.
2566Description:
2567 The value of this property is free-form text that creates an
2568 identifier for associated components.
2569 All components that use the same REFID value are associated through
2570 that value and can be located or retrieved as a group.
2571 For example, all of the events in a travel itinerary
2572 would have the same REFID value, so as to be grouped together.
2574Examples:
2575 The following is an example of this property.
2577 .. code-block:: ics
2579 REFID:itinerary-2014-11-17
2581 Use a REFID to associate several VTODOs:
2583 .. code-block:: pycon
2585 >>> from icalendar import Todo
2586 >>> todo_1 = Todo.new(
2587 ... summary="turn off stove",
2588 ... refids=["travel", "alps"]
2589 ... )
2590 >>> todo_2 = Todo.new(
2591 ... summary="pack backpack",
2592 ... refids=["travel", "alps"]
2593 ... )
2594 >>> todo_1.refids == todo_2.refids
2595 True
2597.. note::
2599 When you assign a list to this property, the returned list
2600 is the same object stored in the component. Modifying it (``append()``,
2601 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2602 the component stores.
2604 However, if you assign a single string value to this property, the returned
2605 list is a temporary copy, and changes to this list don't affect the component.
2606""",
2607)
2609REQUEST_STATUS_property = multi_string_property(
2610 "REQUEST-STATUS",
2611 """This property defines the status code returned for a scheduling request.
2613You can assign a single string, a list of strings, or ``None`` to this
2614property. The property stores a :class:`str` as-is. Assigning ``None`` or
2615an empty list removes all REQUEST-STATUS values, as does deleting the
2616property.
2618The value consists of a short return status code component, a longer
2619return status description component, and optionally a status-specific
2620data component, separated by semicolons (statcode;statdesc[;extdata]).
2621The return status components are defined in :rfc:`5545#section-3.8.8.3`.
2623The REQUEST-STATUS property can be specified in the following
2624icalendar components as ``REQUEST_STATUS``.
2626- :attr:`Event.REQUEST_STATUS <icalendar.cal.event.Event.REQUEST_STATUS>`
2627- :attr:`FreeBusy.REQUEST_STATUS <icalendar.cal.free_busy.FreeBusy.REQUEST_STATUS>`
2628- :attr:`Journal.REQUEST_STATUS <icalendar.cal.journal.Journal.REQUEST_STATUS>`
2629- :attr:`Todo.REQUEST_STATUS <icalendar.cal.todo.Todo.REQUEST_STATUS>`
2631Note:
2632 When you assign a list to this property, the returned list
2633 is the same object stored in the component. Modifying it (``append()``,
2634 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2635 the component stores.
2637 However, if you assign a single string value to this property, the returned
2638 list is a temporary copy, and changes to this list don't affect the component.
2640Parameters:
2641 request_status(str | list[str] | None):
2642 Either a single status string, a list of status strings, or
2643 ``None`` to set the component's status code returned for a
2644 scheduling request.
2646Example:
2647 Add a request status to an event:
2649 .. code-block:: pycon
2651 >>> from icalendar import Event
2652 >>> event = Event.new(request_status="2.0;Success")
2653 >>> event.REQUEST_STATUS == ["2.0;Success"]
2654 True
2655""",
2656)
2658RESOURCES_property = multi_string_property(
2659 "RESOURCES",
2660 """This property defines resources for a calendar component.
2662You can assign a single string, a list of strings, or ``None`` to this
2663property. The property stores a :class:`str` as-is. Assigning ``None`` or
2664an empty list removes all RESOURCES values, as does deleting the property.
2666The value is a comma-separated list of resources, such as equipment,
2667facilities, or other things that the component needs. Each item of the
2668list becomes its own RESOURCES property value. See
2669:rfc:`5545#section-3.8.1.10` for the specification.
2671The RESOURCES property can be specified in the following
2672icalendar components as ``resources`` using the component's
2673``new()`` constructor.
2675- :attr:`Event.RESOURCES <icalendar.cal.event.Event.RESOURCES>`
2676- :attr:`Todo.RESOURCES <icalendar.cal.todo.Todo.RESOURCES>`
2678Parameters:
2679 resources(str | list[str] | None):
2680 Either a single resource string, a list of resource strings,
2681 or ``None`` to set the component's resources.
2683Note:
2684 When you assign a list to this property, the returned list
2685 is the same object stored in the component. Modifying it (``append()``,
2686 ``extend()``, ``remove()``, item assignment, or ``del``) changes what
2687 the component stores.
2689 However, if you assign a single string value to this property, the returned
2690 list is a temporary copy, and changes to this list don't affect the component.
2692Example:
2693 Add resources to an event:
2695 .. code-block:: pycon
2697 >>> from icalendar import Event
2698 >>> event = Event.new(resources=["EASEL", "PROJECTOR", "VCR"])
2699 >>> event.RESOURCES == ["EASEL", "PROJECTOR", "VCR"]
2700 True
2701""",
2702)
2705ATTACHMENTS_TYPE_SETTER: TypeAlias = (
2706 str | bytes | vUri | vBinary | None | list[str | bytes | vUri | vBinary]
2707)
2710def _normalize_attachment(value: str | bytes | vUri | vBinary) -> vUri | vBinary:
2711 """Convert one attachment value."""
2712 if isinstance(value, (vUri, vBinary)):
2713 return value
2714 if isinstance(value, str):
2715 return vUri(value)
2716 if isinstance(value, bytes):
2717 return vBinary(value)
2718 raise TypeError(
2719 f"Attachments must be str, bytes, vUri, or vBinary, not {type(value).__name__}."
2720 )
2723def _get_attachments(self: Component) -> list[vUri | vBinary]:
2724 """Get all the attachments"""
2725 attachments = self.get("ATTACH", [])
2726 if not isinstance(attachments, SEQUENCE_TYPES):
2727 return [attachments]
2728 return list(attachments)
2731def _set_attachments(self: Component, value: ATTACHMENTS_TYPE_SETTER) -> None:
2732 """Set attachments properties"""
2733 if value is None:
2734 _del_attachments(self)
2735 return
2736 if not isinstance(value, list):
2737 value = [value]
2738 attachments = [_normalize_attachment(attachment) for attachment in value]
2739 _del_attachments(self)
2740 for attachment in attachments:
2741 self.add("ATTACH", attachment)
2744def _del_attachments(self: Component) -> None:
2745 """Delete all attachments"""
2746 self.pop("ATTACH", None)
2749attachments_property = property(
2750 _get_attachments,
2751 _set_attachments,
2752 _del_attachments,
2753 """This property defines the attachments for a component.
2755Setting this property replaces all existing attachments. A :class:`str`
2756is converted to :class:`~icalendar.prop.uri.vUri`, and :class:`bytes` is
2757converted to :class:`~icalendar.prop.binary.vBinary`. Values that are
2758already :class:`~icalendar.prop.uri.vUri` or
2759:class:`~icalendar.prop.binary.vBinary` are stored unchanged, so their
2760parameters are preserved. Setting ``None`` or an empty list removes all
2761attachments, as does deleting the property.
2763Parameters:
2764 attachments(str | bytes | vUri | vBinary | list | None):
2765 A single attachment, or a list of attachments to set. Accepts
2766 :class:`str`, :class:`bytes`, :class:`~icalendar.prop.uri.vUri`,
2767 and :class:`~icalendar.prop.binary.vBinary`, individually or
2768 mixed together in a list.
2770Example:
2771 Attach a URI to an event, then replace it with a URI and inline
2772 binary data together:
2774 .. code-block:: pycon
2776 >>> from icalendar import Event, vUri, vBinary
2777 >>> event = Event()
2778 >>> event.attachments
2779 []
2780 >>> event.attachments = ["https://example.com/agenda.pdf"]
2781 >>> print(event.to_ical().decode())
2782 BEGIN:VEVENT
2783 ATTACH:https://example.com/agenda.pdf
2784 END:VEVENT
2785 >>> event.attachments = [
2786 ... vUri(
2787 ... "https://example.com/agenda.pdf",
2788 ... params={"FMTTYPE": "application/pdf"},
2789 ... ),
2790 ... vBinary(b"image-data", params={"FMTTYPE": "image/png"},),
2791 ... ]
2792 >>> len(event.attachments)
2793 2
2795.. note::
2797 An alarm as an audio action must not contain more than one attachment.
2799 List modifications do not modify the component. Methods such as
2800 ``append()``, ``extend()``, and ``remove()``, as well as item
2801 assignment, act on a copy. Assign the list back to the property, or
2802 use :meth:`Component.add <icalendar.cal.component.Component.add>`
2803 with a typed value instead.
2805.. seealso::
2807 :rfc:`5545#section-3.8.1.1` for the definition of the ``ATTACH``
2808 property.
2809""",
2810)
2813__all__ = [
2814 "ATTACHMENTS_TYPE_SETTER",
2815 "ATTENDEE_TYPE_SETTER",
2816 "CONCEPTS_TYPE_SETTER",
2817 "LINKS_TYPE_SETTER",
2818 "RECURRENCE_ID",
2819 "RELATED_TO_TYPE_SETTER",
2820 "REQUEST_STATUS_property",
2821 "RESOURCES_property",
2822 "attachments_property",
2823 "attendees_property",
2824 "busy_type_property",
2825 "categories_property",
2826 "class_property",
2827 "color_property",
2828 "comments_property",
2829 "concepts_property",
2830 "conferences_property",
2831 "contacts_property",
2832 "create_single_property",
2833 "description_property",
2834 "descriptions_property",
2835 "duration_property",
2836 "exdates_property",
2837 "get_duration_property",
2838 "get_end_property",
2839 "get_start_end_duration_with_validation",
2840 "get_start_property",
2841 "images_property",
2842 "links_property",
2843 "location_property",
2844 "multi_language_text_property",
2845 "multi_string_property",
2846 "organizer_property",
2847 "priority_property",
2848 "property_del_duration",
2849 "property_doc_duration_template",
2850 "property_get_duration",
2851 "property_set_duration",
2852 "rdates_property",
2853 "refids_property",
2854 "related_to_property",
2855 "repeat_property",
2856 "rfc_7953_dtend_property",
2857 "rfc_7953_dtstart_property",
2858 "rfc_7953_duration_property",
2859 "rfc_7953_end_property",
2860 "rrules_property",
2861 "sequence_property",
2862 "set_duration_with_locking",
2863 "set_end_with_locking",
2864 "set_start_with_locking",
2865 "single_int_property",
2866 "single_utc_property",
2867 "source_property",
2868 "status_property",
2869 "summary_property",
2870 "transparency_property",
2871 "uid_property",
2872 "url_property",
2873]