1"""This implementes the VAVAILABILITY component.
2
3This is specified in :rfc:`7953`.
4"""
5
6from __future__ import annotations
7
8import uuid
9from datetime import datetime
10from typing import TYPE_CHECKING
11
12from icalendar.attr import (
13 CONCEPTS_TYPE_SETTER,
14 LINKS_TYPE_SETTER,
15 RELATED_TO_TYPE_SETTER,
16 busy_type_property,
17 categories_property,
18 class_property,
19 contacts_property,
20 description_property,
21 duration_property,
22 location_property,
23 organizer_property,
24 priority_property,
25 rfc_7953_dtend_property,
26 rfc_7953_dtstart_property,
27 rfc_7953_duration_property,
28 rfc_7953_end_property,
29 sequence_property,
30 summary_property,
31 url_property,
32)
33from icalendar.cal.examples import get_example
34from icalendar.error import InvalidCalendar
35
36from .component import Component
37
38if TYPE_CHECKING:
39 from collections.abc import Sequence
40 from datetime import date
41
42 from icalendar.cal.available import Available
43 from icalendar.enums import BUSYTYPE, CLASS
44 from icalendar.prop import vCalAddress
45
46
47class Availability(Component):
48 """VAVAILABILITY component from :rfc:`7953`.
49
50 This provides a grouping of component properties and
51 subcomponents that describe the availability associated with a
52 calendar user.
53
54 Description:
55 A "VAVAILABILITY" component indicates a period of time
56 within which availability information is provided. A
57 "VAVAILABILITY" component can specify a start time and an end time
58 or duration. If "DTSTART" is not present, then the start time is
59 unbounded. If "DTEND" or "DURATION" are not present, then the end
60 time is unbounded. Within the specified time period, availability
61 defaults to a free-busy type of "BUSY-UNAVAILABLE" (see
62 Section 3.2), except for any time periods corresponding to
63 "AVAILABLE" subcomponents.
64
65 "AVAILABLE" subcomponents are used to indicate periods of free
66 time within the time range of the enclosing "VAVAILABILITY"
67 component. "AVAILABLE" subcomponents MAY include recurrence
68 properties to specify recurring periods of time, which can be
69 overridden using normal iCalendar recurrence behavior (i.e., use
70 of the "RECURRENCE-ID" property).
71
72 If specified, the "DTSTART" and "DTEND" properties in
73 "VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
74 "DATE-TIME" values specified as either the date with UTC time or
75 the date with local time and a time zone reference.
76
77 The iCalendar object containing the "VAVAILABILITY" component MUST
78 contain appropriate "VTIMEZONE" components corresponding to each
79 unique "TZID" parameter value used in any DATE-TIME properties in
80 all components, unless [RFC7809] is in effect.
81
82 When used to publish available time, the "ORGANIZER" property
83 specifies the calendar user associated with the published
84 available time.
85
86 If the "PRIORITY" property is specified in "VAVAILABILITY"
87 components, it is used to determine how that component is combined
88 with other "VAVAILABILITY" components. See Section 4.
89
90 Other calendar properties MAY be specified in "VAVAILABILITY" or
91 "AVAILABLE" components and are considered attributes of the marked
92 block of time. Their usage is application specific. For example,
93 the "LOCATION" property might be used to indicate that a person is
94 available in one location for part of the week and a different
95 location for another part of the week (but see Section 9 for when
96 it is appropriate to add additional data like this).
97
98 Example:
99 The following is an example of a "VAVAILABILITY" calendar
100 component used to represent the availability of a user, always
101 available Monday through Friday, 9:00 am to 5:00 pm in the
102 America/Montreal time zone:
103
104 .. code-block:: text
105
106 BEGIN:VAVAILABILITY
107 ORGANIZER:mailto:bernard@example.com
108 UID:0428C7D2-688E-4D2E-AC52-CD112E2469DF
109 DTSTAMP:20111005T133225Z
110 BEGIN:AVAILABLE
111 UID:34EDA59B-6BB1-4E94-A66C-64999089C0AF
112 SUMMARY:Monday to Friday from 9:00 to 17:00
113 DTSTART;TZID=America/Montreal:20111002T090000
114 DTEND;TZID=America/Montreal:20111002T170000
115 RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
116 END:AVAILABLE
117 END:VAVAILABILITY
118
119 You can get the same example from :meth:`example`:
120
121 .. code-block: pycon
122
123 >>> from icalendar import Availability
124 >>> a = Availability.example()
125 >>> a.organizer
126 vCalAddress('mailto:bernard@example.com')
127
128 The following is an example of a "VAVAILABILITY" calendar
129 component used to represent the availability of a user available
130 Monday through Thursday, 9:00 am to 5:00 pm, at the main office,
131 and Friday, 9:00 am to 12:00 pm, in the branch office in the
132 America/Montreal time zone between October 2nd and December 2nd
133 2011:
134
135 .. code-block:: text
136
137 BEGIN:VAVAILABILITY
138 ORGANIZER:mailto:bernard@example.com
139 UID:84D0F948-7FC6-4C1D-BBF3-BA9827B424B5
140 DTSTAMP:20111005T133225Z
141 DTSTART;TZID=America/Montreal:20111002T000000
142 DTEND;TZID=America/Montreal:20111202T000000
143 BEGIN:AVAILABLE
144 UID:7B33093A-7F98-4EED-B381-A5652530F04D
145 SUMMARY:Monday to Thursday from 9:00 to 17:00
146 DTSTART;TZID=America/Montreal:20111002T090000
147 DTEND;TZID=America/Montreal:20111002T170000
148 RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH
149 LOCATION:Main Office
150 END:AVAILABLE
151 BEGIN:AVAILABLE
152 UID:DF39DC9E-D8C3-492F-9101-0434E8FC1896
153 SUMMARY:Friday from 9:00 to 12:00
154 DTSTART;TZID=America/Montreal:20111006T090000
155 DTEND;TZID=America/Montreal:20111006T120000
156 RRULE:FREQ=WEEKLY
157 LOCATION:Branch Office
158 END:AVAILABLE
159 END:VAVAILABILITY
160
161 For more examples, have a look at :rfc:`5545`.
162
163 """
164
165 name = "VAVAILABILITY"
166
167 canonical_order = (
168 "DTSTART",
169 "DTEND",
170 "DURATION",
171 "DTSTAMP",
172 "UID",
173 "SEQUENCE",
174 "SUMMARY",
175 "DESCRIPTION",
176 "ORGANIZER",
177 )
178
179 required = (
180 "DTSTART",
181 "DTSTAMP",
182 "UID",
183 )
184
185 singletons = (
186 "DTSTAMP",
187 "UID",
188 "BUSYTYPE",
189 "CLASS",
190 "CREATED",
191 "DESCRIPTION",
192 "DTSTART",
193 "LAST-MODIFIED",
194 "LOCATION",
195 "ORGANIZER",
196 "PRIORITY",
197 "SEQUENCE",
198 "SUMMARY",
199 "URL",
200 "DTEND",
201 "DURATION",
202 )
203
204 exclusive = (
205 "DTEND",
206 "DURATION",
207 )
208
209 organizer = organizer_property
210 busy_type = busy_type_property
211 summary = summary_property
212 description = description_property
213 sequence = sequence_property
214 classification = class_property
215 url = url_property
216 location = location_property
217 categories = categories_property
218 priority = priority_property
219 contacts = contacts_property
220
221 start = DTSTART = rfc_7953_dtstart_property
222 DTEND = rfc_7953_dtend_property
223 DURATION = duration_property("Availability")
224 duration = rfc_7953_duration_property
225 end = rfc_7953_end_property
226
227 @property
228 def available(self) -> list[Available]:
229 """All VAVAILABLE sub-components.
230
231 This is a shortcut to get all VAVAILABLE sub-components.
232 Modifications do not change the calendar.
233 Use :py:meth:`Component.add_component`.
234 """
235 return self.walk("AVAILABLE")
236
237 @classmethod
238 def new(
239 cls,
240 /,
241 busy_type: BUSYTYPE | None = None,
242 categories: Sequence[str] = (),
243 comments: list[str] | str | None = None,
244 components: Sequence[Available] | None = (),
245 concepts: CONCEPTS_TYPE_SETTER = None,
246 contacts: list[str] | str | None = None,
247 created: date | None = None,
248 classification: CLASS | None = None,
249 description: str | None = None,
250 end: datetime | None = None,
251 last_modified: date | None = None,
252 links: LINKS_TYPE_SETTER = None,
253 location: str | None = None,
254 organizer: vCalAddress | str | None = None,
255 priority: int | None = None,
256 refids: list[str] | str | None = None,
257 related_to: RELATED_TO_TYPE_SETTER = None,
258 sequence: int | None = None,
259 stamp: date | None = None,
260 start: datetime | None = None,
261 summary: str | None = None,
262 uid: str | uuid.UUID | None = None,
263 url: str | None = None,
264 ):
265 """Create a new event with all required properties.
266
267 This creates a new Availability in accordance with :rfc:`7953`.
268
269 Parameters:
270 busy_type: The :attr:`busy_type` of the availability.
271 categories: The :attr:`categories` of the availability.
272 classification: The :attr:`classification` of the availability.
273 comments: The :attr:`comments` of the availability.
274 concepts: The :attr:`concepts` of the availability.
275 contacts: The :attr:`contacts` of the availability.
276 created: The :attr:`created` of the availability.
277 description: The :attr:`description` of the availability.
278 end: The :attr:`end` of the availability.
279 last_modified: The :attr:`last_modified` of the
280 availability.
281 links: The :attr:`links` of the availability.
282 location: The :attr:`location` of the availability.
283 organizer: The :attr:`organizer` of the availability.
284 refids: :attr:`refids` of the availability.
285 related_to: :attr:`related_to` of the availability.
286 sequence: The :attr:`sequence` of the availability.
287 stamp: The :attr:`stamp` of the availability.
288 If None, this is set to the current time.
289 start: The :attr:`start` of the availability.
290 summary: The :attr:`summary` of the availability.
291 uid: The :attr:`uid` of the availability.
292 If ``None``, this is set to a new :func:`uuid.uuid4`.
293 url: The :attr:`url` of the availability.
294
295 Returns:
296 :class:`Availability`
297
298 Raises:
299 ~error.InvalidCalendar: If the content is not valid
300 according to :rfc:`7953`.
301
302 .. warning:: As time progresses, we will be stricter with the validation.
303 """
304 availability: Availability = super().new(
305 stamp=stamp if stamp is not None else cls._utc_now(),
306 created=created,
307 comments=comments,
308 last_modified=last_modified,
309 links=links,
310 related_to=related_to,
311 refids=refids,
312 concepts=concepts,
313 )
314 availability.summary = summary
315 availability.description = description
316 availability.uid = uid if uid is not None else uuid.uuid4()
317 availability.sequence = sequence
318 availability.categories = categories
319 availability.classification = classification
320 availability.url = url
321 availability.busy_type = busy_type
322 availability.organizer = organizer
323 availability.location = location
324 availability.priority = priority
325 availability.contacts = contacts
326 for subcomponent in components:
327 availability.add_component(subcomponent)
328 if cls._validate_new:
329 if start is not None and (
330 not isinstance(start, datetime) or start.tzinfo is None
331 ):
332 raise InvalidCalendar(
333 "Availability start must be a datetime with a timezone"
334 )
335 if end is not None and (
336 not isinstance(end, datetime) or end.tzinfo is None
337 ):
338 raise InvalidCalendar(
339 "Availability end must be a datetime with a timezone"
340 )
341 availability._validate_start_and_end(start, end)
342 availability.start = start
343 availability.end = end
344 return availability
345
346 @classmethod
347 def example(cls, name: str = "rfc_7953_1") -> Availability:
348 """Return the calendar example with the given name."""
349 return cls.from_ical(get_example("availabilities", name))
350
351
352__all__ = ["Availability"]