mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-20 13:47:15 +08:00
fix(scheduler): once tasks with tz-aware schedule never fire
This commit is contained in:
@@ -10,6 +10,19 @@ from croniter import croniter
|
|||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_naive_local(iso_str: str) -> datetime:
|
||||||
|
"""Parse an ISO datetime and coerce it to tz-naive local time.
|
||||||
|
|
||||||
|
The scheduler uses ``datetime.now()`` (tz-naive) for all comparisons,
|
||||||
|
so any persisted timestamp must be normalized to the same flavor —
|
||||||
|
otherwise comparing naive vs aware raises TypeError.
|
||||||
|
"""
|
||||||
|
dt = datetime.fromisoformat(iso_str)
|
||||||
|
if dt.tzinfo is not None:
|
||||||
|
dt = dt.astimezone().replace(tzinfo=None)
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
class SchedulerService:
|
class SchedulerService:
|
||||||
"""
|
"""
|
||||||
Background service that executes scheduled tasks
|
Background service that executes scheduled tasks
|
||||||
@@ -113,8 +126,8 @@ class SchedulerService:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
next_run = datetime.fromisoformat(next_run_str)
|
next_run = _parse_naive_local(next_run_str)
|
||||||
|
|
||||||
# Check if task is overdue (e.g., service restart)
|
# Check if task is overdue (e.g., service restart)
|
||||||
if next_run < now:
|
if next_run < now:
|
||||||
time_diff = (now - next_run).total_seconds()
|
time_diff = (now - next_run).total_seconds()
|
||||||
@@ -140,7 +153,11 @@ class SchedulerService:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return now >= next_run
|
return now >= next_run
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"[Scheduler] Failed to evaluate due-state for task "
|
||||||
|
f"{task.get('id')} (next_run_at={next_run_str!r}): {e}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _calculate_next_run(self, task: dict, from_time: datetime) -> Optional[datetime]:
|
def _calculate_next_run(self, task: dict, from_time: datetime) -> Optional[datetime]:
|
||||||
@@ -184,12 +201,14 @@ class SchedulerService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run_at = datetime.fromisoformat(run_at_str)
|
run_at = _parse_naive_local(run_at_str)
|
||||||
# Only return if in the future
|
|
||||||
if run_at > from_time:
|
if run_at > from_time:
|
||||||
return run_at
|
return run_at
|
||||||
except Exception:
|
except Exception as e:
|
||||||
pass
|
logger.error(
|
||||||
|
f"[Scheduler] Failed to parse once-task run_at "
|
||||||
|
f"{run_at_str!r}: {e}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -364,9 +364,12 @@ class SchedulerTool(BaseTool):
|
|||||||
logger.error(f"[SchedulerTool] Invalid relative time format: {schedule_value}")
|
logger.error(f"[SchedulerTool] Invalid relative time format: {schedule_value}")
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
# Absolute time in ISO format
|
# Absolute ISO time. Normalize to tz-naive local so it
|
||||||
datetime.fromisoformat(schedule_value)
|
# stays comparable with the scheduler's datetime.now().
|
||||||
return {"type": "once", "run_at": schedule_value}
|
parsed = datetime.fromisoformat(schedule_value)
|
||||||
|
if parsed.tzinfo is not None:
|
||||||
|
parsed = parsed.astimezone().replace(tzinfo=None)
|
||||||
|
return {"type": "once", "run_at": parsed.isoformat()}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[SchedulerTool] Invalid schedule: {e}")
|
logger.error(f"[SchedulerTool] Invalid schedule: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user