Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/event.py: 59%

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

116 statements  

1""":rfc:`5545` VEVENT component.""" 

2 

3from __future__ import annotations 

4 

5import uuid 

6from datetime import date, datetime, timedelta 

7from typing import TYPE_CHECKING, Literal 

8 

9from icalendar.attr import ( 

10 ATTACHMENTS_TYPE_SETTER, 

11 ATTENDEE_TYPE_SETTER, 

12 CONCEPTS_TYPE_SETTER, 

13 LINKS_TYPE_SETTER, 

14 RELATED_TO_TYPE_SETTER, 

15 REQUEST_STATUS_property, 

16 RESOURCES_property, 

17 X_MOZ_LASTACK_property, 

18 X_MOZ_SNOOZE_TIME_property, 

19 attachments_property, 

20 attendees_property, 

21 categories_property, 

22 class_property, 

23 color_property, 

24 conferences_property, 

25 contacts_property, 

26 create_single_property, 

27 description_property, 

28 exdates_property, 

29 get_duration_property, 

30 get_end_property, 

31 get_start_end_duration_with_validation, 

32 get_start_property, 

33 images_property, 

34 location_property, 

35 organizer_property, 

36 priority_property, 

37 property_del_duration, 

38 property_doc_duration_template, 

39 property_get_duration, 

40 property_set_duration, 

41 rdates_property, 

42 rrules_property, 

43 sequence_property, 

44 set_duration_with_locking, 

45 set_end_with_locking, 

46 set_start_with_locking, 

47 status_property, 

48 summary_property, 

49 transparency_property, 

50 uid_property, 

51 url_property, 

52) 

53from icalendar.cal.component import Component 

54from icalendar.cal.examples import get_example 

55 

56if TYPE_CHECKING: 

57 from collections.abc import Iterable, Sequence 

58 

59 from icalendar.alarms import Alarms 

60 from icalendar.compatibility import Self 

61 from icalendar.enums import CLASS, STATUS, TRANSP 

62 from icalendar.prop import vCalAddress 

63 from icalendar.prop.conference import Conference 

64 

65 

66class Event(Component): 

67 """A grouping of component properties that describe an event. 

68 

69 Description: 

70 A "VEVENT" calendar component is a grouping of 

71 component properties, possibly including "VALARM" calendar 

72 components, that represents a scheduled amount of time on a 

73 calendar. For example, it can be an activity; such as a one-hour 

74 long, department meeting from 8:00 AM to 9:00 AM, tomorrow. 

75 Generally, an event will take up time on an individual calendar. 

76 Hence, the event will appear as an opaque interval in a search for 

77 busy time. Alternately, the event can have its Time Transparency 

78 set to "TRANSPARENT" in order to prevent blocking of the event in 

79 searches for busy time. 

80 

81 The "VEVENT" is also the calendar component used to specify an 

82 anniversary or daily reminder within a calendar. These events 

83 have a DATE value type for the "DTSTART" property instead of the 

84 default value type of DATE-TIME. If such a "VEVENT" has a "DTEND" 

85 property, it MUST be specified as a DATE value also. The 

86 anniversary type of "VEVENT" can span more than one date (i.e., 

87 "DTEND" property value is set to a calendar date after the 

88 "DTSTART" property value). If such a "VEVENT" has a "DURATION" 

89 property, it MUST be specified as a "dur-day" or "dur-week" value. 

90 

91 The "DTSTART" property for a "VEVENT" specifies the inclusive 

92 start of the event. For recurring events, it also specifies the 

93 very first instance in the recurrence set. The "DTEND" property 

94 for a "VEVENT" calendar component specifies the non-inclusive end 

95 of the event. For cases where a "VEVENT" calendar component 

96 specifies a "DTSTART" property with a DATE value type but no 

97 "DTEND" nor "DURATION" property, the event's duration is taken to 

98 be one day. For cases where a "VEVENT" calendar component 

99 specifies a "DTSTART" property with a DATE-TIME value type but no 

100 "DTEND" property, the event ends on the same calendar date and 

101 time of day specified by the "DTSTART" property. 

102 

103 The "VEVENT" calendar component cannot be nested within another 

104 calendar component. However, "VEVENT" calendar components can be 

105 related to each other or to a "VTODO" or to a "VJOURNAL" calendar 

106 component with the "RELATED-TO" property. 

107 

108 Examples: 

109 The following is an example of the "VEVENT" calendar 

110 component used to represent a meeting that will also be opaque to 

111 searches for busy time: 

112 

113 .. code-block:: ics 

114 

115 BEGIN:VEVENT 

116 UID:19970901T130000Z-123401@example.com 

117 DTSTAMP:19970901T130000Z 

118 DTSTART:19970903T163000Z 

119 DTEND:19970903T190000Z 

120 SUMMARY:Annual Employee Review 

121 CLASS:PRIVATE 

122 CATEGORIES:BUSINESS,HUMAN RESOURCES 

123 END:VEVENT 

124 

125 The following is an example of the "VEVENT" calendar component 

126 used to represent a reminder that will not be opaque, but rather 

127 transparent, to searches for busy time: 

128 

129 .. code-block:: ics 

130 

131 BEGIN:VEVENT 

132 UID:19970901T130000Z-123402@example.com 

133 DTSTAMP:19970901T130000Z 

134 DTSTART:19970401T163000Z 

135 DTEND:19970402T010000Z 

136 SUMMARY:Laurel is in sensitivity awareness class. 

137 CLASS:PUBLIC 

138 CATEGORIES:BUSINESS,HUMAN RESOURCES 

139 TRANSP:TRANSPARENT 

140 END:VEVENT 

141 

142 The following is an example of the "VEVENT" calendar component 

143 used to represent an anniversary that will occur annually: 

144 

145 .. code-block:: ics 

146 

147 BEGIN:VEVENT 

148 UID:19970901T130000Z-123403@example.com 

149 DTSTAMP:19970901T130000Z 

150 DTSTART;VALUE=DATE:19971102 

151 SUMMARY:Our Blissful Anniversary 

152 TRANSP:TRANSPARENT 

153 CLASS:CONFIDENTIAL 

154 CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION 

155 RRULE:FREQ=YEARLY 

156 END:VEVENT 

157 

158 The following is an example of the "VEVENT" calendar component 

159 used to represent a multi-day event scheduled from June 28th, 2007 

160 to July 8th, 2007 inclusively. Note that the "DTEND" property is 

161 set to July 9th, 2007, since the "DTEND" property specifies the 

162 non-inclusive end of the event. 

163 

164 .. code-block:: ics 

165 

166 BEGIN:VEVENT 

167 UID:20070423T123432Z-541111@example.com 

168 DTSTAMP:20070423T123432Z 

169 DTSTART;VALUE=DATE:20070628 

170 DTEND;VALUE=DATE:20070709 

171 SUMMARY:Festival International de Jazz de Montreal 

172 TRANSP:TRANSPARENT 

173 END:VEVENT 

174 

175 Create a new Event: 

176 

177 .. code-block:: python 

178 

179 >>> from icalendar import Event 

180 >>> from datetime import datetime 

181 >>> event = Event.new(start=datetime(2021, 1, 1, 12, 30, 0)) 

182 >>> print(event.to_ical()) 

183 BEGIN:VEVENT 

184 DTSTART:20210101T123000 

185 DTSTAMP:20250517T080612Z 

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

187 END:VEVENT 

188 

189 Get the example Event: 

190 

191 .. code-block:: pycon 

192 

193 >>> from icalendar import Event 

194 >>> event = Event.example() 

195 >>> print(event["SUMMARY"]) 

196 Meeting 

197 >>> len(event.alarms.times) 

198 2 

199 

200 """ 

201 

202 name = "VEVENT" 

203 

204 canonical_order = ( 

205 "SUMMARY", 

206 "DTSTART", 

207 "DTEND", 

208 "DURATION", 

209 "DTSTAMP", 

210 "UID", 

211 "RECURRENCE-ID", 

212 "SEQUENCE", 

213 "RRULE", 

214 "RDATE", 

215 "EXDATE", 

216 ) 

217 

218 required = ( 

219 "UID", 

220 "DTSTAMP", 

221 ) 

222 singletons = ( 

223 "CLASS", 

224 "CREATED", 

225 "COLOR", 

226 "DESCRIPTION", 

227 "DTSTART", 

228 "GEO", 

229 "LAST-MODIFIED", 

230 "LOCATION", 

231 "ORGANIZER", 

232 "PRIORITY", 

233 "DTSTAMP", 

234 "SEQUENCE", 

235 "STATUS", 

236 "SUMMARY", 

237 "TRANSP", 

238 "URL", 

239 "RECURRENCE-ID", 

240 "DTEND", 

241 "DURATION", 

242 "UID", 

243 ) 

244 exclusive = ( 

245 "DTEND", 

246 "DURATION", 

247 ) 

248 multiple = ( 

249 "ATTACH", 

250 "ATTENDEE", 

251 "CATEGORIES", 

252 "COMMENT", 

253 "CONTACT", 

254 "EXDATE", 

255 "REQUEST-STATUS", 

256 "RELATED", 

257 "RESOURCES", 

258 "RDATE", 

259 "RRULE", 

260 ) 

261 ignore_exceptions = True 

262 

263 @property 

264 def alarms(self) -> Alarms: 

265 """Compute the alarm times for this component. 

266 

267 >>> from icalendar import Event 

268 >>> event = Event.example("rfc_9074_example_1") 

269 >>> len(event.alarms.times) 

270 1 

271 >>> alarm_time = event.alarms.times[0] 

272 >>> alarm_time.trigger # The time when the alarm pops up 

273 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York')) 

274 >>> alarm_time.is_active() # This alarm has not been acknowledged 

275 True 

276 

277 Note that this only uses DTSTART and DTEND, but ignores 

278 RDATE, EXDATE, and RRULE properties. 

279 """ 

280 from icalendar.alarms import Alarms 

281 

282 return Alarms(self) 

283 

284 @classmethod 

285 def example(cls, name: str = "rfc_9074_example_3") -> Event: 

286 """Return the event example with the given name. 

287 

288 Example: 

289 

290 .. code-block:: pycon 

291 

292 >>> from icalendar import Event 

293 >>> event = Event.example() 

294 >>> print(event["SUMMARY"]) 

295 Meeting 

296 """ 

297 return cls.from_ical(get_example("events", name)) 

298 

299 DTSTART = create_single_property( 

300 "DTSTART", 

301 "dt", 

302 (datetime, date), 

303 date, 

304 'The "DTSTART" property for a "VEVENT" specifies the inclusive start of the event.', 

305 ) 

306 DTEND = create_single_property( 

307 "DTEND", 

308 "dt", 

309 (datetime, date), 

310 date, 

311 'The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event.', 

312 ) 

313 

314 def _get_start_end_duration(self): 

315 """Verify the calendar validity and return the right attributes.""" 

316 return get_start_end_duration_with_validation( 

317 self, "DTSTART", "DTEND", "VEVENT" 

318 ) 

319 

320 DURATION = property( 

321 property_get_duration, 

322 property_set_duration, 

323 property_del_duration, 

324 property_doc_duration_template.format(component="VEVENT"), 

325 ) 

326 

327 @property 

328 def duration(self) -> timedelta: 

329 """The duration of the VEVENT. 

330 

331 Returns the DURATION property if set, otherwise calculated from start and end. 

332 When setting duration, the end time is automatically calculated from start + 

333 duration. 

334 

335 You can set the duration to automatically adjust the end time while keeping 

336 start locked. 

337 

338 Setting the duration will do the following. 

339 

340 1. Keep the start time locked (unchanged) 

341 2. Adjust the end time to start + duration 

342 3. Remove any existing DTEND property 

343 4. Set the DURATION property 

344 """ 

345 return get_duration_property(self) 

346 

347 @duration.setter 

348 def duration(self, value: timedelta): 

349 if not isinstance(value, timedelta): 

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

351 

352 # Use the set_duration method with default start-locked behavior 

353 self.set_duration(value, locked="start") 

354 

355 @property 

356 def start(self) -> date | datetime: 

357 """The start of the event. 

358 

359 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar`. 

360 If there is no start, we also raise an :exc:`~icalendar.error.IncompleteComponent` error. 

361 

362 You can get the start, end and duration of an event as follows: 

363 

364 >>> from datetime import datetime 

365 >>> from icalendar import Event 

366 >>> event = Event() 

367 >>> event.start = datetime(2021, 1, 1, 12) 

368 >>> event.end = datetime(2021, 1, 1, 12, 30) # 30 minutes 

369 >>> event.duration # 1800 seconds == 30 minutes 

370 datetime.timedelta(seconds=1800) 

371 >>> print(event.to_ical()) 

372 BEGIN:VEVENT 

373 DTSTART:20210101T120000 

374 DTEND:20210101T123000 

375 END:VEVENT 

376 """ 

377 return get_start_property(self) 

378 

379 @start.setter 

380 def start(self, start: date | datetime | None): 

381 """Set the start.""" 

382 self.DTSTART = start 

383 

384 @property 

385 def end(self) -> date | datetime: 

386 """The end of the event. 

387 

388 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar` error. 

389 If there is no end, we also raise an :exc:`~icalendar.error.IncompleteComponent` error. 

390 """ 

391 return get_end_property(self, "DTEND") 

392 

393 @end.setter 

394 def end(self, end: date | datetime | None): 

395 """Set the end.""" 

396 self.DTEND = end 

397 

398 def set_duration( 

399 self, duration: timedelta | None, locked: Literal["start", "end"] = "start" 

400 ): 

401 """Set the duration of the event relative to either start or end. 

402 

403 Parameters: 

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

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

406 """ 

407 set_duration_with_locking(self, duration, locked, "DTEND") 

408 

409 def set_start( 

410 self, start: date | datetime, locked: Literal["duration", "end"] | None = None 

411 ): 

412 """Set the start and keep the duration or end of the event. 

413 

414 Parameters: 

415 start: The start time to set 

416 locked: Which property to keep unchanged ('duration', 'end', or None 

417 for auto-detect) 

418 """ 

419 set_start_with_locking(self, start, locked, "DTEND") 

420 

421 def set_end( 

422 self, end: date | datetime, locked: Literal["start", "duration"] = "start" 

423 ): 

424 """Set the end of the component, keeping either the start or the duration same. 

425 

426 Parameters: 

427 end: The end time to set 

428 locked: Which property to keep unchanged ('start' or 'duration') 

429 """ 

430 set_end_with_locking(self, end, locked, "DTEND") 

431 

432 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

433 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

434 color = color_property 

435 sequence = sequence_property 

436 categories = categories_property 

437 rdates = rdates_property 

438 exdates = exdates_property 

439 rrules = rrules_property 

440 REQUEST_STATUS = REQUEST_STATUS_property 

441 RESOURCES = RESOURCES_property 

442 uid = uid_property 

443 summary = summary_property 

444 description = description_property 

445 classification = class_property 

446 url = url_property 

447 organizer = organizer_property 

448 location = location_property 

449 priority = priority_property 

450 contacts = contacts_property 

451 transparency = transparency_property 

452 status = status_property 

453 attendees = attendees_property 

454 attachments = attachments_property 

455 images = images_property 

456 conferences = conferences_property 

457 from icalendar.attr import RECURRENCE_ID 

458 

459 @classmethod 

460 def new( 

461 cls, 

462 /, 

463 attachments: ATTACHMENTS_TYPE_SETTER = None, 

464 attendees: ATTENDEE_TYPE_SETTER = None, 

465 categories: Sequence[str] = (), 

466 classification: CLASS | None = None, 

467 color: str | None = None, 

468 comments: list[str] | str | None = None, 

469 concepts: CONCEPTS_TYPE_SETTER = None, 

470 conferences: list[Conference] | None = None, 

471 contacts: list[str] | str | None = None, 

472 created: date | None = None, 

473 description: str | None = None, 

474 end: date | datetime | None = None, 

475 last_modified: date | None = None, 

476 links: LINKS_TYPE_SETTER = None, 

477 location: str | None = None, 

478 organizer: vCalAddress | str | None = None, 

479 priority: int | None = None, 

480 recurrence_id: date | datetime | None = None, 

481 refids: list[str] | str | None = None, 

482 related_to: RELATED_TO_TYPE_SETTER = None, 

483 request_status: list[str] | str | None = None, 

484 resources: list[str] | str | None = None, 

485 sequence: int | None = None, 

486 stamp: date | None = None, 

487 start: date | datetime | None = None, 

488 status: STATUS | None = None, 

489 subcomponents: Iterable[Component] | None = None, 

490 transparency: TRANSP | None = None, 

491 summary: str | None = None, 

492 uid: str | uuid.UUID | None = None, 

493 url: str | None = None, 

494 ) -> Self: 

495 """Create a new event with the required properties of ``stamp`` and ``uid``. 

496 

497 This creates a new ``Event`` in accordance with :rfc:`5545#section-3.6.1`. 

498 

499 Parameters: 

500 attachments: The :attr:`attachments` of the event. 

501 attendees: The :attr:`attendees` of the event. 

502 categories: The :attr:`categories` of the event. 

503 classification: The :attr:`classification` of the event. 

504 color: The :attr:`color` of the event. 

505 comments: The :attr:`~icalendar.cal.component.Component.comments` of the event. 

506 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the event. 

507 conferences: The :attr:`conferences` of the event. 

508 created: The :attr:`~icalendar.cal.component.Component.created` of the event. 

509 description: The :attr:`description` of the event. 

510 end: The :attr:`end` of the event. 

511 last_modified: The :attr:`~icalendar.cal.component.Component.last_modified` of the event. 

512 links: The :attr:`~icalendar.cal.component.Component.links` of the event. 

513 location: The :attr:`location` of the event. 

514 organizer: The :attr:`organizer` of the event. 

515 priority: The :attr:`priority` of the event. 

516 recurrence_id: The :attr:`RECURRENCE_ID` of the event. 

517 refids: :attr:`~icalendar.cal.component.Component.refids` of the event. 

518 related_to: :attr:`~icalendar.cal.component.Component.related_to` of the event. 

519 request_status: The :attr:`REQUEST_STATUS` of the event. 

520 resources: The :attr:`RESOURCES` of the event. 

521 sequence: The :attr:`sequence` of the event. 

522 stamp: The :attr:`~icalendar.cal.component.Component.stamp` of the event. 

523 If ``None``, this is set to the current UTC time. 

524 start: The :attr:`start` of the event. 

525 status: The :attr:`status` of the event. 

526 subcomponents: The subcomponents of the event. 

527 summary: The :attr:`summary` of the event. 

528 transparency: The :attr:`transparency` of the event. 

529 uid: The :attr:`uid` of the event. 

530 If ``None``, this is set to a new :func:`uuid.uuid4`. 

531 url: The :attr:`url` of the event. 

532 

533 Returns: 

534 :class:`Event` 

535 

536 Raises: 

537 :exc:`~icalendar.error.InvalidCalendar`: If the content is not valid 

538 according to :rfc:`5545`. 

539 

540 .. warning:: As time progresses, we will be stricter with the validation. 

541 """ 

542 event: Self = super().new( 

543 stamp=stamp if stamp is not None else cls._utc_now(), 

544 created=created, 

545 last_modified=last_modified, 

546 comments=comments, 

547 links=links, 

548 related_to=related_to, 

549 refids=refids, 

550 concepts=concepts, 

551 subcomponents=subcomponents, 

552 ) 

553 event.summary = summary 

554 event.description = description 

555 event.uid = uid if uid is not None else uuid.uuid4() 

556 event.start = start 

557 event.end = end 

558 event.color = color 

559 event.categories = categories 

560 event.sequence = sequence 

561 event.classification = classification 

562 event.url = url 

563 event.organizer = organizer 

564 event.location = location 

565 event.priority = priority 

566 event.transparency = transparency 

567 event.attachments = attachments 

568 event.contacts = contacts 

569 event.status = status 

570 event.REQUEST_STATUS = request_status 

571 event.RESOURCES = resources 

572 event.attendees = attendees 

573 event.conferences = conferences 

574 event.RECURRENCE_ID = recurrence_id 

575 

576 if cls._validate_new: 

577 cls._validate_start_and_end(start, end) 

578 return event 

579 

580 

581__all__ = ["Event"]