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