From e9322766b389934ce2242cf4fc2dddd287a7fa59 Mon Sep 17 00:00:00 2001 From: ludeeus Date: Thu, 19 Oct 2023 03:45:52 +0000 Subject: [PATCH 1/2] Rewrite parse_date util to not rely on strptime --- hass_nabucasa/utils.py | 9 +++++-- tests/test_utils.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/test_utils.py diff --git a/hass_nabucasa/utils.py b/hass_nabucasa/utils.py index f4fc56f0d..c5d3741e1 100644 --- a/hass_nabucasa/utils.py +++ b/hass_nabucasa/utils.py @@ -3,12 +3,13 @@ import asyncio import datetime as dt +import re import logging import ssl from typing import Awaitable, Callable, TypeVar CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # noqa pylint: disable=invalid-name -DATE_STR_FORMAT = "%Y-%m-%d" +DATE_STR_FORMAT = re.compile(r"(20\d{2})-(0[1-9]|1[0,1,2])-(0[1-9]|[12][0-9]|3[01])") UTC = dt.timezone.utc @@ -24,8 +25,12 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime: def parse_date(dt_str: str) -> dt.date | None: """Convert a date string to a date object.""" + if (match := DATE_STR_FORMAT.match(dt_str)) is None: + return None + + [year, month, day] = [int(group) for group in match.groups()] try: - return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date() + return dt.datetime(year, month, day).date() except ValueError: # If dt_str did not match our format return None diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 000000000..ff3e36f70 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,54 @@ +"""Tests for hass_nabucaa utils.""" +import pytest + +from hass_nabucasa import utils + + +@pytest.mark.parametrize( + "input_str", + [ + "2020-02-30", + "2019-02-29", + "2021-04-31", + "2023-06-31", + "2018-09-31", + "2015-11-31", + "2022-02-30", + "2020-04-31", + "2021-06-31", + "2017-09-31", + "2019-04-31", + "2023-11-31", + "2020-06-31", + "2016-02-30", + "2021-11-31", + ], +) +def test_parse_date_with_invalid_dates(input_str): + """Test the parse_date util.""" + assert utils.parse_date(input_str) is None + + +@pytest.mark.parametrize( + "input_str", + [ + "2020-02-29", + "2019-03-15", + "2021-04-30", + "2023-06-15", + "2018-09-30", + "2015-12-25", + "2022-02-28", + "2020-07-04", + "2021-08-21", + "2017-10-31", + "2019-01-01", + "2023-11-30", + "2020-05-05", + "2016-12-12", + "2021-03-14", + ], +) +def test_parse_date_with_valid_dates(input_str): + """Test the parse_date util.""" + assert utils.parse_date(input_str) is not None From 200b95db17cc44fef83ac636ebfc3fbe7e21d525 Mon Sep 17 00:00:00 2001 From: ludeeus Date: Thu, 19 Oct 2023 07:34:54 +0000 Subject: [PATCH 2/2] use ciso8601 instead --- hass_nabucasa/utils.py | 10 +++------- pylintrc | 1 + setup.py | 1 + tests/test_utils.py | 2 ++ 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hass_nabucasa/utils.py b/hass_nabucasa/utils.py index c5d3741e1..5a7d7490b 100644 --- a/hass_nabucasa/utils.py +++ b/hass_nabucasa/utils.py @@ -3,13 +3,13 @@ import asyncio import datetime as dt -import re import logging import ssl from typing import Awaitable, Callable, TypeVar +import ciso8601 + CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # noqa pylint: disable=invalid-name -DATE_STR_FORMAT = re.compile(r"(20\d{2})-(0[1-9]|1[0,1,2])-(0[1-9]|[12][0-9]|3[01])") UTC = dt.timezone.utc @@ -25,12 +25,8 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime: def parse_date(dt_str: str) -> dt.date | None: """Convert a date string to a date object.""" - if (match := DATE_STR_FORMAT.match(dt_str)) is None: - return None - - [year, month, day] = [int(group) for group in match.groups()] try: - return dt.datetime(year, month, day).date() + return ciso8601.parse_datetime(dt_str).date() except ValueError: # If dt_str did not match our format return None diff --git a/pylintrc b/pylintrc index ae235d47b..34b1e96b5 100644 --- a/pylintrc +++ b/pylintrc @@ -17,6 +17,7 @@ ignore=tests_* good-names=id,i,j,k,ex,Run,_,fp,T,cb generated-members=botocore.errorfactory +extension-pkg-whitelist=ciso8601 disable= abstract-method, diff --git a/setup.py b/setup.py index 3317d22d2..cfb62b7d0 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ install_requires=[ "pycognito==2023.5.0", "snitun==0.36.2", + "ciso8601>=2.3.0", "acme==2.7.1", "cryptography>=2.8", "attrs>=19.3", diff --git a/tests/test_utils.py b/tests/test_utils.py index ff3e36f70..eeefae3b1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -22,6 +22,8 @@ "2020-06-31", "2016-02-30", "2021-11-31", + "invalid", + "2023/12/12", ], ) def test_parse_date_with_invalid_dates(input_str):