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