56 lines
1.6 KiB
Python
56 lines
1.6 KiB
Python
"""统一时间工具:所有对外展示时间按当前时区输出。"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import datetime, time, timedelta
|
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
|
|
|
|
def get_timezone_name() -> str:
|
|
return os.getenv("TZ") or os.getenv("APP_TIMEZONE") or "Asia/Shanghai"
|
|
|
|
|
|
def get_timezone() -> ZoneInfo:
|
|
name = get_timezone_name()
|
|
try:
|
|
return ZoneInfo(name)
|
|
except ZoneInfoNotFoundError:
|
|
return ZoneInfo("Asia/Shanghai")
|
|
|
|
|
|
APP_TZ = get_timezone()
|
|
|
|
|
|
def now_tz() -> datetime:
|
|
"""当前应用时区时间。"""
|
|
return datetime.now(APP_TZ)
|
|
|
|
|
|
def to_app_tz(value: datetime) -> datetime:
|
|
"""把 datetime 转为应用时区;无 tzinfo 的值按应用时区本地时间处理。"""
|
|
if value.tzinfo is None:
|
|
return value.replace(tzinfo=APP_TZ)
|
|
return value.astimezone(APP_TZ)
|
|
|
|
|
|
def format_dt(value: datetime | None) -> str | None:
|
|
"""面向 API/前端的统一时间格式,包含时区偏移。"""
|
|
if value is None:
|
|
return None
|
|
return to_app_tz(value).isoformat(sep=" ", timespec="seconds")
|
|
|
|
|
|
def from_timestamp(ts: float) -> datetime:
|
|
return datetime.fromtimestamp(ts, APP_TZ)
|
|
|
|
|
|
def local_day_range(day: str | None = None) -> tuple[str, datetime, datetime]:
|
|
"""返回本地日范围。为了兼容当前 ORM/数据库字段,过滤条件返回 naive datetime。"""
|
|
if day:
|
|
target = datetime.strptime(day, "%Y-%m-%d").date()
|
|
else:
|
|
target = now_tz().date()
|
|
start = datetime.combine(target, time.min)
|
|
end = start + timedelta(days=1)
|
|
return target.strftime("%Y-%m-%d"), start, end
|