Add datetime
platform (#81943)
Co-authored-by: Franck Nijhof <frenck@frenck.nl> Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
940942a74a
commit
24290e5d08
17 changed files with 468 additions and 30 deletions
|
@ -21,6 +21,7 @@ base_platforms: &base_platforms
|
|||
- homeassistant/components/climate/**
|
||||
- homeassistant/components/cover/**
|
||||
- homeassistant/components/date/**
|
||||
- homeassistant/components/datetime/**
|
||||
- homeassistant/components/device_tracker/**
|
||||
- homeassistant/components/diagnostics/**
|
||||
- homeassistant/components/fan/**
|
||||
|
|
|
@ -240,6 +240,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/daikin/ @fredrike
|
||||
/homeassistant/components/date/ @home-assistant/core
|
||||
/tests/components/date/ @home-assistant/core
|
||||
/homeassistant/components/datetime/ @home-assistant/core
|
||||
/tests/components/datetime/ @home-assistant/core
|
||||
/homeassistant/components/debugpy/ @frenck
|
||||
/tests/components/debugpy/ @frenck
|
||||
/homeassistant/components/deconz/ @Kane610
|
||||
|
|
|
@ -19,6 +19,7 @@ import yarl
|
|||
from . import config as conf_util, config_entries, core, loader
|
||||
from .components import http
|
||||
from .const import (
|
||||
FORMAT_DATETIME,
|
||||
REQUIRED_NEXT_PYTHON_HA_RELEASE,
|
||||
REQUIRED_NEXT_PYTHON_VER,
|
||||
SIGNAL_BOOTSTRAP_INTEGRATIONS,
|
||||
|
@ -347,7 +348,6 @@ def async_enable_logging(
|
|||
fmt = (
|
||||
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||
)
|
||||
datefmt = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
if not log_no_color:
|
||||
try:
|
||||
|
@ -362,7 +362,7 @@ def async_enable_logging(
|
|||
logging.getLogger().handlers[0].setFormatter(
|
||||
ColoredFormatter(
|
||||
colorfmt,
|
||||
datefmt=datefmt,
|
||||
datefmt=FORMAT_DATETIME,
|
||||
reset=True,
|
||||
log_colors={
|
||||
"DEBUG": "cyan",
|
||||
|
@ -378,7 +378,7 @@ def async_enable_logging(
|
|||
|
||||
# If the above initialization failed for any reason, setup the default
|
||||
# formatting. If the above succeeds, this will result in a no-op.
|
||||
logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.INFO)
|
||||
logging.basicConfig(format=fmt, datefmt=FORMAT_DATETIME, level=logging.INFO)
|
||||
|
||||
# Capture warnings.warn(...) and friends messages in logs.
|
||||
# The standard destination for them is stderr, which may end up unnoticed.
|
||||
|
@ -435,7 +435,7 @@ def async_enable_logging(
|
|||
_LOGGER.error("Error rolling over log file: %s", err)
|
||||
|
||||
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
|
||||
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
|
||||
err_handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
|
||||
|
||||
logger = logging.getLogger("")
|
||||
logger.addHandler(err_handler)
|
||||
|
|
126
homeassistant/components/datetime/__init__.py
Normal file
126
homeassistant/components/datetime/__init__.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
"""Component to allow setting date/time as platforms."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import logging
|
||||
from typing import final
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
ENTITY_SERVICE_FIELDS,
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import ATTR_DATETIME, DOMAIN, SERVICE_SET_VALUE
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ["ATTR_DATETIME", "DOMAIN", "DateTimeEntity", "DateTimeEntityDescription"]
|
||||
|
||||
|
||||
async def _async_set_value(entity: DateTimeEntity, service_call: ServiceCall) -> None:
|
||||
"""Service call wrapper to set a new date/time."""
|
||||
value: datetime = service_call.data[ATTR_DATETIME]
|
||||
if value.tzinfo is None:
|
||||
value = value.replace(
|
||||
tzinfo=dt_util.get_time_zone(entity.hass.config.time_zone)
|
||||
)
|
||||
return await entity.async_set_value(value)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Date/Time entities."""
|
||||
component = hass.data[DOMAIN] = EntityComponent[DateTimeEntity](
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
|
||||
)
|
||||
await component.async_setup(config)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
vol.Required(ATTR_DATETIME): cv.datetime,
|
||||
**ENTITY_SERVICE_FIELDS,
|
||||
},
|
||||
_async_set_value,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
component: EntityComponent[DateTimeEntity] = hass.data[DOMAIN]
|
||||
return await component.async_setup_entry(entry)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
component: EntityComponent[DateTimeEntity] = hass.data[DOMAIN]
|
||||
return await component.async_unload_entry(entry)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DateTimeEntityDescription(EntityDescription):
|
||||
"""A class that describes date/time entities."""
|
||||
|
||||
|
||||
class DateTimeEntity(Entity):
|
||||
"""Representation of a Date/time entity."""
|
||||
|
||||
entity_description: DateTimeEntityDescription
|
||||
_attr_device_class: None = None
|
||||
_attr_state: None = None
|
||||
_attr_native_value: datetime | None
|
||||
|
||||
@property
|
||||
@final
|
||||
def device_class(self) -> None:
|
||||
"""Return entity device class."""
|
||||
return None
|
||||
|
||||
@property
|
||||
@final
|
||||
def state_attributes(self) -> None:
|
||||
"""Return the state attributes."""
|
||||
return None
|
||||
|
||||
@property
|
||||
@final
|
||||
def state(self) -> str | None:
|
||||
"""Return the entity state."""
|
||||
if (value := self.native_value) is None:
|
||||
return None
|
||||
if value.tzinfo is None:
|
||||
raise ValueError(
|
||||
f"Invalid datetime: {self.entity_id} provides state '{value}', "
|
||||
"which is missing timezone information"
|
||||
)
|
||||
|
||||
return value.astimezone(timezone.utc).isoformat(timespec="seconds")
|
||||
|
||||
@property
|
||||
def native_value(self) -> datetime | None:
|
||||
"""Return the value reported by the datetime."""
|
||||
return self._attr_native_value
|
||||
|
||||
def set_value(self, value: datetime) -> None:
|
||||
"""Change the date/time."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_set_value(self, value: datetime) -> None:
|
||||
"""Change the date/time."""
|
||||
await self.hass.async_add_executor_job(self.set_value, value)
|
7
homeassistant/components/datetime/const.py
Normal file
7
homeassistant/components/datetime/const.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""Provides the constants needed for the component."""
|
||||
|
||||
DOMAIN = "datetime"
|
||||
|
||||
ATTR_DATETIME = "datetime"
|
||||
|
||||
SERVICE_SET_VALUE = "set_value"
|
8
homeassistant/components/datetime/manifest.json
Normal file
8
homeassistant/components/datetime/manifest.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"domain": "datetime",
|
||||
"name": "Date/Time",
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/datetime",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal"
|
||||
}
|
14
homeassistant/components/datetime/services.yaml
Normal file
14
homeassistant/components/datetime/services.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
set_value:
|
||||
name: Set Date/Time
|
||||
description: Set the date/time for a datetime entity.
|
||||
target:
|
||||
entity:
|
||||
domain: datetime
|
||||
fields:
|
||||
datetime:
|
||||
name: Date & Time
|
||||
description: The date/time to set. The time zone of the Home Assistant instance is assumed.
|
||||
required: true
|
||||
example: "2022/11/01 22:15"
|
||||
selector:
|
||||
datetime:
|
8
homeassistant/components/datetime/strings.json
Normal file
8
homeassistant/components/datetime/strings.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"title": "Date/Time",
|
||||
"entity_component": {
|
||||
"_": {
|
||||
"name": "[%key:component::datetime::title%]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM = [
|
|||
Platform.CLIMATE,
|
||||
Platform.COVER,
|
||||
Platform.DATE,
|
||||
Platform.DATETIME,
|
||||
Platform.FAN,
|
||||
Platform.HUMIDIFIER,
|
||||
Platform.LIGHT,
|
||||
|
|
77
homeassistant/components/demo/datetime.py
Normal file
77
homeassistant/components/demo/datetime.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""Demo platform that offers a fake date/time entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from homeassistant.components.datetime import DateTimeEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Demo date/time entity."""
|
||||
async_add_entities(
|
||||
[
|
||||
DemoDateTime(
|
||||
"datetime",
|
||||
"Date and Time",
|
||||
datetime(2020, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
||||
"mdi:calendar-clock",
|
||||
False,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Demo config entry."""
|
||||
await async_setup_platform(hass, {}, async_add_entities)
|
||||
|
||||
|
||||
class DemoDateTime(DateTimeEntity):
|
||||
"""Representation of a Demo date/time entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
unique_id: str,
|
||||
name: str,
|
||||
state: datetime,
|
||||
icon: str,
|
||||
assumed_state: bool,
|
||||
) -> None:
|
||||
"""Initialize the Demo date/time entity."""
|
||||
self._attr_assumed_state = assumed_state
|
||||
self._attr_icon = icon
|
||||
self._attr_name = name or DEVICE_DEFAULT_NAME
|
||||
self._attr_native_value = state
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
# Serial numbers are unique identifiers within a specific domain
|
||||
(DOMAIN, unique_id)
|
||||
},
|
||||
name=self.name,
|
||||
)
|
||||
|
||||
async def async_set_value(self, value: datetime) -> None:
|
||||
"""Update the date/time."""
|
||||
self._attr_native_value = value
|
||||
self.async_write_ha_state()
|
|
@ -272,7 +272,7 @@ class InputDatetime(collection.CollectionEntity, RestoreEntity):
|
|||
if self.state is not None:
|
||||
return
|
||||
|
||||
default_value = py_datetime.datetime.today().strftime("%Y-%m-%d 00:00:00")
|
||||
default_value = py_datetime.datetime.today().strftime(f"{FMT_DATE} 00:00:00")
|
||||
|
||||
# Priority 2: Old state
|
||||
if (old_state := await self.async_get_last_state()) is None:
|
||||
|
|
|
@ -32,6 +32,7 @@ class Platform(StrEnum):
|
|||
CLIMATE = "climate"
|
||||
COVER = "cover"
|
||||
DATE = "date"
|
||||
DATETIME = "datetime"
|
||||
DEVICE_TRACKER = "device_tracker"
|
||||
FAN = "fan"
|
||||
GEO_LOCATION = "geo_location"
|
||||
|
@ -1165,6 +1166,11 @@ HASSIO_USER_NAME = "Supervisor"
|
|||
|
||||
SIGNAL_BOOTSTRAP_INTEGRATIONS = "bootstrap_integrations"
|
||||
|
||||
# Date/Time formats
|
||||
FORMAT_DATE: Final = "%Y-%m-%d"
|
||||
FORMAT_TIME: Final = "%H:%M:%S"
|
||||
FORMAT_DATETIME: Final = f"{FORMAT_DATE} {FORMAT_TIME}"
|
||||
|
||||
|
||||
class EntityCategory(StrEnum):
|
||||
"""Category of an entity.
|
||||
|
|
1
tests/components/datetime/__init__.py
Normal file
1
tests/components/datetime/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Tests for the datetime component."""
|
98
tests/components/datetime/test_init.py
Normal file
98
tests/components/datetime/test_init.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
"""The tests for the datetime component."""
|
||||
from datetime import datetime, timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.datetime import (
|
||||
ATTR_DATETIME,
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
DateTimeEntity,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, CONF_PLATFORM
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
DEFAULT_VALUE = datetime(2020, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
class MockDateTimeEntity(DateTimeEntity):
|
||||
"""Mock datetime device to use in tests."""
|
||||
|
||||
def __init__(self, native_value: datetime | None = DEFAULT_VALUE) -> None:
|
||||
"""Initialize mock datetime entity."""
|
||||
self._attr_native_value = native_value
|
||||
|
||||
async def async_set_value(self, value: datetime) -> None:
|
||||
"""Change the date/time."""
|
||||
self._attr_native_value = value
|
||||
|
||||
|
||||
async def test_datetime(hass: HomeAssistant, enable_custom_integrations: None) -> None:
|
||||
"""Test date/time entity."""
|
||||
hass.config.set_time_zone("UTC")
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
platform.init()
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("datetime.test")
|
||||
assert state.state == "2020-01-01T01:02:03+00:00"
|
||||
assert state.attributes == {ATTR_FRIENDLY_NAME: "test"}
|
||||
|
||||
# Test updating datetime
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_DATETIME: datetime(2022, 3, 3, 3, 4, 5), ATTR_ENTITY_ID: "datetime.test"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("datetime.test")
|
||||
assert state.state == "2022-03-03T03:04:05+00:00"
|
||||
|
||||
# Test updating datetime with UTC timezone
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_DATETIME: "2022-03-03T03:04:05+00:00", ATTR_ENTITY_ID: "datetime.test"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("datetime.test")
|
||||
assert state.state == "2022-03-03T03:04:05+00:00"
|
||||
|
||||
# Test updating datetime with non UTC timezone
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_DATETIME: "2022-03-03T03:04:05-05:00", ATTR_ENTITY_ID: "datetime.test"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("datetime.test")
|
||||
assert state.state == "2022-03-03T08:04:05+00:00"
|
||||
|
||||
# Test that non UTC timezone gets converted to UTC
|
||||
assert (
|
||||
MockDateTimeEntity(
|
||||
native_value=datetime(2020, 1, 2, 3, 4, 5, tzinfo=ZoneInfo("US/Eastern"))
|
||||
).state
|
||||
== "2020-01-02T08:04:05+00:00"
|
||||
)
|
||||
|
||||
# Test None state
|
||||
date_entity = MockDateTimeEntity(native_value=None)
|
||||
assert date_entity.state is None
|
||||
assert date_entity.state_attributes is None
|
||||
|
||||
# Test that timezone is required to process state
|
||||
with pytest.raises(ValueError):
|
||||
assert MockDateTimeEntity(
|
||||
native_value=datetime(2020, 1, 2, 3, 4, 5, tzinfo=None)
|
||||
).state
|
35
tests/components/demo/test_datetime.py
Normal file
35
tests/components/demo/test_datetime.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""The tests for the demo datetime component."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.datetime import ATTR_DATETIME, DOMAIN, SERVICE_SET_VALUE
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
ENTITY_DATETIME = "datetime.date_and_time"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_demo_datetime(hass: HomeAssistant) -> None:
|
||||
"""Initialize setup demo datetime."""
|
||||
assert await async_setup_component(hass, DOMAIN, {"datetime": {"platform": "demo"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
def test_setup_params(hass: HomeAssistant) -> None:
|
||||
"""Test the initial parameters."""
|
||||
state = hass.states.get(ENTITY_DATETIME)
|
||||
assert state.state == "2020-01-01T12:00:00+00:00"
|
||||
|
||||
|
||||
async def test_set_datetime(hass: HomeAssistant) -> None:
|
||||
"""Test set datetime service."""
|
||||
hass.config.set_time_zone("UTC")
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: ENTITY_DATETIME, ATTR_DATETIME: "2021-02-03 01:02:03"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(ENTITY_DATETIME)
|
||||
assert state.state == "2021-02-03T01:02:03+00:00"
|
|
@ -19,12 +19,16 @@ from homeassistant.components.input_datetime import (
|
|||
CONFIG_SCHEMA,
|
||||
DEFAULT_TIME,
|
||||
DOMAIN,
|
||||
FMT_DATE,
|
||||
FMT_DATETIME,
|
||||
FMT_TIME,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_NAME,
|
||||
FORMAT_DATE,
|
||||
FORMAT_DATETIME,
|
||||
FORMAT_TIME,
|
||||
)
|
||||
from homeassistant.core import Context, CoreState, HomeAssistant, State
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
@ -136,7 +140,7 @@ async def test_set_datetime(hass: HomeAssistant) -> None:
|
|||
await async_set_date_and_time(hass, entity_id, dt_obj)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == dt_obj.strftime(FMT_DATETIME)
|
||||
assert state.state == dt_obj.strftime(FORMAT_DATETIME)
|
||||
assert state.attributes["has_time"]
|
||||
assert state.attributes["has_date"]
|
||||
|
||||
|
@ -164,7 +168,7 @@ async def test_set_datetime_2(hass: HomeAssistant) -> None:
|
|||
await async_set_datetime(hass, entity_id, dt_obj)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == dt_obj.strftime(FMT_DATETIME)
|
||||
assert state.state == dt_obj.strftime(FORMAT_DATETIME)
|
||||
assert state.attributes["has_time"]
|
||||
assert state.attributes["has_date"]
|
||||
|
||||
|
@ -192,7 +196,7 @@ async def test_set_datetime_3(hass: HomeAssistant) -> None:
|
|||
await async_set_timestamp(hass, entity_id, dt_util.as_utc(dt_obj).timestamp())
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == dt_obj.strftime(FMT_DATETIME)
|
||||
assert state.state == dt_obj.strftime(FORMAT_DATETIME)
|
||||
assert state.attributes["has_time"]
|
||||
assert state.attributes["has_date"]
|
||||
|
||||
|
@ -218,7 +222,7 @@ async def test_set_datetime_time(hass: HomeAssistant) -> None:
|
|||
await async_set_date_and_time(hass, entity_id, dt_obj)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == dt_obj.strftime(FMT_TIME)
|
||||
assert state.state == dt_obj.strftime(FORMAT_TIME)
|
||||
assert state.attributes["has_time"]
|
||||
assert not state.attributes["has_date"]
|
||||
|
||||
|
@ -337,7 +341,7 @@ async def test_restore_state(hass: HomeAssistant) -> None:
|
|||
"test_bogus_data": {
|
||||
"has_time": True,
|
||||
"has_date": True,
|
||||
"initial": initial.strftime(FMT_DATETIME),
|
||||
"initial": initial.strftime(FORMAT_DATETIME),
|
||||
},
|
||||
"test_was_time": {"has_time": False, "has_date": True},
|
||||
"test_was_date": {"has_time": True, "has_date": False},
|
||||
|
@ -347,22 +351,22 @@ async def test_restore_state(hass: HomeAssistant) -> None:
|
|||
|
||||
dt_obj = datetime.datetime(2017, 9, 7, 19, 46)
|
||||
state_time = hass.states.get("input_datetime.test_time")
|
||||
assert state_time.state == dt_obj.strftime(FMT_TIME)
|
||||
assert state_time.state == dt_obj.strftime(FORMAT_TIME)
|
||||
|
||||
state_date = hass.states.get("input_datetime.test_date")
|
||||
assert state_date.state == dt_obj.strftime(FMT_DATE)
|
||||
assert state_date.state == dt_obj.strftime(FORMAT_DATE)
|
||||
|
||||
state_datetime = hass.states.get("input_datetime.test_datetime")
|
||||
assert state_datetime.state == dt_obj.strftime(FMT_DATETIME)
|
||||
assert state_datetime.state == dt_obj.strftime(FORMAT_DATETIME)
|
||||
|
||||
state_bogus = hass.states.get("input_datetime.test_bogus_data")
|
||||
assert state_bogus.state == initial.strftime(FMT_DATETIME)
|
||||
assert state_bogus.state == initial.strftime(FORMAT_DATETIME)
|
||||
|
||||
state_was_time = hass.states.get("input_datetime.test_was_time")
|
||||
assert state_was_time.state == default.strftime(FMT_DATE)
|
||||
assert state_was_time.state == default.strftime(FORMAT_DATE)
|
||||
|
||||
state_was_date = hass.states.get("input_datetime.test_was_date")
|
||||
assert state_was_date.state == default.strftime(FMT_TIME)
|
||||
assert state_was_date.state == default.strftime(FORMAT_TIME)
|
||||
|
||||
|
||||
async def test_default_value(hass: HomeAssistant) -> None:
|
||||
|
@ -381,15 +385,15 @@ async def test_default_value(hass: HomeAssistant) -> None:
|
|||
|
||||
dt_obj = datetime.datetime.combine(datetime.date.today(), DEFAULT_TIME)
|
||||
state_time = hass.states.get("input_datetime.test_time")
|
||||
assert state_time.state == dt_obj.strftime(FMT_TIME)
|
||||
assert state_time.state == dt_obj.strftime(FORMAT_TIME)
|
||||
assert state_time.attributes.get("timestamp") is not None
|
||||
|
||||
state_date = hass.states.get("input_datetime.test_date")
|
||||
assert state_date.state == dt_obj.strftime(FMT_DATE)
|
||||
assert state_date.state == dt_obj.strftime(FORMAT_DATE)
|
||||
assert state_date.attributes.get("timestamp") is not None
|
||||
|
||||
state_datetime = hass.states.get("input_datetime.test_datetime")
|
||||
assert state_datetime.state == dt_obj.strftime(FMT_DATETIME)
|
||||
assert state_datetime.state == dt_obj.strftime(FORMAT_DATETIME)
|
||||
assert state_datetime.attributes.get("timestamp") is not None
|
||||
|
||||
|
||||
|
@ -446,7 +450,7 @@ async def test_reload(
|
|||
assert state_1 is not None
|
||||
assert state_2 is None
|
||||
assert state_3 is not None
|
||||
assert dt_obj.strftime(FMT_DATE) == state_1.state
|
||||
assert dt_obj.strftime(FORMAT_DATE) == state_1.state
|
||||
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt1") == f"{DOMAIN}.dt1"
|
||||
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt2") is None
|
||||
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt3") == f"{DOMAIN}.dt3"
|
||||
|
@ -484,10 +488,10 @@ async def test_reload(
|
|||
assert state_1 is not None
|
||||
assert state_2 is not None
|
||||
assert state_3 is None
|
||||
assert state_1.state == DEFAULT_TIME.strftime(FMT_TIME)
|
||||
assert state_1.state == DEFAULT_TIME.strftime(FORMAT_TIME)
|
||||
assert state_2.state == datetime.datetime.combine(
|
||||
datetime.date.today(), DEFAULT_TIME
|
||||
).strftime(FMT_DATETIME)
|
||||
).strftime(FORMAT_DATETIME)
|
||||
|
||||
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt1") == f"{DOMAIN}.dt1"
|
||||
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt2") == f"{DOMAIN}.dt2"
|
||||
|
@ -705,7 +709,7 @@ async def test_timestamp(hass: HomeAssistant) -> None:
|
|||
assert (
|
||||
dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(state_with_tz.attributes[ATTR_TIMESTAMP])
|
||||
).strftime(FMT_DATETIME)
|
||||
).strftime(FORMAT_DATETIME)
|
||||
== "2020-12-13 01:00:00"
|
||||
)
|
||||
|
||||
|
@ -719,13 +723,13 @@ async def test_timestamp(hass: HomeAssistant) -> None:
|
|||
assert (
|
||||
dt_util.utc_from_timestamp(
|
||||
state_without_tz.attributes[ATTR_TIMESTAMP]
|
||||
).strftime(FMT_DATETIME)
|
||||
).strftime(FORMAT_DATETIME)
|
||||
== "2020-12-13 18:00:00"
|
||||
)
|
||||
assert (
|
||||
dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(state_without_tz.attributes[ATTR_TIMESTAMP])
|
||||
).strftime(FMT_DATETIME)
|
||||
).strftime(FORMAT_DATETIME)
|
||||
== "2020-12-13 10:00:00"
|
||||
)
|
||||
# Use datetime.datetime.fromtimestamp
|
||||
|
@ -734,7 +738,7 @@ async def test_timestamp(hass: HomeAssistant) -> None:
|
|||
datetime.datetime.fromtimestamp(
|
||||
state_without_tz.attributes[ATTR_TIMESTAMP], datetime.timezone.utc
|
||||
)
|
||||
).strftime(FMT_DATETIME)
|
||||
).strftime(FORMAT_DATETIME)
|
||||
== "2020-12-13 10:00:00"
|
||||
)
|
||||
|
||||
|
|
50
tests/testing_config/custom_components/test/datetime.py
Normal file
50
tests/testing_config/custom_components/test/datetime.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
"""Provide a mock time platform.
|
||||
|
||||
Call init before using it in your tests to ensure clean test data.
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from homeassistant.components.datetime import DateTimeEntity
|
||||
|
||||
from tests.common import MockEntity
|
||||
|
||||
UNIQUE_DATETIME = "unique_datetime"
|
||||
|
||||
ENTITIES = []
|
||||
|
||||
|
||||
class MockDateTimeEntity(MockEntity, DateTimeEntity):
|
||||
"""Mock date/time class."""
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the native value of this date/time."""
|
||||
return self._handle("native_value")
|
||||
|
||||
def set_value(self, value: datetime) -> None:
|
||||
"""Change the time."""
|
||||
self._values["native_value"] = value
|
||||
|
||||
|
||||
def init(empty=False):
|
||||
"""Initialize the platform with entities."""
|
||||
global ENTITIES
|
||||
|
||||
ENTITIES = (
|
||||
[]
|
||||
if empty
|
||||
else [
|
||||
MockDateTimeEntity(
|
||||
name="test",
|
||||
unique_id=UNIQUE_DATETIME,
|
||||
native_value=datetime(2020, 1, 1, 1, 2, 3, tzinfo=timezone.utc),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities_callback, discovery_info=None
|
||||
):
|
||||
"""Return mock entities."""
|
||||
async_add_entities_callback(ENTITIES)
|
Loading…
Add table
Reference in a new issue