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