From c4ec23048a9e4df0c5f9c690156205bd67b2bac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Fri, 17 Jul 2026 11:44:13 +0700 Subject: [PATCH] fix: accept datetime ranges in parse_date_range With period.dynamic: true and period.precision: datetime, running without --date crashed: compute_next_period() returns a datetime range (YYYY-MM-DDTHH:MM:SS), which parse_date_range() rejected with a fullmatch against YYYY-MM-DD only. parse_date_range now accepts YYYY-MM-DDTHH:MM:SS--YYYY-MM-DDTHH:MM:SS in addition to plain date ranges, returning datetime strings unchanged (pass-through, matching compute_next_period output). Mixed-precision ranges, invalid times and reversed ranges are still rejected with the existing error messages; date-range behavior is unchanged. Closes #59 --- redmine_reporter/cli.py | 15 +++++++++++---- tests/test_cli.py | 12 +++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/redmine_reporter/cli.py b/redmine_reporter/cli.py index 7709150..482187e 100644 --- a/redmine_reporter/cli.py +++ b/redmine_reporter/cli.py @@ -25,20 +25,27 @@ def parse_date_range(date_arg: str) -> tuple[str, str]: from_date, to_date = parts[0].strip(), parts[1].strip() date_pattern = r"\d{4}-\d{2}-\d{2}" - if not re.fullmatch(date_pattern, from_date) or not re.fullmatch( - date_pattern, to_date + datetime_pattern = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}" + if re.fullmatch(date_pattern, from_date) and re.fullmatch(date_pattern, to_date): + fmt = "%Y-%m-%d" + elif re.fullmatch(datetime_pattern, from_date) and re.fullmatch( + datetime_pattern, to_date ): + fmt = "%Y-%m-%dT%H:%M:%S" + else: raise ValueError("Date range must be in format YYYY-MM-DD--YYYY-MM-DD") try: - start = datetime.strptime(from_date, "%Y-%m-%d").date() - end = datetime.strptime(to_date, "%Y-%m-%d").date() + start = datetime.strptime(from_date, fmt) + end = datetime.strptime(to_date, fmt) except ValueError as e: raise ValueError("Date range contains invalid calendar date") from e if start > end: raise ValueError("Date range start must be less than or equal to end") + if fmt == "%Y-%m-%d": + return start.date().isoformat(), end.date().isoformat() return start.isoformat(), end.isoformat() diff --git a/tests/test_cli.py b/tests/test_cli.py index c1085be..b684cc2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -30,6 +30,10 @@ def _reset_config_overrides(): [ ("2026-01-01--2026-01-31", ("2026-01-01", "2026-01-31")), (" 2026-01-01 -- 2026-01-31 ", ("2026-01-01", "2026-01-31")), + ( + "2026-06-01T00:00:00--2026-06-30T23:59:59", + ("2026-06-01T00:00:00", "2026-06-30T23:59:59"), + ), ], ) def test_parse_date_range_valid(date_arg, expected): @@ -43,6 +47,9 @@ def test_parse_date_range_valid(date_arg, expected): "2026-1-01--2026-01-31", "2026-02-30--2026-03-01", "2026-02-01--2026-01-31", + "2026-06-01T25:00:00--2026-06-30T23:59:59", + "2026-06-01--2026-06-30T23:59:59", + "2026-06-30T23:59:59--2026-06-01T00:00:00", ], ) def test_parse_date_range_invalid(date_arg): @@ -1372,11 +1379,6 @@ class TestReportNoTimeIntegration: # --------------------------------------------------------------------------- -@pytest.mark.xfail( - strict=True, - reason="bug #59: parse_date_range rejects datetime range produced by " - "dynamic period with precision=datetime when --date is omitted", -) @mock.patch.dict(os.environ, VALID_ENV, clear=True) @mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time") def test_cli_dynamic_datetime_period_without_date(mock_fetch, tmp_path):