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 X_MOZ_LASTACK_property, 

11 X_MOZ_SNOOZE_TIME_property, 

12 attendees_property, 

13 categories_property, 

14 class_property, 

15 color_property, 

16 conferences_property, 

17 contacts_property, 

18 create_single_property, 

19 description_property, 

20 exdates_property, 

21 images_property, 

22 get_duration_property, 

23 get_end_property, 

24 get_start_end_duration_with_validation, 

25 get_start_property, 

26 location_property, 

27 organizer_property, 

28 priority_property, 

29 property_del_duration, 

30 property_doc_duration_template, 

31 property_get_duration, 

32 property_set_duration, 

33 rdates_property, 

34 rrules_property, 

35 sequence_property, 

36 set_duration_with_locking, 

37 set_end_with_locking, 

38 set_start_with_locking, 

39 status_property, 

40 summary_property, 

41 transparency_property, 

42 uid_property, 

43 url_property, 

44) 

45from icalendar.cal.component import Component 

46from icalendar.cal.examples import get_example 

47 

48if TYPE_CHECKING: 

49 from icalendar.alarms import Alarms 

50 from icalendar.enums import CLASS, STATUS, TRANSP 

51 from icalendar.prop import vCalAddress 

52 from icalendar.prop.conference import Conference 

53 

54 

55class Event(Component): 

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

57 

58 Description: 

59 A "VEVENT" calendar component is a grouping of 

60 component properties, possibly including "VALARM" calendar 

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

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

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

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

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

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

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

68 searches for busy time. 

69 

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

71 anniversary or daily reminder within a calendar. These events 

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

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

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

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

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

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

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

79 

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

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

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

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

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

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

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

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

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

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

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

91 

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

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

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

95 component with the "RELATED-TO" property. 

96 

97 Examples: 

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

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

100 searches for busy time: 

101 

102 .. code-block:: text 

103 

104 BEGIN:VEVENT 

105 UID:19970901T130000Z-123401@example.com 

106 DTSTAMP:19970901T130000Z 

107 DTSTART:19970903T163000Z 

108 DTEND:19970903T190000Z 

109 SUMMARY:Annual Employee Review 

110 CLASS:PRIVATE 

111 CATEGORIES:BUSINESS,HUMAN RESOURCES 

112 END:VEVENT 

113 

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

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

116 transparent, to searches for busy time: 

117 

118 .. code-block:: text 

119 

120 BEGIN:VEVENT 

121 UID:19970901T130000Z-123402@example.com 

122 DTSTAMP:19970901T130000Z 

123 DTSTART:19970401T163000Z 

124 DTEND:19970402T010000Z 

125 SUMMARY:Laurel is in sensitivity awareness class. 

126 CLASS:PUBLIC 

127 CATEGORIES:BUSINESS,HUMAN RESOURCES 

128 TRANSP:TRANSPARENT 

129 END:VEVENT 

130 

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

132 used to represent an anniversary that will occur annually: 

133 

134 .. code-block:: text 

135 

136 BEGIN:VEVENT 

137 UID:19970901T130000Z-123403@example.com 

138 DTSTAMP:19970901T130000Z 

139 DTSTART;VALUE=DATE:19971102 

140 SUMMARY:Our Blissful Anniversary 

141 TRANSP:TRANSPARENT 

142 CLASS:CONFIDENTIAL 

143 CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION 

144 RRULE:FREQ=YEARLY 

145 END:VEVENT 

146 

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

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

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

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

151 non-inclusive end of the event. 

152 

153 .. code-block:: text 

154 

155 BEGIN:VEVENT 

156 UID:20070423T123432Z-541111@example.com 

157 DTSTAMP:20070423T123432Z 

158 DTSTART;VALUE=DATE:20070628 

159 DTEND;VALUE=DATE:20070709 

160 SUMMARY:Festival International de Jazz de Montreal 

161 TRANSP:TRANSPARENT 

162 END:VEVENT 

163 

164 Create a new Event: 

165 

166 .. code-block:: python 

167 

168 >>> from icalendar import Event 

169 >>> from datetime import datetime 

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

171 >>> print(event.to_ical()) 

172 BEGIN:VEVENT 

173 DTSTART:20210101T123000 

174 DTSTAMP:20250517T080612Z 

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

176 END:VEVENT 

177 

178 """ 

179 

180 name = "VEVENT" 

181 

182 canonical_order = ( 

183 "SUMMARY", 

184 "DTSTART", 

185 "DTEND", 

186 "DURATION", 

187 "DTSTAMP", 

188 "UID", 

189 "RECURRENCE-ID", 

190 "SEQUENCE", 

191 "RRULE", 

192 "RDATE", 

193 "EXDATE", 

194 ) 

195 

196 required = ( 

197 "UID", 

198 "DTSTAMP", 

199 ) 

200 singletons = ( 

201 "CLASS", 

202 "CREATED", 

203 "COLOR", 

204 "DESCRIPTION", 

205 "DTSTART", 

206 "GEO", 

207 "LAST-MODIFIED", 

208 "LOCATION", 

209 "ORGANIZER", 

210 "PRIORITY", 

211 "DTSTAMP", 

212 "SEQUENCE", 

213 "STATUS", 

214 "SUMMARY", 

215 "TRANSP", 

216 "URL", 

217 "RECURRENCE-ID", 

218 "DTEND", 

219 "DURATION", 

220 "UID", 

221 ) 

222 exclusive = ( 

223 "DTEND", 

224 "DURATION", 

225 ) 

226 multiple = ( 

227 "ATTACH", 

228 "ATTENDEE", 

229 "CATEGORIES", 

230 "COMMENT", 

231 "CONTACT", 

232 "EXDATE", 

233 "RSTATUS", 

234 "RELATED", 

235 "RESOURCES", 

236 "RDATE", 

237 "RRULE", 

238 ) 

239 ignore_exceptions = True 

240 

241 @property 

242 def alarms(self) -> Alarms: 

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

244 

245 >>> from icalendar import Event 

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

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

248 1 

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

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

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

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

253 True 

254 

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

256 RDATE, EXDATE, and RRULE properties. 

257 """ 

258 from icalendar.alarms import Alarms 

259 

260 return Alarms(self) 

261 

262 @classmethod 

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

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

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

266 

267 DTSTART = create_single_property( 

268 "DTSTART", 

269 "dt", 

270 (datetime, date), 

271 date, 

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

273 ) 

274 DTEND = create_single_property( 

275 "DTEND", 

276 "dt", 

277 (datetime, date), 

278 date, 

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

280 ) 

281 

282 def _get_start_end_duration(self): 

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

284 return get_start_end_duration_with_validation( 

285 self, "DTSTART", "DTEND", "VEVENT" 

286 ) 

287 

288 DURATION = property( 

289 property_get_duration, 

290 property_set_duration, 

291 property_del_duration, 

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

293 ) 

294 

295 @property 

296 def duration(self) -> timedelta: 

297 """The duration of the VEVENT. 

298 

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

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

301 duration. 

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

303 start locked. 

304 

305 Setting the duration will: 

306 1. Keep the start time locked (unchanged) 

307 2. Adjust the end time to start + duration 

308 3. Remove any existing DTEND property 

309 4. Set the DURATION property 

310 """ 

311 return get_duration_property(self) 

312 

313 @duration.setter 

314 def duration(self, value: timedelta): 

315 if not isinstance(value, timedelta): 

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

317 

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

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

320 

321 @property 

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

323 """The start of the event. 

324 

325 Invalid values raise an InvalidCalendar. 

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

327 

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

329 

330 >>> from datetime import datetime 

331 >>> from icalendar import Event 

332 >>> event = Event() 

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

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

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

336 datetime.timedelta(seconds=1800) 

337 >>> print(event.to_ical()) 

338 BEGIN:VEVENT 

339 DTSTART:20210101T120000 

340 DTEND:20210101T123000 

341 END:VEVENT 

342 """ 

343 return get_start_property(self) 

344 

345 @start.setter 

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

347 """Set the start.""" 

348 self.DTSTART = start 

349 

350 @property 

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

352 """The end of the event. 

353 

354 Invalid values raise an InvalidCalendar error. 

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

356 """ 

357 return get_end_property(self, "DTEND") 

358 

359 @end.setter 

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

361 """Set the end.""" 

362 self.DTEND = end 

363 

364 def set_duration( 

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

366 ): 

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

368 

369 Args: 

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

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

372 """ 

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

374 

375 def set_start( 

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

377 ): 

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

379 

380 Args: 

381 start: The start time to set 

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

383 for auto-detect) 

384 """ 

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

386 

387 def set_end( 

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

389 ): 

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

391 

392 Args: 

393 end: The end time to set 

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

395 """ 

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

397 

398 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property 

399 X_MOZ_LASTACK = X_MOZ_LASTACK_property 

400 color = color_property 

401 sequence = sequence_property 

402 categories = categories_property 

403 rdates = rdates_property 

404 exdates = exdates_property 

405 rrules = rrules_property 

406 uid = uid_property 

407 summary = summary_property 

408 description = description_property 

409 classification = class_property 

410 url = url_property 

411 organizer = organizer_property 

412 location = location_property 

413 priority = priority_property 

414 contacts = contacts_property 

415 transparency = transparency_property 

416 status = status_property 

417 attendees = attendees_property 

418 images = images_property 

419 conferences = conferences_property 

420 

421 @classmethod 

422 def new( 

423 cls, 

424 /, 

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

426 categories: Sequence[str] = (), 

427 classification: CLASS | None = None, 

428 color: str | None = None, 

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

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

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

432 created: date | None = None, 

433 description: str | None = None, 

434 end: date | datetime | None = None, 

435 last_modified: date | None = None, 

436 location: str | None = None, 

437 organizer: vCalAddress | str | None = None, 

438 priority: int | None = None, 

439 sequence: int | None = None, 

440 stamp: date | None = None, 

441 start: date | datetime | None = None, 

442 status: STATUS | None = None, 

443 transparency: TRANSP | None = None, 

444 summary: str | None = None, 

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

446 url: str | None = None, 

447 ): 

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

449 

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

451 

452 Arguments: 

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

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

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

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

457 comments: The :attr:`Component.comments` of the event. 

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

459 created: The :attr:`Component.created` of the event. 

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

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

462 last_modified: The :attr:`Component.last_modified` of the event. 

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

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

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

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

467 stamp: The :attr:`Component.stamp` of the event. 

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

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

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

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

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

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

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

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

476 

477 Returns: 

478 :class:`Event` 

479 

480 Raises: 

481 InvalidCalendar: If the content is not valid according to :rfc:`5545`. 

482 

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

484 """ 

485 event = super().new( 

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

487 created=created, 

488 last_modified=last_modified, 

489 comments=comments, 

490 ) 

491 event.summary = summary 

492 event.description = description 

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

494 event.start = start 

495 event.end = end 

496 event.color = color 

497 event.categories = categories 

498 event.sequence = sequence 

499 event.classification = classification 

500 event.url = url 

501 event.organizer = organizer 

502 event.location = location 

503 event.priority = priority 

504 event.transparency = transparency 

505 event.contacts = contacts 

506 event.status = status 

507 event.attendees = attendees 

508 event.conferences = conferences 

509 if cls._validate_new: 

510 cls._validate_start_and_end(start, end) 

511 return event 

512 

513 

514__all__ = ["Event"]