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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

419 statements  

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

2 

3from __future__ import annotations 

4 

5import itertools 

6from datetime import date, datetime, timedelta 

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

8 

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

10from icalendar.error import IncompleteComponent, InvalidCalendar 

11from icalendar.parser_tools import SEQUENCE_TYPES 

12from icalendar.prop import ( 

13 vCalAddress, 

14 vCategory, 

15 vDDDTypes, 

16 vDuration, 

17 vRecur, 

18 vText, 

19) 

20from icalendar.prop.conference import Conference 

21from icalendar.prop.image import Image 

22from icalendar.timezone import tzp 

23from icalendar.tools import is_date 

24 

25if TYPE_CHECKING: 

26 from icalendar.cal import Component 

27 

28 

29def _get_rdates( 

30 self: Component, 

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

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

33 

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

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

36 

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

38 with and without timezone. 

39 

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

41 if the end is specified. 

42 

43 Value Type: 

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

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

46 

47 Property Parameters: 

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

49 zone identifier property parameters can be specified on this 

50 property. 

51 

52 Conformance: 

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

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

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

56 calendar component. 

57 

58 Description: 

59 This property can appear along with the "RRULE" 

60 property to define an aggregate set of repeating occurrences. 

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

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

63 the "RDATE" and "RRULE". 

64 

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

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

67 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

83 Duplicate instances are ignored. 

84 

85 Example: 

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

87 

88 .. code-block:: pycon 

89 

90 >>> from icalendar import Event 

91 >>> from datetime import datetime 

92 >>> event = Event() 

93 

94 # Add a list of recurrence dates 

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

96 >>> event.rdates 

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

98 

99 .. note:: 

100 

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

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

103 

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

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

106 

107 """ 

108 result = [] 

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

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

111 for dts in rdates.dts: 

112 rdate = dts.dt 

113 if isinstance(rdate, tuple): 

114 # we have a period as rdate 

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

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

117 else: 

118 result.append(rdate) 

119 else: 

120 # we have a date/datetime 

121 result.append((rdate, None)) 

122 return result 

123 

124 

125rdates_property = property(_get_rdates) 

126 

127 

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

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

130 

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

132 

133 Value Type: 

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

135 The value type can be set to DATE. 

136 

137 Property Parameters: 

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

139 zone identifier property parameters can be specified on this 

140 property. 

141 

142 Conformance: 

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

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

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

146 calendar component. 

147 

148 Description: 

149 The exception dates, if specified, are used in 

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

151 set of recurrence instances for a calendar component. The 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

167 Duplicate instances are ignored. 

168 

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

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

171 MUST still be maintained by the calendaring and scheduling system 

172 because the original "DTSTART" value has inherent usage 

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

174 

175 Example: 

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

177 

178 .. code-block:: pycon 

179 

180 >>> from icalendar import Event 

181 >>> from datetime import datetime 

182 >>> event = Event() 

183 

184 # Add a list of excluded dates 

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

186 >>> event.exdates 

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

188 

189 .. note:: 

190 

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

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

193 

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

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

196 

197 """ 

198 result = [] 

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

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

201 for dts in exdates.dts: 

202 exdate = dts.dt 

203 # we have a date/datetime 

204 result.append(exdate) 

205 return result 

206 

207 

208exdates_property = property(_get_exdates) 

209 

210 

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

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

213 

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

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

216 

217 Property Parameters: 

218 IANA and non-standard property parameters can 

219 be specified on this property. 

220 

221 Conformance: 

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

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

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

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

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

227 undefined. 

228 

229 Description: 

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

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

232 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

237 value SHOULD be synchronized with the recurrence rule, if 

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

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

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

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

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

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

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

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

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

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

248 Duplicate instances are ignored. 

249 

250 The "DTSTART" property specified within the iCalendar object 

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

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

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

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

255 same local time regardless of time zone changes. 

256 

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

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

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

260 duration of the recurring component is specified with the 

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

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

263 duration of each recurrence instance will depend on its specific 

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

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

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

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

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

269 type. 

270 

271 Examples: 

272 Daily for 10 occurrences: 

273 

274 .. code-block:: pycon 

275 

276 >>> from icalendar import Event 

277 >>> from datetime import datetime 

278 >>> from zoneinfo import ZoneInfo 

279 >>> event = Event() 

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

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

282 >>> print(event.to_ical()) 

283 BEGIN:VEVENT 

284 DTSTART;TZID=America/New_York:19970902T090000 

285 RRULE:FREQ=DAILY;COUNT=10 

286 END:VEVENT 

287 >>> event.rrules 

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

289 

290 Daily until December 24, 1997: 

291 

292 .. code-block:: pycon 

293 

294 >>> from icalendar import Event, vRecur 

295 >>> from datetime import datetime 

296 >>> from zoneinfo import ZoneInfo 

297 >>> event = Event() 

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

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

300 >>> print(event.to_ical()) 

301 BEGIN:VEVENT 

302 DTSTART;TZID=America/New_York:19970902T090000 

303 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

304 END:VEVENT 

305 >>> event.rrules 

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

307 

308 .. note:: 

309 

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

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

312 

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

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

315 

316 """ # noqa: E501 

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

318 if not isinstance(rrules, list): 

319 return [rrules] 

320 return rrules 

321 

322 

323rrules_property = property(_get_rrules) 

324 

325 

326def multi_language_text_property( 

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

328) -> property: 

329 """This creates a text property. 

330 

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

332 

333 Args: 

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

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

336 doc (str): The documentation string 

337 """ 

338 

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

340 """Get the property""" 

341 result = self.get(main_prop) 

342 if result is None and compatibility_prop is not None: 

343 result = self.get(compatibility_prop) 

344 if isinstance(result, list): 

345 for item in result: 

346 if "LANGUAGE" not in item.params: 

347 return item 

348 return result 

349 

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

351 """Set the property.""" 

352 fdel(self) 

353 if value is not None: 

354 self.add(main_prop, value) 

355 

356 def fdel(self: Component): 

357 """Delete the property.""" 

358 self.pop(main_prop, None) 

359 if compatibility_prop is not None: 

360 self.pop(compatibility_prop, None) 

361 

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

363 

364 

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

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

367 

368 Args: 

369 prop: The name of the property 

370 default: The default value 

371 doc: The documentation string 

372 """ 

373 

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

375 """Get the property""" 

376 try: 

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

378 except ValueError as e: 

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

380 

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

382 """Set the property.""" 

383 fdel(self) 

384 if value is not None: 

385 self.add(prop, value) 

386 

387 def fdel(self: Component): 

388 """Delete the property.""" 

389 self.pop(prop, None) 

390 

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

392 

393 

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

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

396 

397 Args: 

398 name: name of the property 

399 docs: documentation string 

400 """ 

401 docs = ( 

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

403 

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

405 """ 

406 + docs 

407 ) 

408 

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

410 """Get the value.""" 

411 if name not in self: 

412 return None 

413 dt = self.get(name) 

414 if isinstance(dt, vText): 

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

416 value = vDDDTypes.from_ical(dt) 

417 else: 

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

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

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

421 return tzp.localize_utc(value) 

422 

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

424 """Set the value""" 

425 if value is None: 

426 fdel(self) 

427 return 

428 if not isinstance(value, date): 

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

430 fdel(self) 

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

432 

433 def fdel(self: Component): 

434 """Delete the property.""" 

435 self.pop(name, None) 

436 

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

438 

439 

440def single_string_property( 

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

442) -> property: 

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

444 

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

446 """Get the value.""" 

447 result = self.get( 

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

449 ) 

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

451 return default 

452 if isinstance(result, list): 

453 return result[0] 

454 return result 

455 

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

457 """Set the value. 

458 

459 Setting the value to None will delete it. 

460 """ 

461 fdel(self) 

462 if value is not None: 

463 self.add(name, value) 

464 

465 def fdel(self: Component): 

466 """Delete the property.""" 

467 self.pop(name, None) 

468 if other_name is not None: 

469 self.pop(other_name, None) 

470 

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

472 

473 

474color_property = single_string_property( 

475 "COLOR", 

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

477 

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

479 

480 Property Parameters: 

481 IANA and non-standard property parameters can 

482 be specified on this property. 

483 

484 Conformance: 

485 This property can be specified once in an iCalendar 

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

487 

488 Description: 

489 This property specifies a color that clients MAY use 

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

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

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

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

494 

495 Example: 

496 ``"turquoise"``, ``"#ffffff"`` 

497 

498 .. code-block:: pycon 

499 

500 >>> from icalendar import Todo 

501 >>> todo = Todo() 

502 >>> todo.color = "green" 

503 >>> print(todo.to_ical()) 

504 BEGIN:VTODO 

505 COLOR:green 

506 END:VTODO 

507 """, 

508) 

509 

510sequence_property = single_int_property( 

511 "SEQUENCE", 

512 0, 

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

514 

515Value Type: 

516 INTEGER 

517 

518Property Parameters: 

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

520 

521Conformance: 

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

523 "VJOURNAL" calendar component. 

524 

525Description: 

526 When a calendar component is created, its sequence 

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

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

529 calendar component. 

530 

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

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

533 calendar component. 

534 

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

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

537 component to which the "Attendee" is referring. 

538 

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

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

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

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

543 requested. 

544 

545 Recurrence instances of a recurring component MAY have different 

546 sequence numbers. 

547 

548Examples: 

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

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

551 

552 .. code-block:: pycon 

553 

554 >>> from icalendar import Event 

555 >>> event = Event() 

556 >>> event.sequence 

557 0 

558 

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

560 component that has been revised 10 different times by the 

561 "Organizer": 

562 

563 .. code-block:: pycon 

564 

565 >>> from icalendar import Calendar 

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

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

568 >>> event.sequence 

569 10 

570 """, # noqa: E501 

571) 

572 

573 

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

575 """Get all the categories.""" 

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

577 if isinstance(categories, list): 

578 _set_categories( 

579 component, 

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

581 ) 

582 return _get_categories(component) 

583 if categories is None: 

584 categories = vCategory([]) 

585 component.add("CATEGORIES", categories) 

586 return categories.cats 

587 

588 

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

590 """Set the categories.""" 

591 if not cats and cats != []: 

592 _del_categories(component) 

593 return 

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

595 if isinstance(cats, list): 

596 cats.clear() 

597 cats.extend(categories.cats) 

598 categories.cats = cats 

599 

600 

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

602 """Delete the categories.""" 

603 component.pop("CATEGORIES", None) 

604 

605 

606categories_property = property( 

607 _get_categories, 

608 _set_categories, 

609 _del_categories, 

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

611 

612Property Parameters: 

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

614 property. 

615 

616Conformance: 

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

618 components. 

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

620 

621Description: 

622 This property is used to specify categories or subtypes 

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

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

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

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

627 of categories. 

628 

629Example: 

630 Below, we add the categories to an event: 

631 

632 .. code-block:: pycon 

633 

634 >>> from icalendar import Event 

635 >>> event = Event() 

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

637 >>> print(event.to_ical()) 

638 BEGIN:VEVENT 

639 CATEGORIES:Work,Meeting 

640 END:VEVENT 

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

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

643 True 

644 

645.. note:: 

646 

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

648""", 

649) 

650 

651 

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

653 """Get attendees.""" 

654 value = self.get("ATTENDEE") 

655 if value is None: 

656 value = [] 

657 self["ATTENDEE"] = value 

658 return value 

659 if isinstance(value, vCalAddress): 

660 return [value] 

661 return value 

662 

663 

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

665 """Set attendees.""" 

666 _del_attendees(self) 

667 if value is None: 

668 return 

669 if not isinstance(value, list): 

670 value = [value] 

671 self["ATTENDEE"] = value 

672 

673 

674def _del_attendees(self: Component): 

675 """Delete all attendees.""" 

676 self.pop("ATTENDEE", None) 

677 

678 

679attendees_property = property( 

680 _get_attendees, 

681 _set_attendees, 

682 _del_attendees, 

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

684 

685Conformance: 

686 This property MUST be specified in an iCalendar object 

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

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

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

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

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

692 iCalendar object that specifies only a time zone definition or 

693 that defines calendar components that are not group-scheduled 

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

695 

696Description: 

697 This property MUST only be specified within calendar 

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

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

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

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

702 type of iCalendar alarm. 

703 

704Examples: 

705 Add a new attendee to an existing event. 

706 

707 .. code-block:: pycon 

708 

709 >>> from icalendar import Event, vCalAddress 

710 >>> event = Event() 

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

712 >>> print(event.to_ical()) 

713 BEGIN:VEVENT 

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

715 END:VEVENT 

716 

717 Create an email alarm with several attendees: 

718 

719 >>> from icalendar import Alarm, vCalAddress 

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

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

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

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

724 >>> print(alarm.to_ical()) 

725 BEGIN:VALARM 

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

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

728 SUMMARY:Email alarm 

729 END:VALARM 

730""", 

731) 

732 

733uid_property = single_string_property( 

734 "UID", 

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

736 

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

738 

739Returns: 

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

741 

742Description: 

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

744 The generator of the identifier MUST guarantee that the identifier 

745 is unique. 

746 

747 This is the method for correlating scheduling messages with the 

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

749 The full range of calendar components specified by a recurrence 

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

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

752 property allows the reference to an individual instance within the 

753 recurrence set. 

754 

755 This property is an important method for group-scheduling 

756 applications to match requests with later replies, modifications, 

757 or deletion requests. Calendaring and scheduling applications 

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

759 calendar components to assure interoperability with other group- 

760 scheduling applications. This identifier is created by the 

761 calendar system that generates an iCalendar object. 

762 

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

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

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

766 

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

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

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

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

771 to identify the most recent version of a calendar. 

772 

773Conformance: 

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

775 and "VJOURNAL" calendar components. 

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

777 allow it to be defined in an iCalendar object. 

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

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

780 to refer uniquely to the "VALARM" component. 

781 

782 This property can be specified once only. 

783 

784Security: 

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

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

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

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

789 Universally Unique Identifier (UUID) values as defined in 

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

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

792 

793Compatibility: 

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

795 

796Examples: 

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

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

799 

800 Set the UID of a calendar: 

801 

802 .. code-block:: pycon 

803 

804 >>> from icalendar import Calendar 

805 >>> from uuid import uuid4 

806 >>> calendar = Calendar() 

807 >>> calendar.uid = uuid4() 

808 >>> print(calendar.to_ical()) 

809 BEGIN:VCALENDAR 

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

811 END:VCALENDAR 

812 

813""", 

814) 

815 

816summary_property = multi_language_text_property( 

817 "SUMMARY", 

818 None, 

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

820 

821Property Parameters: 

822 IANA, non-standard, alternate text 

823 representation, and language property parameters can be specified 

824 on this property. 

825 

826Conformance: 

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

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

829 

830Description: 

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

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

833 summary about the activity or journal entry. 

834 

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

836 capture the subject of an EMAIL category of alarm. 

837 

838Examples: 

839 The following is an example of this property: 

840 

841 .. code-block:: pycon 

842 

843 SUMMARY:Department Party 

844""", 

845) 

846 

847description_property = multi_language_text_property( 

848 "DESCRIPTION", 

849 None, 

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

851 

852Property Parameters: 

853 IANA, non-standard, alternate text 

854 representation, and language property parameters can be specified 

855 on this property. 

856 

857Conformance: 

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

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

860 specified multiple times only within a "VJOURNAL" calendar 

861 component. 

862 

863Description: 

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

865 capture lengthy textual descriptions associated with the activity. 

866 

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

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

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

870 

871Examples: 

872 The following is an example of this property with formatted 

873 line breaks in the property value: 

874 

875 .. code-block:: pycon 

876 

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

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

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

880 

881 """, # noqa: E501 

882) 

883 

884 

885def create_single_property( 

886 prop: str, 

887 value_attr: Optional[str], 

888 value_type: tuple[type], 

889 type_def: type, 

890 doc: str, 

891 vProp: type = vDDDTypes, # noqa: N803 

892): 

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

894 

895 :param prop: The name of the property. 

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

897 :param value_type: The type of the value. 

898 :param type_def: The type of the property. 

899 :param doc: The docstring of the property. 

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

901 """ 

902 

903 def p_get(self: Component): 

904 default = object() 

905 result = self.get(prop, default) 

906 if result is default: 

907 return None 

908 if isinstance(result, list): 

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

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

911 if not isinstance(value, value_type): 

912 raise InvalidCalendar( 

913 f"{prop} must be either a " 

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

915 f" not {value}." 

916 ) 

917 return value 

918 

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

920 if value is None: 

921 p_del(self) 

922 return 

923 if not isinstance(value, value_type): 

924 raise TypeError( 

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

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

927 ) 

928 self[prop] = vProp(value) 

929 if prop in self.exclusive: 

930 for other_prop in self.exclusive: 

931 if other_prop != prop: 

932 self.pop(other_prop, None) 

933 

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

935 type_def 

936 ] 

937 

938 def p_del(self: Component): 

939 self.pop(prop) 

940 

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

942 

943 {doc} 

944 

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

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

947 If the value is absent, we return None. 

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

949 """ 

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

951 

952 

953X_MOZ_SNOOZE_TIME_property = single_utc_property( 

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

955) 

956X_MOZ_LASTACK_property = single_utc_property( 

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

958) 

959 

960 

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

962 """Getter for property DURATION.""" 

963 default = object() 

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

965 if isinstance(duration, vDDDTypes): 

966 return duration.dt 

967 if isinstance(duration, vDuration): 

968 return duration.td 

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

970 raise InvalidCalendar( 

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

972 ) 

973 return None 

974 

975 

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

977 """Setter for property DURATION.""" 

978 if value is None: 

979 self.pop("duration", None) 

980 return 

981 if not isinstance(value, timedelta): 

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

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

984 self.pop("DTEND") 

985 self.pop("DUE") 

986 

987 

988def property_del_duration(self: Component): 

989 """Delete property DURATION.""" 

990 self.pop("DURATION") 

991 

992 

993property_doc_duration_template = """The DURATION property. 

994 

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

996start of the {component}. 

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

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

999of the event. 

1000 

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

1002Instead use the duration property (lower case). 

1003""" 

1004 

1005 

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

1007 """Return the duration property.""" 

1008 return property( 

1009 property_get_duration, 

1010 property_set_duration, 

1011 property_del_duration, 

1012 property_doc_duration_template.format(component=component), 

1013 ) 

1014 

1015 

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

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

1018 

1019 Examples: Journal.descriptions, Event.comments 

1020 """ 

1021 

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

1023 """Get the values.""" 

1024 descriptions = self.get(name) 

1025 if descriptions is None: 

1026 return [] 

1027 if not isinstance(descriptions, SEQUENCE_TYPES): 

1028 return [descriptions] 

1029 return descriptions 

1030 

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

1032 """Set the values.""" 

1033 fdel(self) 

1034 if values is None: 

1035 return 

1036 if isinstance(values, str): 

1037 self.add(name, values) 

1038 else: 

1039 for description in values: 

1040 self.add(name, description) 

1041 

1042 def fdel(self: Component): 

1043 """Delete the values.""" 

1044 self.pop(name) 

1045 

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

1047 

1048 

1049descriptions_property = multi_text_property( 

1050 "DESCRIPTION", 

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

1052 

1053Property Parameters: 

1054 IANA, non-standard, alternate text 

1055 representation, and language property parameters can be specified 

1056 on this property. 

1057 

1058Conformance: 

1059 The property can be 

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

1061 

1062Description: 

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

1064 capture one or more textual journal entries. 

1065 

1066Examples: 

1067 The following is an example of this property with formatted 

1068 line breaks in the property value: 

1069 

1070 .. code-block:: pycon 

1071 

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

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

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

1075 

1076""", # noqa: E501 

1077) 

1078 

1079comments_property = multi_text_property( 

1080 "COMMENT", 

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

1082 

1083Purpose: 

1084 This property specifies non-processing information intended 

1085 to provide a comment to the calendar user. 

1086 

1087Conformance: 

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

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

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

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

1092 "VAVAILABILITY" and "VAVAILABLE". 

1093 

1094Property Parameters: 

1095 IANA, non-standard, alternate text 

1096 representation, and language property parameters can be specified 

1097 on this property. 

1098 

1099""", 

1100) 

1101 

1102 

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

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

1105 

1106 Property Parameters: 

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

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

1109 specified on this property. 

1110 

1111 Conformance: 

1112 This property MUST be specified in an iCalendar object 

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

1114 MUST be specified in an iCalendar object that specifies the 

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

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

1117 zone definition or that defines calendar components that are not 

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

1119 user's calendar. 

1120 

1121 Description: 

1122 This property is specified within the "VEVENT", 

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

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

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

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

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

1128 the calendar that the published busy time came from. 

1129 

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

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

1132 specifying a pointer to the directory information associated with 

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

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

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

1136 "LANGUAGE" property parameter is specified, the identified 

1137 language applies to the "CN" parameter value. 

1138 """ 

1139 return self.get("ORGANIZER") 

1140 

1141 

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

1143 """Set the value.""" 

1144 _del_organizer(self) 

1145 if value is not None: 

1146 self.add("ORGANIZER", value) 

1147 

1148 

1149def _del_organizer(self: Component): 

1150 """Delete the value.""" 

1151 self.pop("ORGANIZER") 

1152 

1153 

1154organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1155 

1156 

1157def single_string_enum_property( 

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

1159) -> property: 

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

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

1162 

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

1164 """Get the value.""" 

1165 value = prop.fget(self) 

1166 if value == default: 

1167 return default 

1168 return enum(str(value)) 

1169 

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

1171 """Set the value.""" 

1172 if value == "": 

1173 value = None 

1174 prop.fset(self, value) 

1175 

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

1177 

1178 

1179busy_type_property = single_string_enum_property( 

1180 "BUSYTYPE", 

1181 BUSYTYPE, 

1182 BUSYTYPE.BUSY_UNAVAILABLE, 

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

1184 

1185Returns: 

1186 :class:`icalendar.enums.BUSYTYPE` 

1187 

1188Description: 

1189 This property is used to specify the default busy time 

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

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

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

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

1194 UNAVAILABLE". 

1195""", 

1196) 

1197 

1198priority_property = single_int_property( 

1199 "PRIORITY", 

1200 0, 

1201 """ 

1202 

1203Conformance: 

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

1205 according to :rfc:`5545`. 

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

1207 

1208Description: 

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

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

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

1212 priority. Subsequent numbers specify a decreasing ordinal 

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

1214 

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

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

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

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

1219 is "LOW" priority. 

1220 

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

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

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

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

1225 9 specifies "C3". 

1226 

1227 Other integer values are reserved for future use. 

1228 

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

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

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

1232 

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

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

1235 multiple action items for a given time period. 

1236""", 

1237) 

1238 

1239class_property = single_string_enum_property( 

1240 "CLASS", 

1241 CLASS, 

1242 CLASS.PUBLIC, 

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

1244 

1245Returns: 

1246 :class:`icalendar.enums.CLASS` 

1247 

1248Description: 

1249 An access classification is only one component of the 

1250 general security system within a calendar application. It 

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

1252 calendar owner intends for information within an individual 

1253 calendar entry. The access classification of an individual 

1254 iCalendar component is useful when measured along with the other 

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

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

1257 Hence, the semantics of the individual access classifications 

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

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

1260 memo, these access classifications cannot serve as an enforcement 

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

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

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

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

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

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

1267""", 

1268) 

1269 

1270transparency_property = single_string_enum_property( 

1271 "TRANSP", 

1272 TRANSP, 

1273 TRANSP.OPAQUE, 

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

1275 

1276Returns: 

1277 :class:`icalendar.enums.TRANSP` 

1278 

1279Description: 

1280 Time Transparency is the characteristic of an event 

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

1282 Events that consume actual time for the individual or resource 

1283 associated with the calendar SHOULD be recorded as OPAQUE, 

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

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

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

1287 busy time searches. 

1288""", 

1289) 

1290status_property = single_string_enum_property( 

1291 "STATUS", 

1292 STATUS, 

1293 "", 

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

1295 

1296Returns: 

1297 :class:`icalendar.enums.STATUS` 

1298 

1299The default value is ``""``. 

1300 

1301Description: 

1302 In a group-scheduled calendar component, the property 

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

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

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

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

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

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

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

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

1311 cancelled or removed. 

1312""", 

1313) 

1314 

1315url_property = single_string_property( 

1316 "URL", 

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

1318 

1319Description: 

1320 This property may be used in a calendar component to 

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

1322 information associated with the calendar component can be found. 

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

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

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

1326 specified, they MUST point to the same resource. 

1327 

1328Conformance: 

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

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

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

1332 

1333Example: 

1334 The following is an example of this property: 

1335 

1336 .. code-block:: text 

1337 

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

1339 

1340""", 

1341) 

1342 

1343source_property = single_string_property( 

1344 "SOURCE", 

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

1346 

1347Description: 

1348 This property identifies a location where a client can 

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

1350 specified "REFRESH-INTERVAL" value when periodically retrieving 

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

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

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

1354 

1355Conformance: 

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

1357 

1358Example: 

1359 The following is an example of this property: 

1360 

1361 .. code-block:: text 

1362 

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

1364 

1365""", 

1366) 

1367 

1368location_property = multi_language_text_property( 

1369 "LOCATION", 

1370 None, 

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

1372 

1373Property Parameters: 

1374 IANA, non-standard, alternate text 

1375 representation, and language property parameters can be specified 

1376 on this property. 

1377 

1378Conformance: 

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

1380 calendar component. 

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

1382 

1383Description: 

1384 Specific venues such as conference or meeting rooms may 

1385 be explicitly specified using this property. An alternate 

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

1387 directory information with more structured specification of the 

1388 location. For example, the alternate representation may specify 

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

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

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

1392 

1393""", 

1394) 

1395 

1396contacts_property = multi_text_property( 

1397 "CONTACT", 

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

1399 

1400Purpose: 

1401 This property is used to represent contact information or 

1402 alternately a reference to contact information associated with the 

1403 calendar component. 

1404 

1405Property Parameters: 

1406 IANA, non-standard, alternate text 

1407 representation, and language property parameters can be specified 

1408 on this property. 

1409 

1410Conformance: 

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

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

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

1414 amd "VAVAILABLE" calendar component. 

1415 

1416Description: 

1417 The property value consists of textual contact 

1418 information. An alternative representation for the property value 

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

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

1421 information. 

1422 

1423Example: 

1424 The following is an example of this property referencing 

1425 textual contact information: 

1426 

1427 .. code-block:: text 

1428 

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

1430 

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

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

1433 contact information: 

1434 

1435 .. code-block:: text 

1436 

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

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

1439 +1-919-555-1234 

1440 

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

1442 representation of a MIME body part containing the contact 

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

1444 directory media type :rfc:`2425`: 

1445 

1446 .. code-block:: text 

1447 

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

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

1450 

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

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

1453 information: 

1454 

1455 .. code-block:: text 

1456 

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

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

1459""", 

1460) 

1461 

1462 

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

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

1465 

1466 return single_utc_property(name, docs) 

1467 

1468 

1469rfc_7953_dtstart_property = timezone_datetime_property( 

1470 "DTSTART", 

1471 """Start of the component. 

1472 

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

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

1475 

1476 Description: 

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

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

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

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

1481 

1482 """, 

1483) 

1484 

1485rfc_7953_dtend_property = timezone_datetime_property( 

1486 "DTEND", 

1487 """Start of the component. 

1488 

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

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

1491 

1492 Description: 

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

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

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

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

1497 """, 

1498) 

1499 

1500 

1501@property 

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

1503 """Compute the duration of this component. 

1504 

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

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

1507 :attr:`DTEND`/:attr:`DURATION`. 

1508 

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

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

1511 """ 

1512 duration = self.DURATION 

1513 if duration: 

1514 return duration 

1515 end = self.DTEND 

1516 if end is None: 

1517 return None 

1518 start = self.DTSTART 

1519 if start is None: 

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

1521 return end - start 

1522 

1523 

1524@property 

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

1526 """Compute the duration of this component. 

1527 

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

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

1530 :attr:`DTEND`/:attr:`DURATION`. 

1531 

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

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

1534 """ 

1535 duration = self.DURATION 

1536 if duration: 

1537 start = self.DTSTART 

1538 if start is None: 

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

1540 return start + duration 

1541 end = self.DTEND 

1542 if end is None: 

1543 return None 

1544 return end 

1545 

1546 

1547@rfc_7953_end_property.setter 

1548def rfc_7953_end_property(self, value: datetime): 

1549 self.DTEND = value 

1550 

1551 

1552@rfc_7953_end_property.deleter 

1553def rfc_7953_end_property(self): 

1554 del self.DTEND 

1555 

1556 

1557def get_start_end_duration_with_validation( 

1558 component: Component, 

1559 start_property: str, 

1560 end_property: str, 

1561 component_name: str, 

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

1563 """ 

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

1565 

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

1567 for ``Event`` and ``Todo`` components. 

1568 

1569 Args: 

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

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

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

1573 ``DUE`` for ``Todo``. 

1574 component_name: The component name for error messages, 

1575 either ``VEVENT`` or ``VTODO``. 

1576 

1577 Returns: 

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

1579 

1580 Raises: 

1581 InvalidCalendar: If the component violates RFC 5545 constraints. 

1582 

1583 """ 

1584 start = getattr(component, start_property, None) 

1585 end = getattr(component, end_property, None) 

1586 duration = component.DURATION 

1587 

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

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

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

1591 msg = ( 

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

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

1594 ) 

1595 raise InvalidCalendar(msg) 

1596 

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

1598 if ( 

1599 start is not None 

1600 and is_date(start) 

1601 and duration is not None 

1602 and duration.seconds != 0 

1603 ): 

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

1605 raise InvalidCalendar(msg) 

1606 

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

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

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

1610 msg = ( 

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

1612 ) 

1613 raise InvalidCalendar(msg) 

1614 

1615 return start, end, duration 

1616 

1617 

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

1619 """ 

1620 Get the start property with validation. 

1621 

1622 Args: 

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

1624 

1625 Returns: 

1626 The ``DTSTART`` value. 

1627 

1628 Raises: 

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

1630 

1631 """ 

1632 # Trigger validation by calling _get_start_end_duration 

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

1634 if start is None: 

1635 msg = "No DTSTART given." 

1636 raise IncompleteComponent(msg) 

1637 return start 

1638 

1639 

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

1641 """ 

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

1643 

1644 Args: 

1645 component: The component to get end from 

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

1647 ``DUE`` for ``Todo``. 

1648 

1649 Returns: 

1650 The computed end value. 

1651 

1652 Raises: 

1653 IncompleteComponent: If the provided information is incomplete 

1654 to compute the end property. 

1655 

1656 """ 

1657 # Trigger validation by calling _get_start_end_duration 

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

1659 

1660 if end is None and duration is None: 

1661 if start is None: 

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

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

1664 raise IncompleteComponent(msg) 

1665 

1666 # Default behavior differs for Event vs Todo: 

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

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

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

1670 return start + timedelta(days=1) 

1671 return start 

1672 

1673 if duration is not None: 

1674 if start is not None: 

1675 return start + duration 

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

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

1678 raise IncompleteComponent(msg) 

1679 

1680 return end 

1681 

1682 

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

1684 """ 

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

1686 

1687 Args: 

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

1689 

1690 Returns: 

1691 The duration as a timedelta. 

1692 

1693 """ 

1694 # First check if DURATION property is explicitly set 

1695 if "DURATION" in component: 

1696 return component["DURATION"].dt 

1697 

1698 # Fall back to calculated duration from start and end 

1699 return component.end - component.start 

1700 

1701 

1702def set_duration_with_locking( 

1703 component: Component, 

1704 duration: timedelta | None, 

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

1706 end_property: str, 

1707) -> None: 

1708 """ 

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

1710 

1711 Args: 

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

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

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

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

1716 ``DUE`` for ``Todo``. 

1717 

1718 """ 

1719 # Convert to DURATION property if duration is None 

1720 if duration is None: 

1721 if "DURATION" in component: 

1722 return # Already has DURATION property 

1723 current_duration = component.duration 

1724 component.DURATION = current_duration 

1725 return 

1726 

1727 if not isinstance(duration, timedelta): 

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

1729 raise TypeError(msg) 

1730 

1731 # Validate date/duration compatibility 

1732 start = component.DTSTART 

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

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

1735 raise InvalidCalendar(msg) 

1736 

1737 if locked == "start": 

1738 # Keep start locked, adjust end 

1739 if start is None: 

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

1741 raise IncompleteComponent(msg) 

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

1743 component.DURATION = duration 

1744 elif locked == "end": 

1745 # Keep end locked, adjust start 

1746 current_end = component.end 

1747 component.DTSTART = current_end - duration 

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

1749 component.DURATION = duration 

1750 else: 

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

1752 raise ValueError(msg) 

1753 

1754 

1755def set_start_with_locking( 

1756 component: Component, 

1757 start: date | datetime, 

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

1759 end_property: str, 

1760) -> None: 

1761 """ 

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

1763 

1764 Args: 

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

1766 start: The start time to set. 

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

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

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

1770 ``DUE`` for ``Todo``. 

1771 

1772 """ 

1773 if locked is None: 

1774 # Auto-detect based on existing properties 

1775 if "DURATION" in component: 

1776 locked = "duration" 

1777 elif end_property in component: 

1778 locked = "end" 

1779 else: 

1780 # Default to duration if no existing properties 

1781 locked = "duration" 

1782 

1783 if locked == "duration": 

1784 # Keep duration locked, adjust end 

1785 current_duration = ( 

1786 component.duration 

1787 if "DURATION" in component or end_property in component 

1788 else None 

1789 ) 

1790 component.DTSTART = start 

1791 if current_duration is not None: 

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

1793 component.DURATION = current_duration 

1794 elif locked == "end": 

1795 # Keep end locked, adjust duration 

1796 current_end = component.end 

1797 component.DTSTART = start 

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

1799 setattr(component, end_property, current_end) 

1800 else: 

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

1802 raise ValueError(msg) 

1803 

1804 

1805def set_end_with_locking( 

1806 component: Component, 

1807 end: date | datetime, 

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

1809 end_property: str, 

1810) -> None: 

1811 """ 

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

1813 

1814 Args: 

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

1816 end: The end time to set. 

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

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

1819 for ``Todo``. 

1820 

1821 """ 

1822 if locked == "start": 

1823 # Keep start locked, adjust duration 

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

1825 setattr(component, end_property, end) 

1826 elif locked == "duration": 

1827 # Keep duration locked, adjust start 

1828 current_duration = component.duration 

1829 component.DTSTART = end - current_duration 

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

1831 component.DURATION = current_duration 

1832 else: 

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

1834 raise ValueError(msg) 

1835 

1836 

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

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

1839 

1840 Description: 

1841 This property specifies an image for an iCalendar 

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

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

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

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

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

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

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

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

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

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

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

1853 on the image in the calendar user agent. 

1854 

1855 Conformance: 

1856 This property can be specified multiple times in an 

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

1858 components. 

1859 

1860 .. note:: 

1861 

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

1863 please open an issue or a pull request. 

1864 """ 

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

1866 if not isinstance(images, SEQUENCE_TYPES): 

1867 images = [images] 

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

1869 

1870 

1871images_property = property(_get_images) 

1872 

1873 

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

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

1876 

1877 Purpose: 

1878 This property specifies information for accessing a conferencing system. 

1879 

1880 Conformance: 

1881 This property can be specified multiple times in a 

1882 "VEVENT" or "VTODO" calendar component. 

1883 

1884 Description: 

1885 This property specifies information for accessing a 

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

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

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

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

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

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

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

1893 containing a text description can be used. 

1894 

1895 A conference system can be a bidirectional communication channel 

1896 or a uni-directional "broadcast feed". 

1897 

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

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

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

1901 multiple properties. 

1902 

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

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

1905 codes for the moderator and attendee of a teleconference system 

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

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

1908 which. 

1909 

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

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

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

1913 

1914 Example: 

1915 The following are examples of this property: 

1916 

1917 .. code-block:: text 

1918 

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

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

1921 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

1923 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

1925 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

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

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

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

1929 

1930 Get all conferences: 

1931 

1932 .. code-block:: pycon 

1933 

1934 >>> from icalendar import Event 

1935 >>> event = Event() 

1936 >>> event.conferences 

1937 [] 

1938 

1939 Set a conference: 

1940 

1941 .. code-block:: pycon 

1942 

1943 >>> from icalendar import Event, Conference 

1944 >>> event = Event() 

1945 >>> event.conferences = [ 

1946 ... Conference( 

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

1948 ... feature="PHONE,MODERATOR", 

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

1950 ... language="EN", 

1951 ... ) 

1952 ... ] 

1953 >>> print(event.to_ical()) 

1954 BEGIN:VEVENT 

1955 CONFERENCE;FEATURE="PHONE,MODERATOR";LABEL=Moderator dial-in;LANGUAGE=EN:t 

1956 el:+1-412-555-0123,,,654321 

1957 END:VEVENT 

1958 

1959 """ 

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

1961 if not isinstance(conferences, SEQUENCE_TYPES): 

1962 conferences = [conferences] 

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

1964 

1965 

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

1967 """Set the conferences.""" 

1968 _del_conferences(self) 

1969 for conference in conferences or []: 

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

1971 

1972 

1973def _del_conferences(self: Component): 

1974 """Delete all conferences.""" 

1975 self.pop("CONFERENCE") 

1976 

1977 

1978conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

1979 

1980__all__ = [ 

1981 "attendees_property", 

1982 "busy_type_property", 

1983 "categories_property", 

1984 "class_property", 

1985 "color_property", 

1986 "comments_property", 

1987 "conferences_property", 

1988 "contacts_property", 

1989 "create_single_property", 

1990 "description_property", 

1991 "descriptions_property", 

1992 "duration_property", 

1993 "exdates_property", 

1994 "get_duration_property", 

1995 "get_end_property", 

1996 "get_start_end_duration_with_validation", 

1997 "get_start_property", 

1998 "images_property", 

1999 "location_property", 

2000 "multi_language_text_property", 

2001 "organizer_property", 

2002 "priority_property", 

2003 "property_del_duration", 

2004 "property_doc_duration_template", 

2005 "property_get_duration", 

2006 "property_set_duration", 

2007 "rdates_property", 

2008 "rfc_7953_dtend_property", 

2009 "rfc_7953_dtstart_property", 

2010 "rfc_7953_duration_property", 

2011 "rfc_7953_end_property", 

2012 "rrules_property", 

2013 "sequence_property", 

2014 "set_duration_with_locking", 

2015 "set_end_with_locking", 

2016 "set_start_with_locking", 

2017 "single_int_property", 

2018 "single_utc_property", 

2019 "source_property", 

2020 "status_property", 

2021 "summary_property", 

2022 "transparency_property", 

2023 "uid_property", 

2024 "url_property", 

2025]