78 lines
2.4 KiB
Python
78 lines
2.4 KiB
Python
"""日/周/月时间范围解析(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,
|
||
}
|