"""Shared from/to date-range parsing (Chapter 11: consistent across every chart/KPI endpoint; never defaults to "all time" per Chapter 03 rule 7). """ from __future__ import annotations from datetime import date, datetime, timedelta from flask import Request DEFAULT_RANGE_DAYS = 7 # within Ch11's stated "7 or 30 day" default allowance def parse_date_range(request: Request) -> tuple[date, date]: """Parse `?from=&to=` (ISO 8601 dates), defaulting to the last 7 days.""" to_raw = request.args.get("to") from_raw = request.args.get("from") to_date = date.fromisoformat(to_raw) if to_raw else date.today() from_date = date.fromisoformat(from_raw) if from_raw else to_date - timedelta(days=DEFAULT_RANGE_DAYS - 1) if from_date > to_date: from_date, to_date = to_date, from_date return from_date, to_date def day_bounds(from_date: date, to_date: date) -> tuple[datetime, datetime]: """Inclusive [from_date, to_date] -> half-open [start, end) datetime range. Shared by overview/queries.py, seo/queries.py, and security/queries.py (consolidated here rather than each keeping its own local copy). """ start = datetime.combine(from_date, datetime.min.time()) end = datetime.combine(to_date, datetime.min.time()) + timedelta(days=1) return start, end