"""日/周/月时间范围解析(Asia/Shanghai)。""" from __future__ import annotations from datetime import date, datetime, timedelta from zoneinfo import ZoneInfo _SH = ZoneInfo("Asia/Shanghai") def today_shanghai(now: datetime | None = None) -> str: n = (now or datetime.now(tz=_SH)).astimezone(_SH) return n.strftime("%Y-%m-%d") def parse_ymd(ymd: str) -> date: return datetime.strptime(ymd, "%Y-%m-%d").date() def day_bounds_ms(ymd: str) -> tuple[int, int]: """上海自然日 [start_ms, end_ms),end 为次日 00:00。""" d = parse_ymd(ymd) start = datetime(d.year, d.month, d.day, 0, 0, 0, tzinfo=_SH) end = start + timedelta(days=1) return int(start.timestamp() * 1000), int(end.timestamp() * 1000) def resolve_range( range_name: str, anchor_ymd: str | None = None, *, month_mode: str = "rolling_30", now: datetime | None = None, ) -> dict: """ 返回: anchor, start_ymd, end_ymd (含), start_ms, end_ms (半开区间), days """ anchor = anchor_ymd or today_shanghai(now) parse_ymd(anchor) # validate name = (range_name or "day").strip().lower() if name == "day": start_ymd = end_ymd = anchor days = 1 elif name == "week": end_ymd = anchor start = parse_ymd(anchor) - timedelta(days=6) start_ymd = start.strftime("%Y-%m-%d") days = 7 elif name == "month": end_ymd = anchor mode = (month_mode or "rolling_30").strip().lower() if mode in ("calendar", "natural", "natural_month"): d = parse_ymd(anchor) start_ymd = d.replace(day=1).strftime("%Y-%m-%d") days = (parse_ymd(end_ymd) - parse_ymd(start_ymd)).days + 1 else: # rolling_30 start = parse_ymd(anchor) - timedelta(days=29) start_ymd = start.strftime("%Y-%m-%d") days = 30 else: raise ValueError(f"invalid range: {range_name!r}") start_ms, _ = day_bounds_ms(start_ymd) _, end_ms = day_bounds_ms(end_ymd) return { "range": name, "anchor": anchor, "start_ymd": start_ymd, "end_ymd": end_ymd, "start_ms": start_ms, "end_ms": end_ms, "days": days, "month_mode": month_mode if name == "month" else None, }