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