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
154 @property
155 def description(self) -> str:
156 """The concatenated descriptions of the journal.
157
158 A Journal can have several descriptions.
159 This is a compatibility method.
160 """
161 descriptions = self.descriptions
162 if not descriptions:
163 return None
164 return "\r\n\r\n".join(descriptions)
165
166 @description.setter
167 def description(self, description: str | None):
168 """Set the description"""
169 self.descriptions = description
170
171 @description.deleter
172 def description(self):
173 """Delete all descriptions."""
174 del self.descriptions
175
176 images = images_property
177
178 @classmethod
179 def new(
180 cls,
181 /,
182 attendees: list[vCalAddress] | None = None,
183 categories: Sequence[str] = (),
184 classification: CLASS | None = None,
185 color: str | None = None,
186 comments: list[str] | str | None = None,
187 concepts: CONCEPTS_TYPE_SETTER = None,
188 contacts: list[str] | str | None = None,
189 created: date | None = None,
190 description: str | Sequence[str] | None = None,
191 last_modified: date | None = None,
192 links: LINKS_TYPE_SETTER = None,
193 organizer: vCalAddress | str | None = None,
194 refids: list[str] | str | None = None,
195 related_to: RELATED_TO_TYPE_SETTER = None,
196 sequence: int | None = None,
197 stamp: date | None = None,
198 start: date | datetime | None = None,
199 status: STATUS | None = None,
200 summary: str | None = None,
201 uid: str | uuid.UUID | None = None,
202 url: str | None = None,
203 ):
204 """Create a new journal entry with all required properties.
205
206 This creates a new Journal in accordance with :rfc:`5545`.
207
208 Parameters:
209 attendees: The :attr:`attendees` of the journal.
210 categories: The :attr:`categories` of the journal.
211 classification: The :attr:`classification` of the journal.
212 color: The :attr:`color` of the journal.
213 comments: The :attr:`~icalendar.Component.comments` of the journal.
214 concepts: The :attr:`~icalendar.Component.concepts` of the journal.
215 contacts: The :attr:`contacts` of the journal.
216 created: The :attr:`~icalendar.Component.created` of the journal.
217 description: The :attr:`description` of the journal.
218 end: The :attr:`end` of the journal.
219 last_modified: The :attr:`~icalendar.Component.last_modified` of
220 the journal.
221 links: The :attr:`~icalendar.Component.links` of the journal.
222 organizer: The :attr:`organizer` of the journal.
223 refids: :attr:`~icalendar.Component.refids` of the journal.
224 related_to: :attr:`~icalendar.Component.related_to` of the journal.
225 sequence: The :attr:`sequence` of the journal.
226 stamp: The :attr:`~icalendar.Component.stamp` of the journal.
227 If None, this is set to the current time.
228 start: The :attr:`start` of the journal.
229 status: The :attr:`status` of the journal.
230 summary: The :attr:`summary` of the journal.
231 uid: The :attr:`uid` of the journal.
232 If None, this is set to a new :func:`uuid.uuid4`.
233 url: The :attr:`url` of the journal.
234
235 Returns:
236 :class:`Journal`
237
238 Raises:
239 ~error.InvalidCalendar: If the content is not valid
240 according to :rfc:`5545`.
241
242 .. warning:: As time progresses, we will be stricter with the validation.
243 """
244 journal: Journal = super().new(
245 stamp=stamp if stamp is not None else cls._utc_now(),
246 created=created,
247 last_modified=last_modified,
248 comments=comments,
249 links=links,
250 related_to=related_to,
251 refids=refids,
252 concepts=concepts,
253 )
254 journal.summary = summary
255 journal.descriptions = description
256 journal.uid = uid if uid is not None else uuid.uuid4()
257 journal.start = start
258 journal.color = color
259 journal.categories = categories
260 journal.sequence = sequence
261 journal.classification = classification
262 journal.url = url
263 journal.organizer = organizer
264 journal.contacts = contacts
265 journal.start = start
266 journal.status = status
267 journal.attendees = attendees
268
269 return journal
270
271
272__all__ = ["Journal"]