HKUDS/DeepTutor · error · ValueError
'at' schedules need a time
Error message
'at' schedules need a time
What it means
validate_schedule rejects 'at' (one-shot) schedules with no timestamp: at_ms is None, 0, or falsy. An 'at' job fires exactly once at a specific epoch-milliseconds time, so a missing time makes the schedule meaningless and fails at add_job time.
Source
Thrown at deeptutor/services/cron/service.py:155
base = datetime.fromtimestamp(now_ms / 1000, tz=tz)
next_dt = croniter(schedule.expr, base).get_next(datetime)
return int(next_dt.timestamp() * 1000)
except ImportError:
raise ValueError(
"cron expressions need the 'croniter' package — "
"use an 'every' or 'at' schedule instead"
) from None
except Exception as exc:
raise ValueError(f"invalid cron expression {schedule.expr!r}: {exc}") from None
return None
def validate_schedule(schedule: CronSchedule) -> None:
"""Reject schedules that could never run (raises ValueError)."""
if schedule.kind == "at":
if not schedule.at_ms:
raise ValueError("'at' schedules need a time")
if schedule.at_ms <= _now_ms():
raise ValueError("'at' time is in the past")
return
if schedule.kind == "every":
if not schedule.every_seconds or schedule.every_seconds < 30:
raise ValueError("'every' interval must be at least 30 seconds")
return
if schedule.kind == "cron":
if schedule.tz:
try:
from zoneinfo import ZoneInfo
ZoneInfo(schedule.tz)
except Exception:
raise ValueError(f"unknown timezone {schedule.tz!r}") from None
# Raises ValueError on bad/unsupported expressions.
if compute_next_run(schedule, _now_ms()) is None:
raise ValueError(f"cron expression {schedule.expr!r} never fires")View on GitHub (pinned to 3e82f13042)
Solutions
- Set at_ms to a future epoch-milliseconds timestamp (int(dt.timestamp() * 1000))
- Require the time field in the UI/API before submitting
- Skip/reject persisted records missing at_ms during migration instead of passing them through
Example fix
# before
sched = CronSchedule(kind="at") # at_ms=None -> ValueError
# after
from datetime import datetime, timedelta
sched = CronSchedule(
kind="at",
at_ms=int((datetime.now() + timedelta(hours=1)).timestamp() * 1000),
) Defensive patterns
Strategy: validation
Validate before calling
def valid_at_schedule(s: CronSchedule) -> bool:
return s.kind == "at" and bool(s.at_ms) Type guard
def is_complete_at_schedule(s: CronSchedule) -> bool:
return s.kind == "at" and isinstance(s.at_ms, int) and s.at_ms > 0 Try / catch
try:
svc.add_job(schedule, ...)
except ValueError as e:
if "need a time" in str(e):
return bad_request("at_ms is required for 'at' schedules")
raise Prevention
- Make at_ms a required constructor arg in your own schedule builders
- Reject persisted records missing at_ms during load
When it happens
Trigger: CronSchedule(kind="at") with at_ms omitted, None, or 0 passed to add_job/validate_schedule.
Common situations: Deserializing stored jobs where at_ms was dropped; schedule builders where the time field was optional and left blank; default-initialized dataclass without setting at_ms.
Related errors
- 'at' time is in the past
- invalid cron expression {schedule.expr!r}: {exc}
- 'every' interval must be at least 30 seconds
- unknown timezone {schedule.tz!r}
- cron expression {schedule.expr!r} never fires
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/75b69c234158b668.
Report an issue: GitHub.