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

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

554 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.attr.property_list import PropertyListView 

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

12from icalendar.error import IncompleteComponent, InvalidCalendar 

13from icalendar.parser_tools import SEQUENCE_TYPES 

14from icalendar.prop import ( 

15 vBinary, 

16 vCalAddress, 

17 vCategory, 

18 vDDDTypes, 

19 vDuration, 

20 vRecur, 

21 vText, 

22 vUid, 

23 vUnknown, 

24 vUri, 

25 vXmlReference, 

26) 

27from icalendar.prop.conference import Conference 

28from icalendar.prop.image import Image 

29from icalendar.timezone import tzp 

30from icalendar.tools import is_date 

31 

32if TYPE_CHECKING: 

33 from collections.abc import Callable 

34 

35 from icalendar.cal import Component 

36 from icalendar.cal.event import Event 

37 from icalendar.cal.todo import Todo 

38 

39 

40def _get_rdates( 

41 self: Component, 

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

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

44 

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

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

47 

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

49 with and without timezone. 

50 

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

52 if the end is specified. 

53 

54 Value Type: 

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

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

57 

58 Property Parameters: 

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

60 zone identifier property parameters can be specified on this 

61 property. 

62 

63 Conformance: 

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

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

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

67 calendar component. 

68 

69 Description: 

70 This property can appear along with the "RRULE" 

71 property to define an aggregate set of repeating occurrences. 

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

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

74 the "RDATE" and "RRULE". 

75 

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

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

78 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

94 Duplicate instances are ignored. 

95 

96 Example: 

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

98 

99 .. code-block:: pycon 

100 

101 >>> from icalendar import Event 

102 >>> from datetime import datetime 

103 >>> event = Event() 

104 

105 # Add a list of recurrence dates 

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

107 >>> event.rdates 

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

109 

110 .. note:: 

111 

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

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

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

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

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

117 

118 """ 

119 result = [] 

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

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

122 for dts in rdates.dts: 

123 rdate = dts.dt 

124 if isinstance(rdate, tuple): 

125 # we have a period as rdate 

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

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

128 else: 

129 result.append(rdate) 

130 else: 

131 # we have a date/datetime 

132 result.append((rdate, None)) 

133 return result 

134 

135 

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

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

138 

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

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

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

142 """ 

143 _del_rdates(self) 

144 if value: 

145 self.add("RDATE", value) 

146 

147 

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

149 """Delete all RDATE values.""" 

150 self.pop("RDATE", None) 

151 

152 

153rdates_property = property(_get_rdates, _set_rdates, _del_rdates) 

154 

155 

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

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

158 

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

160 

161 Value Type: 

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

163 The value type can be set to DATE. 

164 

165 Property Parameters: 

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

167 zone identifier property parameters can be specified on this 

168 property. 

169 

170 Conformance: 

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

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

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

174 calendar component. 

175 

176 Description: 

177 The exception dates, if specified, are used in 

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

179 set of recurrence instances for a calendar component. The 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

195 Duplicate instances are ignored. 

196 

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

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

199 MUST still be maintained by the calendaring and scheduling system 

200 because the original "DTSTART" value has inherent usage 

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

202 

203 Example: 

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

205 

206 .. code-block:: pycon 

207 

208 >>> from icalendar import Event 

209 >>> from datetime import datetime 

210 >>> event = Event() 

211 

212 # Add a list of excluded dates 

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

214 >>> event.exdates 

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

216 

217 .. note:: 

218 

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

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

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

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

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

224 

225 """ 

226 result = [] 

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

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

229 for dts in exdates.dts: 

230 exdate = dts.dt 

231 # we have a date/datetime 

232 result.append(exdate) 

233 return result 

234 

235 

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

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

238 

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

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

241 """ 

242 _del_exdates(self) 

243 if value: 

244 self.add("EXDATE", value) 

245 

246 

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

248 """Delete all EXDATE values.""" 

249 self.pop("EXDATE", None) 

250 

251 

252exdates_property = property(_get_exdates, _set_exdates, _del_exdates) 

253 

254 

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

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

257 

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

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

260 

261 Property Parameters: 

262 IANA and non-standard property parameters can 

263 be specified on this property. 

264 

265 Conformance: 

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

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

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

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

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

271 undefined. 

272 

273 Description: 

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

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

276 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

281 value SHOULD be synchronized with the recurrence rule, if 

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

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

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

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

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

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

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

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

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

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

292 Duplicate instances are ignored. 

293 

294 The "DTSTART" property specified within the iCalendar object 

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

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

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

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

299 same local time regardless of time zone changes. 

300 

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

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

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

304 duration of the recurring component is specified with the 

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

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

307 duration of each recurrence instance will depend on its specific 

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

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

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

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

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

313 type. 

314 

315 Examples: 

316 Daily for 10 occurrences: 

317 

318 .. code-block:: pycon 

319 

320 >>> from icalendar import Event 

321 >>> from datetime import datetime 

322 >>> from zoneinfo import ZoneInfo 

323 >>> event = Event() 

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

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

326 >>> print(event.to_ical()) 

327 BEGIN:VEVENT 

328 DTSTART;TZID=America/New_York:19970902T090000 

329 RRULE:FREQ=DAILY;COUNT=10 

330 END:VEVENT 

331 >>> event.rrules 

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

333 

334 Daily until December 24, 1997: 

335 

336 .. code-block:: pycon 

337 

338 >>> from icalendar import Event, vRecur 

339 >>> from datetime import datetime 

340 >>> from zoneinfo import ZoneInfo 

341 >>> event = Event() 

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

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

344 >>> print(event.to_ical()) 

345 BEGIN:VEVENT 

346 DTSTART;TZID=America/New_York:19970902T090000 

347 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

348 END:VEVENT 

349 >>> event.rrules 

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

351 

352 .. note:: 

353 

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

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

356 

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

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

359 

360 """ # noqa: E501 

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

362 if not isinstance(rrules, list): 

363 return [rrules] 

364 return rrules 

365 

366 

367rrules_property = property(_get_rrules) 

368 

369 

370def multi_language_text_property( 

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

372) -> property: 

373 """This creates a text property. 

374 

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

376 

377 Parameters: 

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

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

380 doc (str): The documentation string 

381 """ 

382 

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

384 """Get the property""" 

385 result = self.get(main_prop) 

386 if result is None and compatibility_prop is not None: 

387 result = self.get(compatibility_prop) 

388 if isinstance(result, list): 

389 for item in result: 

390 if "LANGUAGE" not in item.params: 

391 return item 

392 return result 

393 

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

395 """Set the property.""" 

396 fdel(self) 

397 if value is not None: 

398 self.add(main_prop, value) 

399 if compatibility_prop is not None: 

400 self.add(compatibility_prop, value) 

401 

402 def fdel(self: Component): 

403 """Delete the property.""" 

404 self.pop(main_prop, None) 

405 if compatibility_prop is not None: 

406 self.pop(compatibility_prop, None) 

407 

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

409 

410 

411def single_int_property( 

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

413) -> property: 

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

415 

416 Parameters: 

417 default: Required. The default value. 

418 doc: Required. The documentation string. 

419 prop: Required. The name of the property. 

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

421 

422 .. versionadded:: 7.3.0 

423 Added the ``min_value`` parameter. 

424 """ 

425 

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

427 """Get the property""" 

428 try: 

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

430 except ValueError as e: 

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

432 

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

434 """Set the property.""" 

435 if value is not None: 

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

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

438 if min_value is not None and value < min_value: 

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

440 fdel(self) 

441 if value is not None: 

442 self.add(prop, value) 

443 

444 def fdel(self: Component): 

445 """Delete the property.""" 

446 self.pop(prop, None) 

447 

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

449 

450 

451def _decode_single_utc_value(name: str, value: object) -> date | datetime: 

452 """Extract and check one datetime value of a singleton UTC property. 

453 

454 Raises: 

455 ValueError: If a text or unknown value cannot be parsed. 

456 InvalidCalendar: If the decoded value is not a date or datetime. 

457 """ 

458 if isinstance(value, (vText, vUnknown)): 

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

460 value = vDDDTypes.from_ical(value) 

461 else: 

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

463 if not isinstance(value, date): 

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

465 return value 

466 

467 

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

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

470 

471 Parameters: 

472 name: name of the property 

473 docs: documentation string 

474 """ 

475 

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

477 """Get the value.""" 

478 if name not in self: 

479 return None 

480 raw = self.get(name) 

481 # Code continues with less indentation 

482 # Broken calendars can repeat singleton properties. Keep all values 

483 # for serialization; when reading, use the earliest valid value. 

484 items = raw if isinstance(raw, list) else [raw] 

485 values = [] 

486 invalid = raw 

487 # duplicate lists are tiny, so the loop overhead is negligible 

488 for item in items: 

489 try: 

490 values.append(_decode_single_utc_value(name, item)) 

491 except (InvalidCalendar, ValueError): # noqa: PERF203 

492 # Report the decoded value as before, or the raw item 

493 # when it cannot be decoded. 

494 if isinstance(item, (vText, vUnknown)): 

495 invalid = item 

496 else: 

497 invalid = getattr(item, "dt", item) 

498 continue 

499 if not values: 

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

501 return min(tzp.localize_utc(value) for value in values) 

502 

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

504 """Set the value""" 

505 if value is None: 

506 fdel(self) 

507 return 

508 if not isinstance(value, date): 

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

510 fdel(self) 

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

512 

513 def fdel(self: Component): 

514 """Delete the property.""" 

515 self.pop(name, None) 

516 

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

518 

519 

520def single_string_property( 

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

522) -> property: 

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

524 other_names = ( 

525 [] 

526 if other_name is None 

527 else [other_name] 

528 if isinstance(other_name, str) 

529 else list(other_name) 

530 ) 

531 

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

533 """Get the value.""" 

534 result = self.get(name, None) 

535 if result is None: 

536 for alias in other_names: 

537 result = self.get(alias, None) 

538 if result is not None: 

539 break 

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

541 return default 

542 if isinstance(result, list): 

543 return result[0] 

544 return result 

545 

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

547 """Set the value. 

548 

549 Setting the value to None will delete it. 

550 """ 

551 fdel(self) 

552 if value is not None: 

553 self.add(name, value) 

554 

555 def fdel(self: Component): 

556 """Delete the property.""" 

557 self.pop(name, None) 

558 for alias in other_names: 

559 self.pop(alias, None) 

560 

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

562 

563 

564color_property = single_string_property( 

565 "COLOR", 

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

567 

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

569 

570 Property Parameters: 

571 IANA and non-standard property parameters can 

572 be specified on this property. 

573 

574 Conformance: 

575 This property can be specified once in an iCalendar 

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

577 

578 Description: 

579 This property specifies a color that clients MAY use 

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

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

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

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

584 

585 Example: 

586 ``"turquoise"``, ``"#ffffff"`` 

587 

588 .. code-block:: pycon 

589 

590 >>> from icalendar import Todo 

591 >>> todo = Todo() 

592 >>> todo.color = "green" 

593 >>> print(todo.to_ical()) 

594 BEGIN:VTODO 

595 COLOR:green 

596 END:VTODO 

597 """, 

598) 

599 

600sequence_property = single_int_property( 

601 "SEQUENCE", 

602 0, 

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

604 

605Value Type: 

606 INTEGER 

607 

608Property Parameters: 

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

610 

611Conformance: 

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

613 "VJOURNAL" calendar component. 

614 

615Description: 

616 When a calendar component is created, its sequence 

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

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

619 calendar component. 

620 

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

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

623 calendar component. 

624 

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

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

627 component to which the "Attendee" is referring. 

628 

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

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

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

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

633 requested. 

634 

635 Recurrence instances of a recurring component MAY have different 

636 sequence numbers. 

637 

638Examples: 

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

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

641 

642 .. code-block:: pycon 

643 

644 >>> from icalendar import Event 

645 >>> event = Event() 

646 >>> event.sequence 

647 0 

648 

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

650 component that has been revised 10 different times by the 

651 "Organizer": 

652 

653 .. code-block:: pycon 

654 

655 >>> from icalendar import Calendar 

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

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

658 >>> event.sequence 

659 10 

660 

661 Raises: 

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

663 even though ``bool`` subclasses ``int``. 

664 

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

666 

667 .. versionchanged:: 7.3.0 

668 Negative values are no longer accepted. 

669 """, # noqa: E501 

670 min_value=0, 

671) 

672 

673 

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

675 """Get all the categories.""" 

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

677 if isinstance(categories, list): 

678 _set_categories( 

679 component, 

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

681 ) 

682 return _get_categories(component) 

683 if categories is None: 

684 categories = vCategory([]) 

685 component.add("CATEGORIES", categories) 

686 return categories.cats 

687 

688 

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

690 """Set the categories.""" 

691 if not cats and cats != []: 

692 _del_categories(component) 

693 return 

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

695 if isinstance(cats, list): 

696 cats.clear() 

697 cats.extend(categories.cats) 

698 categories.cats = cats 

699 

700 

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

702 """Delete the categories.""" 

703 component.pop("CATEGORIES", None) 

704 

705 

706categories_property = property( 

707 _get_categories, 

708 _set_categories, 

709 _del_categories, 

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

711 

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

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

714component of a particular type and category. 

715 

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

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

718 

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

720 

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

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

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

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

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

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

727 

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

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

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

731 

732Note: 

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

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

735 

736Parameters: 

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

738 

739Example: 

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

741 append another category, and finally compare the result 

742 against its expected value. 

743 

744 .. code-block:: pycon 

745 

746 >>> from icalendar import Event 

747 >>> event = Event() 

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

749 >>> print(event.to_ical()) 

750 BEGIN:VEVENT 

751 CATEGORIES:Work,Meeting 

752 END:VEVENT 

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

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

755 True 

756 

757See also: 

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

759""", 

760) 

761 

762 

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

764 """Get attendees.""" 

765 value = self.get("ATTENDEE") 

766 if value is None: 

767 value = [] 

768 self["ATTENDEE"] = value 

769 return value 

770 if isinstance(value, vCalAddress): 

771 return [value] 

772 return value 

773 

774 

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

776 

777 

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

779 """Set attendees.""" 

780 _del_attendees(self) 

781 if value is None: 

782 return 

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

784 value = [value] 

785 elif not isinstance(value, list): 

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

787 for index, attendee in enumerate(value): 

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

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

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

791 self["ATTENDEE"] = value 

792 

793 

794def _del_attendees(self: Component): 

795 """Delete all attendees.""" 

796 self.pop("ATTENDEE", None) 

797 

798 

799attendees_property = property( 

800 _get_attendees, 

801 _set_attendees, 

802 _del_attendees, 

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

804 

805Conformance: 

806 This property MUST be specified in an iCalendar object 

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

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

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

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

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

812 iCalendar object that specifies only a time zone definition or 

813 that defines calendar components that are not group-scheduled 

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

815 

816Description: 

817 This property MUST only be specified within calendar 

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

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

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

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

822 type of iCalendar alarm. 

823 

824Examples: 

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

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

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

828 

829 .. code-block:: pycon 

830 

831 >>> from icalendar import Event 

832 >>> event = Event() 

833 >>> event.attendees = [ 

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

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

836 ... ] 

837 >>> event.attendees[0] 

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

839 >>> event.attendees[1] 

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

841 >>> print(event.to_ical()) 

842 BEGIN:VEVENT 

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

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

845 END:VEVENT 

846 

847 Use :meth:`vCalAddress.new 

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

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

850 

851 .. code-block:: pycon 

852 

853 >>> from icalendar import vCalAddress 

854 >>> event.attendees = [ 

855 ... vCalAddress.new( 

856 ... "chair@example.com", 

857 ... cn="Meeting Chair", 

858 ... role="CHAIR", 

859 ... rsvp=True, 

860 ... ) 

861 ... ] 

862""", 

863) 

864 

865uid_property = single_string_property( 

866 "UID", 

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

868 

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

870 

871Returns: 

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

873 

874Description: 

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

876 The generator of the identifier MUST guarantee that the identifier 

877 is unique. 

878 

879 This is the method for correlating scheduling messages with the 

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

881 The full range of calendar components specified by a recurrence 

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

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

884 property allows the reference to an individual instance within the 

885 recurrence set. 

886 

887 This property is an important method for group-scheduling 

888 applications to match requests with later replies, modifications, 

889 or deletion requests. Calendaring and scheduling applications 

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

891 calendar components to assure interoperability with other group- 

892 scheduling applications. This identifier is created by the 

893 calendar system that generates an iCalendar object. 

894 

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

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

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

898 

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

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

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

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

903 to identify the most recent version of a calendar. 

904 

905Conformance: 

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

907 and "VJOURNAL" calendar components. 

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

909 allow it to be defined in an iCalendar object. 

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

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

912 to refer uniquely to the "VALARM" component. 

913 

914 This property can be specified once only. 

915 

916Security: 

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

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

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

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

921 Universally Unique Identifier (UUID) values as defined in 

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

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

924 

925Compatibility: 

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

927 

928Examples: 

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

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

931 

932 Set the UID of a calendar: 

933 

934 .. code-block:: pycon 

935 

936 >>> from icalendar import Calendar 

937 >>> from uuid import uuid4 

938 >>> calendar = Calendar() 

939 >>> calendar.uid = uuid4() 

940 >>> print(calendar.to_ical()) 

941 BEGIN:VCALENDAR 

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

943 END:VCALENDAR 

944 

945""", 

946) 

947 

948summary_property = multi_language_text_property( 

949 "SUMMARY", 

950 None, 

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

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 "VEVENT", "VTODO", 

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

961 

962Description: 

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

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

965 summary about the activity or journal entry. 

966 

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

968 capture the subject of an EMAIL category of alarm. 

969 

970Examples: 

971 The following is an example of this property: 

972 

973 .. code-block:: pycon 

974 

975 SUMMARY:Department Party 

976""", 

977) 

978 

979description_property = multi_language_text_property( 

980 "DESCRIPTION", 

981 None, 

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

983 

984Property Parameters: 

985 IANA, non-standard, alternate text 

986 representation, and language property parameters can be specified 

987 on this property. 

988 

989Conformance: 

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

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

992 specified multiple times only within a "VJOURNAL" calendar 

993 component. 

994 

995Description: 

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

997 capture lengthy textual descriptions associated with the activity. 

998 

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

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

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

1002 

1003Examples: 

1004 The following is an example of this property with formatted 

1005 line breaks in the property value: 

1006 

1007 .. code-block:: pycon 

1008 

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

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

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

1012 

1013 """, # noqa: E501 

1014) 

1015 

1016 

1017def create_single_property( 

1018 prop: str, 

1019 value_attr: str | None, 

1020 value_type: tuple[type], 

1021 type_def: type, 

1022 doc: str, 

1023 vProp: type = vDDDTypes, # noqa: N803 

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

1025): 

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

1027 

1028 Parameters: 

1029 prop: The name of the property. 

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

1031 value_type: The type of the value. 

1032 type_def: The type of the property. 

1033 doc: The docstring of the property. 

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

1035 """ 

1036 

1037 def p_get(self: Component): 

1038 default = object() 

1039 result = self.get(prop, default) 

1040 if result is default: 

1041 return None 

1042 if isinstance(result, list): 

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

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

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

1046 if not isinstance(value, value_type): 

1047 raise InvalidCalendar( 

1048 f"{prop} must be either a " 

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

1050 f" not {value}." 

1051 ) 

1052 return value 

1053 

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

1055 if value is None: 

1056 p_del(self) 

1057 return 

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

1059 if not isinstance(value, value_type): 

1060 raise TypeError( 

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

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

1063 ) 

1064 self[prop] = vProp(value) 

1065 if prop in self.exclusive: 

1066 for other_prop in self.exclusive: 

1067 if other_prop != prop: 

1068 self.pop(other_prop, None) 

1069 

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

1071 

1072 def p_del(self: Component): 

1073 self.pop(prop) 

1074 

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

1076 

1077 {doc} 

1078 

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

1080 

1081 Raises: 

1082 InvalidCalendar: if the attribute has invalid values. 

1083 """ 

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

1085 

1086 

1087X_MOZ_SNOOZE_TIME_property = single_utc_property( 

1088 "X-MOZ-SNOOZE-TIME", 

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

1090 

1091 Thunderbird: Alarms before this time are snoozed. 

1092""", 

1093) 

1094X_MOZ_LASTACK_property = single_utc_property( 

1095 "X-MOZ-LASTACK", 

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

1097 

1098 Thunderbird: Alarms before this time are acknowledged. 

1099""", 

1100) 

1101 

1102 

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

1104 """Getter for property DURATION.""" 

1105 default = object() 

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

1107 if duration is default: 

1108 return None 

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

1110 if result is None: 

1111 raise InvalidCalendar( 

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

1113 ) 

1114 return result 

1115 

1116 

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

1118 """Setter for property DURATION.""" 

1119 if value is None: 

1120 self.pop("duration", None) 

1121 return 

1122 if not isinstance(value, timedelta): 

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

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

1125 self.pop("DTEND") 

1126 self.pop("DUE") 

1127 

1128 

1129def property_del_duration(self: Component): 

1130 """Delete property DURATION.""" 

1131 self.pop("DURATION") 

1132 

1133 

1134property_doc_duration_template = """The DURATION property. 

1135 

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

1137start of the {component}. 

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

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

1140of the event. 

1141 

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

1143Instead use the duration property (lower case). 

1144""" 

1145 

1146 

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

1148 """Return the duration property.""" 

1149 return property( 

1150 property_get_duration, 

1151 property_set_duration, 

1152 property_del_duration, 

1153 property_doc_duration_template.format(component=component), 

1154 ) 

1155 

1156 

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

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

1159 

1160 Examples: Journal.descriptions, Event.comments 

1161 """ 

1162 

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

1164 """Get the values.""" 

1165 descriptions = self.get(name) 

1166 if descriptions is None: 

1167 return [] 

1168 if not isinstance(descriptions, SEQUENCE_TYPES): 

1169 return [descriptions] 

1170 return descriptions 

1171 

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

1173 """Set the values.""" 

1174 fdel(self) 

1175 if values is None: 

1176 return 

1177 if isinstance(values, str): 

1178 self.add(name, values) 

1179 else: 

1180 for description in values: 

1181 self.add(name, description) 

1182 

1183 def fdel(self: Component): 

1184 """Delete the values.""" 

1185 self.pop(name) 

1186 

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

1188 

1189 

1190descriptions_property = multi_text_property( 

1191 "DESCRIPTION", 

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

1193 

1194Property Parameters: 

1195 IANA, non-standard, alternate text 

1196 representation, and language property parameters can be specified 

1197 on this property. 

1198 

1199Conformance: 

1200 The property can be 

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

1202 

1203Description: 

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

1205 capture one or more textual journal entries. 

1206 

1207Examples: 

1208 The following is an example of this property with formatted 

1209 line breaks in the property value: 

1210 

1211 .. code-block:: pycon 

1212 

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

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

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

1216 

1217""", # noqa: E501 

1218) 

1219 

1220comments_property = multi_text_property( 

1221 "COMMENT", 

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

1223 

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

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

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

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

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

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

1230 

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

1232list of strings; each string is one comment. 

1233 

1234Example: 

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

1236 

1237 .. code-block:: pycon 

1238 

1239 >>> from icalendar import Event 

1240 >>> event = Event() 

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

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

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

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

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

1246 'Moved from the planning board.' 

1247""", 

1248) 

1249 

1250RECURRENCE_ID = create_single_property( 

1251 "RECURRENCE-ID", 

1252 "dt", 

1253 (date, datetime), 

1254 date | datetime, 

1255 """ 

1256Identify a specific occurrence of a recurring calendar object. 

1257 

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

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

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

1261 

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

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

1264all-day items instead. 

1265 

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

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

1268""", 

1269 vDDDTypes, 

1270) 

1271 

1272 

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

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

1275 

1276 Property Parameters: 

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

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

1279 specified on this property. 

1280 

1281 Conformance: 

1282 This property MUST be specified in an iCalendar object 

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

1284 MUST be specified in an iCalendar object that specifies the 

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

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

1287 zone definition or that defines calendar components that are not 

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

1289 user's calendar. 

1290 

1291 Description: 

1292 This property is specified within the "VEVENT", 

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

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

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

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

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

1298 the calendar that the published busy time came from. 

1299 

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

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

1302 specifying a pointer to the directory information associated with 

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

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

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

1306 "LANGUAGE" property parameter is specified, the identified 

1307 language applies to the "CN" parameter value. 

1308 """ 

1309 return self.get("ORGANIZER") 

1310 

1311 

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

1313 """Set the value.""" 

1314 _del_organizer(self) 

1315 if value is not None: 

1316 self.add("ORGANIZER", value) 

1317 

1318 

1319def _del_organizer(self: Component): 

1320 """Delete the value.""" 

1321 self.pop("ORGANIZER") 

1322 

1323 

1324organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1325 

1326 

1327def single_string_enum_property( 

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

1329) -> property: 

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

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

1332 

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

1334 """Get the value.""" 

1335 value = prop.fget(self) 

1336 if value == default: 

1337 return default 

1338 return enum(str(value)) 

1339 

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

1341 """Set the value.""" 

1342 if value == "": 

1343 value = None 

1344 prop.fset(self, value) 

1345 

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

1347 

1348 

1349busy_type_property = single_string_enum_property( 

1350 "BUSYTYPE", 

1351 BUSYTYPE, 

1352 BUSYTYPE.BUSY_UNAVAILABLE, 

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

1354 

1355Returns: 

1356 :class:`icalendar.enums.BUSYTYPE` 

1357 

1358Description: 

1359 This property is used to specify the default busy time 

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

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

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

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

1364 UNAVAILABLE". 

1365""", 

1366) 

1367 

1368 

1369def _make_repeat_property() -> property: 

1370 from icalendar.config import _clamp_repeat 

1371 

1372 _base = single_int_property( 

1373 "REPEAT", 

1374 0, 

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

1376initial trigger. 

1377 

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

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

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

1381 

1382Raises: 

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

1384 even though ``bool`` subclasses ``int``. 

1385 

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

1387 

1388.. versionchanged:: 7.3.0 

1389 Negative values are no longer accepted. 

1390""", 

1391 min_value=0, 

1392 ) 

1393 

1394 def fget(self): 

1395 return _clamp_repeat(_base.fget(self)) 

1396 

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

1398 

1399 

1400repeat_property = _make_repeat_property() 

1401 

1402priority_property = single_int_property( 

1403 "PRIORITY", 

1404 0, 

1405 """ 

1406 

1407Conformance: 

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

1409 according to :rfc:`5545`. 

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

1411 

1412Description: 

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

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

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

1416 priority. Subsequent numbers specify a decreasing ordinal 

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

1418 

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

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

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

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

1423 is "LOW" priority. 

1424 

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

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

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

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

1429 9 specifies "C3". 

1430 

1431 Other integer values are reserved for future use. 

1432 

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

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

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

1436 

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

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

1439 multiple action items for a given time period. 

1440 

1441 Raises: 

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

1443 even though ``bool`` subclasses ``int``. 

1444 

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

1446 

1447 .. versionchanged:: 7.3.0 

1448 Negative values are no longer accepted. 

1449""", 

1450 min_value=0, 

1451) 

1452 

1453class_property = single_string_enum_property( 

1454 "CLASS", 

1455 CLASS, 

1456 CLASS.PUBLIC, 

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

1458 

1459Returns: 

1460 :class:`icalendar.enums.CLASS` 

1461 

1462Description: 

1463 An access classification is only one component of the 

1464 general security system within a calendar application. It 

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

1466 calendar owner intends for information within an individual 

1467 calendar entry. The access classification of an individual 

1468 iCalendar component is useful when measured along with the other 

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

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

1471 Hence, the semantics of the individual access classifications 

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

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

1474 memo, these access classifications cannot serve as an enforcement 

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

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

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

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

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

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

1481""", 

1482) 

1483 

1484transparency_property = single_string_enum_property( 

1485 "TRANSP", 

1486 TRANSP, 

1487 TRANSP.OPAQUE, 

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

1489 

1490Returns: 

1491 :class:`icalendar.enums.TRANSP` 

1492 

1493Description: 

1494 Time Transparency is the characteristic of an event 

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

1496 Events that consume actual time for the individual or resource 

1497 associated with the calendar SHOULD be recorded as OPAQUE, 

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

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

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

1501 busy time searches. 

1502""", 

1503) 

1504status_property = single_string_enum_property( 

1505 "STATUS", 

1506 STATUS, 

1507 "", 

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

1509 

1510Returns: 

1511 :class:`icalendar.enums.STATUS` 

1512 

1513The default value is ``""``. 

1514 

1515Description: 

1516 In a group-scheduled calendar component, the property 

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

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

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

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

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

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

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

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

1525 cancelled or removed. 

1526""", 

1527) 

1528 

1529url_property = single_string_property( 

1530 "URL", 

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

1532 

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

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

1535or provide additional information about the component. 

1536 

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

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

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

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

1541 

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

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

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

1545they MUST point to the same resource. 

1546 

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

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

1549representation of the current calendar data. 

1550 

1551Examples: 

1552 

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

1554 

1555 .. code-block:: pycon 

1556 

1557 >>> from icalendar import Event 

1558 >>> event = Event() 

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

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

1561 BEGIN:VEVENT 

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

1563 END:VEVENT 

1564 

1565 Set a URL for a calendar: 

1566 

1567 .. code-block:: pycon 

1568 

1569 >>> from icalendar import Calendar 

1570 >>> calendar = Calendar() 

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

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

1573 BEGIN:VCALENDAR 

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

1575 END:VCALENDAR 

1576 

1577See also: 

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

1579 calendar data can be refreshed. 

1580 

1581 icalendar implementations: 

1582 

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

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

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

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

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

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

1589 

1590 

1591""", 

1592) 

1593 

1594source_property = single_string_property( 

1595 "SOURCE", 

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

1597 

1598Description: 

1599 This property identifies a location where a client can 

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

1601 specified "REFRESH-INTERVAL" value when periodically retrieving 

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

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

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

1605 

1606Conformance: 

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

1608 

1609Example: 

1610 The following is an example of this property: 

1611 

1612 .. code-block:: ics 

1613 

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

1615 

1616""", 

1617) 

1618 

1619location_property = multi_language_text_property( 

1620 "LOCATION", 

1621 None, 

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

1623 

1624Property Parameters: 

1625 IANA, non-standard, alternate text 

1626 representation, and language property parameters can be specified 

1627 on this property. 

1628 

1629Conformance: 

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

1631 calendar component. 

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

1633 

1634Description: 

1635 Specific venues such as conference or meeting rooms may 

1636 be explicitly specified using this property. An alternate 

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

1638 directory information with more structured specification of the 

1639 location. For example, the alternate representation may specify 

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

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

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

1643 

1644""", 

1645) 

1646 

1647contacts_property = multi_text_property( 

1648 "CONTACT", 

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

1650 

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

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

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

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

1655 

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

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

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

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

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

1661 

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

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

1664 

1665Example: 

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

1667 

1668 .. code-block:: pycon 

1669 

1670 >>> from icalendar import Event 

1671 >>> event = Event() 

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

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

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

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

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

1677""", 

1678) 

1679 

1680 

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

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

1683 

1684 return single_utc_property(name, docs) 

1685 

1686 

1687rfc_7953_dtstart_property = _timezone_datetime_property( 

1688 "DTSTART", 

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

1690 

1691 This is almost the same as 

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

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

1694 

1695 Description: 

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

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

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

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

1700 

1701 """, 

1702) 

1703 

1704rfc_7953_dtend_property = _timezone_datetime_property( 

1705 "DTEND", 

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

1707 

1708 This is almost the same as 

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

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

1711 

1712 Description: 

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

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

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

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

1717 """, 

1718) 

1719 

1720 

1721@property 

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

1723 """Compute the duration of this component. 

1724 

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

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

1727 :attr:`DTEND`/:attr:`DURATION`. 

1728 

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

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

1731 """ 

1732 duration = self.DURATION 

1733 if duration: 

1734 return duration 

1735 end = self.DTEND 

1736 if end is None: 

1737 return None 

1738 start = self.DTSTART 

1739 if start is None: 

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

1741 return end - start 

1742 

1743 

1744@property 

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

1746 """Compute the duration of this component. 

1747 

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

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

1750 :attr:`DTEND`/:attr:`DURATION`. 

1751 

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

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

1754 """ 

1755 duration = self.DURATION 

1756 if duration: 

1757 start = self.DTSTART 

1758 if start is None: 

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

1760 return start + duration 

1761 end = self.DTEND 

1762 if end is None: 

1763 return None 

1764 return end 

1765 

1766 

1767@rfc_7953_end_property.setter 

1768def rfc_7953_end_property(self, value: datetime): 

1769 self.DTEND = value 

1770 

1771 

1772@rfc_7953_end_property.deleter 

1773def rfc_7953_end_property(self): 

1774 del self.DTEND 

1775 

1776 

1777def get_start_end_duration_with_validation( 

1778 component: Event | Todo, 

1779 start_property: Literal["DTSTART"], 

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

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

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

1783 """ 

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

1785 

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

1787 for ``Event`` and ``Todo`` components. 

1788 

1789 Parameters: 

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

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

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

1793 ``DUE`` for ``Todo``. 

1794 component_name: The component name for error messages, 

1795 either ``VEVENT`` or ``VTODO``. 

1796 

1797 Returns: 

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

1799 

1800 Raises: 

1801 ~icalendar.error.InvalidCalendar: If the component violates 

1802 RFC 5545 constraints. 

1803 

1804 """ 

1805 start = getattr(component, start_property, None) 

1806 end = getattr(component, end_property, None) 

1807 duration = component.DURATION 

1808 

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

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

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

1812 msg = ( 

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

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

1815 ) 

1816 raise InvalidCalendar(msg) 

1817 

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

1819 if ( 

1820 start is not None 

1821 and is_date(start) 

1822 and duration is not None 

1823 and duration.seconds != 0 

1824 ): 

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

1826 raise InvalidCalendar(msg) 

1827 

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

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

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

1831 msg = ( 

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

1833 ) 

1834 raise InvalidCalendar(msg) 

1835 

1836 return start, end, duration 

1837 

1838 

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

1840 """ 

1841 Get the start property with validation. 

1842 

1843 Parameters: 

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

1845 

1846 Returns: 

1847 The ``DTSTART`` value. 

1848 

1849 Raises: 

1850 ~icalendar.error.IncompleteComponent: If no ``DTSTART`` is present. 

1851 

1852 """ 

1853 # Trigger validation by calling _get_start_end_duration 

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

1855 if start is None: 

1856 msg = "No DTSTART given." 

1857 raise IncompleteComponent(msg) 

1858 return start 

1859 

1860 

1861def get_end_property( 

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

1863) -> date | datetime: 

1864 """ 

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

1866 

1867 Parameters: 

1868 component: The component to get end from 

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

1870 ``DUE`` for ``Todo``. 

1871 

1872 Returns: 

1873 The computed end value. 

1874 

1875 Raises: 

1876 ~icalendar.error.IncompleteComponent: If the provided information is incomplete 

1877 to compute the end property. 

1878 

1879 """ 

1880 # Trigger validation by calling _get_start_end_duration 

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

1882 

1883 if end is None and duration is None: 

1884 if start is None: 

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

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

1887 raise IncompleteComponent(msg) 

1888 

1889 # Default behavior differs for Event vs Todo: 

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

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

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

1893 return start + timedelta(days=1) 

1894 return start 

1895 

1896 if duration is not None: 

1897 if start is not None: 

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

1899 return start 

1900 return start + duration 

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

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

1903 raise IncompleteComponent(msg) 

1904 

1905 return end 

1906 

1907 

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

1909 """ 

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

1911 

1912 Parameters: 

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

1914 

1915 Returns: 

1916 The duration as a timedelta. 

1917 

1918 """ 

1919 # First check if DURATION property is explicitly set 

1920 if "DURATION" in component: 

1921 return component["DURATION"].dt 

1922 

1923 # Fall back to calculated duration from start and end 

1924 return component.end - component.start 

1925 

1926 

1927def set_duration_with_locking( 

1928 component: Event | Todo, 

1929 duration: timedelta | None, 

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

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

1932) -> None: 

1933 """ 

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

1935 

1936 Parameters: 

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

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

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

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

1941 ``DUE`` for ``Todo``. 

1942 

1943 """ 

1944 # Convert to DURATION property if duration is None 

1945 if duration is None: 

1946 if "DURATION" in component: 

1947 return # Already has DURATION property 

1948 current_duration = component.duration 

1949 component.DURATION = current_duration 

1950 return 

1951 

1952 if not isinstance(duration, timedelta): 

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

1954 raise TypeError(msg) 

1955 

1956 # Validate date/duration compatibility 

1957 start = component.DTSTART 

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

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

1960 raise InvalidCalendar(msg) 

1961 

1962 if locked == "start": 

1963 # Keep start locked, adjust end 

1964 if start is None: 

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

1966 raise IncompleteComponent(msg) 

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

1968 component.DURATION = duration 

1969 elif locked == "end": 

1970 # Keep end locked, adjust start 

1971 current_end = component.end 

1972 component.DTSTART = current_end - duration 

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

1974 component.DURATION = duration 

1975 else: 

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

1977 raise ValueError(msg) 

1978 

1979 

1980def set_start_with_locking( 

1981 component: Event | Todo, 

1982 start: date | datetime, 

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

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

1985) -> None: 

1986 """ 

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

1988 

1989 Parameters: 

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

1991 start: The start time to set. 

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

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

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

1995 ``DUE`` for ``Todo``. 

1996 

1997 """ 

1998 if locked is None: 

1999 # Auto-detect based on existing properties 

2000 if "DURATION" in component: 

2001 locked = "duration" 

2002 elif end_property in component: 

2003 locked = "end" 

2004 else: 

2005 # Default to duration if no existing properties 

2006 locked = "duration" 

2007 

2008 if locked == "duration": 

2009 # Keep duration locked, adjust end 

2010 current_duration = ( 

2011 component.duration 

2012 if "DURATION" in component or end_property in component 

2013 else None 

2014 ) 

2015 component.DTSTART = start 

2016 if current_duration is not None: 

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

2018 component.DURATION = current_duration 

2019 elif locked == "end": 

2020 # Keep end locked, adjust duration 

2021 current_end = component.end 

2022 component.DTSTART = start 

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

2024 setattr(component, end_property, current_end) 

2025 else: 

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

2027 raise ValueError(msg) 

2028 

2029 

2030def set_end_with_locking( 

2031 component: Event | Todo, 

2032 end: date | datetime, 

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

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

2035) -> None: 

2036 """ 

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

2038 

2039 Parameters: 

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

2041 end: The end time to set. 

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

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

2044 for ``Todo``. 

2045 

2046 """ 

2047 if locked == "start": 

2048 # Keep start locked, adjust duration 

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

2050 setattr(component, end_property, end) 

2051 elif locked == "duration": 

2052 # Keep duration locked, adjust start 

2053 current_duration = component.duration 

2054 component.DTSTART = end - current_duration 

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

2056 component.DURATION = current_duration 

2057 else: 

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

2059 raise ValueError(msg) 

2060 

2061 

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

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

2064 

2065 Description: 

2066 This property specifies an image for an iCalendar 

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

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

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

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

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

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

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

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

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

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

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

2078 on the image in the calendar user agent. 

2079 

2080 Conformance: 

2081 This property can be specified multiple times in an 

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

2083 components. 

2084 

2085 .. note:: 

2086 

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

2088 please open an issue or a pull request. 

2089 """ 

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

2091 if not isinstance(images, SEQUENCE_TYPES): 

2092 images = [images] 

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

2094 

2095 

2096images_property = property(_get_images) 

2097 

2098 

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

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

2101 

2102 Purpose: 

2103 This property specifies information for accessing a conferencing system. 

2104 

2105 Conformance: 

2106 This property can be specified multiple times in a 

2107 "VEVENT" or "VTODO" calendar component. 

2108 

2109 Description: 

2110 This property specifies information for accessing a 

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

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

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

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

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

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

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

2118 containing a text description can be used. 

2119 

2120 A conference system can be a bidirectional communication channel 

2121 or a uni-directional "broadcast feed". 

2122 

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

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

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

2126 multiple properties. 

2127 

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

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

2130 codes for the moderator and attendee of a teleconference system 

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

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

2133 which. 

2134 

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

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

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

2138 

2139 Example: 

2140 The following are examples of this property: 

2141 

2142 .. code-block:: ics 

2143 

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

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

2146 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

2148 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

2150 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

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

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

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

2154 

2155 Get all conferences: 

2156 

2157 .. code-block:: pycon 

2158 

2159 >>> from icalendar import Event 

2160 >>> event = Event() 

2161 >>> event.conferences 

2162 [] 

2163 

2164 Set a conference: 

2165 

2166 .. code-block:: pycon 

2167 

2168 >>> from icalendar import Event, Conference 

2169 >>> event = Event() 

2170 >>> event.conferences = [ 

2171 ... Conference( 

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

2173 ... feature="PHONE,MODERATOR", 

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

2175 ... language="EN", 

2176 ... ) 

2177 ... ] 

2178 >>> print(event.to_ical()) 

2179 BEGIN:VEVENT 

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

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

2182 END:VEVENT 

2183 

2184 """ 

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

2186 if not isinstance(conferences, SEQUENCE_TYPES): 

2187 conferences = [conferences] 

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

2189 

2190 

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

2192 """Set the conferences.""" 

2193 _del_conferences(self) 

2194 for conference in conferences or []: 

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

2196 

2197 

2198def _del_conferences(self: Component): 

2199 """Delete all conferences.""" 

2200 self.pop("CONFERENCE") 

2201 

2202 

2203conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

2204 

2205 

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

2207 """LINK properties as a list. 

2208 

2209 Purpose: 

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

2211 

2212 Property Parameters: 

2213 The VALUE parameter is required. 

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

2215 can also be specified on this property. 

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

2217 

2218 Conformance: 

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

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

2221 The LINKREL parameter is required. 

2222 

2223 Description: 

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

2225 additional information related to the component. 

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

2227 

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

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

2230 the link context is the containing calendar entity, 

2231 and the link relation type and any target attributes 

2232 are carried in iCalendar property parameters. 

2233 

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

2235 

2236 LABEL 

2237 This parameter maps to the "title" 

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

2239 LABEL is used to label the destination 

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

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

2242 (if present). 

2243 LANGUAGE 

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

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

2246 LINKREL 

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

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

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

2250 FMTTYPE 

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

2252 :rfc:`8288`. 

2253 

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

2255 

2256 Examples: 

2257 The following is an example of this property, 

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

2259 

2260 .. code-block:: ics 

2261 

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

2263 https://example.com/events 

2264 

2265 The following is an example of this property, 

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

2267 The link relation is a vendor-defined value. 

2268 

2269 .. code-block:: ics 

2270 

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

2272 VALUE=URI: 

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

2274 

2275 The following is an example of this property, 

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

2277 The link relation is a vendor-defined value. 

2278 

2279 .. code-block:: ics 

2280 

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

2282 VALUE=XML-REFERENCE: 

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

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

2285 following::CostStrucEND[1])) 

2286 

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

2288 

2289 .. code-block:: pycon 

2290 

2291 >>> from icalendar import Event, vUri 

2292 >>> from datetime import datetime 

2293 >>> link = vUri( 

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

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

2296 ... ) 

2297 >>> event = Event.new( 

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

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

2300 ... ) 

2301 >>> event.links = [link] 

2302 >>> print(event.to_ical()) 

2303 BEGIN:VEVENT 

2304 SUMMARY:An Example Event with a page 

2305 DTSTART:20250917T120000 

2306 DTSTAMP:20250517T080612Z 

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

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

2309 END:VEVENT 

2310 

2311 """ 

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

2313 if not isinstance(links, list): 

2314 links = [links] 

2315 return links 

2316 

2317 

2318LINKS_TYPE_SETTER: TypeAlias = ( 

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

2320) 

2321 

2322 

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

2324 """Set the LINKs.""" 

2325 _del_links(self) 

2326 if links is None: 

2327 return 

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

2329 links = [links] 

2330 for link in links: 

2331 if type(link) is str: 

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

2333 self.add("LINK", link) 

2334 

2335 

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

2337 """Delete all links.""" 

2338 self.pop("LINK") 

2339 

2340 

2341links_property = property(_get_links, _set_links, _del_links) 

2342 

2343RELATED_TO_TYPE_SETTER: TypeAlias = ( 

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

2345) 

2346 

2347 

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

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

2350 

2351 Purpose: 

2352 This property is used to represent a relationship or reference 

2353 between one calendar component and another. 

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

2355 

2356 Value Type: 

2357 :rfc:`5545`: TEXT 

2358 :rfc:`9253`: URI, UID 

2359 

2360 Conformance: 

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

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

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

2364 iCalendar component. 

2365 

2366 Description (:rfc:`5545`): 

2367 The property value consists of the persistent, globally 

2368 unique identifier of another calendar component. This value would 

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

2370 

2371 By default, the property value points to another calendar 

2372 component that has a PARENT relationship to the referencing 

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

2374 explicitly state the default PARENT relationship type to the 

2375 referenced calendar component or to override the default PARENT 

2376 relationship type and specify either a CHILD or SIBLING 

2377 relationship. The PARENT relationship indicates that the calendar 

2378 component is a subordinate of the referenced calendar component. 

2379 The CHILD relationship indicates that the calendar component is a 

2380 superior of the referenced calendar component. The SIBLING 

2381 relationship indicates that the calendar component is a peer of 

2382 the referenced calendar component. 

2383 

2384 Changes to a calendar component referenced by this property can 

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

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

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

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

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

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

2391 property is intended only to provide information on the 

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

2393 calendar system to maintain any property implications of this 

2394 relationship. 

2395 

2396 Description (:rfc:`9253`): 

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

2398 consists of the persistent, globally unique identifier of another 

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

2400 component by the UID property. 

2401 

2402 By default, the property value 

2403 points to another calendar component that has a PARENT relationship 

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

2405 to either explicitly state the default PARENT relationship type to 

2406 the referenced calendar component or to override the default 

2407 PARENT relationship type and specify either a CHILD or SIBLING 

2408 relationship or a temporal relationship. 

2409 

2410 The PARENT relationship 

2411 indicates that the calendar component is a subordinate of the 

2412 referenced calendar component. The CHILD relationship indicates 

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

2414 component. The SIBLING relationship indicates that the calendar 

2415 component is a peer of the referenced calendar component. 

2416 

2417 To preserve backwards compatibility, the value type MUST 

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

2419 are specified. 

2420 

2421 The FINISHTOSTART, FINISHTOFINISH, STARTTOFINISH, 

2422 or STARTTOSTART relationships define temporal relationships, as 

2423 specified in the RELTYPE parameter definition. 

2424 

2425 The FIRST and NEXT 

2426 define ordering relationships between calendar components. 

2427 

2428 The DEPENDS-ON relationship indicates that the current calendar 

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

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

2431 referenced, task. 

2432 

2433 The REFID and CONCEPT relationships establish 

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

2435 Changes to a calendar component referenced by this property 

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

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

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

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

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

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

2442 components. This property is intended only to provide information 

2443 on the relationship of calendar components. 

2444 

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

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

2447 

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

2449 implications of these relationships. 

2450 

2451 Examples: 

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

2453 

2454 .. code-block:: ics 

2455 

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

2457 

2458 .. code-block:: ics 

2459 

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

2461 

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

2463 

2464 .. code-block:: ics 

2465 

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

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

2468 19960401-080045-4000F192713.ics 

2469 

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

2471 

2472 """ 

2473 return PropertyListView(self, "RELATED-TO") 

2474 

2475 

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

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

2478 PropertyListView(self, "RELATED-TO").replace_with(values) 

2479 

2480 

2481def _del_related_to(self: Component): 

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

2483 PropertyListView(self, "RELATED-TO").clear() 

2484 

2485 

2486related_to_property = property(_get_related_to, _set_related_to, _del_related_to) 

2487 

2488 

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

2490 """CONCEPT 

2491 

2492 Purpose: 

2493 CONCEPT defines the formal categories for a calendar component. 

2494 

2495 Conformance: 

2496 Since :rfc:`9253`, 

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

2498 

2499 Description: 

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

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

2502 component of a particular type and category. 

2503 

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

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

2506 the CONCEPT property will reference an external resource that provides 

2507 information about the categorization. 

2508 

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

2510 events. 

2511 

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

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

2514 

2515 Examples: 

2516 The following is an example of this property. 

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

2518 

2519 .. code-block:: ics 

2520 

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

2522 

2523 .. seealso:: 

2524 

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

2526 """ 

2527 return PropertyListView(self, "CONCEPT") 

2528 

2529 

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

2531 

2532 

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

2534 """Set the concepts.""" 

2535 PropertyListView(self, "CONCEPT").replace_with(concepts) 

2536 

2537 

2538def _del_concepts(self: Component): 

2539 """Delete the concepts.""" 

2540 PropertyListView(self, "CONCEPT").clear() 

2541 

2542 

2543concepts_property = property(_get_concepts, _set_concepts, _del_concepts) 

2544 

2545 

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

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

2548 

2549 def fget(self: Component) -> PropertyListView: 

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

2551 return PropertyListView(self, name) 

2552 

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

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

2555 PropertyListView(self, name).replace_with(value) 

2556 

2557 def fdel(self: Component): 

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

2559 PropertyListView(self, name).clear() 

2560 

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

2562 

2563 

2564refids_property = multi_string_property( 

2565 "REFID", 

2566 """REFID 

2567 

2568Purpose: 

2569 REFID acts as a key for associated iCalendar entities. 

2570 

2571Conformance: 

2572 Since :rfc:`9253`, 

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

2574 

2575Description: 

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

2577 identifier for associated components. 

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

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

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

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

2582 

2583Examples: 

2584 The following is an example of this property. 

2585 

2586 .. code-block:: ics 

2587 

2588 REFID:itinerary-2014-11-17 

2589 

2590 Use a REFID to associate several VTODOs: 

2591 

2592 .. code-block:: pycon 

2593 

2594 >>> from icalendar import Todo 

2595 >>> todo_1 = Todo.new( 

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

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

2598 ... ) 

2599 >>> todo_2 = Todo.new( 

2600 ... summary="pack backpack", 

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

2602 ... ) 

2603 >>> todo_1.refids == todo_2.refids 

2604 True 

2605 

2606.. note:: 

2607 

2608 When you assign a list to this property, the returned list 

2609 is the same object stored in the component. Modifying it (``append()``, 

2610 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2611 the component stores. 

2612 

2613 However, if you assign a single string value to this property, the returned 

2614 list is a temporary copy, and changes to this list don't affect the component. 

2615""", 

2616) 

2617 

2618REQUEST_STATUS_property = multi_string_property( 

2619 "REQUEST-STATUS", 

2620 """This property defines the status code returned for a scheduling request. 

2621 

2622You can assign a single string, a list of strings, or ``None`` to this 

2623property. The property stores a :class:`str` as-is. Assigning ``None`` or 

2624an empty list removes all REQUEST-STATUS values, as does deleting the 

2625property. 

2626 

2627The value consists of a short return status code component, a longer 

2628return status description component, and optionally a status-specific 

2629data component, separated by semicolons (statcode;statdesc[;extdata]). 

2630The return status components are defined in :rfc:`5545#section-3.8.8.3`. 

2631 

2632The REQUEST-STATUS property can be specified in the following 

2633icalendar components as ``REQUEST_STATUS``. 

2634 

2635- :attr:`Event.REQUEST_STATUS <icalendar.cal.event.Event.REQUEST_STATUS>` 

2636- :attr:`FreeBusy.REQUEST_STATUS <icalendar.cal.free_busy.FreeBusy.REQUEST_STATUS>` 

2637- :attr:`Journal.REQUEST_STATUS <icalendar.cal.journal.Journal.REQUEST_STATUS>` 

2638- :attr:`Todo.REQUEST_STATUS <icalendar.cal.todo.Todo.REQUEST_STATUS>` 

2639 

2640Note: 

2641 When you assign a list to this property, the returned list 

2642 is the same object stored in the component. Modifying it (``append()``, 

2643 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2644 the component stores. 

2645 

2646 However, if you assign a single string value to this property, the returned 

2647 list is a temporary copy, and changes to this list don't affect the component. 

2648 

2649Parameters: 

2650 request_status(str | list[str] | None): 

2651 Either a single status string, a list of status strings, or 

2652 ``None`` to set the component's status code returned for a 

2653 scheduling request. 

2654 

2655Example: 

2656 Add a request status to an event: 

2657 

2658 .. code-block:: pycon 

2659 

2660 >>> from icalendar import Event 

2661 >>> event = Event.new(request_status="2.0;Success") 

2662 >>> event.REQUEST_STATUS == ["2.0;Success"] 

2663 True 

2664 

2665 :class:`~icalendar.prop.request_status.vRequestStatus` defines 

2666 properties for ease of use: 

2667 

2668 .. code-block:: pycon 

2669 

2670 >>> request_status = event.REQUEST_STATUS[0] 

2671 >>> request_status.code 

2672 (2, 0) 

2673 >>> request_status.description 

2674 'Success' 

2675 

2676""", 

2677) 

2678 

2679RESOURCES_property = multi_string_property( 

2680 "RESOURCES", 

2681 """This property defines resources for a calendar component. 

2682 

2683You can assign a single string, a list of strings, or ``None`` to this 

2684property. The property stores a :class:`str` as-is. Assigning ``None`` or 

2685an empty list removes all RESOURCES values, as does deleting the property. 

2686 

2687The value is a comma-separated list of resources, such as equipment, 

2688facilities, or other things that the component needs. Each item of the 

2689list becomes its own RESOURCES property value. See 

2690:rfc:`5545#section-3.8.1.10` for the specification. 

2691 

2692The RESOURCES property can be specified in the following 

2693icalendar components as ``resources`` using the component's 

2694``new()`` constructor. 

2695 

2696- :attr:`Event.RESOURCES <icalendar.cal.event.Event.RESOURCES>` 

2697- :attr:`Todo.RESOURCES <icalendar.cal.todo.Todo.RESOURCES>` 

2698 

2699Parameters: 

2700 resources(str | list[str] | None): 

2701 Either a single resource string, a list of resource strings, 

2702 or ``None`` to set the component's resources. 

2703 

2704Note: 

2705 When you assign a list to this property, the returned list 

2706 is the same object stored in the component. Modifying it (``append()``, 

2707 ``extend()``, ``remove()``, item assignment, or ``del``) changes what 

2708 the component stores. 

2709 

2710 However, if you assign a single string value to this property, the returned 

2711 list is a temporary copy, and changes to this list don't affect the component. 

2712 

2713Example: 

2714 Add resources to an event: 

2715 

2716 .. code-block:: pycon 

2717 

2718 >>> from icalendar import Event 

2719 >>> event = Event.new(resources=["EASEL", "PROJECTOR", "VCR"]) 

2720 >>> event.RESOURCES == ["EASEL", "PROJECTOR", "VCR"] 

2721 True 

2722""", 

2723) 

2724 

2725XML_property = multi_string_property( 

2726 "XML", 

2727 """This property embeds XML elements into a component. 

2728 

2729You can assign XML text, an :class:`~xml.etree.ElementTree.Element`, 

2730an :class:`~icalendar.parser.xcal.adapter.ElementAdapter`, 

2731a list of these, or ``None`` to this property. 

2732The values become :class:`~icalendar.prop.xml.vXml` values. 

2733A :class:`~icalendar.prop.text.vText` value is not converted, 

2734so it is written as text in xCal. 

2735XML with the value type BINARY is a :class:`~icalendar.prop.binary.vBinary`. 

2736Assigning ``None`` or an empty list removes all XML values, 

2737as does deleting the property. 

2738 

2739The XML property is defined in :rfc:`6321#section-4.2`. 

2740It can be specified multiple times in any component. 

2741Its value is an XML element in a namespace other than the iCalendar namespace. 

2742In xCal, the element is part of the properties of the component, 

2743see :rfc:`6321#section-4.1`. 

2744 

2745.. note:: 

2746 

2747 To change the values, assign a new list or use ``add()``. 

2748 Changing the returned list does not always change the component. 

2749 

2750Example: 

2751 Add XML to an event and get the XML elements: 

2752 

2753 .. code-block:: pycon 

2754 

2755 >>> from icalendar import Event 

2756 >>> event = Event() 

2757 >>> event.XML = '<a xmlns="http://example.com/ns">text</a>' 

2758 >>> print(event.to_ical().decode()) 

2759 BEGIN:VEVENT 

2760 XML:<a xmlns="http://example.com/ns">text</a> 

2761 END:VEVENT 

2762 >>> [xml.element.tag for xml in event.XML] 

2763 ['{http://example.com/ns}a'] 

2764""", 

2765) 

2766 

2767 

2768ATTACHMENTS_TYPE_SETTER: TypeAlias = ( 

2769 str | bytes | vUri | vBinary | list[str | bytes | vUri | vBinary] | None 

2770) 

2771 

2772 

2773def _normalize_attachment(value: str | bytes | vUri | vBinary) -> vUri | vBinary: 

2774 """Convert one attachment value.""" 

2775 if isinstance(value, (vUri, vBinary)): 

2776 return value 

2777 if isinstance(value, str): 

2778 return vUri(value) 

2779 if isinstance(value, bytes): 

2780 return vBinary(value) 

2781 raise TypeError( 

2782 f"Attachments must be str, bytes, vUri, or vBinary, not {type(value).__name__}." 

2783 ) 

2784 

2785 

2786def _get_attachments(self: Component) -> list[vUri | vBinary]: 

2787 """Get all the attachments""" 

2788 attachments = self.get("ATTACH", []) 

2789 if not isinstance(attachments, SEQUENCE_TYPES): 

2790 return [attachments] 

2791 return list(attachments) 

2792 

2793 

2794def _set_attachments(self: Component, value: ATTACHMENTS_TYPE_SETTER) -> None: 

2795 """Set attachments properties""" 

2796 if value is None: 

2797 _del_attachments(self) 

2798 return 

2799 if not isinstance(value, list): 

2800 value = [value] 

2801 attachments = [_normalize_attachment(attachment) for attachment in value] 

2802 _del_attachments(self) 

2803 for attachment in attachments: 

2804 self.add("ATTACH", attachment) 

2805 

2806 

2807def _del_attachments(self: Component) -> None: 

2808 """Delete all attachments""" 

2809 self.pop("ATTACH", None) 

2810 

2811 

2812attachments_property = property( 

2813 _get_attachments, 

2814 _set_attachments, 

2815 _del_attachments, 

2816 """This property defines the attachments for a component. 

2817 

2818Setting this property replaces all existing attachments. A :class:`str` 

2819is converted to :class:`~icalendar.prop.uri.vUri`, and :class:`bytes` is 

2820converted to :class:`~icalendar.prop.binary.vBinary`. Values that are 

2821already :class:`~icalendar.prop.uri.vUri` or 

2822:class:`~icalendar.prop.binary.vBinary` are stored unchanged, so their 

2823parameters are preserved. Setting ``None`` or an empty list removes all 

2824attachments, as does deleting the property. 

2825 

2826Parameters: 

2827 attachments(str | bytes | vUri | vBinary | list | None): 

2828 A single attachment, or a list of attachments to set. Accepts 

2829 :class:`str`, :class:`bytes`, :class:`~icalendar.prop.uri.vUri`, 

2830 and :class:`~icalendar.prop.binary.vBinary`, individually or 

2831 mixed together in a list. 

2832 

2833Example: 

2834 Attach a URI to an event, then replace it with a URI and inline 

2835 binary data together: 

2836 

2837 .. code-block:: pycon 

2838 

2839 >>> from icalendar import Event, vUri, vBinary 

2840 >>> event = Event() 

2841 >>> event.attachments 

2842 [] 

2843 >>> event.attachments = ["https://example.com/agenda.pdf"] 

2844 >>> print(event.to_ical().decode()) 

2845 BEGIN:VEVENT 

2846 ATTACH:https://example.com/agenda.pdf 

2847 END:VEVENT 

2848 >>> event.attachments = [ 

2849 ... vUri( 

2850 ... "https://example.com/agenda.pdf", 

2851 ... params={"FMTTYPE": "application/pdf"}, 

2852 ... ), 

2853 ... vBinary(b"image-data", params={"FMTTYPE": "image/png"},), 

2854 ... ] 

2855 >>> len(event.attachments) 

2856 2 

2857 

2858.. note:: 

2859 

2860 An alarm as an audio action must not contain more than one attachment. 

2861 

2862 List modifications do not modify the component. Methods such as 

2863 ``append()``, ``extend()``, and ``remove()``, as well as item 

2864 assignment, act on a copy. Assign the list back to the property, or 

2865 use :meth:`Component.add <icalendar.cal.component.Component.add>` 

2866 with a typed value instead. 

2867 

2868.. seealso:: 

2869 

2870 :rfc:`5545#section-3.8.1.1` for the definition of the ``ATTACH`` 

2871 property. 

2872""", 

2873) 

2874 

2875 

2876__all__ = [ 

2877 "ATTACHMENTS_TYPE_SETTER", 

2878 "ATTENDEE_TYPE_SETTER", 

2879 "CONCEPTS_TYPE_SETTER", 

2880 "LINKS_TYPE_SETTER", 

2881 "RECURRENCE_ID", 

2882 "RELATED_TO_TYPE_SETTER", 

2883 "PropertyListView", 

2884 "REQUEST_STATUS_property", 

2885 "RESOURCES_property", 

2886 "XML_property", 

2887 "attachments_property", 

2888 "attendees_property", 

2889 "busy_type_property", 

2890 "categories_property", 

2891 "class_property", 

2892 "color_property", 

2893 "comments_property", 

2894 "concepts_property", 

2895 "conferences_property", 

2896 "contacts_property", 

2897 "create_single_property", 

2898 "description_property", 

2899 "descriptions_property", 

2900 "duration_property", 

2901 "exdates_property", 

2902 "get_duration_property", 

2903 "get_end_property", 

2904 "get_start_end_duration_with_validation", 

2905 "get_start_property", 

2906 "images_property", 

2907 "links_property", 

2908 "location_property", 

2909 "multi_language_text_property", 

2910 "multi_string_property", 

2911 "organizer_property", 

2912 "priority_property", 

2913 "property_del_duration", 

2914 "property_doc_duration_template", 

2915 "property_get_duration", 

2916 "property_set_duration", 

2917 "rdates_property", 

2918 "refids_property", 

2919 "related_to_property", 

2920 "repeat_property", 

2921 "rfc_7953_dtend_property", 

2922 "rfc_7953_dtstart_property", 

2923 "rfc_7953_duration_property", 

2924 "rfc_7953_end_property", 

2925 "rrules_property", 

2926 "sequence_property", 

2927 "set_duration_with_locking", 

2928 "set_end_with_locking", 

2929 "set_start_with_locking", 

2930 "single_int_property", 

2931 "single_utc_property", 

2932 "source_property", 

2933 "status_property", 

2934 "summary_property", 

2935 "transparency_property", 

2936 "uid_property", 

2937 "url_property", 

2938]