From 9466020f1a846b1ea3e1b5979a101b4f03fb9260 Mon Sep 17 00:00:00 2001 From: Renaud <38732257+renaudjester@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:04:31 +0200 Subject: [PATCH] Use only timezones for datetime parser (#108) take into account the timezone when parsing datetime and getting the time from the computer --- conda_environment_test.yaml | 1 + copernicusmarine/catalogue_parser/models.py | 2 +- copernicusmarine/core_functions/utils.py | 32 ++++++++++++++----- .../test_help_command_interface.ambr | 4 +-- tests/test_utility_functions.py | 31 ++++++++++++++++++ 5 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 tests/test_utility_functions.py diff --git a/conda_environment_test.yaml b/conda_environment_test.yaml index a0ea96f8..7075f045 100644 --- a/conda_environment_test.yaml +++ b/conda_environment_test.yaml @@ -9,3 +9,4 @@ dependencies: - syrupy==4.6.1 - pip: - pytest-order==1.2.1 + - freezegun==1.5.1 diff --git a/copernicusmarine/catalogue_parser/models.py b/copernicusmarine/catalogue_parser/models.py index 1b377ed2..fc4dd33e 100644 --- a/copernicusmarine/catalogue_parser/models.py +++ b/copernicusmarine/catalogue_parser/models.py @@ -375,7 +375,7 @@ def sort_parts(self) -> tuple[Optional[str], Optional[str]]: for part in self.parts if part.retired_date } - max_retired_timestamp = 0 + max_retired_timestamp = 0.0 if will_be_retired_parts: max_retired_timestamp = max(will_be_retired_parts.values()) + 1 self.parts = sorted( diff --git a/copernicusmarine/core_functions/utils.py b/copernicusmarine/core_functions/utils.py index 0854e8e1..7cc94b08 100644 --- a/copernicusmarine/core_functions/utils.py +++ b/copernicusmarine/core_functions/utils.py @@ -2,7 +2,7 @@ import logging import pathlib import re -from datetime import datetime +from datetime import datetime, timezone from importlib.metadata import version from typing import ( Any, @@ -43,6 +43,13 @@ "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S.%fZ", + "%Y-%m-%d %H:%M:%S.%f%Z", +] + +DATETIME_NON_ISO_FORMATS = [ + "%Y", "%Y-%m-%dT%H:%M:%S.%fZ", ] @@ -122,14 +129,23 @@ class WrongDatetimeFormat(Exception): ... -def datetime_parser(string: str): +def datetime_parser(string: str) -> datetime: if string == "now": - return datetime.now() - for format in DATETIME_SUPPORTED_FORMATS: - try: - return datetime.strptime(string, format) - except ValueError: - pass + return datetime.now(tz=timezone.utc).replace(tzinfo=None) + try: + parsed_datetime = datetime.fromisoformat(string) + if parsed_datetime.tzinfo is None: + return parsed_datetime + else: + return parsed_datetime.astimezone(timezone.utc).replace( + tzinfo=None + ) + except ValueError: + for datetime_format in DATETIME_NON_ISO_FORMATS: + try: + return datetime.strptime(string, datetime_format) + except ValueError: + pass raise WrongDatetimeFormat(string) diff --git a/tests/__snapshots__/test_help_command_interface.ambr b/tests/__snapshots__/test_help_command_interface.ambr index 66ccbfb9..932a1fbc 100644 --- a/tests/__snapshots__/test_help_command_interface.ambr +++ b/tests/__snapshots__/test_help_command_interface.ambr @@ -253,12 +253,12 @@ ' z-axis) as it is in the dataset originally', ' produced, named `depth` with descending', ' positive values. [default: True]', - ' -t, --start-datetime [%Y|%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S|%Y-%m-%dT%H:%M:%S.%fZ]', + ' -t, --start-datetime [%Y|%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S|%Y-%m-%dT%H:%M:%S.%f|%Y-%m-%dT%H:%M:%S.%fZ|%Y-%m-%d %H:%M:%S.%f%Z]', ' The start datetime of the temporal subset.', ' Caution: encapsulate date with " " to ensure', ' valid expression for format "%Y-%m-%d', ' %H:%M:%S".', - ' -T, --end-datetime [%Y|%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S|%Y-%m-%dT%H:%M:%S.%fZ]', + ' -T, --end-datetime [%Y|%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S|%Y-%m-%dT%H:%M:%S.%f|%Y-%m-%dT%H:%M:%S.%fZ|%Y-%m-%d %H:%M:%S.%f%Z]', ' The end datetime of the temporal subset.', ' Caution: encapsulate date with " " to ensure', ' valid expression for format "%Y-%m-%d', diff --git a/tests/test_utility_functions.py b/tests/test_utility_functions.py new file mode 100644 index 00000000..6f867292 --- /dev/null +++ b/tests/test_utility_functions.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from freezegun import freeze_time + +from copernicusmarine.core_functions.utils import datetime_parser + + +class TestUtilityFunctions: + @freeze_time("2012-01-14 03:21:34", tz_offset=-2) + def test_datetime_parser(self): + # all parsed dates are in UTC + assert datetime_parser("now") == datetime(2012, 1, 14, 1, 21, 34) + assert datetime_parser("2012-01-14T03:21:34.000000+02:00") == datetime( + 2012, 1, 14, 1, 21, 34 + ) + + # All format are supported + assert datetime_parser("2012") == datetime(2012, 1, 1, 0, 0, 0) + assert datetime_parser("2012-01-14") == datetime(2012, 1, 14, 0, 0, 0) + assert datetime_parser("2012-01-14T03:21:34") == datetime( + 2012, 1, 14, 3, 21, 34 + ) + assert datetime_parser("2012-01-14 03:21:34") == datetime( + 2012, 1, 14, 3, 21, 34 + ) + assert datetime_parser("2012-01-14T03:21:34.000000") == datetime( + 2012, 1, 14, 3, 21, 34 + ) + assert datetime_parser("2012-01-14T03:21:34.000000Z") == datetime( + 2012, 1, 14, 3, 21, 34 + )