1"""This implements the sub-component "AVAILABLE" of "VAVAILABILITY".
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 categories_property,
17 contacts_property,
18 description_property,
19 duration_property,
20 exdates_property,
21 location_property,
22 rdates_property,
23 rfc_7953_dtend_property,
24 rfc_7953_dtstart_property,
25 rfc_7953_duration_property,
26 rfc_7953_end_property,
27 rrules_property,
28 sequence_property,
29 summary_property,
30 uid_property,
31)
32from icalendar.cal.examples import get_example
33from icalendar.error import InvalidCalendar
34
35from .component import Component
36
37if TYPE_CHECKING:
38 from datetime import date
39
40
41class Available(Component):
42 """Sub-component of "VAVAILABILITY from :rfc:`7953`.
43
44 Description:
45 "AVAILABLE" subcomponents are used to indicate periods of free
46 time within the time range of the enclosing "VAVAILABILITY"
47 component. "AVAILABLE" subcomponents MAY include recurrence
48 properties to specify recurring periods of time, which can be
49 overridden using normal iCalendar recurrence behavior (i.e., use
50 of the "RECURRENCE-ID" property).
51
52 Examples:
53 This is a recurring "AVAILABLE" subcomponent:
54
55 .. code-block:: text
56
57 BEGIN:AVAILABLE
58 UID:57DD4AAF-3835-46B5-8A39-B3B253157F01
59 SUMMARY:Monday to Friday from 9:00 to 17:00
60 DTSTART;TZID=America/Denver:20111023T090000
61 DTEND;TZID=America/Denver:20111023T170000
62 RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
63 LOCATION:Denver
64 END:AVAILABLE
65
66 You can get the same example from :meth:`example`:
67
68 .. code-block: pycon
69
70 >>> from icalendar import Available
71 >>> a = Available.example()
72 >>> str(a.summary)
73 'Monday to Friday from 9:00 to 17:00'
74
75 """
76
77 name = "VAVAILABLE"
78
79 summary = summary_property
80 description = description_property
81 sequence = sequence_property
82 categories = categories_property
83 uid = uid_property
84 location = location_property
85 contacts = contacts_property
86 exdates = exdates_property
87 rdates = rdates_property
88 rrules = rrules_property
89
90 start = DTSTART = rfc_7953_dtstart_property
91 DTEND = rfc_7953_dtend_property
92 DURATION = duration_property("Available")
93 duration = rfc_7953_duration_property
94 end = rfc_7953_end_property
95
96 @classmethod
97 def new(
98 cls,
99 /,
100 categories: Sequence[str] = (),
101 comments: list[str] | str | None = None,
102 concepts: CONCEPTS_TYPE_SETTER = None,
103 contacts: list[str] | str | None = None,
104 created: Optional[date] = None,
105 description: Optional[str] = None,
106 end: Optional[datetime] = None,
107 last_modified: Optional[date] = None,
108 links: LINKS_TYPE_SETTER = None,
109 location: Optional[str] = None,
110 refids: list[str] | str | None = None,
111 related_to: RELATED_TO_TYPE_SETTER = None,
112 sequence: Optional[int] = None,
113 stamp: Optional[date] = None,
114 start: Optional[datetime] = None,
115 summary: Optional[str] = None,
116 uid: Optional[str | uuid.UUID] = None,
117 ):
118 """Create a new Available component with all required properties.
119
120 This creates a new Available component in accordance with :rfc:`7953`.
121
122 Arguments:
123 categories: The :attr:`categories` of the Available component.
124 comments: The :attr:`~icalendar.Component.comments` of the Available
125 component.
126 concepts: The :attr:`~icalendar.Component.concepts` of the Available
127 component.
128 contacts: The :attr:`contacts` of the Available component.
129 created: The :attr:`~icalendar.Component.created` of the Available
130 component.
131 description: The :attr:`description` of the Available component.
132 end: The :attr:`end` of the Available component.
133 last_modified: The :attr:`~icalendar.Component.last_modified` of the
134 Available component.
135 links: The :attr:`~icalendar.Component.links` of the Available component.
136 location: The :attr:`location` of the Available component.
137 refids: :attr:`~icalendar.Component.refids` of the Available component.
138 related_to: :attr:`~icalendar.Component.related_to` of the Available
139 component.
140 sequence: The :attr:`sequence` of the Available component.
141 stamp: The :attr:`~icalendar.Component.stamp` of the Available component.
142 If None, this is set to the current time.
143 start: The :attr:`start` of the Available component.
144 summary: The :attr:`summary` of the Available component.
145 uid: The :attr:`uid` of the Available component.
146 If None, this is set to a new :func:`uuid.uuid4`.
147
148 Returns:
149 :class:`Available`
150
151 Raises:
152 InvalidCalendar: If the content is not valid according to :rfc:`7953`.
153
154 .. warning:: As time progresses, we will be stricter with the validation.
155 """
156 available: Available = super().new(
157 stamp=stamp if stamp is not None else cls._utc_now(),
158 created=created,
159 last_modified=last_modified,
160 comments=comments,
161 links=links,
162 related_to=related_to,
163 refids=refids,
164 concepts=concepts,
165 )
166 available.summary = summary
167 available.description = description
168 available.uid = uid if uid is not None else uuid.uuid4()
169 available.sequence = sequence
170 available.categories = categories
171 available.location = location
172 available.contacts = contacts
173
174 if cls._validate_new:
175 if end is not None and (
176 not isinstance(end, datetime) or end.tzinfo is None
177 ):
178 raise InvalidCalendar(
179 "Available end must be a datetime with a timezone"
180 )
181 if not isinstance(start, datetime) or start.tzinfo is None:
182 raise InvalidCalendar(
183 "Available start must be a datetime with a timezone"
184 )
185 available._validate_start_and_end(start, end)
186 available.start = start
187 available.end = end
188 return available
189
190 @classmethod
191 def example(cls, name: str = "rfc_7953_1") -> Available:
192 """Return the calendar example with the given name."""
193 return cls.from_ical(get_example("availabilities", name)).available[0]
194
195
196__all__ = ["Available"]