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