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

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

97 statements  

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

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Optional, Sequence 

6 

7from icalendar.attr import ( 

8 categories_property, 

9 multi_language_text_property, 

10 single_string_property, 

11 uid_property, 

12) 

13from icalendar.cal.component import Component 

14from icalendar.cal.examples import get_example 

15from icalendar.cal.timezone import Timezone 

16from icalendar.version import __version__ 

17 

18if TYPE_CHECKING: 

19 import uuid 

20 from datetime import date 

21 

22 from icalendar.cal.availability import Availability 

23 from icalendar.cal.event import Event 

24 from icalendar.cal.free_busy import FreeBusy 

25 from icalendar.cal.todo import Todo 

26 

27 

28class Calendar(Component): 

29 """ 

30 The "VCALENDAR" object is a collection of calendar information. 

31 This information can include a variety of components, such as 

32 "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE", or any 

33 other type of calendar component. 

34 

35 Examples: 

36 Create a new Calendar: 

37 

38 >>> from icalendar import Calendar 

39 >>> calendar = Calendar.new(name="My Calendar") 

40 >>> print(calendar.calendar_name) 

41 My Calendar 

42 

43 """ 

44 

45 name = "VCALENDAR" 

46 canonical_order = ( 

47 "VERSION", 

48 "PRODID", 

49 "CALSCALE", 

50 "METHOD", 

51 "DESCRIPTION", 

52 "X-WR-CALDESC", 

53 "NAME", 

54 "X-WR-CALNAME", 

55 ) 

56 required = ( 

57 "PRODID", 

58 "VERSION", 

59 ) 

60 singletons = ( 

61 "PRODID", 

62 "VERSION", 

63 "CALSCALE", 

64 "METHOD", 

65 "COLOR", # RFC 7986 

66 ) 

67 multiple = ( 

68 "CATEGORIES", # RFC 7986 

69 "DESCRIPTION", # RFC 7986 

70 "NAME", # RFC 7986 

71 ) 

72 

73 @classmethod 

74 def example(cls, name: str = "example") -> Calendar: 

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

76 return cls.from_ical(get_example("calendars", name)) 

77 

78 @classmethod 

79 def from_ical(cls, st, multiple=False): 

80 comps = Component.from_ical(st, multiple=True) 

81 all_timezones_so_far = True 

82 for comp in comps: 

83 for component in comp.subcomponents: 

84 if component.name == "VTIMEZONE": 

85 if not all_timezones_so_far: 

86 # If a preceding component refers to a VTIMEZONE defined 

87 # later in the source st 

88 # (forward references are allowed by RFC 5545), then the 

89 # earlier component may have 

90 # the wrong timezone attached. 

91 # However, during computation of comps, all VTIMEZONEs 

92 # observed do end up in 

93 # the timezone cache. So simply re-running from_ical will 

94 # rely on the cache 

95 # for those forward references to produce the correct result. 

96 # See test_create_america_new_york_forward_reference. 

97 return Component.from_ical(st, multiple) 

98 else: 

99 all_timezones_so_far = False 

100 

101 # No potentially forward VTIMEZONEs to worry about 

102 if multiple: 

103 return comps 

104 if len(comps) > 1: 

105 raise ValueError( 

106 cls._format_error( 

107 "Found multiple components where only one is allowed", st 

108 ) 

109 ) 

110 if len(comps) < 1: 

111 raise ValueError( 

112 cls._format_error( 

113 "Found no components where exactly one is required", st 

114 ) 

115 ) 

116 return comps[0] 

117 

118 @property 

119 def events(self) -> list[Event]: 

120 """All event components in the calendar. 

121 

122 This is a shortcut to get all events. 

123 Modifications do not change the calendar. 

124 Use :py:meth:`Component.add_component`. 

125 

126 >>> from icalendar import Calendar 

127 >>> calendar = Calendar.example() 

128 >>> event = calendar.events[0] 

129 >>> event.start 

130 datetime.date(2022, 1, 1) 

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

132 New Year's Day 

133 """ 

134 return self.walk("VEVENT") 

135 

136 @property 

137 def todos(self) -> list[Todo]: 

138 """All todo components in the calendar. 

139 

140 This is a shortcut to get all todos. 

141 Modifications do not change the calendar. 

142 Use :py:meth:`Component.add_component`. 

143 """ 

144 return self.walk("VTODO") 

145 

146 @property 

147 def availabilities(self) -> list[Availability]: 

148 """All :class:`Availability` components in the calendar. 

149 

150 This is a shortcut to get all availabilities. 

151 Modifications do not change the calendar. 

152 Use :py:meth:`Component.add_component`. 

153 """ 

154 return self.walk("VAVAILABILITY") 

155 

156 @property 

157 def freebusy(self) -> list[FreeBusy]: 

158 """All FreeBusy components in the calendar. 

159 

160 This is a shortcut to get all FreeBusy. 

161 Modifications do not change the calendar. 

162 Use :py:meth:`Component.add_component`. 

163 """ 

164 return self.walk("VFREEBUSY") 

165 

166 def get_used_tzids(self) -> set[str]: 

167 """The set of TZIDs in use. 

168 

169 This goes through the whole calendar to find all occurrences of 

170 timezone information like the TZID parameter in all attributes. 

171 

172 >>> from icalendar import Calendar 

173 >>> calendar = Calendar.example("timezone_rdate") 

174 >>> calendar.get_used_tzids() 

175 {'posix/Europe/Vaduz'} 

176 

177 Even if you use UTC, this will not show up. 

178 """ 

179 result = set() 

180 for _name, value in self.property_items(sorted=False): 

181 if hasattr(value, "params"): 

182 result.add(value.params.get("TZID")) 

183 return result - {None} 

184 

185 def get_missing_tzids(self) -> set[str]: 

186 """The set of missing timezone component tzids. 

187 

188 To create a :rfc:`5545` compatible calendar, 

189 all of these timezones should be added. 

190 """ 

191 tzids = self.get_used_tzids() 

192 for timezone in self.timezones: 

193 tzids.remove(timezone.tz_name) 

194 return tzids 

195 

196 @property 

197 def timezones(self) -> list[Timezone]: 

198 """Return the timezones components in this calendar. 

199 

200 >>> from icalendar import Calendar 

201 >>> calendar = Calendar.example("pacific_fiji") 

202 >>> [timezone.tz_name for timezone in calendar.timezones] 

203 ['custom_Pacific/Fiji'] 

204 

205 .. note:: 

206 

207 This is a read-only property. 

208 """ 

209 return self.walk("VTIMEZONE") 

210 

211 def add_missing_timezones( 

212 self, 

213 first_date: date = Timezone.DEFAULT_FIRST_DATE, 

214 last_date: date = Timezone.DEFAULT_LAST_DATE, 

215 ): 

216 """Add all missing VTIMEZONE components. 

217 

218 This adds all the timezone components that are required. 

219 

220 .. note:: 

221 

222 Timezones that are not known will not be added. 

223 

224 :param first_date: earlier than anything that happens in the calendar 

225 :param last_date: later than anything happening in the calendar 

226 

227 >>> from icalendar import Calendar, Event 

228 >>> from datetime import datetime 

229 >>> from zoneinfo import ZoneInfo 

230 >>> calendar = Calendar() 

231 >>> event = Event() 

232 >>> calendar.add_component(event) 

233 >>> event.start = datetime(1990, 10, 11, 12, tzinfo=ZoneInfo("Europe/Berlin")) 

234 >>> calendar.timezones 

235 [] 

236 >>> calendar.add_missing_timezones() 

237 >>> calendar.timezones[0].tz_name 

238 'Europe/Berlin' 

239 >>> calendar.get_missing_tzids() # check that all are added 

240 set() 

241 """ 

242 for tzid in self.get_missing_tzids(): 

243 try: 

244 timezone = Timezone.from_tzid( 

245 tzid, first_date=first_date, last_date=last_date 

246 ) 

247 except ValueError: 

248 continue 

249 self.add_component(timezone) 

250 

251 calendar_name = multi_language_text_property( 

252 "NAME", 

253 "X-WR-CALNAME", 

254 """This property specifies the name of the calendar. 

255 

256 This implements :rfc:`7986` ``NAME`` and ``X-WR-CALNAME``. 

257 

258 Property Parameters: 

259 IANA, non-standard, alternate text 

260 representation, and language property parameters can be specified 

261 on this property. 

262 

263 Conformance: 

264 This property can be specified multiple times in an 

265 iCalendar object. However, each property MUST represent the name 

266 of the calendar in a different language. 

267 

268 Description: 

269 This property is used to specify a name of the 

270 iCalendar object that can be used by calendar user agents when 

271 presenting the calendar data to a user. Whilst a calendar only 

272 has a single name, multiple language variants can be specified by 

273 including this property multiple times with different "LANGUAGE" 

274 parameter values on each. 

275 

276 Example: 

277 Below, we set the name of the calendar. 

278 

279 .. code-block:: pycon 

280 

281 >>> from icalendar import Calendar 

282 >>> calendar = Calendar() 

283 >>> calendar.calendar_name = "My Calendar" 

284 >>> print(calendar.to_ical()) 

285 BEGIN:VCALENDAR 

286 NAME:My Calendar 

287 END:VCALENDAR 

288 """, 

289 ) 

290 

291 description = multi_language_text_property( 

292 "DESCRIPTION", 

293 "X-WR-CALDESC", 

294 """This property specifies the description of the calendar. 

295 

296 This implements :rfc:`7986` ``DESCRIPTION`` and ``X-WR-CALDESC``. 

297 

298 Conformance: 

299 This property can be specified multiple times in an 

300 iCalendar object. However, each property MUST represent the 

301 description of the calendar in a different language. 

302 

303 Description: 

304 This property is used to specify a lengthy textual 

305 description of the iCalendar object that can be used by calendar 

306 user agents when describing the nature of the calendar data to a 

307 user. Whilst a calendar only has a single description, multiple 

308 language variants can be specified by including this property 

309 multiple times with different "LANGUAGE" parameter values on each. 

310 

311 Example: 

312 Below, we add a description to a calendar. 

313 

314 .. code-block:: pycon 

315 

316 >>> from icalendar import Calendar 

317 >>> calendar = Calendar() 

318 >>> calendar.description = "This is a calendar" 

319 >>> print(calendar.to_ical()) 

320 BEGIN:VCALENDAR 

321 DESCRIPTION:This is a calendar 

322 END:VCALENDAR 

323 """, 

324 ) 

325 

326 color = single_string_property( 

327 "COLOR", 

328 """This property specifies a color used for displaying the calendar. 

329 

330 This implements :rfc:`7986` ``COLOR`` and ``X-APPLE-CALENDAR-COLOR``. 

331 Please note that since :rfc:`7986`, subcomponents can have their own color. 

332 

333 Property Parameters: 

334 IANA and non-standard property parameters can 

335 be specified on this property. 

336 

337 Conformance: 

338 This property can be specified once in an iCalendar 

339 object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components. 

340 

341 Description: 

342 This property specifies a color that clients MAY use 

343 when presenting the relevant data to a user. Typically, this 

344 would appear as the "background" color of events or tasks. The 

345 value is a case-insensitive color name taken from the CSS3 set of 

346 names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 <https://www.w3.org/TR/css-color-3/>`_. 

347 

348 Example: 

349 ``"turquoise"``, ``"#ffffff"`` 

350 

351 .. code-block:: pycon 

352 

353 >>> from icalendar import Calendar 

354 >>> calendar = Calendar() 

355 >>> calendar.color = "black" 

356 >>> print(calendar.to_ical()) 

357 BEGIN:VCALENDAR 

358 COLOR:black 

359 END:VCALENDAR 

360 

361 """, 

362 "X-APPLE-CALENDAR-COLOR", 

363 ) 

364 categories = categories_property 

365 uid = uid_property 

366 prodid = single_string_property( 

367 "PRODID", 

368 """PRODID specifies the identifier for the product that created the iCalendar object. 

369 

370Conformance: 

371 The property MUST be specified once in an iCalendar object. 

372 

373Description: 

374 The vendor of the implementation SHOULD assure that 

375 this is a globally unique identifier; using some technique such as 

376 an FPI value, as defined in [ISO.9070.1991]. 

377 

378 This property SHOULD NOT be used to alter the interpretation of an 

379 iCalendar object beyond the semantics specified in this memo. For 

380 example, it is not to be used to further the understanding of non- 

381 standard properties. 

382 

383Example: 

384 The following is an example of this property. It does not 

385 imply that English is the default language. 

386 

387 .. code-block:: text 

388 

389 -//ABC Corporation//NONSGML My Product//EN 

390""", # noqa: E501 

391 ) 

392 version = single_string_property( 

393 "VERSION", 

394 """VERSION of the calendar specification. 

395 

396The default is ``"2.0"`` for :rfc:`5545`. 

397 

398Purpose: 

399 This property specifies the identifier corresponding to the 

400 highest version number or the minimum and maximum range of the 

401 iCalendar specification that is required in order to interpret the 

402 iCalendar object. 

403 

404 

405 """, 

406 ) 

407 

408 calscale = single_string_property( 

409 "CALSCALE", 

410 """CALSCALE defines the calendar scale used for the calendar information specified in the iCalendar object. 

411 

412Compatibility: 

413 :rfc:`7529` makes the case that GREGORIAN stays the default and other calendar scales 

414 are implemented on the RRULE. 

415 

416Conformance: 

417 This property can be specified once in an iCalendar 

418 object. The default value is "GREGORIAN". 

419 

420Description: 

421 This memo is based on the Gregorian calendar scale. 

422 The Gregorian calendar scale is assumed if this property is not 

423 specified in the iCalendar object. It is expected that other 

424 calendar scales will be defined in other specifications or by 

425 future versions of this memo. 

426 """, # noqa: E501 

427 default="GREGORIAN", 

428 ) 

429 method = single_string_property( 

430 "METHOD", 

431 """METHOD defines the iCalendar object method associated with the calendar object. 

432 

433Description: 

434 When used in a MIME message entity, the value of this 

435 property MUST be the same as the Content-Type "method" parameter 

436 value. If either the "METHOD" property or the Content-Type 

437 "method" parameter is specified, then the other MUST also be 

438 specified. 

439 

440 No methods are defined by this specification. This is the subject 

441 of other specifications, such as the iCalendar Transport- 

442 independent Interoperability Protocol (iTIP) defined by :rfc:`5546`. 

443 

444 If this property is not present in the iCalendar object, then a 

445 scheduling transaction MUST NOT be assumed. In such cases, the 

446 iCalendar object is merely being used to transport a snapshot of 

447 some calendar information; without the intention of conveying a 

448 scheduling semantic. 

449""", # noqa: E501 

450 ) 

451 

452 @classmethod 

453 def new( 

454 cls, 

455 /, 

456 calscale: Optional[str] = None, 

457 categories: Sequence[str] = (), 

458 color: Optional[str] = None, 

459 description: Optional[str] = None, 

460 method: Optional[str] = None, 

461 name: Optional[str] = None, 

462 prodid: Optional[str] = f"-//collective//icalendar//{__version__}//EN", 

463 uid: Optional[str | uuid.UUID] = None, 

464 version: str = "2.0", 

465 ): 

466 """Create a new Calendar with all required properties. 

467 

468 This creates a new Todo in accordance with :rfc:`5545`. 

469 

470 Arguments: 

471 calscale: The :attr:`calscale` of the component. 

472 categories: The :attr:`categories` of the component. 

473 color: The :attr:`color` of the component. 

474 description: The :attr:`description` of the component. 

475 method: The :attr:`method` of the component. 

476 name: The :attr:`calendar_name` of the component. 

477 prodid: The :attr:`prodid` of the component. 

478 uid: The :attr:`uid` of the component. 

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

480 version: The :attr:`version` of the component. 

481 

482 Returns: 

483 :class:`Calendar` 

484 

485 Raises: 

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

487 

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

489 """ 

490 calendar = cls() 

491 calendar.prodid = prodid 

492 calendar.version = version 

493 calendar.calendar_name = name 

494 calendar.color = color 

495 calendar.description = description 

496 calendar.method = method 

497 calendar.calscale = calscale 

498 calendar.categories = categories 

499 calendar.uid = uid 

500 return calendar 

501 

502 

503__all__ = ["Calendar"]