2016-03-07 22:20:48 +00:00
|
|
|
"""Provides helper methods to handle the time in HA."""
|
2015-04-29 02:12:05 +00:00
|
|
|
import datetime as dt
|
2016-04-16 07:55:35 +00:00
|
|
|
import re
|
2015-04-29 02:12:05 +00:00
|
|
|
|
|
|
|
import pytz
|
|
|
|
|
2015-06-15 05:56:55 +00:00
|
|
|
DATE_STR_FORMAT = "%Y-%m-%d"
|
2015-04-29 02:12:05 +00:00
|
|
|
UTC = DEFAULT_TIME_ZONE = pytz.utc
|
|
|
|
|
|
|
|
|
2016-04-16 07:55:35 +00:00
|
|
|
# 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<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
|
|
|
|
r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
|
|
|
|
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
|
|
|
|
r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$'
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2015-04-29 02:12:05 +00:00
|
|
|
def set_default_time_zone(time_zone):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Set a default time zone to be used when none is specified."""
|
2015-04-29 02:12:05 +00:00
|
|
|
global DEFAULT_TIME_ZONE # pylint: disable=global-statement
|
|
|
|
|
|
|
|
assert isinstance(time_zone, dt.tzinfo)
|
|
|
|
|
|
|
|
DEFAULT_TIME_ZONE = time_zone
|
|
|
|
|
|
|
|
|
|
|
|
def get_time_zone(time_zone_str):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Get time zone from string. Return None if unable to determine."""
|
2015-04-29 02:12:05 +00:00
|
|
|
try:
|
|
|
|
return pytz.timezone(time_zone_str)
|
|
|
|
except pytz.exceptions.UnknownTimeZoneError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def utcnow():
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Get now in UTC time."""
|
2015-06-15 05:56:55 +00:00
|
|
|
return dt.datetime.now(UTC)
|
2015-04-29 02:12:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def now(time_zone=None):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Get now in specified time zone."""
|
2015-04-29 05:38:43 +00:00
|
|
|
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
|
2015-04-29 02:12:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def as_utc(dattim):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Return a datetime as UTC time.
|
|
|
|
|
|
|
|
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE.
|
|
|
|
"""
|
2015-06-15 05:56:55 +00:00
|
|
|
if dattim.tzinfo == UTC:
|
2015-04-29 02:12:05 +00:00
|
|
|
return dattim
|
|
|
|
elif dattim.tzinfo is None:
|
2016-02-07 02:31:07 +00:00
|
|
|
dattim = DEFAULT_TIME_ZONE.localize(dattim)
|
2015-04-29 02:12:05 +00:00
|
|
|
|
2015-06-15 05:56:55 +00:00
|
|
|
return dattim.astimezone(UTC)
|
2015-04-29 02:12:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def as_local(dattim):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Convert a UTC datetime object to local time zone."""
|
2015-04-29 02:12:05 +00:00
|
|
|
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
|
|
|
return dattim
|
|
|
|
elif dattim.tzinfo is None:
|
2016-02-07 02:31:07 +00:00
|
|
|
dattim = UTC.localize(dattim)
|
2015-04-29 02:12:05 +00:00
|
|
|
|
|
|
|
return dattim.astimezone(DEFAULT_TIME_ZONE)
|
|
|
|
|
|
|
|
|
|
|
|
def utc_from_timestamp(timestamp):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Return a UTC time from a timestamp."""
|
2015-06-15 05:56:55 +00:00
|
|
|
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC)
|
2015-04-29 02:12:05 +00:00
|
|
|
|
|
|
|
|
2015-06-15 05:56:55 +00:00
|
|
|
def start_of_local_day(dt_or_d=None):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Return local datetime object of start of day from date or datetime."""
|
2015-06-15 05:56:55 +00:00
|
|
|
if dt_or_d is None:
|
|
|
|
dt_or_d = now().date()
|
|
|
|
elif isinstance(dt_or_d, dt.datetime):
|
|
|
|
dt_or_d = dt_or_d.date()
|
|
|
|
|
|
|
|
return dt.datetime.combine(dt_or_d, dt.time()).replace(
|
|
|
|
tzinfo=DEFAULT_TIME_ZONE)
|
|
|
|
|
|
|
|
|
2016-04-16 07:55:35 +00:00
|
|
|
# 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.
|
2015-04-29 02:12:05 +00:00
|
|
|
|
2016-04-16 07:55:35 +00:00
|
|
|
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.
|
2015-04-29 02:12:05 +00:00
|
|
|
"""
|
2016-04-16 07:55:35 +00:00
|
|
|
match = DATETIME_RE.match(dt_str)
|
|
|
|
if not match:
|
2015-06-15 05:56:55 +00:00
|
|
|
return None
|
2016-04-16 07:55:35 +00:00
|
|
|
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 parse_date(dt_str):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Convert a date string to a date object."""
|
2015-06-15 05:56:55 +00:00
|
|
|
try:
|
|
|
|
return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date()
|
2015-04-29 02:12:05 +00:00
|
|
|
except ValueError: # If dt_str did not match our format
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2016-04-16 07:55:35 +00:00
|
|
|
def parse_time(time_str):
|
2016-03-07 22:20:48 +00:00
|
|
|
"""Parse a time string (00:20:00) into Time object.
|
|
|
|
|
|
|
|
Return None if invalid.
|
2015-09-15 15:56:06 +00:00
|
|
|
"""
|
|
|
|
parts = str(time_str).split(':')
|
|
|
|
if len(parts) < 2:
|
|
|
|
return None
|
|
|
|
try:
|
|
|
|
hour = int(parts[0])
|
|
|
|
minute = int(parts[1])
|
|
|
|
second = int(parts[2]) if len(parts) > 2 else 0
|
|
|
|
return dt.time(hour, minute, second)
|
|
|
|
except ValueError:
|
|
|
|
# ValueError if value cannot be converted to an int or not in range
|
|
|
|
return None
|