"""Canonical period helpers shared by analytics report pages.""" from __future__ import annotations from calendar import monthrange from datetime import date, datetime def parse_iso_date(value) -> date | None: if not value: return None if isinstance(value, datetime): return value.date() if isinstance(value, date): return value try: return datetime.strptime(str(value)[:10], "%Y-%m-%d").date() except (TypeError, ValueError): return None def parse_month(value) -> date | None: if not value: return None if isinstance(value, datetime): return value.date().replace(day=1) if isinstance(value, date): return value.replace(day=1) try: return datetime.strptime(str(value)[:7], "%Y-%m").date() except (TypeError, ValueError): return None def month_end(value) -> date | None: month_start = parse_month(value) if not month_start: return None return month_start.replace(day=monthrange(month_start.year, month_start.month)[1]) def make_analysis_period(start, end=None, *, mode="range", label=None) -> dict: start_date = parse_iso_date(start) end_date = parse_iso_date(end) if end else start_date if not start_date and end_date: start_date = end_date if not start_date or not end_date: return { "mode": mode, "label": label or "全部資料", "start_date": "", "end_date": "", "start_month": "", "end_month": "", "is_single_day": False, "is_single_month": False, } if start_date > end_date: start_date, end_date = end_date, start_date start_month = start_date.strftime("%Y-%m") end_month_value = end_date.strftime("%Y-%m") is_single_day = start_date == end_date is_single_month = start_month == end_month_value if not label: if is_single_day: label = start_date.strftime("%Y-%m-%d") elif is_single_month and start_date.day == 1 and end_date == month_end(start_date): label = start_date.strftime("%Y 年 %m 月") else: label = f"{start_date:%Y-%m-%d} 至 {end_date:%Y-%m-%d}" return { "mode": mode, "label": label, "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "start_month": start_month, "end_month": end_month_value, "is_single_day": is_single_day, "is_single_month": is_single_month, } def make_month_period(value, *, mode="month") -> dict: start = parse_month(value) if not start: return make_analysis_period(None, mode=mode) return make_analysis_period(start, month_end(start), mode=mode)