From 68d92c31966dd344a16e9f50dbb28f8ba189bf99 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 16 Apr 2016 00:55:35 -0700 Subject: [PATCH 1/2] Use standardised datetime format --- homeassistant/components/automation/time.py | 6 +- .../components/binary_sensor/vera.py | 3 +- homeassistant/components/history.py | 2 +- homeassistant/components/light/vera.py | 3 +- homeassistant/components/logbook.py | 4 +- homeassistant/components/notify/file.py | 4 +- homeassistant/components/recorder.py | 2 +- .../sensor/swiss_public_transport.py | 10 ++- .../components/sensor/systemmonitor.py | 6 +- homeassistant/components/sensor/time_date.py | 8 +- homeassistant/components/sensor/vera.py | 3 +- homeassistant/components/sensor/worldclock.py | 5 +- homeassistant/components/sensor/yr.py | 9 +- homeassistant/components/sun.py | 11 +-- homeassistant/components/switch/vera.py | 3 +- homeassistant/core.py | 31 +++---- homeassistant/helpers/state.py | 5 +- homeassistant/remote.py | 5 +- homeassistant/util/__init__.py | 4 +- homeassistant/util/dt.py | 86 +++++++++---------- tests/components/automation/test_sun.py | 22 ++--- tests/components/notify/test_file.py | 8 +- tests/components/test_logbook.py | 2 +- tests/components/test_mqtt_eventstream.py | 12 +-- tests/components/test_recorder.py | 14 ++- tests/test_core.py | 7 +- tests/test_remote.py | 4 + tests/util/test_dt.py | 35 +++----- tests/util/test_init.py | 2 +- 29 files changed, 154 insertions(+), 162 deletions(-) diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index f28e95c6f7a..761ad73b826 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) def trigger(hass, config, action): """Listen for state changes based on configuration.""" if CONF_AFTER in config: - after = dt_util.parse_time_str(config[CONF_AFTER]) + after = dt_util.parse_time(config[CONF_AFTER]) if after is None: _error_time(config[CONF_AFTER], CONF_AFTER) return False @@ -62,13 +62,13 @@ def if_action(hass, config): return None if before is not None: - before = dt_util.parse_time_str(before) + before = dt_util.parse_time(before) if before is None: _error_time(before, CONF_BEFORE) return None if after is not None: - after = dt_util.parse_time_str(after) + after = dt_util.parse_time(after) if after is None: _error_time(after, CONF_AFTER) return None diff --git a/homeassistant/components/binary_sensor/vera.py b/homeassistant/components/binary_sensor/vera.py index 3f92503dcbf..27d604805dc 100644 --- a/homeassistant/components/binary_sensor/vera.py +++ b/homeassistant/components/binary_sensor/vera.py @@ -49,8 +49,7 @@ class VeraBinarySensor(VeraDevice, BinarySensorDevice): last_tripped = self.vera_device.last_trip if last_tripped is not None: utc_time = dt_util.utc_from_timestamp(int(last_tripped)) - attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( - utc_time) + attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat() else: attr[ATTR_LAST_TRIP_TIME] = None tripped = self.vera_device.is_tripped diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index 9151406c388..b3ddbe21415 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -182,7 +182,7 @@ def _api_history_period(handler, path_match, data): one_day = timedelta(seconds=86400) if date_str: - start_date = dt_util.date_str_to_date(date_str) + start_date = dt_util.parse_date(date_str) if start_date is None: handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST) diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index 689e3a21818..aa39fb03536 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -72,8 +72,7 @@ class VeraLight(VeraDevice, Light): last_tripped = self.vera_device.last_trip if last_tripped is not None: utc_time = dt_util.utc_from_timestamp(int(last_tripped)) - attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( - utc_time) + attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat() else: attr[ATTR_LAST_TRIP_TIME] = None tripped = self.vera_device.is_tripped diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index 98740388cd5..052f30bf83b 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -87,7 +87,7 @@ def _handle_get_logbook(handler, path_match, data): date_str = path_match.group('date') if date_str: - start_date = dt_util.date_str_to_date(date_str) + start_date = dt_util.parse_date(date_str) if start_date is None: handler.write_json_message("Error parsing JSON", HTTP_BAD_REQUEST) @@ -122,7 +122,7 @@ class Entry(object): def as_dict(self): """Convert entry to a dict to be used within JSON.""" return { - 'when': dt_util.datetime_to_str(self.when), + 'when': self.when, 'name': self.name, 'message': self.message, 'domain': self.domain, diff --git a/homeassistant/components/notify/file.py b/homeassistant/components/notify/file.py index b474f12be63..3d04bf13334 100644 --- a/homeassistant/components/notify/file.py +++ b/homeassistant/components/notify/file.py @@ -44,12 +44,12 @@ class FileNotificationService(BaseNotificationService): if os.stat(self.filepath).st_size == 0: title = '{} notifications (Log started: {})\n{}\n'.format( kwargs.get(ATTR_TITLE), - dt_util.strip_microseconds(dt_util.utcnow()), + dt_util.utcnow().isoformat(), '-' * 80) file.write(title) if self.add_timestamp == 1: - text = '{} {}\n'.format(dt_util.utcnow(), message) + text = '{} {}\n'.format(dt_util.utcnow().isoformat(), message) file.write(text) else: text = '{}\n'.format(message) diff --git a/homeassistant/components/recorder.py b/homeassistant/components/recorder.py index 05a95ee27b4..0a9fe92cde8 100644 --- a/homeassistant/components/recorder.py +++ b/homeassistant/components/recorder.py @@ -478,7 +478,7 @@ class Recorder(threading.Thread): def _adapt_datetime(datetimestamp): """Turn a datetime into an integer for in the DB.""" - return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp() + return dt_util.as_utc(datetimestamp).timestamp() def _verify_instance(): diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 2ef012094c0..2ca1992659b 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -23,6 +23,8 @@ ATTR_TARGET = 'Destination' ATTR_REMAINING_TIME = 'Remaining time' ICON = 'mdi:bus' +TIME_STR_FORMAT = "%H:%M" + # Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) @@ -126,10 +128,10 @@ class PublicTransportData(object): try: self.times = [ - dt_util.datetime_to_time_str( - dt_util.as_local(dt_util.utc_from_timestamp( - item['from']['departureTimestamp'])) - ) + dt_util.as_local( + dt_util.utc_from_timestamp( + item['from']['departureTimestamp'])).strftime( + TIME_STR_FORMAT) for item in connections ] self.times.append( diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/sensor/systemmonitor.py index 928e37f671d..0f2a60d6dd5 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/sensor/systemmonitor.py @@ -131,9 +131,9 @@ class SystemMonitorSensor(Entity): elif self.type == 'ipv6_address': self._state = psutil.net_if_addrs()[self.argument][1][1] elif self.type == 'last_boot': - self._state = dt_util.datetime_to_date_str( - dt_util.as_local( - dt_util.utc_from_timestamp(psutil.boot_time()))) + self._state = dt_util.as_local( + dt_util.utc_from_timestamp(psutil.boot_time()) + ).date().isoformat() elif self.type == 'since_last_boot': self._state = dt_util.utcnow() - dt_util.utc_from_timestamp( psutil.boot_time()) diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py index 5b1471a9464..e6e78fe8ff9 100644 --- a/homeassistant/components/sensor/time_date.py +++ b/homeassistant/components/sensor/time_date.py @@ -19,6 +19,8 @@ OPTION_TYPES = { 'time_utc': 'Time (UTC)', } +TIME_STR_FORMAT = "%H:%M" + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Time and Date sensor.""" @@ -70,9 +72,9 @@ class TimeDateSensor(Entity): def update(self): """Get the latest data and updates the states.""" time_date = dt_util.utcnow() - time = dt_util.datetime_to_time_str(dt_util.as_local(time_date)) - time_utc = dt_util.datetime_to_time_str(time_date) - date = dt_util.datetime_to_date_str(dt_util.as_local(time_date)) + time = dt_util.as_local(time_date).strftime(TIME_STR_FORMAT) + time_utc = time_date.strftime(TIME_STR_FORMAT) + date = dt_util.as_local(time_date).date().isoformat() # Calculate the beat (Swatch Internet Time) time without date. hours, minutes, seconds = time_date.strftime('%H:%M:%S').split(':') diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index 7cf7fed71ee..4f95ab5af32 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -65,8 +65,7 @@ class VeraSensor(VeraDevice, Entity): last_tripped = self.vera_device.last_trip if last_tripped is not None: utc_time = dt_util.utc_from_timestamp(int(last_tripped)) - attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( - utc_time) + attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat() else: attr[ATTR_LAST_TRIP_TIME] = None tripped = self.vera_device.is_tripped diff --git a/homeassistant/components/sensor/worldclock.py b/homeassistant/components/sensor/worldclock.py index 4b729604d7f..84a58dcd75a 100644 --- a/homeassistant/components/sensor/worldclock.py +++ b/homeassistant/components/sensor/worldclock.py @@ -12,6 +12,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Worldclock Sensor" ICON = 'mdi:clock' +TIME_STR_FORMAT = "%H:%M" def setup_platform(hass, config, add_devices, discovery_info=None): @@ -59,5 +60,5 @@ class WorldClockSensor(Entity): def update(self): """Get the time and updates the states.""" - self._state = dt_util.datetime_to_time_str( - dt_util.now(time_zone=self._time_zone)) + self._state = dt_util.now(time_zone=self._time_zone).strftime( + TIME_STR_FORMAT) diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 4e7a7be3451..1c73628398f 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -138,10 +138,8 @@ class YrSensor(Entity): # Find sensor for time_entry in self._weather.data['product']['time']: - valid_from = dt_util.str_to_datetime( - time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ") - valid_to = dt_util.str_to_datetime( - time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ") + valid_from = dt_util.parse_datetime(time_entry['@from']) + valid_to = dt_util.parse_datetime(time_entry['@to']) loc_data = time_entry['location'] @@ -204,5 +202,4 @@ class YrData(object): model = self.data['meta']['model'] if '@nextrun' not in model: model = model[0] - self._nextrun = dt_util.str_to_datetime(model['@nextrun'], - "%Y-%m-%dT%H:%M:%SZ") + self._nextrun = dt_util.parse_datetime(model['@nextrun']) diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index b33159f49e1..9e678ae0ebe 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -50,7 +50,7 @@ def next_setting_utc(hass, entity_id=None): state = hass.states.get(ENTITY_ID) try: - return dt_util.str_to_datetime( + return dt_util.parse_datetime( state.attributes[STATE_ATTR_NEXT_SETTING]) except (AttributeError, KeyError): # AttributeError if state is None @@ -72,8 +72,7 @@ def next_rising_utc(hass, entity_id=None): state = hass.states.get(ENTITY_ID) try: - return dt_util.str_to_datetime( - state.attributes[STATE_ATTR_NEXT_RISING]) + return dt_util.parse_datetime(state.attributes[STATE_ATTR_NEXT_RISING]) except (AttributeError, KeyError): # AttributeError if state is None # KeyError if STATE_ATTR_NEXT_RISING does not exist @@ -150,10 +149,8 @@ class Sun(Entity): def state_attributes(self): """Return the state attributes of the sun.""" return { - STATE_ATTR_NEXT_RISING: - dt_util.datetime_to_str(self.next_rising), - STATE_ATTR_NEXT_SETTING: - dt_util.datetime_to_str(self.next_setting), + STATE_ATTR_NEXT_RISING: self.next_rising.isoformat(), + STATE_ATTR_NEXT_SETTING: self.next_setting.isoformat(), STATE_ATTR_ELEVATION: round(self.solar_elevation, 2) } diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/switch/vera.py index 55d4b77b554..d71d064a3e8 100644 --- a/homeassistant/components/switch/vera.py +++ b/homeassistant/components/switch/vera.py @@ -50,8 +50,7 @@ class VeraSwitch(VeraDevice, SwitchDevice): last_tripped = self.vera_device.last_trip if last_tripped is not None: utc_time = dt_util.utc_from_timestamp(int(last_tripped)) - attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( - utc_time) + attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat() else: attr[ATTR_LAST_TRIP_TIME] = None tripped = self.vera_device.is_tripped diff --git a/homeassistant/core.py b/homeassistant/core.py index f82bfd7b244..88a06ac6156 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -156,8 +156,7 @@ class Event(object): self.event_type = event_type self.data = data or {} self.origin = origin - self.time_fired = dt_util.strip_microseconds( - time_fired or dt_util.utcnow()) + self.time_fired = time_fired or dt_util.utcnow() def as_dict(self): """Create a dict representation of this Event.""" @@ -165,7 +164,7 @@ class Event(object): 'event_type': self.event_type, 'data': dict(self.data), 'origin': str(self.origin), - 'time_fired': dt_util.datetime_to_str(self.time_fired), + 'time_fired': self.time_fired, } def __repr__(self): @@ -310,15 +309,9 @@ class State(object): self.entity_id = entity_id.lower() self.state = str(state) self.attributes = MappingProxyType(attributes or {}) - self.last_updated = dt_util.strip_microseconds( - last_updated or dt_util.utcnow()) + self.last_updated = last_updated or dt_util.utcnow() - # Strip microsecond from last_changed else we cannot guarantee - # state == State.from_dict(state.as_dict()) - # This behavior occurs because to_dict uses datetime_to_str - # which does not preserve microseconds - self.last_changed = dt_util.strip_microseconds( - last_changed or self.last_updated) + self.last_changed = last_changed or self.last_updated @property def domain(self): @@ -346,8 +339,8 @@ class State(object): return {'entity_id': self.entity_id, 'state': self.state, 'attributes': dict(self.attributes), - 'last_changed': dt_util.datetime_to_str(self.last_changed), - 'last_updated': dt_util.datetime_to_str(self.last_updated)} + 'last_changed': self.last_changed, + 'last_updated': self.last_updated} @classmethod def from_dict(cls, json_dict): @@ -361,13 +354,13 @@ class State(object): last_changed = json_dict.get('last_changed') - if last_changed: - last_changed = dt_util.str_to_datetime(last_changed) + if isinstance(last_changed, str): + last_changed = dt_util.parse_datetime(last_changed) last_updated = json_dict.get('last_updated') - if last_updated: - last_updated = dt_util.str_to_datetime(last_updated) + if isinstance(last_updated, str): + last_updated = dt_util.parse_datetime(last_updated) return cls(json_dict['entity_id'], json_dict['state'], json_dict.get('attributes'), last_changed, last_updated) @@ -386,7 +379,7 @@ class State(object): return "".format( self.entity_id, self.state, attr, - dt_util.datetime_to_local_str(self.last_changed)) + dt_util.as_local(self.last_changed).isoformat()) class StateMachine(object): @@ -819,6 +812,6 @@ def create_worker_pool(worker_count=None): for start, job in current_jobs: _LOGGER.warning("WorkerPool:Current job from %s: %s", - dt_util.datetime_to_local_str(start), job) + dt_util.as_local(start).isoformat(), job) return util.ThreadPool(job_handler, worker_count, busy_callback) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 83db4a194b6..1a3421cecaf 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -92,9 +92,8 @@ class TrackStates(object): def get_changed_since(states, utc_point_in_time): """Return list of states that have been changed since utc_point_in_time.""" - point_in_time = dt_util.strip_microseconds(utc_point_in_time) - - return [state for state in states if state.last_updated >= point_in_time] + return [state for state in states + if state.last_updated >= utc_point_in_time] def reproduce_state(hass, states, blocking=False): diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 8eb96f7511c..74d9a958355 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -7,6 +7,7 @@ HomeAssistantError will be raised. For more details about the Python API, please refer to the documentation at https://home-assistant.io/developers/python_api/ """ +from datetime import datetime import enum import json import logging @@ -277,7 +278,9 @@ class JSONEncoder(json.JSONEncoder): Hand other objects to the original method. """ - if hasattr(obj, 'as_dict'): + if isinstance(obj, datetime): + return obj.isoformat() + elif hasattr(obj, 'as_dict'): return obj.as_dict() try: diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 190f6ad340a..67719c18208 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -12,7 +12,7 @@ import string from functools import wraps from types import MappingProxyType -from .dt import datetime_to_local_str, utcnow +from .dt import as_local, utcnow RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)') @@ -43,7 +43,7 @@ def repr_helper(inp): repr_helper(key)+"="+repr_helper(item) for key, item in inp.items()) elif isinstance(inp, datetime): - return datetime_to_local_str(inp) + return as_local(inp).isoformat() else: return str(inp) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index ff4fd6e13bd..fbd962b3e45 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,14 +1,24 @@ """Provides helper methods to handle the time in HA.""" import datetime as dt +import re import pytz -DATETIME_STR_FORMAT = "%H:%M:%S %d-%m-%Y" DATE_STR_FORMAT = "%Y-%m-%d" -TIME_STR_FORMAT = "%H:%M" UTC = DEFAULT_TIME_ZONE = pytz.utc +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# https://github.com/django/django/blob/master/LICENSE +DATETIME_RE = re.compile( + r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' + r'[T ](?P\d{1,2}):(?P\d{1,2})' + r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' + r'(?PZ|[+-]\d{2}(?::?\d{2})?)?$' +) + + def set_default_time_zone(time_zone): """Set a default time zone to be used when none is specified.""" global DEFAULT_TIME_ZONE # pylint: disable=global-statement @@ -75,48 +85,39 @@ def start_of_local_day(dt_or_d=None): tzinfo=DEFAULT_TIME_ZONE) -def datetime_to_local_str(dattim): - """Convert datetime to specified time_zone and returns a string.""" - return datetime_to_str(as_local(dattim)) +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# https://github.com/django/django/blob/master/LICENSE +def parse_datetime(dt_str): + """Parse a string and return a datetime.datetime. - -def datetime_to_str(dattim): - """Convert datetime to a string format. - - @rtype : str + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + Raises ValueError if the input is well formatted but not a valid datetime. + Returns None if the input isn't well formatted. """ - return dattim.strftime(DATETIME_STR_FORMAT) - - -def datetime_to_time_str(dattim): - """Convert datetime to a string containing only the time. - - @rtype : str - """ - return dattim.strftime(TIME_STR_FORMAT) - - -def datetime_to_date_str(dattim): - """Convert datetime to a string containing only the date. - - @rtype : str - """ - return dattim.strftime(DATE_STR_FORMAT) - - -def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT): - """Convert a string to a UTC datetime object. - - @rtype: datetime - """ - try: - return dt.datetime.strptime( - dt_str, dt_format).replace(tzinfo=pytz.utc) - except ValueError: # If dt_str did not match our format + match = DATETIME_RE.match(dt_str) + if not match: return None + kws = match.groupdict() + if kws['microsecond']: + kws['microsecond'] = kws['microsecond'].ljust(6, '0') + tzinfo = kws.pop('tzinfo') + if tzinfo == 'Z': + tzinfo = UTC + elif tzinfo is not None: + offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 + offset_hours = int(tzinfo[1:3]) + offset = dt.timedelta(hours=offset_hours, minutes=offset_mins) + if tzinfo[0] == '-': + offset = -offset + tzinfo = dt.timezone(offset) + kws = {k: int(v) for k, v in kws.items() if v is not None} + kws['tzinfo'] = tzinfo + return dt.datetime(**kws) -def date_str_to_date(dt_str): +def parse_date(dt_str): """Convert a date string to a date object.""" try: return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date() @@ -124,12 +125,7 @@ def date_str_to_date(dt_str): return None -def strip_microseconds(dattim): - """Return a copy of dattime object but with microsecond set to 0.""" - return dattim.replace(microsecond=0) - - -def parse_time_str(time_str): +def parse_time(time_str): """Parse a time string (00:20:00) into Time object. Return None if invalid. diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 58d28e5cbb8..738c171ce6c 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -34,7 +34,7 @@ class TestAutomationSun(unittest.TestCase): def test_sunset_trigger(self): """Test the sunset trigger.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T02:00:00Z', }) now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) @@ -61,7 +61,7 @@ class TestAutomationSun(unittest.TestCase): def test_sunrise_trigger(self): """Test the sunrise trigger.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', }) now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) @@ -88,7 +88,7 @@ class TestAutomationSun(unittest.TestCase): def test_sunset_trigger_with_offset(self): """Test the sunset trigger with offset.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T02:00:00Z', }) now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) @@ -116,7 +116,7 @@ class TestAutomationSun(unittest.TestCase): def test_sunrise_trigger_with_offset(self): """Test the runrise trigger with offset.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', }) now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) @@ -144,7 +144,7 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_before(self): """Test if action was before.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', }) _setup_component(self.hass, automation.DOMAIN, { @@ -180,7 +180,7 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_after(self): """Test if action was after.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', }) _setup_component(self.hass, automation.DOMAIN, { @@ -216,7 +216,7 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_before_with_offset(self): """Test if action was before offset.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', }) _setup_component(self.hass, automation.DOMAIN, { @@ -253,7 +253,7 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_after_with_offset(self): """Test if action was after offset.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', }) _setup_component(self.hass, automation.DOMAIN, { @@ -290,8 +290,8 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_before_and_after_during(self): """Test if action was before and after during.""" self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '10:00:00 16-09-2015', - sun.STATE_ATTR_NEXT_SETTING: '15:00:00 16-09-2015', + sun.STATE_ATTR_NEXT_RISING: '2015-09-16T10:00:00Z', + sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T15:00:00Z', }) _setup_component(self.hass, automation.DOMAIN, { @@ -337,7 +337,7 @@ class TestAutomationSun(unittest.TestCase): import pytz self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_SETTING: '17:30:00 16-09-2015', + sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T17:30:00Z', }) _setup_component(self.hass, automation.DOMAIN, { diff --git a/tests/components/notify/test_file.py b/tests/components/notify/test_file.py index 00dc80f84f4..87d275b05f9 100644 --- a/tests/components/notify/test_file.py +++ b/tests/components/notify/test_file.py @@ -2,6 +2,7 @@ import os import unittest import tempfile +from unittest.mock import patch import homeassistant.components.notify as notify from homeassistant.components.notify import ( @@ -31,8 +32,11 @@ class TestNotifyFile(unittest.TestCase): } })) - def test_notify_file(self): + @patch('homeassistant.util.dt.utcnow') + def test_notify_file(self, mock_utcnow): """Test the notify file output.""" + mock_utcnow.return_value = dt_util.as_utc(dt_util.now()) + with tempfile.TemporaryDirectory() as tempdirname: filename = os.path.join(tempdirname, 'notify.txt') message = 'one, two, testing, testing' @@ -46,7 +50,7 @@ class TestNotifyFile(unittest.TestCase): })) title = '{} notifications (Log started: {})\n{}\n'.format( ATTR_TITLE_DEFAULT, - dt_util.strip_microseconds(dt_util.utcnow()), + dt_util.utcnow().isoformat(), '-' * 80) self.hass.services.call('notify', 'test', {'message': message}, diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index b7e95ad7eed..625d73858d1 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -68,7 +68,7 @@ class TestComponentHistory(unittest.TestCase): """Test humanify filter too frequent sensor values.""" entity_id = 'sensor.bla' - pointA = dt_util.strip_microseconds(dt_util.utcnow().replace(minute=2)) + pointA = dt_util.utcnow().replace(minute=2) pointB = pointA.replace(minute=5) pointC = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py index b4c36132444..ad576a1df7a 100644 --- a/tests/components/test_mqtt_eventstream.py +++ b/tests/components/test_mqtt_eventstream.py @@ -66,13 +66,13 @@ class TestMqttEventStream(unittest.TestCase): mock_sub.assert_called_with(self.hass, sub_topic, ANY) @patch('homeassistant.components.mqtt.publish') - @patch('homeassistant.core.dt_util.datetime_to_str') - def test_state_changed_event_sends_message(self, mock_datetime, mock_pub): + @patch('homeassistant.core.dt_util.utcnow') + def test_state_changed_event_sends_message(self, mock_utcnow, mock_pub): """"Test the sending of a new message if event changed.""" - now = '00:19:19 11-01-2016' + now = dt_util.as_utc(dt_util.now()) e_id = 'fake.entity' pub_topic = 'bar' - mock_datetime.return_value = now + mock_utcnow.return_value = now # Add the eventstream component for publishing events self.assertTrue(self.add_eventstream(pub_topic=pub_topic)) @@ -97,11 +97,11 @@ class TestMqttEventStream(unittest.TestCase): event = {} event['event_type'] = EVENT_STATE_CHANGED new_state = { - "last_updated": now, + "last_updated": now.isoformat(), "state": "on", "entity_id": e_id, "attributes": {}, - "last_changed": now + "last_changed": now.isoformat() } event['event_data'] = {"new_state": new_state, "entity_id": e_id} diff --git a/tests/components/test_recorder.py b/tests/components/test_recorder.py index 4326134ed84..69c4478b121 100644 --- a/tests/components/test_recorder.py +++ b/tests/components/test_recorder.py @@ -63,4 +63,16 @@ class TestRecorder(unittest.TestCase): db_events = recorder.query_events( 'SELECT * FROM events WHERE event_type = ?', (event_type, )) - self.assertEqual(events, db_events) + assert len(events) == 1 + assert len(db_events) == 1 + + event = events[0] + db_event = db_events[0] + + assert event.event_type == db_event.event_type + assert event.data == db_event.data + assert event.origin == db_event.origin + + # Recorder uses SQLite and stores datetimes as integer unix timestamps + assert event.time_fired.replace(microsecond=0) == \ + db_event.time_fired.replace(microsecond=0) diff --git a/tests/test_core.py b/tests/test_core.py index 2f1162863f7..b1f8c5ee86f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -128,7 +128,7 @@ class TestEvent(unittest.TestCase): 'event_type': event_type, 'data': data, 'origin': 'LOCAL', - 'time_fired': dt_util.datetime_to_str(now), + 'time_fired': now, } self.assertEqual(expected, event.as_dict()) @@ -225,13 +225,14 @@ class TestState(unittest.TestCase): def test_repr(self): """Test state.repr.""" - self.assertEqual("", + self.assertEqual("", str(ha.State( "happy.happy", "on", last_changed=datetime(1984, 12, 8, 12, 0, 0)))) self.assertEqual( - "", + "", str(ha.State("happy.happy", "on", {"brightness": 144}, datetime(1984, 12, 8, 12, 0, 0)))) diff --git a/tests/test_remote.py b/tests/test_remote.py index 216e428b202..45224b09c90 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -7,6 +7,7 @@ import homeassistant.bootstrap as bootstrap import homeassistant.remote as remote import homeassistant.components.http as http from homeassistant.const import HTTP_HEADER_HA_AUTH +import homeassistant.util.dt as dt_util from tests.common import get_test_instance_port, get_test_home_assistant @@ -194,6 +195,9 @@ class TestRemoteMethods(unittest.TestCase): # Default method raises TypeError if non HA object self.assertRaises(TypeError, ha_json_enc.default, 1) + now = dt_util.utcnow() + self.assertEqual(now.isoformat(), ha_json_enc.default(now)) + class TestRemoteClasses(unittest.TestCase): """Test the homeassistant.remote module.""" diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 0b6a51c9d76..c98359f3416 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -107,31 +107,16 @@ class TestDateUtil(unittest.TestCase): datetime(1986, 7, 9, tzinfo=dt_util.UTC), dt_util.utc_from_timestamp(521251200)) - def test_datetime_to_str(self): - """Test datetime_to_str.""" - self.assertEqual( - "12:00:00 09-07-1986", - dt_util.datetime_to_str(datetime(1986, 7, 9, 12, 0, 0))) + def test_parse_datetime_converts_correctly(self): + """Test parse_datetime converts strings.""" + assert \ + datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) == \ + dt_util.parse_datetime("1986-07-09T12:00:00Z") - def test_datetime_to_local_str(self): - """Test datetime_to_local_str.""" - self.assertEqual( - dt_util.datetime_to_str(dt_util.now()), - dt_util.datetime_to_local_str(dt_util.utcnow())) + utcnow = dt_util.utcnow() - def test_str_to_datetime_converts_correctly(self): - """Test str_to_datetime converts strings.""" - self.assertEqual( - datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC), - dt_util.str_to_datetime("12:00:00 09-07-1986")) + assert utcnow == dt_util.parse_datetime(utcnow.isoformat()) - def test_str_to_datetime_returns_none_for_incorrect_format(self): - """Test str_to_datetime returns None if incorrect format.""" - self.assertIsNone(dt_util.str_to_datetime("not a datetime string")) - - def test_strip_microseconds(self): - """Test the now method.""" - test_time = datetime(2015, 1, 1, microsecond=5000) - - self.assertNotEqual(0, test_time.microsecond) - self.assertEqual(0, dt_util.strip_microseconds(test_time).microsecond) + def test_parse_datetime_returns_none_for_incorrect_format(self): + """Test parse_datetime returns None if incorrect format.""" + self.assertIsNone(dt_util.parse_datetime("not a datetime string")) diff --git a/tests/util/test_init.py b/tests/util/test_init.py index ea78eb04de5..c8827f91c58 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -39,7 +39,7 @@ class TestUtil(unittest.TestCase): self.assertEqual("True", util.repr_helper(True)) self.assertEqual("test=1", util.repr_helper({"test": 1})) - self.assertEqual("12:00:00 09-07-1986", + self.assertEqual("1986-07-09T12:00:00+00:00", util.repr_helper(datetime(1986, 7, 9, 12, 0, 0))) def test_convert(self): From 1cfbeabaecf6feb6b5b467eabf9e3839095bdc47 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 16 Apr 2016 10:05:29 -0700 Subject: [PATCH 2/2] Update frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 21 +++++++++++-------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 6fa998f9002..f6073979733 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """DO NOT MODIFY. Auto-generated by build_frontend script.""" -VERSION = "91191cb375be56ce689a38edc782875f" +VERSION = "b9460e6bbf5549236815d94f1c1c98b9" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 14fd6ed8f73..0c9551d8d7c 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3206,7 +3206,7 @@ e.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceE color: var(--disabled-text-color); @apply(--paper-dropdown-menu-icon); - }