Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 29%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Attributes of Components and properties."""
3from __future__ import annotations
5import itertools
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING, Literal, TypeAlias
9from icalendar.enums import BUSYTYPE, CLASS, STATUS, TRANSP, StrEnum
10from icalendar.error import IncompleteComponent, InvalidCalendar
11from icalendar.parser_tools import SEQUENCE_TYPES
12from icalendar.prop import (
13 vCalAddress,
14 vCategory,
15 vDDDTypes,
16 vDuration,
17 vRecur,
18 vText,
19 vUid,
20 vUnknown,
21 vUri,
22 vXmlReference,
23)
24from icalendar.prop.conference import Conference
25from icalendar.prop.image import Image
26from icalendar.timezone import tzp
27from icalendar.tools import is_date
29if TYPE_CHECKING:
30 from collections.abc import Callable, Sequence
32 from icalendar.cal import Component
35def _get_rdates(
36 self: Component,
37) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]:
38 """The RDATE property defines the list of DATE-TIME values for recurring components.
40 RDATE is defined in :rfc:`5545`.
41 The return value is a list of tuples ``(start, end)``.
43 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
44 with and without timezone.
46 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
47 if the end is specified.
49 Value Type:
50 The default value type for this property is DATE-TIME.
51 The value type can be set to DATE or PERIOD.
53 Property Parameters:
54 IANA, non-standard, value data type, and time
55 zone identifier property parameters can be specified on this
56 property.
58 Conformance:
59 This property can be specified in recurring "VEVENT",
60 "VTODO", and "VJOURNAL" calendar components as well as in the
61 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
62 calendar component.
64 Description:
65 This property can appear along with the "RRULE"
66 property to define an aggregate set of repeating occurrences.
67 When they both appear in a recurring component, the recurrence
68 instances are defined by the union of occurrences defined by both
69 the "RDATE" and "RRULE".
71 The recurrence dates, if specified, are used in computing the
72 recurrence set. The recurrence set is the complete set of
73 recurrence instances for a calendar component. The recurrence set
74 is generated by considering the initial "DTSTART" property along
75 with the "RRULE", "RDATE", and "EXDATE" properties contained
76 within the recurring component. The "DTSTART" property defines
77 the first instance in the recurrence set. The "DTSTART" property
78 value SHOULD match the pattern of the recurrence rule, if
79 specified. The recurrence set generated with a "DTSTART" property
80 value that doesn't match the pattern of the rule is undefined.
81 The final recurrence set is generated by gathering all of the
82 start DATE-TIME values generated by any of the specified "RRULE"
83 and "RDATE" properties, and then excluding any start DATE-TIME
84 values specified by "EXDATE" properties. This implies that start
85 DATE-TIME values specified by "EXDATE" properties take precedence
86 over those specified by inclusion properties (i.e., "RDATE" and
87 "RRULE"). Where duplicate instances are generated by the "RRULE"
88 and "RDATE" properties, only one recurrence is considered.
89 Duplicate instances are ignored.
91 Example:
92 Below, we set one RDATE in a list and get the resulting tuple of start and end.
94 .. code-block:: pycon
96 >>> from icalendar import Event
97 >>> from datetime import datetime
98 >>> event = Event()
100 # Add a list of recurrence dates
101 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
102 >>> event.rdates
103 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
105 .. note::
107 Modifying the returned list does not change the RDATE value. Assign to
108 :attr:`rdates` or use :func:`icalendar.cal.Component.add` instead.
110 If you want to compute recurrences, have a look at
111 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
113 """
114 result = []
115 rdates = self.get("RDATE", [])
116 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
117 for dts in rdates.dts:
118 rdate = dts.dt
119 if isinstance(rdate, tuple):
120 # we have a period as rdate
121 if isinstance(rdate[1], timedelta):
122 result.append((rdate[0], rdate[0] + rdate[1]))
123 else:
124 result.append(rdate)
125 else:
126 # we have a date/datetime
127 result.append((rdate, None))
128 return result
131def _set_rdates(self: Component, value) -> None:
132 """Set the RDATE values, replacing any existing ones.
134 ``value`` is a list as returned by :attr:`rdates` (each item a date, a
135 datetime, or a ``(start, end)`` period tuple). Setting an empty list or
136 :obj:`None` removes the RDATE property.
137 """
138 _del_rdates(self)
139 if value:
140 self.add("RDATE", value)
143def _del_rdates(self: Component) -> None:
144 """Delete all RDATE values."""
145 self.pop("RDATE", None)
148rdates_property = property(_get_rdates, _set_rdates, _del_rdates)
151def _get_exdates(self: Component) -> list[date | datetime]:
152 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
154 EXDATE is defined in :rfc:`5545`.
156 Value Type:
157 The default value type for this property is DATE-TIME.
158 The value type can be set to DATE.
160 Property Parameters:
161 IANA, non-standard, value data type, and time
162 zone identifier property parameters can be specified on this
163 property.
165 Conformance:
166 This property can be specified in recurring "VEVENT",
167 "VTODO", and "VJOURNAL" calendar components as well as in the
168 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
169 calendar component.
171 Description:
172 The exception dates, if specified, are used in
173 computing the recurrence set. The recurrence set is the complete
174 set of recurrence instances for a calendar component. The
175 recurrence set is generated by considering the initial "DTSTART"
176 property along with the "RRULE", "RDATE", and "EXDATE" properties
177 contained within the recurring component. The "DTSTART" property
178 defines the first instance in the recurrence set. The "DTSTART"
179 property value SHOULD match the pattern of the recurrence rule, if
180 specified. The recurrence set generated with a "DTSTART" property
181 value that doesn't match the pattern of the rule is undefined.
182 The final recurrence set is generated by gathering all of the
183 start DATE-TIME values generated by any of the specified "RRULE"
184 and "RDATE" properties, and then excluding any start DATE-TIME
185 values specified by "EXDATE" properties. This implies that start
186 DATE-TIME values specified by "EXDATE" properties take precedence
187 over those specified by inclusion properties (i.e., "RDATE" and
188 "RRULE"). When duplicate instances are generated by the "RRULE"
189 and "RDATE" properties, only one recurrence is considered.
190 Duplicate instances are ignored.
192 The "EXDATE" property can be used to exclude the value specified
193 in "DTSTART". However, in such cases, the original "DTSTART" date
194 MUST still be maintained by the calendaring and scheduling system
195 because the original "DTSTART" value has inherent usage
196 dependencies by other properties such as the "RECURRENCE-ID".
198 Example:
199 Below, we add an exdate in a list and get the resulting list of exdates.
201 .. code-block:: pycon
203 >>> from icalendar import Event
204 >>> from datetime import datetime
205 >>> event = Event()
207 # Add a list of excluded dates
208 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
209 >>> event.exdates
210 [datetime.datetime(2025, 4, 28, 16, 5)]
212 .. note::
214 Modifying the returned list does not change the EXDATE value. Assign to
215 :attr:`exdates` or use :func:`icalendar.cal.Component.add` instead.
217 If you want to compute recurrences, have a look at
218 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
220 """
221 result = []
222 exdates = self.get("EXDATE", [])
223 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
224 for dts in exdates.dts:
225 exdate = dts.dt
226 # we have a date/datetime
227 result.append(exdate)
228 return result
231def _set_exdates(self: Component, value) -> None:
232 """Set the EXDATE values, replacing any existing ones.
234 ``value`` is a list as returned by :attr:`exdates` (each item a date or a
235 datetime). Setting an empty list or :obj:`None` removes the EXDATE property.
236 """
237 _del_exdates(self)
238 if value:
239 self.add("EXDATE", value)
242def _del_exdates(self: Component) -> None:
243 """Delete all EXDATE values."""
244 self.pop("EXDATE", None)
247exdates_property = property(_get_exdates, _set_exdates, _del_exdates)
250def _get_rrules(self: Component) -> list[vRecur]:
251 """RRULE defines a rule or repeating pattern for recurring components.
253 RRULE is defined in :rfc:`5545`.
254 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
256 Property Parameters:
257 IANA and non-standard property parameters can
258 be specified on this property.
260 Conformance:
261 This property can be specified in recurring "VEVENT",
262 "VTODO", and "VJOURNAL" calendar components as well as in the
263 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
264 calendar component, but it SHOULD NOT be specified more than once.
265 The recurrence set generated with multiple "RRULE" properties is
266 undefined.
268 Description:
269 The recurrence rule, if specified, is used in computing
270 the recurrence set. The recurrence set is the complete set of
271 recurrence instances for a calendar component. The recurrence set
272 is generated by considering the initial "DTSTART" property along
273 with the "RRULE", "RDATE", and "EXDATE" properties contained
274 within the recurring component. The "DTSTART" property defines
275 the first instance in the recurrence set. The "DTSTART" property
276 value SHOULD be synchronized with the recurrence rule, if
277 specified. The recurrence set generated with a "DTSTART" property
278 value not synchronized with the recurrence rule is undefined. The
279 final recurrence set is generated by gathering all of the start
280 DATE-TIME values generated by any of the specified "RRULE" and
281 "RDATE" properties, and then excluding any start DATE-TIME values
282 specified by "EXDATE" properties. This implies that start DATE-
283 TIME values specified by "EXDATE" properties take precedence over
284 those specified by inclusion properties (i.e., "RDATE" and
285 "RRULE"). Where duplicate instances are generated by the "RRULE"
286 and "RDATE" properties, only one recurrence is considered.
287 Duplicate instances are ignored.
289 The "DTSTART" property specified within the iCalendar object
290 defines the first instance of the recurrence. In most cases, a
291 "DTSTART" property of DATE-TIME value type used with a recurrence
292 rule, should be specified as a date with local time and time zone
293 reference to make sure all the recurrence instances start at the
294 same local time regardless of time zone changes.
296 If the duration of the recurring component is specified with the
297 "DTEND" or "DUE" property, then the same exact duration will apply
298 to all the members of the generated recurrence set. Else, if the
299 duration of the recurring component is specified with the
300 "DURATION" property, then the same nominal duration will apply to
301 all the members of the generated recurrence set and the exact
302 duration of each recurrence instance will depend on its specific
303 start time. For example, recurrence instances of a nominal
304 duration of one day will have an exact duration of more or less
305 than 24 hours on a day where a time zone shift occurs. The
306 duration of a specific recurrence may be modified in an exception
307 component or simply by using an "RDATE" property of PERIOD value
308 type.
310 Examples:
311 Daily for 10 occurrences:
313 .. code-block:: pycon
315 >>> from icalendar import Event
316 >>> from datetime import datetime
317 >>> from zoneinfo import ZoneInfo
318 >>> event = Event()
319 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
320 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
321 >>> print(event.to_ical())
322 BEGIN:VEVENT
323 DTSTART;TZID=America/New_York:19970902T090000
324 RRULE:FREQ=DAILY;COUNT=10
325 END:VEVENT
326 >>> event.rrules
327 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
329 Daily until December 24, 1997:
331 .. code-block:: pycon
333 >>> from icalendar import Event, vRecur
334 >>> from datetime import datetime
335 >>> from zoneinfo import ZoneInfo
336 >>> event = Event()
337 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
338 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
339 >>> print(event.to_ical())
340 BEGIN:VEVENT
341 DTSTART;TZID=America/New_York:19970902T090000
342 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
343 END:VEVENT
344 >>> event.rrules
345 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
347 .. note::
349 You cannot modify the RRULE value by modifying the result.
350 Use :func:`icalendar.cal.Component.add` to add values.
352 If you want to compute recurrences, have a look at
353 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
355 """ # noqa: E501
356 rrules = self.get("RRULE", [])
357 if not isinstance(rrules, list):
358 return [rrules]
359 return rrules
362rrules_property = property(_get_rrules)
365def multi_language_text_property(
366 main_prop: str, compatibility_prop: str | None, doc: str
367) -> property:
368 """This creates a text property.
370 This property can be defined several times with different ``LANGUAGE`` parameters.
372 Parameters:
373 main_prop (str): The property to set and get, such as ``NAME``
374 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
375 doc (str): The documentation string
376 """
378 def fget(self: Component) -> str | None:
379 """Get the property"""
380 result = self.get(main_prop)
381 if result is None and compatibility_prop is not None:
382 result = self.get(compatibility_prop)
383 if isinstance(result, list):
384 for item in result:
385 if "LANGUAGE" not in item.params:
386 return item
387 return result
389 def fset(self: Component, value: str | None):
390 """Set the property."""
391 fdel(self)
392 if value is not None:
393 self.add(main_prop, value)
394 if compatibility_prop is not None:
395 self.add(compatibility_prop, value)
397 def fdel(self: Component):
398 """Delete the property."""
399 self.pop(main_prop, None)
400 if compatibility_prop is not None:
401 self.pop(compatibility_prop, None)
403 return property(fget, fset, fdel, doc)
406def single_int_property(prop: str, default: int, doc: str) -> property:
407 """Create a property for an int value that exists only once.
409 Parameters:
410 prop: The name of the property
411 default: The default value
412 doc: The documentation string
413 """
415 def fget(self: Component) -> int:
416 """Get the property"""
417 try:
418 return int(self.get(prop, default))
419 except ValueError as e:
420 raise InvalidCalendar(f"{prop} must be an int") from e
422 def fset(self: Component, value: int | None):
423 """Set the property."""
424 fdel(self)
425 if value is not None:
426 self.add(prop, value)
428 def fdel(self: Component):
429 """Delete the property."""
430 self.pop(prop, None)
432 return property(fget, fset, fdel, doc)
435def single_utc_property(name: str, docs: str) -> property:
436 """Create a property to access a value of datetime in UTC timezone.
438 Parameters:
439 name: name of the property
440 docs: documentation string
441 """
442 docs = (
443 f"""The {name} property with all values converted to a
444 :class:`~datetime.datetime` in UTC.
446 """
447 + docs
448 )
450 def fget(self: Component) -> datetime | None:
451 """Get the value."""
452 if name not in self:
453 return None
454 dt = self.get(name)
455 if isinstance(dt, (vText, vUnknown)):
456 # we might be in an attribute that is not typed
457 value = vDDDTypes.from_ical(dt)
458 else:
459 value = getattr(dt, "dt", dt)
460 if value is None or not isinstance(value, date):
461 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
462 return tzp.localize_utc(value)
464 def fset(self: Component, value: datetime | None):
465 """Set the value"""
466 if value is None:
467 fdel(self)
468 return
469 if not isinstance(value, date):
470 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
471 fdel(self)
472 self.add(name, tzp.localize_utc(value))
474 def fdel(self: Component):
475 """Delete the property."""
476 self.pop(name, None)
478 return property(fget, fset, fdel, doc=docs)
481def single_string_property(
482 name: str, docs: str, other_name: str | list[str] | None = None, default: str = ""
483) -> property:
484 """Create a property to access a single string value."""
485 other_names = (
486 []
487 if other_name is None
488 else [other_name]
489 if isinstance(other_name, str)
490 else list(other_name)
491 )
493 def fget(self: Component) -> str:
494 """Get the value."""
495 result = self.get(name, None)
496 if result is None:
497 for alias in other_names:
498 result = self.get(alias, None)
499 if result is not None:
500 break
501 if result is None or result == []:
502 return default
503 if isinstance(result, list):
504 return result[0]
505 return result
507 def fset(self: Component, value: str | None):
508 """Set the value.
510 Setting the value to None will delete it.
511 """
512 fdel(self)
513 if value is not None:
514 self.add(name, value)
516 def fdel(self: Component):
517 """Delete the property."""
518 self.pop(name, None)
519 for alias in other_names:
520 self.pop(alias, None)
522 return property(fget, fset, fdel, doc=docs)
525color_property = single_string_property(
526 "COLOR",
527 """This property specifies a color used for displaying the component.
529 This implements :rfc:`7986` ``COLOR`` property.
531 Property Parameters:
532 IANA and non-standard property parameters can
533 be specified on this property.
535 Conformance:
536 This property can be specified once in an iCalendar
537 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
539 Description:
540 This property specifies a color that clients MAY use
541 when presenting the relevant data to a user. Typically, this
542 would appear as the "background" color of events or tasks. The
543 value is a case-insensitive color name taken from the CSS3 set of
544 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
546 Example:
547 ``"turquoise"``, ``"#ffffff"``
549 .. code-block:: pycon
551 >>> from icalendar import Todo
552 >>> todo = Todo()
553 >>> todo.color = "green"
554 >>> print(todo.to_ical())
555 BEGIN:VTODO
556 COLOR:green
557 END:VTODO
558 """,
559)
561sequence_property = single_int_property(
562 "SEQUENCE",
563 0,
564 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
566Value Type:
567 INTEGER
569Property Parameters:
570 IANA and non-standard property parameters can be specified on this property.
572Conformance:
573 The property can be specified in "VEVENT", "VTODO", or
574 "VJOURNAL" calendar component.
576Description:
577 When a calendar component is created, its sequence
578 number is 0. It is monotonically incremented by the "Organizer's"
579 CUA each time the "Organizer" makes a significant revision to the
580 calendar component.
582 The "Organizer" includes this property in an iCalendar object that
583 it sends to an "Attendee" to specify the current version of the
584 calendar component.
586 The "Attendee" includes this property in an iCalendar object that
587 it sends to the "Organizer" to specify the version of the calendar
588 component to which the "Attendee" is referring.
590 A change to the sequence number is not the mechanism that an
591 "Organizer" uses to request a response from the "Attendees". The
592 "RSVP" parameter on the "ATTENDEE" property is used by the
593 "Organizer" to indicate that a response from the "Attendees" is
594 requested.
596 Recurrence instances of a recurring component MAY have different
597 sequence numbers.
599Examples:
600 The following is an example of this property for a calendar
601 component that was just created by the "Organizer":
603 .. code-block:: pycon
605 >>> from icalendar import Event
606 >>> event = Event()
607 >>> event.sequence
608 0
610 The following is an example of this property for a calendar
611 component that has been revised 10 different times by the
612 "Organizer":
614 .. code-block:: pycon
616 >>> from icalendar import Calendar
617 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
618 >>> event = calendar.events[0]
619 >>> event.sequence
620 10
621 """, # noqa: E501
622)
625def _get_categories(component: Component) -> list[str]:
626 """Get all the categories."""
627 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES")
628 if isinstance(categories, list):
629 _set_categories(
630 component,
631 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
632 )
633 return _get_categories(component)
634 if categories is None:
635 categories = vCategory([])
636 component.add("CATEGORIES", categories)
637 return categories.cats
640def _set_categories(component: Component, cats: Sequence[str] | None) -> None:
641 """Set the categories."""
642 if not cats and cats != []:
643 _del_categories(component)
644 return
645 component["CATEGORIES"] = categories = vCategory(cats)
646 if isinstance(cats, list):
647 cats.clear()
648 cats.extend(categories.cats)
649 categories.cats = cats
652def _del_categories(component: Component) -> None:
653 """Delete the categories."""
654 component.pop("CATEGORIES", None)
657categories_property = property(
658 _get_categories,
659 _set_categories,
660 _del_categories,
661 """This property defines the categories for a component.
663The categories property is used to specify categories or subtypes of the
664calendar component. The categories are useful to search for a calendar
665component of a particular type and category.
667Within the calendar components, specify categories as a list of strings.
668You can get, set, and delete categories for a component.
670This property can be used in icalendar through its Python attributes of:
672- :attr:`Available.categories <icalendar.cal.available.Available.categories>`
673- :attr:`Availability.categories <icalendar.cal.availability.Availability.categories>`
674- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>`
675- :attr:`Event.categories <icalendar.cal.event.Event.categories>`
676- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>`
677- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>`
679The categories property for ``Available`` and ``Availability`` complies with
680:rfc:`7953#section-3.1`, for ``Event``, ``Journal``, and ``Todo`` with
681:rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`.
683Note:
684 At present, icalendar doesn't take the LANGUAGE parameter as defined
685 in :rfc:`5545#section-3.2.10` into account.
687Parameters:
688 categories(list[str]): A list of categories as strings.
690Example:
691 Create an event, add categories to it, print its ical representation,
692 append another category, and finally compare the result
693 against its expected value.
695 .. code-block:: pycon
697 >>> from icalendar import Event
698 >>> event = Event()
699 >>> event.categories = ["Work", "Meeting"]
700 >>> print(event.to_ical())
701 BEGIN:VEVENT
702 CATEGORIES:Work,Meeting
703 END:VEVENT
704 >>> event.categories.append("Lecture")
705 >>> event.categories == ["Work", "Meeting", "Lecture"]
706 True
708See also:
709 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>`
710""",
711)
714def _get_attendees(self: Component) -> list[vCalAddress]:
715 """Get attendees."""
716 value = self.get("ATTENDEE")
717 if value is None:
718 value = []
719 self["ATTENDEE"] = value
720 return value
721 if isinstance(value, vCalAddress):
722 return [value]
723 return value
726def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
727 """Set attendees."""
728 _del_attendees(self)
729 if value is None:
730 return
731 if not isinstance(value, list):
732 value = [value]
733 self["ATTENDEE"] = value
736def _del_attendees(self: Component):
737 """Delete all attendees."""
738 self.pop("ATTENDEE", None)
741attendees_property = property(
742 _get_attendees,
743 _set_attendees,
744 _del_attendees,
745 """ATTENDEE defines one or more "Attendees" within a calendar component.
747Conformance:
748 This property MUST be specified in an iCalendar object
749 that specifies a group-scheduled calendar entity. This property
750 MUST NOT be specified in an iCalendar object when publishing the
751 calendar information (e.g., NOT in an iCalendar object that
752 specifies the publication of a calendar user's busy time, event,
753 to-do, or journal). This property is not specified in an
754 iCalendar object that specifies only a time zone definition or
755 that defines calendar components that are not group-scheduled
756 components, but are components only on a single user's calendar.
758Description:
759 This property MUST only be specified within calendar
760 components to specify participants, non-participants, and the
761 chair of a group-scheduled calendar entity. The property is
762 specified within an "EMAIL" category of the "VALARM" calendar
763 component to specify an email address that is to receive the email
764 type of iCalendar alarm.
766Examples:
767 Add a new attendee to an existing event.
769 .. code-block:: pycon
771 >>> from icalendar import Event, vCalAddress
772 >>> event = Event()
773 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
774 >>> print(event.to_ical())
775 BEGIN:VEVENT
776 ATTENDEE:mailto:me@my-domain.com
777 END:VEVENT
779 Create an email alarm with several attendees:
781 >>> from icalendar import Alarm, vCalAddress
782 >>> alarm = Alarm.new(attendees = [
783 ... vCalAddress("mailto:me@my-domain.com"),
784 ... vCalAddress("mailto:you@my-domain.com"),
785 ... ], summary = "Email alarm")
786 >>> print(alarm.to_ical())
787 BEGIN:VALARM
788 ATTENDEE:mailto:me@my-domain.com
789 ATTENDEE:mailto:you@my-domain.com
790 SUMMARY:Email alarm
791 END:VALARM
792""",
793)
795uid_property = single_string_property(
796 "UID",
797 """UID specifies the persistent, globally unique identifier for a component.
799We recommend using :func:`uuid.uuid4` to generate new values.
801Returns:
802 The value of the UID property as a string or ``""`` if no value is set.
804Description:
805 The "UID" itself MUST be a globally unique identifier.
806 The generator of the identifier MUST guarantee that the identifier
807 is unique.
809 This is the method for correlating scheduling messages with the
810 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
811 The full range of calendar components specified by a recurrence
812 set is referenced by referring to just the "UID" property value
813 corresponding to the calendar component. The "RECURRENCE-ID"
814 property allows the reference to an individual instance within the
815 recurrence set.
817 This property is an important method for group-scheduling
818 applications to match requests with later replies, modifications,
819 or deletion requests. Calendaring and scheduling applications
820 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
821 calendar components to assure interoperability with other group-
822 scheduling applications. This identifier is created by the
823 calendar system that generates an iCalendar object.
825 Implementations MUST be able to receive and persist values of at
826 least 255 octets for this property, but they MUST NOT truncate
827 values in the middle of a UTF-8 multi-octet sequence.
829 :rfc:`7986` states that UID can be used, for
830 example, to identify duplicate calendar streams that a client may
831 have been given access to. It can be used in conjunction with the
832 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
833 to identify the most recent version of a calendar.
835Conformance:
836 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
837 and "VJOURNAL" calendar components.
838 :rfc:`7986` modifies the definition of the "UID" property to
839 allow it to be defined in an iCalendar object.
840 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
841 identifier to be specified. The value of this property can then be used
842 to refer uniquely to the "VALARM" component.
844 This property can be specified once only.
846Security:
847 :rfc:`7986` states that UID values MUST NOT include any data that
848 might identify a user, host, domain, or any other security- or
849 privacy-sensitive information. It is RECOMMENDED that calendar user
850 agents now generate "UID" values that are hex-encoded random
851 Universally Unique Identifier (UUID) values as defined in
852 Sections 4.4 and 4.5 of :rfc:`4122`.
853 You can use the :mod:`uuid` module to generate new UUIDs.
855Compatibility:
856 For Alarms, ``X-ALARMUID`` is also considered.
858Examples:
859 The following is an example of such a property value:
860 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
862 Set the UID of a calendar:
864 .. code-block:: pycon
866 >>> from icalendar import Calendar
867 >>> from uuid import uuid4
868 >>> calendar = Calendar()
869 >>> calendar.uid = uuid4()
870 >>> print(calendar.to_ical())
871 BEGIN:VCALENDAR
872 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
873 END:VCALENDAR
875""",
876)
878summary_property = multi_language_text_property(
879 "SUMMARY",
880 None,
881 """SUMMARY defines a short summary or subject for the calendar component.
883Property Parameters:
884 IANA, non-standard, alternate text
885 representation, and language property parameters can be specified
886 on this property.
888Conformance:
889 The property can be specified in "VEVENT", "VTODO",
890 "VJOURNAL", or "VALARM" calendar components.
892Description:
893 This property is used in the "VEVENT", "VTODO", and
894 "VJOURNAL" calendar components to capture a short, one-line
895 summary about the activity or journal entry.
897 This property is used in the "VALARM" calendar component to
898 capture the subject of an EMAIL category of alarm.
900Examples:
901 The following is an example of this property:
903 .. code-block:: pycon
905 SUMMARY:Department Party
906""",
907)
909description_property = multi_language_text_property(
910 "DESCRIPTION",
911 None,
912 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
914Property Parameters:
915 IANA, non-standard, alternate text
916 representation, and language property parameters can be specified
917 on this property.
919Conformance:
920 The property can be specified in the "VEVENT", "VTODO",
921 "VJOURNAL", or "VALARM" calendar components. The property can be
922 specified multiple times only within a "VJOURNAL" calendar
923 component.
925Description:
926 This property is used in the "VEVENT" and "VTODO" to
927 capture lengthy textual descriptions associated with the activity.
929 This property is used in the "VALARM" calendar component to
930 capture the display text for a DISPLAY category of alarm, and to
931 capture the body text for an EMAIL category of alarm.
933Examples:
934 The following is an example of this property with formatted
935 line breaks in the property value:
937 .. code-block:: pycon
939 DESCRIPTION:Meeting to provide technical review for "Phoenix"
940 design.\\nHappy Face Conference Room. Phoenix design team
941 MUST attend this meeting.\\nRSVP to team leader.
943 """, # noqa: E501
944)
947def create_single_property(
948 prop: str,
949 value_attr: str | None,
950 value_type: tuple[type],
951 type_def: type,
952 doc: str,
953 vProp: type = vDDDTypes, # noqa: N803
954 convert: Callable[[object], object] | None = None,
955):
956 """Create a single property getter and setter.
958 Parameters:
959 prop: The name of the property.
960 value_attr: The name of the attribute to get the value from.
961 value_type: The type of the value.
962 type_def: The type of the property.
963 doc: The docstring of the property.
964 vProp: The type of the property from :mod:`icalendar.prop`.
965 """
967 def p_get(self: Component):
968 default = object()
969 result = self.get(prop, default)
970 if result is default:
971 return None
972 if isinstance(result, list):
973 raise InvalidCalendar(f"Multiple {prop} defined.")
974 value = result if value_attr is None else getattr(result, value_attr, result)
975 value = value if convert is None else convert(value)
976 if not isinstance(value, value_type):
977 raise InvalidCalendar(
978 f"{prop} must be either a "
979 f"{' or '.join(t.__name__ for t in value_type)},"
980 f" not {value}."
981 )
982 return value
984 def p_set(self: Component, value) -> None:
985 if value is None:
986 p_del(self)
987 return
988 value = convert(value) if convert is not None else value
989 if not isinstance(value, value_type):
990 raise TypeError(
991 f"Use {' or '.join(t.__name__ for t in value_type)}, "
992 f"not {type(value).__name__}."
993 )
994 self[prop] = vProp(value)
995 if prop in self.exclusive:
996 for other_prop in self.exclusive:
997 if other_prop != prop:
998 self.pop(other_prop, None)
1000 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None
1002 def p_del(self: Component):
1003 self.pop(prop)
1005 p_doc = f"""The {prop} property.
1007 {doc}
1009 To delete the value, either use ``del`` or set it to ``None``.
1011 Raises:
1012 InvalidCalendar: if the attribute has invalid values.
1013 """
1014 return property(p_get, p_set, p_del, p_doc)
1017X_MOZ_SNOOZE_TIME_property = single_utc_property(
1018 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
1019)
1020X_MOZ_LASTACK_property = single_utc_property(
1021 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
1022)
1025def property_get_duration(self: Component) -> timedelta | None:
1026 """Getter for property DURATION."""
1027 default = object()
1028 duration = self.get("duration", default)
1029 if duration is default:
1030 return None
1031 result = getattr(duration, "td", None)
1032 if result is None:
1033 raise InvalidCalendar(
1034 f"DURATION must be a timedelta, not {type(duration).__name__}."
1035 )
1036 return result
1039def property_set_duration(self: Component, value: timedelta | None):
1040 """Setter for property DURATION."""
1041 if value is None:
1042 self.pop("duration", None)
1043 return
1044 if not isinstance(value, timedelta):
1045 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
1046 self["duration"] = vDuration(value)
1047 self.pop("DTEND")
1048 self.pop("DUE")
1051def property_del_duration(self: Component):
1052 """Delete property DURATION."""
1053 self.pop("DURATION")
1056property_doc_duration_template = """The DURATION property.
1058The "DTSTART" property for a "{component}" specifies the inclusive
1059start of the {component}.
1060The "DURATION" property in conjunction with the DTSTART property
1061for a "{component}" calendar component specifies the non-inclusive end
1062of the event.
1064If you would like to calculate the duration of a {component}, do not use this.
1065Instead use the duration property (lower case).
1066"""
1069def duration_property(component: str) -> property:
1070 """Return the duration property."""
1071 return property(
1072 property_get_duration,
1073 property_set_duration,
1074 property_del_duration,
1075 property_doc_duration_template.format(component=component),
1076 )
1079def multi_text_property(name: str, docs: str) -> property:
1080 """Get a property that can occur several times and is text.
1082 Examples: Journal.descriptions, Event.comments
1083 """
1085 def fget(self: Component) -> list[str]:
1086 """Get the values."""
1087 descriptions = self.get(name)
1088 if descriptions is None:
1089 return []
1090 if not isinstance(descriptions, SEQUENCE_TYPES):
1091 return [descriptions]
1092 return descriptions
1094 def fset(self: Component, values: str | Sequence[str] | None):
1095 """Set the values."""
1096 fdel(self)
1097 if values is None:
1098 return
1099 if isinstance(values, str):
1100 self.add(name, values)
1101 else:
1102 for description in values:
1103 self.add(name, description)
1105 def fdel(self: Component):
1106 """Delete the values."""
1107 self.pop(name)
1109 return property(fget, fset, fdel, docs)
1112descriptions_property = multi_text_property(
1113 "DESCRIPTION",
1114 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1116Property Parameters:
1117 IANA, non-standard, alternate text
1118 representation, and language property parameters can be specified
1119 on this property.
1121Conformance:
1122 The property can be
1123 specified multiple times only within a "VJOURNAL" calendar component.
1125Description:
1126 This property is used in the "VJOURNAL" calendar component to
1127 capture one or more textual journal entries.
1129Examples:
1130 The following is an example of this property with formatted
1131 line breaks in the property value:
1133 .. code-block:: pycon
1135 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1136 design.\\nHappy Face Conference Room. Phoenix design team
1137 MUST attend this meeting.\\nRSVP to team leader.
1139""", # noqa: E501
1140)
1142comments_property = multi_text_property(
1143 "COMMENT",
1144 """COMMENT is used to specify a comment to the calendar user.
1146Purpose:
1147 This property specifies non-processing information intended
1148 to provide a comment to the calendar user.
1150Conformance:
1151 In :rfc:`5545`, this property can be specified multiple times in
1152 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1153 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1154 In :rfc:`7953`, this property can be specified multiple times in
1155 "VAVAILABILITY" and "VAVAILABLE".
1157Property Parameters:
1158 IANA, non-standard, alternate text
1159 representation, and language property parameters can be specified
1160 on this property.
1162""",
1163)
1165RECURRENCE_ID = create_single_property(
1166 "RECURRENCE-ID",
1167 "dt",
1168 (date, datetime),
1169 date | datetime,
1170 """
1171Identify a specific occurrence of a recurring calendar object.
1173This property is used together with ``UID`` and ``SEQUENCE`` to refer to one
1174particular instance in a recurrence set. The value is the original start
1175date or datetime of that instance, not the rescheduled time.
1177The value is usually a DATE-TIME and must use the same value type as the
1178``DTSTART`` property in the same component. A DATE value may be used for
1179all-day items instead.
1181This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and
1182may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components.
1183""",
1184 vDDDTypes,
1185)
1188def _get_organizer(self: Component) -> vCalAddress | None:
1189 """ORGANIZER defines the organizer for a calendar component.
1191 Property Parameters:
1192 IANA, non-standard, language, common name,
1193 directory entry reference, and sent-by property parameters can be
1194 specified on this property.
1196 Conformance:
1197 This property MUST be specified in an iCalendar object
1198 that specifies a group-scheduled calendar entity. This property
1199 MUST be specified in an iCalendar object that specifies the
1200 publication of a calendar user's busy time. This property MUST
1201 NOT be specified in an iCalendar object that specifies only a time
1202 zone definition or that defines calendar components that are not
1203 group-scheduled components, but are components only on a single
1204 user's calendar.
1206 Description:
1207 This property is specified within the "VEVENT",
1208 "VTODO", and "VJOURNAL" calendar components to specify the
1209 organizer of a group-scheduled calendar entity. The property is
1210 specified within the "VFREEBUSY" calendar component to specify the
1211 calendar user requesting the free or busy time. When publishing a
1212 "VFREEBUSY" calendar component, the property is used to specify
1213 the calendar that the published busy time came from.
1215 The property has the property parameters "CN", for specifying the
1216 common or display name associated with the "Organizer", "DIR", for
1217 specifying a pointer to the directory information associated with
1218 the "Organizer", "SENT-BY", for specifying another calendar user
1219 that is acting on behalf of the "Organizer". The non-standard
1220 parameters may also be specified on this property. If the
1221 "LANGUAGE" property parameter is specified, the identified
1222 language applies to the "CN" parameter value.
1223 """
1224 return self.get("ORGANIZER")
1227def _set_organizer(self: Component, value: vCalAddress | str | None):
1228 """Set the value."""
1229 _del_organizer(self)
1230 if value is not None:
1231 self.add("ORGANIZER", value)
1234def _del_organizer(self: Component):
1235 """Delete the value."""
1236 self.pop("ORGANIZER")
1239organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1242def single_string_enum_property(
1243 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1244) -> property:
1245 """Create a property to access a single string value and convert it to an enum."""
1246 prop = single_string_property(name, docs, default=default)
1248 def fget(self: Component) -> StrEnum:
1249 """Get the value."""
1250 value = prop.fget(self)
1251 if value == default:
1252 return default
1253 return enum(str(value))
1255 def fset(self: Component, value: str | StrEnum | None) -> None:
1256 """Set the value."""
1257 if value == "":
1258 value = None
1259 prop.fset(self, value)
1261 return property(fget, fset, prop.fdel, doc=docs)
1264busy_type_property = single_string_enum_property(
1265 "BUSYTYPE",
1266 BUSYTYPE,
1267 BUSYTYPE.BUSY_UNAVAILABLE,
1268 """BUSYTYPE specifies the default busy time type.
1270Returns:
1271 :class:`icalendar.enums.BUSYTYPE`
1273Description:
1274 This property is used to specify the default busy time
1275 type. The values correspond to those used by the "FBTYPE"
1276 parameter used on a "FREEBUSY" property, with the exception that
1277 the "FREE" value is not used in this property. If not specified
1278 on a component that allows this property, the default is "BUSY-
1279 UNAVAILABLE".
1280""",
1281)
1284def _make_repeat_property() -> property:
1285 from icalendar.config import _clamp_repeat
1287 _base = single_int_property(
1288 "REPEAT",
1289 0,
1290 """The number of additional times the alarm is triggered after the
1291initial trigger.
1293Defaults to ``0``, meaning the alarm fires once. Must be paired with
1294:attr:`~icalendar.cal.alarm.Alarm.DURATION`. Conforms with :rfc:`5545#section-3.8.6.2`.
1295The value is capped at :data:`icalendar.config.MAX_ALARM_REPEAT` on read.
1296""",
1297 )
1299 def fget(self):
1300 return _clamp_repeat(_base.fget(self))
1302 return property(fget, _base.fset, _base.fdel, _base.__doc__)
1305repeat_property = _make_repeat_property()
1307priority_property = single_int_property(
1308 "PRIORITY",
1309 0,
1310 """
1312Conformance:
1313 This property can be specified in "VEVENT" and "VTODO" calendar components
1314 according to :rfc:`5545`.
1315 :rfc:`7953` adds this property to "VAVAILABILITY".
1317Description:
1318 This priority is specified as an integer in the range 0
1319 to 9. A value of 0 specifies an undefined priority. A value of 1
1320 is the highest priority. A value of 2 is the second highest
1321 priority. Subsequent numbers specify a decreasing ordinal
1322 priority. A value of 9 is the lowest priority.
1324 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1325 "LOW" is mapped into this property such that a property value in
1326 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1327 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1328 is "LOW" priority.
1330 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1331 "C3" is mapped into this property such that a property value of 1
1332 specifies "A1", a property value of 2 specifies "A2", a property
1333 value of 3 specifies "A3", and so forth up to a property value of
1334 9 specifies "C3".
1336 Other integer values are reserved for future use.
1338 Within a "VEVENT" calendar component, this property specifies a
1339 priority for the event. This property may be useful when more
1340 than one event is scheduled for a given time period.
1342 Within a "VTODO" calendar component, this property specified a
1343 priority for the to-do. This property is useful in prioritizing
1344 multiple action items for a given time period.
1345""",
1346)
1348class_property = single_string_enum_property(
1349 "CLASS",
1350 CLASS,
1351 CLASS.PUBLIC,
1352 """CLASS specifies the class of the calendar component.
1354Returns:
1355 :class:`icalendar.enums.CLASS`
1357Description:
1358 An access classification is only one component of the
1359 general security system within a calendar application. It
1360 provides a method of capturing the scope of the access the
1361 calendar owner intends for information within an individual
1362 calendar entry. The access classification of an individual
1363 iCalendar component is useful when measured along with the other
1364 security components of a calendar system (e.g., calendar user
1365 authentication, authorization, access rights, access role, etc.).
1366 Hence, the semantics of the individual access classifications
1367 cannot be completely defined by this memo alone. Additionally,
1368 due to the "blind" nature of most exchange processes using this
1369 memo, these access classifications cannot serve as an enforcement
1370 statement for a system receiving an iCalendar object. Rather,
1371 they provide a method for capturing the intention of the calendar
1372 owner for the access to the calendar component. If not specified
1373 in a component that allows this property, the default value is
1374 PUBLIC. Applications MUST treat x-name and iana-token values they
1375 don't recognize the same way as they would the PRIVATE value.
1376""",
1377)
1379transparency_property = single_string_enum_property(
1380 "TRANSP",
1381 TRANSP,
1382 TRANSP.OPAQUE,
1383 """TRANSP defines whether or not an event is transparent to busy time searches.
1385Returns:
1386 :class:`icalendar.enums.TRANSP`
1388Description:
1389 Time Transparency is the characteristic of an event
1390 that determines whether it appears to consume time on a calendar.
1391 Events that consume actual time for the individual or resource
1392 associated with the calendar SHOULD be recorded as OPAQUE,
1393 allowing them to be detected by free/busy time searches. Other
1394 events, which do not take up the individual's (or resource's) time
1395 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1396 busy time searches.
1397""",
1398)
1399status_property = single_string_enum_property(
1400 "STATUS",
1401 STATUS,
1402 "",
1403 """STATUS defines the overall status or confirmation for the calendar component.
1405Returns:
1406 :class:`icalendar.enums.STATUS`
1408The default value is ``""``.
1410Description:
1411 In a group-scheduled calendar component, the property
1412 is used by the "Organizer" to provide a confirmation of the event
1413 to the "Attendees". For example in a "VEVENT" calendar component,
1414 the "Organizer" can indicate that a meeting is tentative,
1415 confirmed, or cancelled. In a "VTODO" calendar component, the
1416 "Organizer" can indicate that an action item needs action, is
1417 completed, is in process or being worked on, or has been
1418 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1419 can indicate that a journal entry is draft, final, or has been
1420 cancelled or removed.
1421""",
1422)
1424url_property = single_string_property(
1425 "URL",
1426 """A Uniform Resource Locator (URL) associated with a calendar component.
1428This property specifies a URI where a more dynamic rendition of the calendar
1429information can be found. It is commonly used to reference related resources
1430or provide additional information about the component.
1432According to :rfc:`5545#section-3.8.4.6`, this property can be specified
1433once in "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1434Since :rfc:`7986#section-5.5`, this property can also be defined on a
1435"VCALENDAR". :rfc:`7953#section-3.1` allows this property in "VAVAILABILITY" components.
1437This property may be used in a calendar component to convey a location
1438where a more dynamic rendition of the calendar information can be found.
1439If both the URL property and Content-Location MIME header are specified,
1440they MUST point to the same resource.
1442This differs from the SOURCE property, which identifies where calendar
1443data can be refreshed from, whereas URL provides an alternative
1444representation of the current calendar data.
1446Examples:
1448 Set a URL for an event that references additional information:
1450 .. code-block:: pycon
1452 >>> from icalendar import Event
1453 >>> event = Event()
1454 >>> event.add('url', 'http://example.com/events/meeting-2025')
1455 >>> print(event.to_ical().decode('utf-8'))
1456 BEGIN:VEVENT
1457 URL:http://example.com/events/meeting-2025
1458 END:VEVENT
1460 Set a URL for a calendar:
1462 .. code-block:: pycon
1464 >>> from icalendar import Calendar
1465 >>> calendar = Calendar()
1466 >>> calendar.add('url', 'http://example.com/pub/calendars/jsmith/mytime.ics')
1467 >>> print(calendar.to_ical().decode('utf-8'))
1468 BEGIN:VCALENDAR
1469 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1470 END:VCALENDAR
1472See also:
1473 :attr:`~icalendar.cal.calendar.Calendar.source` for specifying from where
1474 calendar data can be refreshed.
1476 icalendar implementations:
1478 - :attr:`Availability.url <icalendar.cal.availability.Availability.url>`
1479 - :attr:`Calendar.url <icalendar.cal.calendar.Calendar.url>`
1480 - :attr:`Event.url <icalendar.cal.event.Event.url>`
1481 - :attr:`FreeBusy.url <icalendar.cal.free_busy.FreeBusy.url>`
1482 - :attr:`Journal.url <icalendar.cal.journal.Journal.url>`
1483 - :attr:`Todo.url <icalendar.cal.todo.Todo.url>`
1486""",
1487)
1489source_property = single_string_property(
1490 "SOURCE",
1491 """A URI from where calendar data can be refreshed.
1493Description:
1494 This property identifies a location where a client can
1495 retrieve updated data for the calendar. Clients SHOULD honor any
1496 specified "REFRESH-INTERVAL" value when periodically retrieving
1497 data. Note that this property differs from the "URL" property in
1498 that "URL" is meant to provide an alternative representation of
1499 the calendar data rather than the original location of the data.
1501Conformance:
1502 This property can be specified once in an iCalendar object.
1504Example:
1505 The following is an example of this property:
1507 .. code-block:: ics
1509 SOURCE;VALUE=URI:https://example.com/holidays.ics
1511""",
1512)
1514location_property = multi_language_text_property(
1515 "LOCATION",
1516 None,
1517 """The intended venue for the activity defined by a calendar component.
1519Property Parameters:
1520 IANA, non-standard, alternate text
1521 representation, and language property parameters can be specified
1522 on this property.
1524Conformance:
1525 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1526 calendar component.
1527 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1529Description:
1530 Specific venues such as conference or meeting rooms may
1531 be explicitly specified using this property. An alternate
1532 representation may be specified that is a URI that points to
1533 directory information with more structured specification of the
1534 location. For example, the alternate representation may specify
1535 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1536 CID URL :rfc:`2392` pointing to a MIME body part containing a
1537 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1539""",
1540)
1542contacts_property = multi_text_property(
1543 "CONTACT",
1544 """Contact information associated with the calendar component.
1546Purpose:
1547 This property is used to represent contact information or
1548 alternately a reference to contact information associated with the
1549 calendar component.
1551Property Parameters:
1552 IANA, non-standard, alternate text
1553 representation, and language property parameters can be specified
1554 on this property.
1556Conformance:
1557 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1558 "VJOURNAL", or "VFREEBUSY" calendar component.
1559 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1560 amd "VAVAILABLE" calendar component.
1562Description:
1563 The property value consists of textual contact
1564 information. An alternative representation for the property value
1565 can also be specified that refers to a URI pointing to an
1566 alternate form, such as a vCard :rfc:`2426`, for the contact
1567 information.
1569Example:
1570 The following is an example of this property referencing
1571 textual contact information:
1573 .. code-block:: ics
1575 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1577 The following is an example of this property with an alternate
1578 representation of an LDAP URI to a directory entry containing the
1579 contact information:
1581 .. code-block:: ics
1583 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1584 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1585 +1-919-555-1234
1587 The following is an example of this property with an alternate
1588 representation of a MIME body part containing the contact
1589 information, such as a vCard :rfc:`2426` embedded in a text/
1590 directory media type :rfc:`2425`:
1592 .. code-block:: ics
1594 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1595 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1597 The following is an example of this property referencing a network
1598 resource, such as a vCard :rfc:`2426` object containing the contact
1599 information:
1601 .. code-block:: ics
1603 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1604 Dolittle\\, ABC Industries\\, +1-919-555-1234
1605""",
1606)
1609def timezone_datetime_property(name: str, docs: str):
1610 """Create a property to access the values with a proper timezone."""
1612 return single_utc_property(name, docs)
1615rfc_7953_dtstart_property = timezone_datetime_property(
1616 "DTSTART",
1617 """Start of the component.
1619 This is almost the same as
1620 :attr:`Event.DTSTART <icalendar.cal.event.Event.DTSTART>` with one exception:
1621 The values MUST have a timezone and DATE is not allowed.
1623 Description:
1624 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1625 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1626 "DATE-TIME" values specified as either the date with UTC time or
1627 the date with local time and a time zone reference.
1629 """,
1630)
1632rfc_7953_dtend_property = timezone_datetime_property(
1633 "DTEND",
1634 """Start of the component.
1636 This is almost the same as
1637 :attr:`Event.DTEND <icalendar.cal.event.Event.DTEND>` with one exception:
1638 The values MUST have a timezone and DATE is not allowed.
1640 Description:
1641 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1642 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1643 "DATE-TIME" values specified as either the date with UTC time or
1644 the date with local time and a time zone reference.
1645 """,
1646)
1649@property
1650def rfc_7953_duration_property(self) -> timedelta | None:
1651 """Compute the duration of this component.
1653 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1654 Otherwise, the duration is calculated from :attr:`DTSTART` and
1655 :attr:`DTEND`/:attr:`DURATION`.
1657 This is in accordance with :rfc:`7953`:
1658 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1659 """
1660 duration = self.DURATION
1661 if duration:
1662 return duration
1663 end = self.DTEND
1664 if end is None:
1665 return None
1666 start = self.DTSTART
1667 if start is None:
1668 raise IncompleteComponent("Cannot compute duration without start.")
1669 return end - start
1672@property
1673def rfc_7953_end_property(self) -> timedelta | None:
1674 """Compute the duration of this component.
1676 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1677 Otherwise, the duration is calculated from :attr:`DTSTART` and
1678 :attr:`DTEND`/:attr:`DURATION`.
1680 This is in accordance with :rfc:`7953`:
1681 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1682 """
1683 duration = self.DURATION
1684 if duration:
1685 start = self.DTSTART
1686 if start is None:
1687 raise IncompleteComponent("Cannot compute end without start.")
1688 return start + duration
1689 end = self.DTEND
1690 if end is None:
1691 return None
1692 return end
1695@rfc_7953_end_property.setter
1696def rfc_7953_end_property(self, value: datetime):
1697 self.DTEND = value
1700@rfc_7953_end_property.deleter
1701def rfc_7953_end_property(self):
1702 del self.DTEND
1705def get_start_end_duration_with_validation(
1706 component: Component,
1707 start_property: str,
1708 end_property: str,
1709 component_name: str,
1710) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1711 """
1712 Validate the component and return start, end, and duration.
1714 This tests validity according to :rfc:`5545` rules
1715 for ``Event`` and ``Todo`` components.
1717 Parameters:
1718 component: The component to validate, either ``Event`` or ``Todo``.
1719 start_property: The start property name, ``DTSTART``.
1720 end_property: The end property name, either ``DTEND`` for ``Event`` or
1721 ``DUE`` for ``Todo``.
1722 component_name: The component name for error messages,
1723 either ``VEVENT`` or ``VTODO``.
1725 Returns:
1726 tuple: (start, end, duration) values from the component.
1728 Raises:
1729 ~error.InvalidCalendar: If the component violates RFC 5545 constraints.
1731 """
1732 start = getattr(component, start_property, None)
1733 end = getattr(component, end_property, None)
1734 duration = component.DURATION
1736 # RFC 5545: Only one of end property and DURATION may be present
1737 if duration is not None and end is not None:
1738 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1739 msg = (
1740 f"Only one of {end_name} and DURATION "
1741 f"may be in a {component_name}, not both."
1742 )
1743 raise InvalidCalendar(msg)
1745 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1746 if (
1747 start is not None
1748 and is_date(start)
1749 and duration is not None
1750 and duration.seconds != 0
1751 ):
1752 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1753 raise InvalidCalendar(msg)
1755 # RFC 5545: DTSTART and end property must be of the same type
1756 if start is not None and end is not None and is_date(start) != is_date(end):
1757 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1758 msg = (
1759 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1760 )
1761 raise InvalidCalendar(msg)
1763 return start, end, duration
1766def get_start_property(component: Component) -> date | datetime:
1767 """
1768 Get the start property with validation.
1770 Parameters:
1771 component: The component from which to get its start property.
1773 Returns:
1774 The ``DTSTART`` value.
1776 Raises:
1777 ~error.IncompleteComponent: If no ``DTSTART`` is present.
1779 """
1780 # Trigger validation by calling _get_start_end_duration
1781 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001
1782 if start is None:
1783 msg = "No DTSTART given."
1784 raise IncompleteComponent(msg)
1785 return start
1788def get_end_property(component: Component, end_property: str) -> date | datetime:
1789 """
1790 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1792 Parameters:
1793 component: The component to get end from
1794 end_property: The end property name, either ``DTEND`` for ``Event`` or
1795 ``DUE`` for ``Todo``.
1797 Returns:
1798 The computed end value.
1800 Raises:
1801 ~error.IncompleteComponent: If the provided information is incomplete
1802 to compute the end property.
1804 """
1805 # Trigger validation by calling _get_start_end_duration
1806 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1808 if end is None and duration is None:
1809 if start is None:
1810 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1811 msg = f"No {end_name} or DURATION+DTSTART given."
1812 raise IncompleteComponent(msg)
1814 # Default behavior differs for Event vs Todo:
1815 # Event: date gets +1 day, datetime gets same time
1816 # Todo: both date and datetime get same time (issue #898)
1817 if end_property == "DTEND" and is_date(start):
1818 return start + timedelta(days=1)
1819 return start
1821 if duration is not None:
1822 if start is not None:
1823 if component.name == "VEVENT" and duration.total_seconds() <= 0:
1824 return start
1825 return start + duration
1826 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1827 msg = f"No {end_name} or DURATION+DTSTART given."
1828 raise IncompleteComponent(msg)
1830 return end
1833def get_duration_property(component: Component) -> timedelta:
1834 """
1835 Get the duration property with fallback calculation from start and end.
1837 Parameters:
1838 component: The component from which to get its duration property.
1840 Returns:
1841 The duration as a timedelta.
1843 """
1844 # First check if DURATION property is explicitly set
1845 if "DURATION" in component:
1846 return component["DURATION"].dt
1848 # Fall back to calculated duration from start and end
1849 return component.end - component.start
1852def set_duration_with_locking(
1853 component: Component,
1854 duration: timedelta | None,
1855 locked: Literal["start", "end"],
1856 end_property: str,
1857) -> None:
1858 """
1859 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1861 Parameters:
1862 component: The component to modify, either ``Event`` or ``Todo``.
1863 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1864 locked: Which property to keep unchanged, either ``start`` or ``end``.
1865 end_property: The end property name, either ``DTEND`` for ``Event`` or
1866 ``DUE`` for ``Todo``.
1868 """
1869 # Convert to DURATION property if duration is None
1870 if duration is None:
1871 if "DURATION" in component:
1872 return # Already has DURATION property
1873 current_duration = component.duration
1874 component.DURATION = current_duration
1875 return
1877 if not isinstance(duration, timedelta):
1878 msg = f"Use timedelta, not {type(duration).__name__}."
1879 raise TypeError(msg)
1881 # Validate date/duration compatibility
1882 start = component.DTSTART
1883 if start is not None and is_date(start) and duration.seconds != 0:
1884 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1885 raise InvalidCalendar(msg)
1887 if locked == "start":
1888 # Keep start locked, adjust end
1889 if start is None:
1890 msg = "Cannot set duration without DTSTART. Set start time first."
1891 raise IncompleteComponent(msg)
1892 component.pop(end_property, None) # Remove end property
1893 component.DURATION = duration
1894 elif locked == "end":
1895 # Keep end locked, adjust start
1896 current_end = component.end
1897 component.DTSTART = current_end - duration
1898 component.pop(end_property, None) # Remove end property
1899 component.DURATION = duration
1900 else:
1901 msg = f"locked must be 'start' or 'end', not {locked!r}"
1902 raise ValueError(msg)
1905def set_start_with_locking(
1906 component: Component,
1907 start: date | datetime,
1908 locked: Literal["duration", "end"] | None,
1909 end_property: str,
1910) -> None:
1911 """
1912 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1914 Parameters:
1915 component: The component to modify, either ``Event`` or ``Todo``.
1916 start: The start time to set.
1917 locked: Which property to keep unchanged, either ``duration``, ``end``,
1918 or ``None`` for auto-detect.
1919 end_property: The end property name, either ``DTEND`` for ``Event`` or
1920 ``DUE`` for ``Todo``.
1922 """
1923 if locked is None:
1924 # Auto-detect based on existing properties
1925 if "DURATION" in component:
1926 locked = "duration"
1927 elif end_property in component:
1928 locked = "end"
1929 else:
1930 # Default to duration if no existing properties
1931 locked = "duration"
1933 if locked == "duration":
1934 # Keep duration locked, adjust end
1935 current_duration = (
1936 component.duration
1937 if "DURATION" in component or end_property in component
1938 else None
1939 )
1940 component.DTSTART = start
1941 if current_duration is not None:
1942 component.pop(end_property, None) # Remove end property
1943 component.DURATION = current_duration
1944 elif locked == "end":
1945 # Keep end locked, adjust duration
1946 current_end = component.end
1947 component.DTSTART = start
1948 component.pop("DURATION", None) # Remove duration property
1949 setattr(component, end_property, current_end)
1950 else:
1951 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1952 raise ValueError(msg)
1955def set_end_with_locking(
1956 component: Component,
1957 end: date | datetime,
1958 locked: Literal["start", "duration"],
1959 end_property: str,
1960) -> None:
1961 """
1962 Set the end with explicit locking behavior for Event and Todo components.
1964 Parameters:
1965 component: The component to modify, either ``Event`` or ``Todo``.
1966 end: The end time to set.
1967 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1968 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1969 for ``Todo``.
1971 """
1972 if locked == "start":
1973 # Keep start locked, adjust duration
1974 component.pop("DURATION", None) # Remove duration property
1975 setattr(component, end_property, end)
1976 elif locked == "duration":
1977 # Keep duration locked, adjust start
1978 current_duration = component.duration
1979 component.DTSTART = end - current_duration
1980 component.pop(end_property, None) # Remove end property
1981 component.DURATION = current_duration
1982 else:
1983 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1984 raise ValueError(msg)
1987def _get_images(self: Component) -> list[Image]:
1988 """IMAGE specifies an image associated with the calendar or a calendar component.
1990 Description:
1991 This property specifies an image for an iCalendar
1992 object or a calendar component via a URI or directly with inline
1993 data that can be used by calendar user agents when presenting the
1994 calendar data to a user. Multiple properties MAY be used to
1995 specify alternative sets of images with, for example, varying
1996 media subtypes, resolutions, or sizes. When multiple properties
1997 are present, calendar user agents SHOULD display only one of them,
1998 picking one that provides the most appropriate image quality, or
1999 display none. The "DISPLAY" parameter is used to indicate the
2000 intended display mode for the image. The "ALTREP" parameter,
2001 defined in :rfc:`5545`, can be used to provide a "clickable" image
2002 where the URI in the parameter value can be "launched" by a click
2003 on the image in the calendar user agent.
2005 Conformance:
2006 This property can be specified multiple times in an
2007 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
2008 components.
2010 .. note::
2012 At the present moment, this property is read-only. If you require a setter,
2013 please open an issue or a pull request.
2014 """
2015 images = self.get("IMAGE", [])
2016 if not isinstance(images, SEQUENCE_TYPES):
2017 images = [images]
2018 return [Image.from_property_value(img) for img in images]
2021images_property = property(_get_images)
2024def _get_conferences(self: Component) -> list[Conference]:
2025 """Return the CONFERENCE properties as a list.
2027 Purpose:
2028 This property specifies information for accessing a conferencing system.
2030 Conformance:
2031 This property can be specified multiple times in a
2032 "VEVENT" or "VTODO" calendar component.
2034 Description:
2035 This property specifies information for accessing a
2036 conferencing system for attendees of a meeting or task. This
2037 might be for a telephone-based conference number dial-in with
2038 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
2039 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
2040 or https: URI :rfc:`7230`), or for an instant messaging group chat
2041 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
2042 conferencing system is not available, a data: URI :rfc:`2397`
2043 containing a text description can be used.
2045 A conference system can be a bidirectional communication channel
2046 or a uni-directional "broadcast feed".
2048 The "FEATURE" property parameter is used to describe the key
2049 capabilities of the conference system to allow a client to choose
2050 the ones that give the required level of interaction from a set of
2051 multiple properties.
2053 The "LABEL" property parameter is used to convey additional
2054 details on the use of the URI. For example, the URIs or access
2055 codes for the moderator and attendee of a teleconference system
2056 could be different, and the "LABEL" property parameter could be
2057 used to "tag" each "CONFERENCE" property to indicate which is
2058 which.
2060 The "LANGUAGE" property parameter can be used to specify the
2061 language used for text values used with this property (as per
2062 Section 3.2.10 of :rfc:`5545`).
2064 Example:
2065 The following are examples of this property:
2067 .. code-block:: ics
2069 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
2070 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
2071 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2072 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
2073 CONFERENCE;VALUE=URI;FEATURE=PHONE;
2074 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
2075 CONFERENCE;VALUE=URI;FEATURE=CHAT;
2076 LABEL=Chat room:xmpp:chat-123@conference.example.com
2077 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
2078 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
2080 Get all conferences:
2082 .. code-block:: pycon
2084 >>> from icalendar import Event
2085 >>> event = Event()
2086 >>> event.conferences
2087 []
2089 Set a conference:
2091 .. code-block:: pycon
2093 >>> from icalendar import Event, Conference
2094 >>> event = Event()
2095 >>> event.conferences = [
2096 ... Conference(
2097 ... "tel:+1-412-555-0123,,,654321",
2098 ... feature="PHONE,MODERATOR",
2099 ... label="Moderator dial-in",
2100 ... language="EN",
2101 ... )
2102 ... ]
2103 >>> print(event.to_ical())
2104 BEGIN:VEVENT
2105 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V
2106 ALUE=URI:tel:+1-412-555-0123,,,654321
2107 END:VEVENT
2109 """
2110 conferences = self.get("CONFERENCE", [])
2111 if not isinstance(conferences, SEQUENCE_TYPES):
2112 conferences = [conferences]
2113 return [Conference.from_uri(conference) for conference in conferences]
2116def _set_conferences(self: Component, conferences: list[Conference] | None):
2117 """Set the conferences."""
2118 _del_conferences(self)
2119 for conference in conferences or []:
2120 self.add("CONFERENCE", conference.to_uri())
2123def _del_conferences(self: Component):
2124 """Delete all conferences."""
2125 self.pop("CONFERENCE")
2128conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
2131def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]:
2132 """LINK properties as a list.
2134 Purpose:
2135 LINK provides a reference to external information related to a component.
2137 Property Parameters:
2138 The VALUE parameter is required.
2139 Non-standard, link relation type, format type, label, and language parameters
2140 can also be specified on this property.
2141 The LABEL parameter is defined in :rfc:`7986`.
2143 Conformance:
2144 This property can be specified zero or more times in any iCalendar component.
2145 LINK is specified in :rfc:`9253`.
2146 The LINKREL parameter is required.
2148 Description:
2149 When used in a component, the value of this property points to
2150 additional information related to the component.
2151 For example, it may reference the originating web server.
2153 This property is a serialization of the model in :rfc:`8288`,
2154 where the link target is carried in the property value,
2155 the link context is the containing calendar entity,
2156 and the link relation type and any target attributes
2157 are carried in iCalendar property parameters.
2159 The LINK property parameters map to :rfc:`8288` attributes as follows:
2161 LABEL
2162 This parameter maps to the "title"
2163 attribute defined in Section 3.4.1 of :rfc:`8288`.
2164 LABEL is used to label the destination
2165 of a link such that it can be used as a human-readable identifier
2166 (e.g., a menu entry) in the language indicated by the LANGUAGE
2167 (if present).
2168 LANGUAGE
2169 This parameter maps to the "hreflang" attribute defined in Section 3.4.1
2170 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``.
2171 LINKREL
2172 This parameter maps to the link relation type defined in Section 2.1 of
2173 :rfc:`8288`. See `Registered Link Relation Types
2174 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_.
2175 FMTTYPE
2176 This parameter maps to the "type" attribute defined in Section 3.4.1 of
2177 :rfc:`8288`.
2179 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`.
2181 Examples:
2182 The following is an example of this property,
2183 which provides a reference to the source for the calendar object.
2185 .. code-block:: ics
2187 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI:
2188 https://example.com/events
2190 The following is an example of this property,
2191 which provides a reference to an entity from which this one was derived.
2192 The link relation is a vendor-defined value.
2194 .. code-block:: ics
2196 LINK;LINKREL="https://example.com/linkrel/derivedFrom";
2197 VALUE=URI:
2198 https://example.com/tasks/01234567-abcd1234.ics
2200 The following is an example of this property,
2201 which provides a reference to a fragment of an XML document.
2202 The link relation is a vendor-defined value.
2204 .. code-block:: ics
2206 LINK;LINKREL="https://example.com/linkrel/costStructure";
2207 VALUE=XML-REFERENCE:
2208 https://example.com/xmlDocs/bidFramework.xml
2209 #xpointer(descendant::CostStruc/range-to(
2210 following::CostStrucEND[1]))
2212 Set a link :class:`icalendar.prop.uri.vUri` to the event page:
2214 .. code-block:: pycon
2216 >>> from icalendar import Event, vUri
2217 >>> from datetime import datetime
2218 >>> link = vUri(
2219 ... "http://example.com/event-page",
2220 ... params={"LINKREL":"SOURCE"}
2221 ... )
2222 >>> event = Event.new(
2223 ... start=datetime(2025, 9, 17, 12, 0),
2224 ... summary="An Example Event with a page"
2225 ... )
2226 >>> event.links = [link]
2227 >>> print(event.to_ical())
2228 BEGIN:VEVENT
2229 SUMMARY:An Example Event with a page
2230 DTSTART:20250917T120000
2231 DTSTAMP:20250517T080612Z
2232 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
2233 LINK;LINKREL="SOURCE":http://example.com/event-page
2234 END:VEVENT
2236 """
2237 links = self.get("LINK", [])
2238 if not isinstance(links, list):
2239 links = [links]
2240 return links
2243LINKS_TYPE_SETTER: TypeAlias = (
2244 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference]
2245)
2248def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None:
2249 """Set the LINKs."""
2250 _del_links(self)
2251 if links is None:
2252 return
2253 if isinstance(links, (str, vUri, vUid, vXmlReference)):
2254 links = [links]
2255 for link in links:
2256 if type(link) is str:
2257 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901
2258 self.add("LINK", link)
2261def _del_links(self: Component) -> None:
2262 """Delete all links."""
2263 self.pop("LINK")
2266links_property = property(_get_links, _set_links, _del_links)
2268RELATED_TO_TYPE_SETTER: TypeAlias = (
2269 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid]
2270)
2273def _get_related_to(self: Component) -> list[vText | vUri | vUid]:
2274 """RELATED-TO properties as a list.
2276 Purpose:
2277 This property is used to represent a relationship or reference
2278 between one calendar component and another.
2279 :rfc:`9523` allows URI or UID values and a GAP parameter.
2281 Value Type:
2282 :rfc:`5545`: TEXT
2283 :rfc:`9253`: URI, UID
2285 Conformance:
2286 Since :rfc:`5545`. this property can be specified in the "VEVENT",
2287 "VTODO", and "VJOURNAL" calendar components.
2288 Since :rfc:`9523`, this property MAY be specified in any
2289 iCalendar component.
2291 Description (:rfc:`5545`):
2292 The property value consists of the persistent, globally
2293 unique identifier of another calendar component. This value would
2294 be represented in a calendar component by the "UID" property.
2296 By default, the property value points to another calendar
2297 component that has a PARENT relationship to the referencing
2298 object. The "RELTYPE" property parameter is used to either
2299 explicitly state the default PARENT relationship type to the
2300 referenced calendar component or to override the default PARENT
2301 relationship type and specify either a CHILD or SIBLING
2302 relationship. The PARENT relationship indicates that the calendar
2303 component is a subordinate of the referenced calendar component.
2304 The CHILD relationship indicates that the calendar component is a
2305 superior of the referenced calendar component. The SIBLING
2306 relationship indicates that the calendar component is a peer of
2307 the referenced calendar component.
2309 Changes to a calendar component referenced by this property can
2310 have an implicit impact on the related calendar component. For
2311 example, if a group event changes its start or end date or time,
2312 then the related, dependent events will need to have their start
2313 and end dates changed in a corresponding way. Similarly, if a
2314 PARENT calendar component is cancelled or deleted, then there is
2315 an implied impact to the related CHILD calendar components. This
2316 property is intended only to provide information on the
2317 relationship of calendar components. It is up to the target
2318 calendar system to maintain any property implications of this
2319 relationship.
2321 Description (:rfc:`9253`):
2322 By default or when VALUE=UID is specified, the property value
2323 consists of the persistent, globally unique identifier of another
2324 calendar component. This value would be represented in a calendar
2325 component by the UID property.
2327 By default, the property value
2328 points to another calendar component that has a PARENT relationship
2329 to the referencing object. The RELTYPE property parameter is used
2330 to either explicitly state the default PARENT relationship type to
2331 the referenced calendar component or to override the default
2332 PARENT relationship type and specify either a CHILD or SIBLING
2333 relationship or a temporal relationship.
2335 The PARENT relationship
2336 indicates that the calendar component is a subordinate of the
2337 referenced calendar component. The CHILD relationship indicates
2338 that the calendar component is a superior of the referenced calendar
2339 component. The SIBLING relationship indicates that the calendar
2340 component is a peer of the referenced calendar component.
2342 To preserve backwards compatibility, the value type MUST
2343 be UID when the PARENT, SIBLING, or CHILD relationships
2344 are specified.
2346 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH,
2347 or STARTTOSTART relationships define temporal relationships, as
2348 specified in the RELTYPE parameter definition.
2350 The FIRST and NEXT
2351 define ordering relationships between calendar components.
2353 The DEPENDS-ON relationship indicates that the current calendar
2354 component depends on the referenced calendar component in some manner.
2355 For example, a task may be blocked waiting on the other,
2356 referenced, task.
2358 The REFID and CONCEPT relationships establish
2359 a reference from the current component to the referenced component.
2360 Changes to a calendar component referenced by this property
2361 can have an implicit impact on the related calendar component.
2362 For example, if a group event changes its start or end date or
2363 time, then the related, dependent events will need to have their
2364 start and end dates and times changed in a corresponding way.
2365 Similarly, if a PARENT calendar component is canceled or deleted,
2366 then there is an implied impact to the related CHILD calendar
2367 components. This property is intended only to provide information
2368 on the relationship of calendar components.
2370 Deletion of the target component, for example, the target of a
2371 FIRST, NEXT, or temporal relationship, can result in broken links.
2373 It is up to the target calendar system to maintain any property
2374 implications of these relationships.
2376 Examples:
2377 :rfc:`5545` examples of this property:
2379 .. code-block:: ics
2381 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
2383 .. code-block:: ics
2385 RELATED-TO:19960401-080045-4000F192713-0052@example.com
2387 :rfc:`9253` examples of this property:
2389 .. code-block:: ics
2391 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH:
2392 https://example.com/caldav/user/jb/cal/
2393 19960401-080045-4000F192713.ics
2395 See also :class:`icalendar.enums.RELTYPE`.
2397 """
2398 result = self.get("RELATED-TO", [])
2399 if not isinstance(result, list):
2400 return [result]
2401 return result
2404def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None:
2405 """Set the RELATED-TO properties."""
2406 _del_related_to(self)
2407 if values is None:
2408 return
2409 if not isinstance(values, list):
2410 values = [values]
2411 for value in values:
2412 self.add("RELATED-TO", value)
2415def _del_related_to(self: Component):
2416 """Delete the RELATED-TO properties."""
2417 self.pop("RELATED-TO", None)
2420related_to_property = property(_get_related_to, _set_related_to, _del_related_to)
2423def _get_concepts(self: Component) -> list[vUri]:
2424 """CONCEPT
2426 Purpose:
2427 CONCEPT defines the formal categories for a calendar component.
2429 Conformance:
2430 Since :rfc:`9253`,
2431 this property can be specified zero or more times in any iCalendar component.
2433 Description:
2434 This property is used to specify formal categories or classifications of
2435 the calendar component. The values are useful in searching for a calendar
2436 component of a particular type and category.
2438 This categorization is distinct from the more informal "tagging" of components
2439 provided by the existing CATEGORIES property. It is expected that the value of
2440 the CONCEPT property will reference an external resource that provides
2441 information about the categorization.
2443 In addition, a structured URI value allows for hierarchical categorization of
2444 events.
2446 Possible category resources are the various proprietary systems, for example,
2447 the Library of Congress, or an open source of categorization data.
2449 Examples:
2450 The following is an example of this property.
2451 It points to a server acting as the source for the calendar object.
2453 .. code-block:: ics
2455 CONCEPT:https://example.com/event-types/arts/music
2457 .. seealso::
2459 :attr:`icalendar.prop.categories.vCategory`
2460 """
2461 concepts = self.get("CONCEPT", [])
2462 if not isinstance(concepts, list):
2463 concepts = [concepts]
2464 return concepts
2467CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None
2470def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER):
2471 """Set the concepts."""
2472 _del_concepts(self)
2473 if concepts is None:
2474 return
2475 if not isinstance(concepts, list):
2476 concepts = [concepts]
2477 for value in concepts:
2478 self.add("CONCEPT", value)
2481def _del_concepts(self: Component):
2482 """Delete the concepts."""
2483 self.pop("CONCEPT", None)
2486concepts_property = property(_get_concepts, _set_concepts, _del_concepts)
2489def multi_string_property(name: str, doc: str):
2490 """A property for an iCalendar Property that can occur multiple times."""
2492 def fget(self: Component) -> list[str]:
2493 """Get the values of a multi-string property."""
2494 value = self.get(name, [])
2495 if not isinstance(value, list):
2496 value = [value]
2497 return value
2499 def fset(self: Component, value: list[str] | str | None) -> None:
2500 """Set the values of a multi-string property."""
2501 fdel(self)
2502 if value is None:
2503 return
2504 if not isinstance(value, list):
2505 value = [value]
2506 for value in value:
2507 self.add(name, value)
2509 def fdel(self: Component):
2510 """Delete the values of a multi-string property."""
2511 self.pop(name, None)
2513 return property(fget, fset, fdel, doc=doc)
2516refids_property = multi_string_property(
2517 "REFID",
2518 """REFID
2520Purpose:
2521 REFID acts as a key for associated iCalendar entities.
2523Conformance:
2524 Since :rfc:`9253`,
2525 this property can be specified zero or more times in any iCalendar component.
2527Description:
2528 The value of this property is free-form text that creates an
2529 identifier for associated components.
2530 All components that use the same REFID value are associated through
2531 that value and can be located or retrieved as a group.
2532 For example, all of the events in a travel itinerary
2533 would have the same REFID value, so as to be grouped together.
2535Examples:
2536 The following is an example of this property.
2538 .. code-block:: ics
2540 REFID:itinerary-2014-11-17
2542 Use a REFID to associate several VTODOs:
2544 .. code-block:: pycon
2546 >>> from icalendar import Todo
2547 >>> todo_1 = Todo.new(
2548 ... summary="turn off stove",
2549 ... refids=["travel", "alps"]
2550 ... )
2551 >>> todo_2 = Todo.new(
2552 ... summary="pack backpack",
2553 ... refids=["travel", "alps"]
2554 ... )
2555 >>> todo_1.refids == todo_2.refids
2556 True
2558.. note::
2560 List modifications do not modify the component.
2561""",
2562)
2565__all__ = [
2566 "CONCEPTS_TYPE_SETTER",
2567 "LINKS_TYPE_SETTER",
2568 "RECURRENCE_ID",
2569 "RELATED_TO_TYPE_SETTER",
2570 "attendees_property",
2571 "busy_type_property",
2572 "categories_property",
2573 "class_property",
2574 "color_property",
2575 "comments_property",
2576 "concepts_property",
2577 "conferences_property",
2578 "contacts_property",
2579 "create_single_property",
2580 "description_property",
2581 "descriptions_property",
2582 "duration_property",
2583 "exdates_property",
2584 "get_duration_property",
2585 "get_end_property",
2586 "get_start_end_duration_with_validation",
2587 "get_start_property",
2588 "images_property",
2589 "links_property",
2590 "location_property",
2591 "multi_language_text_property",
2592 "multi_string_property",
2593 "organizer_property",
2594 "priority_property",
2595 "property_del_duration",
2596 "property_doc_duration_template",
2597 "property_get_duration",
2598 "property_set_duration",
2599 "rdates_property",
2600 "refids_property",
2601 "related_to_property",
2602 "repeat_property",
2603 "rfc_7953_dtend_property",
2604 "rfc_7953_dtstart_property",
2605 "rfc_7953_duration_property",
2606 "rfc_7953_end_property",
2607 "rrules_property",
2608 "sequence_property",
2609 "set_duration_with_locking",
2610 "set_end_with_locking",
2611 "set_start_with_locking",
2612 "single_int_property",
2613 "single_utc_property",
2614 "source_property",
2615 "status_property",
2616 "summary_property",
2617 "transparency_property",
2618 "uid_property",
2619 "url_property",
2620]