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 of the VTODO.
208 Sets the ``DTSTART`` property. Pass ``None`` to remove it.
210 Parameters:
211 start: Required. The start as :class:`datetime.date` or
212 :class:`datetime.datetime`, or ``None`` to remove ``DTSTART``.
214 See also:
215 :attr:`end`, :attr:`duration`
216 """
217 self.DTSTART = start
219 @property
220 def end(self) -> date | datetime:
221 """The end of the todo.
223 Invalid values raise an :exc:`~icalendar.error.InvalidCalendar` error.
224 If there is no end, we also raise an :exc:`~icalendar.error.IncompleteComponent` error.
225 """
226 return get_end_property(self, "DUE")
228 @end.setter
229 def end(self, end: date | datetime | None):
230 """Set the end of the VTODO.
232 Sets the ``DUE`` property. Pass ``None`` to remove it.
234 Parameters:
235 end: Required. The end as :class:`datetime.date` or
236 :class:`datetime.datetime`, or ``None`` to remove ``DUE``.
238 See also:
239 :attr:`start`, :attr:`duration`
240 """
241 self.DUE = end
243 @property
244 def duration(self) -> timedelta:
245 """The duration of the VTODO.
247 Returns the DURATION property if set, otherwise calculated from start and end.
248 You can set the duration to automatically adjust the end time while keeping
249 start locked.
251 Setting the duration will do the following.
253 1. Keep the start time locked (unchanged)
254 2. Adjust the end time to start + duration
255 3. Remove any existing DUE property
256 4. Set the DURATION property
257 """
258 if "DURATION" not in self and "DUE" in self and "DTSTART" not in self:
259 return timedelta(0)
260 return get_duration_property(self)
262 @duration.setter
263 def duration(self, value: timedelta):
264 if not isinstance(value, timedelta):
265 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
267 # Use the set_duration method with default start-locked behavior
268 self.set_duration(value, locked="start")
270 def set_duration(
271 self, duration: timedelta | None, locked: Literal["start", "end"] = "start"
272 ):
273 """Set the duration of the event relative to either start or end.
275 Parameters:
276 duration: The duration to set, or None to convert to DURATION property
277 locked: Which property to keep unchanged ('start' or 'end')
278 """
279 set_duration_with_locking(self, duration, locked, "DUE")
281 def set_start(
282 self, start: date | datetime, locked: Literal["duration", "end"] | None = None
283 ):
284 """Set the start with explicit locking behavior.
286 Parameters:
287 start: The start time to set
288 locked: Which property to keep unchanged ('duration', 'end', or None
289 for auto-detect)
290 """
291 set_start_with_locking(self, start, locked, "DUE")
293 def set_end(
294 self, end: date | datetime, locked: Literal["start", "duration"] = "start"
295 ):
296 """Set the end of the component, keeping either the start or the duration same.
298 Parameters:
299 end: The end time to set
300 locked: Which property to keep unchanged ('start' or 'duration')
301 """
302 set_end_with_locking(self, end, locked, "DUE")
304 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
305 X_MOZ_LASTACK = X_MOZ_LASTACK_property
307 @property
308 def alarms(self) -> Alarms:
309 """Compute the alarm times for this component.
311 >>> from datetime import datetime
312 >>> from icalendar import Todo
313 >>> todo = Todo() # empty without alarms
314 >>> todo.start = datetime(2024, 10, 26, 10, 21)
315 >>> len(todo.alarms.times)
316 0
318 Note that this only uses DTSTART and DUE, but ignores
319 RDATE, EXDATE, and RRULE properties.
320 """
321 from icalendar.alarms import Alarms
323 return Alarms(self)
325 color = color_property
326 sequence = sequence_property
327 categories = categories_property
328 rdates = rdates_property
329 exdates = exdates_property
330 rrules = rrules_property
331 REQUEST_STATUS = REQUEST_STATUS_property
332 RESOURCES = RESOURCES_property
333 uid = uid_property
334 summary = summary_property
335 description = description_property
336 classification = class_property
337 url = url_property
338 organizer = organizer_property
339 location = location_property
340 priority = priority_property
341 contacts = contacts_property
342 status = status_property
343 attendees = attendees_property
344 attachments = attachments_property
345 images = images_property
346 conferences = conferences_property
347 from icalendar.attr import RECURRENCE_ID
349 @classmethod
350 def new(
351 cls,
352 /,
353 attachments: ATTACHMENTS_TYPE_SETTER = None,
354 attendees: ATTENDEE_TYPE_SETTER = None,
355 categories: Sequence[str] = (),
356 classification: CLASS | None = None,
357 color: str | None = None,
358 comments: list[str] | str | None = None,
359 concepts: CONCEPTS_TYPE_SETTER = None,
360 contacts: list[str] | str | None = None,
361 conferences: list[Conference] | None = None,
362 created: date | None = None,
363 description: str | None = None,
364 end: date | datetime | None = None,
365 last_modified: date | None = None,
366 links: LINKS_TYPE_SETTER = None,
367 location: str | None = None,
368 organizer: vCalAddress | str | None = None,
369 priority: int | None = None,
370 recurrence_id: date | datetime | None = None,
371 refids: list[str] | str | None = None,
372 related_to: RELATED_TO_TYPE_SETTER = None,
373 request_status: list[str] | str | None = None,
374 resources: list[str] | str | None = None,
375 sequence: int | None = None,
376 stamp: date | None = None,
377 start: date | datetime | None = None,
378 status: STATUS | None = None,
379 subcomponents: Iterable[Component] | None = None,
380 summary: str | None = None,
381 uid: str | uuid.UUID | None = None,
382 url: str | None = None,
383 ) -> Self:
384 """Create a new TODO with all required properties.
386 This creates a new Todo in accordance with :rfc:`5545`.
388 Parameters:
389 attachments: The :attr:`attachments` of the todo.
390 attendees: The :attr:`attendees` of the todo.
391 categories: The :attr:`categories` of the todo.
392 classification: The :attr:`classification` of the todo.
393 color: The :attr:`color` of the todo.
394 comments: The :attr:`~icalendar.cal.component.Component.comments` of the todo.
395 concepts: The :attr:`~icalendar.cal.component.Component.concepts` of the todo.
396 contacts: The :attr:`contacts` of the todo.
397 conferences: The :attr:`conferences` of the todo.
398 created: The :attr:`~icalendar.cal.component.Component.created` of the todo.
399 description: The :attr:`description` of the todo.
400 end: The :attr:`end` of the todo.
401 last_modified: The :attr:`~icalendar.cal.component.Component.last_modified` of the todo.
402 links: The :attr:`~icalendar.cal.component.Component.links` of the todo.
403 location: The :attr:`location` of the todo.
404 organizer: The :attr:`organizer` of the todo.
405 recurrence_id: The :attr:`RECURRENCE_ID` of the todo.
406 refids: :attr:`~icalendar.cal.component.Component.refids` of the todo.
407 related_to: :attr:`~icalendar.cal.component.Component.related_to` of the todo.
408 request_status: The :attr:`REQUEST_STATUS` of the todo.
409 resources: The :attr:`RESOURCES` of the todo.
410 sequence: The :attr:`sequence` of the todo.
411 stamp: The :attr:`~icalendar.cal.component.Component.DTSTAMP` of the todo.
412 If None, this is set to the current time.
413 start: The :attr:`start` of the todo.
414 status: The :attr:`status` of the todo.
415 subcomponents: The subcomponents of the todo.
416 summary: The :attr:`summary` of the todo.
417 uid: The :attr:`uid` of the todo.
418 If None, this is set to a new :func:`uuid.uuid4`.
419 url: The :attr:`url` of the todo.
421 Returns:
422 :class:`Todo`
424 Raises:
425 ~icalendar.error.InvalidCalendar: If the content is not valid
426 according to :rfc:`5545`.
428 .. warning:: As time progresses, we will be stricter with the validation.
429 """
430 todo: Self = super().new(
431 stamp=stamp if stamp is not None else cls._utc_now(),
432 created=created,
433 last_modified=last_modified,
434 comments=comments,
435 links=links,
436 related_to=related_to,
437 refids=refids,
438 concepts=concepts,
439 subcomponents=subcomponents,
440 )
441 todo.summary = summary
442 todo.description = description
443 todo.uid = uid if uid is not None else uuid.uuid4()
444 todo.start = start
445 todo.end = end
446 todo.color = color
447 todo.categories = categories
448 todo.sequence = sequence
449 todo.classification = classification
450 todo.url = url
451 todo.organizer = organizer
452 todo.location = location
453 todo.priority = priority
454 todo.attachments = attachments
455 todo.contacts = contacts
456 todo.status = status
457 todo.REQUEST_STATUS = request_status
458 todo.RESOURCES = resources
459 todo.attendees = attendees
460 todo.conferences = conferences
461 todo.RECURRENCE_ID = recurrence_id
463 if cls._validate_new:
464 cls._validate_start_and_end(start, end)
465 return todo
467 @classmethod
468 def example(cls, name: str = "example") -> Todo:
469 """Return the todo example with the given name."""
470 return cls.from_ical(get_example("todos", name))
473__all__ = ["Todo"]