{"record":{"id":"4b2b45a985ecaa41","repo":"Hmbown/CodeWhale","slug":"once-local-time-does-not-exist-trimmed","errorCode":null,"errorMessage":"ONCE local time does not exist: {trimmed}","messagePattern":"ONCE local time does not exist: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":631,"sourceCode":"    let expr = parts\n        .get(\"EXPR\")\n        .map(|value| value.trim().to_string())\n        .filter(|value| !value.is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"CRON schedules require EXPR\"))?;\n    ParsedCronExpr::parse(&expr)?;\n    Ok(AutomationSchedule::Cron { expr })\n}\n\nfn parse_once_at(raw: &str) -> Result<DateTime<Utc>> {\n    let trimmed = raw.trim();\n    if let Ok(at) = DateTime::parse_from_rfc3339(trimmed) {\n        return Ok(at.with_timezone(&Utc));\n    }\n    for format in [\"%Y-%m-%dT%H:%M:%S\", \"%Y-%m-%dT%H:%M\"] {\n        if let Ok(naive) = NaiveDateTime::parse_from_str(trimmed, format) {\n            return resolve_local_datetime(&Local, naive)\n                .map(|value| value.with_timezone(&Utc))\n                .ok_or_else(|| anyhow::anyhow!(\"ONCE local time does not exist: {trimmed}\"));\n        }\n    }\n    bail!(\"Failed to parse ONCE AT '{trimmed}'. Use local YYYY-MM-DDTHH:MM[:SS] or RFC3339\")\n}\n\n#[derive(Debug, Clone)]\nstruct ParsedCronExpr {\n    minute: CronField,\n    hour: CronField,\n    day_of_month: CronField,\n    month: CronField,\n    day_of_week: CronField,\n}\n\nimpl ParsedCronExpr {\n    fn parse(expr: &str) -> Result<Self> {\n        let fields: Vec<&str> = expr.split_whitespace().collect();\n        if fields.len() != 5 {","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/automation_manager.rs#L613-L649","documentation":"AT values without an offset are read as local wall time and mapped through resolve_local_datetime; the mapping returns None exactly when that wall-clock reading never existed locally — a time inside a DST spring-forward gap. Unlike HOURLY anchors (which silently skip nonexistent clock times), ONCE refuses to guess a different fire time for your one-shot and errors, naming the offending timestamp.","triggerScenarios":"AT=2026-03-08T02:30 in a US timezone where clocks jump 02:00->03:00 that day; any local naive time that a forward offset transition skipped. RFC3339 inputs with an explicit offset never hit this path.","commonSituations":"One-shot automations placed at 2:00-2:59 AM on the spring-forward date; servers in a different timezone than the schedule author; tz-database rule changes moving the gap.","solutions":["Use RFC3339 with an explicit offset/UTC: AT=2026-03-08T06:30:00Z","Or pick a local time outside the gap (e.g. 03:30 after the jump)","Pre-validate local AT strings with Local.from_local_datetime(naive).single().is_some() before persisting"],"exampleFix":"// before (America/New_York: 02:30 never exists on 2026-03-08)\nlet rrule = \"FREQ=ONCE;AT=2026-03-08T02:30\";\n// after\nlet rrule = \"FREQ=ONCE;AT=2026-03-08T06:30:00Z\";","handlingStrategy":"validation","validationCode":"let trimmed = at_raw.trim();\nif chrono::DateTime::parse_from_rfc3339(trimmed).is_err() {\n    let naive = chrono::NaiveDateTime::parse_from_str(trimmed, \"%Y-%m-%dT%H:%M:%S\")\n        .or_else(|_| chrono::NaiveDateTime::parse_from_str(trimmed, \"%Y-%m-%dT%H:%M\"))?;\n    if chrono::Local.from_local_datetime(&naive).single().is_none() {\n        anyhow::bail!(\"local time {trimmed} does not exist (DST spring-forward gap); use an RFC3339 time with offset\");\n    }\n}","typeGuard":"fn once_local_time_exists(raw: &str) -> bool {\n    let trimmed = raw.trim();\n    if chrono::DateTime::parse_from_rfc3339(trimmed).is_ok() {\n        return true; // explicit offset never hits the gap\n    }\n    [\"%Y-%m-%dT%H:%M:%S\", \"%Y-%m-%dT%H:%M\"]\n        .iter()\n        .find_map(|f| chrono::NaiveDateTime::parse_from_str(trimmed, f).ok())\n        .is_none_or(|naive| chrono::Local.from_local_datetime(&naive).single().is_some())\n}","tryCatchPattern":null,"preventionTips":["Prefer RFC3339 with an explicit offset (or UTC 'Z') for one-shot times; it bypasses local-time resolution entirely","Avoid scheduling one-shots inside the 02:00-03:00 window on spring-forward days in DST jurisdictions","Pre-validate local AT strings with Local.from_local_datetime(...).single().is_some() before persisting","Remember the scheduler's timezone is the server's Local — author times with that in mind"],"tags":["automation","dst","timezone","scheduling","once"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}