first commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""日/周/月时间范围解析(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,
|
||||
}
|
||||
Reference in New Issue
Block a user