Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/todo.py: 60%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""":rfc:`5545` VTODO component."""
3from __future__ import annotations
5import uuid
6from datetime import date, datetime, timedelta
7from typing import TYPE_CHECKING, Literal, Sequence
9from icalendar.attr import (
10 CONCEPTS_TYPE_SETTER,
11 LINKS_TYPE_SETTER,
12 RELATED_TO_TYPE_SETTER,
13 X_MOZ_LASTACK_property,
14 X_MOZ_SNOOZE_TIME_property,
15 attendees_property,
16 categories_property,
17 class_property,
18 color_property,
19 conferences_property,
20 contacts_property,
21 create_single_property,
22 description_property,
23 exdates_property,
24 get_duration_property,
25 get_end_property,
26 get_start_end_duration_with_validation,
27 get_start_property,
28 images_property,
29 location_property,
30 organizer_property,
31 priority_property,
32 property_del_duration,
33 property_doc_duration_template,
34 property_get_duration,
35 property_set_duration,
36 rdates_property,
37 rrules_property,
38 sequence_property,
39 set_duration_with_locking,
40 set_end_with_locking,
41 set_start_with_locking,
42 status_property,
43 summary_property,
44 uid_property,
45 url_property,
46)
47from icalendar.cal.component import Component
49if TYPE_CHECKING:
50 from icalendar.alarms import Alarms
51 from icalendar.enums import CLASS, STATUS
52 from icalendar.prop import vCalAddress
53 from icalendar.prop.conference import Conference
56class Todo(Component):
57 """
58 A "VTODO" calendar component is a grouping of component
59 properties that represents an action item or assignment. For
60 example, it can be used to represent an item of work assigned to
61 an individual, such as "Prepare for the upcoming conference
62 seminar on Internet Calendaring".
64 Examples:
65 Create a new Todo:
67 >>> from icalendar import Todo
68 >>> todo = Todo.new()
69 >>> print(todo.to_ical())
70 BEGIN:VTODO
71 DTSTAMP:20250517T080612Z
72 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
73 END:VTODO
75 """
77 name = "VTODO"
79 required = (
80 "UID",
81 "DTSTAMP",
82 )
83 singletons = (
84 "CLASS",
85 "COLOR",
86 "COMPLETED",
87 "CREATED",
88 "DESCRIPTION",
89 "DTSTAMP",
90 "DTSTART",
91 "GEO",
92 "LAST-MODIFIED",
93 "LOCATION",
94 "ORGANIZER",
95 "PERCENT-COMPLETE",
96 "PRIORITY",
97 "RECURRENCE-ID",
98 "SEQUENCE",
99 "STATUS",
100 "SUMMARY",
101 "UID",
102 "URL",
103 "DUE",
104 "DURATION",
105 )
106 exclusive = (
107 "DUE",
108 "DURATION",
109 )
110 multiple = (
111 "ATTACH",
112 "ATTENDEE",
113 "CATEGORIES",
114 "COMMENT",
115 "CONTACT",
116 "EXDATE",
117 "RSTATUS",
118 "RELATED",
119 "RESOURCES",
120 "RDATE",
121 "RRULE",
122 )
123 DTSTART = create_single_property(
124 "DTSTART",
125 "dt",
126 (datetime, date),
127 date,
128 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', # noqa: E501
129 )
130 DUE = create_single_property(
131 "DUE",
132 "dt",
133 (datetime, date),
134 date,
135 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', # noqa: E501
136 )
137 DURATION = property(
138 property_get_duration,
139 property_set_duration,
140 property_del_duration,
141 property_doc_duration_template.format(component="VTODO"),
142 )
144 def _get_start_end_duration(self):
145 """Verify the calendar validity and return the right attributes."""
146 return get_start_end_duration_with_validation(self, "DTSTART", "DUE", "VTODO")
148 @property
149 def start(self) -> date | datetime:
150 """The start of the VTODO.
152 Invalid values raise an InvalidCalendar.
153 If there is no start, we also raise an IncompleteComponent error.
155 You can get the start, end and duration of a Todo as follows:
157 >>> from datetime import datetime
158 >>> from icalendar import Todo
159 >>> todo = Todo()
160 >>> todo.start = datetime(2021, 1, 1, 12)
161 >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes
162 >>> todo.duration # 1800 seconds == 30 minutes
163 datetime.timedelta(seconds=1800)
164 >>> print(todo.to_ical())
165 BEGIN:VTODO
166 DTSTART:20210101T120000
167 DUE:20210101T123000
168 END:VTODO
169 """
170 return get_start_property(self)
172 @start.setter
173 def start(self, start: date | datetime | None):
174 """Set the start."""
175 self.DTSTART = start
177 @property
178 def end(self) -> date | datetime:
179 """The end of the todo.
181 Invalid values raise an InvalidCalendar error.
182 If there is no end, we also raise an IncompleteComponent error.
183 """
184 return get_end_property(self, "DUE")
186 @end.setter
187 def end(self, end: date | datetime | None):
188 """Set the end."""
189 self.DUE = end
191 @property
192 def duration(self) -> timedelta:
193 """The duration of the VTODO.
195 Returns the DURATION property if set, otherwise calculated from start and end.
196 You can set the duration to automatically adjust the end time while keeping
197 start locked.
199 Setting the duration will:
200 1. Keep the start time locked (unchanged)
201 2. Adjust the end time to start + duration
202 3. Remove any existing DUE property
203 4. Set the DURATION property
204 """
205 return get_duration_property(self)
207 @duration.setter
208 def duration(self, value: timedelta):
209 if not isinstance(value, timedelta):
210 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
212 # Use the set_duration method with default start-locked behavior
213 self.set_duration(value, locked="start")
215 def set_duration(
216 self, duration: timedelta | None, locked: Literal["start", "end"] = "start"
217 ):
218 """Set the duration of the event relative to either start or end.
220 Args:
221 duration: The duration to set, or None to convert to DURATION property
222 locked: Which property to keep unchanged ('start' or 'end')
223 """
224 set_duration_with_locking(self, duration, locked, "DUE")
226 def set_start(
227 self, start: date | datetime, locked: Literal["duration", "end"] | None = None
228 ):
229 """Set the start with explicit locking behavior.
231 Args:
232 start: The start time to set
233 locked: Which property to keep unchanged ('duration', 'end', or None
234 for auto-detect)
235 """
236 set_start_with_locking(self, start, locked, "DUE")
238 def set_end(
239 self, end: date | datetime, locked: Literal["start", "duration"] = "start"
240 ):
241 """Set the end of the component, keeping either the start or the duration same.
243 Args:
244 end: The end time to set
245 locked: Which property to keep unchanged ('start' or 'duration')
246 """
247 set_end_with_locking(self, end, locked, "DUE")
249 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
250 X_MOZ_LASTACK = X_MOZ_LASTACK_property
252 @property
253 def alarms(self) -> Alarms:
254 """Compute the alarm times for this component.
256 >>> from datetime import datetime
257 >>> from icalendar import Todo
258 >>> todo = Todo() # empty without alarms
259 >>> todo.start = datetime(2024, 10, 26, 10, 21)
260 >>> len(todo.alarms.times)
261 0
263 Note that this only uses DTSTART and DUE, but ignores
264 RDATE, EXDATE, and RRULE properties.
265 """
266 from icalendar.alarms import Alarms
268 return Alarms(self)
270 color = color_property
271 sequence = sequence_property
272 categories = categories_property
273 rdates = rdates_property
274 exdates = exdates_property
275 rrules = rrules_property
276 uid = uid_property
277 summary = summary_property
278 description = description_property
279 classification = class_property
280 url = url_property
281 organizer = organizer_property
282 location = location_property
283 priority = priority_property
284 contacts = contacts_property
285 status = status_property
286 attendees = attendees_property
287 images = images_property
288 conferences = conferences_property
290 @classmethod
291 def new(
292 cls,
293 /,
294 attendees: list[vCalAddress] | None = None,
295 categories: Sequence[str] = (),
296 classification: CLASS | None = None,
297 color: str | None = None,
298 comments: list[str] | str | None = None,
299 concepts: CONCEPTS_TYPE_SETTER = None,
300 contacts: list[str] | str | None = None,
301 conferences: list[Conference] | None = None,
302 created: date | None = None,
303 description: str | None = None,
304 end: date | datetime | None = None,
305 last_modified: date | None = None,
306 links: LINKS_TYPE_SETTER = None,
307 location: str | None = None,
308 organizer: vCalAddress | str | None = None,
309 priority: int | None = None,
310 refids: list[str] | str | None = None,
311 related_to: RELATED_TO_TYPE_SETTER = None,
312 sequence: int | None = None,
313 stamp: date | None = None,
314 start: date | datetime | None = None,
315 status: STATUS | None = None,
316 summary: str | None = None,
317 uid: str | uuid.UUID | None = None,
318 url: str | None = None,
319 ):
320 """Create a new TODO with all required properties.
322 This creates a new Todo in accordance with :rfc:`5545`.
324 Arguments:
325 attendees: The :attr:`attendees` of the todo.
326 categories: The :attr:`categories` of the todo.
327 classification: The :attr:`classification` of the todo.
328 color: The :attr:`color` of the todo.
329 comments: The :attr:`~icalendar.Component.comments` of the todo.
330 concepts: The :attr:`~icalendar.Component.concepts` of the todo.
331 contacts: The :attr:`contacts` of the todo.
332 conferences: The :attr:`conferences` of the todo.
333 created: The :attr:`~icalendar.Component.created` of the todo.
334 description: The :attr:`description` of the todo.
335 end: The :attr:`end` of the todo.
336 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo.
337 links: The :attr:`~icalendar.Component.links` of the todo.
338 location: The :attr:`location` of the todo.
339 organizer: The :attr:`organizer` of the todo.
340 refids: :attr:`~icalendar.Component.refids` of the todo.
341 related_to: :attr:`~icalendar.Component.related_to` of the todo.
342 sequence: The :attr:`sequence` of the todo.
343 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo.
344 If None, this is set to the current time.
345 start: The :attr:`start` of the todo.
346 status: The :attr:`status` of the todo.
347 summary: The :attr:`summary` of the todo.
348 uid: The :attr:`uid` of the todo.
349 If None, this is set to a new :func:`uuid.uuid4`.
350 url: The :attr:`url` of the todo.
352 Returns:
353 :class:`Todo`
355 Raises:
356 InvalidCalendar: If the content is not valid according to :rfc:`5545`.
358 .. warning:: As time progresses, we will be stricter with the validation.
359 """
360 todo: Todo = super().new(
361 stamp=stamp if stamp is not None else cls._utc_now(),
362 created=created,
363 last_modified=last_modified,
364 comments=comments,
365 links=links,
366 related_to=related_to,
367 refids=refids,
368 concepts=concepts,
369 )
370 todo.summary = summary
371 todo.description = description
372 todo.uid = uid if uid is not None else uuid.uuid4()
373 todo.start = start
374 todo.end = end
375 todo.color = color
376 todo.categories = categories
377 todo.sequence = sequence
378 todo.classification = classification
379 todo.url = url
380 todo.organizer = organizer
381 todo.location = location
382 todo.priority = priority
383 todo.contacts = contacts
384 todo.status = status
385 todo.attendees = attendees
386 todo.conferences = conferences
388 if cls._validate_new:
389 cls._validate_start_and_end(start, end)
390 return todo
393__all__ = ["Todo"]