1""":rfc:`5545` VFREEBUSY component."""
2
3from __future__ import annotations
4
5import uuid
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING
8
9from icalendar.attr import (
10 CONCEPTS_TYPE_SETTER,
11 LINKS_TYPE_SETTER,
12 RELATED_TO_TYPE_SETTER,
13 REQUEST_STATUS_property,
14 contacts_property,
15 create_single_property,
16 organizer_property,
17 uid_property,
18 url_property,
19)
20from icalendar.cal.component import Component
21from icalendar.cal.examples import get_example
22
23if TYPE_CHECKING:
24 from icalendar.compatibility import Self
25 from icalendar.prop import vCalAddress
26
27
28class FreeBusy(Component):
29 """
30 A "VFREEBUSY" calendar component is a grouping of component
31 properties that represents either a request for free or busy time
32 information, a reply to a request for free or busy time
33 information, or a published set of busy time information.
34
35 Examples:
36 Create a new FreeBusy:
37
38 >>> from icalendar import FreeBusy
39 >>> free_busy = FreeBusy.new()
40 >>> print(free_busy.to_ical())
41 BEGIN:VFREEBUSY
42 DTSTAMP:20250517T080612Z
43 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
44 END:VFREEBUSY
45
46 Get the example FreeBusy.
47
48 .. code-block:: pycon
49
50 >>> from icalendar import FreeBusy
51 >>> free_busy = FreeBusy.example()
52 >>> print(free_busy.to_ical().decode())
53 BEGIN:VFREEBUSY
54 DTEND:19980410T234500Z
55 DTSTAMP:19970901T120000Z
56 DTSTART:19980313T141711Z
57 FREEBUSY:19980314T233000Z/19980315T003000Z
58 FREEBUSY:19980316T153000Z/19980316T163000Z
59 FREEBUSY:19980318T030000Z/19980318T040000Z
60 ORGANIZER:jsmith@example.com
61 UID:19970901T115957Z-76A912@example.com
62 URL:http://www.example.com/calendar/busytime/jsmith.ifb
63 END:VFREEBUSY
64
65 """
66
67 name = "VFREEBUSY"
68
69 required = (
70 "UID",
71 "DTSTAMP",
72 )
73 singletons = (
74 "CONTACT",
75 "DTSTART",
76 "DTEND",
77 "DTSTAMP",
78 "ORGANIZER",
79 "UID",
80 "URL",
81 )
82 multiple = (
83 "ATTENDEE",
84 "COMMENT",
85 "FREEBUSY",
86 "REQUEST-STATUS",
87 )
88 uid = uid_property
89 url = url_property
90 organizer = organizer_property
91 contacts = contacts_property
92 REQUEST_STATUS = REQUEST_STATUS_property
93 start = DTSTART = create_single_property(
94 "DTSTART",
95 "dt",
96 (datetime, date),
97 date,
98 'The "DTSTART" property for a "VFREEBUSY" specifies the inclusive start of the component.',
99 )
100 end = DTEND = create_single_property(
101 "DTEND",
102 "dt",
103 (datetime, date),
104 date,
105 'The "DTEND" property for a "VFREEBUSY" calendar component specifies the non-inclusive end of the component.',
106 )
107
108 @property
109 def duration(self) -> timedelta | None:
110 """The duration computed from start and end."""
111 if self.DTSTART is None or self.DTEND is None:
112 return None
113 return self.DTEND - self.DTSTART
114
115 @classmethod
116 def new(
117 cls,
118 /,
119 comments: list[str] | str | None = None,
120 concepts: CONCEPTS_TYPE_SETTER = None,
121 contacts: list[str] | str | None = None,
122 end: date | datetime | None = None,
123 links: LINKS_TYPE_SETTER = None,
124 organizer: vCalAddress | str | None = None,
125 refids: list[str] | str | None = None,
126 related_to: RELATED_TO_TYPE_SETTER = None,
127 request_status: list[str] | str | None = None,
128 stamp: date | None = None,
129 start: date | datetime | None = None,
130 uid: str | uuid.UUID | None = None,
131 url: str | None = None,
132 ) -> Self:
133 """Create a new FreeBusy component with all required properties,
134 in accordance with :rfc:`5545#section-3.6.4`.
135
136 The FreeBusy component has the required properties of UID and DTSTAMP,
137 which may be set with the parameters of ``uid`` and ``stamp``.
138
139 Parameters:
140 comments: The :attr:`~icalendar.cal.component.Component.comments` of the component.
141 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the component.
142 contacts: The :attr:`contacts` of the component.
143 end: The :attr:`end` of the component.
144 links: The :attr:`~icalendar.cal.component.Component.links` of the component.
145 organizer: The :attr:`organizer` of the component.
146 refids: :attr:`~icalendar.cal.component.Component.refids` of the component.
147 related_to: :attr:`~icalendar.cal.component.Component.related_to` of the component.
148 request_status: The :attr:`REQUEST_STATUS` of the component.
149 stamp: The :attr:`~icalendar.cal.component.Component.DTSTAMP` of the component.
150 If None, this is set to the current time.
151 start: The :attr:`start` of the component.
152 uid: The :attr:`uid` of the component.
153 If None, this is set to a new :func:`uuid.uuid4`.
154 url: The :attr:`url` of the component.
155
156 Returns:
157 :class:`FreeBusy`
158
159 Raises:
160 :exc:`~icalendar.error.InvalidCalendar`: If the content is not valid
161 according to :rfc:`5545`.
162
163 .. warning:: As time progresses, we will be stricter with the validation.
164 """
165 free_busy: Self = super().new(
166 stamp=stamp if stamp is not None else cls._utc_now(),
167 comments=comments,
168 links=links,
169 related_to=related_to,
170 refids=refids,
171 concepts=concepts,
172 )
173 free_busy.uid = uid if uid is not None else uuid.uuid4()
174 free_busy.url = url
175 free_busy.organizer = organizer
176 free_busy.contacts = contacts
177 free_busy.REQUEST_STATUS = request_status
178 free_busy.end = end
179 free_busy.start = start
180
181 if cls._validate_new:
182 cls._validate_start_and_end(start, end)
183 return free_busy
184
185 @classmethod
186 def example(cls, name: str = "example") -> FreeBusy:
187 """Return the FreeBusy example with the given name."""
188 return cls.from_ical(get_example("freebusy", name))
189
190
191__all__ = ["FreeBusy"]