1""":rfc:`5545` VFREEBUSY component."""
2
3from __future__ import annotations
4
5import uuid
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING, Optional
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
20
21if TYPE_CHECKING:
22 from icalendar.prop import vCalAddress
23
24
25class FreeBusy(Component):
26 """
27 A "VFREEBUSY" calendar component is a grouping of component
28 properties that represents either a request for free or busy time
29 information, a reply to a request for free or busy time
30 information, or a published set of busy time information.
31
32 Examples:
33 Create a new FreeBusy:
34
35 >>> from icalendar import FreeBusy
36 >>> free_busy = FreeBusy.new()
37 >>> print(free_busy.to_ical())
38 BEGIN:VFREEBUSY
39 DTSTAMP:20250517T080612Z
40 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
41 END:VFREEBUSY
42
43 """
44
45 name = "VFREEBUSY"
46
47 required = (
48 "UID",
49 "DTSTAMP",
50 )
51 singletons = (
52 "CONTACT",
53 "DTSTART",
54 "DTEND",
55 "DTSTAMP",
56 "ORGANIZER",
57 "UID",
58 "URL",
59 )
60 multiple = (
61 "ATTENDEE",
62 "COMMENT",
63 "FREEBUSY",
64 "RSTATUS",
65 )
66 uid = uid_property
67 url = url_property
68 organizer = organizer_property
69 contacts = contacts_property
70 start = DTSTART = create_single_property(
71 "DTSTART",
72 "dt",
73 (datetime, date),
74 date,
75 'The "DTSTART" property for a "VFREEBUSY" specifies the inclusive start of the component.', # noqa: E501
76 )
77 end = DTEND = create_single_property(
78 "DTEND",
79 "dt",
80 (datetime, date),
81 date,
82 'The "DTEND" property for a "VFREEBUSY" calendar component specifies the non-inclusive end of the component.', # noqa: E501
83 )
84
85 @property
86 def duration(self) -> Optional[timedelta]:
87 """The duration computed from start and end."""
88 if self.DTSTART is None or self.DTEND is None:
89 return None
90 return self.DTEND - self.DTSTART
91
92 @classmethod
93 def new(
94 cls,
95 /,
96 comments: list[str] | str | None = None,
97 concepts: CONCEPTS_TYPE_SETTER = None,
98 contacts: list[str] | str | None = None,
99 end: Optional[date | datetime] = None,
100 links: LINKS_TYPE_SETTER = None,
101 organizer: Optional[vCalAddress | str] = None,
102 refids: list[str] | str | None = None,
103 related_to: RELATED_TO_TYPE_SETTER = None,
104 stamp: Optional[date] = None,
105 start: Optional[date | datetime] = None,
106 uid: Optional[str | uuid.UUID] = None,
107 url: Optional[str] = None,
108 ):
109 """Create a new alarm with all required properties.
110
111 This creates a new Alarm in accordance with :rfc:`5545`.
112
113 Arguments:
114 comments: The :attr:`~icalendar.Component.comments` of the component.
115 concepts: The :attr:`~icalendar.Component.concepts` of the component.
116 contacts: The :attr:`contacts` of the component.
117 end: The :attr:`end` of the component.
118 links: The :attr:`~icalendar.Component.links` of the component.
119 organizer: The :attr:`organizer` of the component.
120 refids: :attr:`~icalendar.Component.refids` of the component.
121 related_to: :attr:`~icalendar.Component.related_to` of the component.
122 stamp: The :attr:`DTSTAMP` of the component.
123 If None, this is set to the current time.
124 start: The :attr:`start` of the component.
125 uid: The :attr:`uid` of the component.
126 If None, this is set to a new :func:`uuid.uuid4`.
127 url: The :attr:`url` of the component.
128
129 Returns:
130 :class:`FreeBusy`
131
132 Raises:
133 InvalidCalendar: If the content is not valid according to :rfc:`5545`.
134
135 .. warning:: As time progresses, we will be stricter with the validation.
136 """
137 free_busy: FreeBusy = super().new(
138 stamp=stamp if stamp is not None else cls._utc_now(),
139 comments=comments,
140 links=links,
141 related_to=related_to,
142 refids=refids,
143 concepts=concepts,
144 )
145 free_busy.uid = uid if uid is not None else uuid.uuid4()
146 free_busy.url = url
147 free_busy.organizer = organizer
148 free_busy.contacts = contacts
149 free_busy.end = end
150 free_busy.start = start
151
152 if cls._validate_new:
153 cls._validate_start_and_end(start, end)
154 return free_busy
155
156
157__all__ = ["FreeBusy"]