Hmbown/CodeWhale · error · anyhow::Error
Failed to parse ONCE AT '{trimmed}'. Use local YYYY-MM-DDTHH
Error message
Failed to parse ONCE AT '{trimmed}'. Use local YYYY-MM-DDTHH:MM[:SS] or RFC3339 What it means
parse_once_at accepts exactly two shapes: RFC3339 with an explicit offset (DateTime::parse_from_rfc3339), or a naive local 'YYYY-MM-DDTHH:MM' / 'YYYY-MM-DDTHH:MM:SS' resolved in the machine's local timezone. Anything else — date-only, a space instead of 'T', fractional seconds, or slash formats — falls through to this bail. (A naive local time that falls in a DST gap yields the sibling 'ONCE local time does not exist' error.)
Source
Thrown at crates/tui/src/automation_manager.rs:639
.filter(|value| !value.is_empty())
.ok_or_else(|| anyhow::anyhow!("CRON schedules require EXPR"))?;
ParsedCronExpr::parse(&expr)?;
Ok(AutomationSchedule::Cron { expr })
}
fn parse_once_at(raw: &str) -> Result<DateTime<Utc>> {
let trimmed = raw.trim();
if let Ok(at) = DateTime::parse_from_rfc3339(trimmed) {
return Ok(at.with_timezone(&Utc));
}
for format in ["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M"] {
if let Ok(naive) = NaiveDateTime::parse_from_str(trimmed, format) {
return resolve_local_datetime(&Local, naive)
.map(|value| value.with_timezone(&Utc))
.ok_or_else(|| anyhow::anyhow!("ONCE local time does not exist: {trimmed}"));
}
}
bail!("Failed to parse ONCE AT '{trimmed}'. Use local YYYY-MM-DDTHH:MM[:SS] or RFC3339")
}
#[derive(Debug, Clone)]
struct ParsedCronExpr {
minute: CronField,
hour: CronField,
day_of_month: CronField,
month: CronField,
day_of_week: CronField,
}
impl ParsedCronExpr {
fn parse(expr: &str) -> Result<Self> {
let fields: Vec<&str> = expr.split_whitespace().collect();
if fields.len() != 5 {
bail!(
"CRON EXPR must have exactly 5 fields: minute hour day-of-month month day-of-week"
);View on GitHub (pinned to 0c42157ee5)
Solutions
- Emit the 'T' separator: 2026-08-03T14:30
- For unambiguous scheduling send RFC3339 with an offset, e.g. 2026-08-03T14:30:00Z or +02:00
- Normalize timestamps to RFC3339 client-side before create_automation
- For naive local input keep seconds optional but avoid sub-second precision
Example fix
// before rrule = "FREQ=ONCE;AT=2026-08-03 14:30" // after (unambiguous UTC) rrule = "FREQ=ONCE;AT=2026-08-03T14:30:00Z"
Defensive patterns
Strategy: validation
Validate before calling
fn parseable_once_at(raw: &str) -> bool {
let t = raw.trim();
if chrono::DateTime::parse_from_rfc3339(t).is_ok() {
return true;
}
["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M"]
.iter()
.any(|f| chrono::NaiveDateTime::parse_from_str(t, f).is_ok())
} Prevention
- Prefer RFC3339 with an explicit offset for machine-generated schedules
- Use a date/time picker that emits exactly the accepted formats
- Never pass shell date output unmodified
- Remember naive forms are interpreted in the machine's local timezone, not UTC
When it happens
Trigger: AT=2026-08-03 (date only); AT='2026-08-03 14:30' (space separator); AT=2026-08-03T14:30:30.5Z (fractional seconds); AT=03/08/2026.
Common situations: User-entered timestamps from forms; locales using slash or space formats; pasting output of the date command unmodified.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unsupported RRULE field '{key}' for ONCE. Allowed: FREQ,AT
- fire_at must be in the future (got {}, now is {})
- context_window must be greater than 0
- custom provider '{provider_id}' must set [providers.{provide
- unknown field '{field_key}' for built-in provider '{provider
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/4e85b66b7a7c3653.
Report an issue: GitHub.