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