mirror of
https://github.com/langgenius/dify.git
synced 2025-11-24 00:42:41 +00:00
Signed-off-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: Stream <Stream_2@qq.com> Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do> Co-authored-by: Harry <xh001x@hotmail.com> Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: yessenia <yessenia.contact@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: WTW0313 <twwu@dify.ai> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
import abc
|
|
import datetime
|
|
from typing import Protocol
|
|
|
|
import pytz
|
|
|
|
|
|
class _NowFunction(Protocol):
|
|
@abc.abstractmethod
|
|
def __call__(self, tz: datetime.timezone | None) -> datetime.datetime:
|
|
pass
|
|
|
|
|
|
# _now_func is a callable with the _NowFunction signature.
|
|
# Its sole purpose is to abstract time retrieval, enabling
|
|
# developers to mock this behavior in tests and time-dependent scenarios.
|
|
_now_func: _NowFunction = datetime.datetime.now
|
|
|
|
|
|
def naive_utc_now() -> datetime.datetime:
|
|
"""Return a naive datetime object (without timezone information)
|
|
representing current UTC time.
|
|
"""
|
|
return _now_func(datetime.UTC).replace(tzinfo=None)
|
|
|
|
|
|
def ensure_naive_utc(dt: datetime.datetime) -> datetime.datetime:
|
|
"""Return the datetime as naive UTC (tzinfo=None).
|
|
|
|
If the input is timezone-aware, convert to UTC and drop the tzinfo.
|
|
Assumes naive datetimes are already expressed in UTC.
|
|
"""
|
|
if dt.tzinfo is None:
|
|
return dt
|
|
return dt.astimezone(datetime.UTC).replace(tzinfo=None)
|
|
|
|
|
|
def parse_time_range(
|
|
start: str | None, end: str | None, tzname: str
|
|
) -> tuple[datetime.datetime | None, datetime.datetime | None]:
|
|
"""
|
|
Parse time range strings and convert to UTC datetime objects.
|
|
Handles DST ambiguity and non-existent times gracefully.
|
|
|
|
Args:
|
|
start: Start time string (YYYY-MM-DD HH:MM)
|
|
end: End time string (YYYY-MM-DD HH:MM)
|
|
tzname: Timezone name
|
|
|
|
Returns:
|
|
tuple: (start_datetime_utc, end_datetime_utc)
|
|
|
|
Raises:
|
|
ValueError: When time range is invalid or start > end
|
|
"""
|
|
tz = pytz.timezone(tzname)
|
|
utc = pytz.utc
|
|
|
|
def _parse(time_str: str | None, label: str) -> datetime.datetime | None:
|
|
if not time_str:
|
|
return None
|
|
|
|
try:
|
|
dt = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M").replace(second=0)
|
|
except ValueError as e:
|
|
raise ValueError(f"Invalid {label} time format: {e}")
|
|
|
|
try:
|
|
return tz.localize(dt, is_dst=None).astimezone(utc)
|
|
except pytz.AmbiguousTimeError:
|
|
return tz.localize(dt, is_dst=False).astimezone(utc)
|
|
except pytz.NonExistentTimeError:
|
|
dt += datetime.timedelta(hours=1)
|
|
return tz.localize(dt, is_dst=None).astimezone(utc)
|
|
|
|
start_dt = _parse(start, "start")
|
|
end_dt = _parse(end, "end")
|
|
|
|
# Range validation
|
|
if start_dt and end_dt and start_dt > end_dt:
|
|
raise ValueError("start must be earlier than or equal to end")
|
|
|
|
return start_dt, end_dt
|