Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 33%
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, 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 vCalAddress, vCategory, vDDDTypes, vDuration, vRecur, vText
13from icalendar.timezone import tzp
14from icalendar.tools import is_date
16if TYPE_CHECKING:
17 from icalendar.cal import Component
20def _get_rdates(
21 self: Component,
22) -> list[Union[tuple[date, None], tuple[datetime, None], tuple[datetime, datetime]]]:
23 """The RDATE property defines the list of DATE-TIME values for recurring components.
25 RDATE is defined in :rfc:`5545`.
26 The return value is a list of tuples ``(start, end)``.
28 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`,
29 with and without timezone.
31 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime`
32 if the end is specified.
34 Value Type:
35 The default value type for this property is DATE-TIME.
36 The value type can be set to DATE or PERIOD.
38 Property Parameters:
39 IANA, non-standard, value data type, and time
40 zone identifier property parameters can be specified on this
41 property.
43 Conformance:
44 This property can be specified in recurring "VEVENT",
45 "VTODO", and "VJOURNAL" calendar components as well as in the
46 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
47 calendar component.
49 Description:
50 This property can appear along with the "RRULE"
51 property to define an aggregate set of repeating occurrences.
52 When they both appear in a recurring component, the recurrence
53 instances are defined by the union of occurrences defined by both
54 the "RDATE" and "RRULE".
56 The recurrence dates, if specified, are used in computing the
57 recurrence set. The recurrence set is the complete set of
58 recurrence instances for a calendar component. The recurrence set
59 is generated by considering the initial "DTSTART" property along
60 with the "RRULE", "RDATE", and "EXDATE" properties contained
61 within the recurring component. The "DTSTART" property defines
62 the first instance in the recurrence set. The "DTSTART" property
63 value SHOULD match the pattern of the recurrence rule, if
64 specified. The recurrence set generated with a "DTSTART" property
65 value that doesn't match the pattern of the rule is undefined.
66 The final recurrence set is generated by gathering all of the
67 start DATE-TIME values generated by any of the specified "RRULE"
68 and "RDATE" properties, and then excluding any start DATE-TIME
69 values specified by "EXDATE" properties. This implies that start
70 DATE-TIME values specified by "EXDATE" properties take precedence
71 over those specified by inclusion properties (i.e., "RDATE" and
72 "RRULE"). Where duplicate instances are generated by the "RRULE"
73 and "RDATE" properties, only one recurrence is considered.
74 Duplicate instances are ignored.
76 Example:
77 Below, we set one RDATE in a list and get the resulting tuple of start and end.
79 .. code-block:: pycon
81 >>> from icalendar import Event
82 >>> from datetime import datetime
83 >>> event = Event()
85 # Add a list of recurrence dates
86 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)])
87 >>> event.rdates
88 [(datetime.datetime(2025, 4, 28, 16, 5), None)]
90 .. note::
92 You cannot modify the RDATE value by modifying the result.
93 Use :func:`icalendar.cal.Component.add` to add values.
95 If you want to compute recurrences, have a look at :ref:`Related projects`.
97 """
98 result = []
99 rdates = self.get("RDATE", [])
100 for rdates in (rdates,) if not isinstance(rdates, list) else rdates:
101 for dts in rdates.dts:
102 rdate = dts.dt
103 if isinstance(rdate, tuple):
104 # we have a period as rdate
105 if isinstance(rdate[1], timedelta):
106 result.append((rdate[0], rdate[0] + rdate[1]))
107 else:
108 result.append(rdate)
109 else:
110 # we have a date/datetime
111 result.append((rdate, None))
112 return result
115rdates_property = property(_get_rdates)
118def _get_exdates(self: Component) -> list[date | datetime]:
119 """EXDATE defines the list of DATE-TIME exceptions for recurring components.
121 EXDATE is defined in :rfc:`5545`.
123 Value Type:
124 The default value type for this property is DATE-TIME.
125 The value type can be set to DATE.
127 Property Parameters:
128 IANA, non-standard, value data type, and time
129 zone identifier property parameters can be specified on this
130 property.
132 Conformance:
133 This property can be specified in recurring "VEVENT",
134 "VTODO", and "VJOURNAL" calendar components as well as in the
135 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
136 calendar component.
138 Description:
139 The exception dates, if specified, are used in
140 computing the recurrence set. The recurrence set is the complete
141 set of recurrence instances for a calendar component. The
142 recurrence set is generated by considering the initial "DTSTART"
143 property along with the "RRULE", "RDATE", and "EXDATE" properties
144 contained within the recurring component. The "DTSTART" property
145 defines the first instance in the recurrence set. The "DTSTART"
146 property value SHOULD match the pattern of the recurrence rule, if
147 specified. The recurrence set generated with a "DTSTART" property
148 value that doesn't match the pattern of the rule is undefined.
149 The final recurrence set is generated by gathering all of the
150 start DATE-TIME values generated by any of the specified "RRULE"
151 and "RDATE" properties, and then excluding any start DATE-TIME
152 values specified by "EXDATE" properties. This implies that start
153 DATE-TIME values specified by "EXDATE" properties take precedence
154 over those specified by inclusion properties (i.e., "RDATE" and
155 "RRULE"). When duplicate instances are generated by the "RRULE"
156 and "RDATE" properties, only one recurrence is considered.
157 Duplicate instances are ignored.
159 The "EXDATE" property can be used to exclude the value specified
160 in "DTSTART". However, in such cases, the original "DTSTART" date
161 MUST still be maintained by the calendaring and scheduling system
162 because the original "DTSTART" value has inherent usage
163 dependencies by other properties such as the "RECURRENCE-ID".
165 Example:
166 Below, we add an exdate in a list and get the resulting list of exdates.
168 .. code-block:: pycon
170 >>> from icalendar import Event
171 >>> from datetime import datetime
172 >>> event = Event()
174 # Add a list of excluded dates
175 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)])
176 >>> event.exdates
177 [datetime.datetime(2025, 4, 28, 16, 5)]
179 .. note::
181 You cannot modify the EXDATE value by modifying the result.
182 Use :func:`icalendar.cal.Component.add` to add values.
184 If you want to compute recurrences, have a look at :ref:`Related projects`.
186 """
187 result = []
188 exdates = self.get("EXDATE", [])
189 for exdates in (exdates,) if not isinstance(exdates, list) else exdates:
190 for dts in exdates.dts:
191 exdate = dts.dt
192 # we have a date/datetime
193 result.append(exdate)
194 return result
197exdates_property = property(_get_exdates)
200def _get_rrules(self: Component) -> list[vRecur]:
201 """RRULE defines a rule or repeating pattern for recurring components.
203 RRULE is defined in :rfc:`5545`.
204 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`.
206 Property Parameters:
207 IANA and non-standard property parameters can
208 be specified on this property.
210 Conformance:
211 This property can be specified in recurring "VEVENT",
212 "VTODO", and "VJOURNAL" calendar components as well as in the
213 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE"
214 calendar component, but it SHOULD NOT be specified more than once.
215 The recurrence set generated with multiple "RRULE" properties is
216 undefined.
218 Description:
219 The recurrence rule, if specified, is used in computing
220 the recurrence set. The recurrence set is the complete set of
221 recurrence instances for a calendar component. The recurrence set
222 is generated by considering the initial "DTSTART" property along
223 with the "RRULE", "RDATE", and "EXDATE" properties contained
224 within the recurring component. The "DTSTART" property defines
225 the first instance in the recurrence set. The "DTSTART" property
226 value SHOULD be synchronized with the recurrence rule, if
227 specified. The recurrence set generated with a "DTSTART" property
228 value not synchronized with the recurrence rule is undefined. The
229 final recurrence set is generated by gathering all of the start
230 DATE-TIME values generated by any of the specified "RRULE" and
231 "RDATE" properties, and then excluding any start DATE-TIME values
232 specified by "EXDATE" properties. This implies that start DATE-
233 TIME values specified by "EXDATE" properties take precedence over
234 those specified by inclusion properties (i.e., "RDATE" and
235 "RRULE"). Where duplicate instances are generated by the "RRULE"
236 and "RDATE" properties, only one recurrence is considered.
237 Duplicate instances are ignored.
239 The "DTSTART" property specified within the iCalendar object
240 defines the first instance of the recurrence. In most cases, a
241 "DTSTART" property of DATE-TIME value type used with a recurrence
242 rule, should be specified as a date with local time and time zone
243 reference to make sure all the recurrence instances start at the
244 same local time regardless of time zone changes.
246 If the duration of the recurring component is specified with the
247 "DTEND" or "DUE" property, then the same exact duration will apply
248 to all the members of the generated recurrence set. Else, if the
249 duration of the recurring component is specified with the
250 "DURATION" property, then the same nominal duration will apply to
251 all the members of the generated recurrence set and the exact
252 duration of each recurrence instance will depend on its specific
253 start time. For example, recurrence instances of a nominal
254 duration of one day will have an exact duration of more or less
255 than 24 hours on a day where a time zone shift occurs. The
256 duration of a specific recurrence may be modified in an exception
257 component or simply by using an "RDATE" property of PERIOD value
258 type.
260 Examples:
261 Daily for 10 occurrences:
263 .. code-block:: pycon
265 >>> from icalendar import Event
266 >>> from datetime import datetime
267 >>> from zoneinfo import ZoneInfo
268 >>> event = Event()
269 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
270 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10")
271 >>> print(event.to_ical())
272 BEGIN:VEVENT
273 DTSTART;TZID=America/New_York:19970902T090000
274 RRULE:FREQ=DAILY;COUNT=10
275 END:VEVENT
276 >>> event.rrules
277 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})]
279 Daily until December 24, 1997:
281 .. code-block:: pycon
283 >>> from icalendar import Event, vRecur
284 >>> from datetime import datetime
285 >>> from zoneinfo import ZoneInfo
286 >>> event = Event()
287 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York"))
288 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC"))))
289 >>> print(event.to_ical())
290 BEGIN:VEVENT
291 DTSTART;TZID=America/New_York:19970902T090000
292 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
293 END:VEVENT
294 >>> event.rrules
295 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})]
297 .. note::
299 You cannot modify the RRULE value by modifying the result.
300 Use :func:`icalendar.cal.Component.add` to add values.
302 If you want to compute recurrences, have a look at :ref:`Related projects`.
304 """ # noqa: E501
305 rrules = self.get("RRULE", [])
306 if not isinstance(rrules, list):
307 return [rrules]
308 return rrules
311rrules_property = property(_get_rrules)
314def multi_language_text_property(
315 main_prop: str, compatibility_prop: Optional[str], doc: str
316) -> property:
317 """This creates a text property.
319 This property can be defined several times with different ``LANGUAGE`` parameters.
321 Args:
322 main_prop (str): The property to set and get, such as ``NAME``
323 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
324 doc (str): The documentation string
325 """
327 def fget(self: Component) -> Optional[str]:
328 """Get the property"""
329 result = self.get(main_prop)
330 if result is None and compatibility_prop is not None:
331 result = self.get(compatibility_prop)
332 if isinstance(result, list):
333 for item in result:
334 if "LANGUAGE" not in item.params:
335 return item
336 return result
338 def fset(self: Component, value: Optional[str]):
339 """Set the property."""
340 fdel(self)
341 if value is not None:
342 self.add(main_prop, value)
344 def fdel(self: Component):
345 """Delete the property."""
346 self.pop(main_prop, None)
347 if compatibility_prop is not None:
348 self.pop(compatibility_prop, None)
350 return property(fget, fset, fdel, doc)
353def single_int_property(prop: str, default: int, doc: str) -> property:
354 """Create a property for an int value that exists only once.
356 Args:
357 prop: The name of the property
358 default: The default value
359 doc: The documentation string
360 """
362 def fget(self: Component) -> int:
363 """Get the property"""
364 try:
365 return int(self.get(prop, default))
366 except ValueError as e:
367 raise InvalidCalendar(f"{prop} must be an int") from e
369 def fset(self: Component, value: Optional[int]):
370 """Set the property."""
371 fdel(self)
372 if value is not None:
373 self.add(prop, value)
375 def fdel(self: Component):
376 """Delete the property."""
377 self.pop(prop, None)
379 return property(fget, fset, fdel, doc)
382def single_utc_property(name: str, docs: str) -> property:
383 """Create a property to access a value of datetime in UTC timezone.
385 Args:
386 name: name of the property
387 docs: documentation string
388 """
389 docs = (
390 f"""The {name} property. datetime in UTC
392 All values will be converted to a datetime in UTC.
393 """
394 + docs
395 )
397 def fget(self: Component) -> Optional[datetime]:
398 """Get the value."""
399 if name not in self:
400 return None
401 dt = self.get(name)
402 if isinstance(dt, vText):
403 # we might be in an attribute that is not typed
404 value = vDDDTypes.from_ical(dt)
405 else:
406 value = getattr(dt, "dt", dt)
407 if value is None or not isinstance(value, date):
408 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}")
409 return tzp.localize_utc(value)
411 def fset(self: Component, value: Optional[datetime]):
412 """Set the value"""
413 if value is None:
414 fdel(self)
415 return
416 if not isinstance(value, date):
417 raise TypeError(f"{name} takes a datetime in UTC, not {value}")
418 fdel(self)
419 self.add(name, tzp.localize_utc(value))
421 def fdel(self: Component):
422 """Delete the property."""
423 self.pop(name, None)
425 return property(fget, fset, fdel, doc=docs)
428def single_string_property(
429 name: str, docs: str, other_name: Optional[str] = None, default: str = ""
430) -> property:
431 """Create a property to access a single string value."""
433 def fget(self: Component) -> str:
434 """Get the value."""
435 result = self.get(
436 name, None if other_name is None else self.get(other_name, None)
437 )
438 if result is None or result == []:
439 return default
440 if isinstance(result, list):
441 return result[0]
442 return result
444 def fset(self: Component, value: Optional[str]):
445 """Set the value.
447 Setting the value to None will delete it.
448 """
449 fdel(self)
450 if value is not None:
451 self.add(name, value)
453 def fdel(self: Component):
454 """Delete the property."""
455 self.pop(name, None)
456 if other_name is not None:
457 self.pop(other_name, None)
459 return property(fget, fset, fdel, doc=docs)
462color_property = single_string_property(
463 "COLOR",
464 """This property specifies a color used for displaying the component.
466 This implements :rfc:`7986` ``COLOR`` property.
468 Property Parameters:
469 IANA and non-standard property parameters can
470 be specified on this property.
472 Conformance:
473 This property can be specified once in an iCalendar
474 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components.
476 Description:
477 This property specifies a color that clients MAY use
478 when presenting the relevant data to a user. Typically, this
479 would appear as the "background" color of events or tasks. The
480 value is a case-insensitive color name taken from the CSS3 set of
481 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_.
483 Example:
484 ``"turquoise"``, ``"#ffffff"``
486 .. code-block:: pycon
488 >>> from icalendar import Todo
489 >>> todo = Todo()
490 >>> todo.color = "green"
491 >>> print(todo.to_ical())
492 BEGIN:VTODO
493 COLOR:green
494 END:VTODO
495 """,
496)
498sequence_property = single_int_property(
499 "SEQUENCE",
500 0,
501 """This property defines the revision sequence number of the calendar component within a sequence of revisions.
503Value Type:
504 INTEGER
506Property Parameters:
507 IANA and non-standard property parameters can be specified on this property.
509Conformance:
510 The property can be specified in "VEVENT", "VTODO", or
511 "VJOURNAL" calendar component.
513Description:
514 When a calendar component is created, its sequence
515 number is 0. It is monotonically incremented by the "Organizer's"
516 CUA each time the "Organizer" makes a significant revision to the
517 calendar component.
519 The "Organizer" includes this property in an iCalendar object that
520 it sends to an "Attendee" to specify the current version of the
521 calendar component.
523 The "Attendee" includes this property in an iCalendar object that
524 it sends to the "Organizer" to specify the version of the calendar
525 component to which the "Attendee" is referring.
527 A change to the sequence number is not the mechanism that an
528 "Organizer" uses to request a response from the "Attendees". The
529 "RSVP" parameter on the "ATTENDEE" property is used by the
530 "Organizer" to indicate that a response from the "Attendees" is
531 requested.
533 Recurrence instances of a recurring component MAY have different
534 sequence numbers.
536Examples:
537 The following is an example of this property for a calendar
538 component that was just created by the "Organizer":
540 .. code-block:: pycon
542 >>> from icalendar import Event
543 >>> event = Event()
544 >>> event.sequence
545 0
547 The following is an example of this property for a calendar
548 component that has been revised 10 different times by the
549 "Organizer":
551 .. code-block:: pycon
553 >>> from icalendar import Calendar
554 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal")
555 >>> event = calendar.events[0]
556 >>> event.sequence
557 10
558 """, # noqa: E501
559)
562def _get_categories(component: Component) -> list[str]:
563 """Get all the categories."""
564 categories: Optional[vCategory | list[vCategory]] = component.get("CATEGORIES")
565 if isinstance(categories, list):
566 _set_categories(
567 component,
568 list(itertools.chain.from_iterable(cat.cats for cat in categories)),
569 )
570 return _get_categories(component)
571 if categories is None:
572 categories = vCategory([])
573 component.add("CATEGORIES", categories)
574 return categories.cats
577def _set_categories(component: Component, cats: Optional[Sequence[str]]) -> None:
578 """Set the categories."""
579 if not cats and cats != []:
580 _del_categories(component)
581 return
582 component["CATEGORIES"] = categories = vCategory(cats)
583 if isinstance(cats, list):
584 cats.clear()
585 cats.extend(categories.cats)
586 categories.cats = cats
589def _del_categories(component: Component) -> None:
590 """Delete the categories."""
591 component.pop("CATEGORIES", None)
594categories_property = property(
595 _get_categories,
596 _set_categories,
597 _del_categories,
598 """This property defines the categories for a component.
600Property Parameters:
601 IANA, non-standard, and language property parameters can be specified on this
602 property.
604Conformance:
605 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
606 components.
607 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
609Description:
610 This property is used to specify categories or subtypes
611 of the calendar component. The categories are useful in searching
612 for a calendar component of a particular type and category.
613 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
614 more than one category can be specified as a COMMA-separated list
615 of categories.
617Example:
618 Below, we add the categories to an event:
620 .. code-block:: pycon
622 >>> from icalendar import Event
623 >>> event = Event()
624 >>> event.categories = ["Work", "Meeting"]
625 >>> print(event.to_ical())
626 BEGIN:VEVENT
627 CATEGORIES:Work,Meeting
628 END:VEVENT
629 >>> event.categories.append("Lecture")
630 >>> event.categories == ["Work", "Meeting", "Lecture"]
631 True
633.. note::
635 At present, we do not take the LANGUAGE parameter into account.
636""",
637)
640def _get_attendees(self: Component) -> list[vCalAddress]:
641 """Get attendees."""
642 value = self.get("ATTENDEE")
643 if value is None:
644 value = []
645 self["ATTENDEE"] = value
646 return value
647 if isinstance(value, vCalAddress):
648 return [value]
649 return value
652def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None):
653 """Set attendees."""
654 _del_attendees(self)
655 if value is None:
656 return
657 if not isinstance(value, list):
658 value = [value]
659 self["ATTENDEE"] = value
662def _del_attendees(self: Component):
663 """Delete all attendees."""
664 self.pop("ATTENDEE", None)
667attendees_property = property(
668 _get_attendees,
669 _set_attendees,
670 _del_attendees,
671 """ATTENDEE defines one or more "Attendees" within a calendar component.
673Conformance:
674 This property MUST be specified in an iCalendar object
675 that specifies a group-scheduled calendar entity. This property
676 MUST NOT be specified in an iCalendar object when publishing the
677 calendar information (e.g., NOT in an iCalendar object that
678 specifies the publication of a calendar user's busy time, event,
679 to-do, or journal). This property is not specified in an
680 iCalendar object that specifies only a time zone definition or
681 that defines calendar components that are not group-scheduled
682 components, but are components only on a single user's calendar.
684Description:
685 This property MUST only be specified within calendar
686 components to specify participants, non-participants, and the
687 chair of a group-scheduled calendar entity. The property is
688 specified within an "EMAIL" category of the "VALARM" calendar
689 component to specify an email address that is to receive the email
690 type of iCalendar alarm.
692Examples:
693 Add a new attendee to an existing event.
695 .. code-block:: pycon
697 >>> from icalendar import Event, vCalAddress
698 >>> event = Event()
699 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com"))
700 >>> print(event.to_ical())
701 BEGIN:VEVENT
702 ATTENDEE:mailto:me@my-domain.com
703 END:VEVENT
705 Create an email alarm with several attendees:
707 >>> from icalendar import Alarm, vCalAddress
708 >>> alarm = Alarm.new(attendees = [
709 ... vCalAddress("mailto:me@my-domain.com"),
710 ... vCalAddress("mailto:you@my-domain.com"),
711 ... ], summary = "Email alarm")
712 >>> print(alarm.to_ical())
713 BEGIN:VALARM
714 ATTENDEE:mailto:me@my-domain.com
715 ATTENDEE:mailto:you@my-domain.com
716 SUMMARY:Email alarm
717 END:VALARM
718""",
719)
721uid_property = single_string_property(
722 "UID",
723 """UID specifies the persistent, globally unique identifier for a component.
725We recommend using :func:`uuid.uuid4` to generate new values.
727Returns:
728 The value of the UID property as a string or ``""`` if no value is set.
730Description:
731 The "UID" itself MUST be a globally unique identifier.
732 The generator of the identifier MUST guarantee that the identifier
733 is unique.
735 This is the method for correlating scheduling messages with the
736 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component.
737 The full range of calendar components specified by a recurrence
738 set is referenced by referring to just the "UID" property value
739 corresponding to the calendar component. The "RECURRENCE-ID"
740 property allows the reference to an individual instance within the
741 recurrence set.
743 This property is an important method for group-scheduling
744 applications to match requests with later replies, modifications,
745 or deletion requests. Calendaring and scheduling applications
746 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL"
747 calendar components to assure interoperability with other group-
748 scheduling applications. This identifier is created by the
749 calendar system that generates an iCalendar object.
751 Implementations MUST be able to receive and persist values of at
752 least 255 octets for this property, but they MUST NOT truncate
753 values in the middle of a UTF-8 multi-octet sequence.
755 :rfc:`7986` states that UID can be used, for
756 example, to identify duplicate calendar streams that a client may
757 have been given access to. It can be used in conjunction with the
758 "LAST-MODIFIED" property also specified on the "VCALENDAR" object
759 to identify the most recent version of a calendar.
761Conformance:
762 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO",
763 and "VJOURNAL" calendar components.
764 :rfc:`7986` modifies the definition of the "UID" property to
765 allow it to be defined in an iCalendar object.
766 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique
767 identifier to be specified. The value of this property can then be used
768 to refer uniquely to the "VALARM" component.
770 This property can be specified once only.
772Security:
773 :rfc:`7986` states that UID values MUST NOT include any data that
774 might identify a user, host, domain, or any other security- or
775 privacy-sensitive information. It is RECOMMENDED that calendar user
776 agents now generate "UID" values that are hex-encoded random
777 Universally Unique Identifier (UUID) values as defined in
778 Sections 4.4 and 4.5 of :rfc:`4122`.
779 You can use the :mod:`uuid` module to generate new UUIDs.
781Compatibility:
782 For Alarms, ``X-ALARMUID`` is also considered.
784Examples:
785 The following is an example of such a property value:
786 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``.
788 Set the UID of a calendar:
790 .. code-block:: pycon
792 >>> from icalendar import Calendar
793 >>> from uuid import uuid4
794 >>> calendar = Calendar()
795 >>> calendar.uid = uuid4()
796 >>> print(calendar.to_ical())
797 BEGIN:VCALENDAR
798 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
799 END:VCALENDAR
801""",
802)
804summary_property = multi_language_text_property(
805 "SUMMARY",
806 None,
807 """SUMMARY defines a short summary or subject for the calendar component.
809Property Parameters:
810 IANA, non-standard, alternate text
811 representation, and language property parameters can be specified
812 on this property.
814Conformance:
815 The property can be specified in "VEVENT", "VTODO",
816 "VJOURNAL", or "VALARM" calendar components.
818Description:
819 This property is used in the "VEVENT", "VTODO", and
820 "VJOURNAL" calendar components to capture a short, one-line
821 summary about the activity or journal entry.
823 This property is used in the "VALARM" calendar component to
824 capture the subject of an EMAIL category of alarm.
826Examples:
827 The following is an example of this property:
829 .. code-block:: pycon
831 SUMMARY:Department Party
832""",
833)
835description_property = multi_language_text_property(
836 "DESCRIPTION",
837 None,
838 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
840Property Parameters:
841 IANA, non-standard, alternate text
842 representation, and language property parameters can be specified
843 on this property.
845Conformance:
846 The property can be specified in the "VEVENT", "VTODO",
847 "VJOURNAL", or "VALARM" calendar components. The property can be
848 specified multiple times only within a "VJOURNAL" calendar
849 component.
851Description:
852 This property is used in the "VEVENT" and "VTODO" to
853 capture lengthy textual descriptions associated with the activity.
855 This property is used in the "VALARM" calendar component to
856 capture the display text for a DISPLAY category of alarm, and to
857 capture the body text for an EMAIL category of alarm.
859Examples:
860 The following is an example of this property with formatted
861 line breaks in the property value:
863 .. code-block:: pycon
865 DESCRIPTION:Meeting to provide technical review for "Phoenix"
866 design.\\nHappy Face Conference Room. Phoenix design team
867 MUST attend this meeting.\\nRSVP to team leader.
869 """, # noqa: E501
870)
873def create_single_property(
874 prop: str,
875 value_attr: Optional[str],
876 value_type: tuple[type],
877 type_def: type,
878 doc: str,
879 vProp: type = vDDDTypes, # noqa: N803
880):
881 """Create a single property getter and setter.
883 :param prop: The name of the property.
884 :param value_attr: The name of the attribute to get the value from.
885 :param value_type: The type of the value.
886 :param type_def: The type of the property.
887 :param doc: The docstring of the property.
888 :param vProp: The type of the property from :mod:`icalendar.prop`.
889 """
891 def p_get(self: Component):
892 default = object()
893 result = self.get(prop, default)
894 if result is default:
895 return None
896 if isinstance(result, list):
897 raise InvalidCalendar(f"Multiple {prop} defined.")
898 value = result if value_attr is None else getattr(result, value_attr, result)
899 if not isinstance(value, value_type):
900 raise InvalidCalendar(
901 f"{prop} must be either a "
902 f"{' or '.join(t.__name__ for t in value_type)},"
903 f" not {value}."
904 )
905 return value
907 def p_set(self: Component, value) -> None:
908 if value is None:
909 p_del(self)
910 return
911 if not isinstance(value, value_type):
912 raise TypeError(
913 f"Use {' or '.join(t.__name__ for t in value_type)}, "
914 f"not {type(value).__name__}."
915 )
916 self[prop] = vProp(value)
917 if prop in self.exclusive:
918 for other_prop in self.exclusive:
919 if other_prop != prop:
920 self.pop(other_prop, None)
922 p_set.__annotations__["value"] = p_get.__annotations__["return"] = Optional[
923 type_def
924 ]
926 def p_del(self: Component):
927 self.pop(prop)
929 p_doc = f"""The {prop} property.
931 {doc}
933 Accepted values: {", ".join(t.__name__ for t in value_type)}.
934 If the attribute has invalid values, we raise InvalidCalendar.
935 If the value is absent, we return None.
936 You can also delete the value with del or by setting it to None.
937 """
938 return property(p_get, p_set, p_del, p_doc)
941X_MOZ_SNOOZE_TIME_property = single_utc_property(
942 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed."
943)
944X_MOZ_LASTACK_property = single_utc_property(
945 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged."
946)
949def property_get_duration(self: Component) -> Optional[timedelta]:
950 """Getter for property DURATION."""
951 default = object()
952 duration = self.get("duration", default)
953 if isinstance(duration, vDDDTypes):
954 return duration.dt
955 if isinstance(duration, vDuration):
956 return duration.td
957 if duration is not default and not isinstance(duration, timedelta):
958 raise InvalidCalendar(
959 f"DURATION must be a timedelta, not {type(duration).__name__}."
960 )
961 return None
964def property_set_duration(self: Component, value: Optional[timedelta]):
965 """Setter for property DURATION."""
966 if value is None:
967 self.pop("duration", None)
968 return
969 if not isinstance(value, timedelta):
970 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
971 self["duration"] = vDuration(value)
972 self.pop("DTEND")
973 self.pop("DUE")
976def property_del_duration(self: Component):
977 """Delete property DURATION."""
978 self.pop("DURATION")
981property_doc_duration_template = """The DURATION property.
983The "DTSTART" property for a "{component}" specifies the inclusive
984start of the {component}.
985The "DURATION" property in conjunction with the DTSTART property
986for a "{component}" calendar component specifies the non-inclusive end
987of the event.
989If you would like to calculate the duration of a {component}, do not use this.
990Instead use the duration property (lower case).
991"""
994def duration_property(component: str) -> property:
995 """Return the duration property."""
996 return property(
997 property_get_duration,
998 property_set_duration,
999 property_del_duration,
1000 property_doc_duration_template.format(component=component),
1001 )
1004def multi_text_property(name: str, docs: str) -> property:
1005 """Get a property that can occur several times and is text.
1007 Examples: Journal.descriptions, Event.comments
1008 """
1010 def fget(self: Component) -> list[str]:
1011 """Get the values."""
1012 descriptions = self.get(name)
1013 if descriptions is None:
1014 return []
1015 if not isinstance(descriptions, SEQUENCE_TYPES):
1016 return [descriptions]
1017 return descriptions
1019 def fset(self: Component, values: Optional[str | Sequence[str]]):
1020 """Set the values."""
1021 fdel(self)
1022 if values is None:
1023 return
1024 if isinstance(values, str):
1025 self.add(name, values)
1026 else:
1027 for description in values:
1028 self.add(name, description)
1030 def fdel(self: Component):
1031 """Delete the values."""
1032 self.pop(name)
1034 return property(fget, fset, fdel, docs)
1037descriptions_property = multi_text_property(
1038 "DESCRIPTION",
1039 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
1041Property Parameters:
1042 IANA, non-standard, alternate text
1043 representation, and language property parameters can be specified
1044 on this property.
1046Conformance:
1047 The property can be
1048 specified multiple times only within a "VJOURNAL" calendar component.
1050Description:
1051 This property is used in the "VJOURNAL" calendar component to
1052 capture one or more textual journal entries.
1054Examples:
1055 The following is an example of this property with formatted
1056 line breaks in the property value:
1058 .. code-block:: pycon
1060 DESCRIPTION:Meeting to provide technical review for "Phoenix"
1061 design.\\nHappy Face Conference Room. Phoenix design team
1062 MUST attend this meeting.\\nRSVP to team leader.
1064""", # noqa: E501
1065)
1067comments_property = multi_text_property(
1068 "COMMENT",
1069 """COMMENT is used to specify a comment to the calendar user.
1071Purpose:
1072 This property specifies non-processing information intended
1073 to provide a comment to the calendar user.
1075Conformance:
1076 In :rfc:`5545`, this property can be specified multiple times in
1077 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components
1078 as well as in the "STANDARD" and "DAYLIGHT" sub-components.
1079 In :rfc:`7953`, this property can be specified multiple times in
1080 "VAVAILABILITY" and "VAVAILABLE".
1082Property Parameters:
1083 IANA, non-standard, alternate text
1084 representation, and language property parameters can be specified
1085 on this property.
1087""",
1088)
1091def _get_organizer(self: Component) -> Optional[vCalAddress]:
1092 """ORGANIZER defines the organizer for a calendar component.
1094 Property Parameters:
1095 IANA, non-standard, language, common name,
1096 directory entry reference, and sent-by property parameters can be
1097 specified on this property.
1099 Conformance:
1100 This property MUST be specified in an iCalendar object
1101 that specifies a group-scheduled calendar entity. This property
1102 MUST be specified in an iCalendar object that specifies the
1103 publication of a calendar user's busy time. This property MUST
1104 NOT be specified in an iCalendar object that specifies only a time
1105 zone definition or that defines calendar components that are not
1106 group-scheduled components, but are components only on a single
1107 user's calendar.
1109 Description:
1110 This property is specified within the "VEVENT",
1111 "VTODO", and "VJOURNAL" calendar components to specify the
1112 organizer of a group-scheduled calendar entity. The property is
1113 specified within the "VFREEBUSY" calendar component to specify the
1114 calendar user requesting the free or busy time. When publishing a
1115 "VFREEBUSY" calendar component, the property is used to specify
1116 the calendar that the published busy time came from.
1118 The property has the property parameters "CN", for specifying the
1119 common or display name associated with the "Organizer", "DIR", for
1120 specifying a pointer to the directory information associated with
1121 the "Organizer", "SENT-BY", for specifying another calendar user
1122 that is acting on behalf of the "Organizer". The non-standard
1123 parameters may also be specified on this property. If the
1124 "LANGUAGE" property parameter is specified, the identified
1125 language applies to the "CN" parameter value.
1126 """
1127 return self.get("ORGANIZER")
1130def _set_organizer(self: Component, value: Optional[vCalAddress | str]):
1131 """Set the value."""
1132 _del_organizer(self)
1133 if value is not None:
1134 self.add("ORGANIZER", value)
1137def _del_organizer(self: Component):
1138 """Delete the value."""
1139 self.pop("ORGANIZER")
1142organizer_property = property(_get_organizer, _set_organizer, _del_organizer)
1145def single_string_enum_property(
1146 name: str, enum: type[StrEnum], default: StrEnum, docs: str
1147) -> property:
1148 """Create a property to access a single string value and convert it to an enum."""
1149 prop = single_string_property(name, docs, default=default)
1151 def fget(self: Component) -> StrEnum:
1152 """Get the value."""
1153 value = prop.fget(self)
1154 if value == default:
1155 return default
1156 return enum(str(value))
1158 def fset(self: Component, value: str | StrEnum | None) -> None:
1159 """Set the value."""
1160 if value == "":
1161 value = None
1162 prop.fset(self, value)
1164 return property(fget, fset, prop.fdel, doc=docs)
1167busy_type_property = single_string_enum_property(
1168 "BUSYTYPE",
1169 BUSYTYPE,
1170 BUSYTYPE.BUSY_UNAVAILABLE,
1171 """BUSYTYPE specifies the default busy time type.
1173Returns:
1174 :class:`icalendar.enums.BUSYTYPE`
1176Description:
1177 This property is used to specify the default busy time
1178 type. The values correspond to those used by the FBTYPE"
1179 parameter used on a "FREEBUSY" property, with the exception that
1180 the "FREE" value is not used in this property. If not specified
1181 on a component that allows this property, the default is "BUSY-
1182 UNAVAILABLE".
1183""",
1184)
1186priority_property = single_int_property(
1187 "PRIORITY",
1188 0,
1189 """
1191Conformance:
1192 This property can be specified in "VEVENT" and "VTODO" calendar components
1193 according to :rfc:`5545`.
1194 :rfc:`7953` adds this property to "VAVAILABILITY".
1196Description:
1197 This priority is specified as an integer in the range 0
1198 to 9. A value of 0 specifies an undefined priority. A value of 1
1199 is the highest priority. A value of 2 is the second highest
1200 priority. Subsequent numbers specify a decreasing ordinal
1201 priority. A value of 9 is the lowest priority.
1203 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and
1204 "LOW" is mapped into this property such that a property value in
1205 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is
1206 the normal or "MEDIUM" priority. A value in the range of 6 to 9
1207 is "LOW" priority.
1209 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ...,
1210 "C3" is mapped into this property such that a property value of 1
1211 specifies "A1", a property value of 2 specifies "A2", a property
1212 value of 3 specifies "A3", and so forth up to a property value of
1213 9 specifies "C3".
1215 Other integer values are reserved for future use.
1217 Within a "VEVENT" calendar component, this property specifies a
1218 priority for the event. This property may be useful when more
1219 than one event is scheduled for a given time period.
1221 Within a "VTODO" calendar component, this property specified a
1222 priority for the to-do. This property is useful in prioritizing
1223 multiple action items for a given time period.
1224""",
1225)
1227class_property = single_string_enum_property(
1228 "CLASS",
1229 CLASS,
1230 CLASS.PUBLIC,
1231 """CLASS specifies the class of the calendar component.
1233Returns:
1234 :class:`icalendar.enums.CLASS`
1236Description:
1237 An access classification is only one component of the
1238 general security system within a calendar application. It
1239 provides a method of capturing the scope of the access the
1240 calendar owner intends for information within an individual
1241 calendar entry. The access classification of an individual
1242 iCalendar component is useful when measured along with the other
1243 security components of a calendar system (e.g., calendar user
1244 authentication, authorization, access rights, access role, etc.).
1245 Hence, the semantics of the individual access classifications
1246 cannot be completely defined by this memo alone. Additionally,
1247 due to the "blind" nature of most exchange processes using this
1248 memo, these access classifications cannot serve as an enforcement
1249 statement for a system receiving an iCalendar object. Rather,
1250 they provide a method for capturing the intention of the calendar
1251 owner for the access to the calendar component. If not specified
1252 in a component that allows this property, the default value is
1253 PUBLIC. Applications MUST treat x-name and iana-token values they
1254 don't recognize the same way as they would the PRIVATE value.
1255""",
1256)
1258transparency_property = single_string_enum_property(
1259 "TRANSP",
1260 TRANSP,
1261 TRANSP.OPAQUE,
1262 """TRANSP defines whether or not an event is transparent to busy time searches.
1264Returns:
1265 :class:`icalendar.enums.TRANSP`
1267Description:
1268 Time Transparency is the characteristic of an event
1269 that determines whether it appears to consume time on a calendar.
1270 Events that consume actual time for the individual or resource
1271 associated with the calendar SHOULD be recorded as OPAQUE,
1272 allowing them to be detected by free/busy time searches. Other
1273 events, which do not take up the individual's (or resource's) time
1274 SHOULD be recorded as TRANSPARENT, making them invisible to free/
1275 busy time searches.
1276""",
1277)
1278status_property = single_string_enum_property(
1279 "STATUS",
1280 STATUS,
1281 "",
1282 """STATUS defines the overall status or confirmation for the calendar component.
1284Returns:
1285 :class:`icalendar.enums.STATUS`
1287The default value is ``""``.
1289Description:
1290 In a group-scheduled calendar component, the property
1291 is used by the "Organizer" to provide a confirmation of the event
1292 to the "Attendees". For example in a "VEVENT" calendar component,
1293 the "Organizer" can indicate that a meeting is tentative,
1294 confirmed, or cancelled. In a "VTODO" calendar component, the
1295 "Organizer" can indicate that an action item needs action, is
1296 completed, is in process or being worked on, or has been
1297 cancelled. In a "VJOURNAL" calendar component, the "Organizer"
1298 can indicate that a journal entry is draft, final, or has been
1299 cancelled or removed.
1300""",
1301)
1303url_property = single_string_property(
1304 "URL",
1305 """A Uniform Resource Locator (URL) associated with the iCalendar object.
1307Description:
1308 This property may be used in a calendar component to
1309 convey a location where a more dynamic rendition of the calendar
1310 information associated with the calendar component can be found.
1311 This memo does not attempt to standardize the form of the URI, nor
1312 the format of the resource pointed to by the property value. If
1313 the URL property and Content-Location MIME header are both
1314 specified, they MUST point to the same resource.
1315""",
1316)
1318location_property = multi_language_text_property(
1319 "LOCATION",
1320 None,
1321 """The intended venue for the activity defined by a calendar component.
1323Property Parameters:
1324 IANA, non-standard, alternate text
1325 representation, and language property parameters can be specified
1326 on this property.
1328Conformance:
1329 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO"
1330 calendar component.
1331 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE".
1333Description:
1334 Specific venues such as conference or meeting rooms may
1335 be explicitly specified using this property. An alternate
1336 representation may be specified that is a URI that points to
1337 directory information with more structured specification of the
1338 location. For example, the alternate representation may specify
1339 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a
1340 CID URL :rfc:`2392` pointing to a MIME body part containing a
1341 Virtual-Information Card (vCard) :rfc:`2426` for the location.
1343""",
1344)
1346contacts_property = multi_text_property(
1347 "CONTACT",
1348 """Contact information associated with the calendar component.
1350Purpose:
1351 This property is used to represent contact information or
1352 alternately a reference to contact information associated with the
1353 calendar component.
1355Property Parameters:
1356 IANA, non-standard, alternate text
1357 representation, and language property parameters can be specified
1358 on this property.
1360Conformance:
1361 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO",
1362 "VJOURNAL", or "VFREEBUSY" calendar component.
1363 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY"
1364 amd "VAVAILABLE" calendar component.
1366Description:
1367 The property value consists of textual contact
1368 information. An alternative representation for the property value
1369 can also be specified that refers to a URI pointing to an
1370 alternate form, such as a vCard :rfc:`2426`, for the contact
1371 information.
1373Example:
1374 The following is an example of this property referencing
1375 textual contact information:
1377 .. code-block:: text
1379 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1381 The following is an example of this property with an alternate
1382 representation of an LDAP URI to a directory entry containing the
1383 contact information:
1385 .. code-block:: text
1387 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\,
1388 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\,
1389 +1-919-555-1234
1391 The following is an example of this property with an alternate
1392 representation of a MIME body part containing the contact
1393 information, such as a vCard :rfc:`2426` embedded in a text/
1394 directory media type :rfc:`2425`:
1396 .. code-block:: text
1398 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com":
1399 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234
1401 The following is an example of this property referencing a network
1402 resource, such as a vCard :rfc:`2426` object containing the contact
1403 information:
1405 .. code-block:: text
1407 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim
1408 Dolittle\\, ABC Industries\\, +1-919-555-1234
1409""",
1410)
1413def timezone_datetime_property(name: str, docs: str):
1414 """Create a property to access the values with a proper timezone."""
1416 return single_utc_property(name, docs)
1419rfc_7953_dtstart_property = timezone_datetime_property(
1420 "DTSTART",
1421 """Start of the component.
1423 This is almost the same as :attr:`Event.DTSTART` with one exception:
1424 The values MUST have a timezone and DATE is not allowed.
1426 Description:
1427 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1428 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1429 "DATE-TIME" values specified as either the date with UTC time or
1430 the date with local time and a time zone reference.
1432 """,
1433)
1435rfc_7953_dtend_property = timezone_datetime_property(
1436 "DTEND",
1437 """Start of the component.
1439 This is almost the same as :attr:`Event.DTEND` with one exception:
1440 The values MUST have a timezone and DATE is not allowed.
1442 Description:
1443 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in
1444 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
1445 "DATE-TIME" values specified as either the date with UTC time or
1446 the date with local time and a time zone reference.
1447 """,
1448)
1451@property
1452def rfc_7953_duration_property(self) -> Optional[timedelta]:
1453 """Compute the duration of this component.
1455 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1456 Otherwise, the duration is calculated from :attr:`DTSTART` and
1457 :attr:`DTEND`/:attr:`DURATION`.
1459 This is in accordance with :rfc:`7953`:
1460 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1461 """
1462 duration = self.DURATION
1463 if duration:
1464 return duration
1465 end = self.DTEND
1466 if end is None:
1467 return None
1468 start = self.DTSTART
1469 if start is None:
1470 raise IncompleteComponent("Cannot compute duration without start.")
1471 return end - start
1474@property
1475def rfc_7953_end_property(self) -> Optional[timedelta]:
1476 """Compute the duration of this component.
1478 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None.
1479 Otherwise, the duration is calculated from :attr:`DTSTART` and
1480 :attr:`DTEND`/:attr:`DURATION`.
1482 This is in accordance with :rfc:`7953`:
1483 If "DTEND" or "DURATION" are not present, then the end time is unbounded.
1484 """
1485 duration = self.DURATION
1486 if duration:
1487 start = self.DTSTART
1488 if start is None:
1489 raise IncompleteComponent("Cannot compute end without start.")
1490 return start + duration
1491 end = self.DTEND
1492 if end is None:
1493 return None
1494 return end
1497@rfc_7953_end_property.setter
1498def rfc_7953_end_property(self, value: datetime):
1499 self.DTEND = value
1502@rfc_7953_end_property.deleter
1503def rfc_7953_end_property(self):
1504 del self.DTEND
1507def set_duration_with_locking(component, duration, locked, end_property):
1508 """Set the duration with explicit locking behavior for Event and Todo components.
1510 Args:
1511 component: The component to modify (Event or Todo)
1512 duration: The duration to set, or None to convert to DURATION property
1513 locked: Which property to keep unchanged ('start' or 'end')
1514 end_property: The end property name ('DTEND' for Event, 'DUE' for Todo)
1515 """
1516 # Convert to DURATION property if duration is None
1517 if duration is None:
1518 if "DURATION" in component:
1519 return # Already has DURATION property
1520 current_duration = component.duration
1521 component.DURATION = current_duration
1522 return
1524 if not isinstance(duration, timedelta):
1525 raise TypeError(f"Use timedelta, not {type(duration).__name__}.")
1527 # Validate date/duration compatibility
1528 start = component.DTSTART
1529 if start is not None and is_date(start) and duration.seconds != 0:
1530 raise InvalidCalendar(
1531 "When DTSTART is a date, DURATION must be of days or weeks."
1532 )
1534 if locked == "start":
1535 # Keep start locked, adjust end
1536 if start is None:
1537 raise IncompleteComponent(
1538 "Cannot set duration without DTSTART. Set start time first."
1539 )
1540 component.DURATION = duration
1541 elif locked == "end":
1542 # Keep end locked, adjust start
1543 current_end = component.end
1544 component.DTSTART = current_end - duration
1545 component.DURATION = duration
1546 else:
1547 raise ValueError(f"locked must be 'start' or 'end', not {locked!r}")
1550__all__ = [
1551 "attendees_property",
1552 "busy_type_property",
1553 "categories_property",
1554 "class_property",
1555 "color_property",
1556 "comments_property",
1557 "contacts_property",
1558 "create_single_property",
1559 "description_property",
1560 "descriptions_property",
1561 "duration_property",
1562 "exdates_property",
1563 "location_property",
1564 "multi_language_text_property",
1565 "organizer_property",
1566 "priority_property",
1567 "property_del_duration",
1568 "property_doc_duration_template",
1569 "property_get_duration",
1570 "property_set_duration",
1571 "rdates_property",
1572 "rfc_7953_dtend_property",
1573 "rfc_7953_dtstart_property",
1574 "rfc_7953_duration_property",
1575 "rfc_7953_end_property",
1576 "rrules_property",
1577 "sequence_property",
1578 "set_duration_with_locking",
1579 "single_int_property",
1580 "single_utc_property",
1581 "status_property",
1582 "summary_property",
1583 "transparency_property",
1584 "uid_property",
1585 "url_property",
1586]