"""Asia/Shanghai 日切与时段桶。时间戳一律 UTC ms 入,上海出。""" from __future__ import annotations from datetime import datetime, timedelta from zoneinfo import ZoneInfo _SH = ZoneInfo("Asia/Shanghai") def _to_shanghai(ts_ms: int) -> datetime: return datetime.fromtimestamp(int(ts_ms) / 1000.0, tz=_SH) def shanghai_day(ts_ms: int) -> str: """上海自然日 YYYY-MM-DD。""" return _to_shanghai(ts_ms).strftime("%Y-%m-%d") def shanghai_bucket(ts_ms: int, bucket_minutes: int = 60) -> int: """ 日内时段桶起点(分钟,0–1439)。 默认 60 → 0,60,120,...,1380(即小时 0–23)。 """ if bucket_minutes <= 0: raise ValueError("bucket_minutes must be > 0") dt = _to_shanghai(ts_ms) minutes = dt.hour * 60 + dt.minute return (minutes // bucket_minutes) * bucket_minutes def shanghai_bucket_hour(ts_ms: int) -> int: """0–23 小时桶。""" return shanghai_bucket(ts_ms, 60) // 60 def rolling_day_start(anchor_ymd: str, days: int) -> str: """锚点日(含)往前 days-1 天的起始 YYYY-MM-DD。""" d = datetime.strptime(anchor_ymd, "%Y-%m-%d").date() start = d - timedelta(days=max(0, days - 1)) return start.strftime("%Y-%m-%d")