Source code for icalendar.cal.todo

""":rfc:`5545` VTODO component."""

from __future__ import annotations

import uuid
from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, Literal, Sequence

from icalendar.attr import (
    X_MOZ_LASTACK_property,
    X_MOZ_SNOOZE_TIME_property,
    attendees_property,
    categories_property,
    class_property,
    color_property,
    conferences_property,
    contacts_property,
    create_single_property,
    description_property,
    exdates_property,
    images_property,
    get_duration_property,
    get_end_property,
    get_start_end_duration_with_validation,
    get_start_property,
    location_property,
    organizer_property,
    priority_property,
    property_del_duration,
    property_doc_duration_template,
    property_get_duration,
    property_set_duration,
    rdates_property,
    rrules_property,
    sequence_property,
    set_duration_with_locking,
    set_end_with_locking,
    set_start_with_locking,
    status_property,
    summary_property,
    uid_property,
    url_property,
)
from icalendar.cal.component import Component

if TYPE_CHECKING:
    from icalendar.alarms import Alarms
    from icalendar.enums import CLASS, STATUS
    from icalendar.prop import vCalAddress
    from icalendar.prop.conference import Conference


[docs] class Todo(Component): """ A "VTODO" calendar component is a grouping of component properties that represents an action item or assignment. For example, it can be used to represent an item of work assigned to an individual, such as "Prepare for the upcoming conference seminar on Internet Calendaring". Examples: Create a new Todo: >>> from icalendar import Todo >>> todo = Todo.new() >>> print(todo.to_ical()) BEGIN:VTODO DTSTAMP:20250517T080612Z UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 END:VTODO """ name = "VTODO" required = ( "UID", "DTSTAMP", ) singletons = ( "CLASS", "COLOR", "COMPLETED", "CREATED", "DESCRIPTION", "DTSTAMP", "DTSTART", "GEO", "LAST-MODIFIED", "LOCATION", "ORGANIZER", "PERCENT-COMPLETE", "PRIORITY", "RECURRENCE-ID", "SEQUENCE", "STATUS", "SUMMARY", "UID", "URL", "DUE", "DURATION", ) exclusive = ( "DUE", "DURATION", ) multiple = ( "ATTACH", "ATTENDEE", "CATEGORIES", "COMMENT", "CONTACT", "EXDATE", "RSTATUS", "RELATED", "RESOURCES", "RDATE", "RRULE", ) DTSTART = create_single_property( "DTSTART", "dt", (datetime, date), date, 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', # noqa: E501 ) DUE = create_single_property( "DUE", "dt", (datetime, date), date, 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', # noqa: E501 ) DURATION = property( property_get_duration, property_set_duration, property_del_duration, property_doc_duration_template.format(component="VTODO"), ) def _get_start_end_duration(self): """Verify the calendar validity and return the right attributes.""" return get_start_end_duration_with_validation(self, "DTSTART", "DUE", "VTODO") @property def start(self) -> date | datetime: """The start of the VTODO. Invalid values raise an InvalidCalendar. If there is no start, we also raise an IncompleteComponent error. You can get the start, end and duration of a Todo as follows: >>> from datetime import datetime >>> from icalendar import Todo >>> todo = Todo() >>> todo.start = datetime(2021, 1, 1, 12) >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes >>> todo.duration # 1800 seconds == 30 minutes datetime.timedelta(seconds=1800) >>> print(todo.to_ical()) BEGIN:VTODO DTSTART:20210101T120000 DUE:20210101T123000 END:VTODO """ return get_start_property(self) @start.setter def start(self, start: date | datetime | None): """Set the start.""" self.DTSTART = start @property def end(self) -> date | datetime: """The end of the todo. Invalid values raise an InvalidCalendar error. If there is no end, we also raise an IncompleteComponent error. """ return get_end_property(self, "DUE") @end.setter def end(self, end: date | datetime | None): """Set the end.""" self.DUE = end @property def duration(self) -> timedelta: """The duration of the VTODO. Returns the DURATION property if set, otherwise calculated from start and end. You can set the duration to automatically adjust the end time while keeping start locked. Setting the duration will: 1. Keep the start time locked (unchanged) 2. Adjust the end time to start + duration 3. Remove any existing DUE property 4. Set the DURATION property """ return get_duration_property(self) @duration.setter def duration(self, value: timedelta): if not isinstance(value, timedelta): raise TypeError(f"Use timedelta, not {type(value).__name__}.") # Use the set_duration method with default start-locked behavior self.set_duration(value, locked="start")
[docs] def set_duration( self, duration: timedelta | None, locked: Literal["start", "end"] = "start" ): """Set the duration of the event relative to either start or end. Args: duration: The duration to set, or None to convert to DURATION property locked: Which property to keep unchanged ('start' or 'end') """ set_duration_with_locking(self, duration, locked, "DUE")
[docs] def set_start( self, start: date | datetime, locked: Literal["duration", "end"] | None = None ): """Set the start with explicit locking behavior. Args: start: The start time to set locked: Which property to keep unchanged ('duration', 'end', or None for auto-detect) """ set_start_with_locking(self, start, locked, "DUE")
[docs] def set_end( self, end: date | datetime, locked: Literal["start", "duration"] = "start" ): """Set the end of the component, keeping either the start or the duration same. Args: end: The end time to set locked: Which property to keep unchanged ('start' or 'duration') """ set_end_with_locking(self, end, locked, "DUE")
X_MOZ_SNOOZE_TIME = X_MOZ_SNOOZE_TIME_property X_MOZ_LASTACK = X_MOZ_LASTACK_property @property def alarms(self) -> Alarms: """Compute the alarm times for this component. >>> from datetime import datetime >>> from icalendar import Todo >>> todo = Todo() # empty without alarms >>> todo.start = datetime(2024, 10, 26, 10, 21) >>> len(todo.alarms.times) 0 Note that this only uses DTSTART and DUE, but ignores RDATE, EXDATE, and RRULE properties. """ from icalendar.alarms import Alarms return Alarms(self) color = color_property sequence = sequence_property categories = categories_property rdates = rdates_property exdates = exdates_property rrules = rrules_property uid = uid_property summary = summary_property description = description_property classification = class_property url = url_property organizer = organizer_property location = location_property priority = priority_property contacts = contacts_property status = status_property attendees = attendees_property images = images_property conferences = conferences_property
[docs] @classmethod def new( cls, /, attendees: list[vCalAddress] | None = None, categories: Sequence[str] = (), classification: CLASS | None = None, color: str | None = None, comments: list[str] | str | None = None, contacts: list[str] | str | None = None, conferences: list[Conference] | None = None, created: date | None = None, description: str | None = None, end: date | datetime | None = None, last_modified: date | None = None, location: str | None = None, organizer: vCalAddress | str | None = None, priority: int | None = None, sequence: int | None = None, stamp: date | None = None, start: date | datetime | None = None, status: STATUS | None = None, summary: str | None = None, uid: str | uuid.UUID | None = None, url: str | None = None, ): """Create a new TODO with all required properties. This creates a new Todo in accordance with :rfc:`5545`. Arguments: attendees: The :attr:`attendees` of the todo. categories: The :attr:`categories` of the todo. classification: The :attr:`classification` of the todo. color: The :attr:`color` of the todo. comments: The :attr:`Component.comments` of the todo. conferences: The :attr:`conferences` of the todo. created: The :attr:`Component.created` of the todo. description: The :attr:`description` of the todo. end: The :attr:`end` of the todo. last_modified: The :attr:`Component.last_modified` of the todo. location: The :attr:`location` of the todo. organizer: The :attr:`organizer` of the todo. sequence: The :attr:`sequence` of the todo. stamp: The :attr:`Component.DTSTAMP` of the todo. If None, this is set to the current time. start: The :attr:`start` of the todo. status: The :attr:`status` of the todo. summary: The :attr:`summary` of the todo. uid: The :attr:`uid` of the todo. If None, this is set to a new :func:`uuid.uuid4`. url: The :attr:`url` of the todo. Returns: :class:`Todo` Raises: InvalidCalendar: If the content is not valid according to :rfc:`5545`. .. warning:: As time progresses, we will be stricter with the validation. """ todo = super().new( stamp=stamp if stamp is not None else cls._utc_now(), created=created, last_modified=last_modified, comments=comments, ) todo.summary = summary todo.description = description todo.uid = uid if uid is not None else uuid.uuid4() todo.start = start todo.end = end todo.color = color todo.categories = categories todo.sequence = sequence todo.classification = classification todo.url = url todo.organizer = organizer todo.location = location todo.priority = priority todo.contacts = contacts todo.status = status todo.attendees = attendees todo.conferences = conferences if cls._validate_new: cls._validate_start_and_end(start, end) return todo
__all__ = ["Todo"]