Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/attr.py: 30%

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

564 statements  

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

2 

3from __future__ import annotations 

4 

5import itertools 

6from collections.abc import Sequence 

7from datetime import date, datetime, timedelta 

8from typing import TYPE_CHECKING, Literal, TypeAlias 

9 

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

11from icalendar.error import IncompleteComponent, InvalidCalendar 

12from icalendar.parser_tools import SEQUENCE_TYPES 

13from icalendar.prop import ( 

14 vBinary, 

15 vCalAddress, 

16 vCategory, 

17 vDDDTypes, 

18 vDuration, 

19 vRecur, 

20 vText, 

21 vUid, 

22 vUnknown, 

23 vUri, 

24 vXmlReference, 

25) 

26from icalendar.prop.conference import Conference 

27from icalendar.prop.image import Image 

28from icalendar.timezone import tzp 

29from icalendar.tools import is_date 

30 

31if TYPE_CHECKING: 

32 from collections.abc import Callable 

33 

34 from icalendar.cal import Component 

35 from icalendar.cal.event import Event 

36 from icalendar.cal.todo import Todo 

37 

38 

39def _get_rdates( 

40 self: Component, 

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

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

43 

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

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

46 

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

48 with and without timezone. 

49 

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

51 if the end is specified. 

52 

53 Value Type: 

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

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

56 

57 Property Parameters: 

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

59 zone identifier property parameters can be specified on this 

60 property. 

61 

62 Conformance: 

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

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

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

66 calendar component. 

67 

68 Description: 

69 This property can appear along with the "RRULE" 

70 property to define an aggregate set of repeating occurrences. 

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

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

73 the "RDATE" and "RRULE". 

74 

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

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

77 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

93 Duplicate instances are ignored. 

94 

95 Example: 

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

97 

98 .. code-block:: pycon 

99 

100 >>> from icalendar import Event 

101 >>> from datetime import datetime 

102 >>> event = Event() 

103 

104 # Add a list of recurrence dates 

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

106 >>> event.rdates 

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

108 

109 .. note:: 

110 

111 Modifying the returned list does not change the RDATE value. Assign to 

112 ``rdates`` for the relevant component or use 

113 :meth:`Component.add <icalendar.cal.component.Component.add>` instead. 

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

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

116 

117 """ 

118 result = [] 

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

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

121 for dts in rdates.dts: 

122 rdate = dts.dt 

123 if isinstance(rdate, tuple): 

124 # we have a period as rdate 

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

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

127 else: 

128 result.append(rdate) 

129 else: 

130 # we have a date/datetime 

131 result.append((rdate, None)) 

132 return result 

133 

134 

135def _set_rdates(self: Component, value) -> None: 

136 """Set the RDATE values, replacing any existing ones. 

137 

138 ``value`` is a list as returned by :attr:`rdates` (each item a date, a 

139 datetime, or a ``(start, end)`` period tuple). Setting an empty list or 

140 :obj:`None` removes the RDATE property. 

141 """ 

142 _del_rdates(self) 

143 if value: 

144 self.add("RDATE", value) 

145 

146 

147def _del_rdates(self: Component) -> None: 

148 """Delete all RDATE values.""" 

149 self.pop("RDATE", None) 

150 

151 

152rdates_property = property(_get_rdates, _set_rdates, _del_rdates) 

153 

154 

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

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

157 

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

159 

160 Value Type: 

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

162 The value type can be set to DATE. 

163 

164 Property Parameters: 

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

166 zone identifier property parameters can be specified on this 

167 property. 

168 

169 Conformance: 

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

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

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

173 calendar component. 

174 

175 Description: 

176 The exception dates, if specified, are used in 

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

178 set of recurrence instances for a calendar component. The 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

194 Duplicate instances are ignored. 

195 

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

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

198 MUST still be maintained by the calendaring and scheduling system 

199 because the original "DTSTART" value has inherent usage 

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

201 

202 Example: 

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

204 

205 .. code-block:: pycon 

206 

207 >>> from icalendar import Event 

208 >>> from datetime import datetime 

209 >>> event = Event() 

210 

211 # Add a list of excluded dates 

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

213 >>> event.exdates 

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

215 

216 .. note:: 

217 

218 Modifying the returned list does not change the EXDATE value. Assign to 

219 ``exdates`` for the relevant component or use 

220 :meth:`Component.add <icalendar.cal.component.Component.add>` instead. 

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

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

223 

224 """ 

225 result = [] 

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

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

228 for dts in exdates.dts: 

229 exdate = dts.dt 

230 # we have a date/datetime 

231 result.append(exdate) 

232 return result 

233 

234 

235def _set_exdates(self: Component, value) -> None: 

236 """Set the EXDATE values, replacing any existing ones. 

237 

238 ``value`` is a list as returned by :attr:`exdates` (each item a date or a 

239 datetime). Setting an empty list or :obj:`None` removes the EXDATE property. 

240 """ 

241 _del_exdates(self) 

242 if value: 

243 self.add("EXDATE", value) 

244 

245 

246def _del_exdates(self: Component) -> None: 

247 """Delete all EXDATE values.""" 

248 self.pop("EXDATE", None) 

249 

250 

251exdates_property = property(_get_exdates, _set_exdates, _del_exdates) 

252 

253 

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

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

256 

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

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

259 

260 Property Parameters: 

261 IANA and non-standard property parameters can 

262 be specified on this property. 

263 

264 Conformance: 

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

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

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

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

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

270 undefined. 

271 

272 Description: 

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

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

275 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

280 value SHOULD be synchronized with the recurrence rule, if 

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

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

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

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

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

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

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

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

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

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

291 Duplicate instances are ignored. 

292 

293 The "DTSTART" property specified within the iCalendar object 

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

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

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

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

298 same local time regardless of time zone changes. 

299 

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

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

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

303 duration of the recurring component is specified with the 

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

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

306 duration of each recurrence instance will depend on its specific 

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

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

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

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

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

312 type. 

313 

314 Examples: 

315 Daily for 10 occurrences: 

316 

317 .. code-block:: pycon 

318 

319 >>> from icalendar import Event 

320 >>> from datetime import datetime 

321 >>> from zoneinfo import ZoneInfo 

322 >>> event = Event() 

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

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

325 >>> print(event.to_ical()) 

326 BEGIN:VEVENT 

327 DTSTART;TZID=America/New_York:19970902T090000 

328 RRULE:FREQ=DAILY;COUNT=10 

329 END:VEVENT 

330 >>> event.rrules 

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

332 

333 Daily until December 24, 1997: 

334 

335 .. code-block:: pycon 

336 

337 >>> from icalendar import Event, vRecur 

338 >>> from datetime import datetime 

339 >>> from zoneinfo import ZoneInfo 

340 >>> event = Event() 

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

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

343 >>> print(event.to_ical()) 

344 BEGIN:VEVENT 

345 DTSTART;TZID=America/New_York:19970902T090000 

346 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

347 END:VEVENT 

348 >>> event.rrules 

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

350 

351 .. note:: 

352 

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

354 Use :meth:`Component.add <icalendar.cal.component.Component.add>` to add values. 

355 

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

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

358 

359 """ # noqa: E501 

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

361 if not isinstance(rrules, list): 

362 return [rrules] 

363 return rrules 

364 

365 

366rrules_property = property(_get_rrules) 

367 

368 

369def multi_language_text_property( 

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

371) -> property: 

372 """This creates a text property. 

373 

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

375 

376 Parameters: 

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

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

379 doc (str): The documentation string 

380 """ 

381 

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

383 """Get the property""" 

384 result = self.get(main_prop) 

385 if result is None and compatibility_prop is not None: 

386 result = self.get(compatibility_prop) 

387 if isinstance(result, list): 

388 for item in result: 

389 if "LANGUAGE" not in item.params: 

390 return item 

391 return result 

392 

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

394 """Set the property.""" 

395 fdel(self) 

396 if value is not None: 

397 self.add(main_prop, value) 

398 if compatibility_prop is not None: 

399 self.add(compatibility_prop, value) 

400 

401 def fdel(self: Component): 

402 """Delete the property.""" 

403 self.pop(main_prop, None) 

404 if compatibility_prop is not None: 

405 self.pop(compatibility_prop, None) 

406 

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

408 

409 

410def single_int_property( 

411 prop: str, default: int, doc: str, *, min_value: int | None = None 

412) -> property: 

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

414 

415 Parameters: 

416 default: Required. The default value. 

417 doc: Required. The documentation string. 

418 prop: Required. The name of the property. 

419 min_value: If set, the value must be greater than or equal to this minimum. 

420 

421 .. versionadded:: 7.3.0 

422 Added the ``min_value`` parameter. 

423 """ 

424 

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

426 """Get the property""" 

427 try: 

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

429 except ValueError as e: 

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

431 

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

433 """Set the property.""" 

434 if value is not None: 

435 if not isinstance(value, int) or isinstance(value, bool): 

436 raise TypeError(f"{prop} must be an int, got {value!r}") 

437 if min_value is not None and value < min_value: 

438 raise InvalidCalendar(f"{prop} must be >= {min_value}, got {value}") 

439 fdel(self) 

440 if value is not None: 

441 self.add(prop, value) 

442 

443 def fdel(self: Component): 

444 """Delete the property.""" 

445 self.pop(prop, None) 

446 

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

448 

449 

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

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

452 

453 Parameters: 

454 name: name of the property 

455 docs: documentation string 

456 """ 

457 

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

459 """Get the value.""" 

460 if name not in self: 

461 return None 

462 dt = self.get(name) 

463 if isinstance(dt, (vText, vUnknown)): 

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

465 value = vDDDTypes.from_ical(dt) 

466 else: 

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

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

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

470 return tzp.localize_utc(value) 

471 

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

473 """Set the value""" 

474 if value is None: 

475 fdel(self) 

476 return 

477 if not isinstance(value, date): 

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

479 fdel(self) 

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

481 

482 def fdel(self: Component): 

483 """Delete the property.""" 

484 self.pop(name, None) 

485 

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

487 

488 

489def single_string_property( 

490 name: str, docs: str, other_name: str | list[str] | None = None, default: str = "" 

491) -> property: 

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

493 other_names = ( 

494 [] 

495 if other_name is None 

496 else [other_name] 

497 if isinstance(other_name, str) 

498 else list(other_name) 

499 ) 

500 

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

502 """Get the value.""" 

503 result = self.get(name, None) 

504 if result is None: 

505 for alias in other_names: 

506 result = self.get(alias, None) 

507 if result is not None: 

508 break 

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

510 return default 

511 if isinstance(result, list): 

512 return result[0] 

513 return result 

514 

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

516 """Set the value. 

517 

518 Setting the value to None will delete it. 

519 """ 

520 fdel(self) 

521 if value is not None: 

522 self.add(name, value) 

523 

524 def fdel(self: Component): 

525 """Delete the property.""" 

526 self.pop(name, None) 

527 for alias in other_names: 

528 self.pop(alias, None) 

529 

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

531 

532 

533color_property = single_string_property( 

534 "COLOR", 

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

536 

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

538 

539 Property Parameters: 

540 IANA and non-standard property parameters can 

541 be specified on this property. 

542 

543 Conformance: 

544 This property can be specified once in an iCalendar 

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

546 

547 Description: 

548 This property specifies a color that clients MAY use 

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

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

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

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

553 

554 Example: 

555 ``"turquoise"``, ``"#ffffff"`` 

556 

557 .. code-block:: pycon 

558 

559 >>> from icalendar import Todo 

560 >>> todo = Todo() 

561 >>> todo.color = "green" 

562 >>> print(todo.to_ical()) 

563 BEGIN:VTODO 

564 COLOR:green 

565 END:VTODO 

566 """, 

567) 

568 

569sequence_property = single_int_property( 

570 "SEQUENCE", 

571 0, 

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

573 

574Value Type: 

575 INTEGER 

576 

577Property Parameters: 

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

579 

580Conformance: 

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

582 "VJOURNAL" calendar component. 

583 

584Description: 

585 When a calendar component is created, its sequence 

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

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

588 calendar component. 

589 

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

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

592 calendar component. 

593 

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

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

596 component to which the "Attendee" is referring. 

597 

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

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

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

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

602 requested. 

603 

604 Recurrence instances of a recurring component MAY have different 

605 sequence numbers. 

606 

607Examples: 

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

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

610 

611 .. code-block:: pycon 

612 

613 >>> from icalendar import Event 

614 >>> event = Event() 

615 >>> event.sequence 

616 0 

617 

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

619 component that has been revised 10 different times by the 

620 "Organizer": 

621 

622 .. code-block:: pycon 

623 

624 >>> from icalendar import Calendar 

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

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

627 >>> event.sequence 

628 10 

629 

630 Raises: 

631 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

632 even though ``bool`` subclasses ``int``. 

633 

634 ~icalendar.error.InvalidCalendar: If the value is negative. 

635 

636 .. versionchanged:: 7.3.0 

637 Negative values are no longer accepted. 

638 """, # noqa: E501 

639 min_value=0, 

640) 

641 

642 

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

644 """Get all the categories.""" 

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

646 if isinstance(categories, list): 

647 _set_categories( 

648 component, 

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

650 ) 

651 return _get_categories(component) 

652 if categories is None: 

653 categories = vCategory([]) 

654 component.add("CATEGORIES", categories) 

655 return categories.cats 

656 

657 

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

659 """Set the categories.""" 

660 if not cats and cats != []: 

661 _del_categories(component) 

662 return 

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

664 if isinstance(cats, list): 

665 cats.clear() 

666 cats.extend(categories.cats) 

667 categories.cats = cats 

668 

669 

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

671 """Delete the categories.""" 

672 component.pop("CATEGORIES", None) 

673 

674 

675categories_property = property( 

676 _get_categories, 

677 _set_categories, 

678 _del_categories, 

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

680 

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

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

683component of a particular type and category. 

684 

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

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

687 

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

689 

690- :attr:`Available.categories <icalendar.cal.available.Available.categories>` 

691- :attr:`Availability.categories <icalendar.cal.availability.Availability.categories>` 

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

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

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

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

696 

697The categories property for ``Available`` and ``Availability`` complies with 

698:rfc:`7953#section-3.1`, for ``Event``, ``Journal``, and ``Todo`` with 

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

700 

701Note: 

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

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

704 

705Parameters: 

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

707 

708Example: 

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

710 append another category, and finally compare the result 

711 against its expected value. 

712 

713 .. code-block:: pycon 

714 

715 >>> from icalendar import Event 

716 >>> event = Event() 

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

718 >>> print(event.to_ical()) 

719 BEGIN:VEVENT 

720 CATEGORIES:Work,Meeting 

721 END:VEVENT 

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

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

724 True 

725 

726See also: 

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

728""", 

729) 

730 

731 

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

733 """Get attendees.""" 

734 value = self.get("ATTENDEE") 

735 if value is None: 

736 value = [] 

737 self["ATTENDEE"] = value 

738 return value 

739 if isinstance(value, vCalAddress): 

740 return [value] 

741 return value 

742 

743 

744ATTENDEE_TYPE_SETTER: TypeAlias = Sequence[vCalAddress | str] | vCalAddress | str | None 

745 

746 

747def _set_attendees(self: Component, value: ATTENDEE_TYPE_SETTER): 

748 """Set attendees.""" 

749 _del_attendees(self) 

750 if value is None: 

751 return 

752 if isinstance(value, (vCalAddress, str)): 

753 value = [value] 

754 elif not isinstance(value, list): 

755 value = list(value) if isinstance(value, Sequence) else [value] 

756 for index, attendee in enumerate(value): 

757 # vCalAddress subclasses str, so exclude it before normalizing strings 

758 if not isinstance(attendee, vCalAddress) and isinstance(attendee, str): 

759 value[index] = vCalAddress.new(attendee) 

760 self["ATTENDEE"] = value 

761 

762 

763def _del_attendees(self: Component): 

764 """Delete all attendees.""" 

765 self.pop("ATTENDEE", None) 

766 

767 

768attendees_property = property( 

769 _get_attendees, 

770 _set_attendees, 

771 _del_attendees, 

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

773 

774Conformance: 

775 This property MUST be specified in an iCalendar object 

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

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

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

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

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

781 iCalendar object that specifies only a time zone definition or 

782 that defines calendar components that are not group-scheduled 

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

784 

785Description: 

786 This property MUST only be specified within calendar 

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

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

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

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

791 type of iCalendar alarm. 

792 

793Examples: 

794 Assign one or more attendee email addresses directly. Strings are 

795 converted to :class:`~icalendar.prop.cal_address.vCalAddress` objects 

796 and receive a ``mailto:`` prefix when needed. 

797 

798 .. code-block:: pycon 

799 

800 >>> from icalendar import Event 

801 >>> event = Event() 

802 >>> event.attendees = [ 

803 ... "me@my-domain.com", 

804 ... "mailto:you@my-domain.com", 

805 ... ] 

806 >>> event.attendees[0] 

807 vCalAddress('mailto:me@my-domain.com') 

808 >>> event.attendees[1] 

809 vCalAddress('mailto:you@my-domain.com') 

810 >>> print(event.to_ical()) 

811 BEGIN:VEVENT 

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

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

814 END:VEVENT 

815 

816 Use :meth:`vCalAddress.new 

817 <icalendar.prop.cal_address.vCalAddress.new>` when an attendee needs 

818 parameters such as ``CN``, ``ROLE``, or ``RSVP``. 

819 

820 .. code-block:: pycon 

821 

822 >>> from icalendar import vCalAddress 

823 >>> event.attendees = [ 

824 ... vCalAddress.new( 

825 ... "chair@example.com", 

826 ... cn="Meeting Chair", 

827 ... role="CHAIR", 

828 ... rsvp=True, 

829 ... ) 

830 ... ] 

831""", 

832) 

833 

834uid_property = single_string_property( 

835 "UID", 

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

837 

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

839 

840Returns: 

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

842 

843Description: 

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

845 The generator of the identifier MUST guarantee that the identifier 

846 is unique. 

847 

848 This is the method for correlating scheduling messages with the 

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

850 The full range of calendar components specified by a recurrence 

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

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

853 property allows the reference to an individual instance within the 

854 recurrence set. 

855 

856 This property is an important method for group-scheduling 

857 applications to match requests with later replies, modifications, 

858 or deletion requests. Calendaring and scheduling applications 

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

860 calendar components to assure interoperability with other group- 

861 scheduling applications. This identifier is created by the 

862 calendar system that generates an iCalendar object. 

863 

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

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

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

867 

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

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

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

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

872 to identify the most recent version of a calendar. 

873 

874Conformance: 

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

876 and "VJOURNAL" calendar components. 

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

878 allow it to be defined in an iCalendar object. 

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

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

881 to refer uniquely to the "VALARM" component. 

882 

883 This property can be specified once only. 

884 

885Security: 

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

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

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

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

890 Universally Unique Identifier (UUID) values as defined in 

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

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

893 

894Compatibility: 

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

896 

897Examples: 

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

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

900 

901 Set the UID of a calendar: 

902 

903 .. code-block:: pycon 

904 

905 >>> from icalendar import Calendar 

906 >>> from uuid import uuid4 

907 >>> calendar = Calendar() 

908 >>> calendar.uid = uuid4() 

909 >>> print(calendar.to_ical()) 

910 BEGIN:VCALENDAR 

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

912 END:VCALENDAR 

913 

914""", 

915) 

916 

917summary_property = multi_language_text_property( 

918 "SUMMARY", 

919 None, 

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

921 

922Property Parameters: 

923 IANA, non-standard, alternate text 

924 representation, and language property parameters can be specified 

925 on this property. 

926 

927Conformance: 

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

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

930 

931Description: 

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

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

934 summary about the activity or journal entry. 

935 

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

937 capture the subject of an EMAIL category of alarm. 

938 

939Examples: 

940 The following is an example of this property: 

941 

942 .. code-block:: pycon 

943 

944 SUMMARY:Department Party 

945""", 

946) 

947 

948description_property = multi_language_text_property( 

949 "DESCRIPTION", 

950 None, 

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

952 

953Property Parameters: 

954 IANA, non-standard, alternate text 

955 representation, and language property parameters can be specified 

956 on this property. 

957 

958Conformance: 

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

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

961 specified multiple times only within a "VJOURNAL" calendar 

962 component. 

963 

964Description: 

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

966 capture lengthy textual descriptions associated with the activity. 

967 

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

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

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

971 

972Examples: 

973 The following is an example of this property with formatted 

974 line breaks in the property value: 

975 

976 .. code-block:: pycon 

977 

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

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

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

981 

982 """, # noqa: E501 

983) 

984 

985 

986def create_single_property( 

987 prop: str, 

988 value_attr: str | None, 

989 value_type: tuple[type], 

990 type_def: type, 

991 doc: str, 

992 vProp: type = vDDDTypes, # noqa: N803 

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

994): 

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

996 

997 Parameters: 

998 prop: The name of the property. 

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

1000 value_type: The type of the value. 

1001 type_def: The type of the property. 

1002 doc: The docstring of the property. 

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

1004 """ 

1005 

1006 def p_get(self: Component): 

1007 default = object() 

1008 result = self.get(prop, default) 

1009 if result is default: 

1010 return None 

1011 if isinstance(result, list): 

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

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

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

1015 if not isinstance(value, value_type): 

1016 raise InvalidCalendar( 

1017 f"{prop} must be either a " 

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

1019 f" not {value}." 

1020 ) 

1021 return value 

1022 

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

1024 if value is None: 

1025 p_del(self) 

1026 return 

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

1028 if not isinstance(value, value_type): 

1029 raise TypeError( 

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

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

1032 ) 

1033 self[prop] = vProp(value) 

1034 if prop in self.exclusive: 

1035 for other_prop in self.exclusive: 

1036 if other_prop != prop: 

1037 self.pop(other_prop, None) 

1038 

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

1040 

1041 def p_del(self: Component): 

1042 self.pop(prop) 

1043 

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

1045 

1046 {doc} 

1047 

1048 To delete the value, either use ``del`` or set it to ``None``. 

1049 

1050 Raises: 

1051 InvalidCalendar: if the attribute has invalid values. 

1052 """ 

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

1054 

1055 

1056X_MOZ_SNOOZE_TIME_property = single_utc_property( 

1057 "X-MOZ-SNOOZE-TIME", 

1058 """The ``X-MOZ-SNOOZE-TIME`` property as a :class:`~datetime.datetime` in UTC. 

1059 

1060 Thunderbird: Alarms before this time are snoozed. 

1061""", 

1062) 

1063X_MOZ_LASTACK_property = single_utc_property( 

1064 "X-MOZ-LASTACK", 

1065 """The ``X-MOZ-LASTACK`` property as a :class:`~datetime.datetime` in UTC. 

1066 

1067 Thunderbird: Alarms before this time are acknowledged. 

1068""", 

1069) 

1070 

1071 

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

1073 """Getter for property DURATION.""" 

1074 default = object() 

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

1076 if duration is default: 

1077 return None 

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

1079 if result is None: 

1080 raise InvalidCalendar( 

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

1082 ) 

1083 return result 

1084 

1085 

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

1087 """Setter for property DURATION.""" 

1088 if value is None: 

1089 self.pop("duration", None) 

1090 return 

1091 if not isinstance(value, timedelta): 

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

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

1094 self.pop("DTEND") 

1095 self.pop("DUE") 

1096 

1097 

1098def property_del_duration(self: Component): 

1099 """Delete property DURATION.""" 

1100 self.pop("DURATION") 

1101 

1102 

1103property_doc_duration_template = """The DURATION property. 

1104 

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

1106start of the {component}. 

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

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

1109of the event. 

1110 

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

1112Instead use the duration property (lower case). 

1113""" 

1114 

1115 

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

1117 """Return the duration property.""" 

1118 return property( 

1119 property_get_duration, 

1120 property_set_duration, 

1121 property_del_duration, 

1122 property_doc_duration_template.format(component=component), 

1123 ) 

1124 

1125 

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

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

1128 

1129 Examples: Journal.descriptions, Event.comments 

1130 """ 

1131 

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

1133 """Get the values.""" 

1134 descriptions = self.get(name) 

1135 if descriptions is None: 

1136 return [] 

1137 if not isinstance(descriptions, SEQUENCE_TYPES): 

1138 return [descriptions] 

1139 return descriptions 

1140 

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

1142 """Set the values.""" 

1143 fdel(self) 

1144 if values is None: 

1145 return 

1146 if isinstance(values, str): 

1147 self.add(name, values) 

1148 else: 

1149 for description in values: 

1150 self.add(name, description) 

1151 

1152 def fdel(self: Component): 

1153 """Delete the values.""" 

1154 self.pop(name) 

1155 

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

1157 

1158 

1159descriptions_property = multi_text_property( 

1160 "DESCRIPTION", 

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

1162 

1163Property Parameters: 

1164 IANA, non-standard, alternate text 

1165 representation, and language property parameters can be specified 

1166 on this property. 

1167 

1168Conformance: 

1169 The property can be 

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

1171 

1172Description: 

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

1174 capture one or more textual journal entries. 

1175 

1176Examples: 

1177 The following is an example of this property with formatted 

1178 line breaks in the property value: 

1179 

1180 .. code-block:: pycon 

1181 

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

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

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

1185 

1186""", # noqa: E501 

1187) 

1188 

1189comments_property = multi_text_property( 

1190 "COMMENT", 

1191 """Specifies a comment to the calendar user. 

1192 

1193This property holds free-text notes attached to a component; calendar clients 

1194display it but never act on it. It is defined in :rfc:`5545#section-3.8.1.4` 

1195and may appear multiple times on ``VEVENT``, ``VTODO``, ``VJOURNAL``, and 

1196``VFREEBUSY`` components as well as on their ``STANDARD`` and ``DAYLIGHT`` 

1197sub-components. For availability components, it is defined in :rfc:`7953`, and 

1198may appear multiple times on ``VAVAILABILITY`` and ``VAVAILABLE``. 

1199 

1200You can get, set, append, and delete comments on a component. The value is a 

1201list of strings; each string is one comment. 

1202 

1203Example: 

1204 Add two comments to an event and then read them back. 

1205 

1206 .. code-block:: pycon 

1207 

1208 >>> from icalendar import Event 

1209 >>> event = Event() 

1210 >>> event.add("COMMENT", "Moved from the planning board.") 

1211 >>> event.add("COMMENT", "Confirmed by phone.") 

1212 >>> [str(c) for c in event.comments] 

1213 ['Moved from the planning board.', 'Confirmed by phone.'] 

1214 >>> str(event.comments[0]) 

1215 'Moved from the planning board.' 

1216""", 

1217) 

1218 

1219RECURRENCE_ID = create_single_property( 

1220 "RECURRENCE-ID", 

1221 "dt", 

1222 (date, datetime), 

1223 date | datetime, 

1224 """ 

1225Identify a specific occurrence of a recurring calendar object. 

1226 

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

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

1229date or datetime of that instance, not the rescheduled time. 

1230 

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

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

1233all-day items instead. 

1234 

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

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

1237""", 

1238 vDDDTypes, 

1239) 

1240 

1241 

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

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

1244 

1245 Property Parameters: 

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

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

1248 specified on this property. 

1249 

1250 Conformance: 

1251 This property MUST be specified in an iCalendar object 

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

1253 MUST be specified in an iCalendar object that specifies the 

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

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

1256 zone definition or that defines calendar components that are not 

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

1258 user's calendar. 

1259 

1260 Description: 

1261 This property is specified within the "VEVENT", 

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

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

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

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

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

1267 the calendar that the published busy time came from. 

1268 

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

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

1271 specifying a pointer to the directory information associated with 

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

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

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

1275 "LANGUAGE" property parameter is specified, the identified 

1276 language applies to the "CN" parameter value. 

1277 """ 

1278 return self.get("ORGANIZER") 

1279 

1280 

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

1282 """Set the value.""" 

1283 _del_organizer(self) 

1284 if value is not None: 

1285 self.add("ORGANIZER", value) 

1286 

1287 

1288def _del_organizer(self: Component): 

1289 """Delete the value.""" 

1290 self.pop("ORGANIZER") 

1291 

1292 

1293organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1294 

1295 

1296def single_string_enum_property( 

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

1298) -> property: 

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

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

1301 

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

1303 """Get the value.""" 

1304 value = prop.fget(self) 

1305 if value == default: 

1306 return default 

1307 return enum(str(value)) 

1308 

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

1310 """Set the value.""" 

1311 if value == "": 

1312 value = None 

1313 prop.fset(self, value) 

1314 

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

1316 

1317 

1318busy_type_property = single_string_enum_property( 

1319 "BUSYTYPE", 

1320 BUSYTYPE, 

1321 BUSYTYPE.BUSY_UNAVAILABLE, 

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

1323 

1324Returns: 

1325 :class:`icalendar.enums.BUSYTYPE` 

1326 

1327Description: 

1328 This property is used to specify the default busy time 

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

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

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

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

1333 UNAVAILABLE". 

1334""", 

1335) 

1336 

1337 

1338def _make_repeat_property() -> property: 

1339 from icalendar.config import _clamp_repeat 

1340 

1341 _base = single_int_property( 

1342 "REPEAT", 

1343 0, 

1344 """The number of additional times the alarm is triggered after the 

1345initial trigger. 

1346 

1347Defaults to ``0``, meaning the alarm fires once. Must be paired with 

1348:attr:`~icalendar.cal.alarm.Alarm.DURATION`. Conforms with :rfc:`5545#section-3.8.6.2`. 

1349The value is capped at :data:`icalendar.config.MAX_ALARM_REPEAT` on read. 

1350 

1351Raises: 

1352 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

1353 even though ``bool`` subclasses ``int``. 

1354 

1355 ~icalendar.error.InvalidCalendar: If the value is negative. 

1356 

1357.. versionchanged:: 7.3.0 

1358 Negative values are no longer accepted. 

1359""", 

1360 min_value=0, 

1361 ) 

1362 

1363 def fget(self): 

1364 return _clamp_repeat(_base.fget(self)) 

1365 

1366 return property(fget, _base.fset, _base.fdel, _base.__doc__) 

1367 

1368 

1369repeat_property = _make_repeat_property() 

1370 

1371priority_property = single_int_property( 

1372 "PRIORITY", 

1373 0, 

1374 """ 

1375 

1376Conformance: 

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

1378 according to :rfc:`5545`. 

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

1380 

1381Description: 

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

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

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

1385 priority. Subsequent numbers specify a decreasing ordinal 

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

1387 

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

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

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

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

1392 is "LOW" priority. 

1393 

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

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

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

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

1398 9 specifies "C3". 

1399 

1400 Other integer values are reserved for future use. 

1401 

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

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

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

1405 

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

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

1408 multiple action items for a given time period. 

1409 

1410 Raises: 

1411 TypeError: If the value is not an ``int``. Booleans are rejected, too, 

1412 even though ``bool`` subclasses ``int``. 

1413 

1414 ~icalendar.error.InvalidCalendar: If the value is negative. 

1415 

1416 .. versionchanged:: 7.3.0 

1417 Negative values are no longer accepted. 

1418""", 

1419 min_value=0, 

1420) 

1421 

1422class_property = single_string_enum_property( 

1423 "CLASS", 

1424 CLASS, 

1425 CLASS.PUBLIC, 

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

1427 

1428Returns: 

1429 :class:`icalendar.enums.CLASS` 

1430 

1431Description: 

1432 An access classification is only one component of the 

1433 general security system within a calendar application. It 

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

1435 calendar owner intends for information within an individual 

1436 calendar entry. The access classification of an individual 

1437 iCalendar component is useful when measured along with the other 

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

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

1440 Hence, the semantics of the individual access classifications 

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

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

1443 memo, these access classifications cannot serve as an enforcement 

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

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

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

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

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

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

1450""", 

1451) 

1452 

1453transparency_property = single_string_enum_property( 

1454 "TRANSP", 

1455 TRANSP, 

1456 TRANSP.OPAQUE, 

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

1458 

1459Returns: 

1460 :class:`icalendar.enums.TRANSP` 

1461 

1462Description: 

1463 Time Transparency is the characteristic of an event 

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

1465 Events that consume actual time for the individual or resource 

1466 associated with the calendar SHOULD be recorded as OPAQUE, 

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

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

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

1470 busy time searches. 

1471""", 

1472) 

1473status_property = single_string_enum_property( 

1474 "STATUS", 

1475 STATUS, 

1476 "", 

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

1478 

1479Returns: 

1480 :class:`icalendar.enums.STATUS` 

1481 

1482The default value is ``""``. 

1483 

1484Description: 

1485 In a group-scheduled calendar component, the property 

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

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

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

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

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

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

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

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

1494 cancelled or removed. 

1495""", 

1496) 

1497 

1498url_property = single_string_property( 

1499 "URL", 

1500 """A Uniform Resource Locator (URL) associated with a calendar component. 

1501 

1502This property specifies a URI where a more dynamic rendition of the calendar 

1503information can be found. It is commonly used to reference related resources 

1504or provide additional information about the component. 

1505 

1506According to :rfc:`5545#section-3.8.4.6`, this property can be specified 

1507once in "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components. 

1508Since :rfc:`7986#section-5.5`, this property can also be defined on a 

1509"VCALENDAR". :rfc:`7953#section-3.1` allows this property in "VAVAILABILITY" components. 

1510 

1511This property may be used in a calendar component to convey a location 

1512where a more dynamic rendition of the calendar information can be found. 

1513If both the URL property and Content-Location MIME header are specified, 

1514they MUST point to the same resource. 

1515 

1516This differs from the SOURCE property, which identifies where calendar 

1517data can be refreshed from, whereas URL provides an alternative 

1518representation of the current calendar data. 

1519 

1520Examples: 

1521 

1522 Set a URL for an event that references additional information: 

1523 

1524 .. code-block:: pycon 

1525 

1526 >>> from icalendar import Event 

1527 >>> event = Event() 

1528 >>> event.add('url', 'http://example.com/events/meeting-2025') 

1529 >>> print(event.to_ical().decode('utf-8')) 

1530 BEGIN:VEVENT 

1531 URL:http://example.com/events/meeting-2025 

1532 END:VEVENT 

1533 

1534 Set a URL for a calendar: 

1535 

1536 .. code-block:: pycon 

1537 

1538 >>> from icalendar import Calendar 

1539 >>> calendar = Calendar() 

1540 >>> calendar.add('url', 'http://example.com/pub/calendars/jsmith/mytime.ics') 

1541 >>> print(calendar.to_ical().decode('utf-8')) 

1542 BEGIN:VCALENDAR 

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

1544 END:VCALENDAR 

1545 

1546See also: 

1547 :attr:`~icalendar.cal.calendar.Calendar.source` for specifying from where 

1548 calendar data can be refreshed. 

1549 

1550 icalendar implementations: 

1551 

1552 - :attr:`Availability.url <icalendar.cal.availability.Availability.url>` 

1553 - :attr:`Calendar.url <icalendar.cal.calendar.Calendar.url>` 

1554 - :attr:`Event.url <icalendar.cal.event.Event.url>` 

1555 - :attr:`FreeBusy.url <icalendar.cal.free_busy.FreeBusy.url>` 

1556 - :attr:`Journal.url <icalendar.cal.journal.Journal.url>` 

1557 - :attr:`Todo.url <icalendar.cal.todo.Todo.url>` 

1558 

1559 

1560""", 

1561) 

1562 

1563source_property = single_string_property( 

1564 "SOURCE", 

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

1566 

1567Description: 

1568 This property identifies a location where a client can 

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

1570 specified "REFRESH-INTERVAL" value when periodically retrieving 

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

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

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

1574 

1575Conformance: 

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

1577 

1578Example: 

1579 The following is an example of this property: 

1580 

1581 .. code-block:: ics 

1582 

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

1584 

1585""", 

1586) 

1587 

1588location_property = multi_language_text_property( 

1589 "LOCATION", 

1590 None, 

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

1592 

1593Property Parameters: 

1594 IANA, non-standard, alternate text 

1595 representation, and language property parameters can be specified 

1596 on this property. 

1597 

1598Conformance: 

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

1600 calendar component. 

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

1602 

1603Description: 

1604 Specific venues such as conference or meeting rooms may 

1605 be explicitly specified using this property. An alternate 

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

1607 directory information with more structured specification of the 

1608 location. For example, the alternate representation may specify 

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

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

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

1612 

1613""", 

1614) 

1615 

1616contacts_property = multi_text_property( 

1617 "CONTACT", 

1618 """Contact information associated with a calendar component. 

1619 

1620The contact property holds free-text information for reaching the person or 

1621organization responsible in a component, such as a name, phone number, or a 

1622reference to more detailed contact data. Calendar clients surface it so 

1623attendees know who to contact about the event or task. 

1624 

1625This property is defined in :rfc:`5545#section-3.8.4.2` and may appear on a 

1626``VEVENT``, ``VTODO``, ``VJOURNAL``, or ``VFREEBUSY`` component. For 

1627availability components it is defined in :rfc:`7953` and may appear on a 

1628``VAVAILABILITY`` or ``VAVAILABLE`` component. An alternate representation may 

1629point to a URI, for example, a vCard per :rfc:`2426`, via the ``ALTREP`` parameter. 

1630 

1631You can get, set, append, and delete contacts on a component. The value is a 

1632list of strings; each string is one contact entry. 

1633 

1634Example: 

1635 Add a contact to an event and then read it back. 

1636 

1637 .. code-block:: pycon 

1638 

1639 >>> from icalendar import Event 

1640 >>> event = Event() 

1641 >>> event.add("CONTACT", "Jim Dolittle, ABC Industries, +1-919-555-1234") 

1642 >>> [str(c) for c in event.contacts] 

1643 ['Jim Dolittle, ABC Industries, +1-919-555-1234'] 

1644 >>> str(event.contacts[0]) 

1645 'Jim Dolittle, ABC Industries, +1-919-555-1234' 

1646""", 

1647) 

1648 

1649 

1650def _timezone_datetime_property(name: str, docs: str): 

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

1652 

1653 return single_utc_property(name, docs) 

1654 

1655 

1656rfc_7953_dtstart_property = _timezone_datetime_property( 

1657 "DTSTART", 

1658 """Start of the component as a :class:`~datetime.datetime` in UTC. 

1659 

1660 This is almost the same as 

1661 :attr:`Event.DTSTART <icalendar.cal.event.Event.DTSTART>` with one exception: 

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

1663 

1664 Description: 

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

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

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

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

1669 

1670 """, 

1671) 

1672 

1673rfc_7953_dtend_property = _timezone_datetime_property( 

1674 "DTEND", 

1675 """End of the component as a :class:`~datetime.datetime` in UTC. 

1676 

1677 This is almost the same as 

1678 :attr:`Event.DTEND <icalendar.cal.event.Event.DTEND>` with one exception: 

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

1680 

1681 Description: 

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

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

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

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

1686 """, 

1687) 

1688 

1689 

1690@property 

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

1692 """Compute the duration of this component. 

1693 

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

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

1696 :attr:`DTEND`/:attr:`DURATION`. 

1697 

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

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

1700 """ 

1701 duration = self.DURATION 

1702 if duration: 

1703 return duration 

1704 end = self.DTEND 

1705 if end is None: 

1706 return None 

1707 start = self.DTSTART 

1708 if start is None: 

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

1710 return end - start 

1711 

1712 

1713@property 

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

1715 """Compute the duration of this component. 

1716 

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

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

1719 :attr:`DTEND`/:attr:`DURATION`. 

1720 

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

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

1723 """ 

1724 duration = self.DURATION 

1725 if duration: 

1726 start = self.DTSTART 

1727 if start is None: 

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

1729 return start + duration 

1730 end = self.DTEND 

1731 if end is None: 

1732 return None 

1733 return end 

1734 

1735 

1736@rfc_7953_end_property.setter 

1737def rfc_7953_end_property(self, value: datetime): 

1738 self.DTEND = value 

1739 

1740 

1741@rfc_7953_end_property.deleter 

1742def rfc_7953_end_property(self): 

1743 del self.DTEND 

1744 

1745 

1746def get_start_end_duration_with_validation( 

1747 component: Event | Todo, 

1748 start_property: Literal["DTSTART"], 

1749 end_property: Literal["DTEND", "DUE"], 

1750 component_name: Literal["VEVENT", "VTODO"], 

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

1752 """ 

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

1754 

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

1756 for ``Event`` and ``Todo`` components. 

1757 

1758 Parameters: 

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

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

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

1762 ``DUE`` for ``Todo``. 

1763 component_name: The component name for error messages, 

1764 either ``VEVENT`` or ``VTODO``. 

1765 

1766 Returns: 

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

1768 

1769 Raises: 

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

1771 

1772 """ 

1773 start = getattr(component, start_property, None) 

1774 end = getattr(component, end_property, None) 

1775 duration = component.DURATION 

1776 

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

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

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

1780 msg = ( 

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

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

1783 ) 

1784 raise InvalidCalendar(msg) 

1785 

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

1787 if ( 

1788 start is not None 

1789 and is_date(start) 

1790 and duration is not None 

1791 and duration.seconds != 0 

1792 ): 

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

1794 raise InvalidCalendar(msg) 

1795 

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

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

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

1799 msg = ( 

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

1801 ) 

1802 raise InvalidCalendar(msg) 

1803 

1804 return start, end, duration 

1805 

1806 

1807def get_start_property(component: Event | Todo) -> date | datetime: 

1808 """ 

1809 Get the start property with validation. 

1810 

1811 Parameters: 

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

1813 

1814 Returns: 

1815 The ``DTSTART`` value. 

1816 

1817 Raises: 

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

1819 

1820 """ 

1821 # Trigger validation by calling _get_start_end_duration 

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

1823 if start is None: 

1824 msg = "No DTSTART given." 

1825 raise IncompleteComponent(msg) 

1826 return start 

1827 

1828 

1829def get_end_property( 

1830 component: Event | Todo, end_property: Literal["DTEND", "DUE"] 

1831) -> date | datetime: 

1832 """ 

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

1834 

1835 Parameters: 

1836 component: The component to get end from 

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

1838 ``DUE`` for ``Todo``. 

1839 

1840 Returns: 

1841 The computed end value. 

1842 

1843 Raises: 

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

1845 to compute the end property. 

1846 

1847 """ 

1848 # Trigger validation by calling _get_start_end_duration 

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

1850 

1851 if end is None and duration is None: 

1852 if start is None: 

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

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

1855 raise IncompleteComponent(msg) 

1856 

1857 # Default behavior differs for Event vs Todo: 

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

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

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

1861 return start + timedelta(days=1) 

1862 return start 

1863 

1864 if duration is not None: 

1865 if start is not None: 

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

1867 return start 

1868 return start + duration 

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

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

1871 raise IncompleteComponent(msg) 

1872 

1873 return end 

1874 

1875 

1876def get_duration_property(component: Event | Todo) -> timedelta: 

1877 """ 

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

1879 

1880 Parameters: 

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

1882 

1883 Returns: 

1884 The duration as a timedelta. 

1885 

1886 """ 

1887 # First check if DURATION property is explicitly set 

1888 if "DURATION" in component: 

1889 return component["DURATION"].dt 

1890 

1891 # Fall back to calculated duration from start and end 

1892 return component.end - component.start 

1893 

1894 

1895def set_duration_with_locking( 

1896 component: Event | Todo, 

1897 duration: timedelta | None, 

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

1899 end_property: Literal["DTEND", "DUE"], 

1900) -> None: 

1901 """ 

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

1903 

1904 Parameters: 

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

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

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

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

1909 ``DUE`` for ``Todo``. 

1910 

1911 """ 

1912 # Convert to DURATION property if duration is None 

1913 if duration is None: 

1914 if "DURATION" in component: 

1915 return # Already has DURATION property 

1916 current_duration = component.duration 

1917 component.DURATION = current_duration 

1918 return 

1919 

1920 if not isinstance(duration, timedelta): 

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

1922 raise TypeError(msg) 

1923 

1924 # Validate date/duration compatibility 

1925 start = component.DTSTART 

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

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

1928 raise InvalidCalendar(msg) 

1929 

1930 if locked == "start": 

1931 # Keep start locked, adjust end 

1932 if start is None: 

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

1934 raise IncompleteComponent(msg) 

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

1936 component.DURATION = duration 

1937 elif locked == "end": 

1938 # Keep end locked, adjust start 

1939 current_end = component.end 

1940 component.DTSTART = current_end - duration 

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

1942 component.DURATION = duration 

1943 else: 

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

1945 raise ValueError(msg) 

1946 

1947 

1948def set_start_with_locking( 

1949 component: Event | Todo, 

1950 start: date | datetime, 

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

1952 end_property: Literal["DTEND", "DUE"], 

1953) -> None: 

1954 """ 

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

1956 

1957 Parameters: 

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

1959 start: The start time to set. 

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

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

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

1963 ``DUE`` for ``Todo``. 

1964 

1965 """ 

1966 if locked is None: 

1967 # Auto-detect based on existing properties 

1968 if "DURATION" in component: 

1969 locked = "duration" 

1970 elif end_property in component: 

1971 locked = "end" 

1972 else: 

1973 # Default to duration if no existing properties 

1974 locked = "duration" 

1975 

1976 if locked == "duration": 

1977 # Keep duration locked, adjust end 

1978 current_duration = ( 

1979 component.duration 

1980 if "DURATION" in component or end_property in component 

1981 else None 

1982 ) 

1983 component.DTSTART = start 

1984 if current_duration is not None: 

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

1986 component.DURATION = current_duration 

1987 elif locked == "end": 

1988 # Keep end locked, adjust duration 

1989 current_end = component.end 

1990 component.DTSTART = start 

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

1992 setattr(component, end_property, current_end) 

1993 else: 

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

1995 raise ValueError(msg) 

1996 

1997 

1998def set_end_with_locking( 

1999 component: Event | Todo, 

2000 end: date | datetime, 

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

2002 end_property: Literal["DTEND", "DUE"], 

2003) -> None: 

2004 """ 

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

2006 

2007 Parameters: 

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

2009 end: The end time to set. 

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

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

2012 for ``Todo``. 

2013 

2014 """ 

2015 if locked == "start": 

2016 # Keep start locked, adjust duration 

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

2018 setattr(component, end_property, end) 

2019 elif locked == "duration": 

2020 # Keep duration locked, adjust start 

2021 current_duration = component.duration 

2022 component.DTSTART = end - current_duration 

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

2024 component.DURATION = current_duration 

2025 else: 

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

2027 raise ValueError(msg) 

2028 

2029 

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

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

2032 

2033 Description: 

2034 This property specifies an image for an iCalendar 

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

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

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

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

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

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

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

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

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

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

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

2046 on the image in the calendar user agent. 

2047 

2048 Conformance: 

2049 This property can be specified multiple times in an 

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

2051 components. 

2052 

2053 .. note:: 

2054 

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

2056 please open an issue or a pull request. 

2057 """ 

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

2059 if not isinstance(images, SEQUENCE_TYPES): 

2060 images = [images] 

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

2062 

2063 

2064images_property = property(_get_images) 

2065 

2066 

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

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

2069 

2070 Purpose: 

2071 This property specifies information for accessing a conferencing system. 

2072 

2073 Conformance: 

2074 This property can be specified multiple times in a 

2075 "VEVENT" or "VTODO" calendar component. 

2076 

2077 Description: 

2078 This property specifies information for accessing a 

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

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

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

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

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

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

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

2086 containing a text description can be used. 

2087 

2088 A conference system can be a bidirectional communication channel 

2089 or a uni-directional "broadcast feed". 

2090 

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

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

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

2094 multiple properties. 

2095 

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

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

2098 codes for the moderator and attendee of a teleconference system 

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

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

2101 which. 

2102 

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

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

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

2106 

2107 Example: 

2108 The following are examples of this property: 

2109 

2110 .. code-block:: ics 

2111 

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

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

2114 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

2116 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

2118 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

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

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

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

2122 

2123 Get all conferences: 

2124 

2125 .. code-block:: pycon 

2126 

2127 >>> from icalendar import Event 

2128 >>> event = Event() 

2129 >>> event.conferences 

2130 [] 

2131 

2132 Set a conference: 

2133 

2134 .. code-block:: pycon 

2135 

2136 >>> from icalendar import Event, Conference 

2137 >>> event = Event() 

2138 >>> event.conferences = [ 

2139 ... Conference( 

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

2141 ... feature="PHONE,MODERATOR", 

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

2143 ... language="EN", 

2144 ... ) 

2145 ... ] 

2146 >>> print(event.to_ical()) 

2147 BEGIN:VEVENT 

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

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

2150 END:VEVENT 

2151 

2152 """ 

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

2154 if not isinstance(conferences, SEQUENCE_TYPES): 

2155 conferences = [conferences] 

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

2157 

2158 

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

2160 """Set the conferences.""" 

2161 _del_conferences(self) 

2162 for conference in conferences or []: 

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

2164 

2165 

2166def _del_conferences(self: Component): 

2167 """Delete all conferences.""" 

2168 self.pop("CONFERENCE") 

2169 

2170 

2171conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

2172 

2173 

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

2175 """LINK properties as a list. 

2176 

2177 Purpose: 

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

2179 

2180 Property Parameters: 

2181 The VALUE parameter is required. 

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

2183 can also be specified on this property. 

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

2185 

2186 Conformance: 

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

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

2189 The LINKREL parameter is required. 

2190 

2191 Description: 

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

2193 additional information related to the component. 

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

2195 

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

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

2198 the link context is the containing calendar entity, 

2199 and the link relation type and any target attributes 

2200 are carried in iCalendar property parameters. 

2201 

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

2203 

2204 LABEL 

2205 This parameter maps to the "title" 

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

2207 LABEL is used to label the destination 

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

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

2210 (if present). 

2211 LANGUAGE 

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

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

2214 LINKREL 

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

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

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

2218 FMTTYPE 

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

2220 :rfc:`8288`. 

2221 

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

2223 

2224 Examples: 

2225 The following is an example of this property, 

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

2227 

2228 .. code-block:: ics 

2229 

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

2231 https://example.com/events 

2232 

2233 The following is an example of this property, 

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

2235 The link relation is a vendor-defined value. 

2236 

2237 .. code-block:: ics 

2238 

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

2240 VALUE=URI: 

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

2242 

2243 The following is an example of this property, 

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

2245 The link relation is a vendor-defined value. 

2246 

2247 .. code-block:: ics 

2248 

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

2250 VALUE=XML-REFERENCE: 

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

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

2253 following::CostStrucEND[1])) 

2254 

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

2256 

2257 .. code-block:: pycon 

2258 

2259 >>> from icalendar import Event, vUri 

2260 >>> from datetime import datetime 

2261 >>> link = vUri( 

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

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

2264 ... ) 

2265 >>> event = Event.new( 

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

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

2268 ... ) 

2269 >>> event.links = [link] 

2270 >>> print(event.to_ical()) 

2271 BEGIN:VEVENT 

2272 SUMMARY:An Example Event with a page 

2273 DTSTART:20250917T120000 

2274 DTSTAMP:20250517T080612Z 

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

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

2277 END:VEVENT 

2278 

2279 """ 

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

2281 if not isinstance(links, list): 

2282 links = [links] 

2283 return links 

2284 

2285 

2286LINKS_TYPE_SETTER: TypeAlias = ( 

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

2288) 

2289 

2290 

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

2292 """Set the LINKs.""" 

2293 _del_links(self) 

2294 if links is None: 

2295 return 

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

2297 links = [links] 

2298 for link in links: 

2299 if type(link) is str: 

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

2301 self.add("LINK", link) 

2302 

2303 

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

2305 """Delete all links.""" 

2306 self.pop("LINK") 

2307 

2308 

2309links_property = property(_get_links, _set_links, _del_links) 

2310 

2311RELATED_TO_TYPE_SETTER: TypeAlias = ( 

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

2313) 

2314 

2315 

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

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

2318 

2319 Purpose: 

2320 This property is used to represent a relationship or reference 

2321 between one calendar component and another. 

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

2323 

2324 Value Type: 

2325 :rfc:`5545`: TEXT 

2326 :rfc:`9253`: URI, UID 

2327 

2328 Conformance: 

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

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

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

2332 iCalendar component. 

2333 

2334 Description (:rfc:`5545`): 

2335 The property value consists of the persistent, globally 

2336 unique identifier of another calendar component. This value would 

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

2338 

2339 By default, the property value points to another calendar 

2340 component that has a PARENT relationship to the referencing 

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

2342 explicitly state the default PARENT relationship type to the 

2343 referenced calendar component or to override the default PARENT 

2344 relationship type and specify either a CHILD or SIBLING 

2345 relationship. The PARENT relationship indicates that the calendar 

2346 component is a subordinate of the referenced calendar component. 

2347 The CHILD relationship indicates that the calendar component is a 

2348 superior of the referenced calendar component. The SIBLING 

2349 relationship indicates that the calendar component is a peer of 

2350 the referenced calendar component. 

2351 

2352 Changes to a calendar component referenced by this property can 

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

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

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

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

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

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

2359 property is intended only to provide information on the 

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

2361 calendar system to maintain any property implications of this 

2362 relationship. 

2363 

2364 Description (:rfc:`9253`): 

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

2366 consists of the persistent, globally unique identifier of another 

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

2368 component by the UID property. 

2369 

2370 By default, the property value 

2371 points to another calendar component that has a PARENT relationship 

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

2373 to either explicitly state the default PARENT relationship type to 

2374 the referenced calendar component or to override the default 

2375 PARENT relationship type and specify either a CHILD or SIBLING 

2376 relationship or a temporal relationship. 

2377 

2378 The PARENT relationship 

2379 indicates that the calendar component is a subordinate of the 

2380 referenced calendar component. The CHILD relationship indicates 

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

2382 component. The SIBLING relationship indicates that the calendar 

2383 component is a peer of the referenced calendar component. 

2384 

2385 To preserve backwards compatibility, the value type MUST 

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

2387 are specified. 

2388 

2389 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH, 

2390 or STARTTOSTART relationships define temporal relationships, as 

2391 specified in the RELTYPE parameter definition. 

2392 

2393 The FIRST and NEXT 

2394 define ordering relationships between calendar components. 

2395 

2396 The DEPENDS-ON relationship indicates that the current calendar 

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

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

2399 referenced, task. 

2400 

2401 The REFID and CONCEPT relationships establish 

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

2403 Changes to a calendar component referenced by this property 

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

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

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

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

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

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

2410 components. This property is intended only to provide information 

2411 on the relationship of calendar components. 

2412 

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

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

2415 

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

2417 implications of these relationships. 

2418 

2419 Examples: 

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

2421 

2422 .. code-block:: ics 

2423 

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

2425 

2426 .. code-block:: ics 

2427 

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

2429 

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

2431 

2432 .. code-block:: ics 

2433 

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

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

2436 19960401-080045-4000F192713.ics 

2437 

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

2439 

2440 """ 

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

2442 if not isinstance(result, list): 

2443 return [result] 

2444 return result 

2445 

2446 

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

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

2449 _del_related_to(self) 

2450 if values is None: 

2451 return 

2452 if not isinstance(values, list): 

2453 values = [values] 

2454 for value in values: 

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

2456 

2457 

2458def _del_related_to(self: Component): 

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

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

2461 

2462 

2463related_to_property = property(_get_related_to, _set_related_to, _del_related_to) 

2464 

2465 

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

2467 """CONCEPT 

2468 

2469 Purpose: 

2470 CONCEPT defines the formal categories for a calendar component. 

2471 

2472 Conformance: 

2473 Since :rfc:`9253`, 

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

2475 

2476 Description: 

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

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

2479 component of a particular type and category. 

2480 

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

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

2483 the CONCEPT property will reference an external resource that provides 

2484 information about the categorization. 

2485 

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

2487 events. 

2488 

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

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

2491 

2492 Examples: 

2493 The following is an example of this property. 

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

2495 

2496 .. code-block:: ics 

2497 

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

2499 

2500 .. seealso:: 

2501 

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

2503 """ 

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

2505 if not isinstance(concepts, list): 

2506 concepts = [concepts] 

2507 return concepts 

2508 

2509 

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

2511 

2512 

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

2514 """Set the concepts.""" 

2515 _del_concepts(self) 

2516 if concepts is None: 

2517 return 

2518 if not isinstance(concepts, list): 

2519 concepts = [concepts] 

2520 for value in concepts: 

2521 self.add("CONCEPT", value) 

2522 

2523 

2524def _del_concepts(self: Component): 

2525 """Delete the concepts.""" 

2526 self.pop("CONCEPT", None) 

2527 

2528 

2529concepts_property = property(_get_concepts, _set_concepts, _del_concepts) 

2530 

2531 

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

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

2534 

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

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

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

2538 if not isinstance(value, list): 

2539 value = [value] 

2540 return value 

2541 

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

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

2544 fdel(self) 

2545 if value is None: 

2546 return 

2547 if not isinstance(value, list): 

2548 value = [value] 

2549 for value in value: 

2550 self.add(name, value) 

2551 

2552 def fdel(self: Component): 

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

2554 self.pop(name, None) 

2555 

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

2557 

2558 

2559refids_property = multi_string_property( 

2560 "REFID", 

2561 """REFID 

2562 

2563Purpose: 

2564 REFID acts as a key for associated iCalendar entities. 

2565 

2566Conformance: 

2567 Since :rfc:`9253`, 

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

2569 

2570Description: 

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

2572 identifier for associated components. 

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

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

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

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

2577 

2578Examples: 

2579 The following is an example of this property. 

2580 

2581 .. code-block:: ics 

2582 

2583 REFID:itinerary-2014-11-17 

2584 

2585 Use a REFID to associate several VTODOs: 

2586 

2587 .. code-block:: pycon 

2588 

2589 >>> from icalendar import Todo 

2590 >>> todo_1 = Todo.new( 

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

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

2593 ... ) 

2594 >>> todo_2 = Todo.new( 

2595 ... summary="pack backpack", 

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

2597 ... ) 

2598 >>> todo_1.refids == todo_2.refids 

2599 True 

2600 

2601.. note:: 

2602 

2603 When you assign a list to this property, the returned list 

2604 is the same object stored in the component. Modifying it (``append()``, 

2605 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2606 the component stores. 

2607 

2608 However, if you assign a single string value to this property, the returned 

2609 list is a temporary copy, and changes to this list don't affect the component. 

2610""", 

2611) 

2612 

2613REQUEST_STATUS_property = multi_string_property( 

2614 "REQUEST-STATUS", 

2615 """This property defines the status code returned for a scheduling request. 

2616 

2617You can assign a single string, a list of strings, or ``None`` to this 

2618property. The property stores a :class:`str` as-is. Assigning ``None`` or 

2619an empty list removes all REQUEST-STATUS values, as does deleting the 

2620property. 

2621 

2622The value consists of a short return status code component, a longer 

2623return status description component, and optionally a status-specific 

2624data component, separated by semicolons (statcode;statdesc[;extdata]). 

2625The return status components are defined in :rfc:`5545#section-3.8.8.3`. 

2626 

2627The REQUEST-STATUS property can be specified in the following 

2628icalendar components as ``REQUEST_STATUS``. 

2629 

2630- :attr:`Event.REQUEST_STATUS <icalendar.cal.event.Event.REQUEST_STATUS>` 

2631- :attr:`FreeBusy.REQUEST_STATUS <icalendar.cal.free_busy.FreeBusy.REQUEST_STATUS>` 

2632- :attr:`Journal.REQUEST_STATUS <icalendar.cal.journal.Journal.REQUEST_STATUS>` 

2633- :attr:`Todo.REQUEST_STATUS <icalendar.cal.todo.Todo.REQUEST_STATUS>` 

2634 

2635Note: 

2636 When you assign a list to this property, the returned list 

2637 is the same object stored in the component. Modifying it (``append()``, 

2638 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2639 the component stores. 

2640 

2641 However, if you assign a single string value to this property, the returned 

2642 list is a temporary copy, and changes to this list don't affect the component. 

2643 

2644Parameters: 

2645 request_status(str | list[str] | None): 

2646 Either a single status string, a list of status strings, or 

2647 ``None`` to set the component's status code returned for a 

2648 scheduling request. 

2649 

2650Example: 

2651 Add a request status to an event: 

2652 

2653 .. code-block:: pycon 

2654 

2655 >>> from icalendar import Event 

2656 >>> event = Event.new(request_status="2.0;Success") 

2657 >>> event.REQUEST_STATUS == ["2.0;Success"] 

2658 True 

2659""", 

2660) 

2661 

2662RESOURCES_property = multi_string_property( 

2663 "RESOURCES", 

2664 """This property defines resources for a calendar component. 

2665 

2666You can assign a single string, a list of strings, or ``None`` to this 

2667property. The property stores a :class:`str` as-is. Assigning ``None`` or 

2668an empty list removes all RESOURCES values, as does deleting the property. 

2669 

2670The value is a comma-separated list of resources, such as equipment, 

2671facilities, or other things that the component needs. Each item of the 

2672list becomes its own RESOURCES property value. See 

2673:rfc:`5545#section-3.8.1.10` for the specification. 

2674 

2675The RESOURCES property can be specified in the following 

2676icalendar components as ``resources`` using the component's 

2677``new()`` constructor. 

2678 

2679- :attr:`Event.RESOURCES <icalendar.cal.event.Event.RESOURCES>` 

2680- :attr:`Todo.RESOURCES <icalendar.cal.todo.Todo.RESOURCES>` 

2681 

2682Parameters: 

2683 resources(str | list[str] | None): 

2684 Either a single resource string, a list of resource strings, 

2685 or ``None`` to set the component's resources. 

2686 

2687Note: 

2688 When you assign a list to this property, the returned list 

2689 is the same object stored in the component. Modifying it (``append()``, 

2690 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2691 the component stores. 

2692 

2693 However, if you assign a single string value to this property, the returned 

2694 list is a temporary copy, and changes to this list don't affect the component. 

2695 

2696Example: 

2697 Add resources to an event: 

2698 

2699 .. code-block:: pycon 

2700 

2701 >>> from icalendar import Event 

2702 >>> event = Event.new(resources=["EASEL", "PROJECTOR", "VCR"]) 

2703 >>> event.RESOURCES == ["EASEL", "PROJECTOR", "VCR"] 

2704 True 

2705""", 

2706) 

2707 

2708 

2709ATTACHMENTS_TYPE_SETTER: TypeAlias = ( 

2710 str | bytes | vUri | vBinary | None | list[str | bytes | vUri | vBinary] 

2711) 

2712 

2713 

2714def _normalize_attachment(value: str | bytes | vUri | vBinary) -> vUri | vBinary: 

2715 """Convert one attachment value.""" 

2716 if isinstance(value, (vUri, vBinary)): 

2717 return value 

2718 if isinstance(value, str): 

2719 return vUri(value) 

2720 if isinstance(value, bytes): 

2721 return vBinary(value) 

2722 raise TypeError( 

2723 f"Attachments must be str, bytes, vUri, or vBinary, not {type(value).__name__}." 

2724 ) 

2725 

2726 

2727def _get_attachments(self: Component) -> list[vUri | vBinary]: 

2728 """Get all the attachments""" 

2729 attachments = self.get("ATTACH", []) 

2730 if not isinstance(attachments, SEQUENCE_TYPES): 

2731 return [attachments] 

2732 return list(attachments) 

2733 

2734 

2735def _set_attachments(self: Component, value: ATTACHMENTS_TYPE_SETTER) -> None: 

2736 """Set attachments properties""" 

2737 if value is None: 

2738 _del_attachments(self) 

2739 return 

2740 if not isinstance(value, list): 

2741 value = [value] 

2742 attachments = [_normalize_attachment(attachment) for attachment in value] 

2743 _del_attachments(self) 

2744 for attachment in attachments: 

2745 self.add("ATTACH", attachment) 

2746 

2747 

2748def _del_attachments(self: Component) -> None: 

2749 """Delete all attachments""" 

2750 self.pop("ATTACH", None) 

2751 

2752 

2753attachments_property = property( 

2754 _get_attachments, 

2755 _set_attachments, 

2756 _del_attachments, 

2757 """This property defines the attachments for a component. 

2758 

2759Setting this property replaces all existing attachments. A :class:`str` 

2760is converted to :class:`~icalendar.prop.uri.vUri`, and :class:`bytes` is 

2761converted to :class:`~icalendar.prop.binary.vBinary`. Values that are 

2762already :class:`~icalendar.prop.uri.vUri` or 

2763:class:`~icalendar.prop.binary.vBinary` are stored unchanged, so their 

2764parameters are preserved. Setting ``None`` or an empty list removes all 

2765attachments, as does deleting the property. 

2766 

2767Parameters: 

2768 attachments(str | bytes | vUri | vBinary | list | None): 

2769 A single attachment, or a list of attachments to set. Accepts 

2770 :class:`str`, :class:`bytes`, :class:`~icalendar.prop.uri.vUri`, 

2771 and :class:`~icalendar.prop.binary.vBinary`, individually or 

2772 mixed together in a list. 

2773 

2774Example: 

2775 Attach a URI to an event, then replace it with a URI and inline 

2776 binary data together: 

2777 

2778 .. code-block:: pycon 

2779 

2780 >>> from icalendar import Event, vUri, vBinary 

2781 >>> event = Event() 

2782 >>> event.attachments 

2783 [] 

2784 >>> event.attachments = ["https://example.com/agenda.pdf"] 

2785 >>> print(event.to_ical().decode()) 

2786 BEGIN:VEVENT 

2787 ATTACH:https://example.com/agenda.pdf 

2788 END:VEVENT 

2789 >>> event.attachments = [ 

2790 ... vUri( 

2791 ... "https://example.com/agenda.pdf", 

2792 ... params={"FMTTYPE": "application/pdf"}, 

2793 ... ), 

2794 ... vBinary(b"image-data", params={"FMTTYPE": "image/png"},), 

2795 ... ] 

2796 >>> len(event.attachments) 

2797 2 

2798 

2799.. note:: 

2800 

2801 An alarm as an audio action must not contain more than one attachment. 

2802 

2803 List modifications do not modify the component. Methods such as 

2804 ``append()``, ``extend()``, and ``remove()``, as well as item 

2805 assignment, act on a copy. Assign the list back to the property, or 

2806 use :meth:`Component.add <icalendar.cal.component.Component.add>` 

2807 with a typed value instead. 

2808 

2809.. seealso:: 

2810 

2811 :rfc:`5545#section-3.8.1.1` for the definition of the ``ATTACH`` 

2812 property. 

2813""", 

2814) 

2815 

2816 

2817__all__ = [ 

2818 "ATTACHMENTS_TYPE_SETTER", 

2819 "ATTENDEE_TYPE_SETTER", 

2820 "CONCEPTS_TYPE_SETTER", 

2821 "LINKS_TYPE_SETTER", 

2822 "RECURRENCE_ID", 

2823 "RELATED_TO_TYPE_SETTER", 

2824 "REQUEST_STATUS_property", 

2825 "RESOURCES_property", 

2826 "attachments_property", 

2827 "attendees_property", 

2828 "busy_type_property", 

2829 "categories_property", 

2830 "class_property", 

2831 "color_property", 

2832 "comments_property", 

2833 "concepts_property", 

2834 "conferences_property", 

2835 "contacts_property", 

2836 "create_single_property", 

2837 "description_property", 

2838 "descriptions_property", 

2839 "duration_property", 

2840 "exdates_property", 

2841 "get_duration_property", 

2842 "get_end_property", 

2843 "get_start_end_duration_with_validation", 

2844 "get_start_property", 

2845 "images_property", 

2846 "links_property", 

2847 "location_property", 

2848 "multi_language_text_property", 

2849 "multi_string_property", 

2850 "organizer_property", 

2851 "priority_property", 

2852 "property_del_duration", 

2853 "property_doc_duration_template", 

2854 "property_get_duration", 

2855 "property_set_duration", 

2856 "rdates_property", 

2857 "refids_property", 

2858 "related_to_property", 

2859 "repeat_property", 

2860 "rfc_7953_dtend_property", 

2861 "rfc_7953_dtstart_property", 

2862 "rfc_7953_duration_property", 

2863 "rfc_7953_end_property", 

2864 "rrules_property", 

2865 "sequence_property", 

2866 "set_duration_with_locking", 

2867 "set_end_with_locking", 

2868 "set_start_with_locking", 

2869 "single_int_property", 

2870 "single_utc_property", 

2871 "source_property", 

2872 "status_property", 

2873 "summary_property", 

2874 "transparency_property", 

2875 "uid_property", 

2876 "url_property", 

2877]