refac
This commit is contained in:
1 parent
2e8d92c7b1
commit
b3aead23da
1 file changed
+23
-8
@@ -66,7 +66,7 @@ def _resolve_tz(tz: str = None) -> Optional[ZoneInfo]:
|
|||||||
return None
|
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.
|
"""Parse RRULE with clock-aligned DTSTART for sub-daily frequencies.
|
||||||
|
|
||||||
MINUTELY/HOURLY rules use a fixed epoch DTSTART (2000-01-01 00:00)
|
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'):
|
if freq in ('MINUTELY', 'HOURLY'):
|
||||||
epoch = datetime(2000, 1, 1, 0, 0, 0)
|
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, dtstart=epoch, ignoretz=True)
|
||||||
return rrulestr(s, 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
|
clock so that near-future schedules are not incorrectly rejected
|
||||||
on servers whose system clock is ahead (e.g. UTC vs US timezones).
|
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)
|
zi = _resolve_tz(tz)
|
||||||
now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now()
|
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:
|
if rule.after(now) is None:
|
||||||
raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS)
|
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."""
|
"""Next occurrence as epoch nanoseconds, respecting user timezone."""
|
||||||
zi = _resolve_tz(tz)
|
zi = _resolve_tz(tz)
|
||||||
now = datetime.now(zi) if zi else datetime.now()
|
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:
|
if dt is None:
|
||||||
return None
|
return None
|
||||||
if zi:
|
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).
|
preview matches the user's local clock (same as next_run_ns).
|
||||||
"""
|
"""
|
||||||
zi = _resolve_tz(tz)
|
zi = _resolve_tz(tz)
|
||||||
rule = _parse_rule(s)
|
|
||||||
result = []
|
result = []
|
||||||
now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now()
|
now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now()
|
||||||
|
rule = _parse_rule(s, now)
|
||||||
dt = now
|
dt = now
|
||||||
for _ in range(n):
|
for _ in range(n):
|
||||||
dt = rule.after(dt)
|
dt = rule.after(dt)
|
||||||
@@ -142,8 +157,8 @@ def rrule_interval_seconds(s: str) -> Optional[int]:
|
|||||||
"""
|
"""
|
||||||
if 'COUNT=1' in s:
|
if 'COUNT=1' in s:
|
||||||
return None
|
return None
|
||||||
rule = _parse_rule(s)
|
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
rule = _parse_rule(s, now)
|
||||||
first = rule.after(now)
|
first = rule.after(now)
|
||||||
if first is None:
|
if first is None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in new issue
Block a user