Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/cal/todo.py: 59%
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 return get_duration_property(self)
240 @duration.setter
241 def duration(self, value: timedelta):
242 if not isinstance(value, timedelta):
243 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
245 # Use the set_duration method with default start-locked behavior
246 self.set_duration(value, locked="start")
248 def set_duration(
249 self, duration: timedelta | None, locked: Literal["start", "end"] = "start"
250 ):
251 """Set the duration of the event relative to either start or end.
253 Parameters:
254 duration: The duration to set, or None to convert to DURATION property
255 locked: Which property to keep unchanged ('start' or 'end')
256 """
257 set_duration_with_locking(self, duration, locked, "DUE")
259 def set_start(
260 self, start: date | datetime, locked: Literal["duration", "end"] | None = None
261 ):
262 """Set the start with explicit locking behavior.
264 Parameters:
265 start: The start time to set
266 locked: Which property to keep unchanged ('duration', 'end', or None
267 for auto-detect)
268 """
269 set_start_with_locking(self, start, locked, "DUE")
271 def set_end(
272 self, end: date | datetime, locked: Literal["start", "duration"] = "start"
273 ):
274 """Set the end of the component, keeping either the start or the duration same.
276 Parameters:
277 end: The end time to set
278 locked: Which property to keep unchanged ('start' or 'duration')
279 """
280 set_end_with_locking(self, end, locked, "DUE")
282 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
283 X_MOZ_LASTACK = X_MOZ_LASTACK_property
285 @property
286 def alarms(self) -> Alarms:
287 """Compute the alarm times for this component.
289 >>> from datetime import datetime
290 >>> from icalendar import Todo
291 >>> todo = Todo() # empty without alarms
292 >>> todo.start = datetime(2024, 10, 26, 10, 21)
293 >>> len(todo.alarms.times)
294 0
296 Note that this only uses DTSTART and DUE, but ignores
297 RDATE, EXDATE, and RRULE properties.
298 """
299 from icalendar.alarms import Alarms
301 return Alarms(self)
303 color = color_property
304 sequence = sequence_property
305 categories = categories_property
306 rdates = rdates_property
307 exdates = exdates_property
308 rrules = rrules_property
309 REQUEST_STATUS = REQUEST_STATUS_property
310 RESOURCES = RESOURCES_property
311 uid = uid_property
312 summary = summary_property
313 description = description_property
314 classification = class_property
315 url = url_property
316 organizer = organizer_property
317 location = location_property
318 priority = priority_property
319 contacts = contacts_property
320 status = status_property
321 attendees = attendees_property
322 attachments = attachments_property
323 images = images_property
324 conferences = conferences_property
325 from icalendar.attr import RECURRENCE_ID
327 @classmethod
328 def new(
329 cls,
330 /,
331 attachments: ATTACHMENTS_TYPE_SETTER = None,
332 attendees: ATTENDEE_TYPE_SETTER = None,
333 categories: Sequence[str] = (),
334 classification: CLASS | None = None,
335 color: str | None = None,
336 comments: list[str] | str | None = None,
337 concepts: CONCEPTS_TYPE_SETTER = None,
338 contacts: list[str] | str | None = None,
339 conferences: list[Conference] | None = None,
340 created: date | None = None,
341 description: str | None = None,
342 end: date | datetime | None = None,
343 last_modified: date | None = None,
344 links: LINKS_TYPE_SETTER = None,
345 location: str | None = None,
346 organizer: vCalAddress | str | None = None,
347 priority: int | None = None,
348 recurrence_id: date | datetime | None = None,
349 refids: list[str] | str | None = None,
350 related_to: RELATED_TO_TYPE_SETTER = None,
351 request_status: list[str] | str | None = None,
352 resources: list[str] | str | None = None,
353 sequence: int | None = None,
354 stamp: date | None = None,
355 start: date | datetime | None = None,
356 status: STATUS | None = None,
357 subcomponents: Iterable[Component] | None = None,
358 summary: str | None = None,
359 uid: str | uuid.UUID | None = None,
360 url: str | None = None,
361 ) -> Self:
362 """Create a new TODO with all required properties.
364 This creates a new Todo in accordance with :rfc:`5545`.
366 Parameters:
367 attachments: The :attr:`attachments` of the todo.
368 attendees: The :attr:`attendees` of the todo.
369 categories: The :attr:`categories` of the todo.
370 classification: The :attr:`classification` of the todo.
371 color: The :attr:`color` of the todo.
372 comments: The :attr:`~icalendar.Component.comments` of the todo.
373 concepts: The :attr:`~icalendar.Component.concepts` of the todo.
374 contacts: The :attr:`contacts` of the todo.
375 conferences: The :attr:`conferences` of the todo.
376 created: The :attr:`~icalendar.Component.created` of the todo.
377 description: The :attr:`description` of the todo.
378 end: The :attr:`end` of the todo.
379 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo.
380 links: The :attr:`~icalendar.Component.links` of the todo.
381 location: The :attr:`location` of the todo.
382 organizer: The :attr:`organizer` of the todo.
383 recurrence_id: The :attr:`RECURRENCE_ID` of the todo.
384 refids: :attr:`~icalendar.Component.refids` of the todo.
385 related_to: :attr:`~icalendar.Component.related_to` of the todo.
386 request_status: The :attr:`REQUEST_STATUS` of the todo.
387 resources: The :attr:`RESOURCES` of the todo.
388 sequence: The :attr:`sequence` of the todo.
389 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo.
390 If None, this is set to the current time.
391 start: The :attr:`start` of the todo.
392 status: The :attr:`status` of the todo.
393 subcomponents: The subcomponents of the todo.
394 summary: The :attr:`summary` of the todo.
395 uid: The :attr:`uid` of the todo.
396 If None, this is set to a new :func:`uuid.uuid4`.
397 url: The :attr:`url` of the todo.
399 Returns:
400 :class:`Todo`
402 Raises:
403 ~error.InvalidCalendar: If the content is not valid
404 according to :rfc:`5545`.
406 .. warning:: As time progresses, we will be stricter with the validation.
407 """
408 todo: Self = super().new(
409 stamp=stamp if stamp is not None else cls._utc_now(),
410 created=created,
411 last_modified=last_modified,
412 comments=comments,
413 links=links,
414 related_to=related_to,
415 refids=refids,
416 concepts=concepts,
417 subcomponents=subcomponents,
418 )
419 todo.summary = summary
420 todo.description = description
421 todo.uid = uid if uid is not None else uuid.uuid4()
422 todo.start = start
423 todo.end = end
424 todo.color = color
425 todo.categories = categories
426 todo.sequence = sequence
427 todo.classification = classification
428 todo.url = url
429 todo.organizer = organizer
430 todo.location = location
431 todo.priority = priority
432 todo.attachments = attachments
433 todo.contacts = contacts
434 todo.status = status
435 todo.REQUEST_STATUS = request_status
436 todo.RESOURCES = resources
437 todo.attendees = attendees
438 todo.conferences = conferences
439 todo.RECURRENCE_ID = recurrence_id
441 if cls._validate_new:
442 cls._validate_start_and_end(start, end)
443 return todo
445 @classmethod
446 def example(cls, name: str = "example") -> Todo:
447 """Return the todo example with the given name."""
448 return cls.from_ical(get_example("todos", name))
451__all__ = ["Todo"]