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 """ 

190 

191 name = "VEVENT" 

192 

193 canonical_order = ( 

194 "SUMMARY", 

195 "DTSTART", 

196 "DTEND", 

197 "DURATION", 

198 "DTSTAMP", 

199 "UID", 

200 "RECURRENCE-ID", 

201 "SEQUENCE", 

202 "RRULE", 

203 "RDATE", 

204 "EXDATE", 

205 ) 

206 

207 required = ( 

208 "UID", 

209 "DTSTAMP", 

210 ) 

211 singletons = ( 

212 "CLASS", 

213 "CREATED", 

214 "COLOR", 

215 "DESCRIPTION", 

216 "DTSTART", 

217 "GEO", 

218 "LAST-MODIFIED", 

219 "LOCATION", 

220 "ORGANIZER", 

221 "PRIORITY", 

222 "DTSTAMP", 

223 "SEQUENCE", 

224 "STATUS", 

225 "SUMMARY", 

226 "TRANSP", 

227 "URL", 

228 "RECURRENCE-ID", 

229 "DTEND", 

230 "DURATION", 

231 "UID", 

232 ) 

233 exclusive = ( 

234 "DTEND", 

235 "DURATION", 

236 ) 

237 multiple = ( 

238 "ATTACH", 

239 "ATTENDEE", 

240 "CATEGORIES", 

241 "COMMENT", 

242 "CONTACT", 

243 "EXDATE", 

244 "REQUEST-STATUS", 

245 "RELATED", 

246 "RESOURCES", 

247 "RDATE", 

248 "RRULE", 

249 ) 

250 ignore_exceptions = True 

251 

252 @property 

253 def alarms(self) -> Alarms: 

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

255 

256 >>> from icalendar import Event 

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

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

259 1 

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

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

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

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

264 True 

265 

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

267 RDATE, EXDATE, and RRULE properties. 

268 """ 

269 from icalendar.alarms import Alarms 

270 

271 return Alarms(self) 

272 

273 @classmethod 

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

275 """Return the calendar example with the given name.""" 

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

277 

278 DTSTART = create_single_property( 

279 "DTSTART", 

280 "dt", 

281 (datetime, date), 

282 date, 

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

284 ) 

285 DTEND = create_single_property( 

286 "DTEND", 

287 "dt", 

288 (datetime, date), 

289 date, 

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

291 ) 

292 

293 def _get_start_end_duration(self): 

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

295 return get_start_end_duration_with_validation( 

296 self, "DTSTART", "DTEND", "VEVENT" 

297 ) 

298 

299 DURATION = property( 

300 property_get_duration, 

301 property_set_duration, 

302 property_del_duration, 

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

304 ) 

305 

306 @property 

307 def duration(self) -> timedelta: 

308 """The duration of the VEVENT. 

309 

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

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

312 duration. 

313 

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

315 start locked. 

316 

317 Setting the duration will do the following. 

318 

319 1. Keep the start time locked (unchanged) 

320 2. Adjust the end time to start + duration 

321 3. Remove any existing DTEND property 

322 4. Set the DURATION property 

323 """ 

324 return get_duration_property(self) 

325 

326 @duration.setter 

327 def duration(self, value: timedelta): 

328 if not isinstance(value, timedelta): 

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

330 

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

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

333 

334 @property 

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

336 """The start of the event. 

337 

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

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

340 

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

342 

343 >>> from datetime import datetime 

344 >>> from icalendar import Event 

345 >>> event = Event() 

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

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

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

349 datetime.timedelta(seconds=1800) 

350 >>> print(event.to_ical()) 

351 BEGIN:VEVENT 

352 DTSTART:20210101T120000 

353 DTEND:20210101T123000 

354 END:VEVENT 

355 """ 

356 return get_start_property(self) 

357 

358 @start.setter 

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

360 """Set the start.""" 

361 self.DTSTART = start 

362 

363 @property 

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

365 """The end of the event. 

366 

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

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

369 """ 

370 return get_end_property(self, "DTEND") 

371 

372 @end.setter 

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

374 """Set the end.""" 

375 self.DTEND = end 

376 

377 def set_duration( 

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

379 ): 

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

381 

382 Parameters: 

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

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

385 """ 

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

387 

388 def set_start( 

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

390 ): 

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

392 

393 Parameters: 

394 start: The start time to set 

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

396 for auto-detect) 

397 """ 

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

399 

400 def set_end( 

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

402 ): 

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

404 

405 Parameters: 

406 end: The end time to set 

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

408 """ 

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

410 

411 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

412 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

413 color = color_property 

414 sequence = sequence_property 

415 categories = categories_property 

416 rdates = rdates_property 

417 exdates = exdates_property 

418 rrules = rrules_property 

419 REQUEST_STATUS = REQUEST_STATUS_property 

420 RESOURCES = RESOURCES_property 

421 uid = uid_property 

422 summary = summary_property 

423 description = description_property 

424 classification = class_property 

425 url = url_property 

426 organizer = organizer_property 

427 location = location_property 

428 priority = priority_property 

429 contacts = contacts_property 

430 transparency = transparency_property 

431 status = status_property 

432 attendees = attendees_property 

433 attachments = attachments_property 

434 images = images_property 

435 conferences = conferences_property 

436 from icalendar.attr import RECURRENCE_ID 

437 

438 @classmethod 

439 def new( 

440 cls, 

441 /, 

442 attachments: ATTACHMENTS_TYPE_SETTER = None, 

443 attendees: ATTENDEE_TYPE_SETTER = None, 

444 categories: Sequence[str] = (), 

445 classification: CLASS | None = None, 

446 color: str | None = None, 

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

448 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

451 created: date | None = None, 

452 description: str | None = None, 

453 end: date | datetime | None = None, 

454 last_modified: date | None = None, 

455 links: LINKS_TYPE_SETTER = None, 

456 location: str | None = None, 

457 organizer: vCalAddress | str | None = None, 

458 priority: int | None = None, 

459 recurrence_id: date | datetime | None = None, 

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

461 related_to: RELATED_TO_TYPE_SETTER = None, 

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

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

464 sequence: int | None = None, 

465 stamp: date | None = None, 

466 start: date | datetime | None = None, 

467 status: STATUS | None = None, 

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

469 transparency: TRANSP | None = None, 

470 summary: str | None = None, 

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

472 url: str | None = None, 

473 ) -> Self: 

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

475 

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

477 

478 Parameters: 

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

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

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

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

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

484 comments: The :attr:`~icalendar.Component.comments` of the event. 

485 concepts: The :attr:`~icalendar.Component.concepts` of the event. 

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

487 created: The :attr:`~icalendar.Component.created` of the event. 

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

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

490 last_modified: The :attr:`~icalendar.Component.last_modified` of the event. 

491 links: The :attr:`~icalendar.Component.links` of the event. 

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

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

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

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

496 refids: :attr:`~icalendar.Component.refids` of the event. 

497 related_to: :attr:`~icalendar.Component.related_to` of the event. 

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

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

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

501 stamp: The :attr:`~icalendar.Component.stamp` of the event. 

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

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

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

505 subcomponents: The subcomponents of the event. 

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

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

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

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

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

511 

512 Returns: 

513 :class:`Event` 

514 

515 Raises: 

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

517 according to :rfc:`5545`. 

518 

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

520 """ 

521 event: Self = super().new( 

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

523 created=created, 

524 last_modified=last_modified, 

525 comments=comments, 

526 links=links, 

527 related_to=related_to, 

528 refids=refids, 

529 concepts=concepts, 

530 subcomponents=subcomponents, 

531 ) 

532 event.summary = summary 

533 event.description = description 

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

535 event.start = start 

536 event.end = end 

537 event.color = color 

538 event.categories = categories 

539 event.sequence = sequence 

540 event.classification = classification 

541 event.url = url 

542 event.organizer = organizer 

543 event.location = location 

544 event.priority = priority 

545 event.transparency = transparency 

546 event.attachments = attachments 

547 event.contacts = contacts 

548 event.status = status 

549 event.REQUEST_STATUS = request_status 

550 event.RESOURCES = resources 

551 event.attendees = attendees 

552 event.conferences = conferences 

553 event.RECURRENCE_ID = recurrence_id 

554 

555 if cls._validate_new: 

556 cls._validate_start_and_end(start, end) 

557 return event 

558 

559 

560__all__ = ["Event"]