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
This commit is contained in:
Кокос Артем Николаевич
2026-07-17 11:44:13 +07:00
parent d135408f5e
commit c4ec23048a
2 changed files with 18 additions and 9 deletions

View File

@@ -25,20 +25,27 @@ def parse_date_range(date_arg: str) -> tuple[str, str]:
from_date, to_date = parts[0].strip(), parts[1].strip() from_date, to_date = parts[0].strip(), parts[1].strip()
date_pattern = r"\d{4}-\d{2}-\d{2}" date_pattern = r"\d{4}-\d{2}-\d{2}"
if not re.fullmatch(date_pattern, from_date) or not re.fullmatch( datetime_pattern = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}"
date_pattern, to_date 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") raise ValueError("Date range must be in format YYYY-MM-DD--YYYY-MM-DD")
try: try:
start = datetime.strptime(from_date, "%Y-%m-%d").date() start = datetime.strptime(from_date, fmt)
end = datetime.strptime(to_date, "%Y-%m-%d").date() end = datetime.strptime(to_date, fmt)
except ValueError as e: except ValueError as e:
raise ValueError("Date range contains invalid calendar date") from e raise ValueError("Date range contains invalid calendar date") from e
if start > end: if start > end:
raise ValueError("Date range start must be less than or equal to 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() return start.isoformat(), end.isoformat()

View File

@@ -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-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): 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-1-01--2026-01-31",
"2026-02-30--2026-03-01", "2026-02-30--2026-03-01",
"2026-02-01--2026-01-31", "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): 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.dict(os.environ, VALID_ENV, clear=True)
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time") @mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
def test_cli_dynamic_datetime_period_without_date(mock_fetch, tmp_path): def test_cli_dynamic_datetime_period_without_date(mock_fetch, tmp_path):