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