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