Files
market_intel/packages/domain/range.py
T
2026-08-01 10:33:19 +08:00

78 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""日/周/月时间范围解析(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,
}