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

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

109 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 CONCEPTS_TYPE_SETTER, 

11 LINKS_TYPE_SETTER, 

12 RELATED_TO_TYPE_SETTER, 

13 X_MOZ_LASTACK_property, 

14 X_MOZ_SNOOZE_TIME_property, 

15 attendees_property, 

16 categories_property, 

17 class_property, 

18 color_property, 

19 conferences_property, 

20 contacts_property, 

21 create_single_property, 

22 description_property, 

23 exdates_property, 

24 get_duration_property, 

25 get_end_property, 

26 get_start_end_duration_with_validation, 

27 get_start_property, 

28 images_property, 

29 location_property, 

30 organizer_property, 

31 priority_property, 

32 property_del_duration, 

33 property_doc_duration_template, 

34 property_get_duration, 

35 property_set_duration, 

36 rdates_property, 

37 rrules_property, 

38 sequence_property, 

39 set_duration_with_locking, 

40 set_end_with_locking, 

41 set_start_with_locking, 

42 status_property, 

43 summary_property, 

44 transparency_property, 

45 uid_property, 

46 url_property, 

47) 

48from icalendar.cal.component import Component 

49from icalendar.cal.examples import get_example 

50 

51if TYPE_CHECKING: 

52 from collections.abc import Iterable, Sequence 

53 

54 from icalendar.alarms import Alarms 

55 from icalendar.enums import CLASS, STATUS, TRANSP 

56 from icalendar.prop import vCalAddress 

57 from icalendar.prop.conference import Conference 

58 

59 

60class Event(Component): 

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

62 

63 Description: 

64 A "VEVENT" calendar component is a grouping of 

65 component properties, possibly including "VALARM" calendar 

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

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

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

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

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

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

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

73 searches for busy time. 

74 

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

76 anniversary or daily reminder within a calendar. These events 

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

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

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

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

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

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

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

84 

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

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

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

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

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

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

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

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

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

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

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

96 

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

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

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

100 component with the "RELATED-TO" property. 

101 

102 Examples: 

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

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

105 searches for busy time: 

106 

107 .. code-block:: ics 

108 

109 BEGIN:VEVENT 

110 UID:19970901T130000Z-123401@example.com 

111 DTSTAMP:19970901T130000Z 

112 DTSTART:19970903T163000Z 

113 DTEND:19970903T190000Z 

114 SUMMARY:Annual Employee Review 

115 CLASS:PRIVATE 

116 CATEGORIES:BUSINESS,HUMAN RESOURCES 

117 END:VEVENT 

118 

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

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

121 transparent, to searches for busy time: 

122 

123 .. code-block:: ics 

124 

125 BEGIN:VEVENT 

126 UID:19970901T130000Z-123402@example.com 

127 DTSTAMP:19970901T130000Z 

128 DTSTART:19970401T163000Z 

129 DTEND:19970402T010000Z 

130 SUMMARY:Laurel is in sensitivity awareness class. 

131 CLASS:PUBLIC 

132 CATEGORIES:BUSINESS,HUMAN RESOURCES 

133 TRANSP:TRANSPARENT 

134 END:VEVENT 

135 

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

137 used to represent an anniversary that will occur annually: 

138 

139 .. code-block:: ics 

140 

141 BEGIN:VEVENT 

142 UID:19970901T130000Z-123403@example.com 

143 DTSTAMP:19970901T130000Z 

144 DTSTART;VALUE=DATE:19971102 

145 SUMMARY:Our Blissful Anniversary 

146 TRANSP:TRANSPARENT 

147 CLASS:CONFIDENTIAL 

148 CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION 

149 RRULE:FREQ=YEARLY 

150 END:VEVENT 

151 

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

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

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

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

156 non-inclusive end of the event. 

157 

158 .. code-block:: ics 

159 

160 BEGIN:VEVENT 

161 UID:20070423T123432Z-541111@example.com 

162 DTSTAMP:20070423T123432Z 

163 DTSTART;VALUE=DATE:20070628 

164 DTEND;VALUE=DATE:20070709 

165 SUMMARY:Festival International de Jazz de Montreal 

166 TRANSP:TRANSPARENT 

167 END:VEVENT 

168 

169 Create a new Event: 

170 

171 .. code-block:: python 

172 

173 >>> from icalendar import Event 

174 >>> from datetime import datetime 

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

176 >>> print(event.to_ical()) 

177 BEGIN:VEVENT 

178 DTSTART:20210101T123000 

179 DTSTAMP:20250517T080612Z 

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

181 END:VEVENT 

182 

183 """ 

184 

185 name = "VEVENT" 

186 

187 canonical_order = ( 

188 "SUMMARY", 

189 "DTSTART", 

190 "DTEND", 

191 "DURATION", 

192 "DTSTAMP", 

193 "UID", 

194 "RECURRENCE-ID", 

195 "SEQUENCE", 

196 "RRULE", 

197 "RDATE", 

198 "EXDATE", 

199 ) 

200 

201 required = ( 

202 "UID", 

203 "DTSTAMP", 

204 ) 

205 singletons = ( 

206 "CLASS", 

207 "CREATED", 

208 "COLOR", 

209 "DESCRIPTION", 

210 "DTSTART", 

211 "GEO", 

212 "LAST-MODIFIED", 

213 "LOCATION", 

214 "ORGANIZER", 

215 "PRIORITY", 

216 "DTSTAMP", 

217 "SEQUENCE", 

218 "STATUS", 

219 "SUMMARY", 

220 "TRANSP", 

221 "URL", 

222 "RECURRENCE-ID", 

223 "DTEND", 

224 "DURATION", 

225 "UID", 

226 ) 

227 exclusive = ( 

228 "DTEND", 

229 "DURATION", 

230 ) 

231 multiple = ( 

232 "ATTACH", 

233 "ATTENDEE", 

234 "CATEGORIES", 

235 "COMMENT", 

236 "CONTACT", 

237 "EXDATE", 

238 "RSTATUS", 

239 "RELATED", 

240 "RESOURCES", 

241 "RDATE", 

242 "RRULE", 

243 ) 

244 ignore_exceptions = True 

245 

246 @property 

247 def alarms(self) -> Alarms: 

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

249 

250 >>> from icalendar import Event 

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

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

253 1 

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

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

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

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

258 True 

259 

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

261 RDATE, EXDATE, and RRULE properties. 

262 """ 

263 from icalendar.alarms import Alarms 

264 

265 return Alarms(self) 

266 

267 @classmethod 

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

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

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

271 

272 DTSTART = create_single_property( 

273 "DTSTART", 

274 "dt", 

275 (datetime, date), 

276 date, 

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

278 ) 

279 DTEND = create_single_property( 

280 "DTEND", 

281 "dt", 

282 (datetime, date), 

283 date, 

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

285 ) 

286 

287 def _get_start_end_duration(self): 

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

289 return get_start_end_duration_with_validation( 

290 self, "DTSTART", "DTEND", "VEVENT" 

291 ) 

292 

293 DURATION = property( 

294 property_get_duration, 

295 property_set_duration, 

296 property_del_duration, 

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

298 ) 

299 

300 @property 

301 def duration(self) -> timedelta: 

302 """The duration of the VEVENT. 

303 

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

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

306 duration. 

307 

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

309 start locked. 

310 

311 Setting the duration will: 

312 

313 1. Keep the start time locked (unchanged) 

314 2. Adjust the end time to start + duration 

315 3. Remove any existing DTEND property 

316 4. Set the DURATION property 

317 """ 

318 return get_duration_property(self) 

319 

320 @duration.setter 

321 def duration(self, value: timedelta): 

322 if not isinstance(value, timedelta): 

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

324 

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

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

327 

328 @property 

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

330 """The start of the event. 

331 

332 Invalid values raise an InvalidCalendar. 

333 If there is no start, we also raise an IncompleteComponent error. 

334 

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

336 

337 >>> from datetime import datetime 

338 >>> from icalendar import Event 

339 >>> event = Event() 

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

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

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

343 datetime.timedelta(seconds=1800) 

344 >>> print(event.to_ical()) 

345 BEGIN:VEVENT 

346 DTSTART:20210101T120000 

347 DTEND:20210101T123000 

348 END:VEVENT 

349 """ 

350 return get_start_property(self) 

351 

352 @start.setter 

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

354 """Set the start.""" 

355 self.DTSTART = start 

356 

357 @property 

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

359 """The end of the event. 

360 

361 Invalid values raise an InvalidCalendar error. 

362 If there is no end, we also raise an IncompleteComponent error. 

363 """ 

364 return get_end_property(self, "DTEND") 

365 

366 @end.setter 

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

368 """Set the end.""" 

369 self.DTEND = end 

370 

371 def set_duration( 

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

373 ): 

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

375 

376 Parameters: 

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

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

379 """ 

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

381 

382 def set_start( 

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

384 ): 

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

386 

387 Parameters: 

388 start: The start time to set 

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

390 for auto-detect) 

391 """ 

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

393 

394 def set_end( 

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

396 ): 

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

398 

399 Parameters: 

400 end: The end time to set 

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

402 """ 

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

404 

405 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

406 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

407 color = color_property 

408 sequence = sequence_property 

409 categories = categories_property 

410 rdates = rdates_property 

411 exdates = exdates_property 

412 rrules = rrules_property 

413 uid = uid_property 

414 summary = summary_property 

415 description = description_property 

416 classification = class_property 

417 url = url_property 

418 organizer = organizer_property 

419 location = location_property 

420 priority = priority_property 

421 contacts = contacts_property 

422 transparency = transparency_property 

423 status = status_property 

424 attendees = attendees_property 

425 images = images_property 

426 conferences = conferences_property 

427 from icalendar.attr import RECURRENCE_ID 

428 

429 @classmethod 

430 def new( 

431 cls, 

432 /, 

433 attendees: list[vCalAddress] | None = None, 

434 categories: Sequence[str] = (), 

435 classification: CLASS | None = None, 

436 color: str | None = None, 

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

438 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

441 created: date | None = None, 

442 description: str | None = None, 

443 end: date | datetime | None = None, 

444 last_modified: date | None = None, 

445 links: LINKS_TYPE_SETTER = None, 

446 location: str | None = None, 

447 organizer: vCalAddress | str | None = None, 

448 priority: int | None = None, 

449 recurrence_id: date | datetime | None = None, 

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

451 related_to: RELATED_TO_TYPE_SETTER = None, 

452 sequence: int | None = None, 

453 stamp: date | None = None, 

454 start: date | datetime | None = None, 

455 status: STATUS | None = None, 

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

457 transparency: TRANSP | None = None, 

458 summary: str | None = None, 

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

460 url: str | None = None, 

461 ): 

462 """Create a new event with all required properties. 

463 

464 This creates a new Event in accordance with :rfc:`5545`. 

465 

466 Parameters: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

487 If None, this is set to the current time. 

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

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

490 subcomponents: The subcomponents of the event. 

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

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

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

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

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

496 

497 Returns: 

498 :class:`Event` 

499 

500 Raises: 

501 ~error.InvalidCalendar: If the content is not valid 

502 according to :rfc:`5545`. 

503 

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

505 """ 

506 event: Event = super().new( 

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

508 created=created, 

509 last_modified=last_modified, 

510 comments=comments, 

511 links=links, 

512 related_to=related_to, 

513 refids=refids, 

514 concepts=concepts, 

515 subcomponents=subcomponents, 

516 ) 

517 event.summary = summary 

518 event.description = description 

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

520 event.start = start 

521 event.end = end 

522 event.color = color 

523 event.categories = categories 

524 event.sequence = sequence 

525 event.classification = classification 

526 event.url = url 

527 event.organizer = organizer 

528 event.location = location 

529 event.priority = priority 

530 event.transparency = transparency 

531 event.contacts = contacts 

532 event.status = status 

533 event.attendees = attendees 

534 event.conferences = conferences 

535 event.RECURRENCE_ID = recurrence_id 

536 

537 if cls._validate_new: 

538 cls._validate_start_and_end(start, end) 

539 return event 

540 

541 

542__all__ = ["Event"]