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