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 """
78 name = "VTODO"
80 required = (
81 "UID",
82 "DTSTAMP",
83 )
84 singletons = (
85 "CLASS",
86 "COLOR",
87 "COMPLETED",
88 "CREATED",
89 "DESCRIPTION",
90 "DTSTAMP",
91 "DTSTART",
92 "GEO",
93 "LAST-MODIFIED",
94 "LOCATION",
95 "ORGANIZER",
96 "PERCENT-COMPLETE",
97 "PRIORITY",
98 "RECURRENCE-ID",
99 "SEQUENCE",
100 "STATUS",
101 "SUMMARY",
102 "UID",
103 "URL",
104 "DUE",
105 "DURATION",
106 )
107 exclusive = (
108 "DUE",
109 "DURATION",
110 )
111 multiple = (
112 "ATTACH",
113 "ATTENDEE",
114 "CATEGORIES",
115 "COMMENT",
116 "CONTACT",
117 "EXDATE",
118 "RSTATUS",
119 "RELATED",
120 "RESOURCES",
121 "RDATE",
122 "RRULE",
123 )
124 DTSTART = create_single_property(
125 "DTSTART",
126 "dt",
127 (datetime, date),
128 date,
129 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', # noqa: E501
130 )
131 DUE = create_single_property(
132 "DUE",
133 "dt",
134 (datetime, date),
135 date,
136 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', # noqa: E501
137 )
138 DURATION = property(
139 property_get_duration,
140 property_set_duration,
141 property_del_duration,
142 property_doc_duration_template.format(component="VTODO"),
143 )
145 def _get_start_end_duration(self):
146 """Verify the calendar validity and return the right attributes."""
147 return get_start_end_duration_with_validation(self, "DTSTART", "DUE", "VTODO")
149 @property
150 def start(self) -> date | datetime:
151 """The start of the VTODO.
153 Invalid values raise an InvalidCalendar.
154 If there is no start, we also raise an IncompleteComponent error.
156 You can get the start, end and duration of a Todo as follows:
158 >>> from datetime import datetime
159 >>> from icalendar import Todo
160 >>> todo = Todo()
161 >>> todo.start = datetime(2021, 1, 1, 12)
162 >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes
163 >>> todo.duration # 1800 seconds == 30 minutes
164 datetime.timedelta(seconds=1800)
165 >>> print(todo.to_ical())
166 BEGIN:VTODO
167 DTSTART:20210101T120000
168 DUE:20210101T123000
169 END:VTODO
170 """
171 return get_start_property(self)
173 @start.setter
174 def start(self, start: date | datetime | None):
175 """Set the start."""
176 self.DTSTART = start
178 @property
179 def end(self) -> date | datetime:
180 """The end of the todo.
182 Invalid values raise an InvalidCalendar error.
183 If there is no end, we also raise an IncompleteComponent error.
184 """
185 return get_end_property(self, "DUE")
187 @end.setter
188 def end(self, end: date | datetime | None):
189 """Set the end."""
190 self.DUE = end
192 @property
193 def duration(self) -> timedelta:
194 """The duration of the VTODO.
196 Returns the DURATION property if set, otherwise calculated from start and end.
197 You can set the duration to automatically adjust the end time while keeping
198 start locked.
200 Setting the duration will:
201 1. Keep the start time locked (unchanged)
202 2. Adjust the end time to start + duration
203 3. Remove any existing DUE property
204 4. Set the DURATION property
205 """
206 return get_duration_property(self)
208 @duration.setter
209 def duration(self, value: timedelta):
210 if not isinstance(value, timedelta):
211 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
213 # Use the set_duration method with default start-locked behavior
214 self.set_duration(value, locked="start")
216 def set_duration(
217 self, duration: timedelta | None, locked: Literal["start", "end"] = "start"
218 ):
219 """Set the duration of the event relative to either start or end.
221 Args:
222 duration: The duration to set, or None to convert to DURATION property
223 locked: Which property to keep unchanged ('start' or 'end')
224 """
225 set_duration_with_locking(self, duration, locked, "DUE")
227 def set_start(
228 self, start: date | datetime, locked: Literal["duration", "end"] | None = None
229 ):
230 """Set the start with explicit locking behavior.
232 Args:
233 start: The start time to set
234 locked: Which property to keep unchanged ('duration', 'end', or None
235 for auto-detect)
236 """
237 set_start_with_locking(self, start, locked, "DUE")
239 def set_end(
240 self, end: date | datetime, locked: Literal["start", "duration"] = "start"
241 ):
242 """Set the end of the component, keeping either the start or the duration same.
244 Args:
245 end: The end time to set
246 locked: Which property to keep unchanged ('start' or 'duration')
247 """
248 set_end_with_locking(self, end, locked, "DUE")
250 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
251 X_MOZ_LASTACK = X_MOZ_LASTACK_property
253 @property
254 def alarms(self) -> Alarms:
255 """Compute the alarm times for this component.
257 >>> from datetime import datetime
258 >>> from icalendar import Todo
259 >>> todo = Todo() # empty without alarms
260 >>> todo.start = datetime(2024, 10, 26, 10, 21)
261 >>> len(todo.alarms.times)
262 0
264 Note that this only uses DTSTART and DUE, but ignores
265 RDATE, EXDATE, and RRULE properties.
266 """
267 from icalendar.alarms import Alarms
269 return Alarms(self)
271 color = color_property
272 sequence = sequence_property
273 categories = categories_property
274 rdates = rdates_property
275 exdates = exdates_property
276 rrules = rrules_property
277 uid = uid_property
278 summary = summary_property
279 description = description_property
280 classification = class_property
281 url = url_property
282 organizer = organizer_property
283 location = location_property
284 priority = priority_property
285 contacts = contacts_property
286 status = status_property
287 attendees = attendees_property
288 images = images_property
289 conferences = conferences_property
291 @classmethod
292 def new(
293 cls,
294 /,
295 attendees: list[vCalAddress] | None = None,
296 categories: Sequence[str] = (),
297 classification: CLASS | None = None,
298 color: str | None = None,
299 comments: list[str] | str | None = None,
300 concepts: CONCEPTS_TYPE_SETTER = None,
301 contacts: list[str] | str | None = None,
302 conferences: list[Conference] | None = None,
303 created: date | None = None,
304 description: str | None = None,
305 end: date | datetime | None = None,
306 last_modified: date | None = None,
307 links: LINKS_TYPE_SETTER = None,
308 location: str | None = None,
309 organizer: vCalAddress | str | None = None,
310 priority: int | None = None,
311 refids: list[str] | str | None = None,
312 related_to: RELATED_TO_TYPE_SETTER = None,
313 sequence: int | None = None,
314 stamp: date | None = None,
315 start: date | datetime | None = None,
316 status: STATUS | None = None,
317 summary: str | None = None,
318 uid: str | uuid.UUID | None = None,
319 url: str | None = None,
320 ):
321 """Create a new TODO with all required properties.
323 This creates a new Todo in accordance with :rfc:`5545`.
325 Arguments:
326 attendees: The :attr:`attendees` of the todo.
327 categories: The :attr:`categories` of the todo.
328 classification: The :attr:`classification` of the todo.
329 color: The :attr:`color` of the todo.
330 comments: The :attr:`~icalendar.Component.comments` of the todo.
331 concepts: The :attr:`~icalendar.Component.concepts` of the todo.
332 contacts: The :attr:`contacts` of the todo.
333 conferences: The :attr:`conferences` of the todo.
334 created: The :attr:`~icalendar.Component.created` of the todo.
335 description: The :attr:`description` of the todo.
336 end: The :attr:`end` of the todo.
337 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo.
338 links: The :attr:`~icalendar.Component.links` of the todo.
339 location: The :attr:`location` of the todo.
340 organizer: The :attr:`organizer` of the todo.
341 refids: :attr:`~icalendar.Component.refids` of the todo.
342 related_to: :attr:`~icalendar.Component.related_to` of the todo.
343 sequence: The :attr:`sequence` of the todo.
344 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo.
345 If None, this is set to the current time.
346 start: The :attr:`start` of the todo.
347 status: The :attr:`status` of the todo.
348 summary: The :attr:`summary` of the todo.
349 uid: The :attr:`uid` of the todo.
350 If None, this is set to a new :func:`uuid.uuid4`.
351 url: The :attr:`url` of the todo.
353 Returns:
354 :class:`Todo`
356 Raises:
357 InvalidCalendar: If the content is not valid according to :rfc:`5545`.
359 .. warning:: As time progresses, we will be stricter with the validation.
360 """
361 todo: Todo = super().new(
362 stamp=stamp if stamp is not None else cls._utc_now(),
363 created=created,
364 last_modified=last_modified,
365 comments=comments,
366 links=links,
367 related_to=related_to,
368 refids=refids,
369 concepts=concepts,
370 )
371 todo.summary = summary
372 todo.description = description
373 todo.uid = uid if uid is not None else uuid.uuid4()
374 todo.start = start
375 todo.end = end
376 todo.color = color
377 todo.categories = categories
378 todo.sequence = sequence
379 todo.classification = classification
380 todo.url = url
381 todo.organizer = organizer
382 todo.location = location
383 todo.priority = priority
384 todo.contacts = contacts
385 todo.status = status
386 todo.attendees = attendees
387 todo.conferences = conferences
389 if cls._validate_new:
390 cls._validate_start_and_end(start, end)
391 return todo
393 @classmethod
394 def example(cls, name: str = "example") -> "Todo":
395 """Return the todo example with the given name."""
396 return cls.from_ical(get_example("todos", name))
398__all__ = ["Todo"]