1""":rfc:`5545` VJOURNAL 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 ATTACHMENTS_TYPE_SETTER,
11 ATTENDEE_TYPE_SETTER,
12 CONCEPTS_TYPE_SETTER,
13 LINKS_TYPE_SETTER,
14 RELATED_TO_TYPE_SETTER,
15 REQUEST_STATUS_property,
16 attachments_property,
17 attendees_property,
18 categories_property,
19 class_property,
20 color_property,
21 contacts_property,
22 create_single_property,
23 descriptions_property,
24 exdates_property,
25 images_property,
26 organizer_property,
27 rdates_property,
28 rrules_property,
29 sequence_property,
30 status_property,
31 summary_property,
32 uid_property,
33 url_property,
34)
35from icalendar.cal.component import Component
36from icalendar.cal.examples import get_example
37from icalendar.error import IncompleteComponent
38
39if TYPE_CHECKING:
40 from collections.abc import Sequence
41
42 from icalendar.compatibility import Self
43 from icalendar.enums import CLASS, STATUS
44 from icalendar.prop import vCalAddress
45
46
47class Journal(Component):
48 """A descriptive text at a certain time or associated with a component.
49
50 Description:
51 A "VJOURNAL" calendar component is a grouping of
52 component properties that represent one or more descriptive text
53 notes associated with a particular calendar date. The "DTSTART"
54 property is used to specify the calendar date with which the
55 journal entry is associated. Generally, it will have a DATE value
56 data type, but it can also be used to specify a DATE-TIME value
57 data type. Examples of a journal entry include a daily record of
58 a legislative body or a journal entry of individual telephone
59 contacts for the day or an ordered list of accomplishments for the
60 day.
61
62 Examples:
63 Create a new Journal:
64
65 >>> from icalendar import Journal
66 >>> journal = Journal.new()
67 >>> print(journal.to_ical())
68 BEGIN:VJOURNAL
69 DTSTAMP:20250517T080612Z
70 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
71 END:VJOURNAL
72
73 Get the example Journal.
74
75 .. code-block:: pycon
76
77 >>> from icalendar import Journal
78 >>> journal = Journal.example()
79 >>> print(journal.to_ical().decode())
80 BEGIN:VJOURNAL
81 DESCRIPTION:Aurora project plans were reviewed.
82 DTSTAMP:19970901T130000Z
83 DTSTART;VALUE=DATE:19970317
84 SUMMARY:Staff meeting minutes
85 UID:19970901T130000Z-123405@example.com
86 END:VJOURNAL
87
88 """
89
90 name = "VJOURNAL"
91
92 required = (
93 "UID",
94 "DTSTAMP",
95 )
96 singletons = (
97 "CLASS",
98 "COLOR",
99 "CREATED",
100 "DTSTART",
101 "DTSTAMP",
102 "LAST-MODIFIED",
103 "ORGANIZER",
104 "RECURRENCE-ID",
105 "SEQUENCE",
106 "STATUS",
107 "SUMMARY",
108 "UID",
109 "URL",
110 )
111 multiple = (
112 "ATTACH",
113 "ATTENDEE",
114 "CATEGORIES",
115 "COMMENT",
116 "CONTACT",
117 "EXDATE",
118 "RELATED",
119 "RDATE",
120 "RRULE",
121 "REQUEST-STATUS",
122 "DESCRIPTION",
123 )
124
125 DTSTART = create_single_property(
126 "DTSTART",
127 "dt",
128 (datetime, date),
129 date,
130 'The "DTSTART" property for a "VJOURNAL" that specifies the exact date at which the journal entry was made.',
131 )
132
133 @property
134 def start(self) -> date:
135 """The start of the Journal.
136
137 The "DTSTART"
138 property is used to specify the calendar date with which the
139 journal entry is associated.
140 """
141 start = self.DTSTART
142 if start is None:
143 raise IncompleteComponent("No DTSTART given.")
144 return start
145
146 @start.setter
147 def start(self, value: datetime | date) -> None:
148 """Set the start of the journal."""
149 self.DTSTART = value
150
151 end = start
152
153 @property
154 def duration(self) -> timedelta:
155 """The journal has no duration: timedelta(0)."""
156 return timedelta(0)
157
158 color = color_property
159 sequence = sequence_property
160 categories = categories_property
161 rdates = rdates_property
162 exdates = exdates_property
163 rrules = rrules_property
164 REQUEST_STATUS = REQUEST_STATUS_property
165 uid = uid_property
166
167 summary = summary_property
168 descriptions = descriptions_property
169 classification = class_property
170 url = url_property
171 organizer = organizer_property
172 contacts = contacts_property
173 status = status_property
174 attendees = attendees_property
175 attachments = attachments_property
176 from icalendar.attr import RECURRENCE_ID
177
178 @property
179 def description(self) -> str:
180 """The concatenated descriptions of the journal.
181
182 A Journal can have several descriptions.
183 This is a compatibility method.
184 """
185 descriptions = self.descriptions
186 if not descriptions:
187 return None
188 return "\r\n\r\n".join(descriptions)
189
190 @description.setter
191 def description(self, description: str | None):
192 """Set the description"""
193 self.descriptions = description
194
195 @description.deleter
196 def description(self):
197 """Delete all descriptions."""
198 del self.descriptions
199
200 images = images_property
201
202 @classmethod
203 def new(
204 cls,
205 /,
206 attachments: ATTACHMENTS_TYPE_SETTER = None,
207 attendees: ATTENDEE_TYPE_SETTER = None,
208 categories: Sequence[str] = (),
209 classification: CLASS | None = None,
210 color: str | None = None,
211 comments: list[str] | str | None = None,
212 concepts: CONCEPTS_TYPE_SETTER = None,
213 contacts: list[str] | str | None = None,
214 created: date | None = None,
215 description: str | Sequence[str] | None = None,
216 last_modified: date | None = None,
217 links: LINKS_TYPE_SETTER = None,
218 organizer: vCalAddress | str | None = None,
219 recurrence_id: date | datetime | None = None,
220 refids: list[str] | str | None = None,
221 related_to: RELATED_TO_TYPE_SETTER = None,
222 request_status: list[str] | str | None = None,
223 sequence: int | None = None,
224 stamp: date | None = None,
225 start: date | datetime | None = None,
226 status: STATUS | None = None,
227 summary: str | None = None,
228 uid: str | uuid.UUID | None = None,
229 url: str | None = None,
230 ) -> Self:
231 """Create a new journal entry with all required properties.
232
233 This creates a new Journal in accordance with :rfc:`5545`.
234
235 Parameters:
236 attachments: The :attr:`attachments` of the journal.
237 attendees: The :attr:`attendees` of the journal.
238 categories: The :attr:`categories` of the journal.
239 classification: The :attr:`classification` of the journal.
240 color: The :attr:`color` of the journal.
241 comments: The :attr:`~icalendar.Component.comments` of the journal.
242 concepts: The :attr:`~icalendar.Component.concepts` of the journal.
243 contacts: The :attr:`contacts` of the journal.
244 created: The :attr:`~icalendar.Component.created` of the journal.
245 description: The :attr:`description` of the journal.
246 last_modified: The :attr:`~icalendar.Component.last_modified` of
247 the journal.
248 links: The :attr:`~icalendar.Component.links` of the journal.
249 organizer: The :attr:`organizer` of the journal.
250 recurrence_id: The :attr:`RECURRENCE_ID` of the journal.
251 refids: :attr:`~icalendar.Component.refids` of the journal.
252 related_to: :attr:`~icalendar.Component.related_to` of the journal.
253 request_status: The :attr:`REQUEST_STATUS` of the journal.
254 sequence: The :attr:`sequence` of the journal.
255 stamp: The :attr:`~icalendar.Component.stamp` of the journal.
256 If None, this is set to the current time.
257 start: The :attr:`start` of the journal.
258 status: The :attr:`status` of the journal.
259 summary: The :attr:`summary` of the journal.
260 uid: The :attr:`uid` of the journal.
261 If None, this is set to a new :func:`uuid.uuid4`.
262 url: The :attr:`url` of the journal.
263
264 Returns:
265 :class:`Journal`
266
267 Raises:
268 ~error.InvalidCalendar: If the content is not valid
269 according to :rfc:`5545`.
270
271 .. warning:: As time progresses, we will be stricter with the validation.
272 """
273 journal: Self = super().new(
274 stamp=stamp if stamp is not None else cls._utc_now(),
275 created=created,
276 last_modified=last_modified,
277 comments=comments,
278 links=links,
279 related_to=related_to,
280 refids=refids,
281 concepts=concepts,
282 )
283 journal.summary = summary
284 journal.descriptions = description
285 journal.uid = uid if uid is not None else uuid.uuid4()
286 journal.start = start
287 journal.color = color
288 journal.categories = categories
289 journal.sequence = sequence
290 journal.classification = classification
291 journal.url = url
292 journal.organizer = organizer
293 journal.attachments = attachments
294 journal.contacts = contacts
295 journal.start = start
296 journal.status = status
297 journal.REQUEST_STATUS = request_status
298 journal.attendees = attendees
299 journal.RECURRENCE_ID = recurrence_id
300
301 return journal
302
303 @classmethod
304 def example(cls, name: str = "example") -> Journal:
305 """Return the journal example with the given name."""
306 return cls.from_ical(get_example("journals", name))
307
308
309__all__ = ["Journal"]