refac
This commit is contained in:
@@ -66,7 +66,7 @@ def _resolve_tz(tz: str = None) -> Optional[ZoneInfo]:
|
||||
return None
|
||||
|
||||
|
||||
def _parse_rule(s: str):
|
||||
def _parse_rule(s: str, now: Optional[datetime] = None):
|
||||
"""Parse RRULE with clock-aligned DTSTART for sub-daily frequencies.
|
||||
|
||||
MINUTELY/HOURLY rules use a fixed epoch DTSTART (2000-01-01 00:00)
|
||||
@@ -78,6 +78,20 @@ def _parse_rule(s: str):
|
||||
|
||||
if freq in ('MINUTELY', 'HOURLY'):
|
||||
epoch = datetime(2000, 1, 1, 0, 0, 0)
|
||||
if (
|
||||
now is not None
|
||||
and s.startswith('RRULE:')
|
||||
and '\n' not in s
|
||||
and '\r' not in s
|
||||
and set(parts) <= {'FREQ', 'INTERVAL', 'BYMINUTE', 'BYSECOND'}
|
||||
):
|
||||
try:
|
||||
interval = int(parts.get('INTERVAL', '1'))
|
||||
if interval > 0:
|
||||
step = timedelta(minutes=interval) if freq == 'MINUTELY' else timedelta(hours=interval)
|
||||
return rrulestr(s, dtstart=epoch + ((now - epoch) // step) * step, ignoretz=True)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return rrulestr(s, dtstart=epoch, ignoretz=True)
|
||||
return rrulestr(s, ignoretz=True)
|
||||
|
||||
@@ -89,12 +103,12 @@ def validate_rrule(s: str, tz: str = None) -> None:
|
||||
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))
|
||||
zi = _resolve_tz(tz)
|
||||
now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now()
|
||||
try:
|
||||
rule = _parse_rule(s, now)
|
||||
except Exception as e:
|
||||
raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e))
|
||||
if rule.after(now) is None:
|
||||
raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS)
|
||||
|
||||
@@ -103,7 +117,8 @@ def next_run_ns(s: str, tz: str = None) -> Optional[int]:
|
||||
"""Next occurrence as epoch nanoseconds, respecting user timezone."""
|
||||
zi = _resolve_tz(tz)
|
||||
now = datetime.now(zi) if zi else datetime.now()
|
||||
dt = _parse_rule(s).after(now.replace(tzinfo=None))
|
||||
now_naive = now.replace(tzinfo=None)
|
||||
dt = _parse_rule(s, now_naive).after(now_naive)
|
||||
if dt is None:
|
||||
return None
|
||||
if zi:
|
||||
@@ -118,9 +133,9 @@ def next_n_runs_ns(s: str, n: int = 5, tz: str = None) -> list[int]:
|
||||
preview matches the user's local clock (same as next_run_ns).
|
||||
"""
|
||||
zi = _resolve_tz(tz)
|
||||
rule = _parse_rule(s)
|
||||
result = []
|
||||
now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now()
|
||||
rule = _parse_rule(s, now)
|
||||
dt = now
|
||||
for _ in range(n):
|
||||
dt = rule.after(dt)
|
||||
@@ -142,8 +157,8 @@ def rrule_interval_seconds(s: str) -> Optional[int]:
|
||||
"""
|
||||
if 'COUNT=1' in s:
|
||||
return None
|
||||
rule = _parse_rule(s)
|
||||
now = datetime.now()
|
||||
rule = _parse_rule(s, now)
|
||||
first = rule.after(now)
|
||||
if first is None:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user