From 46d73c9dcd4ff7afd6c0efc98fd42f5f18cef555 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 13:46:39 +0900 Subject: [PATCH] refac --- backend/open_webui/routers/automations.py | 4 ++-- backend/open_webui/tools/builtin.py | 4 ++-- backend/open_webui/utils/automations.py | 21 ++++++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/routers/automations.py b/backend/open_webui/routers/automations.py index ed33c4e8cb..4ff66feb97 100644 --- a/backend/open_webui/routers/automations.py +++ b/backend/open_webui/routers/automations.py @@ -163,7 +163,7 @@ async def create_new_automation( ): await check_automations_permission(request, user) try: - validate_rrule(form_data.data.rrule) + validate_rrule(form_data.data.rrule, tz=user.timezone) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -213,7 +213,7 @@ async def update_automation_by_id( check_automation_access(automation, user) try: - validate_rrule(form_data.data.rrule) + validate_rrule(form_data.data.rrule, tz=user.timezone) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index 9c1a91abc3..afa3cb63a9 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -2575,7 +2575,7 @@ async def create_automation( # Validate the RRULE try: - validate_rrule(rrule) + validate_rrule(rrule, tz=user.timezone) except ValueError as e: return json.dumps({'error': f'Invalid schedule: {e}'}) @@ -2656,7 +2656,7 @@ async def update_automation( # Validate RRULE if changed if rrule is not None: try: - validate_rrule(new_rrule) + validate_rrule(new_rrule, tz=user.timezone if user else None) except ValueError as e: return json.dumps({'error': f'Invalid schedule: {e}'}) diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 0c6e4e969a..984c8a0e4e 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -61,13 +61,19 @@ def _parse_rule(s: str): return rrulestr(s, ignoretz=True) -def validate_rrule(s: str) -> None: - """Raise ValueError if the RRULE is malformed or exhausted.""" +def validate_rrule(s: str, tz: str = None) -> None: + """Raise ValueError if the RRULE is malformed or exhausted. + + When *tz* is provided the "now" reference uses the user's local + clock so that near-future schedules are not incorrectly rejected + on servers whose system clock is ahead (e.g. UTC vs US timezones). + """ try: rule = _parse_rule(s) except Exception as e: raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e)) - if rule.after(datetime.now()) is None: + now = datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if tz else datetime.now() + if rule.after(now) is None: raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS) @@ -83,10 +89,15 @@ def next_run_ns(s: str, tz: str = None) -> Optional[int]: def next_n_runs_ns(s: str, n: int = 5, tz: str = None) -> list[int]: - """Compute next N occurrences for UI preview.""" + """Compute next N occurrences for UI preview. + + Uses the user's timezone for the starting "now" so that the + preview matches the user's local clock (same as next_run_ns). + """ rule = _parse_rule(s) result = [] - dt = datetime.now() + now = datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if tz else datetime.now() + dt = now for _ in range(n): dt = rule.after(dt) if not dt: