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