后端统一时区为 UTC
This commit is contained in:
+60
-49
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from dateutil import parser, tz
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
|
||||
from dateutil import parser, tz
|
||||
|
||||
'''
|
||||
2025-02-09T15:45:00+00:00 采用的是 ISO 8601 国际标准日期时间格式,具体特点如下:
|
||||
@@ -13,57 +14,67 @@ from dateutil import parser, tz
|
||||
2025-02-09T15:45:00+08:00
|
||||
|
||||
'''
|
||||
BG_TZ = tz.gettz('Asia/Shanghai')
|
||||
UTC_TZ = tz.gettz('UTC')
|
||||
BG_TZ = tz.gettz("Asia/Shanghai")
|
||||
UTC_TZ = timezone.utc
|
||||
|
||||
def parse_utc_time(query_time: str) -> datetime:
|
||||
'''
|
||||
接受 任意格式的字符串,如果解析出来不带时区,则用 replace 添加 +00:00 时区
|
||||
如果解析出来已经有时区,则用 astimezone 转换成UTC时间
|
||||
'''
|
||||
TIMEZONE_REQUIRED_MESSAGE = (
|
||||
"Datetime values must include an explicit timezone offset, for example "
|
||||
"'2025-02-09T15:45:00Z' or '2025-02-09T23:45:00+08:00'."
|
||||
)
|
||||
|
||||
# 解析时间字符串
|
||||
dt: datetime = parser.parse(query_time)
|
||||
|
||||
def parse_aware_time(query_time: datetime | str, field_name: str = "datetime") -> datetime:
|
||||
"""
|
||||
解析时间并确保结果带有时区信息。
|
||||
"""
|
||||
dt = parser.parse(query_time) if isinstance(query_time, str) else query_time
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=UTC_TZ)
|
||||
else:
|
||||
dt = dt.astimezone(UTC_TZ)
|
||||
|
||||
raise ValueError(f"{field_name} is missing timezone information. {TIMEZONE_REQUIRED_MESSAGE}")
|
||||
return dt
|
||||
|
||||
|
||||
def extract_date(value: date | datetime | str, field_name: str = "date") -> date:
|
||||
"""
|
||||
提取日期部分,但保留调用方原始时区语义,不强制转换到 UTC。
|
||||
"""
|
||||
if isinstance(value, date) and not isinstance(value, datetime):
|
||||
return value
|
||||
return parse_aware_time(value, field_name=field_name).date()
|
||||
|
||||
|
||||
def utc_now() -> datetime:
|
||||
"""
|
||||
返回带 UTC 时区的当前时间。
|
||||
"""
|
||||
return datetime.now(UTC_TZ)
|
||||
|
||||
|
||||
def parse_utc_time(query_time: datetime | str, field_name: str = "datetime") -> datetime:
|
||||
'''
|
||||
接受带时区的时间字符串/对象,并统一转换成 UTC 时间。
|
||||
'''
|
||||
return parse_aware_time(query_time, field_name=field_name).astimezone(UTC_TZ)
|
||||
|
||||
def parse_beijing_time(query_time: str) -> datetime:
|
||||
|
||||
def parse_beijing_time(query_time: datetime | str, field_name: str = "datetime") -> datetime:
|
||||
'''
|
||||
接受 任意格式的字符串,如果解析出来不带时区,则用 replace 添加 +08:00 时区
|
||||
如果解析出来已经有时区,则用 astimezone 转换成北京时间
|
||||
|
||||
也就是任意合法的时间字符串,最后都解析成 北京 时间
|
||||
|
||||
接受带时区的时间字符串/对象,并统一转换成北京时间。
|
||||
'''
|
||||
|
||||
# 解析时间字符串
|
||||
dt: datetime = parser.parse(query_time)
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=BG_TZ)
|
||||
else:
|
||||
dt = dt.astimezone(tz=BG_TZ)
|
||||
|
||||
return dt
|
||||
return parse_aware_time(query_time, field_name=field_name).astimezone(tz=BG_TZ)
|
||||
|
||||
|
||||
def to_utc_time(dt: datetime) -> datetime:
|
||||
def to_utc_time(dt: datetime | str, field_name: str = "datetime") -> datetime:
|
||||
'''
|
||||
将一个北京时间的时间点,转换成utc
|
||||
将一个带时区的时间点,转换成 UTC。
|
||||
'''
|
||||
utc_time = dt.astimezone(UTC_TZ)
|
||||
return utc_time
|
||||
return parse_aware_time(dt, field_name=field_name).astimezone(UTC_TZ)
|
||||
|
||||
|
||||
def to_beijing_time(dt: datetime) -> datetime:
|
||||
def to_beijing_time(dt: datetime | str, field_name: str = "datetime") -> datetime:
|
||||
'''
|
||||
将一个 utc 的时间点,转换成北京时间
|
||||
将一个带时区的时间点,转换成北京时间。
|
||||
'''
|
||||
beijing_time = dt.astimezone(tz=BG_TZ)
|
||||
return beijing_time
|
||||
return parse_aware_time(dt, field_name=field_name).astimezone(tz=BG_TZ)
|
||||
|
||||
|
||||
def to_time_range(dt: datetime, delta: float) -> tuple[datetime, datetime]:
|
||||
@@ -83,7 +94,8 @@ def parse_beijing_date_range(query_date: str) -> tuple[datetime, datetime]:
|
||||
将一个日期字符串,转换成 start/end 时间段,传进来的日期被认为是北京时间
|
||||
日期字符串格式:YYYY-MM-DD
|
||||
'''
|
||||
start_time = parse_beijing_time(query_date)
|
||||
target_date = date.fromisoformat(query_date)
|
||||
start_time = datetime.combine(target_date, time.min, BG_TZ)
|
||||
end_time = start_time + timedelta(days=1)
|
||||
|
||||
return (start_time, end_time)
|
||||
@@ -108,7 +120,7 @@ def get_date_from_time(time: str) -> str:
|
||||
'''
|
||||
将一个时间点,转换成日期
|
||||
'''
|
||||
dt = parse_beijing_time(time)
|
||||
dt = parse_beijing_time(time, field_name="time")
|
||||
return str(dt.date())
|
||||
|
||||
|
||||
@@ -116,28 +128,27 @@ def is_today(query_date: str) -> bool:
|
||||
'''
|
||||
判断一个日期是否是今天
|
||||
'''
|
||||
dt = parse_beijing_time(query_date)
|
||||
return dt.date() == datetime.now().date()
|
||||
dt = parse_beijing_time(query_date, field_name="query_date")
|
||||
return dt.date() == datetime.now(BG_TZ).date()
|
||||
|
||||
|
||||
def is_yesterday(query_date: str) -> bool:
|
||||
'''
|
||||
判断一个日期是否是昨天
|
||||
'''
|
||||
dt = parse_beijing_time(query_date)
|
||||
return dt.date() == (datetime.now().date() - timedelta(days=1))
|
||||
dt = parse_beijing_time(query_date, field_name="query_date")
|
||||
return dt.date() == (datetime.now(BG_TZ).date() - timedelta(days=1))
|
||||
|
||||
def is_tomorrow(query_date: str) -> bool:
|
||||
'''
|
||||
判断一个日期是否是明天
|
||||
'''
|
||||
dt = parse_beijing_time(query_date)
|
||||
return dt.date() == (datetime.now().date() + timedelta(days=1))
|
||||
dt = parse_beijing_time(query_date, field_name="query_date")
|
||||
return dt.date() == (datetime.now(BG_TZ).date() + timedelta(days=1))
|
||||
|
||||
def is_today_or_future(query_date: str) -> bool:
|
||||
'''
|
||||
判断一个日期是否是今天或未来
|
||||
'''
|
||||
dt = parse_beijing_time(query_date)
|
||||
return dt.date() >= datetime.now().date()
|
||||
|
||||
dt = parse_beijing_time(query_date, field_name="query_date")
|
||||
return dt.date() >= datetime.now(BG_TZ).date()
|
||||
|
||||
Reference in New Issue
Block a user