1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Add calendar to Workday (#150596)

This commit is contained in:
G Johansson
2025-09-16 17:29:04 +02:00
committed by GitHub
parent 9ee9bb368d
commit 6aafa666d6
4 changed files with 193 additions and 1 deletions
@@ -0,0 +1,104 @@
"""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
CALENDAR_DAYS_AHEAD = 365
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 Workday 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 = entry_id
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(CALENDAR_DAYS_AHEAD):
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 [
workday
for workday in self.event_list
if start_date.date() <= workday.start <= end_date.date()
]
+1 -1
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"
@@ -212,6 +212,11 @@
}
}
}
},
"calendar": {
"workday": {
"name": "[%key:component::calendar::title%]"
}
}
},
"services": {
+83
View File
@@ -0,0 +1,83 @@
"""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,
EVENT_END_DATETIME,
EVENT_START_DATETIME,
EVENT_SUMMARY,
SERVICE_GET_EVENTS,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from . import TEST_CONFIG_WITH_PROVINCE, init_integration
from tests.common import async_fire_time_changed
ATTR_END = "end"
ATTR_START = "start"
@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)
response = await hass.services.async_call(
CALENDAR_DOMAIN,
SERVICE_GET_EVENTS,
{
ATTR_ENTITY_ID: "calendar.workday_sensor_calendar",
EVENT_START_DATETIME: dt_util.now(),
EVENT_END_DATETIME: dt_util.now() + timedelta(days=10, hours=1),
},
blocking=True,
return_response=True,
)
assert {
ATTR_END: "2023-01-02",
ATTR_START: "2023-01-01",
EVENT_SUMMARY: "Workday Sensor",
} not in response["calendar.workday_sensor_calendar"]["events"]
assert {
ATTR_END: "2023-01-04",
ATTR_START: "2023-01-03",
EVENT_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"