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 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
317 @classmethod
318 def new(
319 cls,
320 /,
321 attendees: list[vCalAddress] | None = None,
322 categories: Sequence[str] = (),
323 classification: CLASS | None = None,
324 color: str | None = None,
325 comments: list[str] | str | None = None,
326 concepts: CONCEPTS_TYPE_SETTER = None,
327 contacts: list[str] | str | None = None,
328 conferences: list[Conference] | None = None,
329 created: date | None = None,
330 description: str | None = None,
331 end: date | datetime | None = None,
332 last_modified: date | None = None,
333 links: LINKS_TYPE_SETTER = None,
334 location: str | None = None,
335 organizer: vCalAddress | str | None = None,
336 priority: int | None = None,
337 refids: list[str] | str | None = None,
338 related_to: RELATED_TO_TYPE_SETTER = None,
339 sequence: int | None = None,
340 stamp: date | None = None,
341 start: date | datetime | None = None,
342 status: STATUS | None = None,
343 summary: str | None = None,
344 uid: str | uuid.UUID | None = None,
345 url: str | None = None,
346 ):
347 """Create a new TODO with all required properties.
349 This creates a new Todo in accordance with :rfc:`5545`.
351 Parameters:
352 attendees: The :attr:`attendees` of the todo.
353 categories: The :attr:`categories` of the todo.
354 classification: The :attr:`classification` of the todo.
355 color: The :attr:`color` of the todo.
356 comments: The :attr:`~icalendar.Component.comments` of the todo.
357 concepts: The :attr:`~icalendar.Component.concepts` of the todo.
358 contacts: The :attr:`contacts` of the todo.
359 conferences: The :attr:`conferences` of the todo.
360 created: The :attr:`~icalendar.Component.created` of the todo.
361 description: The :attr:`description` of the todo.
362 end: The :attr:`end` of the todo.
363 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo.
364 links: The :attr:`~icalendar.Component.links` of the todo.
365 location: The :attr:`location` of the todo.
366 organizer: The :attr:`organizer` of the todo.
367 refids: :attr:`~icalendar.Component.refids` of the todo.
368 related_to: :attr:`~icalendar.Component.related_to` of the todo.
369 sequence: The :attr:`sequence` of the todo.
370 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo.
371 If None, this is set to the current time.
372 start: The :attr:`start` of the todo.
373 status: The :attr:`status` of the todo.
374 summary: The :attr:`summary` of the todo.
375 uid: The :attr:`uid` of the todo.
376 If None, this is set to a new :func:`uuid.uuid4`.
377 url: The :attr:`url` of the todo.
379 Returns:
380 :class:`Todo`
382 Raises:
383 ~error.InvalidCalendar: If the content is not valid
384 according to :rfc:`5545`.
386 .. warning:: As time progresses, we will be stricter with the validation.
387 """
388 todo: Todo = super().new(
389 stamp=stamp if stamp is not None else cls._utc_now(),
390 created=created,
391 last_modified=last_modified,
392 comments=comments,
393 links=links,
394 related_to=related_to,
395 refids=refids,
396 concepts=concepts,
397 )
398 todo.summary = summary
399 todo.description = description
400 todo.uid = uid if uid is not None else uuid.uuid4()
401 todo.start = start
402 todo.end = end
403 todo.color = color
404 todo.categories = categories
405 todo.sequence = sequence
406 todo.classification = classification
407 todo.url = url
408 todo.organizer = organizer
409 todo.location = location
410 todo.priority = priority
411 todo.contacts = contacts
412 todo.status = status
413 todo.attendees = attendees
414 todo.conferences = conferences
416 if cls._validate_new:
417 cls._validate_start_and_end(start, end)
418 return todo
420 @classmethod
421 def example(cls, name: str = "example") -> Todo:
422 """Return the todo example with the given name."""
423 return cls.from_ical(get_example("todos", name))
426__all__ = ["Todo"]