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

494 statements  

1"""Attributes of Components and properties.""" 

2 

3from __future__ import annotations 

4 

5import itertools 

6from datetime import date, datetime, timedelta 

7from typing import TYPE_CHECKING, List, Literal, Optional, Sequence, Union 

8 

9from icalendar.enums import BUSYTYPE, CLASS, STATUS, TRANSP, StrEnum 

10from icalendar.error import IncompleteComponent, InvalidCalendar 

11from icalendar.parser_tools import SEQUENCE_TYPES 

12from icalendar.prop import ( 

13 vCalAddress, 

14 vCategory, 

15 vDDDTypes, 

16 vDuration, 

17 vRecur, 

18 vText, 

19 vUid, 

20 vUri, 

21 vXmlReference, 

22) 

23from icalendar.prop.conference import Conference 

24from icalendar.prop.image import Image 

25from icalendar.timezone import tzp 

26from icalendar.tools import is_date 

27 

28if TYPE_CHECKING: 

29 from icalendar.cal import Component 

30 

31try: 

32 from typing import TypeAlias 

33except ImportError: 

34 from typing_extensions import TypeAlias 

35 

36 

37def _get_rdates( 

38 self: Component, 

39) -> list[Union[tuple[date, None], tuple[datetime, None], tuple[datetime, datetime]]]: 

40 """The RDATE property defines the list of DATE-TIME values for recurring components. 

41 

42 RDATE is defined in :rfc:`5545`. 

43 The return value is a list of tuples ``(start, end)``. 

44 

45 ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`, 

46 with and without timezone. 

47 

48 ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime` 

49 if the end is specified. 

50 

51 Value Type: 

52 The default value type for this property is DATE-TIME. 

53 The value type can be set to DATE or PERIOD. 

54 

55 Property Parameters: 

56 IANA, non-standard, value data type, and time 

57 zone identifier property parameters can be specified on this 

58 property. 

59 

60 Conformance: 

61 This property can be specified in recurring "VEVENT", 

62 "VTODO", and "VJOURNAL" calendar components as well as in the 

63 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" 

64 calendar component. 

65 

66 Description: 

67 This property can appear along with the "RRULE" 

68 property to define an aggregate set of repeating occurrences. 

69 When they both appear in a recurring component, the recurrence 

70 instances are defined by the union of occurrences defined by both 

71 the "RDATE" and "RRULE". 

72 

73 The recurrence dates, if specified, are used in computing the 

74 recurrence set. The recurrence set is the complete set of 

75 recurrence instances for a calendar component. The recurrence set 

76 is generated by considering the initial "DTSTART" property along 

77 with the "RRULE", "RDATE", and "EXDATE" properties contained 

78 within the recurring component. The "DTSTART" property defines 

79 the first instance in the recurrence set. The "DTSTART" property 

80 value SHOULD match the pattern of the recurrence rule, if 

81 specified. The recurrence set generated with a "DTSTART" property 

82 value that doesn't match the pattern of the rule is undefined. 

83 The final recurrence set is generated by gathering all of the 

84 start DATE-TIME values generated by any of the specified "RRULE" 

85 and "RDATE" properties, and then excluding any start DATE-TIME 

86 values specified by "EXDATE" properties. This implies that start 

87 DATE-TIME values specified by "EXDATE" properties take precedence 

88 over those specified by inclusion properties (i.e., "RDATE" and 

89 "RRULE"). Where duplicate instances are generated by the "RRULE" 

90 and "RDATE" properties, only one recurrence is considered. 

91 Duplicate instances are ignored. 

92 

93 Example: 

94 Below, we set one RDATE in a list and get the resulting tuple of start and end. 

95 

96 .. code-block:: pycon 

97 

98 >>> from icalendar import Event 

99 >>> from datetime import datetime 

100 >>> event = Event() 

101 

102 # Add a list of recurrence dates 

103 >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)]) 

104 >>> event.rdates 

105 [(datetime.datetime(2025, 4, 28, 16, 5), None)] 

106 

107 .. note:: 

108 

109 You cannot modify the RDATE value by modifying the result. 

110 Use :func:`icalendar.cal.Component.add` to add values. 

111 

112 If you want to compute recurrences, have a look at 

113 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_. 

114 

115 """ 

116 result = [] 

117 rdates = self.get("RDATE", []) 

118 for rdates in (rdates,) if not isinstance(rdates, list) else rdates: 

119 for dts in rdates.dts: 

120 rdate = dts.dt 

121 if isinstance(rdate, tuple): 

122 # we have a period as rdate 

123 if isinstance(rdate[1], timedelta): 

124 result.append((rdate[0], rdate[0] + rdate[1])) 

125 else: 

126 result.append(rdate) 

127 else: 

128 # we have a date/datetime 

129 result.append((rdate, None)) 

130 return result 

131 

132 

133rdates_property = property(_get_rdates) 

134 

135 

136def _get_exdates(self: Component) -> list[date | datetime]: 

137 """EXDATE defines the list of DATE-TIME exceptions for recurring components. 

138 

139 EXDATE is defined in :rfc:`5545`. 

140 

141 Value Type: 

142 The default value type for this property is DATE-TIME. 

143 The value type can be set to DATE. 

144 

145 Property Parameters: 

146 IANA, non-standard, value data type, and time 

147 zone identifier property parameters can be specified on this 

148 property. 

149 

150 Conformance: 

151 This property can be specified in recurring "VEVENT", 

152 "VTODO", and "VJOURNAL" calendar components as well as in the 

153 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" 

154 calendar component. 

155 

156 Description: 

157 The exception dates, if specified, are used in 

158 computing the recurrence set. The recurrence set is the complete 

159 set of recurrence instances for a calendar component. The 

160 recurrence set is generated by considering the initial "DTSTART" 

161 property along with the "RRULE", "RDATE", and "EXDATE" properties 

162 contained within the recurring component. The "DTSTART" property 

163 defines the first instance in the recurrence set. The "DTSTART" 

164 property value SHOULD match the pattern of the recurrence rule, if 

165 specified. The recurrence set generated with a "DTSTART" property 

166 value that doesn't match the pattern of the rule is undefined. 

167 The final recurrence set is generated by gathering all of the 

168 start DATE-TIME values generated by any of the specified "RRULE" 

169 and "RDATE" properties, and then excluding any start DATE-TIME 

170 values specified by "EXDATE" properties. This implies that start 

171 DATE-TIME values specified by "EXDATE" properties take precedence 

172 over those specified by inclusion properties (i.e., "RDATE" and 

173 "RRULE"). When duplicate instances are generated by the "RRULE" 

174 and "RDATE" properties, only one recurrence is considered. 

175 Duplicate instances are ignored. 

176 

177 The "EXDATE" property can be used to exclude the value specified 

178 in "DTSTART". However, in such cases, the original "DTSTART" date 

179 MUST still be maintained by the calendaring and scheduling system 

180 because the original "DTSTART" value has inherent usage 

181 dependencies by other properties such as the "RECURRENCE-ID". 

182 

183 Example: 

184 Below, we add an exdate in a list and get the resulting list of exdates. 

185 

186 .. code-block:: pycon 

187 

188 >>> from icalendar import Event 

189 >>> from datetime import datetime 

190 >>> event = Event() 

191 

192 # Add a list of excluded dates 

193 >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)]) 

194 >>> event.exdates 

195 [datetime.datetime(2025, 4, 28, 16, 5)] 

196 

197 .. note:: 

198 

199 You cannot modify the EXDATE value by modifying the result. 

200 Use :func:`icalendar.cal.Component.add` to add values. 

201 

202 If you want to compute recurrences, have a look at 

203 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_. 

204 

205 """ 

206 result = [] 

207 exdates = self.get("EXDATE", []) 

208 for exdates in (exdates,) if not isinstance(exdates, list) else exdates: 

209 for dts in exdates.dts: 

210 exdate = dts.dt 

211 # we have a date/datetime 

212 result.append(exdate) 

213 return result 

214 

215 

216exdates_property = property(_get_exdates) 

217 

218 

219def _get_rrules(self: Component) -> list[vRecur]: 

220 """RRULE defines a rule or repeating pattern for recurring components. 

221 

222 RRULE is defined in :rfc:`5545`. 

223 :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`. 

224 

225 Property Parameters: 

226 IANA and non-standard property parameters can 

227 be specified on this property. 

228 

229 Conformance: 

230 This property can be specified in recurring "VEVENT", 

231 "VTODO", and "VJOURNAL" calendar components as well as in the 

232 "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" 

233 calendar component, but it SHOULD NOT be specified more than once. 

234 The recurrence set generated with multiple "RRULE" properties is 

235 undefined. 

236 

237 Description: 

238 The recurrence rule, if specified, is used in computing 

239 the recurrence set. The recurrence set is the complete set of 

240 recurrence instances for a calendar component. The recurrence set 

241 is generated by considering the initial "DTSTART" property along 

242 with the "RRULE", "RDATE", and "EXDATE" properties contained 

243 within the recurring component. The "DTSTART" property defines 

244 the first instance in the recurrence set. The "DTSTART" property 

245 value SHOULD be synchronized with the recurrence rule, if 

246 specified. The recurrence set generated with a "DTSTART" property 

247 value not synchronized with the recurrence rule is undefined. The 

248 final recurrence set is generated by gathering all of the start 

249 DATE-TIME values generated by any of the specified "RRULE" and 

250 "RDATE" properties, and then excluding any start DATE-TIME values 

251 specified by "EXDATE" properties. This implies that start DATE- 

252 TIME values specified by "EXDATE" properties take precedence over 

253 those specified by inclusion properties (i.e., "RDATE" and 

254 "RRULE"). Where duplicate instances are generated by the "RRULE" 

255 and "RDATE" properties, only one recurrence is considered. 

256 Duplicate instances are ignored. 

257 

258 The "DTSTART" property specified within the iCalendar object 

259 defines the first instance of the recurrence. In most cases, a 

260 "DTSTART" property of DATE-TIME value type used with a recurrence 

261 rule, should be specified as a date with local time and time zone 

262 reference to make sure all the recurrence instances start at the 

263 same local time regardless of time zone changes. 

264 

265 If the duration of the recurring component is specified with the 

266 "DTEND" or "DUE" property, then the same exact duration will apply 

267 to all the members of the generated recurrence set. Else, if the 

268 duration of the recurring component is specified with the 

269 "DURATION" property, then the same nominal duration will apply to 

270 all the members of the generated recurrence set and the exact 

271 duration of each recurrence instance will depend on its specific 

272 start time. For example, recurrence instances of a nominal 

273 duration of one day will have an exact duration of more or less 

274 than 24 hours on a day where a time zone shift occurs. The 

275 duration of a specific recurrence may be modified in an exception 

276 component or simply by using an "RDATE" property of PERIOD value 

277 type. 

278 

279 Examples: 

280 Daily for 10 occurrences: 

281 

282 .. code-block:: pycon 

283 

284 >>> from icalendar import Event 

285 >>> from datetime import datetime 

286 >>> from zoneinfo import ZoneInfo 

287 >>> event = Event() 

288 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York")) 

289 >>> event.add("RRULE", "FREQ=DAILY;COUNT=10") 

290 >>> print(event.to_ical()) 

291 BEGIN:VEVENT 

292 DTSTART;TZID=America/New_York:19970902T090000 

293 RRULE:FREQ=DAILY;COUNT=10 

294 END:VEVENT 

295 >>> event.rrules 

296 [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})] 

297 

298 Daily until December 24, 1997: 

299 

300 .. code-block:: pycon 

301 

302 >>> from icalendar import Event, vRecur 

303 >>> from datetime import datetime 

304 >>> from zoneinfo import ZoneInfo 

305 >>> event = Event() 

306 >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York")) 

307 >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC")))) 

308 >>> print(event.to_ical()) 

309 BEGIN:VEVENT 

310 DTSTART;TZID=America/New_York:19970902T090000 

311 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

312 END:VEVENT 

313 >>> event.rrules 

314 [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})] 

315 

316 .. note:: 

317 

318 You cannot modify the RRULE value by modifying the result. 

319 Use :func:`icalendar.cal.Component.add` to add values. 

320 

321 If you want to compute recurrences, have a look at 

322 `Related Projects <https://github.com/collective/icalendar/blob/main/README.rst#related-projects>`_. 

323 

324 """ # noqa: E501 

325 rrules = self.get("RRULE", []) 

326 if not isinstance(rrules, list): 

327 return [rrules] 

328 return rrules 

329 

330 

331rrules_property = property(_get_rrules) 

332 

333 

334def multi_language_text_property( 

335 main_prop: str, compatibility_prop: Optional[str], doc: str 

336) -> property: 

337 """This creates a text property. 

338 

339 This property can be defined several times with different ``LANGUAGE`` parameters. 

340 

341 Args: 

342 main_prop (str): The property to set and get, such as ``NAME`` 

343 compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME`` 

344 doc (str): The documentation string 

345 """ 

346 

347 def fget(self: Component) -> Optional[str]: 

348 """Get the property""" 

349 result = self.get(main_prop) 

350 if result is None and compatibility_prop is not None: 

351 result = self.get(compatibility_prop) 

352 if isinstance(result, list): 

353 for item in result: 

354 if "LANGUAGE" not in item.params: 

355 return item 

356 return result 

357 

358 def fset(self: Component, value: Optional[str]): 

359 """Set the property.""" 

360 fdel(self) 

361 if value is not None: 

362 self.add(main_prop, value) 

363 

364 def fdel(self: Component): 

365 """Delete the property.""" 

366 self.pop(main_prop, None) 

367 if compatibility_prop is not None: 

368 self.pop(compatibility_prop, None) 

369 

370 return property(fget, fset, fdel, doc) 

371 

372 

373def single_int_property(prop: str, default: int, doc: str) -> property: 

374 """Create a property for an int value that exists only once. 

375 

376 Args: 

377 prop: The name of the property 

378 default: The default value 

379 doc: The documentation string 

380 """ 

381 

382 def fget(self: Component) -> int: 

383 """Get the property""" 

384 try: 

385 return int(self.get(prop, default)) 

386 except ValueError as e: 

387 raise InvalidCalendar(f"{prop} must be an int") from e 

388 

389 def fset(self: Component, value: Optional[int]): 

390 """Set the property.""" 

391 fdel(self) 

392 if value is not None: 

393 self.add(prop, value) 

394 

395 def fdel(self: Component): 

396 """Delete the property.""" 

397 self.pop(prop, None) 

398 

399 return property(fget, fset, fdel, doc) 

400 

401 

402def single_utc_property(name: str, docs: str) -> property: 

403 """Create a property to access a value of datetime in UTC timezone. 

404 

405 Args: 

406 name: name of the property 

407 docs: documentation string 

408 """ 

409 docs = ( 

410 f"""The {name} property. datetime in UTC 

411 

412 All values will be converted to a datetime in UTC. 

413 """ 

414 + docs 

415 ) 

416 

417 def fget(self: Component) -> Optional[datetime]: 

418 """Get the value.""" 

419 if name not in self: 

420 return None 

421 dt = self.get(name) 

422 if isinstance(dt, vText): 

423 # we might be in an attribute that is not typed 

424 value = vDDDTypes.from_ical(dt) 

425 else: 

426 value = getattr(dt, "dt", dt) 

427 if value is None or not isinstance(value, date): 

428 raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}") 

429 return tzp.localize_utc(value) 

430 

431 def fset(self: Component, value: Optional[datetime]): 

432 """Set the value""" 

433 if value is None: 

434 fdel(self) 

435 return 

436 if not isinstance(value, date): 

437 raise TypeError(f"{name} takes a datetime in UTC, not {value}") 

438 fdel(self) 

439 self.add(name, tzp.localize_utc(value)) 

440 

441 def fdel(self: Component): 

442 """Delete the property.""" 

443 self.pop(name, None) 

444 

445 return property(fget, fset, fdel, doc=docs) 

446 

447 

448def single_string_property( 

449 name: str, docs: str, other_name: Optional[str] = None, default: str = "" 

450) -> property: 

451 """Create a property to access a single string value.""" 

452 

453 def fget(self: Component) -> str: 

454 """Get the value.""" 

455 result = self.get( 

456 name, None if other_name is None else self.get(other_name, None) 

457 ) 

458 if result is None or result == []: 

459 return default 

460 if isinstance(result, list): 

461 return result[0] 

462 return result 

463 

464 def fset(self: Component, value: Optional[str]): 

465 """Set the value. 

466 

467 Setting the value to None will delete it. 

468 """ 

469 fdel(self) 

470 if value is not None: 

471 self.add(name, value) 

472 

473 def fdel(self: Component): 

474 """Delete the property.""" 

475 self.pop(name, None) 

476 if other_name is not None: 

477 self.pop(other_name, None) 

478 

479 return property(fget, fset, fdel, doc=docs) 

480 

481 

482color_property = single_string_property( 

483 "COLOR", 

484 """This property specifies a color used for displaying the component. 

485 

486 This implements :rfc:`7986` ``COLOR`` property. 

487 

488 Property Parameters: 

489 IANA and non-standard property parameters can 

490 be specified on this property. 

491 

492 Conformance: 

493 This property can be specified once in an iCalendar 

494 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components. 

495 

496 Description: 

497 This property specifies a color that clients MAY use 

498 when presenting the relevant data to a user. Typically, this 

499 would appear as the "background" color of events or tasks. The 

500 value is a case-insensitive color name taken from the CSS3 set of 

501 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_. 

502 

503 Example: 

504 ``"turquoise"``, ``"#ffffff"`` 

505 

506 .. code-block:: pycon 

507 

508 >>> from icalendar import Todo 

509 >>> todo = Todo() 

510 >>> todo.color = "green" 

511 >>> print(todo.to_ical()) 

512 BEGIN:VTODO 

513 COLOR:green 

514 END:VTODO 

515 """, 

516) 

517 

518sequence_property = single_int_property( 

519 "SEQUENCE", 

520 0, 

521 """This property defines the revision sequence number of the calendar component within a sequence of revisions. 

522 

523Value Type: 

524 INTEGER 

525 

526Property Parameters: 

527 IANA and non-standard property parameters can be specified on this property. 

528 

529Conformance: 

530 The property can be specified in "VEVENT", "VTODO", or 

531 "VJOURNAL" calendar component. 

532 

533Description: 

534 When a calendar component is created, its sequence 

535 number is 0. It is monotonically incremented by the "Organizer's" 

536 CUA each time the "Organizer" makes a significant revision to the 

537 calendar component. 

538 

539 The "Organizer" includes this property in an iCalendar object that 

540 it sends to an "Attendee" to specify the current version of the 

541 calendar component. 

542 

543 The "Attendee" includes this property in an iCalendar object that 

544 it sends to the "Organizer" to specify the version of the calendar 

545 component to which the "Attendee" is referring. 

546 

547 A change to the sequence number is not the mechanism that an 

548 "Organizer" uses to request a response from the "Attendees". The 

549 "RSVP" parameter on the "ATTENDEE" property is used by the 

550 "Organizer" to indicate that a response from the "Attendees" is 

551 requested. 

552 

553 Recurrence instances of a recurring component MAY have different 

554 sequence numbers. 

555 

556Examples: 

557 The following is an example of this property for a calendar 

558 component that was just created by the "Organizer": 

559 

560 .. code-block:: pycon 

561 

562 >>> from icalendar import Event 

563 >>> event = Event() 

564 >>> event.sequence 

565 0 

566 

567 The following is an example of this property for a calendar 

568 component that has been revised 10 different times by the 

569 "Organizer": 

570 

571 .. code-block:: pycon 

572 

573 >>> from icalendar import Calendar 

574 >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal") 

575 >>> event = calendar.events[0] 

576 >>> event.sequence 

577 10 

578 """, # noqa: E501 

579) 

580 

581 

582def _get_categories(component: Component) -> list[str]: 

583 """Get all the categories.""" 

584 categories: Optional[vCategory | list[vCategory]] = component.get("CATEGORIES") 

585 if isinstance(categories, list): 

586 _set_categories( 

587 component, 

588 list(itertools.chain.from_iterable(cat.cats for cat in categories)), 

589 ) 

590 return _get_categories(component) 

591 if categories is None: 

592 categories = vCategory([]) 

593 component.add("CATEGORIES", categories) 

594 return categories.cats 

595 

596 

597def _set_categories(component: Component, cats: Optional[Sequence[str]]) -> None: 

598 """Set the categories.""" 

599 if not cats and cats != []: 

600 _del_categories(component) 

601 return 

602 component["CATEGORIES"] = categories = vCategory(cats) 

603 if isinstance(cats, list): 

604 cats.clear() 

605 cats.extend(categories.cats) 

606 categories.cats = cats 

607 

608 

609def _del_categories(component: Component) -> None: 

610 """Delete the categories.""" 

611 component.pop("CATEGORIES", None) 

612 

613 

614categories_property = property( 

615 _get_categories, 

616 _set_categories, 

617 _del_categories, 

618 """This property defines the categories for a component. 

619 

620Property Parameters: 

621 IANA, non-standard, and language property parameters can be specified on this 

622 property. 

623 

624Conformance: 

625 The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar 

626 components. 

627 Since :rfc:`7986` it can also be defined on a "VCALENDAR" component. 

628 

629Description: 

630 This property is used to specify categories or subtypes 

631 of the calendar component. The categories are useful in searching 

632 for a calendar component of a particular type and category. 

633 Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components, 

634 more than one category can be specified as a COMMA-separated list 

635 of categories. 

636 

637Example: 

638 Below, we add the categories to an event: 

639 

640 .. code-block:: pycon 

641 

642 >>> from icalendar import Event 

643 >>> event = Event() 

644 >>> event.categories = ["Work", "Meeting"] 

645 >>> print(event.to_ical()) 

646 BEGIN:VEVENT 

647 CATEGORIES:Work,Meeting 

648 END:VEVENT 

649 >>> event.categories.append("Lecture") 

650 >>> event.categories == ["Work", "Meeting", "Lecture"] 

651 True 

652 

653.. note:: 

654 

655 At present, we do not take the LANGUAGE parameter into account. 

656 

657.. seealso:: 

658 

659 :attr:`Component.concepts` 

660""", 

661) 

662 

663 

664def _get_attendees(self: Component) -> list[vCalAddress]: 

665 """Get attendees.""" 

666 value = self.get("ATTENDEE") 

667 if value is None: 

668 value = [] 

669 self["ATTENDEE"] = value 

670 return value 

671 if isinstance(value, vCalAddress): 

672 return [value] 

673 return value 

674 

675 

676def _set_attendees(self: Component, value: list[vCalAddress] | vCalAddress | None): 

677 """Set attendees.""" 

678 _del_attendees(self) 

679 if value is None: 

680 return 

681 if not isinstance(value, list): 

682 value = [value] 

683 self["ATTENDEE"] = value 

684 

685 

686def _del_attendees(self: Component): 

687 """Delete all attendees.""" 

688 self.pop("ATTENDEE", None) 

689 

690 

691attendees_property = property( 

692 _get_attendees, 

693 _set_attendees, 

694 _del_attendees, 

695 """ATTENDEE defines one or more "Attendees" within a calendar component. 

696 

697Conformance: 

698 This property MUST be specified in an iCalendar object 

699 that specifies a group-scheduled calendar entity. This property 

700 MUST NOT be specified in an iCalendar object when publishing the 

701 calendar information (e.g., NOT in an iCalendar object that 

702 specifies the publication of a calendar user's busy time, event, 

703 to-do, or journal). This property is not specified in an 

704 iCalendar object that specifies only a time zone definition or 

705 that defines calendar components that are not group-scheduled 

706 components, but are components only on a single user's calendar. 

707 

708Description: 

709 This property MUST only be specified within calendar 

710 components to specify participants, non-participants, and the 

711 chair of a group-scheduled calendar entity. The property is 

712 specified within an "EMAIL" category of the "VALARM" calendar 

713 component to specify an email address that is to receive the email 

714 type of iCalendar alarm. 

715 

716Examples: 

717 Add a new attendee to an existing event. 

718 

719 .. code-block:: pycon 

720 

721 >>> from icalendar import Event, vCalAddress 

722 >>> event = Event() 

723 >>> event.attendees.append(vCalAddress("mailto:me@my-domain.com")) 

724 >>> print(event.to_ical()) 

725 BEGIN:VEVENT 

726 ATTENDEE:mailto:me@my-domain.com 

727 END:VEVENT 

728 

729 Create an email alarm with several attendees: 

730 

731 >>> from icalendar import Alarm, vCalAddress 

732 >>> alarm = Alarm.new(attendees = [ 

733 ... vCalAddress("mailto:me@my-domain.com"), 

734 ... vCalAddress("mailto:you@my-domain.com"), 

735 ... ], summary = "Email alarm") 

736 >>> print(alarm.to_ical()) 

737 BEGIN:VALARM 

738 ATTENDEE:mailto:me@my-domain.com 

739 ATTENDEE:mailto:you@my-domain.com 

740 SUMMARY:Email alarm 

741 END:VALARM 

742""", 

743) 

744 

745uid_property = single_string_property( 

746 "UID", 

747 """UID specifies the persistent, globally unique identifier for a component. 

748 

749We recommend using :func:`uuid.uuid4` to generate new values. 

750 

751Returns: 

752 The value of the UID property as a string or ``""`` if no value is set. 

753 

754Description: 

755 The "UID" itself MUST be a globally unique identifier. 

756 The generator of the identifier MUST guarantee that the identifier 

757 is unique. 

758 

759 This is the method for correlating scheduling messages with the 

760 referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component. 

761 The full range of calendar components specified by a recurrence 

762 set is referenced by referring to just the "UID" property value 

763 corresponding to the calendar component. The "RECURRENCE-ID" 

764 property allows the reference to an individual instance within the 

765 recurrence set. 

766 

767 This property is an important method for group-scheduling 

768 applications to match requests with later replies, modifications, 

769 or deletion requests. Calendaring and scheduling applications 

770 MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL" 

771 calendar components to assure interoperability with other group- 

772 scheduling applications. This identifier is created by the 

773 calendar system that generates an iCalendar object. 

774 

775 Implementations MUST be able to receive and persist values of at 

776 least 255 octets for this property, but they MUST NOT truncate 

777 values in the middle of a UTF-8 multi-octet sequence. 

778 

779 :rfc:`7986` states that UID can be used, for 

780 example, to identify duplicate calendar streams that a client may 

781 have been given access to. It can be used in conjunction with the 

782 "LAST-MODIFIED" property also specified on the "VCALENDAR" object 

783 to identify the most recent version of a calendar. 

784 

785Conformance: 

786 :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO", 

787 and "VJOURNAL" calendar components. 

788 :rfc:`7986` modifies the definition of the "UID" property to 

789 allow it to be defined in an iCalendar object. 

790 :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique 

791 identifier to be specified. The value of this property can then be used 

792 to refer uniquely to the "VALARM" component. 

793 

794 This property can be specified once only. 

795 

796Security: 

797 :rfc:`7986` states that UID values MUST NOT include any data that 

798 might identify a user, host, domain, or any other security- or 

799 privacy-sensitive information. It is RECOMMENDED that calendar user 

800 agents now generate "UID" values that are hex-encoded random 

801 Universally Unique Identifier (UUID) values as defined in 

802 Sections 4.4 and 4.5 of :rfc:`4122`. 

803 You can use the :mod:`uuid` module to generate new UUIDs. 

804 

805Compatibility: 

806 For Alarms, ``X-ALARMUID`` is also considered. 

807 

808Examples: 

809 The following is an example of such a property value: 

810 ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``. 

811 

812 Set the UID of a calendar: 

813 

814 .. code-block:: pycon 

815 

816 >>> from icalendar import Calendar 

817 >>> from uuid import uuid4 

818 >>> calendar = Calendar() 

819 >>> calendar.uid = uuid4() 

820 >>> print(calendar.to_ical()) 

821 BEGIN:VCALENDAR 

822 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 

823 END:VCALENDAR 

824 

825""", 

826) 

827 

828summary_property = multi_language_text_property( 

829 "SUMMARY", 

830 None, 

831 """SUMMARY defines a short summary or subject for the calendar component. 

832 

833Property Parameters: 

834 IANA, non-standard, alternate text 

835 representation, and language property parameters can be specified 

836 on this property. 

837 

838Conformance: 

839 The property can be specified in "VEVENT", "VTODO", 

840 "VJOURNAL", or "VALARM" calendar components. 

841 

842Description: 

843 This property is used in the "VEVENT", "VTODO", and 

844 "VJOURNAL" calendar components to capture a short, one-line 

845 summary about the activity or journal entry. 

846 

847 This property is used in the "VALARM" calendar component to 

848 capture the subject of an EMAIL category of alarm. 

849 

850Examples: 

851 The following is an example of this property: 

852 

853 .. code-block:: pycon 

854 

855 SUMMARY:Department Party 

856""", 

857) 

858 

859description_property = multi_language_text_property( 

860 "DESCRIPTION", 

861 None, 

862 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property. 

863 

864Property Parameters: 

865 IANA, non-standard, alternate text 

866 representation, and language property parameters can be specified 

867 on this property. 

868 

869Conformance: 

870 The property can be specified in the "VEVENT", "VTODO", 

871 "VJOURNAL", or "VALARM" calendar components. The property can be 

872 specified multiple times only within a "VJOURNAL" calendar 

873 component. 

874 

875Description: 

876 This property is used in the "VEVENT" and "VTODO" to 

877 capture lengthy textual descriptions associated with the activity. 

878 

879 This property is used in the "VALARM" calendar component to 

880 capture the display text for a DISPLAY category of alarm, and to 

881 capture the body text for an EMAIL category of alarm. 

882 

883Examples: 

884 The following is an example of this property with formatted 

885 line breaks in the property value: 

886 

887 .. code-block:: pycon 

888 

889 DESCRIPTION:Meeting to provide technical review for "Phoenix" 

890 design.\\nHappy Face Conference Room. Phoenix design team 

891 MUST attend this meeting.\\nRSVP to team leader. 

892 

893 """, # noqa: E501 

894) 

895 

896 

897def create_single_property( 

898 prop: str, 

899 value_attr: Optional[str], 

900 value_type: tuple[type], 

901 type_def: type, 

902 doc: str, 

903 vProp: type = vDDDTypes, # noqa: N803 

904): 

905 """Create a single property getter and setter. 

906 

907 :param prop: The name of the property. 

908 :param value_attr: The name of the attribute to get the value from. 

909 :param value_type: The type of the value. 

910 :param type_def: The type of the property. 

911 :param doc: The docstring of the property. 

912 :param vProp: The type of the property from :mod:`icalendar.prop`. 

913 """ 

914 

915 def p_get(self: Component): 

916 default = object() 

917 result = self.get(prop, default) 

918 if result is default: 

919 return None 

920 if isinstance(result, list): 

921 raise InvalidCalendar(f"Multiple {prop} defined.") 

922 value = result if value_attr is None else getattr(result, value_attr, result) 

923 if not isinstance(value, value_type): 

924 raise InvalidCalendar( 

925 f"{prop} must be either a " 

926 f"{' or '.join(t.__name__ for t in value_type)}," 

927 f" not {value}." 

928 ) 

929 return value 

930 

931 def p_set(self: Component, value) -> None: 

932 if value is None: 

933 p_del(self) 

934 return 

935 if not isinstance(value, value_type): 

936 raise TypeError( 

937 f"Use {' or '.join(t.__name__ for t in value_type)}, " 

938 f"not {type(value).__name__}." 

939 ) 

940 self[prop] = vProp(value) 

941 if prop in self.exclusive: 

942 for other_prop in self.exclusive: 

943 if other_prop != prop: 

944 self.pop(other_prop, None) 

945 

946 p_set.__annotations__["value"] = p_get.__annotations__["return"] = Optional[ 

947 type_def 

948 ] 

949 

950 def p_del(self: Component): 

951 self.pop(prop) 

952 

953 p_doc = f"""The {prop} property. 

954 

955 {doc} 

956 

957 Accepted values: {", ".join(t.__name__ for t in value_type)}. 

958 If the attribute has invalid values, we raise InvalidCalendar. 

959 If the value is absent, we return None. 

960 You can also delete the value with del or by setting it to None. 

961 """ 

962 return property(p_get, p_set, p_del, p_doc) 

963 

964 

965X_MOZ_SNOOZE_TIME_property = single_utc_property( 

966 "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed." 

967) 

968X_MOZ_LASTACK_property = single_utc_property( 

969 "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged." 

970) 

971 

972 

973def property_get_duration(self: Component) -> Optional[timedelta]: 

974 """Getter for property DURATION.""" 

975 default = object() 

976 duration = self.get("duration", default) 

977 if isinstance(duration, vDDDTypes): 

978 return duration.dt 

979 if isinstance(duration, vDuration): 

980 return duration.td 

981 if duration is not default and not isinstance(duration, timedelta): 

982 raise InvalidCalendar( 

983 f"DURATION must be a timedelta, not {type(duration).__name__}." 

984 ) 

985 return None 

986 

987 

988def property_set_duration(self: Component, value: Optional[timedelta]): 

989 """Setter for property DURATION.""" 

990 if value is None: 

991 self.pop("duration", None) 

992 return 

993 if not isinstance(value, timedelta): 

994 raise TypeError(f"Use timedelta, not {type(value).__name__}.") 

995 self["duration"] = vDuration(value) 

996 self.pop("DTEND") 

997 self.pop("DUE") 

998 

999 

1000def property_del_duration(self: Component): 

1001 """Delete property DURATION.""" 

1002 self.pop("DURATION") 

1003 

1004 

1005property_doc_duration_template = """The DURATION property. 

1006 

1007The "DTSTART" property for a "{component}" specifies the inclusive 

1008start of the {component}. 

1009The "DURATION" property in conjunction with the DTSTART property 

1010for a "{component}" calendar component specifies the non-inclusive end 

1011of the event. 

1012 

1013If you would like to calculate the duration of a {component}, do not use this. 

1014Instead use the duration property (lower case). 

1015""" 

1016 

1017 

1018def duration_property(component: str) -> property: 

1019 """Return the duration property.""" 

1020 return property( 

1021 property_get_duration, 

1022 property_set_duration, 

1023 property_del_duration, 

1024 property_doc_duration_template.format(component=component), 

1025 ) 

1026 

1027 

1028def multi_text_property(name: str, docs: str) -> property: 

1029 """Get a property that can occur several times and is text. 

1030 

1031 Examples: Journal.descriptions, Event.comments 

1032 """ 

1033 

1034 def fget(self: Component) -> list[str]: 

1035 """Get the values.""" 

1036 descriptions = self.get(name) 

1037 if descriptions is None: 

1038 return [] 

1039 if not isinstance(descriptions, SEQUENCE_TYPES): 

1040 return [descriptions] 

1041 return descriptions 

1042 

1043 def fset(self: Component, values: Optional[str | Sequence[str]]): 

1044 """Set the values.""" 

1045 fdel(self) 

1046 if values is None: 

1047 return 

1048 if isinstance(values, str): 

1049 self.add(name, values) 

1050 else: 

1051 for description in values: 

1052 self.add(name, description) 

1053 

1054 def fdel(self: Component): 

1055 """Delete the values.""" 

1056 self.pop(name) 

1057 

1058 return property(fget, fset, fdel, docs) 

1059 

1060 

1061descriptions_property = multi_text_property( 

1062 "DESCRIPTION", 

1063 """DESCRIPTION provides a more complete description of the calendar component than that provided by the "SUMMARY" property. 

1064 

1065Property Parameters: 

1066 IANA, non-standard, alternate text 

1067 representation, and language property parameters can be specified 

1068 on this property. 

1069 

1070Conformance: 

1071 The property can be 

1072 specified multiple times only within a "VJOURNAL" calendar component. 

1073 

1074Description: 

1075 This property is used in the "VJOURNAL" calendar component to 

1076 capture one or more textual journal entries. 

1077 

1078Examples: 

1079 The following is an example of this property with formatted 

1080 line breaks in the property value: 

1081 

1082 .. code-block:: pycon 

1083 

1084 DESCRIPTION:Meeting to provide technical review for "Phoenix" 

1085 design.\\nHappy Face Conference Room. Phoenix design team 

1086 MUST attend this meeting.\\nRSVP to team leader. 

1087 

1088""", # noqa: E501 

1089) 

1090 

1091comments_property = multi_text_property( 

1092 "COMMENT", 

1093 """COMMENT is used to specify a comment to the calendar user. 

1094 

1095Purpose: 

1096 This property specifies non-processing information intended 

1097 to provide a comment to the calendar user. 

1098 

1099Conformance: 

1100 In :rfc:`5545`, this property can be specified multiple times in 

1101 "VEVENT", "VTODO", "VJOURNAL", and "VFREEBUSY" calendar components 

1102 as well as in the "STANDARD" and "DAYLIGHT" sub-components. 

1103 In :rfc:`7953`, this property can be specified multiple times in 

1104 "VAVAILABILITY" and "VAVAILABLE". 

1105 

1106Property Parameters: 

1107 IANA, non-standard, alternate text 

1108 representation, and language property parameters can be specified 

1109 on this property. 

1110 

1111""", 

1112) 

1113 

1114 

1115def _get_organizer(self: Component) -> Optional[vCalAddress]: 

1116 """ORGANIZER defines the organizer for a calendar component. 

1117 

1118 Property Parameters: 

1119 IANA, non-standard, language, common name, 

1120 directory entry reference, and sent-by property parameters can be 

1121 specified on this property. 

1122 

1123 Conformance: 

1124 This property MUST be specified in an iCalendar object 

1125 that specifies a group-scheduled calendar entity. This property 

1126 MUST be specified in an iCalendar object that specifies the 

1127 publication of a calendar user's busy time. This property MUST 

1128 NOT be specified in an iCalendar object that specifies only a time 

1129 zone definition or that defines calendar components that are not 

1130 group-scheduled components, but are components only on a single 

1131 user's calendar. 

1132 

1133 Description: 

1134 This property is specified within the "VEVENT", 

1135 "VTODO", and "VJOURNAL" calendar components to specify the 

1136 organizer of a group-scheduled calendar entity. The property is 

1137 specified within the "VFREEBUSY" calendar component to specify the 

1138 calendar user requesting the free or busy time. When publishing a 

1139 "VFREEBUSY" calendar component, the property is used to specify 

1140 the calendar that the published busy time came from. 

1141 

1142 The property has the property parameters "CN", for specifying the 

1143 common or display name associated with the "Organizer", "DIR", for 

1144 specifying a pointer to the directory information associated with 

1145 the "Organizer", "SENT-BY", for specifying another calendar user 

1146 that is acting on behalf of the "Organizer". The non-standard 

1147 parameters may also be specified on this property. If the 

1148 "LANGUAGE" property parameter is specified, the identified 

1149 language applies to the "CN" parameter value. 

1150 """ 

1151 return self.get("ORGANIZER") 

1152 

1153 

1154def _set_organizer(self: Component, value: Optional[vCalAddress | str]): 

1155 """Set the value.""" 

1156 _del_organizer(self) 

1157 if value is not None: 

1158 self.add("ORGANIZER", value) 

1159 

1160 

1161def _del_organizer(self: Component): 

1162 """Delete the value.""" 

1163 self.pop("ORGANIZER") 

1164 

1165 

1166organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1167 

1168 

1169def single_string_enum_property( 

1170 name: str, enum: type[StrEnum], default: StrEnum, docs: str 

1171) -> property: 

1172 """Create a property to access a single string value and convert it to an enum.""" 

1173 prop = single_string_property(name, docs, default=default) 

1174 

1175 def fget(self: Component) -> StrEnum: 

1176 """Get the value.""" 

1177 value = prop.fget(self) 

1178 if value == default: 

1179 return default 

1180 return enum(str(value)) 

1181 

1182 def fset(self: Component, value: str | StrEnum | None) -> None: 

1183 """Set the value.""" 

1184 if value == "": 

1185 value = None 

1186 prop.fset(self, value) 

1187 

1188 return property(fget, fset, prop.fdel, doc=docs) 

1189 

1190 

1191busy_type_property = single_string_enum_property( 

1192 "BUSYTYPE", 

1193 BUSYTYPE, 

1194 BUSYTYPE.BUSY_UNAVAILABLE, 

1195 """BUSYTYPE specifies the default busy time type. 

1196 

1197Returns: 

1198 :class:`icalendar.enums.BUSYTYPE` 

1199 

1200Description: 

1201 This property is used to specify the default busy time 

1202 type. The values correspond to those used by the FBTYPE" 

1203 parameter used on a "FREEBUSY" property, with the exception that 

1204 the "FREE" value is not used in this property. If not specified 

1205 on a component that allows this property, the default is "BUSY- 

1206 UNAVAILABLE". 

1207""", 

1208) 

1209 

1210priority_property = single_int_property( 

1211 "PRIORITY", 

1212 0, 

1213 """ 

1214 

1215Conformance: 

1216 This property can be specified in "VEVENT" and "VTODO" calendar components 

1217 according to :rfc:`5545`. 

1218 :rfc:`7953` adds this property to "VAVAILABILITY". 

1219 

1220Description: 

1221 This priority is specified as an integer in the range 0 

1222 to 9. A value of 0 specifies an undefined priority. A value of 1 

1223 is the highest priority. A value of 2 is the second highest 

1224 priority. Subsequent numbers specify a decreasing ordinal 

1225 priority. A value of 9 is the lowest priority. 

1226 

1227 A CUA with a three-level priority scheme of "HIGH", "MEDIUM", and 

1228 "LOW" is mapped into this property such that a property value in 

1229 the range of 1 to 4 specifies "HIGH" priority. A value of 5 is 

1230 the normal or "MEDIUM" priority. A value in the range of 6 to 9 

1231 is "LOW" priority. 

1232 

1233 A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ..., 

1234 "C3" is mapped into this property such that a property value of 1 

1235 specifies "A1", a property value of 2 specifies "A2", a property 

1236 value of 3 specifies "A3", and so forth up to a property value of 

1237 9 specifies "C3". 

1238 

1239 Other integer values are reserved for future use. 

1240 

1241 Within a "VEVENT" calendar component, this property specifies a 

1242 priority for the event. This property may be useful when more 

1243 than one event is scheduled for a given time period. 

1244 

1245 Within a "VTODO" calendar component, this property specified a 

1246 priority for the to-do. This property is useful in prioritizing 

1247 multiple action items for a given time period. 

1248""", 

1249) 

1250 

1251class_property = single_string_enum_property( 

1252 "CLASS", 

1253 CLASS, 

1254 CLASS.PUBLIC, 

1255 """CLASS specifies the class of the calendar component. 

1256 

1257Returns: 

1258 :class:`icalendar.enums.CLASS` 

1259 

1260Description: 

1261 An access classification is only one component of the 

1262 general security system within a calendar application. It 

1263 provides a method of capturing the scope of the access the 

1264 calendar owner intends for information within an individual 

1265 calendar entry. The access classification of an individual 

1266 iCalendar component is useful when measured along with the other 

1267 security components of a calendar system (e.g., calendar user 

1268 authentication, authorization, access rights, access role, etc.). 

1269 Hence, the semantics of the individual access classifications 

1270 cannot be completely defined by this memo alone. Additionally, 

1271 due to the "blind" nature of most exchange processes using this 

1272 memo, these access classifications cannot serve as an enforcement 

1273 statement for a system receiving an iCalendar object. Rather, 

1274 they provide a method for capturing the intention of the calendar 

1275 owner for the access to the calendar component. If not specified 

1276 in a component that allows this property, the default value is 

1277 PUBLIC. Applications MUST treat x-name and iana-token values they 

1278 don't recognize the same way as they would the PRIVATE value. 

1279""", 

1280) 

1281 

1282transparency_property = single_string_enum_property( 

1283 "TRANSP", 

1284 TRANSP, 

1285 TRANSP.OPAQUE, 

1286 """TRANSP defines whether or not an event is transparent to busy time searches. 

1287 

1288Returns: 

1289 :class:`icalendar.enums.TRANSP` 

1290 

1291Description: 

1292 Time Transparency is the characteristic of an event 

1293 that determines whether it appears to consume time on a calendar. 

1294 Events that consume actual time for the individual or resource 

1295 associated with the calendar SHOULD be recorded as OPAQUE, 

1296 allowing them to be detected by free/busy time searches. Other 

1297 events, which do not take up the individual's (or resource's) time 

1298 SHOULD be recorded as TRANSPARENT, making them invisible to free/ 

1299 busy time searches. 

1300""", 

1301) 

1302status_property = single_string_enum_property( 

1303 "STATUS", 

1304 STATUS, 

1305 "", 

1306 """STATUS defines the overall status or confirmation for the calendar component. 

1307 

1308Returns: 

1309 :class:`icalendar.enums.STATUS` 

1310 

1311The default value is ``""``. 

1312 

1313Description: 

1314 In a group-scheduled calendar component, the property 

1315 is used by the "Organizer" to provide a confirmation of the event 

1316 to the "Attendees". For example in a "VEVENT" calendar component, 

1317 the "Organizer" can indicate that a meeting is tentative, 

1318 confirmed, or cancelled. In a "VTODO" calendar component, the 

1319 "Organizer" can indicate that an action item needs action, is 

1320 completed, is in process or being worked on, or has been 

1321 cancelled. In a "VJOURNAL" calendar component, the "Organizer" 

1322 can indicate that a journal entry is draft, final, or has been 

1323 cancelled or removed. 

1324""", 

1325) 

1326 

1327url_property = single_string_property( 

1328 "URL", 

1329 """A Uniform Resource Locator (URL) associated with the iCalendar object. 

1330 

1331Description: 

1332 This property may be used in a calendar component to 

1333 convey a location where a more dynamic rendition of the calendar 

1334 information associated with the calendar component can be found. 

1335 This memo does not attempt to standardize the form of the URI, nor 

1336 the format of the resource pointed to by the property value. If 

1337 the URL property and Content-Location MIME header are both 

1338 specified, they MUST point to the same resource. 

1339 

1340Conformance: 

1341 This property can be specified once in the "VEVENT", 

1342 "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components. 

1343 Since :rfc:`7986`, this property can also be defined on a "VCALENDAR". 

1344 

1345Example: 

1346 The following is an example of this property: 

1347 

1348 .. code-block:: text 

1349 

1350 URL:http://example.com/pub/calendars/jsmith/mytime.ics 

1351 

1352""", 

1353) 

1354 

1355source_property = single_string_property( 

1356 "SOURCE", 

1357 """A URI from where calendar data can be refreshed. 

1358 

1359Description: 

1360 This property identifies a location where a client can 

1361 retrieve updated data for the calendar. Clients SHOULD honor any 

1362 specified "REFRESH-INTERVAL" value when periodically retrieving 

1363 data. Note that this property differs from the "URL" property in 

1364 that "URL" is meant to provide an alternative representation of 

1365 the calendar data rather than the original location of the data. 

1366 

1367Conformance: 

1368 This property can be specified once in an iCalendar object. 

1369 

1370Example: 

1371 The following is an example of this property: 

1372 

1373 .. code-block:: text 

1374 

1375 SOURCE;VALUE=URI:https://example.com/holidays.ics 

1376 

1377""", 

1378) 

1379 

1380location_property = multi_language_text_property( 

1381 "LOCATION", 

1382 None, 

1383 """The intended venue for the activity defined by a calendar component. 

1384 

1385Property Parameters: 

1386 IANA, non-standard, alternate text 

1387 representation, and language property parameters can be specified 

1388 on this property. 

1389 

1390Conformance: 

1391 Since :rfc:`5545`, this property can be specified in "VEVENT" or "VTODO" 

1392 calendar component. 

1393 :rfc:`7953` adds this property to "VAVAILABILITY" and "VAVAILABLE". 

1394 

1395Description: 

1396 Specific venues such as conference or meeting rooms may 

1397 be explicitly specified using this property. An alternate 

1398 representation may be specified that is a URI that points to 

1399 directory information with more structured specification of the 

1400 location. For example, the alternate representation may specify 

1401 either an LDAP URL :rfc:`4516` pointing to an LDAP server entry or a 

1402 CID URL :rfc:`2392` pointing to a MIME body part containing a 

1403 Virtual-Information Card (vCard) :rfc:`2426` for the location. 

1404 

1405""", 

1406) 

1407 

1408contacts_property = multi_text_property( 

1409 "CONTACT", 

1410 """Contact information associated with the calendar component. 

1411 

1412Purpose: 

1413 This property is used to represent contact information or 

1414 alternately a reference to contact information associated with the 

1415 calendar component. 

1416 

1417Property Parameters: 

1418 IANA, non-standard, alternate text 

1419 representation, and language property parameters can be specified 

1420 on this property. 

1421 

1422Conformance: 

1423 In :rfc:`5545`, this property can be specified in a "VEVENT", "VTODO", 

1424 "VJOURNAL", or "VFREEBUSY" calendar component. 

1425 In :rfc:`7953`, this property can be specified in a "VAVAILABILITY" 

1426 amd "VAVAILABLE" calendar component. 

1427 

1428Description: 

1429 The property value consists of textual contact 

1430 information. An alternative representation for the property value 

1431 can also be specified that refers to a URI pointing to an 

1432 alternate form, such as a vCard :rfc:`2426`, for the contact 

1433 information. 

1434 

1435Example: 

1436 The following is an example of this property referencing 

1437 textual contact information: 

1438 

1439 .. code-block:: text 

1440 

1441 CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234 

1442 

1443 The following is an example of this property with an alternate 

1444 representation of an LDAP URI to a directory entry containing the 

1445 contact information: 

1446 

1447 .. code-block:: text 

1448 

1449 CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\\, 

1450 c=US???(cn=Jim%20Dolittle)":Jim Dolittle\\, ABC Industries\\, 

1451 +1-919-555-1234 

1452 

1453 The following is an example of this property with an alternate 

1454 representation of a MIME body part containing the contact 

1455 information, such as a vCard :rfc:`2426` embedded in a text/ 

1456 directory media type :rfc:`2425`: 

1457 

1458 .. code-block:: text 

1459 

1460 CONTACT;ALTREP="CID:part3.msg970930T083000SILVER@example.com": 

1461 Jim Dolittle\\, ABC Industries\\, +1-919-555-1234 

1462 

1463 The following is an example of this property referencing a network 

1464 resource, such as a vCard :rfc:`2426` object containing the contact 

1465 information: 

1466 

1467 .. code-block:: text 

1468 

1469 CONTACT;ALTREP="http://example.com/pdi/jdoe.vcf":Jim 

1470 Dolittle\\, ABC Industries\\, +1-919-555-1234 

1471""", 

1472) 

1473 

1474 

1475def timezone_datetime_property(name: str, docs: str): 

1476 """Create a property to access the values with a proper timezone.""" 

1477 

1478 return single_utc_property(name, docs) 

1479 

1480 

1481rfc_7953_dtstart_property = timezone_datetime_property( 

1482 "DTSTART", 

1483 """Start of the component. 

1484 

1485 This is almost the same as :attr:`Event.DTSTART` with one exception: 

1486 The values MUST have a timezone and DATE is not allowed. 

1487 

1488 Description: 

1489 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in 

1490 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be 

1491 "DATE-TIME" values specified as either the date with UTC time or 

1492 the date with local time and a time zone reference. 

1493 

1494 """, 

1495) 

1496 

1497rfc_7953_dtend_property = timezone_datetime_property( 

1498 "DTEND", 

1499 """Start of the component. 

1500 

1501 This is almost the same as :attr:`Event.DTEND` with one exception: 

1502 The values MUST have a timezone and DATE is not allowed. 

1503 

1504 Description: 

1505 :rfc:`7953`: If specified, the "DTSTART" and "DTEND" properties in 

1506 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be 

1507 "DATE-TIME" values specified as either the date with UTC time or 

1508 the date with local time and a time zone reference. 

1509 """, 

1510) 

1511 

1512 

1513@property 

1514def rfc_7953_duration_property(self) -> Optional[timedelta]: 

1515 """Compute the duration of this component. 

1516 

1517 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None. 

1518 Otherwise, the duration is calculated from :attr:`DTSTART` and 

1519 :attr:`DTEND`/:attr:`DURATION`. 

1520 

1521 This is in accordance with :rfc:`7953`: 

1522 If "DTEND" or "DURATION" are not present, then the end time is unbounded. 

1523 """ 

1524 duration = self.DURATION 

1525 if duration: 

1526 return duration 

1527 end = self.DTEND 

1528 if end is None: 

1529 return None 

1530 start = self.DTSTART 

1531 if start is None: 

1532 raise IncompleteComponent("Cannot compute duration without start.") 

1533 return end - start 

1534 

1535 

1536@property 

1537def rfc_7953_end_property(self) -> Optional[timedelta]: 

1538 """Compute the duration of this component. 

1539 

1540 If there is no :attr:`DTEND` or :attr:`DURATION` set, this is None. 

1541 Otherwise, the duration is calculated from :attr:`DTSTART` and 

1542 :attr:`DTEND`/:attr:`DURATION`. 

1543 

1544 This is in accordance with :rfc:`7953`: 

1545 If "DTEND" or "DURATION" are not present, then the end time is unbounded. 

1546 """ 

1547 duration = self.DURATION 

1548 if duration: 

1549 start = self.DTSTART 

1550 if start is None: 

1551 raise IncompleteComponent("Cannot compute end without start.") 

1552 return start + duration 

1553 end = self.DTEND 

1554 if end is None: 

1555 return None 

1556 return end 

1557 

1558 

1559@rfc_7953_end_property.setter 

1560def rfc_7953_end_property(self, value: datetime): 

1561 self.DTEND = value 

1562 

1563 

1564@rfc_7953_end_property.deleter 

1565def rfc_7953_end_property(self): 

1566 del self.DTEND 

1567 

1568 

1569def get_start_end_duration_with_validation( 

1570 component: Component, 

1571 start_property: str, 

1572 end_property: str, 

1573 component_name: str, 

1574) -> tuple[date | datetime | None, date | datetime | None, timedelta | None]: 

1575 """ 

1576 Validate the component and return start, end, and duration. 

1577 

1578 This tests validity according to :rfc:`5545` rules 

1579 for ``Event`` and ``Todo`` components. 

1580 

1581 Args: 

1582 component: The component to validate, either ``Event`` or ``Todo``. 

1583 start_property: The start property name, ``DTSTART``. 

1584 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1585 ``DUE`` for ``Todo``. 

1586 component_name: The component name for error messages, 

1587 either ``VEVENT`` or ``VTODO``. 

1588 

1589 Returns: 

1590 tuple: (start, end, duration) values from the component. 

1591 

1592 Raises: 

1593 InvalidCalendar: If the component violates RFC 5545 constraints. 

1594 

1595 """ 

1596 start = getattr(component, start_property, None) 

1597 end = getattr(component, end_property, None) 

1598 duration = component.DURATION 

1599 

1600 # RFC 5545: Only one of end property and DURATION may be present 

1601 if duration is not None and end is not None: 

1602 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1603 msg = ( 

1604 f"Only one of {end_name} and DURATION " 

1605 f"may be in a {component_name}, not both." 

1606 ) 

1607 raise InvalidCalendar(msg) 

1608 

1609 # RFC 5545: When DTSTART is a date, DURATION must be of days or weeks 

1610 if ( 

1611 start is not None 

1612 and is_date(start) 

1613 and duration is not None 

1614 and duration.seconds != 0 

1615 ): 

1616 msg = "When DTSTART is a date, DURATION must be of days or weeks." 

1617 raise InvalidCalendar(msg) 

1618 

1619 # RFC 5545: DTSTART and end property must be of the same type 

1620 if start is not None and end is not None and is_date(start) != is_date(end): 

1621 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1622 msg = ( 

1623 f"DTSTART and {end_name} must be of the same type, either date or datetime." 

1624 ) 

1625 raise InvalidCalendar(msg) 

1626 

1627 return start, end, duration 

1628 

1629 

1630def get_start_property(component: Component) -> date | datetime: 

1631 """ 

1632 Get the start property with validation. 

1633 

1634 Args: 

1635 component: The component from which to get its start property. 

1636 

1637 Returns: 

1638 The ``DTSTART`` value. 

1639 

1640 Raises: 

1641 IncompleteComponent: If no ``DTSTART`` is present. 

1642 

1643 """ 

1644 # Trigger validation by calling _get_start_end_duration 

1645 start, end, duration = component._get_start_end_duration() # noqa: SLF001 

1646 if start is None: 

1647 msg = "No DTSTART given." 

1648 raise IncompleteComponent(msg) 

1649 return start 

1650 

1651 

1652def get_end_property(component: Component, end_property: str) -> date | datetime: 

1653 """ 

1654 Get the end property with fallback logic for ``Event`` and ``Todo`` components. 

1655 

1656 Args: 

1657 component: The component to get end from 

1658 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1659 ``DUE`` for ``Todo``. 

1660 

1661 Returns: 

1662 The computed end value. 

1663 

1664 Raises: 

1665 IncompleteComponent: If the provided information is incomplete 

1666 to compute the end property. 

1667 

1668 """ 

1669 # Trigger validation by calling _get_start_end_duration 

1670 start, end, duration = component._get_start_end_duration() # noqa: SLF001 

1671 

1672 if end is None and duration is None: 

1673 if start is None: 

1674 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1675 msg = f"No {end_name} or DURATION+DTSTART given." 

1676 raise IncompleteComponent(msg) 

1677 

1678 # Default behavior differs for Event vs Todo: 

1679 # Event: date gets +1 day, datetime gets same time 

1680 # Todo: both date and datetime get same time (issue #898) 

1681 if end_property == "DTEND" and is_date(start): 

1682 return start + timedelta(days=1) 

1683 return start 

1684 

1685 if duration is not None: 

1686 if start is not None: 

1687 return start + duration 

1688 end_name = "DTEND" if end_property == "DTEND" else "DUE" 

1689 msg = f"No {end_name} or DURATION+DTSTART given." 

1690 raise IncompleteComponent(msg) 

1691 

1692 return end 

1693 

1694 

1695def get_duration_property(component: Component) -> timedelta: 

1696 """ 

1697 Get the duration property with fallback calculation from start and end. 

1698 

1699 Args: 

1700 component: The component from which to get its duration property. 

1701 

1702 Returns: 

1703 The duration as a timedelta. 

1704 

1705 """ 

1706 # First check if DURATION property is explicitly set 

1707 if "DURATION" in component: 

1708 return component["DURATION"].dt 

1709 

1710 # Fall back to calculated duration from start and end 

1711 return component.end - component.start 

1712 

1713 

1714def set_duration_with_locking( 

1715 component: Component, 

1716 duration: timedelta | None, 

1717 locked: Literal["start", "end"], 

1718 end_property: str, 

1719) -> None: 

1720 """ 

1721 Set the duration with explicit locking behavior for ``Event`` and ``Todo``. 

1722 

1723 Args: 

1724 component: The component to modify, either ``Event`` or ``Todo``. 

1725 duration: The duration to set, or ``None`` to convert to ``DURATION`` property. 

1726 locked: Which property to keep unchanged, either ``start`` or ``end``. 

1727 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1728 ``DUE`` for ``Todo``. 

1729 

1730 """ 

1731 # Convert to DURATION property if duration is None 

1732 if duration is None: 

1733 if "DURATION" in component: 

1734 return # Already has DURATION property 

1735 current_duration = component.duration 

1736 component.DURATION = current_duration 

1737 return 

1738 

1739 if not isinstance(duration, timedelta): 

1740 msg = f"Use timedelta, not {type(duration).__name__}." 

1741 raise TypeError(msg) 

1742 

1743 # Validate date/duration compatibility 

1744 start = component.DTSTART 

1745 if start is not None and is_date(start) and duration.seconds != 0: 

1746 msg = "When DTSTART is a date, DURATION must be of days or weeks." 

1747 raise InvalidCalendar(msg) 

1748 

1749 if locked == "start": 

1750 # Keep start locked, adjust end 

1751 if start is None: 

1752 msg = "Cannot set duration without DTSTART. Set start time first." 

1753 raise IncompleteComponent(msg) 

1754 component.pop(end_property, None) # Remove end property 

1755 component.DURATION = duration 

1756 elif locked == "end": 

1757 # Keep end locked, adjust start 

1758 current_end = component.end 

1759 component.DTSTART = current_end - duration 

1760 component.pop(end_property, None) # Remove end property 

1761 component.DURATION = duration 

1762 else: 

1763 msg = f"locked must be 'start' or 'end', not {locked!r}" 

1764 raise ValueError(msg) 

1765 

1766 

1767def set_start_with_locking( 

1768 component: Component, 

1769 start: date | datetime, 

1770 locked: Literal["duration", "end"] | None, 

1771 end_property: str, 

1772) -> None: 

1773 """ 

1774 Set the start with explicit locking behavior for ``Event`` and ``Todo`` components. 

1775 

1776 Args: 

1777 component: The component to modify, either ``Event`` or ``Todo``. 

1778 start: The start time to set. 

1779 locked: Which property to keep unchanged, either ``duration``, ``end``, 

1780 or ``None`` for auto-detect. 

1781 end_property: The end property name, either ``DTEND`` for ``Event`` or 

1782 ``DUE`` for ``Todo``. 

1783 

1784 """ 

1785 if locked is None: 

1786 # Auto-detect based on existing properties 

1787 if "DURATION" in component: 

1788 locked = "duration" 

1789 elif end_property in component: 

1790 locked = "end" 

1791 else: 

1792 # Default to duration if no existing properties 

1793 locked = "duration" 

1794 

1795 if locked == "duration": 

1796 # Keep duration locked, adjust end 

1797 current_duration = ( 

1798 component.duration 

1799 if "DURATION" in component or end_property in component 

1800 else None 

1801 ) 

1802 component.DTSTART = start 

1803 if current_duration is not None: 

1804 component.pop(end_property, None) # Remove end property 

1805 component.DURATION = current_duration 

1806 elif locked == "end": 

1807 # Keep end locked, adjust duration 

1808 current_end = component.end 

1809 component.DTSTART = start 

1810 component.pop("DURATION", None) # Remove duration property 

1811 setattr(component, end_property, current_end) 

1812 else: 

1813 msg = f"locked must be 'duration', 'end', or None, not {locked!r}" 

1814 raise ValueError(msg) 

1815 

1816 

1817def set_end_with_locking( 

1818 component: Component, 

1819 end: date | datetime, 

1820 locked: Literal["start", "duration"], 

1821 end_property: str, 

1822) -> None: 

1823 """ 

1824 Set the end with explicit locking behavior for Event and Todo components. 

1825 

1826 Args: 

1827 component: The component to modify, either ``Event`` or ``Todo``. 

1828 end: The end time to set. 

1829 locked: Which property to keep unchanged, either ``start`` or ``duration``. 

1830 end_property: The end property name, either ``DTEND`` for ``Event`` or ``DUE`` 

1831 for ``Todo``. 

1832 

1833 """ 

1834 if locked == "start": 

1835 # Keep start locked, adjust duration 

1836 component.pop("DURATION", None) # Remove duration property 

1837 setattr(component, end_property, end) 

1838 elif locked == "duration": 

1839 # Keep duration locked, adjust start 

1840 current_duration = component.duration 

1841 component.DTSTART = end - current_duration 

1842 component.pop(end_property, None) # Remove end property 

1843 component.DURATION = current_duration 

1844 else: 

1845 msg = f"locked must be 'start' or 'duration', not {locked!r}" 

1846 raise ValueError(msg) 

1847 

1848 

1849def _get_images(self: Component) -> list[Image]: 

1850 """IMAGE specifies an image associated with the calendar or a calendar component. 

1851 

1852 Description: 

1853 This property specifies an image for an iCalendar 

1854 object or a calendar component via a URI or directly with inline 

1855 data that can be used by calendar user agents when presenting the 

1856 calendar data to a user. Multiple properties MAY be used to 

1857 specify alternative sets of images with, for example, varying 

1858 media subtypes, resolutions, or sizes. When multiple properties 

1859 are present, calendar user agents SHOULD display only one of them, 

1860 picking one that provides the most appropriate image quality, or 

1861 display none. The "DISPLAY" parameter is used to indicate the 

1862 intended display mode for the image. The "ALTREP" parameter, 

1863 defined in :rfc:`5545`, can be used to provide a "clickable" image 

1864 where the URI in the parameter value can be "launched" by a click 

1865 on the image in the calendar user agent. 

1866 

1867 Conformance: 

1868 This property can be specified multiple times in an 

1869 iCalendar object or in "VEVENT", "VTODO", or "VJOURNAL" calendar 

1870 components. 

1871 

1872 .. note:: 

1873 

1874 At the present moment, this property is read-only. If you require a setter, 

1875 please open an issue or a pull request. 

1876 """ 

1877 images = self.get("IMAGE", []) 

1878 if not isinstance(images, SEQUENCE_TYPES): 

1879 images = [images] 

1880 return [Image.from_property_value(img) for img in images] 

1881 

1882 

1883images_property = property(_get_images) 

1884 

1885 

1886def _get_conferences(self: Component) -> list[Conference]: 

1887 """Return the CONFERENCE properties as a list. 

1888 

1889 Purpose: 

1890 This property specifies information for accessing a conferencing system. 

1891 

1892 Conformance: 

1893 This property can be specified multiple times in a 

1894 "VEVENT" or "VTODO" calendar component. 

1895 

1896 Description: 

1897 This property specifies information for accessing a 

1898 conferencing system for attendees of a meeting or task. This 

1899 might be for a telephone-based conference number dial-in with 

1900 access codes included (such as a tel: URI :rfc:`3966` or a sip: or 

1901 sips: URI :rfc:`3261`), for a web-based video chat (such as an http: 

1902 or https: URI :rfc:`7230`), or for an instant messaging group chat 

1903 room (such as an xmpp: URI :rfc:`5122`). If a specific URI for a 

1904 conferencing system is not available, a data: URI :rfc:`2397` 

1905 containing a text description can be used. 

1906 

1907 A conference system can be a bidirectional communication channel 

1908 or a uni-directional "broadcast feed". 

1909 

1910 The "FEATURE" property parameter is used to describe the key 

1911 capabilities of the conference system to allow a client to choose 

1912 the ones that give the required level of interaction from a set of 

1913 multiple properties. 

1914 

1915 The "LABEL" property parameter is used to convey additional 

1916 details on the use of the URI. For example, the URIs or access 

1917 codes for the moderator and attendee of a teleconference system 

1918 could be different, and the "LABEL" property parameter could be 

1919 used to "tag" each "CONFERENCE" property to indicate which is 

1920 which. 

1921 

1922 The "LANGUAGE" property parameter can be used to specify the 

1923 language used for text values used with this property (as per 

1924 Section 3.2.10 of :rfc:`5545`). 

1925 

1926 Example: 

1927 The following are examples of this property: 

1928 

1929 .. code-block:: text 

1930 

1931 CONFERENCE;VALUE=URI;FEATURE=PHONE,MODERATOR; 

1932 LABEL=Moderator dial-in:tel:+1-412-555-0123,,,654321 

1933 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

1934 LABEL=Attendee dial-in:tel:+1-412-555-0123,,,555123 

1935 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

1936 LABEL=Attendee dial-in:tel:+1-888-555-0456,,,555123 

1937 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

1938 LABEL=Chat room:xmpp:chat-123@conference.example.com 

1939 CONFERENCE;VALUE=URI;FEATURE=AUDIO,VIDEO; 

1940 LABEL=Attendee dial-in:https://chat.example.com/audio?id=123456 

1941 

1942 Get all conferences: 

1943 

1944 .. code-block:: pycon 

1945 

1946 >>> from icalendar import Event 

1947 >>> event = Event() 

1948 >>> event.conferences 

1949 [] 

1950 

1951 Set a conference: 

1952 

1953 .. code-block:: pycon 

1954 

1955 >>> from icalendar import Event, Conference 

1956 >>> event = Event() 

1957 >>> event.conferences = [ 

1958 ... Conference( 

1959 ... "tel:+1-412-555-0123,,,654321", 

1960 ... feature="PHONE,MODERATOR", 

1961 ... label="Moderator dial-in", 

1962 ... language="EN", 

1963 ... ) 

1964 ... ] 

1965 >>> print(event.to_ical()) 

1966 BEGIN:VEVENT 

1967 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN;V 

1968 ALUE=URI:tel:+1-412-555-0123,,,654321 

1969 END:VEVENT 

1970 

1971 """ 

1972 conferences = self.get("CONFERENCE", []) 

1973 if not isinstance(conferences, SEQUENCE_TYPES): 

1974 conferences = [conferences] 

1975 return [Conference.from_uri(conference) for conference in conferences] 

1976 

1977 

1978def _set_conferences(self: Component, conferences: list[Conference] | None): 

1979 """Set the conferences.""" 

1980 _del_conferences(self) 

1981 for conference in conferences or []: 

1982 self.add("CONFERENCE", conference.to_uri()) 

1983 

1984 

1985def _del_conferences(self: Component): 

1986 """Delete all conferences.""" 

1987 self.pop("CONFERENCE") 

1988 

1989 

1990conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

1991 

1992 

1993def _get_links(self: Component) -> list[vUri | vUid | vXmlReference]: 

1994 """LINK properties as a list. 

1995 

1996 Purpose: 

1997 LINK provides a reference to external information related to a component. 

1998 

1999 Property Parameters: 

2000 The VALUE parameter is required. 

2001 Non-standard, link relation type, format type, label, and language parameters 

2002 can also be specified on this property. 

2003 The LABEL parameter is defined in :rfc:`7986`. 

2004 

2005 Conformance: 

2006 This property can be specified zero or more times in any iCalendar component. 

2007 LINK is specified in :rfc:`9253`. 

2008 The LINKREL parameter is required. 

2009 

2010 Description: 

2011 When used in a component, the value of this property points to 

2012 additional information related to the component. 

2013 For example, it may reference the originating web server. 

2014 

2015 This property is a serialization of the model in :rfc:`8288`, 

2016 where the link target is carried in the property value, 

2017 the link context is the containing calendar entity, 

2018 and the link relation type and any target attributes 

2019 are carried in iCalendar property parameters. 

2020 

2021 The LINK property parameters map to :rfc:`8288` attributes as follows: 

2022 

2023 LABEL 

2024 This parameter maps to the "title" 

2025 attribute defined in Section 3.4.1 of :rfc:`8288`. 

2026 LABEL is used to label the destination 

2027 of a link such that it can be used as a human-readable identifier 

2028 (e.g., a menu entry) in the language indicated by the LANGUAGE 

2029 (if present). 

2030 LANGUAGE 

2031 This parameter maps to the "hreflang" attribute defined in Section 3.4.1 

2032 of :rfc:`8288`. See :rfc:`5646`. Example: ``en``, ``de-ch``. 

2033 LINKREL 

2034 This parameter maps to the link relation type defined in Section 2.1 of 

2035 :rfc:`8288`. See `Registered Link Relation Types 

2036 <https://www.iana.org/assignments/link-relations/link-relations.xhtml>`_. 

2037 FMTTYPE 

2038 This parameter maps to the "type" attribute defined in Section 3.4.1 of 

2039 :rfc:`8288`. 

2040 

2041 There is no mapping for "title*", "anchor", "rev", or "media" :rfc:`8288`. 

2042 

2043 Examples: 

2044 The following is an example of this property, 

2045 which provides a reference to the source for the calendar object. 

2046 

2047 .. code-block:: text 

2048 

2049 LINK;LINKREL=SOURCE;LABEL=Venue;VALUE=URI: 

2050 https://example.com/events 

2051 

2052 The following is an example of this property, 

2053 which provides a reference to an entity from which this one was derived. 

2054 The link relation is a vendor-defined value. 

2055 

2056 .. code-block:: text 

2057 

2058 LINK;LINKREL="https://example.com/linkrel/derivedFrom"; 

2059 VALUE=URI: 

2060 https://example.com/tasks/01234567-abcd1234.ics 

2061 

2062 The following is an example of this property, 

2063 which provides a reference to a fragment of an XML document. 

2064 The link relation is a vendor-defined value. 

2065 

2066 .. code-block:: text 

2067 

2068 LINK;LINKREL="https://example.com/linkrel/costStructure"; 

2069 VALUE=XML-REFERENCE: 

2070 https://example.com/xmlDocs/bidFramework.xml 

2071 #xpointer(descendant::CostStruc/range-to( 

2072 following::CostStrucEND[1])) 

2073 

2074 Set a link :class:`icalendar.vUri` to the event page: 

2075 

2076 .. code-block:: pycon 

2077 

2078 >>> from icalendar import Event, vUri 

2079 >>> from datetime import datetime 

2080 >>> link = vUri( 

2081 ... "http://example.com/event-page", 

2082 ... params={"LINKREL":"SOURCE"} 

2083 ... ) 

2084 >>> event = Event.new( 

2085 ... start=datetime(2025, 9, 17, 12, 0), 

2086 ... summary="An Example Event with a page" 

2087 ... ) 

2088 >>> event.links = [link] 

2089 >>> print(event.to_ical()) 

2090 BEGIN:VEVENT 

2091 SUMMARY:An Example Event with a page 

2092 DTSTART:20250917T120000 

2093 DTSTAMP:20250517T080612Z 

2094 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 

2095 LINK;LINKREL="SOURCE":http://example.com/event-page 

2096 END:VEVENT 

2097 

2098 """ 

2099 links = self.get("LINK", []) 

2100 if not isinstance(links, list): 

2101 links = [links] 

2102 return links 

2103 

2104 

2105LINKS_TYPE_SETTER: TypeAlias = Union[ 

2106 str, vUri, vUid, vXmlReference, None, List[Union[str, vUri, vUid, vXmlReference]] 

2107] 

2108 

2109 

2110def _set_links(self: Component, links: LINKS_TYPE_SETTER) -> None: 

2111 """Set the LINKs.""" 

2112 _del_links(self) 

2113 if links is None: 

2114 return 

2115 if isinstance(links, (str, vUri, vUid, vXmlReference)): 

2116 links = [links] 

2117 for link in links: 

2118 if type(link) is str: 

2119 link = vUri(link, params={"VALUE": "URI"}) # noqa: PLW2901 

2120 self.add("LINK", link) 

2121 

2122 

2123def _del_links(self: Component) -> None: 

2124 """Delete all links.""" 

2125 self.pop("LINK") 

2126 

2127 

2128links_property = property(_get_links, _set_links, _del_links) 

2129 

2130RELATED_TO_TYPE_SETTER: TypeAlias = Union[ 

2131 None, str, vText, vUri, vUid, List[Union[str, vText, vUri, vUid]] 

2132] 

2133 

2134 

2135def _get_related_to(self: Component) -> list[Union[vText, vUri, vUid]]: 

2136 """RELATED-TO properties as a list. 

2137 

2138 Purpose: 

2139 This property is used to represent a relationship or reference 

2140 between one calendar component and another. 

2141 :rfc:`9523` allows URI or UID values and a GAP parameter. 

2142 

2143 Value Type: 

2144 :rfc:`5545`: TEXT 

2145 :rfc:`9253`: URI, UID 

2146 

2147 Conformance: 

2148 Since :rfc:`5545`. this property can be specified in the "VEVENT", 

2149 "VTODO", and "VJOURNAL" calendar components. 

2150 Since :rfc:`9523`, this property MAY be specified in any 

2151 iCalendar component. 

2152 

2153 Description (:rfc:`5545`): 

2154 The property value consists of the persistent, globally 

2155 unique identifier of another calendar component. This value would 

2156 be represented in a calendar component by the "UID" property. 

2157 

2158 By default, the property value points to another calendar 

2159 component that has a PARENT relationship to the referencing 

2160 object. The "RELTYPE" property parameter is used to either 

2161 explicitly state the default PARENT relationship type to the 

2162 referenced calendar component or to override the default PARENT 

2163 relationship type and specify either a CHILD or SIBLING 

2164 relationship. The PARENT relationship indicates that the calendar 

2165 component is a subordinate of the referenced calendar component. 

2166 The CHILD relationship indicates that the calendar component is a 

2167 superior of the referenced calendar component. The SIBLING 

2168 relationship indicates that the calendar component is a peer of 

2169 the referenced calendar component. 

2170 

2171 Changes to a calendar component referenced by this property can 

2172 have an implicit impact on the related calendar component. For 

2173 example, if a group event changes its start or end date or time, 

2174 then the related, dependent events will need to have their start 

2175 and end dates changed in a corresponding way. Similarly, if a 

2176 PARENT calendar component is cancelled or deleted, then there is 

2177 an implied impact to the related CHILD calendar components. This 

2178 property is intended only to provide information on the 

2179 relationship of calendar components. It is up to the target 

2180 calendar system to maintain any property implications of this 

2181 relationship. 

2182 

2183 Description (:rfc:`9253`): 

2184 By default or when VALUE=UID is specified, the property value 

2185 consists of the persistent, globally unique identifier of another 

2186 calendar component. This value would be represented in a calendar 

2187 component by the UID property. 

2188 

2189 By default, the property value 

2190 points to another calendar component that has a PARENT relationship 

2191 to the referencing object. The RELTYPE property parameter is used 

2192 to either explicitly state the default PARENT relationship type to 

2193 the referenced calendar component or to override the default 

2194 PARENT relationship type and specify either a CHILD or SIBLING 

2195 relationship or a temporal relationship. 

2196 

2197 The PARENT relationship 

2198 indicates that the calendar component is a subordinate of the 

2199 referenced calendar component. The CHILD relationship indicates 

2200 that the calendar component is a superior of the referenced calendar 

2201 component. The SIBLING relationship indicates that the calendar 

2202 component is a peer of the referenced calendar component. 

2203 

2204 To preserve backwards compatibility, the value type MUST 

2205 be UID when the PARENT, SIBLING, or CHILD relationships 

2206 are specified. 

2207 

2208 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH, 

2209 or STARTTOSTART relationships define temporal relationships, as 

2210 specified in the RELTYPE parameter definition. 

2211 

2212 The FIRST and NEXT 

2213 define ordering relationships between calendar components. 

2214 

2215 The DEPENDS-ON relationship indicates that the current calendar 

2216 component depends on the referenced calendar component in some manner. 

2217 For example, a task may be blocked waiting on the other, 

2218 referenced, task. 

2219 

2220 The REFID and CONCEPT relationships establish 

2221 a reference from the current component to the referenced component. 

2222 Changes to a calendar component referenced by this property 

2223 can have an implicit impact on the related calendar component. 

2224 For example, if a group event changes its start or end date or 

2225 time, then the related, dependent events will need to have their 

2226 start and end dates and times changed in a corresponding way. 

2227 Similarly, if a PARENT calendar component is canceled or deleted, 

2228 then there is an implied impact to the related CHILD calendar 

2229 components. This property is intended only to provide information 

2230 on the relationship of calendar components. 

2231 

2232 Deletion of the target component, for example, the target of a 

2233 FIRST, NEXT, or temporal relationship, can result in broken links. 

2234 

2235 It is up to the target calendar system to maintain any property 

2236 implications of these relationships. 

2237 

2238 Examples: 

2239 :rfc:`5545` examples of this property: 

2240 

2241 .. code-block:: text 

2242 

2243 RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com 

2244 

2245 .. code-block:: text 

2246 

2247 RELATED-TO:19960401-080045-4000F192713-0052@example.com 

2248 

2249 :rfc:`9253` examples of this property: 

2250 

2251 .. code-block:: text 

2252 

2253 RELATED-TO;VALUE=URI;RELTYPE=STARTTOFINISH: 

2254 https://example.com/caldav/user/jb/cal/ 

2255 19960401-080045-4000F192713.ics 

2256 

2257 See also :class:`icalendar.enum.RELTYPE`. 

2258 

2259 """ 

2260 result = self.get("RELATED-TO", []) 

2261 if not isinstance(result, list): 

2262 return [result] 

2263 return result 

2264 

2265 

2266def _set_related_to(self: Component, values: RELATED_TO_TYPE_SETTER) -> None: 

2267 """Set the RELATED-TO properties.""" 

2268 _del_related_to(self) 

2269 if values is None: 

2270 return 

2271 if not isinstance(values, list): 

2272 values = [values] 

2273 for value in values: 

2274 self.add("RELATED-TO", value) 

2275 

2276 

2277def _del_related_to(self: Component): 

2278 """Delete the RELATED-TO properties.""" 

2279 self.pop("RELATED-TO", None) 

2280 

2281 

2282related_to_property = property(_get_related_to, _set_related_to, _del_related_to) 

2283 

2284 

2285def _get_concepts(self: Component) -> list[vUri]: 

2286 """CONCEPT 

2287 

2288 Purpose: 

2289 CONCEPT defines the formal categories for a calendar component. 

2290 

2291 Conformance: 

2292 Since :rfc:`9253`, 

2293 this property can be specified zero or more times in any iCalendar component. 

2294 

2295 Description: 

2296 This property is used to specify formal categories or classifications of 

2297 the calendar component. The values are useful in searching for a calendar 

2298 component of a particular type and category. 

2299 

2300 This categorization is distinct from the more informal "tagging" of components 

2301 provided by the existing CATEGORIES property. It is expected that the value of 

2302 the CONCEPT property will reference an external resource that provides 

2303 information about the categorization. 

2304 

2305 In addition, a structured URI value allows for hierarchical categorization of 

2306 events. 

2307 

2308 Possible category resources are the various proprietary systems, for example, 

2309 the Library of Congress, or an open source of categorization data. 

2310 

2311 Examples: 

2312 The following is an example of this property. 

2313 It points to a server acting as the source for the calendar object. 

2314 

2315 .. code-block:: text 

2316 

2317 CONCEPT:https://example.com/event-types/arts/music 

2318 

2319 .. seealso:: 

2320 

2321 :attr:`Component.categories` 

2322 """ 

2323 concepts = self.get("CONCEPT", []) 

2324 if not isinstance(concepts, list): 

2325 concepts = [concepts] 

2326 return concepts 

2327 

2328 

2329CONCEPTS_TYPE_SETTER: TypeAlias = Union[List[Union[vUri, str]], str, vUri, None] 

2330 

2331 

2332def _set_concepts(self: Component, concepts: CONCEPTS_TYPE_SETTER): 

2333 """Set the concepts.""" 

2334 _del_concepts(self) 

2335 if concepts is None: 

2336 return 

2337 if not isinstance(concepts, list): 

2338 concepts = [concepts] 

2339 for value in concepts: 

2340 self.add("CONCEPT", value) 

2341 

2342 

2343def _del_concepts(self: Component): 

2344 """Delete the concepts.""" 

2345 self.pop("CONCEPT", None) 

2346 

2347 

2348concepts_property = property(_get_concepts, _set_concepts, _del_concepts) 

2349 

2350 

2351def multi_string_property(name: str, doc: str): 

2352 """A property for an iCalendar Property that can occur multiple times.""" 

2353 

2354 def fget(self: Component) -> list[str]: 

2355 """Get the values of a multi-string property.""" 

2356 value = self.get(name, []) 

2357 if not isinstance(value, list): 

2358 value = [value] 

2359 return value 

2360 

2361 def fset(self: Component, value: list[str] | str | None) -> None: 

2362 """Set the values of a multi-string property.""" 

2363 fdel(self) 

2364 if value is None: 

2365 return 

2366 if not isinstance(value, list): 

2367 value = [value] 

2368 for value in value: 

2369 self.add(name, value) 

2370 

2371 def fdel(self: Component): 

2372 """Delete the values of a multi-string property.""" 

2373 self.pop(name, None) 

2374 

2375 return property(fget, fset, fdel, doc=doc) 

2376 

2377 

2378refids_property = multi_string_property( 

2379 "REFID", 

2380 """REFID 

2381 

2382Purpose: 

2383 REFID acts as a key for associated iCalendar entities. 

2384 

2385Conformance: 

2386 Since :rfc:`9253`, 

2387 this property can be specified zero or more times in any iCalendar component. 

2388 

2389Description: 

2390 The value of this property is free-form text that creates an 

2391 identifier for associated components. 

2392 All components that use the same REFID value are associated through 

2393 that value and can be located or retrieved as a group. 

2394 For example, all of the events in a travel itinerary 

2395 would have the same REFID value, so as to be grouped together. 

2396 

2397Examples: 

2398 The following is an example of this property. 

2399 

2400 .. code-block:: text 

2401 

2402 REFID:itinerary-2014-11-17 

2403 

2404 Use a REFID to associate several VTODOs: 

2405 

2406 .. code-block:: pycon 

2407 

2408 >>> from icalendar import Todo 

2409 >>> todo_1 = Todo.new( 

2410 ... summary="turn off stove", 

2411 ... refids=["travel", "alps"] 

2412 ... ) 

2413 >>> todo_2 = Todo.new( 

2414 ... summary="pack backpack", 

2415 ... refids=["travel", "alps"] 

2416 ... ) 

2417 >>> todo_1.refids == todo_2.refids 

2418 True 

2419 

2420.. note:: 

2421 

2422 List modifications do not modify the component. 

2423""", 

2424) 

2425 

2426 

2427__all__ = [ 

2428 "CONCEPTS_TYPE_SETTER", 

2429 "LINKS_TYPE_SETTER", 

2430 "RELATED_TO_TYPE_SETTER", 

2431 "attendees_property", 

2432 "busy_type_property", 

2433 "categories_property", 

2434 "class_property", 

2435 "color_property", 

2436 "comments_property", 

2437 "concepts_property", 

2438 "conferences_property", 

2439 "contacts_property", 

2440 "create_single_property", 

2441 "description_property", 

2442 "descriptions_property", 

2443 "duration_property", 

2444 "exdates_property", 

2445 "get_duration_property", 

2446 "get_end_property", 

2447 "get_start_end_duration_with_validation", 

2448 "get_start_property", 

2449 "images_property", 

2450 "links_property", 

2451 "location_property", 

2452 "multi_language_text_property", 

2453 "multi_string_property", 

2454 "organizer_property", 

2455 "priority_property", 

2456 "property_del_duration", 

2457 "property_doc_duration_template", 

2458 "property_get_duration", 

2459 "property_set_duration", 

2460 "rdates_property", 

2461 "refids_property", 

2462 "related_to_property", 

2463 "rfc_7953_dtend_property", 

2464 "rfc_7953_dtstart_property", 

2465 "rfc_7953_duration_property", 

2466 "rfc_7953_end_property", 

2467 "rrules_property", 

2468 "sequence_property", 

2469 "set_duration_with_locking", 

2470 "set_end_with_locking", 

2471 "set_start_with_locking", 

2472 "single_int_property", 

2473 "single_utc_property", 

2474 "source_property", 

2475 "status_property", 

2476 "summary_property", 

2477 "transparency_property", 

2478 "uid_property", 

2479 "url_property", 

2480]