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

497 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, Literal, TypeAlias 

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 collections.abc import Callable, Sequence 

30 

31 from icalendar.cal import Component 

32 

33 

34def _get_rdates( 

35 self: Component, 

36) -> list[tuple[date, None] | tuple[datetime, None] | tuple[datetime, datetime]]: 

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

38 

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

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

41 

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

43 with and without timezone. 

44 

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

46 if the end is specified. 

47 

48 Value Type: 

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

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

51 

52 Property Parameters: 

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

54 zone identifier property parameters can be specified on this 

55 property. 

56 

57 Conformance: 

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

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

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

61 calendar component. 

62 

63 Description: 

64 This property can appear along with the "RRULE" 

65 property to define an aggregate set of repeating occurrences. 

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

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

68 the "RDATE" and "RRULE". 

69 

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

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

72 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

88 Duplicate instances are ignored. 

89 

90 Example: 

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

92 

93 .. code-block:: pycon 

94 

95 >>> from icalendar import Event 

96 >>> from datetime import datetime 

97 >>> event = Event() 

98 

99 # Add a list of recurrence dates 

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

101 >>> event.rdates 

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

103 

104 .. note:: 

105 

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

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

108 

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

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

111 

112 """ 

113 result = [] 

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

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

116 for dts in rdates.dts: 

117 rdate = dts.dt 

118 if isinstance(rdate, tuple): 

119 # we have a period as rdate 

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

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

122 else: 

123 result.append(rdate) 

124 else: 

125 # we have a date/datetime 

126 result.append((rdate, None)) 

127 return result 

128 

129 

130rdates_property = property(_get_rdates) 

131 

132 

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

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

135 

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

137 

138 Value Type: 

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

140 The value type can be set to DATE. 

141 

142 Property Parameters: 

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

144 zone identifier property parameters can be specified on this 

145 property. 

146 

147 Conformance: 

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

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

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

151 calendar component. 

152 

153 Description: 

154 The exception dates, if specified, are used in 

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

156 set of recurrence instances for a calendar component. The 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

172 Duplicate instances are ignored. 

173 

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

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

176 MUST still be maintained by the calendaring and scheduling system 

177 because the original "DTSTART" value has inherent usage 

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

179 

180 Example: 

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

182 

183 .. code-block:: pycon 

184 

185 >>> from icalendar import Event 

186 >>> from datetime import datetime 

187 >>> event = Event() 

188 

189 # Add a list of excluded dates 

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

191 >>> event.exdates 

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

193 

194 .. note:: 

195 

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

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

198 

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

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

201 

202 """ 

203 result = [] 

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

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

206 for dts in exdates.dts: 

207 exdate = dts.dt 

208 # we have a date/datetime 

209 result.append(exdate) 

210 return result 

211 

212 

213exdates_property = property(_get_exdates) 

214 

215 

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

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

218 

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

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

221 

222 Property Parameters: 

223 IANA and non-standard property parameters can 

224 be specified on this property. 

225 

226 Conformance: 

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

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

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

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

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

232 undefined. 

233 

234 Description: 

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

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

237 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

242 value SHOULD be synchronized with the recurrence rule, if 

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

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

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

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

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

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

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

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

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

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

253 Duplicate instances are ignored. 

254 

255 The "DTSTART" property specified within the iCalendar object 

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

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

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

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

260 same local time regardless of time zone changes. 

261 

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

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

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

265 duration of the recurring component is specified with the 

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

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

268 duration of each recurrence instance will depend on its specific 

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

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

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

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

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

274 type. 

275 

276 Examples: 

277 Daily for 10 occurrences: 

278 

279 .. code-block:: pycon 

280 

281 >>> from icalendar import Event 

282 >>> from datetime import datetime 

283 >>> from zoneinfo import ZoneInfo 

284 >>> event = Event() 

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

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

287 >>> print(event.to_ical()) 

288 BEGIN:VEVENT 

289 DTSTART;TZID=America/New_York:19970902T090000 

290 RRULE:FREQ=DAILY;COUNT=10 

291 END:VEVENT 

292 >>> event.rrules 

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

294 

295 Daily until December 24, 1997: 

296 

297 .. code-block:: pycon 

298 

299 >>> from icalendar import Event, vRecur 

300 >>> from datetime import datetime 

301 >>> from zoneinfo import ZoneInfo 

302 >>> event = Event() 

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

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

305 >>> print(event.to_ical()) 

306 BEGIN:VEVENT 

307 DTSTART;TZID=America/New_York:19970902T090000 

308 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

309 END:VEVENT 

310 >>> event.rrules 

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

312 

313 .. note:: 

314 

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

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

317 

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

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

320 

321 """ # noqa: E501 

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

323 if not isinstance(rrules, list): 

324 return [rrules] 

325 return rrules 

326 

327 

328rrules_property = property(_get_rrules) 

329 

330 

331def multi_language_text_property( 

332 main_prop: str, compatibility_prop: str | None, doc: str 

333) -> property: 

334 """This creates a text property. 

335 

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

337 

338 Parameters: 

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

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

341 doc (str): The documentation string 

342 """ 

343 

344 def fget(self: Component) -> str | None: 

345 """Get the property""" 

346 result = self.get(main_prop) 

347 if result is None and compatibility_prop is not None: 

348 result = self.get(compatibility_prop) 

349 if isinstance(result, list): 

350 for item in result: 

351 if "LANGUAGE" not in item.params: 

352 return item 

353 return result 

354 

355 def fset(self: Component, value: str | None): 

356 """Set the property.""" 

357 fdel(self) 

358 if value is not None: 

359 self.add(main_prop, value) 

360 if compatibility_prop is not None: 

361 self.add(compatibility_prop, value) 

362 

363 def fdel(self: Component): 

364 """Delete the property.""" 

365 self.pop(main_prop, None) 

366 if compatibility_prop is not None: 

367 self.pop(compatibility_prop, None) 

368 

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

370 

371 

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

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

374 

375 Parameters: 

376 prop: The name of the property 

377 default: The default value 

378 doc: The documentation string 

379 """ 

380 

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

382 """Get the property""" 

383 try: 

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

385 except ValueError as e: 

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

387 

388 def fset(self: Component, value: int | None): 

389 """Set the property.""" 

390 fdel(self) 

391 if value is not None: 

392 self.add(prop, value) 

393 

394 def fdel(self: Component): 

395 """Delete the property.""" 

396 self.pop(prop, None) 

397 

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

399 

400 

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

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

403 

404 Parameters: 

405 name: name of the property 

406 docs: documentation string 

407 """ 

408 docs = ( 

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

410 

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

412 """ 

413 + docs 

414 ) 

415 

416 def fget(self: Component) -> datetime | None: 

417 """Get the value.""" 

418 if name not in self: 

419 return None 

420 dt = self.get(name) 

421 if isinstance(dt, vText): 

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

423 value = vDDDTypes.from_ical(dt) 

424 else: 

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

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

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

428 return tzp.localize_utc(value) 

429 

430 def fset(self: Component, value: datetime | None): 

431 """Set the value""" 

432 if value is None: 

433 fdel(self) 

434 return 

435 if not isinstance(value, date): 

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

437 fdel(self) 

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

439 

440 def fdel(self: Component): 

441 """Delete the property.""" 

442 self.pop(name, None) 

443 

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

445 

446 

447def single_string_property( 

448 name: str, docs: str, other_name: str | None = None, default: str = "" 

449) -> property: 

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

451 

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

453 """Get the value.""" 

454 result = self.get( 

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

456 ) 

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

458 return default 

459 if isinstance(result, list): 

460 return result[0] 

461 return result 

462 

463 def fset(self: Component, value: str | None): 

464 """Set the value. 

465 

466 Setting the value to None will delete it. 

467 """ 

468 fdel(self) 

469 if value is not None: 

470 self.add(name, value) 

471 

472 def fdel(self: Component): 

473 """Delete the property.""" 

474 self.pop(name, None) 

475 if other_name is not None: 

476 self.pop(other_name, None) 

477 

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

479 

480 

481color_property = single_string_property( 

482 "COLOR", 

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

484 

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

486 

487 Property Parameters: 

488 IANA and non-standard property parameters can 

489 be specified on this property. 

490 

491 Conformance: 

492 This property can be specified once in an iCalendar 

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

494 

495 Description: 

496 This property specifies a color that clients MAY use 

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

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

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

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

501 

502 Example: 

503 ``"turquoise"``, ``"#ffffff"`` 

504 

505 .. code-block:: pycon 

506 

507 >>> from icalendar import Todo 

508 >>> todo = Todo() 

509 >>> todo.color = "green" 

510 >>> print(todo.to_ical()) 

511 BEGIN:VTODO 

512 COLOR:green 

513 END:VTODO 

514 """, 

515) 

516 

517sequence_property = single_int_property( 

518 "SEQUENCE", 

519 0, 

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

521 

522Value Type: 

523 INTEGER 

524 

525Property Parameters: 

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

527 

528Conformance: 

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

530 "VJOURNAL" calendar component. 

531 

532Description: 

533 When a calendar component is created, its sequence 

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

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

536 calendar component. 

537 

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

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

540 calendar component. 

541 

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

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

544 component to which the "Attendee" is referring. 

545 

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

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

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

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

550 requested. 

551 

552 Recurrence instances of a recurring component MAY have different 

553 sequence numbers. 

554 

555Examples: 

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

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

558 

559 .. code-block:: pycon 

560 

561 >>> from icalendar import Event 

562 >>> event = Event() 

563 >>> event.sequence 

564 0 

565 

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

567 component that has been revised 10 different times by the 

568 "Organizer": 

569 

570 .. code-block:: pycon 

571 

572 >>> from icalendar import Calendar 

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

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

575 >>> event.sequence 

576 10 

577 """, # noqa: E501 

578) 

579 

580 

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

582 """Get all the categories.""" 

583 categories: vCategory | list[vCategory] | None = component.get("CATEGORIES") 

584 if isinstance(categories, list): 

585 _set_categories( 

586 component, 

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

588 ) 

589 return _get_categories(component) 

590 if categories is None: 

591 categories = vCategory([]) 

592 component.add("CATEGORIES", categories) 

593 return categories.cats 

594 

595 

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

597 """Set the categories.""" 

598 if not cats and cats != []: 

599 _del_categories(component) 

600 return 

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

602 if isinstance(cats, list): 

603 cats.clear() 

604 cats.extend(categories.cats) 

605 categories.cats = cats 

606 

607 

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

609 """Delete the categories.""" 

610 component.pop("CATEGORIES", None) 

611 

612 

613categories_property = property( 

614 _get_categories, 

615 _set_categories, 

616 _del_categories, 

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

618 

619The categories property is used to specify categories or subtypes of the 

620calendar component. The categories are useful to search for a calendar 

621component of a particular type and category. 

622 

623Within the calendar components, specify categories as a list of strings. 

624You can get, set, and delete categories for a component. 

625 

626This property can be used in icalendar through its Python attributes of: 

627 

628- :attr:`Calendar.categories <icalendar.cal.calendar.Calendar.categories>` 

629- :attr:`Event.categories <icalendar.cal.event.Event.categories>` 

630- :attr:`Journal.categories <icalendar.cal.journal.Journal.categories>` 

631- :attr:`Todo.categories <icalendar.cal.todo.Todo.categories>` 

632 

633The categories property for ``Event``, ``Journal``, and ``Todo`` complies 

634with :rfc:`5545#section-3.8.1.2`, and for ``Calendar`` with :rfc:`7986#section-5.6`. 

635 

636Note: 

637 At present, icalendar doesn't take the LANGUAGE parameter as defined 

638 in :rfc:`5545#section-3.2.10` into account. 

639 

640Parameters: 

641 categories(list[str]): A list of categories as strings. 

642 

643Example: 

644 Create an event, add categories to it, print its ical representation, 

645 append another category, and finally compare the result 

646 against its expected value. 

647 

648 .. code-block:: pycon 

649 

650 >>> from icalendar import Event 

651 >>> event = Event() 

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

653 >>> print(event.to_ical()) 

654 BEGIN:VEVENT 

655 CATEGORIES:Work,Meeting 

656 END:VEVENT 

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

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

659 True 

660 

661See also: 

662 :attr:`Component.concepts <icalendar.cal.component.Component.concepts>` 

663""", 

664) 

665 

666 

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

668 """Get attendees.""" 

669 value = self.get("ATTENDEE") 

670 if value is None: 

671 value = [] 

672 self["ATTENDEE"] = value 

673 return value 

674 if isinstance(value, vCalAddress): 

675 return [value] 

676 return value 

677 

678 

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

680 """Set attendees.""" 

681 _del_attendees(self) 

682 if value is None: 

683 return 

684 if not isinstance(value, list): 

685 value = [value] 

686 self["ATTENDEE"] = value 

687 

688 

689def _del_attendees(self: Component): 

690 """Delete all attendees.""" 

691 self.pop("ATTENDEE", None) 

692 

693 

694attendees_property = property( 

695 _get_attendees, 

696 _set_attendees, 

697 _del_attendees, 

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

699 

700Conformance: 

701 This property MUST be specified in an iCalendar object 

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

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

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

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

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

707 iCalendar object that specifies only a time zone definition or 

708 that defines calendar components that are not group-scheduled 

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

710 

711Description: 

712 This property MUST only be specified within calendar 

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

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

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

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

717 type of iCalendar alarm. 

718 

719Examples: 

720 Add a new attendee to an existing event. 

721 

722 .. code-block:: pycon 

723 

724 >>> from icalendar import Event, vCalAddress 

725 >>> event = Event() 

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

727 >>> print(event.to_ical()) 

728 BEGIN:VEVENT 

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

730 END:VEVENT 

731 

732 Create an email alarm with several attendees: 

733 

734 >>> from icalendar import Alarm, vCalAddress 

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

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

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

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

739 >>> print(alarm.to_ical()) 

740 BEGIN:VALARM 

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

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

743 SUMMARY:Email alarm 

744 END:VALARM 

745""", 

746) 

747 

748uid_property = single_string_property( 

749 "UID", 

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

751 

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

753 

754Returns: 

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

756 

757Description: 

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

759 The generator of the identifier MUST guarantee that the identifier 

760 is unique. 

761 

762 This is the method for correlating scheduling messages with the 

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

764 The full range of calendar components specified by a recurrence 

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

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

767 property allows the reference to an individual instance within the 

768 recurrence set. 

769 

770 This property is an important method for group-scheduling 

771 applications to match requests with later replies, modifications, 

772 or deletion requests. Calendaring and scheduling applications 

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

774 calendar components to assure interoperability with other group- 

775 scheduling applications. This identifier is created by the 

776 calendar system that generates an iCalendar object. 

777 

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

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

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

781 

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

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

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

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

786 to identify the most recent version of a calendar. 

787 

788Conformance: 

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

790 and "VJOURNAL" calendar components. 

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

792 allow it to be defined in an iCalendar object. 

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

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

795 to refer uniquely to the "VALARM" component. 

796 

797 This property can be specified once only. 

798 

799Security: 

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

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

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

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

804 Universally Unique Identifier (UUID) values as defined in 

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

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

807 

808Compatibility: 

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

810 

811Examples: 

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

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

814 

815 Set the UID of a calendar: 

816 

817 .. code-block:: pycon 

818 

819 >>> from icalendar import Calendar 

820 >>> from uuid import uuid4 

821 >>> calendar = Calendar() 

822 >>> calendar.uid = uuid4() 

823 >>> print(calendar.to_ical()) 

824 BEGIN:VCALENDAR 

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

826 END:VCALENDAR 

827 

828""", 

829) 

830 

831summary_property = multi_language_text_property( 

832 "SUMMARY", 

833 None, 

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

835 

836Property Parameters: 

837 IANA, non-standard, alternate text 

838 representation, and language property parameters can be specified 

839 on this property. 

840 

841Conformance: 

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

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

844 

845Description: 

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

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

848 summary about the activity or journal entry. 

849 

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

851 capture the subject of an EMAIL category of alarm. 

852 

853Examples: 

854 The following is an example of this property: 

855 

856 .. code-block:: pycon 

857 

858 SUMMARY:Department Party 

859""", 

860) 

861 

862description_property = multi_language_text_property( 

863 "DESCRIPTION", 

864 None, 

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

866 

867Property Parameters: 

868 IANA, non-standard, alternate text 

869 representation, and language property parameters can be specified 

870 on this property. 

871 

872Conformance: 

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

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

875 specified multiple times only within a "VJOURNAL" calendar 

876 component. 

877 

878Description: 

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

880 capture lengthy textual descriptions associated with the activity. 

881 

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

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

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

885 

886Examples: 

887 The following is an example of this property with formatted 

888 line breaks in the property value: 

889 

890 .. code-block:: pycon 

891 

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

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

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

895 

896 """, # noqa: E501 

897) 

898 

899 

900def create_single_property( 

901 prop: str, 

902 value_attr: str | None, 

903 value_type: tuple[type], 

904 type_def: type, 

905 doc: str, 

906 vProp: type = vDDDTypes, # noqa: N803 

907 convert: Callable[[object], object] | None = None, 

908): 

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

910 

911 Parameters: 

912 prop: The name of the property. 

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

914 value_type: The type of the value. 

915 type_def: The type of the property. 

916 doc: The docstring of the property. 

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

918 """ 

919 

920 def p_get(self: Component): 

921 default = object() 

922 result = self.get(prop, default) 

923 if result is default: 

924 return None 

925 if isinstance(result, list): 

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

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

928 value = value if convert is None else convert(value) 

929 if not isinstance(value, value_type): 

930 raise InvalidCalendar( 

931 f"{prop} must be either a " 

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

933 f" not {value}." 

934 ) 

935 return value 

936 

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

938 if value is None: 

939 p_del(self) 

940 return 

941 value = convert(value) if convert is not None else value 

942 if not isinstance(value, value_type): 

943 raise TypeError( 

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

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

946 ) 

947 self[prop] = vProp(value) 

948 if prop in self.exclusive: 

949 for other_prop in self.exclusive: 

950 if other_prop != prop: 

951 self.pop(other_prop, None) 

952 

953 p_set.__annotations__["value"] = p_get.__annotations__["return"] = type_def | None 

954 

955 def p_del(self: Component): 

956 self.pop(prop) 

957 

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

959 

960 {doc} 

961 

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

963 If the attribute has invalid values, we raise 

964 :exc:`~icalendar.error.InvalidCalendar`. 

965 If the value is absent, we return None. 

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

967 """ 

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

969 

970 

971X_MOZ_SNOOZE_TIME_property = single_utc_property( 

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

973) 

974X_MOZ_LASTACK_property = single_utc_property( 

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

976) 

977 

978 

979def property_get_duration(self: Component) -> timedelta | None: 

980 """Getter for property DURATION.""" 

981 default = object() 

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

983 if duration is default: 

984 return None 

985 result = getattr(duration, "td", None) 

986 if result is None: 

987 raise InvalidCalendar( 

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

989 ) 

990 return result 

991 

992 

993def property_set_duration(self: Component, value: timedelta | None): 

994 """Setter for property DURATION.""" 

995 if value is None: 

996 self.pop("duration", None) 

997 return 

998 if not isinstance(value, timedelta): 

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

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

1001 self.pop("DTEND") 

1002 self.pop("DUE") 

1003 

1004 

1005def property_del_duration(self: Component): 

1006 """Delete property DURATION.""" 

1007 self.pop("DURATION") 

1008 

1009 

1010property_doc_duration_template = """The DURATION property. 

1011 

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

1013start of the {component}. 

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

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

1016of the event. 

1017 

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

1019Instead use the duration property (lower case). 

1020""" 

1021 

1022 

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

1024 """Return the duration property.""" 

1025 return property( 

1026 property_get_duration, 

1027 property_set_duration, 

1028 property_del_duration, 

1029 property_doc_duration_template.format(component=component), 

1030 ) 

1031 

1032 

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

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

1035 

1036 Examples: Journal.descriptions, Event.comments 

1037 """ 

1038 

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

1040 """Get the values.""" 

1041 descriptions = self.get(name) 

1042 if descriptions is None: 

1043 return [] 

1044 if not isinstance(descriptions, SEQUENCE_TYPES): 

1045 return [descriptions] 

1046 return descriptions 

1047 

1048 def fset(self: Component, values: str | Sequence[str] | None): 

1049 """Set the values.""" 

1050 fdel(self) 

1051 if values is None: 

1052 return 

1053 if isinstance(values, str): 

1054 self.add(name, values) 

1055 else: 

1056 for description in values: 

1057 self.add(name, description) 

1058 

1059 def fdel(self: Component): 

1060 """Delete the values.""" 

1061 self.pop(name) 

1062 

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

1064 

1065 

1066descriptions_property = multi_text_property( 

1067 "DESCRIPTION", 

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

1069 

1070Property Parameters: 

1071 IANA, non-standard, alternate text 

1072 representation, and language property parameters can be specified 

1073 on this property. 

1074 

1075Conformance: 

1076 The property can be 

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

1078 

1079Description: 

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

1081 capture one or more textual journal entries. 

1082 

1083Examples: 

1084 The following is an example of this property with formatted 

1085 line breaks in the property value: 

1086 

1087 .. code-block:: pycon 

1088 

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

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

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

1092 

1093""", # noqa: E501 

1094) 

1095 

1096comments_property = multi_text_property( 

1097 "COMMENT", 

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

1099 

1100Purpose: 

1101 This property specifies non-processing information intended 

1102 to provide a comment to the calendar user. 

1103 

1104Conformance: 

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

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

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

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

1109 "VAVAILABILITY" and "VAVAILABLE". 

1110 

1111Property Parameters: 

1112 IANA, non-standard, alternate text 

1113 representation, and language property parameters can be specified 

1114 on this property. 

1115 

1116""", 

1117) 

1118 

1119RECURRENCE_ID = create_single_property( 

1120 "RECURRENCE-ID", 

1121 "dt", 

1122 (date, datetime), 

1123 date | datetime, 

1124 """ 

1125Identify a specific occurrence of a recurring calendar object. 

1126 

1127This property is used together with ``UID`` and ``SEQUENCE`` to refer to one 

1128particular instance in a recurrence set. The value is the original start 

1129date or date-time of that instance, not the rescheduled time. 

1130 

1131The value is usually a DATE-TIME and must use the same value type as the 

1132``DTSTART`` property in the same component. A DATE value may be used for 

1133all-day items instead. 

1134 

1135This property corresponds to ``RECURRENCE-ID`` as defined in RFC 5545 and 

1136may appear in recurring ``VEVENT``, ``VTODO``, and ``VJOURNAL`` components. 

1137""", 

1138 vDDDTypes, 

1139) 

1140 

1141 

1142def _get_organizer(self: Component) -> vCalAddress | None: 

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

1144 

1145 Property Parameters: 

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

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

1148 specified on this property. 

1149 

1150 Conformance: 

1151 This property MUST be specified in an iCalendar object 

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

1153 MUST be specified in an iCalendar object that specifies the 

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

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

1156 zone definition or that defines calendar components that are not 

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

1158 user's calendar. 

1159 

1160 Description: 

1161 This property is specified within the "VEVENT", 

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

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

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

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

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

1167 the calendar that the published busy time came from. 

1168 

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

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

1171 specifying a pointer to the directory information associated with 

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

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

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

1175 "LANGUAGE" property parameter is specified, the identified 

1176 language applies to the "CN" parameter value. 

1177 """ 

1178 return self.get("ORGANIZER") 

1179 

1180 

1181def _set_organizer(self: Component, value: vCalAddress | str | None): 

1182 """Set the value.""" 

1183 _del_organizer(self) 

1184 if value is not None: 

1185 self.add("ORGANIZER", value) 

1186 

1187 

1188def _del_organizer(self: Component): 

1189 """Delete the value.""" 

1190 self.pop("ORGANIZER") 

1191 

1192 

1193organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1194 

1195 

1196def single_string_enum_property( 

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

1198) -> property: 

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

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

1201 

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

1203 """Get the value.""" 

1204 value = prop.fget(self) 

1205 if value == default: 

1206 return default 

1207 return enum(str(value)) 

1208 

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

1210 """Set the value.""" 

1211 if value == "": 

1212 value = None 

1213 prop.fset(self, value) 

1214 

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

1216 

1217 

1218busy_type_property = single_string_enum_property( 

1219 "BUSYTYPE", 

1220 BUSYTYPE, 

1221 BUSYTYPE.BUSY_UNAVAILABLE, 

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

1223 

1224Returns: 

1225 :class:`icalendar.enums.BUSYTYPE` 

1226 

1227Description: 

1228 This property is used to specify the default busy time 

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

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

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

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

1233 UNAVAILABLE". 

1234""", 

1235) 

1236 

1237priority_property = single_int_property( 

1238 "PRIORITY", 

1239 0, 

1240 """ 

1241 

1242Conformance: 

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

1244 according to :rfc:`5545`. 

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

1246 

1247Description: 

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

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

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

1251 priority. Subsequent numbers specify a decreasing ordinal 

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

1253 

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

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

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

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

1258 is "LOW" priority. 

1259 

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

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

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

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

1264 9 specifies "C3". 

1265 

1266 Other integer values are reserved for future use. 

1267 

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

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

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

1271 

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

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

1274 multiple action items for a given time period. 

1275""", 

1276) 

1277 

1278class_property = single_string_enum_property( 

1279 "CLASS", 

1280 CLASS, 

1281 CLASS.PUBLIC, 

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

1283 

1284Returns: 

1285 :class:`icalendar.enums.CLASS` 

1286 

1287Description: 

1288 An access classification is only one component of the 

1289 general security system within a calendar application. It 

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

1291 calendar owner intends for information within an individual 

1292 calendar entry. The access classification of an individual 

1293 iCalendar component is useful when measured along with the other 

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

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

1296 Hence, the semantics of the individual access classifications 

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

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

1299 memo, these access classifications cannot serve as an enforcement 

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

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

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

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

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

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

1306""", 

1307) 

1308 

1309transparency_property = single_string_enum_property( 

1310 "TRANSP", 

1311 TRANSP, 

1312 TRANSP.OPAQUE, 

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

1314 

1315Returns: 

1316 :class:`icalendar.enums.TRANSP` 

1317 

1318Description: 

1319 Time Transparency is the characteristic of an event 

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

1321 Events that consume actual time for the individual or resource 

1322 associated with the calendar SHOULD be recorded as OPAQUE, 

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

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

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

1326 busy time searches. 

1327""", 

1328) 

1329status_property = single_string_enum_property( 

1330 "STATUS", 

1331 STATUS, 

1332 "", 

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

1334 

1335Returns: 

1336 :class:`icalendar.enums.STATUS` 

1337 

1338The default value is ``""``. 

1339 

1340Description: 

1341 In a group-scheduled calendar component, the property 

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

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

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

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

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

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

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

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

1350 cancelled or removed. 

1351""", 

1352) 

1353 

1354url_property = single_string_property( 

1355 "URL", 

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

1357 

1358Description: 

1359 This property may be used in a calendar component to 

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

1361 information associated with the calendar component can be found. 

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

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

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

1365 specified, they MUST point to the same resource. 

1366 

1367Conformance: 

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

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

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

1371 

1372Example: 

1373 The following is an example of this property: 

1374 

1375 .. code-block:: ics 

1376 

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

1378 

1379""", 

1380) 

1381 

1382source_property = single_string_property( 

1383 "SOURCE", 

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

1385 

1386Description: 

1387 This property identifies a location where a client can 

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

1389 specified "REFRESH-INTERVAL" value when periodically retrieving 

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

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

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

1393 

1394Conformance: 

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

1396 

1397Example: 

1398 The following is an example of this property: 

1399 

1400 .. code-block:: ics 

1401 

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

1403 

1404""", 

1405) 

1406 

1407location_property = multi_language_text_property( 

1408 "LOCATION", 

1409 None, 

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

1411 

1412Property Parameters: 

1413 IANA, non-standard, alternate text 

1414 representation, and language property parameters can be specified 

1415 on this property. 

1416 

1417Conformance: 

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

1419 calendar component. 

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

1421 

1422Description: 

1423 Specific venues such as conference or meeting rooms may 

1424 be explicitly specified using this property. An alternate 

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

1426 directory information with more structured specification of the 

1427 location. For example, the alternate representation may specify 

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

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

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

1431 

1432""", 

1433) 

1434 

1435contacts_property = multi_text_property( 

1436 "CONTACT", 

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

1438 

1439Purpose: 

1440 This property is used to represent contact information or 

1441 alternately a reference to contact information associated with the 

1442 calendar component. 

1443 

1444Property Parameters: 

1445 IANA, non-standard, alternate text 

1446 representation, and language property parameters can be specified 

1447 on this property. 

1448 

1449Conformance: 

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

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

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

1453 amd "VAVAILABLE" calendar component. 

1454 

1455Description: 

1456 The property value consists of textual contact 

1457 information. An alternative representation for the property value 

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

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

1460 information. 

1461 

1462Example: 

1463 The following is an example of this property referencing 

1464 textual contact information: 

1465 

1466 .. code-block:: ics 

1467 

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

1469 

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

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

1472 contact information: 

1473 

1474 .. code-block:: ics 

1475 

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

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

1478 +1-919-555-1234 

1479 

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

1481 representation of a MIME body part containing the contact 

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

1483 directory media type :rfc:`2425`: 

1484 

1485 .. code-block:: ics 

1486 

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

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

1489 

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

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

1492 information: 

1493 

1494 .. code-block:: ics 

1495 

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

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

1498""", 

1499) 

1500 

1501 

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

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

1504 

1505 return single_utc_property(name, docs) 

1506 

1507 

1508rfc_7953_dtstart_property = timezone_datetime_property( 

1509 "DTSTART", 

1510 """Start of the component. 

1511 

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

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

1514 

1515 Description: 

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

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

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

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

1520 

1521 """, 

1522) 

1523 

1524rfc_7953_dtend_property = timezone_datetime_property( 

1525 "DTEND", 

1526 """Start of the component. 

1527 

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

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

1530 

1531 Description: 

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

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

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

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

1536 """, 

1537) 

1538 

1539 

1540@property 

1541def rfc_7953_duration_property(self) -> timedelta | None: 

1542 """Compute the duration of this component. 

1543 

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

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

1546 :attr:`DTEND`/:attr:`DURATION`. 

1547 

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

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

1550 """ 

1551 duration = self.DURATION 

1552 if duration: 

1553 return duration 

1554 end = self.DTEND 

1555 if end is None: 

1556 return None 

1557 start = self.DTSTART 

1558 if start is None: 

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

1560 return end - start 

1561 

1562 

1563@property 

1564def rfc_7953_end_property(self) -> timedelta | None: 

1565 """Compute the duration of this component. 

1566 

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

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

1569 :attr:`DTEND`/:attr:`DURATION`. 

1570 

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

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

1573 """ 

1574 duration = self.DURATION 

1575 if duration: 

1576 start = self.DTSTART 

1577 if start is None: 

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

1579 return start + duration 

1580 end = self.DTEND 

1581 if end is None: 

1582 return None 

1583 return end 

1584 

1585 

1586@rfc_7953_end_property.setter 

1587def rfc_7953_end_property(self, value: datetime): 

1588 self.DTEND = value 

1589 

1590 

1591@rfc_7953_end_property.deleter 

1592def rfc_7953_end_property(self): 

1593 del self.DTEND 

1594 

1595 

1596def get_start_end_duration_with_validation( 

1597 component: Component, 

1598 start_property: str, 

1599 end_property: str, 

1600 component_name: str, 

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

1602 """ 

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

1604 

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

1606 for ``Event`` and ``Todo`` components. 

1607 

1608 Parameters: 

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

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

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

1612 ``DUE`` for ``Todo``. 

1613 component_name: The component name for error messages, 

1614 either ``VEVENT`` or ``VTODO``. 

1615 

1616 Returns: 

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

1618 

1619 Raises: 

1620 ~error.InvalidCalendar: If the component violates RFC 5545 constraints. 

1621 

1622 """ 

1623 start = getattr(component, start_property, None) 

1624 end = getattr(component, end_property, None) 

1625 duration = component.DURATION 

1626 

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

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

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

1630 msg = ( 

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

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

1633 ) 

1634 raise InvalidCalendar(msg) 

1635 

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

1637 if ( 

1638 start is not None 

1639 and is_date(start) 

1640 and duration is not None 

1641 and duration.seconds != 0 

1642 ): 

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

1644 raise InvalidCalendar(msg) 

1645 

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

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

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

1649 msg = ( 

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

1651 ) 

1652 raise InvalidCalendar(msg) 

1653 

1654 return start, end, duration 

1655 

1656 

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

1658 """ 

1659 Get the start property with validation. 

1660 

1661 Parameters: 

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

1663 

1664 Returns: 

1665 The ``DTSTART`` value. 

1666 

1667 Raises: 

1668 ~error.IncompleteComponent: If no ``DTSTART`` is present. 

1669 

1670 """ 

1671 # Trigger validation by calling _get_start_end_duration 

1672 start, _end, _duration = component._get_start_end_duration() # noqa: SLF001 

1673 if start is None: 

1674 msg = "No DTSTART given." 

1675 raise IncompleteComponent(msg) 

1676 return start 

1677 

1678 

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

1680 """ 

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

1682 

1683 Parameters: 

1684 component: The component to get end from 

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

1686 ``DUE`` for ``Todo``. 

1687 

1688 Returns: 

1689 The computed end value. 

1690 

1691 Raises: 

1692 ~error.IncompleteComponent: If the provided information is incomplete 

1693 to compute the end property. 

1694 

1695 """ 

1696 # Trigger validation by calling _get_start_end_duration 

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

1698 

1699 if end is None and duration is None: 

1700 if start is None: 

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

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

1703 raise IncompleteComponent(msg) 

1704 

1705 # Default behavior differs for Event vs Todo: 

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

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

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

1709 return start + timedelta(days=1) 

1710 return start 

1711 

1712 if duration is not None: 

1713 if start is not None: 

1714 if component.name == "VEVENT" and duration.total_seconds() <= 0: 

1715 return start 

1716 return start + duration 

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

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

1719 raise IncompleteComponent(msg) 

1720 

1721 return end 

1722 

1723 

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

1725 """ 

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

1727 

1728 Parameters: 

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

1730 

1731 Returns: 

1732 The duration as a timedelta. 

1733 

1734 """ 

1735 # First check if DURATION property is explicitly set 

1736 if "DURATION" in component: 

1737 return component["DURATION"].dt 

1738 

1739 # Fall back to calculated duration from start and end 

1740 return component.end - component.start 

1741 

1742 

1743def set_duration_with_locking( 

1744 component: Component, 

1745 duration: timedelta | None, 

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

1747 end_property: str, 

1748) -> None: 

1749 """ 

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

1751 

1752 Parameters: 

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

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

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

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

1757 ``DUE`` for ``Todo``. 

1758 

1759 """ 

1760 # Convert to DURATION property if duration is None 

1761 if duration is None: 

1762 if "DURATION" in component: 

1763 return # Already has DURATION property 

1764 current_duration = component.duration 

1765 component.DURATION = current_duration 

1766 return 

1767 

1768 if not isinstance(duration, timedelta): 

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

1770 raise TypeError(msg) 

1771 

1772 # Validate date/duration compatibility 

1773 start = component.DTSTART 

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

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

1776 raise InvalidCalendar(msg) 

1777 

1778 if locked == "start": 

1779 # Keep start locked, adjust end 

1780 if start is None: 

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

1782 raise IncompleteComponent(msg) 

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

1784 component.DURATION = duration 

1785 elif locked == "end": 

1786 # Keep end locked, adjust start 

1787 current_end = component.end 

1788 component.DTSTART = current_end - duration 

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

1790 component.DURATION = duration 

1791 else: 

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

1793 raise ValueError(msg) 

1794 

1795 

1796def set_start_with_locking( 

1797 component: Component, 

1798 start: date | datetime, 

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

1800 end_property: str, 

1801) -> None: 

1802 """ 

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

1804 

1805 Parameters: 

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

1807 start: The start time to set. 

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

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

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

1811 ``DUE`` for ``Todo``. 

1812 

1813 """ 

1814 if locked is None: 

1815 # Auto-detect based on existing properties 

1816 if "DURATION" in component: 

1817 locked = "duration" 

1818 elif end_property in component: 

1819 locked = "end" 

1820 else: 

1821 # Default to duration if no existing properties 

1822 locked = "duration" 

1823 

1824 if locked == "duration": 

1825 # Keep duration locked, adjust end 

1826 current_duration = ( 

1827 component.duration 

1828 if "DURATION" in component or end_property in component 

1829 else None 

1830 ) 

1831 component.DTSTART = start 

1832 if current_duration is not None: 

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

1834 component.DURATION = current_duration 

1835 elif locked == "end": 

1836 # Keep end locked, adjust duration 

1837 current_end = component.end 

1838 component.DTSTART = start 

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

1840 setattr(component, end_property, current_end) 

1841 else: 

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

1843 raise ValueError(msg) 

1844 

1845 

1846def set_end_with_locking( 

1847 component: Component, 

1848 end: date | datetime, 

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

1850 end_property: str, 

1851) -> None: 

1852 """ 

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

1854 

1855 Parameters: 

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

1857 end: The end time to set. 

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

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

1860 for ``Todo``. 

1861 

1862 """ 

1863 if locked == "start": 

1864 # Keep start locked, adjust duration 

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

1866 setattr(component, end_property, end) 

1867 elif locked == "duration": 

1868 # Keep duration locked, adjust start 

1869 current_duration = component.duration 

1870 component.DTSTART = end - current_duration 

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

1872 component.DURATION = current_duration 

1873 else: 

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

1875 raise ValueError(msg) 

1876 

1877 

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

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

1880 

1881 Description: 

1882 This property specifies an image for an iCalendar 

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

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

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

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

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

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

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

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

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

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

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

1894 on the image in the calendar user agent. 

1895 

1896 Conformance: 

1897 This property can be specified multiple times in an 

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

1899 components. 

1900 

1901 .. note:: 

1902 

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

1904 please open an issue or a pull request. 

1905 """ 

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

1907 if not isinstance(images, SEQUENCE_TYPES): 

1908 images = [images] 

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

1910 

1911 

1912images_property = property(_get_images) 

1913 

1914 

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

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

1917 

1918 Purpose: 

1919 This property specifies information for accessing a conferencing system. 

1920 

1921 Conformance: 

1922 This property can be specified multiple times in a 

1923 "VEVENT" or "VTODO" calendar component. 

1924 

1925 Description: 

1926 This property specifies information for accessing a 

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

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

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

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

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

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

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

1934 containing a text description can be used. 

1935 

1936 A conference system can be a bidirectional communication channel 

1937 or a uni-directional "broadcast feed". 

1938 

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

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

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

1942 multiple properties. 

1943 

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

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

1946 codes for the moderator and attendee of a teleconference system 

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

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

1949 which. 

1950 

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

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

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

1954 

1955 Example: 

1956 The following are examples of this property: 

1957 

1958 .. code-block:: ics 

1959 

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

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

1962 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

1964 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

1966 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

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

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

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

1970 

1971 Get all conferences: 

1972 

1973 .. code-block:: pycon 

1974 

1975 >>> from icalendar import Event 

1976 >>> event = Event() 

1977 >>> event.conferences 

1978 [] 

1979 

1980 Set a conference: 

1981 

1982 .. code-block:: pycon 

1983 

1984 >>> from icalendar import Event, Conference 

1985 >>> event = Event() 

1986 >>> event.conferences = [ 

1987 ... Conference( 

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

1989 ... feature="PHONE,MODERATOR", 

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

1991 ... language="EN", 

1992 ... ) 

1993 ... ] 

1994 >>> print(event.to_ical()) 

1995 BEGIN:VEVENT 

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

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

1998 END:VEVENT 

1999 

2000 """ 

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

2002 if not isinstance(conferences, SEQUENCE_TYPES): 

2003 conferences = [conferences] 

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

2005 

2006 

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

2008 """Set the conferences.""" 

2009 _del_conferences(self) 

2010 for conference in conferences or []: 

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

2012 

2013 

2014def _del_conferences(self: Component): 

2015 """Delete all conferences.""" 

2016 self.pop("CONFERENCE") 

2017 

2018 

2019conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

2020 

2021 

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

2023 """LINK properties as a list. 

2024 

2025 Purpose: 

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

2027 

2028 Property Parameters: 

2029 The VALUE parameter is required. 

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

2031 can also be specified on this property. 

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

2033 

2034 Conformance: 

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

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

2037 The LINKREL parameter is required. 

2038 

2039 Description: 

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

2041 additional information related to the component. 

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

2043 

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

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

2046 the link context is the containing calendar entity, 

2047 and the link relation type and any target attributes 

2048 are carried in iCalendar property parameters. 

2049 

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

2051 

2052 LABEL 

2053 This parameter maps to the "title" 

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

2055 LABEL is used to label the destination 

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

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

2058 (if present). 

2059 LANGUAGE 

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

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

2062 LINKREL 

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

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

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

2066 FMTTYPE 

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

2068 :rfc:`8288`. 

2069 

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

2071 

2072 Examples: 

2073 The following is an example of this property, 

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

2075 

2076 .. code-block:: ics 

2077 

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

2079 https://example.com/events 

2080 

2081 The following is an example of this property, 

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

2083 The link relation is a vendor-defined value. 

2084 

2085 .. code-block:: ics 

2086 

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

2088 VALUE=URI: 

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

2090 

2091 The following is an example of this property, 

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

2093 The link relation is a vendor-defined value. 

2094 

2095 .. code-block:: ics 

2096 

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

2098 VALUE=XML-REFERENCE: 

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

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

2101 following::CostStrucEND[1])) 

2102 

2103 Set a link :class:`icalendar.prop.uri.vUri` to the event page: 

2104 

2105 .. code-block:: pycon 

2106 

2107 >>> from icalendar import Event, vUri 

2108 >>> from datetime import datetime 

2109 >>> link = vUri( 

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

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

2112 ... ) 

2113 >>> event = Event.new( 

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

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

2116 ... ) 

2117 >>> event.links = [link] 

2118 >>> print(event.to_ical()) 

2119 BEGIN:VEVENT 

2120 SUMMARY:An Example Event with a page 

2121 DTSTART:20250917T120000 

2122 DTSTAMP:20250517T080612Z 

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

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

2125 END:VEVENT 

2126 

2127 """ 

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

2129 if not isinstance(links, list): 

2130 links = [links] 

2131 return links 

2132 

2133 

2134LINKS_TYPE_SETTER: TypeAlias = ( 

2135 str | vUri | vUid | vXmlReference | None | list[str | vUri | vUid | vXmlReference] 

2136) 

2137 

2138 

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

2140 """Set the LINKs.""" 

2141 _del_links(self) 

2142 if links is None: 

2143 return 

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

2145 links = [links] 

2146 for link in links: 

2147 if type(link) is str: 

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

2149 self.add("LINK", link) 

2150 

2151 

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

2153 """Delete all links.""" 

2154 self.pop("LINK") 

2155 

2156 

2157links_property = property(_get_links, _set_links, _del_links) 

2158 

2159RELATED_TO_TYPE_SETTER: TypeAlias = ( 

2160 None | str | vText | vUri | vUid | list[str | vText | vUri | vUid] 

2161) 

2162 

2163 

2164def _get_related_to(self: Component) -> list[vText | vUri | vUid]: 

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

2166 

2167 Purpose: 

2168 This property is used to represent a relationship or reference 

2169 between one calendar component and another. 

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

2171 

2172 Value Type: 

2173 :rfc:`5545`: TEXT 

2174 :rfc:`9253`: URI, UID 

2175 

2176 Conformance: 

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

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

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

2180 iCalendar component. 

2181 

2182 Description (:rfc:`5545`): 

2183 The property value consists of the persistent, globally 

2184 unique identifier of another calendar component. This value would 

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

2186 

2187 By default, the property value points to another calendar 

2188 component that has a PARENT relationship to the referencing 

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

2190 explicitly state the default PARENT relationship type to the 

2191 referenced calendar component or to override the default PARENT 

2192 relationship type and specify either a CHILD or SIBLING 

2193 relationship. The PARENT relationship indicates that the calendar 

2194 component is a subordinate of the referenced calendar component. 

2195 The CHILD relationship indicates that the calendar component is a 

2196 superior of the referenced calendar component. The SIBLING 

2197 relationship indicates that the calendar component is a peer of 

2198 the referenced calendar component. 

2199 

2200 Changes to a calendar component referenced by this property can 

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

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

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

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

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

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

2207 property is intended only to provide information on the 

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

2209 calendar system to maintain any property implications of this 

2210 relationship. 

2211 

2212 Description (:rfc:`9253`): 

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

2214 consists of the persistent, globally unique identifier of another 

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

2216 component by the UID property. 

2217 

2218 By default, the property value 

2219 points to another calendar component that has a PARENT relationship 

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

2221 to either explicitly state the default PARENT relationship type to 

2222 the referenced calendar component or to override the default 

2223 PARENT relationship type and specify either a CHILD or SIBLING 

2224 relationship or a temporal relationship. 

2225 

2226 The PARENT relationship 

2227 indicates that the calendar component is a subordinate of the 

2228 referenced calendar component. The CHILD relationship indicates 

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

2230 component. The SIBLING relationship indicates that the calendar 

2231 component is a peer of the referenced calendar component. 

2232 

2233 To preserve backwards compatibility, the value type MUST 

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

2235 are specified. 

2236 

2237 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH, 

2238 or STARTTOSTART relationships define temporal relationships, as 

2239 specified in the RELTYPE parameter definition. 

2240 

2241 The FIRST and NEXT 

2242 define ordering relationships between calendar components. 

2243 

2244 The DEPENDS-ON relationship indicates that the current calendar 

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

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

2247 referenced, task. 

2248 

2249 The REFID and CONCEPT relationships establish 

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

2251 Changes to a calendar component referenced by this property 

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

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

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

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

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

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

2258 components. This property is intended only to provide information 

2259 on the relationship of calendar components. 

2260 

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

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

2263 

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

2265 implications of these relationships. 

2266 

2267 Examples: 

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

2269 

2270 .. code-block:: ics 

2271 

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

2273 

2274 .. code-block:: ics 

2275 

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

2277 

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

2279 

2280 .. code-block:: ics 

2281 

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

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

2284 19960401-080045-4000F192713.ics 

2285 

2286 See also :class:`icalendar.enums.RELTYPE`. 

2287 

2288 """ 

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

2290 if not isinstance(result, list): 

2291 return [result] 

2292 return result 

2293 

2294 

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

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

2297 _del_related_to(self) 

2298 if values is None: 

2299 return 

2300 if not isinstance(values, list): 

2301 values = [values] 

2302 for value in values: 

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

2304 

2305 

2306def _del_related_to(self: Component): 

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

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

2309 

2310 

2311related_to_property = property(_get_related_to, _set_related_to, _del_related_to) 

2312 

2313 

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

2315 """CONCEPT 

2316 

2317 Purpose: 

2318 CONCEPT defines the formal categories for a calendar component. 

2319 

2320 Conformance: 

2321 Since :rfc:`9253`, 

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

2323 

2324 Description: 

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

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

2327 component of a particular type and category. 

2328 

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

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

2331 the CONCEPT property will reference an external resource that provides 

2332 information about the categorization. 

2333 

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

2335 events. 

2336 

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

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

2339 

2340 Examples: 

2341 The following is an example of this property. 

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

2343 

2344 .. code-block:: ics 

2345 

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

2347 

2348 .. seealso:: 

2349 

2350 :attr:`icalendar.prop.categories.vCategory` 

2351 """ 

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

2353 if not isinstance(concepts, list): 

2354 concepts = [concepts] 

2355 return concepts 

2356 

2357 

2358CONCEPTS_TYPE_SETTER: TypeAlias = list[vUri | str] | str | vUri | None 

2359 

2360 

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

2362 """Set the concepts.""" 

2363 _del_concepts(self) 

2364 if concepts is None: 

2365 return 

2366 if not isinstance(concepts, list): 

2367 concepts = [concepts] 

2368 for value in concepts: 

2369 self.add("CONCEPT", value) 

2370 

2371 

2372def _del_concepts(self: Component): 

2373 """Delete the concepts.""" 

2374 self.pop("CONCEPT", None) 

2375 

2376 

2377concepts_property = property(_get_concepts, _set_concepts, _del_concepts) 

2378 

2379 

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

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

2382 

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

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

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

2386 if not isinstance(value, list): 

2387 value = [value] 

2388 return value 

2389 

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

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

2392 fdel(self) 

2393 if value is None: 

2394 return 

2395 if not isinstance(value, list): 

2396 value = [value] 

2397 for value in value: 

2398 self.add(name, value) 

2399 

2400 def fdel(self: Component): 

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

2402 self.pop(name, None) 

2403 

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

2405 

2406 

2407refids_property = multi_string_property( 

2408 "REFID", 

2409 """REFID 

2410 

2411Purpose: 

2412 REFID acts as a key for associated iCalendar entities. 

2413 

2414Conformance: 

2415 Since :rfc:`9253`, 

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

2417 

2418Description: 

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

2420 identifier for associated components. 

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

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

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

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

2425 

2426Examples: 

2427 The following is an example of this property. 

2428 

2429 .. code-block:: ics 

2430 

2431 REFID:itinerary-2014-11-17 

2432 

2433 Use a REFID to associate several VTODOs: 

2434 

2435 .. code-block:: pycon 

2436 

2437 >>> from icalendar import Todo 

2438 >>> todo_1 = Todo.new( 

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

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

2441 ... ) 

2442 >>> todo_2 = Todo.new( 

2443 ... summary="pack backpack", 

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

2445 ... ) 

2446 >>> todo_1.refids == todo_2.refids 

2447 True 

2448 

2449.. note:: 

2450 

2451 List modifications do not modify the component. 

2452""", 

2453) 

2454 

2455 

2456__all__ = [ 

2457 "CONCEPTS_TYPE_SETTER", 

2458 "LINKS_TYPE_SETTER", 

2459 "RECURRENCE_ID", 

2460 "RELATED_TO_TYPE_SETTER", 

2461 "attendees_property", 

2462 "busy_type_property", 

2463 "categories_property", 

2464 "class_property", 

2465 "color_property", 

2466 "comments_property", 

2467 "concepts_property", 

2468 "conferences_property", 

2469 "contacts_property", 

2470 "create_single_property", 

2471 "description_property", 

2472 "descriptions_property", 

2473 "duration_property", 

2474 "exdates_property", 

2475 "get_duration_property", 

2476 "get_end_property", 

2477 "get_start_end_duration_with_validation", 

2478 "get_start_property", 

2479 "images_property", 

2480 "links_property", 

2481 "location_property", 

2482 "multi_language_text_property", 

2483 "multi_string_property", 

2484 "organizer_property", 

2485 "priority_property", 

2486 "property_del_duration", 

2487 "property_doc_duration_template", 

2488 "property_get_duration", 

2489 "property_set_duration", 

2490 "rdates_property", 

2491 "refids_property", 

2492 "related_to_property", 

2493 "rfc_7953_dtend_property", 

2494 "rfc_7953_dtstart_property", 

2495 "rfc_7953_duration_property", 

2496 "rfc_7953_end_property", 

2497 "rrules_property", 

2498 "sequence_property", 

2499 "set_duration_with_locking", 

2500 "set_end_with_locking", 

2501 "set_start_with_locking", 

2502 "single_int_property", 

2503 "single_utc_property", 

2504 "source_property", 

2505 "status_property", 

2506 "summary_property", 

2507 "transparency_property", 

2508 "uid_property", 

2509 "url_property", 

2510]