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 :ref:`Related projects`. 

105 

106 """ 

107 result = [] 

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

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

110 for dts in rdates.dts: 

111 rdate = dts.dt 

112 if isinstance(rdate, tuple): 

113 # we have a period as rdate 

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

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

116 else: 

117 result.append(rdate) 

118 else: 

119 # we have a date/datetime 

120 result.append((rdate, None)) 

121 return result 

122 

123 

124rdates_property = property(_get_rdates) 

125 

126 

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

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

129 

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

131 

132 Value Type: 

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

134 The value type can be set to DATE. 

135 

136 Property Parameters: 

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

138 zone identifier property parameters can be specified on this 

139 property. 

140 

141 Conformance: 

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

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

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

145 calendar component. 

146 

147 Description: 

148 The exception dates, if specified, are used in 

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

150 set of recurrence instances for a calendar component. The 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

166 Duplicate instances are ignored. 

167 

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

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

170 MUST still be maintained by the calendaring and scheduling system 

171 because the original "DTSTART" value has inherent usage 

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

173 

174 Example: 

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

176 

177 .. code-block:: pycon 

178 

179 >>> from icalendar import Event 

180 >>> from datetime import datetime 

181 >>> event = Event() 

182 

183 # Add a list of excluded dates 

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

185 >>> event.exdates 

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

187 

188 .. note:: 

189 

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

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

192 

193 If you want to compute recurrences, have a look at :ref:`Related projects`. 

194 

195 """ 

196 result = [] 

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

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

199 for dts in exdates.dts: 

200 exdate = dts.dt 

201 # we have a date/datetime 

202 result.append(exdate) 

203 return result 

204 

205 

206exdates_property = property(_get_exdates) 

207 

208 

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

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

211 

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

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

214 

215 Property Parameters: 

216 IANA and non-standard property parameters can 

217 be specified on this property. 

218 

219 Conformance: 

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

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

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

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

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

225 undefined. 

226 

227 Description: 

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

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

230 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

235 value SHOULD be synchronized with the recurrence rule, if 

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

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

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

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

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

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

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

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

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

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

246 Duplicate instances are ignored. 

247 

248 The "DTSTART" property specified within the iCalendar object 

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

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

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

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

253 same local time regardless of time zone changes. 

254 

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

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

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

258 duration of the recurring component is specified with the 

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

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

261 duration of each recurrence instance will depend on its specific 

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

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

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

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

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

267 type. 

268 

269 Examples: 

270 Daily for 10 occurrences: 

271 

272 .. code-block:: pycon 

273 

274 >>> from icalendar import Event 

275 >>> from datetime import datetime 

276 >>> from zoneinfo import ZoneInfo 

277 >>> event = Event() 

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

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

280 >>> print(event.to_ical()) 

281 BEGIN:VEVENT 

282 DTSTART;TZID=America/New_York:19970902T090000 

283 RRULE:FREQ=DAILY;COUNT=10 

284 END:VEVENT 

285 >>> event.rrules 

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

287 

288 Daily until December 24, 1997: 

289 

290 .. code-block:: pycon 

291 

292 >>> from icalendar import Event, vRecur 

293 >>> from datetime import datetime 

294 >>> from zoneinfo import ZoneInfo 

295 >>> event = Event() 

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

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

298 >>> print(event.to_ical()) 

299 BEGIN:VEVENT 

300 DTSTART;TZID=America/New_York:19970902T090000 

301 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

302 END:VEVENT 

303 >>> event.rrules 

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

305 

306 .. note:: 

307 

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

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

310 

311 If you want to compute recurrences, have a look at :ref:`Related projects`. 

312 

313 """ # noqa: E501 

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

315 if not isinstance(rrules, list): 

316 return [rrules] 

317 return rrules 

318 

319 

320rrules_property = property(_get_rrules) 

321 

322 

323def multi_language_text_property( 

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

325) -> property: 

326 """This creates a text property. 

327 

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

329 

330 Args: 

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

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

333 doc (str): The documentation string 

334 """ 

335 

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

337 """Get the property""" 

338 result = self.get(main_prop) 

339 if result is None and compatibility_prop is not None: 

340 result = self.get(compatibility_prop) 

341 if isinstance(result, list): 

342 for item in result: 

343 if "LANGUAGE" not in item.params: 

344 return item 

345 return result 

346 

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

348 """Set the property.""" 

349 fdel(self) 

350 if value is not None: 

351 self.add(main_prop, value) 

352 

353 def fdel(self: Component): 

354 """Delete the property.""" 

355 self.pop(main_prop, None) 

356 if compatibility_prop is not None: 

357 self.pop(compatibility_prop, None) 

358 

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

360 

361 

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

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

364 

365 Args: 

366 prop: The name of the property 

367 default: The default value 

368 doc: The documentation string 

369 """ 

370 

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

372 """Get the property""" 

373 try: 

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

375 except ValueError as e: 

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

377 

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

379 """Set the property.""" 

380 fdel(self) 

381 if value is not None: 

382 self.add(prop, value) 

383 

384 def fdel(self: Component): 

385 """Delete the property.""" 

386 self.pop(prop, None) 

387 

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

389 

390 

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

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

393 

394 Args: 

395 name: name of the property 

396 docs: documentation string 

397 """ 

398 docs = ( 

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

400 

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

402 """ 

403 + docs 

404 ) 

405 

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

407 """Get the value.""" 

408 if name not in self: 

409 return None 

410 dt = self.get(name) 

411 if isinstance(dt, vText): 

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

413 value = vDDDTypes.from_ical(dt) 

414 else: 

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

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

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

418 return tzp.localize_utc(value) 

419 

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

421 """Set the value""" 

422 if value is None: 

423 fdel(self) 

424 return 

425 if not isinstance(value, date): 

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

427 fdel(self) 

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

429 

430 def fdel(self: Component): 

431 """Delete the property.""" 

432 self.pop(name, None) 

433 

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

435 

436 

437def single_string_property( 

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

439) -> property: 

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

441 

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

443 """Get the value.""" 

444 result = self.get( 

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

446 ) 

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

448 return default 

449 if isinstance(result, list): 

450 return result[0] 

451 return result 

452 

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

454 """Set the value. 

455 

456 Setting the value to None will delete it. 

457 """ 

458 fdel(self) 

459 if value is not None: 

460 self.add(name, value) 

461 

462 def fdel(self: Component): 

463 """Delete the property.""" 

464 self.pop(name, None) 

465 if other_name is not None: 

466 self.pop(other_name, None) 

467 

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

469 

470 

471color_property = single_string_property( 

472 "COLOR", 

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

474 

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

476 

477 Property Parameters: 

478 IANA and non-standard property parameters can 

479 be specified on this property. 

480 

481 Conformance: 

482 This property can be specified once in an iCalendar 

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

484 

485 Description: 

486 This property specifies a color that clients MAY use 

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

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

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

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

491 

492 Example: 

493 ``"turquoise"``, ``"#ffffff"`` 

494 

495 .. code-block:: pycon 

496 

497 >>> from icalendar import Todo 

498 >>> todo = Todo() 

499 >>> todo.color = "green" 

500 >>> print(todo.to_ical()) 

501 BEGIN:VTODO 

502 COLOR:green 

503 END:VTODO 

504 """, 

505) 

506 

507sequence_property = single_int_property( 

508 "SEQUENCE", 

509 0, 

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

511 

512Value Type: 

513 INTEGER 

514 

515Property Parameters: 

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

517 

518Conformance: 

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

520 "VJOURNAL" calendar component. 

521 

522Description: 

523 When a calendar component is created, its sequence 

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

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

526 calendar component. 

527 

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

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

530 calendar component. 

531 

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

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

534 component to which the "Attendee" is referring. 

535 

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

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

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

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

540 requested. 

541 

542 Recurrence instances of a recurring component MAY have different 

543 sequence numbers. 

544 

545Examples: 

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

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

548 

549 .. code-block:: pycon 

550 

551 >>> from icalendar import Event 

552 >>> event = Event() 

553 >>> event.sequence 

554 0 

555 

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

557 component that has been revised 10 different times by the 

558 "Organizer": 

559 

560 .. code-block:: pycon 

561 

562 >>> from icalendar import Calendar 

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

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

565 >>> event.sequence 

566 10 

567 """, # noqa: E501 

568) 

569 

570 

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

572 """Get all the categories.""" 

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

574 if isinstance(categories, list): 

575 _set_categories( 

576 component, 

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

578 ) 

579 return _get_categories(component) 

580 if categories is None: 

581 categories = vCategory([]) 

582 component.add("CATEGORIES", categories) 

583 return categories.cats 

584 

585 

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

587 """Set the categories.""" 

588 if not cats and cats != []: 

589 _del_categories(component) 

590 return 

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

592 if isinstance(cats, list): 

593 cats.clear() 

594 cats.extend(categories.cats) 

595 categories.cats = cats 

596 

597 

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

599 """Delete the categories.""" 

600 component.pop("CATEGORIES", None) 

601 

602 

603categories_property = property( 

604 _get_categories, 

605 _set_categories, 

606 _del_categories, 

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

608 

609Property Parameters: 

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

611 property. 

612 

613Conformance: 

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

615 components. 

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

617 

618Description: 

619 This property is used to specify categories or subtypes 

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

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

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

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

624 of categories. 

625 

626Example: 

627 Below, we add the categories to an event: 

628 

629 .. code-block:: pycon 

630 

631 >>> from icalendar import Event 

632 >>> event = Event() 

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

634 >>> print(event.to_ical()) 

635 BEGIN:VEVENT 

636 CATEGORIES:Work,Meeting 

637 END:VEVENT 

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

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

640 True 

641 

642.. note:: 

643 

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

645""", 

646) 

647 

648 

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

650 """Get attendees.""" 

651 value = self.get("ATTENDEE") 

652 if value is None: 

653 value = [] 

654 self["ATTENDEE"] = value 

655 return value 

656 if isinstance(value, vCalAddress): 

657 return [value] 

658 return value 

659 

660 

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

662 """Set attendees.""" 

663 _del_attendees(self) 

664 if value is None: 

665 return 

666 if not isinstance(value, list): 

667 value = [value] 

668 self["ATTENDEE"] = value 

669 

670 

671def _del_attendees(self: Component): 

672 """Delete all attendees.""" 

673 self.pop("ATTENDEE", None) 

674 

675 

676attendees_property = property( 

677 _get_attendees, 

678 _set_attendees, 

679 _del_attendees, 

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

681 

682Conformance: 

683 This property MUST be specified in an iCalendar object 

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

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

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

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

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

689 iCalendar object that specifies only a time zone definition or 

690 that defines calendar components that are not group-scheduled 

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

692 

693Description: 

694 This property MUST only be specified within calendar 

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

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

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

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

699 type of iCalendar alarm. 

700 

701Examples: 

702 Add a new attendee to an existing event. 

703 

704 .. code-block:: pycon 

705 

706 >>> from icalendar import Event, vCalAddress 

707 >>> event = Event() 

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

709 >>> print(event.to_ical()) 

710 BEGIN:VEVENT 

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

712 END:VEVENT 

713 

714 Create an email alarm with several attendees: 

715 

716 >>> from icalendar import Alarm, vCalAddress 

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

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

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

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

721 >>> print(alarm.to_ical()) 

722 BEGIN:VALARM 

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

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

725 SUMMARY:Email alarm 

726 END:VALARM 

727""", 

728) 

729 

730uid_property = single_string_property( 

731 "UID", 

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

733 

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

735 

736Returns: 

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

738 

739Description: 

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

741 The generator of the identifier MUST guarantee that the identifier 

742 is unique. 

743 

744 This is the method for correlating scheduling messages with the 

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

746 The full range of calendar components specified by a recurrence 

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

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

749 property allows the reference to an individual instance within the 

750 recurrence set. 

751 

752 This property is an important method for group-scheduling 

753 applications to match requests with later replies, modifications, 

754 or deletion requests. Calendaring and scheduling applications 

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

756 calendar components to assure interoperability with other group- 

757 scheduling applications. This identifier is created by the 

758 calendar system that generates an iCalendar object. 

759 

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

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

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

763 

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

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

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

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

768 to identify the most recent version of a calendar. 

769 

770Conformance: 

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

772 and "VJOURNAL" calendar components. 

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

774 allow it to be defined in an iCalendar object. 

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

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

777 to refer uniquely to the "VALARM" component. 

778 

779 This property can be specified once only. 

780 

781Security: 

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

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

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

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

786 Universally Unique Identifier (UUID) values as defined in 

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

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

789 

790Compatibility: 

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

792 

793Examples: 

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

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

796 

797 Set the UID of a calendar: 

798 

799 .. code-block:: pycon 

800 

801 >>> from icalendar import Calendar 

802 >>> from uuid import uuid4 

803 >>> calendar = Calendar() 

804 >>> calendar.uid = uuid4() 

805 >>> print(calendar.to_ical()) 

806 BEGIN:VCALENDAR 

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

808 END:VCALENDAR 

809 

810""", 

811) 

812 

813summary_property = multi_language_text_property( 

814 "SUMMARY", 

815 None, 

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

817 

818Property Parameters: 

819 IANA, non-standard, alternate text 

820 representation, and language property parameters can be specified 

821 on this property. 

822 

823Conformance: 

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

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

826 

827Description: 

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

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

830 summary about the activity or journal entry. 

831 

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

833 capture the subject of an EMAIL category of alarm. 

834 

835Examples: 

836 The following is an example of this property: 

837 

838 .. code-block:: pycon 

839 

840 SUMMARY:Department Party 

841""", 

842) 

843 

844description_property = multi_language_text_property( 

845 "DESCRIPTION", 

846 None, 

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

848 

849Property Parameters: 

850 IANA, non-standard, alternate text 

851 representation, and language property parameters can be specified 

852 on this property. 

853 

854Conformance: 

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

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

857 specified multiple times only within a "VJOURNAL" calendar 

858 component. 

859 

860Description: 

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

862 capture lengthy textual descriptions associated with the activity. 

863 

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

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

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

867 

868Examples: 

869 The following is an example of this property with formatted 

870 line breaks in the property value: 

871 

872 .. code-block:: pycon 

873 

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

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

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

877 

878 """, # noqa: E501 

879) 

880 

881 

882def create_single_property( 

883 prop: str, 

884 value_attr: Optional[str], 

885 value_type: tuple[type], 

886 type_def: type, 

887 doc: str, 

888 vProp: type = vDDDTypes, # noqa: N803 

889): 

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

891 

892 :param prop: The name of the property. 

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

894 :param value_type: The type of the value. 

895 :param type_def: The type of the property. 

896 :param doc: The docstring of the property. 

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

898 """ 

899 

900 def p_get(self: Component): 

901 default = object() 

902 result = self.get(prop, default) 

903 if result is default: 

904 return None 

905 if isinstance(result, list): 

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

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

908 if not isinstance(value, value_type): 

909 raise InvalidCalendar( 

910 f"{prop} must be either a " 

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

912 f" not {value}." 

913 ) 

914 return value 

915 

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

917 if value is None: 

918 p_del(self) 

919 return 

920 if not isinstance(value, value_type): 

921 raise TypeError( 

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

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

924 ) 

925 self[prop] = vProp(value) 

926 if prop in self.exclusive: 

927 for other_prop in self.exclusive: 

928 if other_prop != prop: 

929 self.pop(other_prop, None) 

930 

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

932 type_def 

933 ] 

934 

935 def p_del(self: Component): 

936 self.pop(prop) 

937 

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

939 

940 {doc} 

941 

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

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

944 If the value is absent, we return None. 

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

946 """ 

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

948 

949 

950X_MOZ_SNOOZE_TIME_property = single_utc_property( 

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

952) 

953X_MOZ_LASTACK_property = single_utc_property( 

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

955) 

956 

957 

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

959 """Getter for property DURATION.""" 

960 default = object() 

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

962 if isinstance(duration, vDDDTypes): 

963 return duration.dt 

964 if isinstance(duration, vDuration): 

965 return duration.td 

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

967 raise InvalidCalendar( 

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

969 ) 

970 return None 

971 

972 

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

974 """Setter for property DURATION.""" 

975 if value is None: 

976 self.pop("duration", None) 

977 return 

978 if not isinstance(value, timedelta): 

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

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

981 self.pop("DTEND") 

982 self.pop("DUE") 

983 

984 

985def property_del_duration(self: Component): 

986 """Delete property DURATION.""" 

987 self.pop("DURATION") 

988 

989 

990property_doc_duration_template = """The DURATION property. 

991 

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

993start of the {component}. 

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

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

996of the event. 

997 

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

999Instead use the duration property (lower case). 

1000""" 

1001 

1002 

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

1004 """Return the duration property.""" 

1005 return property( 

1006 property_get_duration, 

1007 property_set_duration, 

1008 property_del_duration, 

1009 property_doc_duration_template.format(component=component), 

1010 ) 

1011 

1012 

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

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

1015 

1016 Examples: Journal.descriptions, Event.comments 

1017 """ 

1018 

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

1020 """Get the values.""" 

1021 descriptions = self.get(name) 

1022 if descriptions is None: 

1023 return [] 

1024 if not isinstance(descriptions, SEQUENCE_TYPES): 

1025 return [descriptions] 

1026 return descriptions 

1027 

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

1029 """Set the values.""" 

1030 fdel(self) 

1031 if values is None: 

1032 return 

1033 if isinstance(values, str): 

1034 self.add(name, values) 

1035 else: 

1036 for description in values: 

1037 self.add(name, description) 

1038 

1039 def fdel(self: Component): 

1040 """Delete the values.""" 

1041 self.pop(name) 

1042 

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

1044 

1045 

1046descriptions_property = multi_text_property( 

1047 "DESCRIPTION", 

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

1049 

1050Property Parameters: 

1051 IANA, non-standard, alternate text 

1052 representation, and language property parameters can be specified 

1053 on this property. 

1054 

1055Conformance: 

1056 The property can be 

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

1058 

1059Description: 

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

1061 capture one or more textual journal entries. 

1062 

1063Examples: 

1064 The following is an example of this property with formatted 

1065 line breaks in the property value: 

1066 

1067 .. code-block:: pycon 

1068 

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

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

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

1072 

1073""", # noqa: E501 

1074) 

1075 

1076comments_property = multi_text_property( 

1077 "COMMENT", 

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

1079 

1080Purpose: 

1081 This property specifies non-processing information intended 

1082 to provide a comment to the calendar user. 

1083 

1084Conformance: 

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

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

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

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

1089 "VAVAILABILITY" and "VAVAILABLE". 

1090 

1091Property Parameters: 

1092 IANA, non-standard, alternate text 

1093 representation, and language property parameters can be specified 

1094 on this property. 

1095 

1096""", 

1097) 

1098 

1099 

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

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

1102 

1103 Property Parameters: 

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

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

1106 specified on this property. 

1107 

1108 Conformance: 

1109 This property MUST be specified in an iCalendar object 

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

1111 MUST be specified in an iCalendar object that specifies the 

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

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

1114 zone definition or that defines calendar components that are not 

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

1116 user's calendar. 

1117 

1118 Description: 

1119 This property is specified within the "VEVENT", 

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

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

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

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

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

1125 the calendar that the published busy time came from. 

1126 

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

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

1129 specifying a pointer to the directory information associated with 

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

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

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

1133 "LANGUAGE" property parameter is specified, the identified 

1134 language applies to the "CN" parameter value. 

1135 """ 

1136 return self.get("ORGANIZER") 

1137 

1138 

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

1140 """Set the value.""" 

1141 _del_organizer(self) 

1142 if value is not None: 

1143 self.add("ORGANIZER", value) 

1144 

1145 

1146def _del_organizer(self: Component): 

1147 """Delete the value.""" 

1148 self.pop("ORGANIZER") 

1149 

1150 

1151organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1152 

1153 

1154def single_string_enum_property( 

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

1156) -> property: 

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

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

1159 

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

1161 """Get the value.""" 

1162 value = prop.fget(self) 

1163 if value == default: 

1164 return default 

1165 return enum(str(value)) 

1166 

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

1168 """Set the value.""" 

1169 if value == "": 

1170 value = None 

1171 prop.fset(self, value) 

1172 

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

1174 

1175 

1176busy_type_property = single_string_enum_property( 

1177 "BUSYTYPE", 

1178 BUSYTYPE, 

1179 BUSYTYPE.BUSY_UNAVAILABLE, 

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

1181 

1182Returns: 

1183 :class:`icalendar.enums.BUSYTYPE` 

1184 

1185Description: 

1186 This property is used to specify the default busy time 

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

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

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

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

1191 UNAVAILABLE". 

1192""", 

1193) 

1194 

1195priority_property = single_int_property( 

1196 "PRIORITY", 

1197 0, 

1198 """ 

1199 

1200Conformance: 

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

1202 according to :rfc:`5545`. 

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

1204 

1205Description: 

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

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

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

1209 priority. Subsequent numbers specify a decreasing ordinal 

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

1211 

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

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

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

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

1216 is "LOW" priority. 

1217 

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

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

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

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

1222 9 specifies "C3". 

1223 

1224 Other integer values are reserved for future use. 

1225 

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

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

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

1229 

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

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

1232 multiple action items for a given time period. 

1233""", 

1234) 

1235 

1236class_property = single_string_enum_property( 

1237 "CLASS", 

1238 CLASS, 

1239 CLASS.PUBLIC, 

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

1241 

1242Returns: 

1243 :class:`icalendar.enums.CLASS` 

1244 

1245Description: 

1246 An access classification is only one component of the 

1247 general security system within a calendar application. It 

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

1249 calendar owner intends for information within an individual 

1250 calendar entry. The access classification of an individual 

1251 iCalendar component is useful when measured along with the other 

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

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

1254 Hence, the semantics of the individual access classifications 

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

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

1257 memo, these access classifications cannot serve as an enforcement 

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

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

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

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

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

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

1264""", 

1265) 

1266 

1267transparency_property = single_string_enum_property( 

1268 "TRANSP", 

1269 TRANSP, 

1270 TRANSP.OPAQUE, 

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

1272 

1273Returns: 

1274 :class:`icalendar.enums.TRANSP` 

1275 

1276Description: 

1277 Time Transparency is the characteristic of an event 

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

1279 Events that consume actual time for the individual or resource 

1280 associated with the calendar SHOULD be recorded as OPAQUE, 

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

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

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

1284 busy time searches. 

1285""", 

1286) 

1287status_property = single_string_enum_property( 

1288 "STATUS", 

1289 STATUS, 

1290 "", 

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

1292 

1293Returns: 

1294 :class:`icalendar.enums.STATUS` 

1295 

1296The default value is ``""``. 

1297 

1298Description: 

1299 In a group-scheduled calendar component, the property 

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

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

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

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

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

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

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

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

1308 cancelled or removed. 

1309""", 

1310) 

1311 

1312url_property = single_string_property( 

1313 "URL", 

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

1315 

1316Description: 

1317 This property may be used in a calendar component to 

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

1319 information associated with the calendar component can be found. 

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

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

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

1323 specified, they MUST point to the same resource. 

1324 

1325Conformance: 

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

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

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

1329 

1330Example: 

1331 The following is an example of this property: 

1332 

1333 .. code-block:: text 

1334 

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

1336 

1337""", 

1338) 

1339 

1340source_property = single_string_property( 

1341 "SOURCE", 

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

1343 

1344Description: 

1345 This property identifies a location where a client can 

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

1347 specified "REFRESH-INTERVAL" value when periodically retrieving 

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

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

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

1351 

1352Conformance: 

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

1354 

1355Example: 

1356 The following is an example of this property: 

1357 

1358 .. code-block:: text 

1359 

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

1361 

1362""", 

1363) 

1364 

1365location_property = multi_language_text_property( 

1366 "LOCATION", 

1367 None, 

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

1369 

1370Property Parameters: 

1371 IANA, non-standard, alternate text 

1372 representation, and language property parameters can be specified 

1373 on this property. 

1374 

1375Conformance: 

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

1377 calendar component. 

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

1379 

1380Description: 

1381 Specific venues such as conference or meeting rooms may 

1382 be explicitly specified using this property. An alternate 

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

1384 directory information with more structured specification of the 

1385 location. For example, the alternate representation may specify 

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

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

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

1389 

1390""", 

1391) 

1392 

1393contacts_property = multi_text_property( 

1394 "CONTACT", 

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

1396 

1397Purpose: 

1398 This property is used to represent contact information or 

1399 alternately a reference to contact information associated with the 

1400 calendar component. 

1401 

1402Property Parameters: 

1403 IANA, non-standard, alternate text 

1404 representation, and language property parameters can be specified 

1405 on this property. 

1406 

1407Conformance: 

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

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

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

1411 amd "VAVAILABLE" calendar component. 

1412 

1413Description: 

1414 The property value consists of textual contact 

1415 information. An alternative representation for the property value 

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

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

1418 information. 

1419 

1420Example: 

1421 The following is an example of this property referencing 

1422 textual contact information: 

1423 

1424 .. code-block:: text 

1425 

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

1427 

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

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

1430 contact information: 

1431 

1432 .. code-block:: text 

1433 

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

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

1436 +1-919-555-1234 

1437 

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

1439 representation of a MIME body part containing the contact 

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

1441 directory media type :rfc:`2425`: 

1442 

1443 .. code-block:: text 

1444 

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

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

1447 

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

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

1450 information: 

1451 

1452 .. code-block:: text 

1453 

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

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

1456""", 

1457) 

1458 

1459 

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

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

1462 

1463 return single_utc_property(name, docs) 

1464 

1465 

1466rfc_7953_dtstart_property = timezone_datetime_property( 

1467 "DTSTART", 

1468 """Start of the component. 

1469 

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

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

1472 

1473 Description: 

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

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

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

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

1478 

1479 """, 

1480) 

1481 

1482rfc_7953_dtend_property = timezone_datetime_property( 

1483 "DTEND", 

1484 """Start of the component. 

1485 

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

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

1488 

1489 Description: 

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

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

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

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

1494 """, 

1495) 

1496 

1497 

1498@property 

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

1500 """Compute the duration of this component. 

1501 

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

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

1504 :attr:`DTEND`/:attr:`DURATION`. 

1505 

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

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

1508 """ 

1509 duration = self.DURATION 

1510 if duration: 

1511 return duration 

1512 end = self.DTEND 

1513 if end is None: 

1514 return None 

1515 start = self.DTSTART 

1516 if start is None: 

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

1518 return end - start 

1519 

1520 

1521@property 

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

1523 """Compute the duration of this component. 

1524 

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

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

1527 :attr:`DTEND`/:attr:`DURATION`. 

1528 

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

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

1531 """ 

1532 duration = self.DURATION 

1533 if duration: 

1534 start = self.DTSTART 

1535 if start is None: 

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

1537 return start + duration 

1538 end = self.DTEND 

1539 if end is None: 

1540 return None 

1541 return end 

1542 

1543 

1544@rfc_7953_end_property.setter 

1545def rfc_7953_end_property(self, value: datetime): 

1546 self.DTEND = value 

1547 

1548 

1549@rfc_7953_end_property.deleter 

1550def rfc_7953_end_property(self): 

1551 del self.DTEND 

1552 

1553 

1554def get_start_end_duration_with_validation( 

1555 component: Component, 

1556 start_property: str, 

1557 end_property: str, 

1558 component_name: str, 

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

1560 """ 

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

1562 

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

1564 for ``Event`` and ``Todo`` components. 

1565 

1566 Args: 

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

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

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

1570 ``DUE`` for ``Todo``. 

1571 component_name: The component name for error messages, 

1572 either ``VEVENT`` or ``VTODO``. 

1573 

1574 Returns: 

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

1576 

1577 Raises: 

1578 InvalidCalendar: If the component violates RFC 5545 constraints. 

1579 

1580 """ 

1581 start = getattr(component, start_property, None) 

1582 end = getattr(component, end_property, None) 

1583 duration = component.DURATION 

1584 

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

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

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

1588 msg = ( 

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

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

1591 ) 

1592 raise InvalidCalendar(msg) 

1593 

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

1595 if ( 

1596 start is not None 

1597 and is_date(start) 

1598 and duration is not None 

1599 and duration.seconds != 0 

1600 ): 

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

1602 raise InvalidCalendar(msg) 

1603 

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

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

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

1607 msg = ( 

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

1609 ) 

1610 raise InvalidCalendar(msg) 

1611 

1612 return start, end, duration 

1613 

1614 

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

1616 """ 

1617 Get the start property with validation. 

1618 

1619 Args: 

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

1621 

1622 Returns: 

1623 The ``DTSTART`` value. 

1624 

1625 Raises: 

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

1627 

1628 """ 

1629 # Trigger validation by calling _get_start_end_duration 

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

1631 if start is None: 

1632 msg = "No DTSTART given." 

1633 raise IncompleteComponent(msg) 

1634 return start 

1635 

1636 

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

1638 """ 

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

1640 

1641 Args: 

1642 component: The component to get end from 

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

1644 ``DUE`` for ``Todo``. 

1645 

1646 Returns: 

1647 The computed end value. 

1648 

1649 Raises: 

1650 IncompleteComponent: If the provided information is incomplete 

1651 to compute the end property. 

1652 

1653 """ 

1654 # Trigger validation by calling _get_start_end_duration 

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

1656 

1657 if end is None and duration is None: 

1658 if start is None: 

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

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

1661 raise IncompleteComponent(msg) 

1662 

1663 # Default behavior: date gets +1 day, datetime gets same time 

1664 if is_date(start): 

1665 return start + timedelta(days=1) 

1666 return start 

1667 

1668 if duration is not None: 

1669 if start is not None: 

1670 return start + duration 

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

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

1673 raise IncompleteComponent(msg) 

1674 

1675 return end 

1676 

1677 

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

1679 """ 

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

1681 

1682 Args: 

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

1684 

1685 Returns: 

1686 The duration as a timedelta. 

1687 

1688 """ 

1689 # First check if DURATION property is explicitly set 

1690 if "DURATION" in component: 

1691 return component["DURATION"].dt 

1692 

1693 # Fall back to calculated duration from start and end 

1694 return component.end - component.start 

1695 

1696 

1697def set_duration_with_locking( 

1698 component: Component, 

1699 duration: timedelta | None, 

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

1701 end_property: str, 

1702) -> None: 

1703 """ 

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

1705 

1706 Args: 

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

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

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

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

1711 ``DUE`` for ``Todo``. 

1712 

1713 """ 

1714 # Convert to DURATION property if duration is None 

1715 if duration is None: 

1716 if "DURATION" in component: 

1717 return # Already has DURATION property 

1718 current_duration = component.duration 

1719 component.DURATION = current_duration 

1720 return 

1721 

1722 if not isinstance(duration, timedelta): 

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

1724 raise TypeError(msg) 

1725 

1726 # Validate date/duration compatibility 

1727 start = component.DTSTART 

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

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

1730 raise InvalidCalendar(msg) 

1731 

1732 if locked == "start": 

1733 # Keep start locked, adjust end 

1734 if start is None: 

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

1736 raise IncompleteComponent(msg) 

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

1738 component.DURATION = duration 

1739 elif locked == "end": 

1740 # Keep end locked, adjust start 

1741 current_end = component.end 

1742 component.DTSTART = current_end - duration 

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

1744 component.DURATION = duration 

1745 else: 

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

1747 raise ValueError(msg) 

1748 

1749 

1750def set_start_with_locking( 

1751 component: Component, 

1752 start: date | datetime, 

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

1754 end_property: str, 

1755) -> None: 

1756 """ 

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

1758 

1759 Args: 

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

1761 start: The start time to set. 

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

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

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

1765 ``DUE`` for ``Todo``. 

1766 

1767 """ 

1768 if locked is None: 

1769 # Auto-detect based on existing properties 

1770 if "DURATION" in component: 

1771 locked = "duration" 

1772 elif end_property in component: 

1773 locked = "end" 

1774 else: 

1775 # Default to duration if no existing properties 

1776 locked = "duration" 

1777 

1778 if locked == "duration": 

1779 # Keep duration locked, adjust end 

1780 current_duration = ( 

1781 component.duration 

1782 if "DURATION" in component or end_property in component 

1783 else None 

1784 ) 

1785 component.DTSTART = start 

1786 if current_duration is not None: 

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

1788 component.DURATION = current_duration 

1789 elif locked == "end": 

1790 # Keep end locked, adjust duration 

1791 current_end = component.end 

1792 component.DTSTART = start 

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

1794 setattr(component, end_property, current_end) 

1795 else: 

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

1797 raise ValueError(msg) 

1798 

1799 

1800def set_end_with_locking( 

1801 component: Component, 

1802 end: date | datetime, 

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

1804 end_property: str, 

1805) -> None: 

1806 """ 

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

1808 

1809 Args: 

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

1811 end: The end time to set. 

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

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

1814 for ``Todo``. 

1815 

1816 """ 

1817 if locked == "start": 

1818 # Keep start locked, adjust duration 

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

1820 setattr(component, end_property, end) 

1821 elif locked == "duration": 

1822 # Keep duration locked, adjust start 

1823 current_duration = component.duration 

1824 component.DTSTART = end - current_duration 

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

1826 component.DURATION = current_duration 

1827 else: 

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

1829 raise ValueError(msg) 

1830 

1831 

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

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

1834 

1835 Description: 

1836 This property specifies an image for an iCalendar 

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

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

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

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

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

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

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

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

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

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

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

1848 on the image in the calendar user agent. 

1849 

1850 Conformance: 

1851 This property can be specified multiple times in an 

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

1853 components. 

1854 

1855 .. note:: 

1856 

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

1858 please open an issue or a pull request. 

1859 """ 

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

1861 if not isinstance(images, SEQUENCE_TYPES): 

1862 images = [images] 

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

1864 

1865 

1866images_property = property(_get_images) 

1867 

1868 

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

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

1871 

1872 Purpose: 

1873 This property specifies information for accessing a conferencing system. 

1874 

1875 Conformance: 

1876 This property can be specified multiple times in a 

1877 "VEVENT" or "VTODO" calendar component. 

1878 

1879 Description: 

1880 This property specifies information for accessing a 

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

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

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

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

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

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

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

1888 containing a text description can be used. 

1889 

1890 A conference system can be a bidirectional communication channel 

1891 or a uni-directional "broadcast feed". 

1892 

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

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

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

1896 multiple properties. 

1897 

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

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

1900 codes for the moderator and attendee of a teleconference system 

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

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

1903 which. 

1904 

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

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

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

1908 

1909 Example: 

1910 The following are examples of this property: 

1911 

1912 .. code-block:: text 

1913 

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

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

1916 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

1918 CONFERENCE;VALUE=URI;FEATURE=PHONE; 

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

1920 CONFERENCE;VALUE=URI;FEATURE=CHAT; 

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

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

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

1924 

1925 Get all conferences: 

1926 

1927 .. code-block:: pycon 

1928 

1929 >>> from icalendar import Event 

1930 >>> event = Event() 

1931 >>> event.conferences 

1932 [] 

1933 

1934 Set a conference: 

1935 

1936 .. code-block:: pycon 

1937 

1938 >>> from icalendar import Event, Conference 

1939 >>> event = Event() 

1940 >>> event.conferences = [ 

1941 ... Conference( 

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

1943 ... feature="PHONE,MODERATOR", 

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

1945 ... language="EN", 

1946 ... ) 

1947 ... ] 

1948 >>> print(event.to_ical()) 

1949 BEGIN:VEVENT 

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

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

1952 END:VEVENT 

1953 

1954 """ 

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

1956 if not isinstance(conferences, SEQUENCE_TYPES): 

1957 conferences = [conferences] 

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

1959 

1960 

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

1962 """Set the conferences.""" 

1963 _del_conferences(self) 

1964 for conference in conferences or []: 

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

1966 

1967 

1968def _del_conferences(self: Component): 

1969 """Delete all conferences.""" 

1970 self.pop("CONFERENCE") 

1971 

1972 

1973conferences_property = property(_get_conferences, _set_conferences, _del_conferences) 

1974 

1975__all__ = [ 

1976 "attendees_property", 

1977 "busy_type_property", 

1978 "categories_property", 

1979 "class_property", 

1980 "color_property", 

1981 "comments_property", 

1982 "conferences_property", 

1983 "contacts_property", 

1984 "create_single_property", 

1985 "description_property", 

1986 "descriptions_property", 

1987 "duration_property", 

1988 "exdates_property", 

1989 "get_duration_property", 

1990 "get_end_property", 

1991 "get_start_end_duration_with_validation", 

1992 "get_start_property", 

1993 "images_property", 

1994 "location_property", 

1995 "multi_language_text_property", 

1996 "organizer_property", 

1997 "priority_property", 

1998 "property_del_duration", 

1999 "property_doc_duration_template", 

2000 "property_get_duration", 

2001 "property_set_duration", 

2002 "rdates_property", 

2003 "rfc_7953_dtend_property", 

2004 "rfc_7953_dtstart_property", 

2005 "rfc_7953_duration_property", 

2006 "rfc_7953_end_property", 

2007 "rrules_property", 

2008 "sequence_property", 

2009 "set_duration_with_locking", 

2010 "set_end_with_locking", 

2011 "set_start_with_locking", 

2012 "single_int_property", 

2013 "single_utc_property", 

2014 "source_property", 

2015 "status_property", 

2016 "summary_property", 

2017 "transparency_property", 

2018 "uid_property", 

2019 "url_property", 

2020]