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, Optional, Sequence, Union
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)
20from icalendar.prop.conference import Conference
21from icalendar.prop.image import Image
22from icalendar.timezone import tzp
23from icalendar.tools import is_date
25if TYPE_CHECKING:
26 from icalendar.cal import Component
29def _get_rdates(
30 self: Component,
31) -> list[Union[tuple[date, None], tuple[datetime, None], tuple[datetime, datetime]]]:
32 """The RDATE property defines the list of DATE-TIME values for recurring components.
34 RDATE is defined in :rfc:`5545`.
35 The return value is a list of tuples ``(start, end)``.
37 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
38 with and without timezone.
40 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
41 if the end is specified.
43 Value Type:
44 The default value type for this property is DATE-TIME.
45 The value type can be set to DATE or PERIOD.
47 Property Parameters:
48 IANA, non-standard, value data type, and time
49 zone identifier property parameters can be specified on this
50 property.
52 Conformance:
53 This property can be specified in recurring "VEVENT",
54 "VTODO", and "VJOURNAL" calendar components as well as in the
55 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
56 calendar component.
58 Description:
59 This property can appear along with the "RRULE"
60 property to define an aggregate set of repeating occurrences.
61 When they both appear in a recurring component, the recurrence
62 instances are defined by the union of occurrences defined by both
63 the "RDATE" and "RRULE".
65 The recurrence dates, if specified, are used in computing the
66 recurrence set. The recurrence set is the complete set of
67 recurrence instances for a calendar component. The recurrence set
68 is generated by considering the initial "DTSTART" property along
69 with the "RRULE", "RDATE", and "EXDATE" properties contained
70 within the recurring component. The "DTSTART" property defines
71 the first instance in the recurrence set. The "DTSTART" property
72 value SHOULD match the pattern of the recurrence rule, if
73 specified. The recurrence set generated with a "DTSTART" property
74 value that doesn't match the pattern of the rule is undefined.
75 The final recurrence set is generated by gathering all of the
76 start DATE-TIME values generated by any of the specified "RRULE"
77 and "RDATE" properties, and then excluding any start DATE-TIME
78 values specified by "EXDATE" properties. This implies that start
79 DATE-TIME values specified by "EXDATE" properties take precedence
80 over those specified by inclusion properties (i.e., "RDATE" and
81 "RRULE"). Where duplicate instances are generated by the "RRULE"
82 and "RDATE" properties, only one recurrence is considered.
83 Duplicate instances are ignored.
85 Example:
86 Below, we set one RDATE in a list and get the resulting tuple of start and end.
88 .. code-block:: pycon
90 >>> from icalendar import Event
91 >>> from datetime import datetime
92 >>> event = Event()
94 # Add a list of recurrence dates
95 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
96 >>> event.rdates
97 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
99 .. note::
101 You cannot modify the RDATE value by modifying the result.
102 Use :func:`icalendar.cal.Component.add` to add values.
104 If you want to compute recurrences, have a look at
105 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
107 """
108 result = []
109 rdates = self.get("RDATE", [])
110 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
111 for dts in rdates.dts:
112 rdate = dts.dt
113 if isinstance(rdate, tuple):
114 # we have a period as rdate
115 if isinstance(rdate[1], timedelta):
116 result.append((rdate[0], rdate[0] + rdate[1]))
117 else:
118 result.append(rdate)
119 else:
120 # we have a date/datetime
121 result.append((rdate, None))
122 return result
125rdates_property = property(_get_rdates)
128def _get_exdates(self: Component) -> list[date | datetime]:
129 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
131 EXDATE is defined in :rfc:`5545`.
133 Value Type:
134 The default value type for this property is DATE-TIME.
135 The value type can be set to DATE.
137 Property Parameters:
138 IANA, non-standard, value data type, and time
139 zone identifier property parameters can be specified on this
140 property.
142 Conformance:
143 This property can be specified in recurring "VEVENT",
144 "VTODO", and "VJOURNAL" calendar components as well as in the
145 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
146 calendar component.
148 Description:
149 The exception dates, if specified, are used in
150 computing the recurrence set. The recurrence set is the complete
151 set of recurrence instances for a calendar component. The
152 recurrence set is generated by considering the initial "DTSTART"
153 property along with the "RRULE", "RDATE", and "EXDATE" properties
154 contained within the recurring component. The "DTSTART" property
155 defines the first instance in the recurrence set. The "DTSTART"
156 property value SHOULD match the pattern of the recurrence rule, if
157 specified. The recurrence set generated with a "DTSTART" property
158 value that doesn't match the pattern of the rule is undefined.
159 The final recurrence set is generated by gathering all of the
160 start DATE-TIME values generated by any of the specified "RRULE"
161 and "RDATE" properties, and then excluding any start DATE-TIME
162 values specified by "EXDATE" properties. This implies that start
163 DATE-TIME values specified by "EXDATE" properties take precedence
164 over those specified by inclusion properties (i.e., "RDATE" and
165 "RRULE"). When duplicate instances are generated by the "RRULE"
166 and "RDATE" properties, only one recurrence is considered.
167 Duplicate instances are ignored.
169 The "EXDATE" property can be used to exclude the value specified
170 in "DTSTART". However, in such cases, the original "DTSTART" date
171 MUST still be maintained by the calendaring and scheduling system
172 because the original "DTSTART" value has inherent usage
173 dependencies by other properties such as the "RECURRENCE-ID".
175 Example:
176 Below, we add an exdate in a list and get the resulting list of exdates.
178 .. code-block:: pycon
180 >>> from icalendar import Event
181 >>> from datetime import datetime
182 >>> event = Event()
184 # Add a list of excluded dates
185 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
186 >>> event.exdates
187 [datetime.datetime(2025, 4, 28, 16, 5)]
189 .. note::
191 You cannot modify the EXDATE value by modifying the result.
192 Use :func:`icalendar.cal.Component.add` to add values.
194 If you want to compute recurrences, have a look at
195 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
197 """
198 result = []
199 exdates = self.get("EXDATE", [])
200 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
201 for dts in exdates.dts:
202 exdate = dts.dt
203 # we have a date/datetime
204 result.append(exdate)
205 return result
208exdates_property = property(_get_exdates)
211def _get_rrules(self: Component) -> list[vRecur]:
212 """RRULE defines a rule or repeating pattern for recurring components.
214 RRULE is defined in :rfc:`5545`.
215 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
217 Property Parameters:
218 IANA and non-standard property parameters can
219 be specified on this property.
221 Conformance:
222 This property can be specified in recurring "VEVENT",
223 "VTODO", and "VJOURNAL" calendar components as well as in the
224 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
225 calendar component, but it SHOULD NOT be specified more than once.
226 The recurrence set generated with multiple "RRULE" properties is
227 undefined.
229 Description:
230 The recurrence rule, if specified, is used in computing
231 the recurrence set. The recurrence set is the complete set of
232 recurrence instances for a calendar component. The recurrence set
233 is generated by considering the initial "DTSTART" property along
234 with the "RRULE", "RDATE", and "EXDATE" properties contained
235 within the recurring component. The "DTSTART" property defines
236 the first instance in the recurrence set. The "DTSTART" property
237 value SHOULD be synchronized with the recurrence rule, if
238 specified. The recurrence set generated with a "DTSTART" property
239 value not synchronized with the recurrence rule is undefined. The
240 final recurrence set is generated by gathering all of the start
241 DATE-TIME values generated by any of the specified "RRULE" and
242 "RDATE" properties, and then excluding any start DATE-TIME values
243 specified by "EXDATE" properties. This implies that start DATE-
244 TIME values specified by "EXDATE" properties take precedence over
245 those specified by inclusion properties (i.e., "RDATE" and
246 "RRULE"). Where duplicate instances are generated by the "RRULE"
247 and "RDATE" properties, only one recurrence is considered.
248 Duplicate instances are ignored.
250 The "DTSTART" property specified within the iCalendar object
251 defines the first instance of the recurrence. In most cases, a
252 "DTSTART" property of DATE-TIME value type used with a recurrence
253 rule, should be specified as a date with local time and time zone
254 reference to make sure all the recurrence instances start at the
255 same local time regardless of time zone changes.
257 If the duration of the recurring component is specified with the
258 "DTEND" or "DUE" property, then the same exact duration will apply
259 to all the members of the generated recurrence set. Else, if the
260 duration of the recurring component is specified with the
261 "DURATION" property, then the same nominal duration will apply to
262 all the members of the generated recurrence set and the exact
263 duration of each recurrence instance will depend on its specific
264 start time. For example, recurrence instances of a nominal
265 duration of one day will have an exact duration of more or less
266 than 24 hours on a day where a time zone shift occurs. The
267 duration of a specific recurrence may be modified in an exception
268 component or simply by using an "RDATE" property of PERIOD value
269 type.
271 Examples:
272 Daily for 10 occurrences:
274 .. code-block:: pycon
276 >>> from icalendar import Event
277 >>> from datetime import datetime
278 >>> from zoneinfo import ZoneInfo
279 >>> event = Event()
280 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
281 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
282 >>> print(event.to_ical())
283 BEGIN:VEVENT
284 DTSTART;TZID=America/New_York:19970902T090000
285 RRULE:FREQ=DAILY;COUNT=10
286 END:VEVENT
287 >>> event.rrules
288 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
290 Daily until December 24, 1997:
292 .. code-block:: pycon
294 >>> from icalendar import Event, vRecur
295 >>> from datetime import datetime
296 >>> from zoneinfo import ZoneInfo
297 >>> event = Event()
298 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
299 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
300 >>> print(event.to_ical())
301 BEGIN:VEVENT
302 DTSTART;TZID=America/New_York:19970902T090000
303 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
304 END:VEVENT
305 >>> event.rrules
306 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
308 .. note::
310 You cannot modify the RRULE value by modifying the result.
311 Use :func:`icalendar.cal.Component.add` to add values.
313 If you want to compute recurrences, have a look at
314 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_.
316 """ # noqa: E501
317 rrules = self.get("RRULE", [])
318 if not isinstance(rrules, list):
319 return [rrules]
320 return rrules
323rrules_property = property(_get_rrules)
326def multi_language_text_property(
327 main_prop: str, compatibility_prop: Optional[str], doc: str
328) -> property:
329 """This creates a text property.
331 This property can be defined several times with different ``LANGUAGE`` parameters.
333 Args:
334 main_prop (str): The property to set and get, such as ``NAME``
335 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
336 doc (str): The documentation string
337 """
339 def fget(self: Component) -> Optional[str]:
340 """Get the property"""
341 result = self.get(main_prop)
342 if result is None and compatibility_prop is not None:
343 result = self.get(compatibility_prop)
344 if isinstance(result, list):
345 for item in result:
346 if "LANGUAGE" not in item.params:
347 return item
348 return result
350 def fset(self: Component, value: Optional[str]):
351 """Set the property."""
352 fdel(self)
353 if value is not None:
354 self.add(main_prop, value)
356 def fdel(self: Component):
357 """Delete the property."""
358 self.pop(main_prop, None)
359 if compatibility_prop is not None:
360 self.pop(compatibility_prop, None)
362 return property(fget, fset, fdel, doc)
365def single_int_property(prop: str, default: int, doc: str) -> property:
366 """Create a property for an int value that exists only once.
368 Args:
369 prop: The name of the property
370 default: The default value
371 doc: The documentation string
372 """
374 def fget(self: Component) -> int:
375 """Get the property"""
376 try:
377 return int(self.get(prop, default))
378 except ValueError as e:
379 raise InvalidCalendar(f"{prop} must be an int") from e
381 def fset(self: Component, value: Optional[int]):
382 """Set the property."""
383 fdel(self)
384 if value is not None:
385 self.add(prop, value)
387 def fdel(self: Component):
388 """Delete the property."""
389 self.pop(prop, None)
391 return property(fget, fset, fdel, doc)
394def single_utc_property(name: str, docs: str) -> property:
395 """Create a property to access a value of datetime in UTC timezone.
397 Args:
398 name: name of the property
399 docs: documentation string
400 """
401 docs = (
402 f"""The {name} property. datetime in UTC
404 All values will be converted to a datetime in UTC.
405 """
406 + docs
407 )
409 def fget(self: Component) -> Optional[datetime]:
410 """Get the value."""
411 if name not in self:
412 return None
413 dt = self.get(name)
414 if isinstance(dt, vText):
415 # we might be in an attribute that is not typed
416 value = vDDDTypes.from_ical(dt)
417 else:
418 value = getattr(dt, "dt", dt)
419 if value is None or not isinstance(value, date):
420 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
421 return tzp.localize_utc(value)
423 def fset(self: Component, value: Optional[datetime]):
424 """Set the value"""
425 if value is None:
426 fdel(self)
427 return
428 if not isinstance(value, date):
429 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
430 fdel(self)
431 self.add(name, tzp.localize_utc(value))
433 def fdel(self: Component):
434 """Delete the property."""
435 self.pop(name, None)
437 return property(fget, fset, fdel, doc=docs)
440def single_string_property(
441 name: str, docs: str, other_name: Optional[str] = None, default: str = ""
442) -> property:
443 """Create a property to access a single string value."""
445 def fget(self: Component) -> str:
446 """Get the value."""
447 result = self.get(
448 name, None if other_name is None else self.get(other_name, None)
449 )
450 if result is None or result == []:
451 return default
452 if isinstance(result, list):
453 return result[0]
454 return result
456 def fset(self: Component, value: Optional[str]):
457 """Set the value.
459 Setting the value to None will delete it.
460 """
461 fdel(self)
462 if value is not None:
463 self.add(name, value)
465 def fdel(self: Component):
466 """Delete the property."""
467 self.pop(name, None)
468 if other_name is not None:
469 self.pop(other_name, None)
471 return property(fget, fset, fdel, doc=docs)
474color_property = single_string_property(
475 "COLOR",
476 """This property specifies a color used for displaying the component.
478 This implements :rfc:`7986` ``COLOR`` property.
480 Property Parameters:
481 IANA and non-standard property parameters can
482 be specified on this property.
484 Conformance:
485 This property can be specified once in an iCalendar
486 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
488 Description:
489 This property specifies a color that clients MAY use
490 when presenting the relevant data to a user. Typically, this
491 would appear as the "background" color of events or tasks. The
492 value is a case-insensitive color name taken from the CSS3 set of
493 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
495 Example:
496 ``"turquoise"``, ``"#ffffff"``
498 .. code-block:: pycon
500 >>> from icalendar import Todo
501 >>> todo = Todo()
502 >>> todo.color = "green"
503 >>> print(todo.to_ical())
504 BEGIN:VTODO
505 COLOR:green
506 END:VTODO
507 """,
508)
510sequence_property = single_int_property(
511 "SEQUENCE",
512 0,
513 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
515Value Type:
516 INTEGER
518Property Parameters:
519 IANA and non-standard property parameters can be specified on this property.
521Conformance:
522 The property can be specified in "VEVENT", "VTODO", or
523 "VJOURNAL" calendar component.
525Description:
526 When a calendar component is created, its sequence
527 number is 0. It is monotonically incremented by the "Organizer's"
528 CUA each time the "Organizer" makes a significant revision to the
529 calendar component.
531 The "Organizer" includes this property in an iCalendar object that
532 it sends to an "Attendee" to specify the current version of the
533 calendar component.
535 The "Attendee" includes this property in an iCalendar object that
536 it sends to the "Organizer" to specify the version of the calendar
537 component to which the "Attendee" is referring.
539 A change to the sequence number is not the mechanism that an
540 "Organizer" uses to request a response from the "Attendees". The
541 "RSVP" parameter on the "ATTENDEE" property is used by the
542 "Organizer" to indicate that a response from the "Attendees" is
543 requested.
545 Recurrence instances of a recurring component MAY have different
546 sequence numbers.
548Examples:
549 The following is an example of this property for a calendar
550 component that was just created by the "Organizer":
552 .. code-block:: pycon
554 >>> from icalendar import Event
555 >>> event = Event()
556 >>> event.sequence
557 0
559 The following is an example of this property for a calendar
560 component that has been revised 10 different times by the
561 "Organizer":
563 .. code-block:: pycon
565 >>> from icalendar import Calendar
566 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
567 >>> event = calendar.events[0]
568 >>> event.sequence
569 10
570 """, # noqa: E501
571)
574def _get_categories(component: Component) -> list[str]:
575 """Get all the categories."""
576 categories: Optional[vCategory | list[vCategory]] = component.get("CATEGORIES")
577 if isinstance(categories, list):
578 _set_categories(
579 component,
580 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
581 )
582 return _get_categories(component)
583 if categories is None:
584 categories = vCategory([])
585 component.add("CATEGORIES", categories)
586 return categories.cats
589def _set_categories(component: Component, cats: Optional[Sequence[str]]) -> None:
590 """Set the categories."""
591 if not cats and cats != []:
592 _del_categories(component)
593 return
594 component["CATEGORIES"] = categories = vCategory(cats)
595 if isinstance(cats, list):
596 cats.clear()
597 cats.extend(categories.cats)
598 categories.cats = cats
601def _del_categories(component: Component) -> None:
602 """Delete the categories."""
603 component.pop("CATEGORIES", None)
606categories_property = property(
607 _get_categories,
608 _set_categories,
609 _del_categories,
610 """This property defines the categories for a component.
612Property Parameters:
613 IANA, non-standard, and language property parameters can be specified on this
614 property.
616Conformance:
617 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
618 components.
619 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
621Description:
622 This property is used to specify categories or subtypes
623 of the calendar component. The categories are useful in searching
624 for a calendar component of a particular type and category.
625 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
626 more than one category can be specified as a COMMA-separated list
627 of categories.
629Example:
630 Below, we add the categories to an event:
632 .. code-block:: pycon
634 >>> from icalendar import Event
635 >>> event = Event()
636 >>> event.categories = ["Work", "Meeting"]
637 >>> print(event.to_ical())
638 BEGIN:VEVENT
639 CATEGORIES:Work,Meeting
640 END:VEVENT
641 >>> event.categories.append("Lecture")
642 >>> event.categories == ["Work", "Meeting", "Lecture"]
643 True
645.. note::
647 At present, we do not take the LANGUAGE parameter into account.
648""",
649)
652def _get_attendees(self: Component) -> list[vCalAddress]:
653 """Get attendees."""
654 value = self.get("ATTENDEE")
655 if value is None:
656 value = []
657 self["ATTENDEE"] = value
658 return value
659 if isinstance(value, vCalAddress):
660 return [value]
661 return value
664def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
665 """Set attendees."""
666 _del_attendees(self)
667 if value is None:
668 return
669 if not isinstance(value, list):
670 value = [value]
671 self["ATTENDEE"] = value
674def _del_attendees(self: Component):
675 """Delete all attendees."""
676 self.pop("ATTENDEE", None)
679attendees_property = property(
680 _get_attendees,
681 _set_attendees,
682 _del_attendees,
683 """ATTENDEE defines one or more "Attendees" within a calendar component.
685Conformance:
686 This property MUST be specified in an iCalendar object
687 that specifies a group-scheduled calendar entity. This property
688 MUST NOT be specified in an iCalendar object when publishing the
689 calendar information (e.g., NOT in an iCalendar object that
690 specifies the publication of a calendar user's busy time, event,
691 to-do, or journal). This property is not specified in an
692 iCalendar object that specifies only a time zone definition or
693 that defines calendar components that are not group-scheduled
694 components, but are components only on a single user's calendar.
696Description:
697 This property MUST only be specified within calendar
698 components to specify participants, non-participants, and the
699 chair of a group-scheduled calendar entity. The property is
700 specified within an "EMAIL" category of the "VALARM" calendar
701 component to specify an email address that is to receive the email
702 type of iCalendar alarm.
704Examples:
705 Add a new attendee to an existing event.
707 .. code-block:: pycon
709 >>> from icalendar import Event, vCalAddress
710 >>> event = Event()
711 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
712 >>> print(event.to_ical())
713 BEGIN:VEVENT
714 ATTENDEE:mailto:me@my-domain.com
715 END:VEVENT
717 Create an email alarm with several attendees:
719 >>> from icalendar import Alarm, vCalAddress
720 >>> alarm = Alarm.new(attendees = [
721 ... vCalAddress("mailto:me@my-domain.com"),
722 ... vCalAddress("mailto:you@my-domain.com"),
723 ... ], summary = "Email alarm")
724 >>> print(alarm.to_ical())
725 BEGIN:VALARM
726 ATTENDEE:mailto:me@my-domain.com
727 ATTENDEE:mailto:you@my-domain.com
728 SUMMARY:Email alarm
729 END:VALARM
730""",
731)
733uid_property = single_string_property(
734 "UID",
735 """UID specifies the persistent, globally unique identifier for a component.
737We recommend using :func:`uuid.uuid4` to generate new values.
739Returns:
740 The value of the UID property as a string or ``""`` if no value is set.
742Description:
743 The "UID" itself MUST be a globally unique identifier.
744 The generator of the identifier MUST guarantee that the identifier
745 is unique.
747 This is the method for correlating scheduling messages with the
748 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
749 The full range of calendar components specified by a recurrence
750 set is referenced by referring to just the "UID" property value
751 corresponding to the calendar component. The "RECURRENCE-ID"
752 property allows the reference to an individual instance within the
753 recurrence set.
755 This property is an important method for group-scheduling
756 applications to match requests with later replies, modifications,
757 or deletion requests. Calendaring and scheduling applications
758 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
759 calendar components to assure interoperability with other group-
760 scheduling applications. This identifier is created by the
761 calendar system that generates an iCalendar object.
763 Implementations MUST be able to receive and persist values of at
764 least 255 octets for this property, but they MUST NOT truncate
765 values in the middle of a UTF-8 multi-octet sequence.
767 :rfc:`7986` states that UID can be used, for
768 example, to identify duplicate calendar streams that a client may
769 have been given access to. It can be used in conjunction with the
770 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
771 to identify the most recent version of a calendar.
773Conformance:
774 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
775 and "VJOURNAL" calendar components.
776 :rfc:`7986` modifies the definition of the "UID" property to
777 allow it to be defined in an iCalendar object.
778 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
779 identifier to be specified. The value of this property can then be used
780 to refer uniquely to the "VALARM" component.
782 This property can be specified once only.
784Security:
785 :rfc:`7986` states that UID values MUST NOT include any data that
786 might identify a user, host, domain, or any other security- or
787 privacy-sensitive information. It is RECOMMENDED that calendar user
788 agents now generate "UID" values that are hex-encoded random
789 Universally Unique Identifier (UUID) values as defined in
790 Sections 4.4 and 4.5 of :rfc:`4122`.
791 You can use the :mod:`uuid` module to generate new UUIDs.
793Compatibility:
794 For Alarms, ``X-ALARMUID`` is also considered.
796Examples:
797 The following is an example of such a property value:
798 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
800 Set the UID of a calendar:
802 .. code-block:: pycon
804 >>> from icalendar import Calendar
805 >>> from uuid import uuid4
806 >>> calendar = Calendar()
807 >>> calendar.uid = uuid4()
808 >>> print(calendar.to_ical())
809 BEGIN:VCALENDAR
810 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
811 END:VCALENDAR
813""",
814)
816summary_property = multi_language_text_property(
817 "SUMMARY",
818 None,
819 """SUMMARY defines a short summary or subject for the calendar component.
821Property Parameters:
822 IANA, non-standard, alternate text
823 representation, and language property parameters can be specified
824 on this property.
826Conformance:
827 The property can be specified in "VEVENT", "VTODO",
828 "VJOURNAL", or "VALARM" calendar components.
830Description:
831 This property is used in the "VEVENT", "VTODO", and
832 "VJOURNAL" calendar components to capture a short, one-line
833 summary about the activity or journal entry.
835 This property is used in the "VALARM" calendar component to
836 capture the subject of an EMAIL category of alarm.
838Examples:
839 The following is an example of this property:
841 .. code-block:: pycon
843 SUMMARY:Department Party
844""",
845)
847description_property = multi_language_text_property(
848 "DESCRIPTION",
849 None,
850 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
852Property Parameters:
853 IANA, non-standard, alternate text
854 representation, and language property parameters can be specified
855 on this property.
857Conformance:
858 The property can be specified in the "VEVENT", "VTODO",
859 "VJOURNAL", or "VALARM" calendar components. The property can be
860 specified multiple times only within a "VJOURNAL" calendar
861 component.
863Description:
864 This property is used in the "VEVENT" and "VTODO" to
865 capture lengthy textual descriptions associated with the activity.
867 This property is used in the "VALARM" calendar component to
868 capture the display text for a DISPLAY category of alarm, and to
869 capture the body text for an EMAIL category of alarm.
871Examples:
872 The following is an example of this property with formatted
873 line breaks in the property value:
875 .. code-block:: pycon
877 DESCRIPTION:Meeting to provide technical review for "Phoenix"
878 design.\\nHappy Face Conference Room. Phoenix design team
879 MUST attend this meeting.\\nRSVP to team leader.
881 """, # noqa: E501
882)
885def create_single_property(
886 prop: str,
887 value_attr: Optional[str],
888 value_type: tuple[type],
889 type_def: type,
890 doc: str,
891 vProp: type = vDDDTypes, # noqa: N803
892):
893 """Create a single property getter and setter.
895 :param prop: The name of the property.
896 :param value_attr: The name of the attribute to get the value from.
897 :param value_type: The type of the value.
898 :param type_def: The type of the property.
899 :param doc: The docstring of the property.
900 :param vProp: The type of the property from :mod:`icalendar.prop`.
901 """
903 def p_get(self: Component):
904 default = object()
905 result = self.get(prop, default)
906 if result is default:
907 return None
908 if isinstance(result, list):
909 raise InvalidCalendar(f"Multiple {prop} defined.")
910 value = result if value_attr is None else getattr(result, value_attr, result)
911 if not isinstance(value, value_type):
912 raise InvalidCalendar(
913 f"{prop} must be either a "
914 f"{' or '.join(t.__name__ for t in value_type)},"
915 f" not {value}."
916 )
917 return value
919 def p_set(self: Component, value) -> None:
920 if value is None:
921 p_del(self)
922 return
923 if not isinstance(value, value_type):
924 raise TypeError(
925 f"Use {' or '.join(t.__name__ for t in value_type)}, "
926 f"not {type(value).__name__}."
927 )
928 self[prop] = vProp(value)
929 if prop in self.exclusive:
930 for other_prop in self.exclusive:
931 if other_prop != prop:
932 self.pop(other_prop, None)
934 p_set.__annotations__["value"] = p_get.__annotations__["return"] = Optional[
935 type_def
936 ]
938 def p_del(self: Component):
939 self.pop(prop)
941 p_doc = f"""The {prop} property.
943 {doc}
945 Accepted values: {", ".join(t.__name__ for t in value_type)}.
946 If the attribute has invalid values, we raise InvalidCalendar.
947 If the value is absent, we return None.
948 You can also delete the value with del or by setting it to None.
949 """
950 return property(p_get, p_set, p_del, p_doc)
953X_MOZ_SNOOZE_TIME_property = single_utc_property(
954 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
955)
956X_MOZ_LASTACK_property = single_utc_property(
957 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
958)
961def property_get_duration(self: Component) -> Optional[timedelta]:
962 """Getter for property DURATION."""
963 default = object()
964 duration = self.get("duration", default)
965 if isinstance(duration, vDDDTypes):
966 return duration.dt
967 if isinstance(duration, vDuration):
968 return duration.td
969 if duration is not default and not isinstance(duration, timedelta):
970 raise InvalidCalendar(
971 f"DURATION must be a timedelta, not {type(duration).__name__}."
972 )
973 return None
976def property_set_duration(self: Component, value: Optional[timedelta]):
977 """Setter for property DURATION."""
978 if value is None:
979 self.pop("duration", None)
980 return
981 if not isinstance(value, timedelta):
982 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
983 self["duration"] = vDuration(value)
984 self.pop("DTEND")
985 self.pop("DUE")
988def property_del_duration(self: Component):
989 """Delete property DURATION."""
990 self.pop("DURATION")
993property_doc_duration_template = """The DURATION property.
995The "DTSTART" property for a "{component}" specifies the inclusive
996start of the {component}.
997The "DURATION" property in conjunction with the DTSTART property
998for a "{component}" calendar component specifies the non-inclusive end
999of the event.
1001If you would like to calculate the duration of a {component}, do not use this.
1002Instead use the duration property (lower case).
1003"""
1006def duration_property(component: str) -> property:
1007 """Return the duration property."""
1008 return property(
1009 property_get_duration,
1010 property_set_duration,
1011 property_del_duration,
1012 property_doc_duration_template.format(component=component),
1013 )
1016def multi_text_property(name: str, docs: str) -> property:
1017 """Get a property that can occur several times and is text.
1019 Examples: Journal.descriptions, Event.comments
1020 """
1022 def fget(self: Component) -> list[str]:
1023 """Get the values."""
1024 descriptions = self.get(name)
1025 if descriptions is None:
1026 return []
1027 if not isinstance(descriptions, SEQUENCE_TYPES):
1028 return [descriptions]
1029 return descriptions
1031 def fset(self: Component, values: Optional[str | Sequence[str]]):
1032 """Set the values."""
1033 fdel(self)
1034 if values is None:
1035 return
1036 if isinstance(values, str):
1037 self.add(name, values)
1038 else:
1039 for description in values:
1040 self.add(name, description)
1042 def fdel(self: Component):
1043 """Delete the values."""
1044 self.pop(name)
1046 return property(fget, fset, fdel, docs)
1049descriptions_property = multi_text_property(
1050 "DESCRIPTION",
1051 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1053Property Parameters:
1054 IANA, non-standard, alternate text
1055 representation, and language property parameters can be specified
1056 on this property.
1058Conformance:
1059 The property can be
1060 specified multiple times only within a "VJOURNAL" calendar component.
1062Description:
1063 This property is used in the "VJOURNAL" calendar component to
1064 capture one or more textual journal entries.
1066Examples:
1067 The following is an example of this property with formatted
1068 line breaks in the property value:
1070 .. code-block:: pycon
1072 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1073 design.\\nHappy Face Conference Room. Phoenix design team
1074 MUST attend this meeting.\\nRSVP to team leader.
1076""", # noqa: E501
1077)
1079comments_property = multi_text_property(
1080 "COMMENT",
1081 """COMMENT is used to specify a comment to the calendar user.
1083Purpose:
1084 This property specifies non-processing information intended
1085 to provide a comment to the calendar user.
1087Conformance:
1088 In :rfc:`5545`, this property can be specified multiple times in
1089 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1090 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1091 In :rfc:`7953`, this property can be specified multiple times in
1092 "VAVAILABILITY" and "VAVAILABLE".
1094Property Parameters:
1095 IANA, non-standard, alternate text
1096 representation, and language property parameters can be specified
1097 on this property.
1099""",
1100)
1103def _get_organizer(self: Component) -> Optional[vCalAddress]:
1104 """ORGANIZER defines the organizer for a calendar component.
1106 Property Parameters:
1107 IANA, non-standard, language, common name,
1108 directory entry reference, and sent-by property parameters can be
1109 specified on this property.
1111 Conformance:
1112 This property MUST be specified in an iCalendar object
1113 that specifies a group-scheduled calendar entity. This property
1114 MUST be specified in an iCalendar object that specifies the
1115 publication of a calendar user's busy time. This property MUST
1116 NOT be specified in an iCalendar object that specifies only a time
1117 zone definition or that defines calendar components that are not
1118 group-scheduled components, but are components only on a single
1119 user's calendar.
1121 Description:
1122 This property is specified within the "VEVENT",
1123 "VTODO", and "VJOURNAL" calendar components to specify the
1124 organizer of a group-scheduled calendar entity. The property is
1125 specified within the "VFREEBUSY" calendar component to specify the
1126 calendar user requesting the free or busy time. When publishing a
1127 "VFREEBUSY" calendar component, the property is used to specify
1128 the calendar that the published busy time came from.
1130 The property has the property parameters "CN", for specifying the
1131 common or display name associated with the "Organizer", "DIR", for
1132 specifying a pointer to the directory information associated with
1133 the "Organizer", "SENT-BY", for specifying another calendar user
1134 that is acting on behalf of the "Organizer". The non-standard
1135 parameters may also be specified on this property. If the
1136 "LANGUAGE" property parameter is specified, the identified
1137 language applies to the "CN" parameter value.
1138 """
1139 return self.get("ORGANIZER")
1142def _set_organizer(self: Component, value: Optional[vCalAddress | str]):
1143 """Set the value."""
1144 _del_organizer(self)
1145 if value is not None:
1146 self.add("ORGANIZER", value)
1149def _del_organizer(self: Component):
1150 """Delete the value."""
1151 self.pop("ORGANIZER")
1154organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1157def single_string_enum_property(
1158 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1159) -> property:
1160 """Create a property to access a single string value and convert it to an enum."""
1161 prop = single_string_property(name, docs, default=default)
1163 def fget(self: Component) -> StrEnum:
1164 """Get the value."""
1165 value = prop.fget(self)
1166 if value == default:
1167 return default
1168 return enum(str(value))
1170 def fset(self: Component, value: str | StrEnum | None) -> None:
1171 """Set the value."""
1172 if value == "":
1173 value = None
1174 prop.fset(self, value)
1176 return property(fget, fset, prop.fdel, doc=docs)
1179busy_type_property = single_string_enum_property(
1180 "BUSYTYPE",
1181 BUSYTYPE,
1182 BUSYTYPE.BUSY_UNAVAILABLE,
1183 """BUSYTYPE specifies the default busy time type.
1185Returns:
1186 :class:`icalendar.enums.BUSYTYPE`
1188Description:
1189 This property is used to specify the default busy time
1190 type. The values correspond to those used by the FBTYPE"
1191 parameter used on a "FREEBUSY" property, with the exception that
1192 the "FREE" value is not used in this property. If not specified
1193 on a component that allows this property, the default is "BUSY-
1194 UNAVAILABLE".
1195""",
1196)
1198priority_property = single_int_property(
1199 "PRIORITY",
1200 0,
1201 """
1203Conformance:
1204 This property can be specified in "VEVENT" and "VTODO" calendar components
1205 according to :rfc:`5545`.
1206 :rfc:`7953` adds this property to "VAVAILABILITY".
1208Description:
1209 This priority is specified as an integer in the range 0
1210 to 9. A value of 0 specifies an undefined priority. A value of 1
1211 is the highest priority. A value of 2 is the second highest
1212 priority. Subsequent numbers specify a decreasing ordinal
1213 priority. A value of 9 is the lowest priority.
1215 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1216 "LOW" is mapped into this property such that a property value in
1217 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1218 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1219 is "LOW" priority.
1221 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1222 "C3" is mapped into this property such that a property value of 1
1223 specifies "A1", a property value of 2 specifies "A2", a property
1224 value of 3 specifies "A3", and so forth up to a property value of
1225 9 specifies "C3".
1227 Other integer values are reserved for future use.
1229 Within a "VEVENT" calendar component, this property specifies a
1230 priority for the event. This property may be useful when more
1231 than one event is scheduled for a given time period.
1233 Within a "VTODO" calendar component, this property specified a
1234 priority for the to-do. This property is useful in prioritizing
1235 multiple action items for a given time period.
1236""",
1237)
1239class_property = single_string_enum_property(
1240 "CLASS",
1241 CLASS,
1242 CLASS.PUBLIC,
1243 """CLASS specifies the class of the calendar component.
1245Returns:
1246 :class:`icalendar.enums.CLASS`
1248Description:
1249 An access classification is only one component of the
1250 general security system within a calendar application. It
1251 provides a method of capturing the scope of the access the
1252 calendar owner intends for information within an individual
1253 calendar entry. The access classification of an individual
1254 iCalendar component is useful when measured along with the other
1255 security components of a calendar system (e.g., calendar user
1256 authentication, authorization, access rights, access role, etc.).
1257 Hence, the semantics of the individual access classifications
1258 cannot be completely defined by this memo alone. Additionally,
1259 due to the "blind" nature of most exchange processes using this
1260 memo, these access classifications cannot serve as an enforcement
1261 statement for a system receiving an iCalendar object. Rather,
1262 they provide a method for capturing the intention of the calendar
1263 owner for the access to the calendar component. If not specified
1264 in a component that allows this property, the default value is
1265 PUBLIC. Applications MUST treat x-name and iana-token values they
1266 don't recognize the same way as they would the PRIVATE value.
1267""",
1268)
1270transparency_property = single_string_enum_property(
1271 "TRANSP",
1272 TRANSP,
1273 TRANSP.OPAQUE,
1274 """TRANSP defines whether or not an event is transparent to busy time searches.
1276Returns:
1277 :class:`icalendar.enums.TRANSP`
1279Description:
1280 Time Transparency is the characteristic of an event
1281 that determines whether it appears to consume time on a calendar.
1282 Events that consume actual time for the individual or resource
1283 associated with the calendar SHOULD be recorded as OPAQUE,
1284 allowing them to be detected by free/busy time searches. Other
1285 events, which do not take up the individual's (or resource's) time
1286 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1287 busy time searches.
1288""",
1289)
1290status_property = single_string_enum_property(
1291 "STATUS",
1292 STATUS,
1293 "",
1294 """STATUS defines the overall status or confirmation for the calendar component.
1296Returns:
1297 :class:`icalendar.enums.STATUS`
1299The default value is ``""``.
1301Description:
1302 In a group-scheduled calendar component, the property
1303 is used by the "Organizer" to provide a confirmation of the event
1304 to the "Attendees". For example in a "VEVENT" calendar component,
1305 the "Organizer" can indicate that a meeting is tentative,
1306 confirmed, or cancelled. In a "VTODO" calendar component, the
1307 "Organizer" can indicate that an action item needs action, is
1308 completed, is in process or being worked on, or has been
1309 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1310 can indicate that a journal entry is draft, final, or has been
1311 cancelled or removed.
1312""",
1313)
1315url_property = single_string_property(
1316 "URL",
1317 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1319Description:
1320 This property may be used in a calendar component to
1321 convey a location where a more dynamic rendition of the calendar
1322 information associated with the calendar component can be found.
1323 This memo does not attempt to standardize the form of the URI, nor
1324 the format of the resource pointed to by the property value. If
1325 the URL property and Content-Location MIME header are both
1326 specified, they MUST point to the same resource.
1328Conformance:
1329 This property can be specified once in the "VEVENT",
1330 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components.
1331 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR".
1333Example:
1334 The following is an example of this property:
1336 .. code-block:: text
1338 URL:http://example.com/pub/calendars/jsmith/mytime.ics
1340""",
1341)
1343source_property = single_string_property(
1344 "SOURCE",
1345 """A URI from where calendar data can be refreshed.
1347Description:
1348 This property identifies a location where a client can
1349 retrieve updated data for the calendar. Clients SHOULD honor any
1350 specified "REFRESH-INTERVAL" value when periodically retrieving
1351 data. Note that this property differs from the "URL" property in
1352 that "URL" is meant to provide an alternative representation of
1353 the calendar data rather than the original location of the data.
1355Conformance:
1356 This property can be specified once in an iCalendar object.
1358Example:
1359 The following is an example of this property:
1361 .. code-block:: text
1363 SOURCE;VALUE=URI:https://example.com/holidays.ics
1365""",
1366)
1368location_property = multi_language_text_property(
1369 "LOCATION",
1370 None,
1371 """The intended venue for the activity defined by a calendar component.
1373Property Parameters:
1374 IANA, non-standard, alternate text
1375 representation, and language property parameters can be specified
1376 on this property.
1378Conformance:
1379 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1380 calendar component.
1381 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1383Description:
1384 Specific venues such as conference or meeting rooms may
1385 be explicitly specified using this property. An alternate
1386 representation may be specified that is a URI that points to
1387 directory information with more structured specification of the
1388 location. For example, the alternate representation may specify
1389 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1390 CID URL :rfc:`2392` pointing to a MIME body part containing a
1391 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1393""",
1394)
1396contacts_property = multi_text_property(
1397 "CONTACT",
1398 """Contact information associated with the calendar component.
1400Purpose:
1401 This property is used to represent contact information or
1402 alternately a reference to contact information associated with the
1403 calendar component.
1405Property Parameters:
1406 IANA, non-standard, alternate text
1407 representation, and language property parameters can be specified
1408 on this property.
1410Conformance:
1411 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1412 "VJOURNAL", or "VFREEBUSY" calendar component.
1413 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1414 amd "VAVAILABLE" calendar component.
1416Description:
1417 The property value consists of textual contact
1418 information. An alternative representation for the property value
1419 can also be specified that refers to a URI pointing to an
1420 alternate form, such as a vCard :rfc:`2426`, for the contact
1421 information.
1423Example:
1424 The following is an example of this property referencing
1425 textual contact information:
1427 .. code-block:: text
1429 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1431 The following is an example of this property with an alternate
1432 representation of an LDAP URI to a directory entry containing the
1433 contact information:
1435 .. code-block:: text
1437 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1438 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1439 +1-919-555-1234
1441 The following is an example of this property with an alternate
1442 representation of a MIME body part containing the contact
1443 information, such as a vCard :rfc:`2426` embedded in a text/
1444 directory media type :rfc:`2425`:
1446 .. code-block:: text
1448 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1449 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1451 The following is an example of this property referencing a network
1452 resource, such as a vCard :rfc:`2426` object containing the contact
1453 information:
1455 .. code-block:: text
1457 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1458 Dolittle\\, ABC Industries\\, +1-919-555-1234
1459""",
1460)
1463def timezone_datetime_property(name: str, docs: str):
1464 """Create a property to access the values with a proper timezone."""
1466 return single_utc_property(name, docs)
1469rfc_7953_dtstart_property = timezone_datetime_property(
1470 "DTSTART",
1471 """Start of the component.
1473 This is almost the same as :attr:`Event.DTSTART` with one exception:
1474 The values MUST have a timezone and DATE is not allowed.
1476 Description:
1477 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1478 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1479 "DATE-TIME" values specified as either the date with UTC time or
1480 the date with local time and a time zone reference.
1482 """,
1483)
1485rfc_7953_dtend_property = timezone_datetime_property(
1486 "DTEND",
1487 """Start of the component.
1489 This is almost the same as :attr:`Event.DTEND` with one exception:
1490 The values MUST have a timezone and DATE is not allowed.
1492 Description:
1493 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1494 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1495 "DATE-TIME" values specified as either the date with UTC time or
1496 the date with local time and a time zone reference.
1497 """,
1498)
1501@property
1502def rfc_7953_duration_property(self) -> Optional[timedelta]:
1503 """Compute the duration of this component.
1505 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1506 Otherwise, the duration is calculated from :attr:`DTSTART` and
1507 :attr:`DTEND`/:attr:`DURATION`.
1509 This is in accordance with :rfc:`7953`:
1510 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1511 """
1512 duration = self.DURATION
1513 if duration:
1514 return duration
1515 end = self.DTEND
1516 if end is None:
1517 return None
1518 start = self.DTSTART
1519 if start is None:
1520 raise IncompleteComponent("Cannot compute duration without start.")
1521 return end - start
1524@property
1525def rfc_7953_end_property(self) -> Optional[timedelta]:
1526 """Compute the duration of this component.
1528 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1529 Otherwise, the duration is calculated from :attr:`DTSTART` and
1530 :attr:`DTEND`/:attr:`DURATION`.
1532 This is in accordance with :rfc:`7953`:
1533 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1534 """
1535 duration = self.DURATION
1536 if duration:
1537 start = self.DTSTART
1538 if start is None:
1539 raise IncompleteComponent("Cannot compute end without start.")
1540 return start + duration
1541 end = self.DTEND
1542 if end is None:
1543 return None
1544 return end
1547@rfc_7953_end_property.setter
1548def rfc_7953_end_property(self, value: datetime):
1549 self.DTEND = value
1552@rfc_7953_end_property.deleter
1553def rfc_7953_end_property(self):
1554 del self.DTEND
1557def get_start_end_duration_with_validation(
1558 component: Component,
1559 start_property: str,
1560 end_property: str,
1561 component_name: str,
1562) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]:
1563 """
1564 Validate the component and return start, end, and duration.
1566 This tests validity according to :rfc:`5545` rules
1567 for ``Event`` and ``Todo`` components.
1569 Args:
1570 component: The component to validate, either ``Event`` or ``Todo``.
1571 start_property: The start property name, ``DTSTART``.
1572 end_property: The end property name, either ``DTEND`` for ``Event`` or
1573 ``DUE`` for ``Todo``.
1574 component_name: The component name for error messages,
1575 either ``VEVENT`` or ``VTODO``.
1577 Returns:
1578 tuple: (start, end, duration) values from the component.
1580 Raises:
1581 InvalidCalendar: If the component violates RFC 5545 constraints.
1583 """
1584 start = getattr(component, start_property, None)
1585 end = getattr(component, end_property, None)
1586 duration = component.DURATION
1588 # RFC 5545: Only one of end property and DURATION may be present
1589 if duration is not None and end is not None:
1590 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1591 msg = (
1592 f"Only one of {end_name} and DURATION "
1593 f"may be in a {component_name}, not both."
1594 )
1595 raise InvalidCalendar(msg)
1597 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks
1598 if (
1599 start is not None
1600 and is_date(start)
1601 and duration is not None
1602 and duration.seconds != 0
1603 ):
1604 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1605 raise InvalidCalendar(msg)
1607 # RFC 5545: DTSTART and end property must be of the same type
1608 if start is not None and end is not None and is_date(start) != is_date(end):
1609 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1610 msg = (
1611 f"DTSTART and {end_name} must be of the same type, either date or datetime."
1612 )
1613 raise InvalidCalendar(msg)
1615 return start, end, duration
1618def get_start_property(component: Component) -> date | datetime:
1619 """
1620 Get the start property with validation.
1622 Args:
1623 component: The component from which to get its start property.
1625 Returns:
1626 The ``DTSTART`` value.
1628 Raises:
1629 IncompleteComponent: If no ``DTSTART`` is present.
1631 """
1632 # Trigger validation by calling _get_start_end_duration
1633 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1634 if start is None:
1635 msg = "No DTSTART given."
1636 raise IncompleteComponent(msg)
1637 return start
1640def get_end_property(component: Component, end_property: str) -> date | datetime:
1641 """
1642 Get the end property with fallback logic for ``Event`` and ``Todo`` components.
1644 Args:
1645 component: The component to get end from
1646 end_property: The end property name, either ``DTEND`` for ``Event`` or
1647 ``DUE`` for ``Todo``.
1649 Returns:
1650 The computed end value.
1652 Raises:
1653 IncompleteComponent: If the provided information is incomplete
1654 to compute the end property.
1656 """
1657 # Trigger validation by calling _get_start_end_duration
1658 start, end, duration = component._get_start_end_duration() # noqa: SLF001
1660 if end is None and duration is None:
1661 if start is None:
1662 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1663 msg = f"No {end_name} or DURATION+DTSTART given."
1664 raise IncompleteComponent(msg)
1666 # Default behavior differs for Event vs Todo:
1667 # Event: date gets +1 day, datetime gets same time
1668 # Todo: both date and datetime get same time (issue #898)
1669 if end_property == "DTEND" and is_date(start):
1670 return start + timedelta(days=1)
1671 return start
1673 if duration is not None:
1674 if start is not None:
1675 return start + duration
1676 end_name = "DTEND" if end_property == "DTEND" else "DUE"
1677 msg = f"No {end_name} or DURATION+DTSTART given."
1678 raise IncompleteComponent(msg)
1680 return end
1683def get_duration_property(component: Component) -> timedelta:
1684 """
1685 Get the duration property with fallback calculation from start and end.
1687 Args:
1688 component: The component from which to get its duration property.
1690 Returns:
1691 The duration as a timedelta.
1693 """
1694 # First check if DURATION property is explicitly set
1695 if "DURATION" in component:
1696 return component["DURATION"].dt
1698 # Fall back to calculated duration from start and end
1699 return component.end - component.start
1702def set_duration_with_locking(
1703 component: Component,
1704 duration: timedelta | None,
1705 locked: Literal["start", "end"],
1706 end_property: str,
1707) -> None:
1708 """
1709 Set the duration with explicit locking behavior for ``Event`` and ``Todo``.
1711 Args:
1712 component: The component to modify, either ``Event`` or ``Todo``.
1713 duration: The duration to set, or ``None`` to convert to ``DURATION`` property.
1714 locked: Which property to keep unchanged, either ``start`` or ``end``.
1715 end_property: The end property name, either ``DTEND`` for ``Event`` or
1716 ``DUE`` for ``Todo``.
1718 """
1719 # Convert to DURATION property if duration is None
1720 if duration is None:
1721 if "DURATION" in component:
1722 return # Already has DURATION property
1723 current_duration = component.duration
1724 component.DURATION = current_duration
1725 return
1727 if not isinstance(duration, timedelta):
1728 msg = f"Use timedelta, not {type(duration).__name__}."
1729 raise TypeError(msg)
1731 # Validate date/duration compatibility
1732 start = component.DTSTART
1733 if start is not None and is_date(start) and duration.seconds != 0:
1734 msg = "When DTSTART is a date, DURATION must be of days or weeks."
1735 raise InvalidCalendar(msg)
1737 if locked == "start":
1738 # Keep start locked, adjust end
1739 if start is None:
1740 msg = "Cannot set duration without DTSTART. Set start time first."
1741 raise IncompleteComponent(msg)
1742 component.pop(end_property, None) # Remove end property
1743 component.DURATION = duration
1744 elif locked == "end":
1745 # Keep end locked, adjust start
1746 current_end = component.end
1747 component.DTSTART = current_end - duration
1748 component.pop(end_property, None) # Remove end property
1749 component.DURATION = duration
1750 else:
1751 msg = f"locked must be 'start' or 'end', not {locked!r}"
1752 raise ValueError(msg)
1755def set_start_with_locking(
1756 component: Component,
1757 start: date | datetime,
1758 locked: Literal["duration", "end"] | None,
1759 end_property: str,
1760) -> None:
1761 """
1762 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components.
1764 Args:
1765 component: The component to modify, either ``Event`` or ``Todo``.
1766 start: The start time to set.
1767 locked: Which property to keep unchanged, either ``duration``, ``end``,
1768 or ``None`` for auto-detect.
1769 end_property: The end property name, either ``DTEND`` for ``Event`` or
1770 ``DUE`` for ``Todo``.
1772 """
1773 if locked is None:
1774 # Auto-detect based on existing properties
1775 if "DURATION" in component:
1776 locked = "duration"
1777 elif end_property in component:
1778 locked = "end"
1779 else:
1780 # Default to duration if no existing properties
1781 locked = "duration"
1783 if locked == "duration":
1784 # Keep duration locked, adjust end
1785 current_duration = (
1786 component.duration
1787 if "DURATION" in component or end_property in component
1788 else None
1789 )
1790 component.DTSTART = start
1791 if current_duration is not None:
1792 component.pop(end_property, None) # Remove end property
1793 component.DURATION = current_duration
1794 elif locked == "end":
1795 # Keep end locked, adjust duration
1796 current_end = component.end
1797 component.DTSTART = start
1798 component.pop("DURATION", None) # Remove duration property
1799 setattr(component, end_property, current_end)
1800 else:
1801 msg = f"locked must be 'duration', 'end', or None, not {locked!r}"
1802 raise ValueError(msg)
1805def set_end_with_locking(
1806 component: Component,
1807 end: date | datetime,
1808 locked: Literal["start", "duration"],
1809 end_property: str,
1810) -> None:
1811 """
1812 Set the end with explicit locking behavior for Event and Todo components.
1814 Args:
1815 component: The component to modify, either ``Event`` or ``Todo``.
1816 end: The end time to set.
1817 locked: Which property to keep unchanged, either ``start`` or ``duration``.
1818 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE``
1819 for ``Todo``.
1821 """
1822 if locked == "start":
1823 # Keep start locked, adjust duration
1824 component.pop("DURATION", None) # Remove duration property
1825 setattr(component, end_property, end)
1826 elif locked == "duration":
1827 # Keep duration locked, adjust start
1828 current_duration = component.duration
1829 component.DTSTART = end - current_duration
1830 component.pop(end_property, None) # Remove end property
1831 component.DURATION = current_duration
1832 else:
1833 msg = f"locked must be 'start' or 'duration', not {locked!r}"
1834 raise ValueError(msg)
1837def _get_images(self: Component) -> list[Image]:
1838 """IMAGE specifies an image associated with the calendar or a calendar component.
1840 Description:
1841 This property specifies an image for an iCalendar
1842 object or a calendar component via a URI or directly with inline
1843 data that can be used by calendar user agents when presenting the
1844 calendar data to a user. Multiple properties MAY be used to
1845 specify alternative sets of images with, for example, varying
1846 media subtypes, resolutions, or sizes. When multiple properties
1847 are present, calendar user agents SHOULD display only one of them,
1848 picking one that provides the most appropriate image quality, or
1849 display none. The "DISPLAY" parameter is used to indicate the
1850 intended display mode for the image. The "ALTREP" parameter,
1851 defined in :rfc:`5545`, can be used to provide a "clickable" image
1852 where the URI in the parameter value can be "launched" by a click
1853 on the image in the calendar user agent.
1855 Conformance:
1856 This property can be specified multiple times in an
1857 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar
1858 components.
1860 .. note::
1862 At the present moment, this property is read-only. If you require a setter,
1863 please open an issue or a pull request.
1864 """
1865 images = self.get("IMAGE", [])
1866 if not isinstance(images, SEQUENCE_TYPES):
1867 images = [images]
1868 return [Image.from_property_value(img) for img in images]
1871images_property = property(_get_images)
1874def _get_conferences(self: Component) -> list[Conference]:
1875 """Return the CONFERENCE properties as a list.
1877 Purpose:
1878 This property specifies information for accessing a conferencing system.
1880 Conformance:
1881 This property can be specified multiple times in a
1882 "VEVENT" or "VTODO" calendar component.
1884 Description:
1885 This property specifies information for accessing a
1886 conferencing system for attendees of a meeting or task. This
1887 might be for a telephone-based conference number dial-in with
1888 access codes included (such as a tel: URI :rfc:`3966` or a sip: or
1889 sips: URI :rfc:`3261`), for a web-based video chat (such as an http:
1890 or https: URI :rfc:`7230`), or for an instant messaging group chat
1891 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a
1892 conferencing system is not available, a data: URI :rfc:`2397`
1893 containing a text description can be used.
1895 A conference system can be a bidirectional communication channel
1896 or a uni-directional "broadcast feed".
1898 The "FEATURE" property parameter is used to describe the key
1899 capabilities of the conference system to allow a client to choose
1900 the ones that give the required level of interaction from a set of
1901 multiple properties.
1903 The "LABEL" property parameter is used to convey additional
1904 details on the use of the URI. For example, the URIs or access
1905 codes for the moderator and attendee of a teleconference system
1906 could be different, and the "LABEL" property parameter could be
1907 used to "tag" each "CONFERENCE" property to indicate which is
1908 which.
1910 The "LANGUAGE" property parameter can be used to specify the
1911 language used for text values used with this property (as per
1912 Section 3.2.10 of :rfc:`5545`).
1914 Example:
1915 The following are examples of this property:
1917 .. code-block:: text
1919 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR;
1920 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321
1921 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1922 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123
1923 CONFERENCE;VALUE=URI;FEATURE=PHONE;
1924 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123
1925 CONFERENCE;VALUE=URI;FEATURE=CHAT;
1926 LABEL=Chat room:xmpp:chat-123@conference.example.com
1927 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO;
1928 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456
1930 Get all conferences:
1932 .. code-block:: pycon
1934 >>> from icalendar import Event
1935 >>> event = Event()
1936 >>> event.conferences
1937 []
1939 Set a conference:
1941 .. code-block:: pycon
1943 >>> from icalendar import Event, Conference
1944 >>> event = Event()
1945 >>> event.conferences = [
1946 ... Conference(
1947 ... "tel:+1-412-555-0123,,,654321",
1948 ... feature="PHONE,MODERATOR",
1949 ... label="Moderator dial-in",
1950 ... language="EN",
1951 ... )
1952 ... ]
1953 >>> print(event.to_ical())
1954 BEGIN:VEVENT
1955 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN:t
1956 el:+1-412-555-0123,,,654321
1957 END:VEVENT
1959 """
1960 conferences = self.get("CONFERENCE", [])
1961 if not isinstance(conferences, SEQUENCE_TYPES):
1962 conferences = [conferences]
1963 return [Conference.from_uri(conference) for conference in conferences]
1966def _set_conferences(self: Component, conferences: list[Conference] | None):
1967 """Set the conferences."""
1968 _del_conferences(self)
1969 for conference in conferences or []:
1970 self.add("CONFERENCE", conference.to_uri())
1973def _del_conferences(self: Component):
1974 """Delete all conferences."""
1975 self.pop("CONFERENCE")
1978conferences_property = property(_get_conferences, _set_conferences, _del_conferences)
1980__all__ = [
1981 "attendees_property",
1982 "busy_type_property",
1983 "categories_property",
1984 "class_property",
1985 "color_property",
1986 "comments_property",
1987 "conferences_property",
1988 "contacts_property",
1989 "create_single_property",
1990 "description_property",
1991 "descriptions_property",
1992 "duration_property",
1993 "exdates_property",
1994 "get_duration_property",
1995 "get_end_property",
1996 "get_start_end_duration_with_validation",
1997 "get_start_property",
1998 "images_property",
1999 "location_property",
2000 "multi_language_text_property",
2001 "organizer_property",
2002 "priority_property",
2003 "property_del_duration",
2004 "property_doc_duration_template",
2005 "property_get_duration",
2006 "property_set_duration",
2007 "rdates_property",
2008 "rfc_7953_dtend_property",
2009 "rfc_7953_dtstart_property",
2010 "rfc_7953_duration_property",
2011 "rfc_7953_end_property",
2012 "rrules_property",
2013 "sequence_property",
2014 "set_duration_with_locking",
2015 "set_end_with_locking",
2016 "set_start_with_locking",
2017 "single_int_property",
2018 "single_utc_property",
2019 "source_property",
2020 "status_property",
2021 "summary_property",
2022 "transparency_property",
2023 "uid_property",
2024 "url_property",
2025]