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:
202 1. Keep the start time locked (unchanged)
203 2. Adjust the end time to start + duration
204 3. Remove any existing DUE property
205 4. Set the DURATION property
206 """
207 return get_duration_property(self)
209 @duration.setter
210 def duration(self, value: timedelta):
211 if not isinstance(value, timedelta):
212 raise TypeError(f"Use timedelta, not {type(value).__name__}.")
214 # Use the set_duration method with default start-locked behavior
215 self.set_duration(value, locked="start")
217 def set_duration(
218 self, duration: timedelta | None, locked: Literal["start", "end"] = "start"
219 ):
220 """Set the duration of the event relative to either start or end.
222 Args:
223 duration: The duration to set, or None to convert to DURATION property
224 locked: Which property to keep unchanged ('start' or 'end')
225 """
226 set_duration_with_locking(self, duration, locked, "DUE")
228 def set_start(
229 self, start: date | datetime, locked: Literal["duration", "end"] | None = None
230 ):
231 """Set the start with explicit locking behavior.
233 Args:
234 start: The start time to set
235 locked: Which property to keep unchanged ('duration', 'end', or None
236 for auto-detect)
237 """
238 set_start_with_locking(self, start, locked, "DUE")
240 def set_end(
241 self, end: date | datetime, locked: Literal["start", "duration"] = "start"
242 ):
243 """Set the end of the component, keeping either the start or the duration same.
245 Args:
246 end: The end time to set
247 locked: Which property to keep unchanged ('start' or 'duration')
248 """
249 set_end_with_locking(self, end, locked, "DUE")
251 X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property
252 X_MOZ_LASTACK = X_MOZ_LASTACK_property
254 @property
255 def alarms(self) -> Alarms:
256 """Compute the alarm times for this component.
258 >>> from datetime import datetime
259 >>> from icalendar import Todo
260 >>> todo = Todo() # empty without alarms
261 >>> todo.start = datetime(2024, 10, 26, 10, 21)
262 >>> len(todo.alarms.times)
263 0
265 Note that this only uses DTSTART and DUE, but ignores
266 RDATE, EXDATE, and RRULE properties.
267 """
268 from icalendar.alarms import Alarms
270 return Alarms(self)
272 color = color_property
273 sequence = sequence_property
274 categories = categories_property
275 rdates = rdates_property
276 exdates = exdates_property
277 rrules = rrules_property
278 uid = uid_property
279 summary = summary_property
280 description = description_property
281 classification = class_property
282 url = url_property
283 organizer = organizer_property
284 location = location_property
285 priority = priority_property
286 contacts = contacts_property
287 status = status_property
288 attendees = attendees_property
289 images = images_property
290 conferences = conferences_property
292 @classmethod
293 def new(
294 cls,
295 /,
296 attendees: list[vCalAddress] | None = None,
297 categories: Sequence[str] = (),
298 classification: CLASS | None = None,
299 color: str | None = None,
300 comments: list[str] | str | None = None,
301 concepts: CONCEPTS_TYPE_SETTER = None,
302 contacts: list[str] | str | None = None,
303 conferences: list[Conference] | None = None,
304 created: date | None = None,
305 description: str | None = None,
306 end: date | datetime | None = None,
307 last_modified: date | None = None,
308 links: LINKS_TYPE_SETTER = None,
309 location: str | None = None,
310 organizer: vCalAddress | str | None = None,
311 priority: int | None = None,
312 refids: list[str] | str | None = None,
313 related_to: RELATED_TO_TYPE_SETTER = None,
314 sequence: int | None = None,
315 stamp: date | None = None,
316 start: date | datetime | None = None,
317 status: STATUS | None = None,
318 summary: str | None = None,
319 uid: str | uuid.UUID | None = None,
320 url: str | None = None,
321 ):
322 """Create a new TODO with all required properties.
324 This creates a new Todo in accordance with :rfc:`5545`.
326 Arguments:
327 attendees: The :attr:`attendees` of the todo.
328 categories: The :attr:`categories` of the todo.
329 classification: The :attr:`classification` of the todo.
330 color: The :attr:`color` of the todo.
331 comments: The :attr:`~icalendar.Component.comments` of the todo.
332 concepts: The :attr:`~icalendar.Component.concepts` of the todo.
333 contacts: The :attr:`contacts` of the todo.
334 conferences: The :attr:`conferences` of the todo.
335 created: The :attr:`~icalendar.Component.created` of the todo.
336 description: The :attr:`description` of the todo.
337 end: The :attr:`end` of the todo.
338 last_modified: The :attr:`~icalendar.Component.last_modified` of the todo.
339 links: The :attr:`~icalendar.Component.links` of the todo.
340 location: The :attr:`location` of the todo.
341 organizer: The :attr:`organizer` of the todo.
342 refids: :attr:`~icalendar.Component.refids` of the todo.
343 related_to: :attr:`~icalendar.Component.related_to` of the todo.
344 sequence: The :attr:`sequence` of the todo.
345 stamp: The :attr:`~icalendar.Component.DTSTAMP` of the todo.
346 If None, this is set to the current time.
347 start: The :attr:`start` of the todo.
348 status: The :attr:`status` of the todo.
349 summary: The :attr:`summary` of the todo.
350 uid: The :attr:`uid` of the todo.
351 If None, this is set to a new :func:`uuid.uuid4`.
352 url: The :attr:`url` of the todo.
354 Returns:
355 :class:`Todo`
357 Raises:
358 ~error.InvalidCalendar: If the content is not valid according to :rfc:`5545`.
360 .. warning:: As time progresses, we will be stricter with the validation.
361 """
362 todo: Todo = super().new(
363 stamp=stamp if stamp is not None else cls._utc_now(),
364 created=created,
365 last_modified=last_modified,
366 comments=comments,
367 links=links,
368 related_to=related_to,
369 refids=refids,
370 concepts=concepts,
371 )
372 todo.summary = summary
373 todo.description = description
374 todo.uid = uid if uid is not None else uuid.uuid4()
375 todo.start = start
376 todo.end = end
377 todo.color = color
378 todo.categories = categories
379 todo.sequence = sequence
380 todo.classification = classification
381 todo.url = url
382 todo.organizer = organizer
383 todo.location = location
384 todo.priority = priority
385 todo.contacts = contacts
386 todo.status = status
387 todo.attendees = attendees
388 todo.conferences = conferences
390 if cls._validate_new:
391 cls._validate_start_and_end(start, end)
392 return todo
394 @classmethod
395 def example(cls, name: str = "example") -> "Todo":
396 """Return the todo example with the given name."""
397 return cls.from_ical(get_example("todos", name))
399__all__ = ["Todo"]