Repository: home-assistant/core
File: homeassistant/components/calendar/__init__.py (Python)

Code before:
    start: datetime.datetime, offset_time: datetime.timedelta
) -> bool:
    """Have we reached the offset time specified in the event title."""
    if offset_time == datetime.timedelta():
        return False
    return start + offset_time <= dt_util.now(start.tzinfo)


class CalendarEntityDescription(EntityDescription, frozen_or_thawed=True):
    """A class that describes calendar entities."""


class CalendarEntity(Entity):
    """Base class for calendar event entities."""

    entity_description: CalendarEntityDescription

    _entity_component_unrecorded_attributes = frozenset({"description"})

    _alarm_unsubs: list[CALLBACK_TYPE] | None = None

    _attr_color: RGBColor | None = None
    _calendar_option_color: RGBColor | None = None

    @property
    def color(self) -> RGBColor | None:
        """Return the color of the calendar entity as RGBColor.

        First checks entity registry options, then falls back to _attr_color.
        """
        return self._calendar_option_color or self._attr_color

    async def async_internal_added_to_hass(self) -> None:
        """Call when the calendar entity is added to hass."""
        await super().async_internal_added_to_hass()
        if not self.registry_entry:
            return
        self._async_read_entity_options()

    @callback
    def async_registry_entry_updated(self) -> None:
        """Run when the entity registry entry has been updated."""
        self._async_read_entity_options()

    @callback
    def _async_read_entity_options(self) -> None:
        """Read entity options from entity registry.

        Called when the entity registry entry has been updated and before the
        calendar is added to the state machine.
        """

Review comment (suggestion):
I have an outstanding comment about developer documentation https://developers.home-assistant.io/docs/core/entity/calendar/ -- please update those docs describing the responsibilities of someone implementing a calendar integration with respect to color, and attach to this PR. I think it should mention the full color lifecycle

Code after:
    """Have we reached the offset time specified in the event title."""
    if offset_time == datetime.timedelta():
        return False
    return start + offset_time <= dt_util.now(start.tzinfo)


class CalendarEntityDescription(EntityDescription, frozen_or_thawed=True):
    """A class that describes calendar entities."""


class CalendarEntity(Entity):
    """Base class for calendar event entities."""

    entity_description: CalendarEntityDescription

    _entity_component_unrecorded_attributes = frozenset({"description"})

    _alarm_unsubs: list[CALLBACK_TYPE] | None = None

    _attr_initial_color: str | None = None

    @property
    def initial_color(self) -> str | None:
        """Return the initial color for the calendar entity."""
        return self._attr_initial_color

    def get_initial_entity_options(self) -> er.EntityOptionsType | None:
        """Return initial entity options."""
        if self.initial_color is None:
            return None

        # Validate that it's a valid hex color string with # prefix
        try:
            validated_color = cv.color_hex(self.initial_color)
        except vol.Invalid:
            return None

        return {DOMAIN: {"color": validated_color}}

    @property
    def event(self) -> CalendarEvent | None:
        """Return the next upcoming event."""
        raise NotImplementedError

    @final
    @property
    def state_attributes(self) -> dict[str, Any] | None:
        """Return the entity state attributes."""
        if (event := self.event) is None:
            return None