1""":rfc:`5545` VJOURNAL component."""
2
3from __future__ import annotations
4
5import uuid
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING, Optional, Sequence
8
9from icalendar.attr import (
10 attendees_property,
11 categories_property,
12 class_property,
13 color_property,
14 contacts_property,
15 create_single_property,
16 descriptions_property,
17 exdates_property,
18 organizer_property,
19 rdates_property,
20 rrules_property,
21 sequence_property,
22 status_property,
23 summary_property,
24 uid_property,
25 url_property,
26)
27from icalendar.cal.component import Component
28from icalendar.error import IncompleteComponent
29
30if TYPE_CHECKING:
31 from icalendar.enums import CLASS, STATUS
32 from icalendar.prop import vCalAddress
33
34
35class Journal(Component):
36 """A descriptive text at a certain time or associated with a component.
37
38 Description:
39 A "VJOURNAL" calendar component is a grouping of
40 component properties that represent one or more descriptive text
41 notes associated with a particular calendar date. The "DTSTART"
42 property is used to specify the calendar date with which the
43 journal entry is associated. Generally, it will have a DATE value
44 data type, but it can also be used to specify a DATE-TIME value
45 data type. Examples of a journal entry include a daily record of
46 a legislative body or a journal entry of individual telephone
47 contacts for the day or an ordered list of accomplishments for the
48 day.
49
50 Examples:
51 Create a new Journal:
52
53 >>> from icalendar import Journal
54 >>> journal = Journal.new()
55 >>> print(journal.to_ical())
56 BEGIN:VJOURNAL
57 DTSTAMP:20250517T080612Z
58 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
59 END:VJOURNAL
60
61 """
62
63 name = "VJOURNAL"
64
65 required = (
66 "UID",
67 "DTSTAMP",
68 )
69 singletons = (
70 "CLASS",
71 "COLOR",
72 "CREATED",
73 "DTSTART",
74 "DTSTAMP",
75 "LAST-MODIFIED",
76 "ORGANIZER",
77 "RECURRENCE-ID",
78 "SEQUENCE",
79 "STATUS",
80 "SUMMARY",
81 "UID",
82 "URL",
83 )
84 multiple = (
85 "ATTACH",
86 "ATTENDEE",
87 "CATEGORIES",
88 "COMMENT",
89 "CONTACT",
90 "EXDATE",
91 "RELATED",
92 "RDATE",
93 "RRULE",
94 "RSTATUS",
95 "DESCRIPTION",
96 )
97
98 DTSTART = create_single_property(
99 "DTSTART",
100 "dt",
101 (datetime, date),
102 date,
103 'The "DTSTART" property for a "VJOURNAL" that specifies the exact date at which the journal entry was made.', # noqa: E501
104 )
105
106 @property
107 def start(self) -> date:
108 """The start of the Journal.
109
110 The "DTSTART"
111 property is used to specify the calendar date with which the
112 journal entry is associated.
113 """
114 start = self.DTSTART
115 if start is None:
116 raise IncompleteComponent("No DTSTART given.")
117 return start
118
119 @start.setter
120 def start(self, value: datetime | date) -> None:
121 """Set the start of the journal."""
122 self.DTSTART = value
123
124 end = start
125
126 @property
127 def duration(self) -> timedelta:
128 """The journal has no duration: timedelta(0)."""
129 return timedelta(0)
130
131 color = color_property
132 sequence = sequence_property
133 categories = categories_property
134 rdates = rdates_property
135 exdates = exdates_property
136 rrules = rrules_property
137 uid = uid_property
138
139 summary = summary_property
140 descriptions = descriptions_property
141 classification = class_property
142 url = url_property
143 organizer = organizer_property
144 contacts = contacts_property
145 status = status_property
146 attendees = attendees_property
147
148 @property
149 def description(self) -> str:
150 """The concatenated descriptions of the journal.
151
152 A Journal can have several descriptions.
153 This is a compatibility method.
154 """
155 descriptions = self.descriptions
156 if not descriptions:
157 return None
158 return "\r\n\r\n".join(descriptions)
159
160 @description.setter
161 def description(self, description: Optional[str]):
162 """Set the description"""
163 self.descriptions = description
164
165 @description.deleter
166 def description(self):
167 """Delete all descriptions."""
168 del self.descriptions
169
170 @classmethod
171 def new(
172 cls,
173 /,
174 attendees: Optional[list[vCalAddress]] = None,
175 categories: Sequence[str] = (),
176 classification: Optional[CLASS] = None,
177 color: Optional[str] = None,
178 comments: list[str] | str | None = None,
179 contacts: list[str] | str | None = None,
180 created: Optional[date] = None,
181 description: Optional[str | Sequence[str]] = None,
182 last_modified: Optional[date] = None,
183 organizer: Optional[vCalAddress | str] = None,
184 sequence: Optional[int] = None,
185 stamp: Optional[date] = None,
186 start: Optional[date | datetime] = None,
187 status: Optional[STATUS] = None,
188 summary: Optional[str] = None,
189 uid: Optional[str | uuid.UUID] = None,
190 url: Optional[str] = None,
191 ):
192 """Create a new journal entry with all required properties.
193
194 This creates a new Journal in accordance with :rfc:`5545`.
195
196 Arguments:
197 attendees: The :attr:`attendees` of the journal.
198 categories: The :attr:`categories` of the journal.
199 classification: The :attr:`classification` of the journal.
200 color: The :attr:`color` of the journal.
201 comments: The :attr:`Component.comments` of the journal.
202 created: The :attr:`Component.created` of the journal.
203 description: The :attr:`description` of the journal.
204 end: The :attr:`end` of the journal.
205 last_modified: The :attr:`Component.last_modified` of the journal.
206 organizer: The :attr:`organizer` of the journal.
207 sequence: The :attr:`sequence` of the journal.
208 stamp: The :attr:`Component.stamp` of the journal.
209 If None, this is set to the current time.
210 start: The :attr:`start` of the journal.
211 status: The :attr:`status` of the journal.
212 summary: The :attr:`summary` of the journal.
213 uid: The :attr:`uid` of the journal.
214 If None, this is set to a new :func:`uuid.uuid4`.
215 url: The :attr:`url` of the journal.
216
217 Returns:
218 :class:`Journal`
219
220 Raises:
221 InvalidCalendar: If the content is not valid according to :rfc:`5545`.
222
223 .. warning:: As time progresses, we will be stricter with the validation.
224 """
225 journal = super().new(
226 stamp=stamp if stamp is not None else cls._utc_now(),
227 created=created,
228 last_modified=last_modified,
229 comments=comments,
230 )
231 journal.summary = summary
232 journal.descriptions = description
233 journal.uid = uid if uid is not None else uuid.uuid4()
234 journal.start = start
235 journal.color = color
236 journal.categories = categories
237 journal.sequence = sequence
238 journal.classification = classification
239 journal.url = url
240 journal.organizer = organizer
241 journal.contacts = contacts
242 journal.start = start
243 journal.status = status
244 journal.attendees = attendees
245 return journal
246
247
248__all__ = ["Journal"]