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

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

318 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, 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 vCalAddress, vCategory, vDDDTypes, vDuration, vRecur, vText 

13from icalendar.timezone import tzp 

14from icalendar.tools import is_date 

15 

16if TYPE_CHECKING: 

17 from icalendar.cal import Component 

18 

19 

20def _get_rdates( 

21 self: Component, 

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

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

24 

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

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

27 

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

29 with and without timezone. 

30 

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

32 if the end is specified. 

33 

34 Value Type: 

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

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

37 

38 Property Parameters: 

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

40 zone identifier property parameters can be specified on this 

41 property. 

42 

43 Conformance: 

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

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

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

47 calendar component. 

48 

49 Description: 

50 This property can appear along with the "RRULE" 

51 property to define an aggregate set of repeating occurrences. 

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

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

54 the "RDATE" and "RRULE". 

55 

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

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

58 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

74 Duplicate instances are ignored. 

75 

76 Example: 

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

78 

79 .. code-block:: pycon 

80 

81 >>> from icalendar import Event 

82 >>> from datetime import datetime 

83 >>> event = Event() 

84 

85 # Add a list of recurrence dates 

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

87 >>> event.rdates 

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

89 

90 .. note:: 

91 

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

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

94 

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

96 

97 """ 

98 result = [] 

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

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

101 for dts in rdates.dts: 

102 rdate = dts.dt 

103 if isinstance(rdate, tuple): 

104 # we have a period as rdate 

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

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

107 else: 

108 result.append(rdate) 

109 else: 

110 # we have a date/datetime 

111 result.append((rdate, None)) 

112 return result 

113 

114 

115rdates_property = property(_get_rdates) 

116 

117 

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

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

120 

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

122 

123 Value Type: 

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

125 The value type can be set to DATE. 

126 

127 Property Parameters: 

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

129 zone identifier property parameters can be specified on this 

130 property. 

131 

132 Conformance: 

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

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

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

136 calendar component. 

137 

138 Description: 

139 The exception dates, if specified, are used in 

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

141 set of recurrence instances for a calendar component. The 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

157 Duplicate instances are ignored. 

158 

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

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

161 MUST still be maintained by the calendaring and scheduling system 

162 because the original "DTSTART" value has inherent usage 

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

164 

165 Example: 

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

167 

168 .. code-block:: pycon 

169 

170 >>> from icalendar import Event 

171 >>> from datetime import datetime 

172 >>> event = Event() 

173 

174 # Add a list of excluded dates 

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

176 >>> event.exdates 

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

178 

179 .. note:: 

180 

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

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

183 

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

185 

186 """ 

187 result = [] 

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

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

190 for dts in exdates.dts: 

191 exdate = dts.dt 

192 # we have a date/datetime 

193 result.append(exdate) 

194 return result 

195 

196 

197exdates_property = property(_get_exdates) 

198 

199 

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

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

202 

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

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

205 

206 Property Parameters: 

207 IANA and non-standard property parameters can 

208 be specified on this property. 

209 

210 Conformance: 

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

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

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

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

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

216 undefined. 

217 

218 Description: 

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

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

221 recurrence instances for a calendar component. The recurrence set 

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

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

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

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

226 value SHOULD be synchronized with the recurrence rule, if 

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

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

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

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

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

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

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

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

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

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

237 Duplicate instances are ignored. 

238 

239 The "DTSTART" property specified within the iCalendar object 

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

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

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

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

244 same local time regardless of time zone changes. 

245 

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

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

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

249 duration of the recurring component is specified with the 

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

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

252 duration of each recurrence instance will depend on its specific 

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

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

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

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

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

258 type. 

259 

260 Examples: 

261 Daily for 10 occurrences: 

262 

263 .. code-block:: pycon 

264 

265 >>> from icalendar import Event 

266 >>> from datetime import datetime 

267 >>> from zoneinfo import ZoneInfo 

268 >>> event = Event() 

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

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

271 >>> print(event.to_ical()) 

272 BEGIN:VEVENT 

273 DTSTART;TZID=America/New_York:19970902T090000 

274 RRULE:FREQ=DAILY;COUNT=10 

275 END:VEVENT 

276 >>> event.rrules 

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

278 

279 Daily until December 24, 1997: 

280 

281 .. code-block:: pycon 

282 

283 >>> from icalendar import Event, vRecur 

284 >>> from datetime import datetime 

285 >>> from zoneinfo import ZoneInfo 

286 >>> event = Event() 

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

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

289 >>> print(event.to_ical()) 

290 BEGIN:VEVENT 

291 DTSTART;TZID=America/New_York:19970902T090000 

292 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 

293 END:VEVENT 

294 >>> event.rrules 

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

296 

297 .. note:: 

298 

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

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

301 

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

303 

304 """ # noqa: E501 

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

306 if not isinstance(rrules, list): 

307 return [rrules] 

308 return rrules 

309 

310 

311rrules_property = property(_get_rrules) 

312 

313 

314def multi_language_text_property( 

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

316) -> property: 

317 """This creates a text property. 

318 

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

320 

321 Args: 

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

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

324 doc (str): The documentation string 

325 """ 

326 

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

328 """Get the property""" 

329 result = self.get(main_prop) 

330 if result is None and compatibility_prop is not None: 

331 result = self.get(compatibility_prop) 

332 if isinstance(result, list): 

333 for item in result: 

334 if "LANGUAGE" not in item.params: 

335 return item 

336 return result 

337 

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

339 """Set the property.""" 

340 fdel(self) 

341 if value is not None: 

342 self.add(main_prop, value) 

343 

344 def fdel(self: Component): 

345 """Delete the property.""" 

346 self.pop(main_prop, None) 

347 if compatibility_prop is not None: 

348 self.pop(compatibility_prop, None) 

349 

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

351 

352 

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

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

355 

356 Args: 

357 prop: The name of the property 

358 default: The default value 

359 doc: The documentation string 

360 """ 

361 

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

363 """Get the property""" 

364 try: 

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

366 except ValueError as e: 

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

368 

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

370 """Set the property.""" 

371 fdel(self) 

372 if value is not None: 

373 self.add(prop, value) 

374 

375 def fdel(self: Component): 

376 """Delete the property.""" 

377 self.pop(prop, None) 

378 

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

380 

381 

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

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

384 

385 Args: 

386 name: name of the property 

387 docs: documentation string 

388 """ 

389 docs = ( 

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

391 

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

393 """ 

394 + docs 

395 ) 

396 

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

398 """Get the value.""" 

399 if name not in self: 

400 return None 

401 dt = self.get(name) 

402 if isinstance(dt, vText): 

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

404 value = vDDDTypes.from_ical(dt) 

405 else: 

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

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

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

409 return tzp.localize_utc(value) 

410 

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

412 """Set the value""" 

413 if value is None: 

414 fdel(self) 

415 return 

416 if not isinstance(value, date): 

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

418 fdel(self) 

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

420 

421 def fdel(self: Component): 

422 """Delete the property.""" 

423 self.pop(name, None) 

424 

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

426 

427 

428def single_string_property( 

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

430) -> property: 

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

432 

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

434 """Get the value.""" 

435 result = self.get( 

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

437 ) 

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

439 return default 

440 if isinstance(result, list): 

441 return result[0] 

442 return result 

443 

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

445 """Set the value. 

446 

447 Setting the value to None will delete it. 

448 """ 

449 fdel(self) 

450 if value is not None: 

451 self.add(name, value) 

452 

453 def fdel(self: Component): 

454 """Delete the property.""" 

455 self.pop(name, None) 

456 if other_name is not None: 

457 self.pop(other_name, None) 

458 

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

460 

461 

462color_property = single_string_property( 

463 "COLOR", 

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

465 

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

467 

468 Property Parameters: 

469 IANA and non-standard property parameters can 

470 be specified on this property. 

471 

472 Conformance: 

473 This property can be specified once in an iCalendar 

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

475 

476 Description: 

477 This property specifies a color that clients MAY use 

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

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

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

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

482 

483 Example: 

484 ``"turquoise"``, ``"#ffffff"`` 

485 

486 .. code-block:: pycon 

487 

488 >>> from icalendar import Todo 

489 >>> todo = Todo() 

490 >>> todo.color = "green" 

491 >>> print(todo.to_ical()) 

492 BEGIN:VTODO 

493 COLOR:green 

494 END:VTODO 

495 """, 

496) 

497 

498sequence_property = single_int_property( 

499 "SEQUENCE", 

500 0, 

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

502 

503Value Type: 

504 INTEGER 

505 

506Property Parameters: 

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

508 

509Conformance: 

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

511 "VJOURNAL" calendar component. 

512 

513Description: 

514 When a calendar component is created, its sequence 

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

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

517 calendar component. 

518 

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

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

521 calendar component. 

522 

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

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

525 component to which the "Attendee" is referring. 

526 

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

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

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

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

531 requested. 

532 

533 Recurrence instances of a recurring component MAY have different 

534 sequence numbers. 

535 

536Examples: 

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

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

539 

540 .. code-block:: pycon 

541 

542 >>> from icalendar import Event 

543 >>> event = Event() 

544 >>> event.sequence 

545 0 

546 

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

548 component that has been revised 10 different times by the 

549 "Organizer": 

550 

551 .. code-block:: pycon 

552 

553 >>> from icalendar import Calendar 

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

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

556 >>> event.sequence 

557 10 

558 """, # noqa: E501 

559) 

560 

561 

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

563 """Get all the categories.""" 

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

565 if isinstance(categories, list): 

566 _set_categories( 

567 component, 

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

569 ) 

570 return _get_categories(component) 

571 if categories is None: 

572 categories = vCategory([]) 

573 component.add("CATEGORIES", categories) 

574 return categories.cats 

575 

576 

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

578 """Set the categories.""" 

579 if not cats and cats != []: 

580 _del_categories(component) 

581 return 

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

583 if isinstance(cats, list): 

584 cats.clear() 

585 cats.extend(categories.cats) 

586 categories.cats = cats 

587 

588 

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

590 """Delete the categories.""" 

591 component.pop("CATEGORIES", None) 

592 

593 

594categories_property = property( 

595 _get_categories, 

596 _set_categories, 

597 _del_categories, 

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

599 

600Property Parameters: 

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

602 property. 

603 

604Conformance: 

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

606 components. 

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

608 

609Description: 

610 This property is used to specify categories or subtypes 

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

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

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

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

615 of categories. 

616 

617Example: 

618 Below, we add the categories to an event: 

619 

620 .. code-block:: pycon 

621 

622 >>> from icalendar import Event 

623 >>> event = Event() 

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

625 >>> print(event.to_ical()) 

626 BEGIN:VEVENT 

627 CATEGORIES:Work,Meeting 

628 END:VEVENT 

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

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

631 True 

632 

633.. note:: 

634 

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

636""", 

637) 

638 

639 

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

641 """Get attendees.""" 

642 value = self.get("ATTENDEE") 

643 if value is None: 

644 value = [] 

645 self["ATTENDEE"] = value 

646 return value 

647 if isinstance(value, vCalAddress): 

648 return [value] 

649 return value 

650 

651 

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

653 """Set attendees.""" 

654 _del_attendees(self) 

655 if value is None: 

656 return 

657 if not isinstance(value, list): 

658 value = [value] 

659 self["ATTENDEE"] = value 

660 

661 

662def _del_attendees(self: Component): 

663 """Delete all attendees.""" 

664 self.pop("ATTENDEE", None) 

665 

666 

667attendees_property = property( 

668 _get_attendees, 

669 _set_attendees, 

670 _del_attendees, 

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

672 

673Conformance: 

674 This property MUST be specified in an iCalendar object 

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

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

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

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

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

680 iCalendar object that specifies only a time zone definition or 

681 that defines calendar components that are not group-scheduled 

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

683 

684Description: 

685 This property MUST only be specified within calendar 

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

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

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

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

690 type of iCalendar alarm. 

691 

692Examples: 

693 Add a new attendee to an existing event. 

694 

695 .. code-block:: pycon 

696 

697 >>> from icalendar import Event, vCalAddress 

698 >>> event = Event() 

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

700 >>> print(event.to_ical()) 

701 BEGIN:VEVENT 

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

703 END:VEVENT 

704 

705 Create an email alarm with several attendees: 

706 

707 >>> from icalendar import Alarm, vCalAddress 

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

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

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

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

712 >>> print(alarm.to_ical()) 

713 BEGIN:VALARM 

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

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

716 SUMMARY:Email alarm 

717 END:VALARM 

718""", 

719) 

720 

721uid_property = single_string_property( 

722 "UID", 

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

724 

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

726 

727Returns: 

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

729 

730Description: 

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

732 The generator of the identifier MUST guarantee that the identifier 

733 is unique. 

734 

735 This is the method for correlating scheduling messages with the 

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

737 The full range of calendar components specified by a recurrence 

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

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

740 property allows the reference to an individual instance within the 

741 recurrence set. 

742 

743 This property is an important method for group-scheduling 

744 applications to match requests with later replies, modifications, 

745 or deletion requests. Calendaring and scheduling applications 

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

747 calendar components to assure interoperability with other group- 

748 scheduling applications. This identifier is created by the 

749 calendar system that generates an iCalendar object. 

750 

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

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

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

754 

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

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

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

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

759 to identify the most recent version of a calendar. 

760 

761Conformance: 

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

763 and "VJOURNAL" calendar components. 

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

765 allow it to be defined in an iCalendar object. 

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

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

768 to refer uniquely to the "VALARM" component. 

769 

770 This property can be specified once only. 

771 

772Security: 

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

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

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

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

777 Universally Unique Identifier (UUID) values as defined in 

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

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

780 

781Compatibility: 

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

783 

784Examples: 

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

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

787 

788 Set the UID of a calendar: 

789 

790 .. code-block:: pycon 

791 

792 >>> from icalendar import Calendar 

793 >>> from uuid import uuid4 

794 >>> calendar = Calendar() 

795 >>> calendar.uid = uuid4() 

796 >>> print(calendar.to_ical()) 

797 BEGIN:VCALENDAR 

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

799 END:VCALENDAR 

800 

801""", 

802) 

803 

804summary_property = multi_language_text_property( 

805 "SUMMARY", 

806 None, 

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

808 

809Property Parameters: 

810 IANA, non-standard, alternate text 

811 representation, and language property parameters can be specified 

812 on this property. 

813 

814Conformance: 

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

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

817 

818Description: 

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

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

821 summary about the activity or journal entry. 

822 

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

824 capture the subject of an EMAIL category of alarm. 

825 

826Examples: 

827 The following is an example of this property: 

828 

829 .. code-block:: pycon 

830 

831 SUMMARY:Department Party 

832""", 

833) 

834 

835description_property = multi_language_text_property( 

836 "DESCRIPTION", 

837 None, 

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

839 

840Property Parameters: 

841 IANA, non-standard, alternate text 

842 representation, and language property parameters can be specified 

843 on this property. 

844 

845Conformance: 

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

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

848 specified multiple times only within a "VJOURNAL" calendar 

849 component. 

850 

851Description: 

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

853 capture lengthy textual descriptions associated with the activity. 

854 

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

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

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

858 

859Examples: 

860 The following is an example of this property with formatted 

861 line breaks in the property value: 

862 

863 .. code-block:: pycon 

864 

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

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

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

868 

869 """, # noqa: E501 

870) 

871 

872 

873def create_single_property( 

874 prop: str, 

875 value_attr: Optional[str], 

876 value_type: tuple[type], 

877 type_def: type, 

878 doc: str, 

879 vProp: type = vDDDTypes, # noqa: N803 

880): 

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

882 

883 :param prop: The name of the property. 

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

885 :param value_type: The type of the value. 

886 :param type_def: The type of the property. 

887 :param doc: The docstring of the property. 

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

889 """ 

890 

891 def p_get(self: Component): 

892 default = object() 

893 result = self.get(prop, default) 

894 if result is default: 

895 return None 

896 if isinstance(result, list): 

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

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

899 if not isinstance(value, value_type): 

900 raise InvalidCalendar( 

901 f"{prop} must be either a " 

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

903 f" not {value}." 

904 ) 

905 return value 

906 

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

908 if value is None: 

909 p_del(self) 

910 return 

911 if not isinstance(value, value_type): 

912 raise TypeError( 

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

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

915 ) 

916 self[prop] = vProp(value) 

917 if prop in self.exclusive: 

918 for other_prop in self.exclusive: 

919 if other_prop != prop: 

920 self.pop(other_prop, None) 

921 

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

923 type_def 

924 ] 

925 

926 def p_del(self: Component): 

927 self.pop(prop) 

928 

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

930 

931 {doc} 

932 

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

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

935 If the value is absent, we return None. 

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

937 """ 

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

939 

940 

941X_MOZ_SNOOZE_TIME_property = single_utc_property( 

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

943) 

944X_MOZ_LASTACK_property = single_utc_property( 

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

946) 

947 

948 

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

950 """Getter for property DURATION.""" 

951 default = object() 

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

953 if isinstance(duration, vDDDTypes): 

954 return duration.dt 

955 if isinstance(duration, vDuration): 

956 return duration.td 

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

958 raise InvalidCalendar( 

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

960 ) 

961 return None 

962 

963 

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

965 """Setter for property DURATION.""" 

966 if value is None: 

967 self.pop("duration", None) 

968 return 

969 if not isinstance(value, timedelta): 

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

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

972 self.pop("DTEND") 

973 self.pop("DUE") 

974 

975 

976def property_del_duration(self: Component): 

977 """Delete property DURATION.""" 

978 self.pop("DURATION") 

979 

980 

981property_doc_duration_template = """The DURATION property. 

982 

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

984start of the {component}. 

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

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

987of the event. 

988 

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

990Instead use the duration property (lower case). 

991""" 

992 

993 

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

995 """Return the duration property.""" 

996 return property( 

997 property_get_duration, 

998 property_set_duration, 

999 property_del_duration, 

1000 property_doc_duration_template.format(component=component), 

1001 ) 

1002 

1003 

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

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

1006 

1007 Examples: Journal.descriptions, Event.comments 

1008 """ 

1009 

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

1011 """Get the values.""" 

1012 descriptions = self.get(name) 

1013 if descriptions is None: 

1014 return [] 

1015 if not isinstance(descriptions, SEQUENCE_TYPES): 

1016 return [descriptions] 

1017 return descriptions 

1018 

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

1020 """Set the values.""" 

1021 fdel(self) 

1022 if values is None: 

1023 return 

1024 if isinstance(values, str): 

1025 self.add(name, values) 

1026 else: 

1027 for description in values: 

1028 self.add(name, description) 

1029 

1030 def fdel(self: Component): 

1031 """Delete the values.""" 

1032 self.pop(name) 

1033 

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

1035 

1036 

1037descriptions_property = multi_text_property( 

1038 "DESCRIPTION", 

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

1040 

1041Property Parameters: 

1042 IANA, non-standard, alternate text 

1043 representation, and language property parameters can be specified 

1044 on this property. 

1045 

1046Conformance: 

1047 The property can be 

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

1049 

1050Description: 

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

1052 capture one or more textual journal entries. 

1053 

1054Examples: 

1055 The following is an example of this property with formatted 

1056 line breaks in the property value: 

1057 

1058 .. code-block:: pycon 

1059 

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

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

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

1063 

1064""", # noqa: E501 

1065) 

1066 

1067comments_property = multi_text_property( 

1068 "COMMENT", 

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

1070 

1071Purpose: 

1072 This property specifies non-processing information intended 

1073 to provide a comment to the calendar user. 

1074 

1075Conformance: 

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

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

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

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

1080 "VAVAILABILITY" and "VAVAILABLE". 

1081 

1082Property Parameters: 

1083 IANA, non-standard, alternate text 

1084 representation, and language property parameters can be specified 

1085 on this property. 

1086 

1087""", 

1088) 

1089 

1090 

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

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

1093 

1094 Property Parameters: 

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

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

1097 specified on this property. 

1098 

1099 Conformance: 

1100 This property MUST be specified in an iCalendar object 

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

1102 MUST be specified in an iCalendar object that specifies the 

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

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

1105 zone definition or that defines calendar components that are not 

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

1107 user's calendar. 

1108 

1109 Description: 

1110 This property is specified within the "VEVENT", 

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

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

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

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

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

1116 the calendar that the published busy time came from. 

1117 

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

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

1120 specifying a pointer to the directory information associated with 

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

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

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

1124 "LANGUAGE" property parameter is specified, the identified 

1125 language applies to the "CN" parameter value. 

1126 """ 

1127 return self.get("ORGANIZER") 

1128 

1129 

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

1131 """Set the value.""" 

1132 _del_organizer(self) 

1133 if value is not None: 

1134 self.add("ORGANIZER", value) 

1135 

1136 

1137def _del_organizer(self: Component): 

1138 """Delete the value.""" 

1139 self.pop("ORGANIZER") 

1140 

1141 

1142organizer_property = property(_get_organizer, _set_organizer, _del_organizer) 

1143 

1144 

1145def single_string_enum_property( 

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

1147) -> property: 

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

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

1150 

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

1152 """Get the value.""" 

1153 value = prop.fget(self) 

1154 if value == default: 

1155 return default 

1156 return enum(str(value)) 

1157 

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

1159 """Set the value.""" 

1160 if value == "": 

1161 value = None 

1162 prop.fset(self, value) 

1163 

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

1165 

1166 

1167busy_type_property = single_string_enum_property( 

1168 "BUSYTYPE", 

1169 BUSYTYPE, 

1170 BUSYTYPE.BUSY_UNAVAILABLE, 

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

1172 

1173Returns: 

1174 :class:`icalendar.enums.BUSYTYPE` 

1175 

1176Description: 

1177 This property is used to specify the default busy time 

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

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

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

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

1182 UNAVAILABLE". 

1183""", 

1184) 

1185 

1186priority_property = single_int_property( 

1187 "PRIORITY", 

1188 0, 

1189 """ 

1190 

1191Conformance: 

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

1193 according to :rfc:`5545`. 

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

1195 

1196Description: 

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

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

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

1200 priority. Subsequent numbers specify a decreasing ordinal 

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

1202 

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

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

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

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

1207 is "LOW" priority. 

1208 

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

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

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

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

1213 9 specifies "C3". 

1214 

1215 Other integer values are reserved for future use. 

1216 

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

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

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

1220 

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

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

1223 multiple action items for a given time period. 

1224""", 

1225) 

1226 

1227class_property = single_string_enum_property( 

1228 "CLASS", 

1229 CLASS, 

1230 CLASS.PUBLIC, 

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

1232 

1233Returns: 

1234 :class:`icalendar.enums.CLASS` 

1235 

1236Description: 

1237 An access classification is only one component of the 

1238 general security system within a calendar application. It 

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

1240 calendar owner intends for information within an individual 

1241 calendar entry. The access classification of an individual 

1242 iCalendar component is useful when measured along with the other 

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

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

1245 Hence, the semantics of the individual access classifications 

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

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

1248 memo, these access classifications cannot serve as an enforcement 

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

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

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

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

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

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

1255""", 

1256) 

1257 

1258transparency_property = single_string_enum_property( 

1259 "TRANSP", 

1260 TRANSP, 

1261 TRANSP.OPAQUE, 

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

1263 

1264Returns: 

1265 :class:`icalendar.enums.TRANSP` 

1266 

1267Description: 

1268 Time Transparency is the characteristic of an event 

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

1270 Events that consume actual time for the individual or resource 

1271 associated with the calendar SHOULD be recorded as OPAQUE, 

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

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

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

1275 busy time searches. 

1276""", 

1277) 

1278status_property = single_string_enum_property( 

1279 "STATUS", 

1280 STATUS, 

1281 "", 

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

1283 

1284Returns: 

1285 :class:`icalendar.enums.STATUS` 

1286 

1287The default value is ``""``. 

1288 

1289Description: 

1290 In a group-scheduled calendar component, the property 

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

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

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

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

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

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

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

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

1299 cancelled or removed. 

1300""", 

1301) 

1302 

1303url_property = single_string_property( 

1304 "URL", 

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

1306 

1307Description: 

1308 This property may be used in a calendar component to 

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

1310 information associated with the calendar component can be found. 

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

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

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

1314 specified, they MUST point to the same resource. 

1315""", 

1316) 

1317 

1318location_property = multi_language_text_property( 

1319 "LOCATION", 

1320 None, 

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

1322 

1323Property Parameters: 

1324 IANA, non-standard, alternate text 

1325 representation, and language property parameters can be specified 

1326 on this property. 

1327 

1328Conformance: 

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

1330 calendar component. 

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

1332 

1333Description: 

1334 Specific venues such as conference or meeting rooms may 

1335 be explicitly specified using this property. An alternate 

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

1337 directory information with more structured specification of the 

1338 location. For example, the alternate representation may specify 

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

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

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

1342 

1343""", 

1344) 

1345 

1346contacts_property = multi_text_property( 

1347 "CONTACT", 

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

1349 

1350Purpose: 

1351 This property is used to represent contact information or 

1352 alternately a reference to contact information associated with the 

1353 calendar component. 

1354 

1355Property Parameters: 

1356 IANA, non-standard, alternate text 

1357 representation, and language property parameters can be specified 

1358 on this property. 

1359 

1360Conformance: 

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

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

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

1364 amd "VAVAILABLE" calendar component. 

1365 

1366Description: 

1367 The property value consists of textual contact 

1368 information. An alternative representation for the property value 

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

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

1371 information. 

1372 

1373Example: 

1374 The following is an example of this property referencing 

1375 textual contact information: 

1376 

1377 .. code-block:: text 

1378 

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

1380 

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

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

1383 contact information: 

1384 

1385 .. code-block:: text 

1386 

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

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

1389 +1-919-555-1234 

1390 

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

1392 representation of a MIME body part containing the contact 

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

1394 directory media type :rfc:`2425`: 

1395 

1396 .. code-block:: text 

1397 

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

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

1400 

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

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

1403 information: 

1404 

1405 .. code-block:: text 

1406 

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

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

1409""", 

1410) 

1411 

1412 

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

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

1415 

1416 return single_utc_property(name, docs) 

1417 

1418 

1419rfc_7953_dtstart_property = timezone_datetime_property( 

1420 "DTSTART", 

1421 """Start of the component. 

1422 

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

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

1425 

1426 Description: 

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

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

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

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

1431 

1432 """, 

1433) 

1434 

1435rfc_7953_dtend_property = timezone_datetime_property( 

1436 "DTEND", 

1437 """Start of the component. 

1438 

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

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

1441 

1442 Description: 

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

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

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

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

1447 """, 

1448) 

1449 

1450 

1451@property 

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

1453 """Compute the duration of this component. 

1454 

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

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

1457 :attr:`DTEND`/:attr:`DURATION`. 

1458 

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

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

1461 """ 

1462 duration = self.DURATION 

1463 if duration: 

1464 return duration 

1465 end = self.DTEND 

1466 if end is None: 

1467 return None 

1468 start = self.DTSTART 

1469 if start is None: 

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

1471 return end - start 

1472 

1473 

1474@property 

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

1476 """Compute the duration of this component. 

1477 

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

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

1480 :attr:`DTEND`/:attr:`DURATION`. 

1481 

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

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

1484 """ 

1485 duration = self.DURATION 

1486 if duration: 

1487 start = self.DTSTART 

1488 if start is None: 

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

1490 return start + duration 

1491 end = self.DTEND 

1492 if end is None: 

1493 return None 

1494 return end 

1495 

1496 

1497@rfc_7953_end_property.setter 

1498def rfc_7953_end_property(self, value: datetime): 

1499 self.DTEND = value 

1500 

1501 

1502@rfc_7953_end_property.deleter 

1503def rfc_7953_end_property(self): 

1504 del self.DTEND 

1505 

1506 

1507def set_duration_with_locking(component, duration, locked, end_property): 

1508 """Set the duration with explicit locking behavior for Event and Todo components. 

1509 

1510 Args: 

1511 component: The component to modify (Event or Todo) 

1512 duration: The duration to set, or None to convert to DURATION property 

1513 locked: Which property to keep unchanged ('start' or 'end') 

1514 end_property: The end property name ('DTEND' for Event, 'DUE' for Todo) 

1515 """ 

1516 # Convert to DURATION property if duration is None 

1517 if duration is None: 

1518 if "DURATION" in component: 

1519 return # Already has DURATION property 

1520 current_duration = component.duration 

1521 component.DURATION = current_duration 

1522 return 

1523 

1524 if not isinstance(duration, timedelta): 

1525 raise TypeError(f"Use timedelta, not {type(duration).__name__}.") 

1526 

1527 # Validate date/duration compatibility 

1528 start = component.DTSTART 

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

1530 raise InvalidCalendar( 

1531 "When DTSTART is a date, DURATION must be of days or weeks." 

1532 ) 

1533 

1534 if locked == "start": 

1535 # Keep start locked, adjust end 

1536 if start is None: 

1537 raise IncompleteComponent( 

1538 "Cannot set duration without DTSTART. Set start time first." 

1539 ) 

1540 component.DURATION = duration 

1541 elif locked == "end": 

1542 # Keep end locked, adjust start 

1543 current_end = component.end 

1544 component.DTSTART = current_end - duration 

1545 component.DURATION = duration 

1546 else: 

1547 raise ValueError(f"locked must be 'start' or 'end', not {locked!r}") 

1548 

1549 

1550__all__ = [ 

1551 "attendees_property", 

1552 "busy_type_property", 

1553 "categories_property", 

1554 "class_property", 

1555 "color_property", 

1556 "comments_property", 

1557 "contacts_property", 

1558 "create_single_property", 

1559 "description_property", 

1560 "descriptions_property", 

1561 "duration_property", 

1562 "exdates_property", 

1563 "location_property", 

1564 "multi_language_text_property", 

1565 "organizer_property", 

1566 "priority_property", 

1567 "property_del_duration", 

1568 "property_doc_duration_template", 

1569 "property_get_duration", 

1570 "property_set_duration", 

1571 "rdates_property", 

1572 "rfc_7953_dtend_property", 

1573 "rfc_7953_dtstart_property", 

1574 "rfc_7953_duration_property", 

1575 "rfc_7953_end_property", 

1576 "rrules_property", 

1577 "sequence_property", 

1578 "set_duration_with_locking", 

1579 "single_int_property", 

1580 "single_utc_property", 

1581 "status_property", 

1582 "summary_property", 

1583 "transparency_property", 

1584 "uid_property", 

1585 "url_property", 

1586]