"""This implementes the VAVAILABILITY component.
This is specified in :rfc:`7953`.
"""
from __future__ import annotations
import uuid
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Sequence
from icalendar.attr import (
busy_type_property,
categories_property,
class_property,
contacts_property,
description_property,
duration_property,
location_property,
organizer_property,
priority_property,
rfc_7953_dtend_property,
rfc_7953_dtstart_property,
rfc_7953_duration_property,
rfc_7953_end_property,
sequence_property,
summary_property,
url_property,
)
from icalendar.cal.examples import get_example
from icalendar.error import InvalidCalendar
from .component import Component
if TYPE_CHECKING:
from datetime import date
from icalendar.cal.available import Available
from icalendar.enums import BUSYTYPE, CLASS
from icalendar.prop import vCalAddress
[docs]
class Availability(Component):
"""VAVAILABILITY component from :rfc:`7953`.
This provides a grouping of component properties and
subcomponents that describe the availability associated with a
calendar user.
Description:
A "VAVAILABILITY" component indicates a period of time
within which availability information is provided. A
"VAVAILABILITY" component can specify a start time and an end time
or duration. If "DTSTART" is not present, then the start time is
unbounded. If "DTEND" or "DURATION" are not present, then the end
time is unbounded. Within the specified time period, availability
defaults to a free-busy type of "BUSY-UNAVAILABLE" (see
Section 3.2), except for any time periods corresponding to
"AVAILABLE" subcomponents.
"AVAILABLE" subcomponents are used to indicate periods of free
time within the time range of the enclosing "VAVAILABILITY"
component. "AVAILABLE" subcomponents MAY include recurrence
properties to specify recurring periods of time, which can be
overridden using normal iCalendar recurrence behavior (i.e., use
of the "RECURRENCE-ID" property).
If specified, the "DTSTART" and "DTEND" properties in
"VAVAILABILITY" components and "AVAILABLE" subcomponents MUST be
"DATE-TIME" values specified as either the date with UTC time or
the date with local time and a time zone reference.
The iCalendar object containing the "VAVAILABILITY" component MUST
contain appropriate "VTIMEZONE" components corresponding to each
unique "TZID" parameter value used in any DATE-TIME properties in
all components, unless [RFC7809] is in effect.
When used to publish available time, the "ORGANIZER" property
specifies the calendar user associated with the published
available time.
If the "PRIORITY" property is specified in "VAVAILABILITY"
components, it is used to determine how that component is combined
with other "VAVAILABILITY" components. See Section 4.
Other calendar properties MAY be specified in "VAVAILABILITY" or
"AVAILABLE" components and are considered attributes of the marked
block of time. Their usage is application specific. For example,
the "LOCATION" property might be used to indicate that a person is
available in one location for part of the week and a different
location for another part of the week (but see Section 9 for when
it is appropriate to add additional data like this).
Example:
The following is an example of a "VAVAILABILITY" calendar
component used to represent the availability of a user, always
available Monday through Friday, 9:00 am to 5:00 pm in the
America/Montreal time zone:
.. code-block:: text
BEGIN:VAVAILABILITY
ORGANIZER:mailto:bernard@example.com
UID:0428C7D2-688E-4D2E-AC52-CD112E2469DF
DTSTAMP:20111005T133225Z
BEGIN:AVAILABLE
UID:34EDA59B-6BB1-4E94-A66C-64999089C0AF
SUMMARY:Monday to Friday from 9:00 to 17:00
DTSTART;TZID=America/Montreal:20111002T090000
DTEND;TZID=America/Montreal:20111002T170000
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
END:AVAILABLE
END:VAVAILABILITY
You can get the same example from :meth:`example`:
.. code-block: pycon
>>> from icalendar import Availability
>>> a = Availability.example()
>>> a.organizer
vCalAddress('mailto:bernard@example.com')
The following is an example of a "VAVAILABILITY" calendar
component used to represent the availability of a user available
Monday through Thursday, 9:00 am to 5:00 pm, at the main office,
and Friday, 9:00 am to 12:00 pm, in the branch office in the
America/Montreal time zone between October 2nd and December 2nd
2011:
.. code-block:: text
BEGIN:VAVAILABILITY
ORGANIZER:mailto:bernard@example.com
UID:84D0F948-7FC6-4C1D-BBF3-BA9827B424B5
DTSTAMP:20111005T133225Z
DTSTART;TZID=America/Montreal:20111002T000000
DTEND;TZID=America/Montreal:20111202T000000
BEGIN:AVAILABLE
UID:7B33093A-7F98-4EED-B381-A5652530F04D
SUMMARY:Monday to Thursday from 9:00 to 17:00
DTSTART;TZID=America/Montreal:20111002T090000
DTEND;TZID=America/Montreal:20111002T170000
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH
LOCATION:Main Office
END:AVAILABLE
BEGIN:AVAILABLE
UID:DF39DC9E-D8C3-492F-9101-0434E8FC1896
SUMMARY:Friday from 9:00 to 12:00
DTSTART;TZID=America/Montreal:20111006T090000
DTEND;TZID=America/Montreal:20111006T120000
RRULE:FREQ=WEEKLY
LOCATION:Branch Office
END:AVAILABLE
END:VAVAILABILITY
For more examples, have a look at :rfc:`5545`.
"""
name = "VAVAILABILITY"
canonical_order = (
"DTSTART",
"DTEND",
"DURATION",
"DTSTAMP",
"UID",
"SEQUENCE",
"SUMMARY",
"DESCRIPTION",
"ORGANIZER",
)
required = (
"DTSTART",
"DTSTAMP",
"UID",
)
singletons = (
"DTSTAMP",
"UID",
"BUSYTYPE",
"CLASS",
"CREATED",
"DESCRIPTION",
"DTSTART",
"LAST-MODIFIED",
"LOCATION",
"ORGANIZER",
"PRIORITY",
"SEQUENCE",
"SUMMARY",
"URL",
"DTEND",
"DURATION",
)
exclusive = (
"DTEND",
"DURATION",
)
organizer = organizer_property
busy_type = busy_type_property
summary = summary_property
description = description_property
sequence = sequence_property
classification = class_property
url = url_property
location = location_property
categories = categories_property
priority = priority_property
contacts = contacts_property
start = DTSTART = rfc_7953_dtstart_property
DTEND = rfc_7953_dtend_property
DURATION = duration_property("Availability")
duration = rfc_7953_duration_property
end = rfc_7953_end_property
@property
def available(self) -> list[Available]:
"""All VAVAILABLE sub-components.
This is a shortcut to get all VAVAILABLE sub-components.
Modifications do not change the calendar.
Use :py:meth:`Component.add_component`.
"""
return self.walk("VAVAILABLE")
[docs]
@classmethod
def new(
cls,
/,
busy_type: Optional[BUSYTYPE] = None,
categories: Sequence[str] = (),
comments: list[str] | str | None = None,
components: Sequence[Available] | None = (),
contacts: list[str] | str | None = None,
created: Optional[date] = None,
classification: Optional[CLASS] = None,
description: Optional[str] = None,
end: Optional[datetime] = None,
last_modified: Optional[date] = None,
location: Optional[str] = None,
organizer: Optional[vCalAddress | str] = None,
priority: Optional[int] = None,
sequence: Optional[int] = None,
stamp: Optional[date] = None,
start: Optional[datetime] = None,
summary: Optional[str] = None,
uid: Optional[str | uuid.UUID] = None,
url: Optional[str] = None,
):
"""Create a new event with all required properties.
This creates a new Availability in accordance with :rfc:`7953`.
Arguments:
busy_type: The :attr:`busy_type` of the availability.
categories: The :attr:`categories` of the availability.
classification: The :attr:`classification` of the availability.
comments: The :attr:`Component.comments` of the availability.
contacts: The :attr:`contacts` of the availability.
created: The :attr:`Component.created` of the availability.
description: The :attr:`description` of the availability.
last_modified: The :attr:`Component.last_modified` of the availability.
location: The :attr:`location` of the availability.
organizer: The :attr:`organizer` of the availability.
sequence: The :attr:`sequence` of the availability.
stamp: The :attr:`Component.stamp` of the availability.
If None, this is set to the current time.
summary: The :attr:`summary` of the availability.
uid: The :attr:`uid` of the availability.
If None, this is set to a new :func:`uuid.uuid4`.
url: The :attr:`url` of the availability.
Returns:
:class:`Availability`
Raises:
InvalidCalendar: If the content is not valid according to :rfc:`7953`.
.. warning:: As time progresses, we will be stricter with the validation.
"""
availability = super().new(
stamp=stamp if stamp is not None else cls._utc_now(),
created=created,
last_modified=last_modified,
)
availability.summary = summary
availability.description = description
availability.uid = uid if uid is not None else uuid.uuid4()
availability.sequence = sequence
availability.categories = categories
availability.classification = classification
availability.url = url
availability.busy_type = busy_type
availability.organizer = organizer
availability.location = location
availability.comments = comments
availability.priority = priority
availability.contacts = contacts
for subcomponent in components:
availability.add_component(subcomponent)
if cls._validate_new:
if start is not None and (
not isinstance(start, datetime) or start.tzinfo is None
):
raise InvalidCalendar(
"Availability start must be a datetime with a timezone"
)
if end is not None and (
not isinstance(end, datetime) or end.tzinfo is None
):
raise InvalidCalendar(
"Availability end must be a datetime with a timezone"
)
availability._validate_start_and_end(start, end) # noqa: SLF001
availability.start = start
availability.end = end
return availability
[docs]
@classmethod
def example(cls, name: str = "rfc_7953_1") -> Availability:
"""Return the calendar example with the given name."""
return cls.from_ical(get_example("availabilities", name))
__all__ = ["Availability"]