{"record":{"id":"2f2a32a618a88c67","repo":"atuinsh/atuin","slug":"failed-to-format-sqlite-date","errorCode":null,"errorMessage":"failed to format sqlite date","messagePattern":"failed to format sqlite date","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/search/inspector.rs","lineNumber":196,"sourceCode":"    let output = format_description!(\"[month]/[year repr:last_two]\");\n\n    let mut durations: Vec<(time::Date, i64)> = durations\n        .iter()\n        .map(|d| {\n            (\n                time::Date::parse(d.0.as_str(), &format).expect(\"invalid date string from sqlite\"),\n                d.1,\n            )\n        })\n        .collect();\n\n    durations.sort_by_key(|a| a.0);\n\n    durations\n        .iter()\n        .map(|(date, duration)| {\n            (\n                date.format(output).expect(\"failed to format sqlite date\"),\n                *duration,\n            )\n        })\n        .collect()\n}\n\nfn draw_stats_charts(f: &mut Frame<'_>, parent: Rect, stats: &HistoryStats, theme: &Theme) {\n    let exits: Vec<Bar> = stats\n        .exits\n        .iter()\n        .map(|(exit, count)| {\n            Bar::default()\n                .label(exit.to_string())\n                .value(u64_or_zero(*count))\n        })\n        .collect();\n\n    let exits = BarChart::default()","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/atuinsh/atuin/blob/202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1/crates/atuin/src/command/client/search/inspector.rs#L178-L214","documentation":"This is a Rust panic from `.expect()` on `time::Date::format` inside `sort_duration_over_time` (inspector.rs:176-201), which prepares the \"duration over time\" bar chart in Atuin's TUI inspector. The `time` crate's `format` returns `Err` when the format description requires a component the type cannot supply — a `time::Date` only carries year/month/day, so any time-of-day or offset component makes formatting fail. Here the output format is the compile-time `format_description!(\"[month]/[year repr:last_two]\")`, which `Date` fully supports, so the panic is a defensive assertion that only fires if the format is changed to something `Date` cannot render (its sibling at line 184 panics first if sqlite hands back a string that is not `DD-MM-YYYY`).","triggerScenarios":"Calling `sort_duration_over_time(&stats.duration_over_time)` while drawing the inspector tab, where `stats.duration_over_time` comes from `db.stats(&selected)`; the `.expect` at line 196 trips only when `date.format(output)` errors, i.e. the `output` format_description was edited to include components a `Date` lacks (e.g. `[hour]`, `[minute]`, `[offset]`). Editing `output` to a time-aware format compiles fine (formats are runtime values) but panics on the first inspector draw.","commonSituations":"A contributor customizing the inspector chart labels (e.g. wanting `08/25 14:00`-style buckets) adds time components to `output` without switching from `time::Date` to `PrimitiveDateTime`/`OffsetDateTime`; or a DB written by a different/older Atuin version returns duration buckets whose date strings don't match `[day]-[month]-[year]` (hits the adjacent parse expect at line 184 with 'invalid date string from sqlite').","solutions":["Keep the output format limited to date components (`[month]`, `[year]`, `[day]`, `[weekday]`) since the values are parsed as `time::Date`","If time-of-day components are needed, parse into a `PrimitiveDateTime`/`OffsetDateTime` (e.g. midnight in `settings.timezone`) instead of `Date` before formatting","Replace `.expect` with graceful handling: `date.format(output).unwrap_or_else(|e| { tracing::warn!(...); date.to_string() })` so one bad bucket cannot crash the whole TUI","If the panic message is actually 'invalid date string from sqlite' (line 184), inspect the `duration_over_time` rows returned by `db.stats` for a legacy/corrupted date format and migrate the DB"],"exampleFix":"// before\nlet output = format_description!(\"[month]/[year repr:last_two] [hour]\");\n...\ndate.format(output).expect(\"failed to format sqlite date\"),\n\n// after\nlet output = format_description!(\"[month]/[year repr:last_two]\");\n...\ndate.format(output).unwrap_or_else(|e| {\n    tracing::warn!(\"failed to format sqlite date {date}: {e}\");\n    date.to_string()\n}),","handlingStrategy":"validation","validationCode":"// Before charting, confirm every sqlite bucket parses with the input format\n// (catches the sibling 'invalid date string from sqlite' panic early)\nuse time::parsing::Parsable;\nlet input = time::macros::format_description!(\"[day]-[month]-[year]\");\nlet clean: Vec<_> = durations\n    .iter()\n    .filter(|(d, _)| time::Date::parse(d.as_str(), &input).is_ok())\n    .collect();\nassert_eq!(clean.len(), durations.len(), \"unexpected sqlite date bucket\");","typeGuard":"// Only date-level components are formattable on a time::Date;\n// keep the whitelist explicit when the format is configurable.\nconst DATE_SAFE_COMPONENTS: [&str; 5] = [\"year\", \"month\", \"day\", \"weekday\", \"week_number\"];","tryCatchPattern":"// Treat formatting as fallible; degrade to a default rendering instead of panicking\nlet label = match date.format(output) {\n    Ok(s) => s,\n    Err(e) => {\n        tracing::warn!(\"failed to format sqlite date {date}: {e}\");\n        date.to_string()\n    }\n};","preventionTips":["Keep the inspector chart output format restricted to components a time::Date supports (year/month/day/weekday); format from OffsetDateTime if you need time-of-day","Never widen a format_description! without re-checking the type being formatted — the time crate only errors at runtime","Prefer `unwrap_or_else`/`map_err` + logging over `.expect` in render paths; a chart label is never worth crashing the TUI","Cover chart helpers with unit tests that feed both normal and legacy-shaped date strings"],"tags":["rust","time-crate","ratatui","tui","inspector","date-formatting","panic"],"backgroundTag":"date-format-failed","analyzedSha":"202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1","analyzedAt":"2026-08-16T19:30:24.731Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}