_date_utils.py 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import re
  2. from datetime import date, datetime, timedelta
  3. from zoneinfo import ZoneInfo
  4. SHANGHAI_TZ = ZoneInfo("Asia/Shanghai")
  5. _DATE_PARTITION_RE = re.compile(r"^\d{8}$")
  6. def today_shanghai_date() -> date:
  7. return datetime.now(SHANGHAI_TZ).date()
  8. def same_day_last_year(value: date) -> date:
  9. try:
  10. return value.replace(year=value.year - 1)
  11. except ValueError:
  12. return date(value.year - 1, 2, 28)
  13. def partition_yyyymmdd(value: date) -> str:
  14. return value.strftime("%Y%m%d")
  15. def parse_partition_date(value: str) -> date:
  16. text = value.strip()
  17. if not _DATE_PARTITION_RE.match(text):
  18. raise ValueError(f"日期须为 YYYYMMDD 格式,当前为 {value!r}")
  19. return datetime.strptime(text, "%Y%m%d").date()
  20. def resolve_solar_same_period_window(
  21. *,
  22. bizdate: str,
  23. period_days: int = 7,
  24. ) -> tuple[str, str, str]:
  25. """
  26. 返回 (bizdate, start_date, end_date)。
  27. - bizdate:当天 YYYYMMDD
  28. - start_date:bizdate 去年同期(闰年 2/29 → 去年 2/28)
  29. - end_date:start_date + (period_days - 1) 天;period_days=7 → +6 天
  30. """
  31. if period_days < 1:
  32. raise ValueError("period_days 须 >= 1")
  33. biz_dt = parse_partition_date(bizdate)
  34. start_dt = same_day_last_year(biz_dt)
  35. end_dt = start_dt + timedelta(days=period_days - 1)
  36. return (
  37. partition_yyyymmdd(biz_dt),
  38. partition_yyyymmdd(start_dt),
  39. partition_yyyymmdd(end_dt),
  40. )