Files
xiaobai-review/xiaobai-datahub/datahub/timeutil.py
T
2026-09-02 22:16:05 +08:00

75 lines
2.1 KiB
Python

from __future__ import annotations
from datetime import date, datetime, time, timedelta, timezone
from typing import Any
from zoneinfo import ZoneInfo
SHANGHAI = ZoneInfo("Asia/Shanghai")
def now_shanghai(clock: datetime | None = None) -> datetime:
if clock is not None:
if clock.tzinfo is None:
return clock.replace(tzinfo=SHANGHAI)
return clock.astimezone(SHANGHAI)
return datetime.now(SHANGHAI)
def isoformat(value: datetime | None = None) -> str:
current = now_shanghai(value)
return current.isoformat(timespec="seconds")
def yyyymmdd(value: date | datetime | str | None = None) -> str:
if value is None:
return now_shanghai().strftime("%Y%m%d")
if isinstance(value, str):
digits = value.replace("-", "")[:8]
if len(digits) != 8 or not digits.isdigit():
raise ValueError(f"invalid trade_date: {value}")
return digits
if isinstance(value, datetime):
return value.astimezone(SHANGHAI).strftime("%Y%m%d")
return value.strftime("%Y%m%d")
def parse_trade_date(value: str) -> date:
text = yyyymmdd(value)
return date(int(text[:4]), int(text[4:6]), int(text[6:8]))
def session_phase(clock: datetime | None, is_open_day: bool) -> str:
"""pre | intradaily | lunch | eod | closed"""
if not is_open_day:
return "closed"
current = now_shanghai(clock).time()
if current < time(9, 15):
return "pre"
if current < time(11, 30) or (time(13, 0) <= current <= time(15, 5)):
return "intraday"
if current < time(13, 0):
return "lunch"
if current <= time(23, 40):
return "eod"
return "closed"
def add_days(trade_date: str, days: int) -> str:
return (parse_trade_date(trade_date) + timedelta(days=days)).strftime("%Y%m%d")
def iter_yyyymmdd(start: str, end: str):
cursor = parse_trade_date(start)
last = parse_trade_date(end)
if cursor > last:
return
while cursor <= last:
yield cursor.strftime("%Y%m%d")
cursor += timedelta(days=1)
def utc_timestamp(value: Any) -> str:
if isinstance(value, datetime):
return isoformat(value)
return isoformat()