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