Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/todo.py: 58%
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
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 RESOURCES_property,
17 X_MOZ_LASTACK_property,
18 X_MOZ_SNOOZE_TIME_property,
19 attachments_property,
20 attendees_property,
21 categories_property,
22 class_property,
23 color_property,
24 conferences_property,
25 contacts_property,
26 create_single_property,
27 description_property,
28 exdates_property,
29 get_duration_property,
30 get_end_property,
31 get_start_end_duration_with_validation,
32 get_start_property,
33 images_property,
34 location_property,
35 organizer_property,
36 priority_property,
37 property_del_duration,
38 property_doc_duration_template,
39 property_get_duration,
40 property_set_duration,
41 rdates_property,
42 rrules_property,
43 sequence_property,
44 set_duration_with_locking,
45 set_end_with_locking,
46 set_start_with_locking,
47 status_property,
48 summary_property,
49 uid_property,
50 url_property,
51)
52from icalendar.cal.component import Component
53from icalendar.cal.examples import get_example
55if TYPE_CHECKING:
56 from collections.abc import Iterable, Sequence
58 from icalendar.alarms import Alarms
59 from icalendar.compatibility import Self
60 from icalendar.enums import CLASS, STATUS
61 from icalendar.prop import vCalAddress
62 from icalendar.prop.conference import Conference
65class Todo(Component):
66 """
67 A "VTODO" calendar component is a grouping of component
68 properties that represents an action item or assignment. For
69 example, it can be used to represent an item of work assigned to
70 an individual, such as "Prepare for the upcoming conference
71 seminar on Internet Calendaring".
73 Examples:
74 Create a new Todo:
76 >>> from icalendar import Todo
77 >>> todo = Todo.new()
78 >>> print(todo.to_ical())
79 BEGIN:VTODO
80 DTSTAMP:20250517T080612Z
81 UID:d755cef5-2311-46ed-a0e1-6733c9e15c63
82 END:VTODO
84 Complete the example Todo.
86 .. code-block:: pycon
88 >>> from datetime import datetime, timezone
89 >>> from icalendar import Todo, STATUS
90 >>> todo = Todo.example()
91 >>> todo["PERCENT-COMPLETE"] = 100
92 >>> todo["COMPLETED"] = datetime(2007, 5, 1, 12, tzinfo=timezone.utc)
93 >>> todo.status = STATUS.COMPLETED
94 >>> print(todo.to_ical().decode())
95 BEGIN:VTODO
96 CATEGORIES:FAMILY,FINANCE
97 CLASS:CONFIDENTIAL
98 COMPLETED:2007-05-01 12:00:00+00:00
99 DTSTAMP:20070313T123432Z
100 DUE;VALUE=DATE:20070501
101 PERCENT-COMPLETE:100
102 STATUS:COMPLETED
103 SUMMARY:Submit Quebec Income Tax Return for 2006
104 UID:20070313T123432Z-456553@example.com
105 END:VTODO
107 """
109 name = "VTODO"
111 required = (
112 "UID",
113 "DTSTAMP",
114 )
115 singletons = (
116 "CLASS",
117 "COLOR",
118 "COMPLETED",
119 "CREATED",
120 "DESCRIPTION",
121 "DTSTAMP",
122 "DTSTART",
123 "GEO",
124 "LAST-MODIFIED",
125 "LOCATION",
126 "ORGANIZER",
127 "PERCENT-COMPLETE",
128 "PRIORITY",
129 "RECURRENCE-ID",
130 "SEQUENCE",
131 "STATUS",
132 "SUMMARY",
133 "UID",
134 "URL",
135 "DUE",
136 "DURATION",
137 )
138 exclusive = (
139 "DUE",
140 "DURATION",
141 )
142 multiple = (
143 "ATTACH",
144 "ATTENDEE",
145 "CATEGORIES",
146 "COMMENT",
147 "CONTACT",
148 "EXDATE",
149 "REQUEST-STATUS",
150 "RELATED",
151 "RESOURCES",
152 "RDATE",
153 "RRULE",
154 )
155 DTSTART = create_single_property(
156 "DTSTART",
157 "dt",
158 (datetime, date),
159 date,
160 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.',
161 )
162 DUE = create_single_property(
163 "DUE",
164 "dt",
165 (datetime, date),
166 date,
167 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.',
168 )
169 DURATION = property(
170 property_get_duration,
171 property_set_duration,
172 property_del_duration,
173 property_doc_duration_template.format(component="VTODO"),
174 )
176 def _get_start_end_duration(self):
177 """Verify the calendar validity and return the right attributes."""
178 return get_start_end_duration_with_validation(self, "DTSTART", "DUE", "VTODO")
180 @property
181 def start(self) -> date | datetime:
182 """The start of the VTODO.
184 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar`.
185 If there is no start, we also raise an :exc:`~icalendar.error.IncompleteComponent` error.
187 You can get the start, end and duration of a Todo as follows:
189 >>> from datetime import datetime
190 >>> from icalendar import Todo
191 >>> todo = Todo()
192 >>> todo.start = datetime(2021, 1, 1, 12)
193 >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes
194 >>> todo.duration # 1800 seconds == 30 minutes
195 datetime.timedelta(seconds=1800)
196 >>> print(todo.to_ical())
197 BEGIN:VTODO
198 DTSTART:20210101T120000
199 DUE:20210101T123000
200 END:VTODO
201 """
202 return get_start_property(self)
204 @start.setter
205 def start(self, start: date | datetime | None):
206 """Set the start."""
207 self.DTSTART = start
209 @property
210 def end(self) -> date | datetime:
211 """The end of the todo.
213 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar` error.
214 If there is no end, we also raise an :exc:`~icalendar.error.IncompleteComponent` error.
215 """
216 return get_end_property(self, "DUE")
218 @end.setter
219 def end(self, end: date | datetime | None):
220 """Set the end."""
221 self.DUE = end
223 @property
224 def duration(self) -> timedelta:
225 """The duration of the VTODO.
227 Returns the DURATION property if set, otherwise calculated from start and end.
228 You can set the duration to automatically adjust the end time while keeping
229 start locked.
231 Setting the duration will do the following.
233 1. Keep the start time locked (unchanged)
234 2. Adjust the end time to start + duration
235 3. Remove any existing DUE property
236 4. Set the DURATION property
237 """
238 if "DURATION" not in self and "DUE" in self and "DTSTART" not in self:
239 return timedelta(0)
240 return get_duration_property(self)
242 @duration.setter
243 def duration(self, value: timedelta):
244 if not isinstance(value, timedelta):
245 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
247 # Use the set_duration method with default start-locked behavior
248 self.set_duration(value, locked="start")
250 def set_duration(
251 self, duration: timedelta | None, locked: Literal["start", "end"] = "start"
252 ):
253 """Set the duration of the event relative to either start or end.
255 Parameters:
256 duration: The duration to set, or None to convert to DURATION property
257 locked: Which property to keep unchanged ('start' or 'end')
258 """
259 set_duration_with_locking(self, duration, locked, "DUE")
261 def set_start(
262 self, start: date | datetime, locked: Literal["duration", "end"] | None = None
263 ):
264 """Set the start with explicit locking behavior.
266 Parameters:
267 start: The start time to set
268 locked: Which property to keep unchanged ('duration', 'end', or None
269 for auto-detect)
270 """
271 set_start_with_locking(self, start, locked, "DUE")
273 def set_end(
274 self, end: date | datetime, locked: Literal["start", "duration"] = "start"
275 ):
276 """Set the end of the component, keeping either the start or the duration same.
278 Parameters:
279 end: The end time to set
280 locked: Which property to keep unchanged ('start' or 'duration')
281 """
282 set_end_with_locking(self, end, locked, "DUE")
284 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
285 X_MOZ_LASTACK = X_MOZ_LASTACK_property
287 @property
288 def alarms(self) -> Alarms:
289 """Compute the alarm times for this component.
291 >>> from datetime import datetime
292 >>> from icalendar import Todo
293 >>> todo = Todo() # empty without alarms
294 >>> todo.start = datetime(2024, 10, 26, 10, 21)
295 >>> len(todo.alarms.times)
296 0
298 Note that this only uses DTSTART and DUE, but ignores
299 RDATE, EXDATE, and RRULE properties.
300 """
301 from icalendar.alarms import Alarms
303 return Alarms(self)
305 color = color_property
306 sequence = sequence_property
307 categories = categories_property
308 rdates = rdates_property
309 exdates = exdates_property
310 rrules = rrules_property
311 REQUEST_STATUS = REQUEST_STATUS_property
312 RESOURCES = RESOURCES_property
313 uid = uid_property
314 summary = summary_property
315 description = description_property
316 classification = class_property
317 url = url_property
318 organizer = organizer_property
319 location = location_property
320 priority = priority_property
321 contacts = contacts_property
322 status = status_property
323 attendees = attendees_property
324 attachments = attachments_property
325 images = images_property
326 conferences = conferences_property
327 from icalendar.attr import RECURRENCE_ID
329 @classmethod
330 def new(
331 cls,
332 /,
333 attachments: ATTACHMENTS_TYPE_SETTER = None,
334 attendees: ATTENDEE_TYPE_SETTER = None,
335 categories: Sequence[str] = (),
336 classification: CLASS | None = None,
337 color: str | None = None,
338 comments: list[str] | str | None = None,
339 concepts: CONCEPTS_TYPE_SETTER = None,
340 contacts: list[str] | str | None = None,
341 conferences: list[Conference] | None = None,
342 created: date | None = None,
343 description: str | None = None,
344 end: date | datetime | None = None,
345 last_modified: date | None = None,
346 links: LINKS_TYPE_SETTER = None,
347 location: str | None = None,
348 organizer: vCalAddress | str | None = None,
349 priority: int | None = None,
350 recurrence_id: date | datetime | None = None,
351 refids: list[str] | str | None = None,
352 related_to: RELATED_TO_TYPE_SETTER = None,
353 request_status: list[str] | str | None = None,
354 resources: list[str] | str | None = None,
355 sequence: int | None = None,
356 stamp: date | None = None,
357 start: date | datetime | None = None,
358 status: STATUS | None = None,
359 subcomponents: Iterable[Component] | None = None,
360 summary: str | None = None,
361 uid: str | uuid.UUID | None = None,
362 url: str | None = None,
363 ) -> Self:
364 """Create a new TODO with all required properties.
366 This creates a new Todo in accordance with :rfc:`5545`.
368 Parameters:
369 attachments: The :attr:`attachments` of the todo.
370 attendees: The :attr:`attendees` of the todo.
371 categories: The :attr:`categories` of the todo.
372 classification: The :attr:`classification` of the todo.
373 color: The :attr:`color` of the todo.
374 comments: The :attr:`~icalendar.Component.comments` of the todo.
375 concepts: The :attr:`~icalendar.Component.concepts` of the todo.
376 contacts: The :attr:`contacts` of the todo.
377 conferences: The :attr:`conferences` of the todo.
378 created: The :attr:`~icalendar.Component.created` of the todo.
379 description: The :attr:`description` of the todo.
380 end: The :attr:`end` of the todo.
381 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo.
382 links: The :attr:`~icalendar.Component.links` of the todo.
383 location: The :attr:`location` of the todo.
384 organizer: The :attr:`organizer` of the todo.
385 recurrence_id: The :attr:`RECURRENCE_ID` of the todo.
386 refids: :attr:`~icalendar.Component.refids` of the todo.
387 related_to: :attr:`~icalendar.Component.related_to` of the todo.
388 request_status: The :attr:`REQUEST_STATUS` of the todo.
389 resources: The :attr:`RESOURCES` of the todo.
390 sequence: The :attr:`sequence` of the todo.
391 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo.
392 If None, this is set to the current time.
393 start: The :attr:`start` of the todo.
394 status: The :attr:`status` of the todo.
395 subcomponents: The subcomponents of the todo.
396 summary: The :attr:`summary` of the todo.
397 uid: The :attr:`uid` of the todo.
398 If None, this is set to a new :func:`uuid.uuid4`.
399 url: The :attr:`url` of the todo.
401 Returns:
402 :class:`Todo`
404 Raises:
405 ~error.InvalidCalendar: If the content is not valid
406 according to :rfc:`5545`.
408 .. warning:: As time progresses, we will be stricter with the validation.
409 """
410 todo: Self = super().new(
411 stamp=stamp if stamp is not None else cls._utc_now(),
412 created=created,
413 last_modified=last_modified,
414 comments=comments,
415 links=links,
416 related_to=related_to,
417 refids=refids,
418 concepts=concepts,
419 subcomponents=subcomponents,
420 )
421 todo.summary = summary
422 todo.description = description
423 todo.uid = uid if uid is not None else uuid.uuid4()
424 todo.start = start
425 todo.end = end
426 todo.color = color
427 todo.categories = categories
428 todo.sequence = sequence
429 todo.classification = classification
430 todo.url = url
431 todo.organizer = organizer
432 todo.location = location
433 todo.priority = priority
434 todo.attachments = attachments
435 todo.contacts = contacts
436 todo.status = status
437 todo.REQUEST_STATUS = request_status
438 todo.RESOURCES = resources
439 todo.attendees = attendees
440 todo.conferences = conferences
441 todo.RECURRENCE_ID = recurrence_id
443 if cls._validate_new:
444 cls._validate_start_and_end(start, end)
445 return todo
447 @classmethod
448 def example(cls, name: str = "example") -> Todo:
449 """Return the todo example with the given name."""
450 return cls.from_ical(get_example("todos", name))
453__all__ = ["Todo"]