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