Repository: collective/icalendar
Pull request #810: Add parameters to properties
Author: login: niccokunzmann
Created: 2025-04-24T05:56:28Z | Merged: 2025-05-03T10:21:26Z

This adds parameters to the properties and contributes to #798.

Steps:

- [x] merge
- [x] https://github.com/collective/icalendar/pull/809#issuecomment-2841752480 check docs for consistency
- [x] ask for review


<!-- readthedocs-preview icalendar start -->
----
📚 Documentation preview 📚: https://icalendar--810.org.readthedocs.build/

<!-- readthedocs-preview icalendar end -->


Diff:
diff --git a/CHANGES.rst b/CHANGES.rst
index 328db4f20..fe4d6b330 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,6 +8,8 @@ Minor changes:
 
 - Use ``ruff`` to format the source code.
 - Update project metadata to use License-Expression.
+- Use ``tzp.localize(dt, None)`` to remove the timezone from a datetime.
+- Remove the HTML documentation when building with ``tox`` to force rebuild.
 - Switch to PyData Sphinx Theme for documentation. See `Issue 803 <https://github.com/collective/icalendar/issues/804>`_.
 
 Breaking changes:
@@ -22,11 +24,14 @@ New features:
 - Add ``sequence`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 802 <https://github.com/collective/icalendar/issues/802>`_.
 - Add ``categories`` attribute to ``Calendar``, ``Event``, ``Todo``, and ``Journal`` components. See `Issue 655 <https://github.com/collective/icalendar/issues/655>`_.
 - Add compatibility to :rfc:`6868`. See `Issue 652 <https://github.com/collective/icalendar/issues/652>`_.
+- Add ``freebusy`` property to the ``Calendar`` to get this type of subcomponents easier.
+- Add parameters from :rfc:`5545` to properties ``ALTREP``, ``CN``, ``CUTYPE``, ``DELEGATED_FROM``, ``DELEGATED_TO``, ``DIR``, ``FBTYPE``, ``LANGUAGE``, ``MEMBER``, ``PARTSTAT``, ``RANGE``, ``RELATED``, ``ROLE``, ``RSVP``, ``SENT_BY``, ``TZID``, and ``RELTYPE``. See `Issue 798 <https://github.com/collective/icalendar/issues/798>`_.
 - New properties from :rfc:`7986` can occur multiple times in ``VCALENDAR``. See `PR 808`_.
 
 Bug fixes:
 
 - Move import at the end of ``icalendar.parser`` into a function to mitigate import errors, see `Issue 781 <https://github.com/collective/icalendar/issues/781>`_.
+- ``ALTREP``, ``DELEGATED-FROM``, ``DELEGATED-TO``, ``DIR``, ``MEMBER``, and ``SENT-BY`` require double quotes. These are now always added.
 - Classify ``CATEGORIES`` as multiple in ``VEVENT``. See `PR 808 <https://github.com/collective/icalendar/pull/808>`_.
 
 6.1.3 (2025-03-19)
diff --git a/docs/api.rst b/docs/api.rst
index 7ef3cfa78..86790d7dc 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -1,13 +1,18 @@
 API Reference
 -------------
 
+icalendar.attr
+++++++++++++++
+
+.. automodule:: icalendar.attr
+   :members:
+
 icalendar.alarms
 ++++++++++++++++
 
 .. automodule:: icalendar.alarms
    :members:
 
-
 icalendar.cal
 +++++++++++++
 
@@ -26,12 +31,24 @@ icalendar.cli
 .. automodule:: icalendar.cli
    :members:
 
+icalendar.enums
++++++++++++++++
+
+.. automodule:: icalendar.enums
+   :members:
+
 icalendar.error
 +++++++++++++++
 
 .. automodule:: icalendar.error
    :members:
 
+icalendar.param
++++++++++++++++
+
+.. automodule:: icalendar.param
+   :members:
+
 icalendar.parser
 ++++++++++++++++
 
diff --git a/docs/conf.py b/docs/conf.py
index 92930b543..351f842d9 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -10,6 +10,7 @@
     "sphinx_copybutton",
     "sphinx.ext.intersphinx",
     "sphinx.ext.autosectionlabel",
+    "sphinx.ext.napoleon",
 ]
 source_suffix = ".rst"
 master_doc = "index"
diff --git a/src/icalendar/__init__.py b/src/icalendar/__init__.py
index 24a891896..2cdecf30e 100644
--- a/src/icalendar/__init__.py
+++ b/src/icalendar/__init__.py
@@ -15,6 +15,7 @@
     TimezoneStandard,
     Todo,
 )
+from icalendar.enums import CUTYPE, FBTYPE, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE
 from icalendar.error import (
     ComponentEndMissing,
     ComponentStartMissing,
@@ -111,5 +112,13 @@
     "ComponentStartMissing",
     "IncompleteAlarmInformation",
     "LocalTimezoneMissing",
+    "CUTYPE",
+    "FBTYPE",
+    "PARTSTAT",
+    "RANGE",
     "vSkip",
+    "RELATED",
+    "vSkip",
+    "RELTYPE",
+    "ROLE",
 ]
diff --git a/src/icalendar/attr.py b/src/icalendar/attr.py
index e91fbd43b..d5ff17139 100644
--- a/src/icalendar/attr.py
+++ b/src/icalendar/attr.py
@@ -1,8 +1,8 @@
 """Attributes of Components and properties."""
 from __future__ import annotations
 
-from datetime import date, datetime, timedelta
 import itertools
+from datetime import date, datetime, timedelta
 from typing import TYPE_CHECKING, Optional, Union
 
 from icalendar.error import InvalidCalendar
@@ -313,9 +313,9 @@ def multi_language_text_property(main_prop:str, compatibility_prop:str, doc:str)
     This property can be defined several times with different ``LANGUAGE`` parameters.
 
     Args:
-        main_prop: The property to set and get, such as ``NAME``
-        compatibility_prop: An old property used before, such as ``X-WR-CALNAME``
-        doc: The documentation string
+        main_prop (str): The property to set and get, such as ``NAME``
+        compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME``
+        doc (str): The documentation string
     """
     def fget(self: Component) -> Optional[str]:
         """Get the property"""
@@ -369,8 +369,9 @@ def fdel(self: Component):
 def single_utc_property(name: str, docs: str) -> property:
     """Create a property to access a value of datetime in UTC timezone.
 
-    name - name of the property
-    docs - documentation string
+    Args:
+        name: name of the property
+        docs: documentation string
     """
     docs = (
         f"""The {name} property. datetime in UTC
diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py
index cda1a84fb..fede4cb01 100644
--- a/src/icalendar/cal.py
+++ b/src/icalendar/cal.py
@@ -1956,6 +1956,16 @@ def todos(self) -> list[Todo]:
         """
         return self.walk("VTODO")
 
+    @property
+    def freebusy(self) -> list[FreeBusy]:
+        """All FreeBusy components in the calendar.
+
+        This is a shortcut to get all FreeBusy.
+        Modifications do not change the calendar.
+        Use :py:meth:`Component.add_component`.
+        """
+        return self.walk("VFREEBUSY")
+
     def get_used_tzids(self) -> set[str]:
         """The set of TZIDs in use.
 
diff --git a/src/icalendar/enums.py b/src/icalendar/enums.py
new file mode 100644
index 000000000..c4f0aa719
--- /dev/null
+++ b/src/icalendar/enums.py
@@ -0,0 +1,129 @@
+"""Enumerations for different types in the RFCs."""
+from enum import Enum as _Enum
+
+
+class Enum(_Enum):
+    """Enum class that can be pickled."""
+
+    def __reduce_ex__(self, _p):
+        """For pickling."""
+        return self.__class__, (self._name_,)
+
+
+class StrEnum(str, Enum):
+    """Enum for strings."""
+
+
+class PARTSTAT(StrEnum):
+    """Enum for PARTSTAT from :rfc:`5545`.
+
+    Attributes:
+        ``NEEDS_ACTION``,
+        ``ACCEPTED``,
+        ``DECLINED``,
+        ``TENTATIVE``,
+        ``DELEGATED``,
+        ``COMPLETED``,
+        ``IN_PROCESS``
+    """
+    NEEDS_ACTION = "NEEDS-ACTION"
+    ACCEPTED = "ACCEPTED"
+    DECLINED = "DECLINED"
+    TENTATIVE = "TENTATIVE"
+    DELEGATED = "DELEGATED"
+    COMPLETED = "COMPLETED"
+    IN_PROCESS = "IN-PROCESS"
+
+
+class FBTYPE(StrEnum):
+    """Enum for FBTYPE from :rfc:`5545`.
+
+    Attributes:
+        ``FREE``,
+        ``BUSY``,
+        ``BUSY-UNAVAILABLE``,
+        ``BUSY-TENTATIVE``
+    """
+    FREE = "FREE"
+    BUSY = "BUSY"
+    BUSY_UNAVAILABLE = "BUSY-UNAVAILABLE"
+    BUSY_TENTATIVE = "BUSY-TENTATIVE"
+
+
+class CUTYPE(StrEnum):
+    """Enum for CTYPE from :rfc:`5545`.
+
+    Attributes:
+        ``INDIVIDUAL``,
+        ``GROUP``,
+        ``RESOURCE``,
+        ``ROOM``,
+        ``UNKNOWN``
+    """
+    INDIVIDUAL = "INDIVIDUAL"
+    GROUP = "GROUP"
+    RESOURCE = "RESOURCE"
+    ROOM = "ROOM"
+    UNKNOWN = "UNKNOWN"
+
+
+class RELTYPE(StrEnum):
+    """Enum for RELTYPE from :rfc:`5545`.
+
+    Attributes:
+        ``PARENT``,
+        ``CHILD``,
+        ``SIBLING``
+    """
+    PARENT = "PARENT"
+    CHILD = "CHILD"
+    SIBLING = "SIBLING"
+
+
+class RANGE(StrEnum):
+    """Enum for RANGE from :rfc:`5545`.
+
+    Attributes:
+        ``THISANDFUTURE``,
+        ``THISANDPRIOR``
+    """
+
+    THISANDFUTURE = "THISANDFUTURE"
+    THISANDPRIOR = "THISANDPRIOR"  # deprecated
+
+
+class RELATED(StrEnum):
+    """Enum for RELATED from :rfc:`5545`.
+
+    Attributes:
+        ``START``,
+        ``END``
+    """
+    START = "START"
+    END = "END"
+
+
+class ROLE(StrEnum):
+    """Enum for ROLE from :rfc:`5545`.
+
+    Attributes:
+        ``CHAIR``,
+        ``REQ-PARTICIPANT``,
+        ``OPT-PARTICIPANT``,
+        ``NON-PARTICIPANT``
+    """
+    CHAIR = "CHAIR"
+    REQ_PARTICIPANT = "REQ-PARTICIPANT"
+    OPT_PARTICIPANT = "OPT-PARTICIPANT"
+    NON_PARTICIPANT = "NON-PARTICIPANT"
+
+
+__all__ = [
+    "PARTSTAT",
+    "FBTYPE",
+    "CUTYPE",
+    "RANGE",
+    "RELATED",
+    "ROLE",
+    "RELTYPE",
+]
diff --git a/src/icalendar/param.py b/src/icalendar/param.py
new file mode 100644
index 000000000..2a79f2e1d
--- /dev/null
+++ b/src/icalendar/param.py
@@ -0,0 +1,454 @@
+"""Parameter access for icalendar.
+
+Related:
+
+- :rfc:`5545`, Section 3.2. Property Parameters
+- :rfc:`7986`, Section 6. Property Parameters
+- https://github.com/collective/icalendar/issues/798
+"""
+from __future__ import annotations
+
+import functools
+from typing import TYPE_CHECKING, Callable, Optional, TypeVar, Union
+
+from icalendar import enums
+
+if TYPE_CHECKING:
+    from enum import Enum
+
+    from icalendar.parser import Parameters
+
+
+class IcalendarProperty:
+    """Interface provided by properties in icalendar.prop."""
+    params: Parameters
+
+
+def _default_return_none() -> Optional[str]:
+    """Return None by default."""
+    return None
+
+
+def _default_return_string() -> str:
+    """Return None by default."""
+    return ""
+
+T = TypeVar("T")
+
+def string_parameter(
+        name:str,
+        doc:str,
+        default:Callable = _default_return_none,
+        convert:Optional[Callable[[str], T]] = None,
+        convert_to:Optional[Callable[[T], str]] = None
+    ) -> property:
+    """Return a parameter with a quoted value (case sensitive)."""
+
+    if convert_to is None:
+        convert_to = convert
+
+    @functools.wraps(default)
+    def fget(self: IcalendarProperty) -> Optional[str]:
+        value = self.params.get(name)
+        if value is None:
+            return default()
+        return convert(value) if convert else value
+
+    def fset(self: IcalendarProperty, value: str):
+        self.params[name] = convert_to(value) if convert_to else value
+
+    def fdel(self: IcalendarProperty):
+        self.params.pop(name, None)
+
+    return property(fget, fset, fdel, doc=doc)
+
+
+ALTREP = string_parameter(
+    "ALTREP",
+    """ALTREP - Specify an alternate text representation for the property value.
+
+Description:
+    This parameter specifies a URI that points to an
+    alternate representation for a textual property value.  A property
+    specifying this parameter MUST also include a value that reflects
+    the default representation of the text value.  The URI parameter
+    value MUST be specified in a quoted-string.
+
+.. note::
+
+    While there is no restriction imposed on the URI schemes
+    allowed for this parameter, Content Identifier (CID) :rfc:`2392`,
+    HTTP :rfc:`2616`, and HTTPS :rfc:`2818` are the URI schemes most
+    commonly used by current implementations.
+""")
+
+CN = string_parameter(
+    "CN",
+    """Specify the common name to be associated with the calendar user specified.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter specifies the common name
+    to be associated with the calendar user specified by the property.
+    The parameter value is text.  The parameter value can be used for
+    display text to be associated with the calendar address specified
+    by the property.
+""",
+    default=_default_return_string
+)
+
+def _default_return_individual() -> enums.CUTYPE|str:
+    """Default value."""
+    return enums.CUTYPE.INDIVIDUAL
+
+def _convert_enum(enum: type[Enum]) -> Callable[[str], Enum]:
+
+    def convert(value: str) -> str:
+        """Convert if possible."""
+        try:
+            return enum(value.upper())
+        except ValueError:
+            return value
+    return convert
+
+CUTYPE = string_parameter(
+    "CUTYPE",
+    """Identify the type of calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter identifies the type of
+    calendar user specified by the property.  If not specified on a
+    property that allows this parameter, the default is INDIVIDUAL.
+    Applications MUST treat x-name and iana-token values they don't
+    recognize the same way as they would the UNKNOWN value.
+""", default=_default_return_individual, convert=_convert_enum(enums.CUTYPE))
+
+
+def quoted_list_parameter(name: str, doc: str) -> property:
+    """Return a parameter that contains a quoted list."""
+
+    def fget(self: IcalendarProperty) -> tuple[str]:
+        value = self.params.get(name)
+        if value is None:
+            return ()
+        if isinstance(value, str):
+            return tuple(value.split(","))
+        return value
+
+    def fset(self: IcalendarProperty, value: str|tuple[str]):
+        if value == ():
+            fdel(self)
+        else:
+            self.params[name] = (value,) if isinstance(value, str) else value
+
+    def fdel(self: IcalendarProperty):
+        self.params.pop(name, None)
+
+    return property(fget, fset, fdel, doc=doc)
+
+
+DELEGATED_FROM = quoted_list_parameter(
+    "DELEGATED-FROM",
+    """Specify the calendar users that have delegated their participation to the calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  This parameter specifies those calendar
+    users that have delegated their participation in a group-scheduled
+    event or to-do to the calendar user specified by the property.
+    The individual calendar address parameter values MUST each be
+    specified in a quoted-string.
+""")
+
+DELEGATED_TO = quoted_list_parameter(
+    "DELEGATED-TO",
+    """Specify the calendar users to whom the calendar user specified by the property has delegated participation.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  This parameter specifies those calendar
+    users whom have been delegated participation in a group-scheduled
+    event or to-do by the calendar user specified by the property.
+    The individual calendar address parameter values MUST each be
+    specified in a quoted-string.
+    """)
+
+DIR = string_parameter(
+    "DIR",
+    """Specify reference to a directory entry associated with the calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter specifies a reference to
+    the directory entry associated with the calendar user specified by
+    the property.  The parameter value is a URI.  The URI parameter
+    value MUST be specified in a quoted-string.
+
+.. note::
+
+    While there is no restriction imposed on the URI schemes
+    allowed for this parameter, CID :rfc:`2392`, DATA :rfc:`2397`, FILE
+    :rfc:`1738`, FTP :rfc:`1738`, HTTP :rfc:`2616`, HTTPS :rfc:`2818`, LDAP
+    :rfc:`4516`, and MID :rfc:`2392` are the URI schemes most commonly
+    used by current implementations.
+""")
+
+def _default_return_busy() -> enums.FBTYPE|str:
+    """Default value."""
+    return enums.FBTYPE.BUSY
+
+FBTYPE = string_parameter(
+    "FBTYPE",
+    """Specify the free or busy time type.
+
+Description:
+    This parameter specifies the free or busy time type.
+    The value FREE indicates that the time interval is free for
+    scheduling.  The value BUSY indicates that the time interval is
+    busy because one or more events have been scheduled for that
+    interval.  The value BUSY-UNAVAILABLE indicates that the time
+    interval is busy and that the interval can not be scheduled.  The
+    value BUSY-TENTATIVE indicates that the time interval is busy
+    because one or more events have been tentatively scheduled for
+    that interval.  If not specified on a property that allows this
+    parameter, the default is BUSY.  Applications MUST treat x-name
+    and iana-token values they don't recognize the same way as they
+    would the BUSY value.
+""", default=_default_return_busy, convert=_convert_enum(enums.FBTYPE))
+
+LANGUAGE = string_parameter(
+    "LANGUAGE",
+    """Specify the language for text values in a property or property parameter.
+
+Description:
+    This parameter identifies the language of the text in
+    the property value and of all property parameter values of the
+    property.  The value of the "LANGUAGE" property parameter is that
+    defined in :rfc:`5646`.
+
+    For transport in a MIME entity, the Content-Language header field
+    can be used to set the default language for the entire body part.
+    Otherwise, no default language is assumed.
+""")
+
+MEMBER = quoted_list_parameter(
+    "MEMBER",
+    """Specify the group or list membership of the calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter identifies the groups or
+    list membership for the calendar user specified by the property.
+    The parameter value is either a single calendar address in a
+    quoted-string or a COMMA-separated list of calendar addresses,
+    each in a quoted-string.  The individual calendar address
+    parameter values MUST each be specified in a quoted-string.
+"""
+)
+
+def _default_return_needs_action() -> enums.PARTSTAT|str:
+    """Default value."""
+    return enums.PARTSTAT.NEEDS_ACTION
+
+PARTSTAT = string_parameter(
+    "PARTSTAT",
+    """Specify the participation status for the calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter identifies the
+    participation status for the calendar user specified by the
+    property value.  The parameter values differ depending on whether
+    they are associated with a group-scheduled "VEVENT", "VTODO", or
+    "VJOURNAL".  The values MUST match one of the values allowed for
+    the given calendar component.  If not specified on a property that
+    allows this parameter, the default value is NEEDS-ACTION.
+    Applications MUST treat x-name and iana-token values they don't
+    recognize the same way as they would the NEEDS-ACTION value.
+""", default=_default_return_needs_action, convert=_convert_enum(enums.PARTSTAT))
+
+def _default_range_none() -> Optional[enums.RANGE|str]:
+    return None
+
+RANGE = string_parameter(
+    "RANGE",
+    """Specify the effective range of recurrence instances from the instance specified by the recurrence identifier specified by the property.
+
+Description:
+    This parameter can be specified on a property that
+    specifies a recurrence identifier.  The parameter specifies the
+    effective range of recurrence instances that is specified by the
+    property.  The effective range is from the recurrence identifier
+    specified by the property.  If this parameter is not specified on
+    an allowed property, then the default range is the single instance
+    specified by the recurrence identifier value of the property.  The
+    parameter value can only be "THISANDFUTURE" to indicate a range
+    defined by the recurrence identifier and all subsequent instances.
+    The value "THISANDPRIOR" is deprecated by this revision of
+    iCalendar and MUST NOT be generated by applications.
+""", default=_default_range_none, convert=_convert_enum(enums.RANGE))
+
+def _default_related() -> enums.RELATED|str:
+    return enums.RELATED.START
+
+RELATED = string_parameter(
+    "RELATED",
+    """Specify the relationship of the alarm trigger with respect to the start or end of the calendar component.
+
+Description:
+    This parameter can be specified on properties that
+    specify an alarm trigger with a "DURATION" value type.  The
+    parameter specifies whether the alarm will trigger relative to the
+    start or end of the calendar component.  The parameter value START
+    will set the alarm to trigger off the start of the calendar
+    component; the parameter value END will set the alarm to trigger
+    off the end of the calendar component.  If the parameter is not
+    specified on an allowable property, then the default is START.
+
+""", default=_default_related, convert=_convert_enum(enums.RANGE))
+
+
+def _default_req_participant() -> enums.ROLE|str:
+    return enums.ROLE.REQ_PARTICIPANT
+
+ROLE = string_parameter(
+    "ROLE",
+    """Specify the participation role for the calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter specifies the participation
+    role for the calendar user specified by the property in the group
+    schedule calendar component.  If not specified on a property that
+    allows this parameter, the default value is REQ-PARTICIPANT.
+    Applications MUST treat x-name and iana-token values they don't
+    recognize the same way as they would the REQ-PARTICIPANT value.
+""", default=_default_req_participant, convert=_convert_enum(enums.ROLE)
+)
+
+def boolean_parameter(name: str, default:bool, doc: str) -> property:
+
+    def _default() -> bool:
+        return default
+
+    return string_parameter(
+        name,
+        doc,
+        default=_default,
+        convert=lambda x: x.upper() == "TRUE",
+        convert_to=lambda x: "TRUE" if x else "FALSE",
+    )
+
+RSVP = boolean_parameter(
+    "RSVP",
+    False,
+    """Specify whether there is an expectation of a favor of anreply from the calendar user specified by the property value.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter identifies the expectation
+    of a reply from the calendar user specified by the property value.
+    This parameter is used by the "Organizer" to request a
+    participation status reply from an "Attendee" of a group-scheduled
+    event or to-do.  If not specified on a property that allows this
+    parameter, the default value is ``False``.
+"""
+)
+
+SENT_BY = string_parameter(
+    "SENT-BY",
+    """Specify the calendar user that is acting on behalf of the calendar user specified by the property.
+
+Description:
+    This parameter can be specified on properties with a
+    CAL-ADDRESS value type.  The parameter specifies the calendar user
+    that is acting on behalf of the calendar user specified by the
+    property.  The parameter value MUST be a mailto URI as defined in
+    :rfc:`2368`.  The individual calendar address parameter values MUST
+    each be specified in a quoted-string.
+"""
+)
+
+TZID = string_parameter(
+    "TZID",
+    """Specify the identifier for the time zone definition for a time component in the property value.
+
+Description:
+    This parameter MUST be specified on the "DTSTART",
+    "DTEND", "DUE", "EXDATE", and "RDATE" properties when either a
+    DATE-TIME or TIME value type is specified and when the value is
+    neither a UTC or a "floating" time.  Refer to the DATE-TIME or
+    TIME value type definition for a description of UTC and "floating
+    time" formats.  This property parameter specifies a text value
+    that uniquely identifies the "VTIMEZONE" calendar component to be
+    used when evaluating the time portion of the property.  The value
+    of the "TZID" property parameter will be equal to the value of the
+    "TZID" property for the matching time zone definition.  An
+    individual "VTIMEZONE" calendar component MUST be specified for
+    each unique "TZID" parameter value specified in the iCalendar
+    object.
+
+    The parameter MUST be specified on properties with a DATE-TIME
+    value if the DATE-TIME is not either a UTC or a "floating" time.
+    Failure to include and follow VTIMEZONE definitions in iCalendar
+    objects may lead to inconsistent understanding of the local time
+    at any given location.
+
+    The presence of the SOLIDUS character as a prefix, indicates that
+    this "TZID" represents a unique ID in a globally defined time zone
+    registry (when such registry is defined).
+
+.. note::
+
+    This document does not define a naming convention for
+    time zone identifiers.  Implementers may want to use the naming
+    conventions defined in existing time zone specifications such
+    as the public-domain TZ database (TZDB). The specification of
+    globally unique time zone identifiers is not addressed by this
+    document and is left for future study.
+"""
+)
+
+def _default_return_parent() -> enums.RELTYPE:
+    return enums.RELTYPE.PARENT
+
+RELTYPE = string_parameter(
+    "RELTYPE",
+    """Specify the type of hierarchical relationship associated with the calendar component specified by the property.
+
+Description:
+    This parameter can be specified on a property that
+    references another related calendar.  The parameter specifies the
+    hierarchical relationship type of the calendar component
+    referenced by the property.  The parameter value can be PARENT, to
+    indicate that the referenced calendar component is a superior of
+    calendar component; CHILD to indicate that the referenced calendar
+    component is a subordinate of the calendar component; or SIBLING
+    to indicate that the referenced calendar component is a peer of
+    the calendar component.  If this parameter is not specified on an
+    allowable property, the default relationship type is PARENT.
+    Applications MUST treat x-name and iana-token values they don't
+    recognize the same way as they would the PARENT value.
+""", default=_default_return_parent, convert=_convert_enum(enums.RELTYPE))
+
+__all__ = [
+    "string_parameter",
+    "quoted_list_parameter",
+    "ALTREP",
+    "CN",
+    "CUTYPE",
+    "DELEGATED_FROM",
+    "DELEGATED_TO",
+    "DIR",
+    "FBTYPE",
+    "LANGUAGE",
+    "MEMBER",
+    "PARTSTAT",
+    "RANGE",
+    "RELATED",
+    "ROLE",
+    "RSVP",
+    "SENT_BY",
+    "TZID",
+]
diff --git a/src/icalendar/parser.py b/src/icalendar/parser.py
index e08e19e40..5262ab15f 100644
--- a/src/icalendar/parser.py
+++ b/src/icalendar/parser.py
@@ -5,6 +5,7 @@
 It is stupid in the sense that it treats the content purely as strings. No type
 conversion is attempted.
 """
+from __future__ import annotations
 
 import os
 from icalendar.caselessdict import CaselessDict
@@ -91,12 +92,12 @@ def foldline(line, limit=75, fold_sep="\r\n "):
 # Property parameter stuff
 
 
-def param_value(value):
+def param_value(value, always_quote=False):
     """Returns a parameter value."""
     if isinstance(value, SEQUENCE_TYPES):
-        return q_join(value)
+        return q_join(map(rfc_6868_escape, value), always_quote=always_quote)
     if isinstance(value, str):
-        return dquote(rfc_6868_escape(value))
+        return dquote(rfc_6868_escape(value), always_quote=always_quote)
     return dquote(rfc_6868_escape(value.to_ical().decode(DEFAULT_ENCODING)))
 
 
@@ -131,12 +132,12 @@ def validate_param_value(value, quoted=True):
 QUOTABLE = re.compile("[,;: ’']")
 
 
-def dquote(val):
+def dquote(val, always_quote=False):
     """Enclose parameter values containing [,;:] in double quotes."""
     # a double-quote character is forbidden to appear in a parameter value
     # so replace it with a single-quote character
     val = val.replace('"', "'")
-    if QUOTABLE.search(val):
+    if QUOTABLE.search(val) or always_quote:
         return f'"{val}"'
     return val
 
@@ -165,15 +166,25 @@ def q_split(st, sep=",", maxsplit=-1):
     return result
 
 
-def q_join(lst, sep=","):
+def q_join(lst, sep=",", always_quote=False):
     """Joins a list on sep, quoting strings with QUOTABLE chars."""
-    return sep.join(dquote(itm) for itm in lst)
+    return sep.join(dquote(itm, always_quote=always_quote) for itm in lst)
 
 
 class Parameters(CaselessDict):
     """Parser and generator of Property parameter strings. It knows nothing of
     datatypes. Its main concern is textual structure.
     """
+    
+    # The following paremeters must always be enclosed in double quotes
+    always_quoted = (
+        "ALTREP",
+        "DELEGATED-FROM",
+        "DELEGATED-TO",
+        "DIR",
+        "MEMBER",
+        "SENT-BY",
+    )
 
     def params(self):
         """In RFC 5545 keys are called parameters, so this is to be consitent
@@ -207,12 +218,12 @@ def to_ical(self, sorted=True):
             items.sort()
 
         for key, value in items:
-            value = param_value(value)
-            if isinstance(value, str):
-                value = value.encode(DEFAULT_ENCODING)
+            upper_key = key.upper()
+            quoted_value = param_value(value, always_quote=upper_key in self.always_quoted)
+            if isinstance(quoted_value, str):
+                quoted_value = quoted_value.encode(DEFAULT_ENCODING)
             # CaselessDict keys are always unicode
-            key = key.upper().encode(DEFAULT_ENCODING)
-            result.append(key + b"=" + value)
+            result.append(upper_key.encode(DEFAULT_ENCODING) + b"=" + quoted_value)
         return b";".join(result)
 
     @classmethod
diff --git a/src/icalendar/prop.py b/src/icalendar/prop.py
index f716fa955..aa4a33a62 100644
--- a/src/icalendar/prop.py
+++ b/src/icalendar/prop.py
@@ -48,10 +48,10 @@
 import binascii
 import re
 from datetime import date, datetime, time, timedelta
-from enum import Enum
 from typing import Union
 
 from icalendar.caselessdict import CaselessDict
+from icalendar.enums import Enum
 from icalendar.parser import Parameters, escape_char, unescape_char
 from icalendar.parser_tools import (
     DEFAULT_ENCODING,
@@ -181,6 +181,8 @@ def from_ical(cls, ical: ICAL_TYPE):
         ical_unesc = unescape_char(ical)
         return cls(ical_unesc)
 
+    from icalendar.param import ALTREP, LANGUAGE, RELTYPE
+
 
 class vCalAddress(str):
     """Calendar User Address
@@ -192,13 +194,6 @@ class vCalAddress(str):
         This value type is used to identify properties that contain a
         calendar user address.
 
-    Format Definition:
-        This value type is defined by the following notation:
-
-    .. code-block:: text
-
-        cal-address        = uri
-
     Description:
         The value is a URI as defined by [RFC3986] or any other
         IANA-registered form for a URI.  When used to address an Internet
@@ -206,19 +201,34 @@ class vCalAddress(str):
         mailto URI, as defined by [RFC2368].
 
     Example:
+        ``mailto:`` is in front of the address.
 
-    .. code-block:: text
+        .. code-block:: text
 
-        mailto:jane_doe@example.com
+            mailto:jane_doe@example.com
 
-    .. code-block:: pycon
+        Parsing:
+
+        .. code-block:: pycon
 
-        >>> from icalendar.prop import vCalAddress
-        >>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com')
-        >>> cal_address
-        vCalAddress('mailto:jane_doe@example.com')
+            >>> from icalendar import vCalAddress
+            >>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com')
+            >>> cal_address
+            vCalAddress('mailto:jane_doe@example.com')
 
+        Encoding:
 
+        .. code-block:: pycon
+
+            >>> from icalendar import vCalAddress, Event
+            >>> event = Event()
+            >>> jane = vCalAddress("mailto:jane_doe@example.com")
+            >>> jane.name = "Jane"
+            >>> event["organizer"] = jane
+            >>> print(event.to_ical())
+            BEGIN:VEVENT
+            ORGANIZER;CN=Jane:mailto:jane_doe@example.com
+            END:VEVENT
     """
 
     params: Parameters
@@ -246,15 +256,19 @@ def email(self) -> str:
             return self[7:]
         return str(self)
 
-    @property
-    def name(self) -> str:
-        """The CN parameter or an empty string."""
-        return self.params.get("CN", "")
-
-    @name.setter
-    def name(self, value: str):
-        self.params["CN"] = value
-
+    from icalendar.param import (
+        CN,
+        CUTYPE,
+        DELEGATED_FROM,
+        DELEGATED_TO,
+        DIR,
+        LANGUAGE,
+        PARTSTAT,
+        ROLE,
+        RSVP,
+        SENT_BY,
+    )
+    name = CN
 
 class vFloat(float):
     """Float
@@ -468,6 +482,8 @@ def __eq__(self, other):
     def __hash__(self):
         return hash(self.dt)
 
+    from icalendar.param import RANGE, RELATED, TZID
+
 
 class vDDDTypes(TimeBase):
     """A combined Datetime, Date or Duration parser/generator. Their format
@@ -535,7 +551,6 @@ def __repr__(self):
         """repr(self)"""
         return f"{self.__class__.__name__}({self.dt}, {self.params})"
 
-
 class vDate(TimeBase):
     """Date
 
@@ -963,6 +978,7 @@ def dt(self):
         """Make this cooperate with the other vDDDTypes."""
         return (self.start, (self.duration if self.by_duration else self.end))
 
+    from icalendar.param import FBTYPE
 
 class vWeekday(str):
     """Either a ``weekday`` or a ``weekdaynum``
@@ -1177,9 +1193,7 @@ class vSkip(vText, Enum):
     FORWARD = "FORWARD"
     BACKWARD = "BACKWARD"
 
-    def __reduce_ex__(self, _p):
-        """For pickling."""
-        return self.__class__, (self._name_,)
+    __reduce_ex__ = Enum.__reduce_ex__
 
     def __repr__(self):
         return f"{self.__class__.__name__}({self._name_!r})"
@@ -1930,7 +1944,6 @@ def __init__(self, *args, **kwargs):
             "role": "text",
             "rsvp": "boolean",
             "sent-by": "cal-address",
-            "tzid": "text",
             "value": "text",
         }
     )
@@ -1978,11 +1991,11 @@ def from_ical(self, name, value):
     "vMonth",
     "vPeriod",
     "vRecur",
-    "vSkip",
     "vText",
     "vTime",
     "vUTCOffset",
     "vUri",
     "vWeekday",
     "tzid_from_tzinfo",
+    "vSkip",
 ]
diff --git a/src/icalendar/tests/calendars/issue_798_freebusy.ics b/src/icalendar/tests/calendars/issue_798_freebusy.ics
new file mode 100644
index 000000000..3f04d5258
--- /dev/null
+++ b/src/icalendar/tests/calendars/issue_798_freebusy.ics
@@ -0,0 +1,12 @@
+BEGIN:VCALENDAR
+BEGIN:VFREEBUSY
+FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:19970308T160000Z/PT8H30M
+END:VFREEBUSY
+BEGIN:VFREEBUSY
+FREEBUSY:19970308T160000Z/PT3H,19970308T200000Z/PT1H
+END:VFREEBUSY
+BEGIN:VFREEBUSY
+FREEBUSY;FBTYPE=FREE:19970308T160000Z/PT3H,19970308T200000Z/PT1H
+ ,19970308T230000Z/19970309T000000Z
+END:VFREEBUSY
+END:VCALENDAR
\ No newline at end of file
diff --git a/src/icalendar/tests/calendars/issue_798_related_to.ics b/src/icalendar/tests/calendars/issue_798_related_to.ics
new file mode 100644
index 000000000..76d75a87b
--- /dev/null
+++ b/src/icalendar/tests/calendars/issue_798_related_to.ics
@@ -0,0 +1,8 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com
+END:VEVENT
+BEGIN:VEVENT
+RELATED-TO;RELTYPE=SIBLING:19960401-080045-4000F192713@example.com
+END:VEVENT
+END:VCALENDAR
\ No newline at end of file
diff --git a/src/icalendar/tests/test_issue_798_property_parameters.py b/src/icalendar/tests/test_issue_798_property_parameters.py
new file mode 100644
index 000000000..19b770091
--- /dev/null
+++ b/src/icalendar/tests/test_issue_798_property_parameters.py
@@ -0,0 +1,199 @@
+"""Test the property parameters."""
+
+from datetime import datetime
+
+import pytest
+
+from icalendar import (
+    CUTYPE,
+    FBTYPE,
+    PARTSTAT,
+    RANGE,
+    RELTYPE,
+    vCalAddress,
+    vDatetime,
+    vPeriod,
+    vText,
+)
+from icalendar.parser import Parameters
+from icalendar.timezone.tzp import TZP
+
+
+class Prop:
+
+    params: Parameters
+
+    def __init__(self, **parameters):
+        """Create a new property."""
+        self.params = Parameters(parameters)
+
+    def to_ical(self) -> str:
+        """Parameters to bytes to string."""
+        return self.params.to_ical().decode("utf-8")
+
+    from icalendar.param import (
+        ALTREP,
+        CN,
+        CUTYPE,
+        DELEGATED_FROM,
+        DELEGATED_TO,
+        RELTYPE,
+    )
+
+
+@pytest.fixture()
+def p():
+    """Empty test property."""
+    return Prop()
+
+
+def test_set_altrep(p):
+    p.ALTREP = "http://example.com"
+    assert p.params == {"ALTREP": "http://example.com"}
+    assert p.ALTREP == "http://example.com"
+
+def test_altrep_must_be_quoted():
+    """altrepparam = "ALTREP" "=" DQUOTE uri DQUOTE"""
+    assert Prop(ALTREP="1234aA").to_ical() == 'ALTREP="1234aA"'
+
+def test_del_altrep(p):
+    """Del when empty"""
+    del p.ALTREP
+    assert p.params == {}
+    assert p.ALTREP is None
+
+def test_del_altrep_full(p):
+    """Del when empty"""
+    p.ALTREP = "http://example.com"
+    del p.ALTREP
+    assert p.params == {}
+    assert p.ALTREP is None
+
+
+def test_get_cutype(p):
+    """The default is individual."""
+    assert p.CUTYPE == "INDIVIDUAL"
+
+
+def test_set_lowercase():
+    p = Prop(CUTYPE="individual")
+    assert p.CUTYPE == "INDIVIDUAL"
+    p.CUTYPE = "unknown"
+    assert p.CUTYPE == CUTYPE.UNKNOWN
+
+
+def test_set_delegation_to_string(p):
+    p.DELEGATED_FROM = "mailto:foo"
+    assert p.DELEGATED_FROM == ("mailto:foo",)
+
+def test_set_delegation_to_tuple(p):
+    p.DELEGATED_TO = ("mailto:foo","mailto:bar")
+    assert p.DELEGATED_TO == ("mailto:foo","mailto:bar")
+
+def test_delete_delegation_to(p):
+    p.DELEGATED_TO = ("mailto:foo","mailto:bar")
+    del p.DELEGATED_TO
+    assert p.DELEGATED_TO == ()
+
+@pytest.mark.parametrize(
+    ("value", "expected"),
+    [
+        ((), ""),
+        (("mailto:foo",), 'DELEGATED-TO="mailto:foo"'),
+        (("mailto:foo","mailto:bar"), 'DELEGATED-TO="mailto:foo","mailto:bar"'),
+        (('mailto:"asd"',), "DELEGATED-TO=\"mailto:^'asd^'\""),
+    ]
+)
+def test_serialize_delegation_to(p, value, expected):
+    p.DELEGATED_TO = value
+    assert p.to_ical() == expected
+
+
+@pytest.mark.parametrize(
+    ("index", "expected"),
+    [
+        (0, FBTYPE.BUSY_UNAVAILABLE),
+        (1, FBTYPE.BUSY),
+        (2, FBTYPE.FREE),
+    ]
+)
+def test_get_fbtype(calendars, index, expected):
+    fb = calendars.issue_798_freebusy.freebusy[index]
+    p : vPeriod = fb["FREEBUSY"]
+    if isinstance(p, list):
+        p = p[0]
+    assert expected == p.FBTYPE
+
+
+@pytest.fixture()
+def addr():
+    return vCalAddress("mailto:foo")
+
+def test_partstat_get(addr: vCalAddress):
+    """test the default partstat"""
+    assert addr.PARTSTAT == "NEEDS-ACTION"
+
+
+def test_set_the_partstat(addr: vCalAddress):
+    addr.PARTSTAT = PARTSTAT.ACCEPTED
+    assert addr.PARTSTAT == "ACCEPTED"
+
+
+def test_this_and_future():
+    assert vDatetime(datetime(2019, 12, 10)).RANGE is None
+
+def test_this_and_future_set():
+    d = vDatetime(datetime(2019, 12, 10))
+    d.RANGE = RANGE.THISANDFUTURE
+    assert d.params["RANGE"] == "THISANDFUTURE"
+
+
+def test_rsvp_default(addr):
+    assert not addr.RSVP
+
+@pytest.mark.parametrize("rsvp", [True, False])
+def test_set_rsvp(addr: vCalAddress, rsvp):
+    addr.RSVP = rsvp
+    assert addr.RSVP == rsvp
+    assert addr.params["RSVP"] == ("TRUE" if rsvp else "FALSE")
+
+
+def test_sent_by(addr: vCalAddress):
+    assert addr.SENT_BY is None
+
+
+
+def test_set_sent_by(addr: vCalAddress):
+    addr.SENT_BY = "mailto:asd"
+    assert addr.SENT_BY == "mailto:asd"
+    assert addr.params["SENT-BY"] == "mailto:asd"
+    assert addr.params.to_ical() == b'SENT-BY="mailto:asd"'
+
+
+@pytest.mark.parametrize("tzid", [None, "Europe/Berlin"])
+def test_tzid(tzid, tzp:TZP):
+    dt = vDatetime(tzp.localize(datetime(2019, 12, 10), tzid))
+    assert dt.TZID is None
+
+
+@pytest.mark.parametrize(
+    ("index", "reltype"),
+    [
+        (0, RELTYPE.PARENT),
+        (1, RELTYPE.SIBLING),
+    ]
+)
+def test_reltype_example(calendars, index, reltype):
+    """The reltype parameter in the examples."""
+    event = calendars.issue_798_related_to.events[index]
+    print(event.to_ical().decode())
+    r : vText = event["RELATED-TO"]
+    print(r)
+    print(r.params)
+    assert reltype == r.RELTYPE
+
+
+def test_set_reltype(p):
+    p.RELTYPE = RELTYPE.CHILD
+    assert p.RELTYPE == RELTYPE.CHILD
+    assert p.params["RELTYPE"] == "CHILD"
diff --git a/src/icalendar/tests/test_property_params.py b/src/icalendar/tests/test_property_params.py
index 7cf928bb9..aaaeb23fd 100644
--- a/src/icalendar/tests/test_property_params.py
+++ b/src/icalendar/tests/test_property_params.py
@@ -34,7 +34,7 @@
                     "ALTREP": ["http://www.wiz.org", "value4"],
                 }
             ),
-            b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3',
+            b'ALTREP="http://www.wiz.org","value4";PARAMETER1=Value1;PARAMETER2=Value2,Value3',
         ),
         # Including empty strings
         (Parameters({"PARAM": ""}), b"PARAM="),
@@ -54,7 +54,7 @@
                     "PARAMETER2": ["Value2", "Value3"],
                 }
             ),
-            b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3',
+            b'ALTREP="http://www.wiz.org","value4";PARAMETER1=Value1;PARAMETER2=Value2,Value3',
         ),
     ],
 )
diff --git a/src/icalendar/timezone/tzp.py b/src/icalendar/timezone/tzp.py
index d0a7754f4..4e81604ae 100644
--- a/src/icalendar/timezone/tzp.py
+++ b/src/icalendar/timezone/tzp.py
@@ -71,11 +71,13 @@ def localize_utc(self, dt: datetime.date) -> datetime.datetime:
         return self.__provider.localize_utc(to_datetime(dt))
 
     def localize(
-        self, dt: datetime.date, tz: Union[datetime.tzinfo, str]
+        self, dt: datetime.date, tz: Union[datetime.tzinfo, str, None]
     ) -> datetime.datetime:
         """Localize a datetime to a timezone."""
         if isinstance(tz, str):
             tz = self.timezone(tz)
+        if tz is None:
+            return dt.replace(tzinfo=None)
         return self.__provider.localize(to_datetime(dt), tz)
 
     def cache_timezone_component(self, timezone_component: cal.Timezone) -> None:
diff --git a/tox.ini b/tox.ini
index 25ce93bab..cc10b119b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -35,9 +35,10 @@ deps =
     -r {toxinidir}/requirements_docs.txt
     setuptools
 changedir = docs
-allowlist_externals = make
+allowlist_externals =
+    make
 commands =
-    make html
+    make clean html
 
 [testenv:ruff]
 # use tox -e ruff to format the code base


Reviews:
user: login: stevepiercy
body: This looks really good. There are a couple of artifacts from previous work that need to be addressed, one that now shows up and another that needs a little nudge.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-02T08:59:41Z
user: login: niccokunzmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T16:59:15Z
user: login: niccokunzmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T17:00:04Z
user: login: niccokunzmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T17:02:14Z
user: login: stevepiercy
body: One question. OK to merge after resolution. Thank you!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-02T22:09:49Z

Review comments:
diff_hunk: @@ -22,11 +24,14 @@ New features:
 - Add ``sequence`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 802 <https://github.com/collective/icalendar/issues/802>`_.
 - Add ``categories`` attribute to ``Calendar``, ``Event``, ``Todo``, and ``Journal`` components. See `Issue 655 <https://github.com/collective/icalendar/issues/655>`_.
 - Add compatibility to :rfc:`6868`. See `Issue 652 <https://github.com/collective/icalendar/issues/652>`_.
+- Add ``freebusy`` property to the ``Calendar`` to get this type of subcomponents easier.
+- Add parameters from :rfc:`5545` to properties: ``ALTREP``, ``CN``, ``CUTYPE``, ``DELEGATED_FROM``, ``DELEGATED_TO``, ``DIR``, ``FBTYPE``, ``LANGUAGE``, ``MEMBER``, ``PARTSTAT``, ``RANGE``, ``RELATED``, ``ROLE``, ``RSVP``, ``SENT_BY``, ``TZID``, ``RELTYPE``. See `Issue 798 <https://github.com/collective/icalendar/issues/798>`_.
path: CHANGES.rst
user: login: stevepiercy
body: ```suggestion
- Add parameters from :rfc:`5545` to properties ``ALTREP``, ``CN``, ``CUTYPE``, ``DELEGATED_FROM``, ``DELEGATED_TO``, ``DIR``, ``FBTYPE``, ``LANGUAGE``, ``MEMBER``, ``PARTSTAT``, ``RANGE``, ``RELATED``, ``ROLE``, ``RSVP``, ``SENT_BY``, ``TZID``, and ``RELTYPE``. See `Issue 798 <https://github.com/collective/icalendar/issues/798>`_.
```
created_at: 2025-05-02T08:38:23Z
author_association: MEMBER
diff_hunk: @@ -22,11 +24,14 @@ New features:
 - Add ``sequence`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 802 <https://github.com/collective/icalendar/issues/802>`_.
 - Add ``categories`` attribute to ``Calendar``, ``Event``, ``Todo``, and ``Journal`` components. See `Issue 655 <https://github.com/collective/icalendar/issues/655>`_.
 - Add compatibility to :rfc:`6868`. See `Issue 652 <https://github.com/collective/icalendar/issues/652>`_.
+- Add ``freebusy`` property to the ``Calendar`` to get this type of subcomponents easier.
+- Add parameters from :rfc:`5545` to properties: ``ALTREP``, ``CN``, ``CUTYPE``, ``DELEGATED_FROM``, ``DELEGATED_TO``, ``DIR``, ``FBTYPE``, ``LANGUAGE``, ``MEMBER``, ``PARTSTAT``, ``RANGE``, ``RELATED``, ``ROLE``, ``RSVP``, ``SENT_BY``, ``TZID``, ``RELTYPE``. See `Issue 798 <https://github.com/collective/icalendar/issues/798>`_.
 - New properties from :rfc:`7986` can occur multiple times in ``VCALENDAR``. See `PR 808`_.
 
 Bug fixes:
 
 - Move import at the end of ``icalendar.parser`` into a function to mitigate import errors, see `Issue 781 <https://github.com/collective/icalendar/issues/781>`_.
+- ``ALTREP``, ``DELEGATED-FROM``, ``DELEGATED-TO``, ``DIR``, ``MEMBER`` and ``SENT-BY`` require double quotes. These are now always added.
path: CHANGES.rst
user: login: stevepiercy
body: ```suggestion
- ``ALTREP``, ``DELEGATED-FROM``, ``DELEGATED-TO``, ``DIR``, ``MEMBER``, and ``SENT-BY`` require double quotes. These are now always added.
```
created_at: 2025-05-02T08:38:38Z
author_association: MEMBER
diff_hunk: @@ -35,8 +35,11 @@ deps =
     -r {toxinidir}/requirements_docs.txt
     setuptools
 changedir = docs
-allowlist_externals = make
+allowlist_externals =
+    make
+    rm
 commands =
+    rm -rf _build
     make html
path: tox.ini
user: login: stevepiercy
body: Revert the above and use the command `make clean html`.
created_at: 2025-05-02T08:46:08Z
author_association: MEMBER
path: src/icalendar/prop.py
user: login: stevepiercy
body: Check lines 200-202 and indent the code-block so it's within its heading's context.
created_at: 2025-05-02T08:48:45Z
author_association: MEMBER
diff_hunk: @@ -1,13 +1,18 @@
 API Reference
 -------------
 
+icalendar.attr
+++++++++++++++
+
+.. automodule:: icalendar.attr
+   :members:
path: docs/api.rst
user: login: stevepiercy
body: `Args:` is badly formatted for all its members. See https://icalendar--810.org.readthedocs.build/en/810/api.html#module-icalendar.attr
created_at: 2025-05-02T08:51:16Z
author_association: MEMBER
diff_hunk: @@ -35,8 +35,11 @@ deps =
     -r {toxinidir}/requirements_docs.txt
     setuptools
 changedir = docs
-allowlist_externals = make
+allowlist_externals =
+    make
+    rm
 commands =
+    rm -rf _build
     make html
path: tox.ini
user: login: niccokunzmann
body: Yes, I did this. Thanks!
created_at: 2025-05-02T16:59:15Z
author_association: MEMBER
diff_hunk: @@ -1,13 +1,18 @@
 API Reference
 -------------
 
+icalendar.attr
+++++++++++++++
+
+.. automodule:: icalendar.attr
+   :members:
path: docs/api.rst
user: login: niccokunzmann
body: I activated this extension: `sphinx.ext.napoleon`
Now, the Google Style arguments should all be processed properly.
created_at: 2025-05-02T17:00:04Z
author_association: MEMBER
path: src/icalendar/prop.py
user: login: niccokunzmann
body: Thanks. I removed the format definition as it is often omitted in other doc strings. 
created_at: 2025-05-02T17:02:14Z
author_association: MEMBER
diff_hunk: @@ -35,9 +35,11 @@ deps =
     -r {toxinidir}/requirements_docs.txt
     setuptools
 changedir = docs
-allowlist_externals = make
+allowlist_externals =
+    make
+    rm
path: tox.ini
user: login: stevepiercy
body: I don't think `rm` is required, assuming that `make` is enough. If it is required, then ignore this suggestion.

```suggestion
```
created_at: 2025-05-02T22:07:06Z
author_association: MEMBER

PR comments:
user: login: coveralls
created_at: 2025-04-24T06:14:47Z
body: ## Pull Request Test Coverage Report for [Build 14809848415](https://coveralls.io/builds/73497570)


### Details

* **249** of **256**   **(97.27%)**  changed or added relevant lines in **9** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.02%**) to **96.734%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/icalendar/param.py](https://coveralls.io/builds/73497570/source?filename=src%2Ficalendar%2Fparam.py#L17) | 79 | 86 | 91.86%
<!-- | **Total:** | **249** | **256** | **97.27%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/73497570/badge)](https://coveralls.io/builds/73497570) |
| :-- | --: |
| Change from base [Build 14784832439](https://coveralls.io/builds/73479547): |  0.02% |
| Covered Lines: | 5443 |
| Relevant Lines: | 5619 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE
user: login: niccokunzmann
created_at: 2025-05-02T17:05:02Z
body: Thanks for the review! Could you do another one?
author_association: MEMBER
user: login: niccokunzmann
created_at: 2025-05-03T10:21:34Z
body: Thanks!
author_association: MEMBER