Add calendar to Workday

This commit is contained in:
G Johansson
2025-08-13 19:35:42 +00:00
parent b40aab479a
commit 9c84ccb594
4 changed files with 183 additions and 1 deletions

View File

@@ -0,0 +1,98 @@
"""Workday Calendar."""
from __future__ import annotations
from datetime import datetime, timedelta
from holidays import HolidayBase
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import WorkdayConfigEntry
from .const import CONF_EXCLUDES, CONF_OFFSET, CONF_WORKDAYS
from .entity import BaseWorkdayEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: WorkdayConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Holiday Calendar config entry."""
days_offset: int = int(entry.options[CONF_OFFSET])
excludes: list[str] = entry.options[CONF_EXCLUDES]
sensor_name: str = entry.options[CONF_NAME]
workdays: list[str] = entry.options[CONF_WORKDAYS]
obj_holidays = entry.runtime_data
async_add_entities(
[
WorkdayCalendarEntity(
obj_holidays,
workdays,
excludes,
days_offset,
sensor_name,
entry.entry_id,
)
],
)
class WorkdayCalendarEntity(BaseWorkdayEntity, CalendarEntity):
"""Representation of a Wokrday Calendar."""
def __init__(
self,
obj_holidays: HolidayBase,
workdays: list[str],
excludes: list[str],
days_offset: int,
name: str,
entry_id: str,
) -> None:
"""Initialize WorkdayCalendarEntity."""
super().__init__(
obj_holidays,
workdays,
excludes,
days_offset,
name,
entry_id,
)
self._attr_unique_id = f"{entry_id}-calender"
self._attr_event = None
self.event_list: list[CalendarEvent] = []
self._name = name
def update_data(self, now: datetime) -> None:
"""Update data."""
event_list = []
for i in range(365):
future_date = now.date() + timedelta(days=i)
if self.date_is_workday(future_date):
event = CalendarEvent(
summary=self._name,
start=future_date,
end=future_date,
)
event_list.append(event)
self.event_list = event_list
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return (
sorted(self.event_list, key=lambda e: e.start)[0]
if self.event_list
else None
)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Get all events in a specific time frame."""
return self.event_list

View File

@@ -11,7 +11,7 @@ LOGGER = logging.getLogger(__package__)
ALLOWED_DAYS = [*WEEKDAYS, "holiday"]
DOMAIN = "workday"
PLATFORMS = [Platform.BINARY_SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CALENDAR]
CONF_PROVINCE = "province"
CONF_WORKDAYS = "workdays"

View File

@@ -212,6 +212,11 @@
}
}
}
},
"calendar": {
"workday": {
"name": "[%key:component::calendar::title%]"
}
}
},
"services": {

View File

@@ -0,0 +1,79 @@
"""Tests for calendar platform of Workday integration."""
from datetime import datetime, timedelta
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.calendar import (
DOMAIN as CALENDAR_DOMAIN,
SERVICE_GET_EVENTS,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from . import TEST_CONFIG_WITH_PROVINCE, init_integration
from tests.common import async_fire_time_changed
@pytest.mark.parametrize(
"time_zone", ["Asia/Tokyo", "Europe/Berlin", "America/Chicago", "US/Hawaii"]
)
async def test_holiday_calendar_entity(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
time_zone: str,
) -> None:
"""Test HolidayCalendarEntity functionality."""
await hass.config.async_set_time_zone(time_zone)
zone = await dt_util.async_get_time_zone(time_zone)
freezer.move_to(datetime(2023, 1, 1, 0, 1, 1, tzinfo=zone)) # New Years Day
await init_integration(hass, TEST_CONFIG_WITH_PROVINCE)
await async_setup_component(hass, "calendar", {})
await hass.async_block_till_done()
response = await hass.services.async_call(
CALENDAR_DOMAIN,
SERVICE_GET_EVENTS,
{
"entity_id": "calendar.workday_sensor_calendar",
"end_date_time": dt_util.now() + timedelta(hours=1),
},
blocking=True,
return_response=True,
)
assert {
"end": "2023-01-01",
"start": "2023-01-02",
"summary": "Workday Sensor",
} not in response["calendar.workday_sensor_calendar"]["events"]
assert {
"end": "2023-01-04",
"start": "2023-01-03",
"summary": "Workday Sensor",
} in response["calendar.workday_sensor_calendar"]["events"]
state = hass.states.get("calendar.workday_sensor_calendar")
assert state is not None
assert state.state == "off"
freezer.move_to(
datetime(2023, 1, 2, 0, 1, 1, tzinfo=zone)
) # Day after New Years Day
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("calendar.workday_sensor_calendar")
assert state is not None
assert state.state == "on"
freezer.move_to(datetime(2023, 1, 7, 0, 1, 1, tzinfo=zone)) # Workday
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("calendar.workday_sensor_calendar")
assert state is not None
assert state.state == "off"