From 6bb7e1cf186fe41fed187e1e7d7c777d4e0978af Mon Sep 17 00:00:00 2001 From: "Mathias L. Baumann" Date: Thu, 9 Oct 2025 10:01:24 +0200 Subject: [PATCH] Fix: Use midnight UTC for date-only CLI timestamps instead of current time Date-only inputs now default to midnight UTC instead of using current time Signed-off-by: Mathias L. Baumann --- RELEASE_NOTES.md | 1 + src/frequenz/client/dispatch/_cli_types.py | 9 +++++++ tests/test_cli.py | 28 ++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8348b356..bf127a2d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -15,6 +15,7 @@ - `filter_queries` parameter supports text-based filtering on dispatch `id` and `type` fields - Query format: IDs are prefixed with `#` (e.g., `#4`), types are matched as substrings (e.g., `bar` matches `foobar`) - Multiple queries are combined with logical OR +* Date-only inputs in CLI timestamps now default to midnight UTC instead of using the current time. ## Bug Fixes diff --git a/src/frequenz/client/dispatch/_cli_types.py b/src/frequenz/client/dispatch/_cli_types.py index 276d544b..5ae8b7d7 100644 --- a/src/frequenz/client/dispatch/_cli_types.py +++ b/src/frequenz/client/dispatch/_cli_types.py @@ -57,6 +57,15 @@ def convert( if parse_status == 0: self.fail(f"Invalid time expression: {value}", param, ctx) + # Check if only a date was provided (no time component) + # parsedatetime returns status 1 for date-only parsing + if parse_status == 1: + # Set time to midnight in UTC for date-only inputs + # First convert to UTC, then set time to midnight + parsed_dt = parsed_dt.astimezone(timezone.utc).replace( + hour=0, minute=0, second=0, microsecond=0 + ) + return cast(datetime, parsed_dt.astimezone(timezone.utc)) except Exception as e: # pylint: disable=broad-except self.fail( diff --git a/tests/test_cli.py b/tests/test_cli.py index 185ff9b1..ec6bf24c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -17,6 +17,7 @@ ElectricalComponentCategory, ) from frequenz.client.dispatch.__main__ import cli +from frequenz.client.dispatch._cli_types import FuzzyDateTime from frequenz.client.dispatch.recurrence import ( EndCriteria, Frequency, @@ -739,3 +740,30 @@ async def test_delete_command( assert expected_output in result.output if dispatches: assert len(fake_client.dispatches(MicrogridId(1))) == 0 + + +def test_fuzzy_datetime_date_only() -> None: + """Test that date-only inputs are parsed as midnight.""" + fuzzy_dt = FuzzyDateTime() + + # Test date-only input + result = fuzzy_dt.convert("2025-08-06", None, None) + assert isinstance(result, datetime) + # Check that time is set to midnight in UTC (accounting for timezone conversion) + # For Europe/Berlin (UTC+2), midnight local time becomes 22:00 UTC previous day + assert result.hour in [ + 0, + 22, + ] # Could be 0 (UTC) or 22 (UTC for Europe/Berlin midnight) + assert result.minute == 0 + assert result.second == 0 + assert result.microsecond == 0 + + # Test date-time input (should preserve time) + result_with_time = fuzzy_dt.convert("2025-08-06 14:30:15", None, None) + assert isinstance(result_with_time, datetime) + # Time should be preserved (accounting for timezone conversion) + # For Europe/Berlin (UTC+2), 14:30 local becomes 12:30 UTC + assert result_with_time.hour in [12, 14] # Could be 12 (UTC) or 14 (local) + assert result_with_time.minute == 30 + assert result_with_time.second == 15