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

106 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, Sequence 

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 icalendar.alarms import Alarms 

53 from icalendar.enums import CLASS, STATUS, TRANSP 

54 from icalendar.prop import vCalAddress 

55 from icalendar.prop.conference import Conference 

56 

57 

58class Event(Component): 

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

60 

61 Description: 

62 A "VEVENT" calendar component is a grouping of 

63 component properties, possibly including "VALARM" calendar 

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

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

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

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

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

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

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

71 searches for busy time. 

72 

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

74 anniversary or daily reminder within a calendar. These events 

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

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

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

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

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

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

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

82 

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

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

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

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

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

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

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

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

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

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

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

94 

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

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

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

98 component with the "RELATED-TO" property. 

99 

100 Examples: 

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

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

103 searches for busy time: 

104 

105 .. code-block:: text 

106 

107 BEGIN:VEVENT 

108 UID:19970901T130000Z-123401@example.com 

109 DTSTAMP:19970901T130000Z 

110 DTSTART:19970903T163000Z 

111 DTEND:19970903T190000Z 

112 SUMMARY:Annual Employee Review 

113 CLASS:PRIVATE 

114 CATEGORIES:BUSINESS,HUMAN RESOURCES 

115 END:VEVENT 

116 

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

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

119 transparent, to searches for busy time: 

120 

121 .. code-block:: text 

122 

123 BEGIN:VEVENT 

124 UID:19970901T130000Z-123402@example.com 

125 DTSTAMP:19970901T130000Z 

126 DTSTART:19970401T163000Z 

127 DTEND:19970402T010000Z 

128 SUMMARY:Laurel is in sensitivity awareness class. 

129 CLASS:PUBLIC 

130 CATEGORIES:BUSINESS,HUMAN RESOURCES 

131 TRANSP:TRANSPARENT 

132 END:VEVENT 

133 

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

135 used to represent an anniversary that will occur annually: 

136 

137 .. code-block:: text 

138 

139 BEGIN:VEVENT 

140 UID:19970901T130000Z-123403@example.com 

141 DTSTAMP:19970901T130000Z 

142 DTSTART;VALUE=DATE:19971102 

143 SUMMARY:Our Blissful Anniversary 

144 TRANSP:TRANSPARENT 

145 CLASS:CONFIDENTIAL 

146 CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION 

147 RRULE:FREQ=YEARLY 

148 END:VEVENT 

149 

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

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

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

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

154 non-inclusive end of the event. 

155 

156 .. code-block:: text 

157 

158 BEGIN:VEVENT 

159 UID:20070423T123432Z-541111@example.com 

160 DTSTAMP:20070423T123432Z 

161 DTSTART;VALUE=DATE:20070628 

162 DTEND;VALUE=DATE:20070709 

163 SUMMARY:Festival International de Jazz de Montreal 

164 TRANSP:TRANSPARENT 

165 END:VEVENT 

166 

167 Create a new Event: 

168 

169 .. code-block:: python 

170 

171 >>> from icalendar import Event 

172 >>> from datetime import datetime 

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

174 >>> print(event.to_ical()) 

175 BEGIN:VEVENT 

176 DTSTART:20210101T123000 

177 DTSTAMP:20250517T080612Z 

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

179 END:VEVENT 

180 

181 """ 

182 

183 name = "VEVENT" 

184 

185 canonical_order = ( 

186 "SUMMARY", 

187 "DTSTART", 

188 "DTEND", 

189 "DURATION", 

190 "DTSTAMP", 

191 "UID", 

192 "RECURRENCE-ID", 

193 "SEQUENCE", 

194 "RRULE", 

195 "RDATE", 

196 "EXDATE", 

197 ) 

198 

199 required = ( 

200 "UID", 

201 "DTSTAMP", 

202 ) 

203 singletons = ( 

204 "CLASS", 

205 "CREATED", 

206 "COLOR", 

207 "DESCRIPTION", 

208 "DTSTART", 

209 "GEO", 

210 "LAST-MODIFIED", 

211 "LOCATION", 

212 "ORGANIZER", 

213 "PRIORITY", 

214 "DTSTAMP", 

215 "SEQUENCE", 

216 "STATUS", 

217 "SUMMARY", 

218 "TRANSP", 

219 "URL", 

220 "RECURRENCE-ID", 

221 "DTEND", 

222 "DURATION", 

223 "UID", 

224 ) 

225 exclusive = ( 

226 "DTEND", 

227 "DURATION", 

228 ) 

229 multiple = ( 

230 "ATTACH", 

231 "ATTENDEE", 

232 "CATEGORIES", 

233 "COMMENT", 

234 "CONTACT", 

235 "EXDATE", 

236 "RSTATUS", 

237 "RELATED", 

238 "RESOURCES", 

239 "RDATE", 

240 "RRULE", 

241 ) 

242 ignore_exceptions = True 

243 

244 @property 

245 def alarms(self) -> Alarms: 

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

247 

248 >>> from icalendar import Event 

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

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

251 1 

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

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

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

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

256 True 

257 

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

259 RDATE, EXDATE, and RRULE properties. 

260 """ 

261 from icalendar.alarms import Alarms 

262 

263 return Alarms(self) 

264 

265 @classmethod 

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

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

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

269 

270 DTSTART = create_single_property( 

271 "DTSTART", 

272 "dt", 

273 (datetime, date), 

274 date, 

275 'The "DTSTART" property for a "VEVENT" specifies the inclusive start of the event.', # noqa: E501 

276 ) 

277 DTEND = create_single_property( 

278 "DTEND", 

279 "dt", 

280 (datetime, date), 

281 date, 

282 'The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event.', # noqa: E501 

283 ) 

284 

285 def _get_start_end_duration(self): 

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

287 return get_start_end_duration_with_validation( 

288 self, "DTSTART", "DTEND", "VEVENT" 

289 ) 

290 

291 DURATION = property( 

292 property_get_duration, 

293 property_set_duration, 

294 property_del_duration, 

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

296 ) 

297 

298 @property 

299 def duration(self) -> timedelta: 

300 """The duration of the VEVENT. 

301 

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

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

304 duration. 

305 

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

307 start locked. 

308 

309 Setting the duration will: 

310 

311 1. Keep the start time locked (unchanged) 

312 2. Adjust the end time to start + duration 

313 3. Remove any existing DTEND property 

314 4. Set the DURATION property 

315 """ 

316 return get_duration_property(self) 

317 

318 @duration.setter 

319 def duration(self, value: timedelta): 

320 if not isinstance(value, timedelta): 

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

322 

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

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

325 

326 @property 

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

328 """The start of the event. 

329 

330 Invalid values raise an InvalidCalendar. 

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

332 

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

334 

335 >>> from datetime import datetime 

336 >>> from icalendar import Event 

337 >>> event = Event() 

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

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

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

341 datetime.timedelta(seconds=1800) 

342 >>> print(event.to_ical()) 

343 BEGIN:VEVENT 

344 DTSTART:20210101T120000 

345 DTEND:20210101T123000 

346 END:VEVENT 

347 """ 

348 return get_start_property(self) 

349 

350 @start.setter 

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

352 """Set the start.""" 

353 self.DTSTART = start 

354 

355 @property 

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

357 """The end of the event. 

358 

359 Invalid values raise an InvalidCalendar error. 

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

361 """ 

362 return get_end_property(self, "DTEND") 

363 

364 @end.setter 

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

366 """Set the end.""" 

367 self.DTEND = end 

368 

369 def set_duration( 

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

371 ): 

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

373 

374 Args: 

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

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

377 """ 

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

379 

380 def set_start( 

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

382 ): 

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

384 

385 Args: 

386 start: The start time to set 

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

388 for auto-detect) 

389 """ 

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

391 

392 def set_end( 

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

394 ): 

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

396 

397 Args: 

398 end: The end time to set 

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

400 """ 

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

402 

403 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

404 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

405 color = color_property 

406 sequence = sequence_property 

407 categories = categories_property 

408 rdates = rdates_property 

409 exdates = exdates_property 

410 rrules = rrules_property 

411 uid = uid_property 

412 summary = summary_property 

413 description = description_property 

414 classification = class_property 

415 url = url_property 

416 organizer = organizer_property 

417 location = location_property 

418 priority = priority_property 

419 contacts = contacts_property 

420 transparency = transparency_property 

421 status = status_property 

422 attendees = attendees_property 

423 images = images_property 

424 conferences = conferences_property 

425 

426 @classmethod 

427 def new( 

428 cls, 

429 /, 

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

431 categories: Sequence[str] = (), 

432 classification: CLASS | None = None, 

433 color: str | None = None, 

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

435 concepts: CONCEPTS_TYPE_SETTER = None, 

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

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

438 created: date | None = None, 

439 description: str | None = None, 

440 end: date | datetime | None = None, 

441 last_modified: date | None = None, 

442 links: LINKS_TYPE_SETTER = None, 

443 location: str | None = None, 

444 organizer: vCalAddress | str | None = None, 

445 priority: int | None = None, 

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

447 related_to: RELATED_TO_TYPE_SETTER = None, 

448 sequence: int | None = None, 

449 stamp: date | None = None, 

450 start: date | datetime | None = None, 

451 status: STATUS | None = None, 

452 transparency: TRANSP | None = None, 

453 summary: str | None = None, 

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

455 url: str | None = None, 

456 ): 

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

458 

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

460 

461 Arguments: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

489 

490 Returns: 

491 :class:`Event` 

492 

493 Raises: 

494 ~error.InvalidCalendar: If the content is not valid according to :rfc:`5545`. 

495 

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

497 """ 

498 event: Event = super().new( 

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

500 created=created, 

501 last_modified=last_modified, 

502 comments=comments, 

503 links=links, 

504 related_to=related_to, 

505 refids=refids, 

506 concepts=concepts, 

507 ) 

508 event.summary = summary 

509 event.description = description 

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

511 event.start = start 

512 event.end = end 

513 event.color = color 

514 event.categories = categories 

515 event.sequence = sequence 

516 event.classification = classification 

517 event.url = url 

518 event.organizer = organizer 

519 event.location = location 

520 event.priority = priority 

521 event.transparency = transparency 

522 event.contacts = contacts 

523 event.status = status 

524 event.attendees = attendees 

525 event.conferences = conferences 

526 

527 if cls._validate_new: 

528 cls._validate_start_and_end(start, end) 

529 return event 

530 

531 

532__all__ = ["Event"]