atuinsh/atuin · error
failed to format sqlite date
Error message
failed to format sqlite date
What it means
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`).
Source
Thrown at crates/atuin/src/command/client/search/inspector.rs:196
let output = format_description!("[month]/[year repr:last_two]");
let mut durations: Vec<(time::Date, i64)> = durations
.iter()
.map(|d| {
(
time::Date::parse(d.0.as_str(), &format).expect("invalid date string from sqlite"),
d.1,
)
})
.collect();
durations.sort_by_key(|a| a.0);
durations
.iter()
.map(|(date, duration)| {
(
date.format(output).expect("failed to format sqlite date"),
*duration,
)
})
.collect()
}
fn draw_stats_charts(f: &mut Frame<'_>, parent: Rect, stats: &HistoryStats, theme: &Theme) {
let exits: Vec<Bar> = stats
.exits
.iter()
.map(|(exit, count)| {
Bar::default()
.label(exit.to_string())
.value(u64_or_zero(*count))
})
.collect();
let exits = BarChart::default()View on GitHub (pinned to 202f6ad98e)
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
Example fix
// before
let output = format_description!("[month]/[year repr:last_two] [hour]");
...
date.format(output).expect("failed to format sqlite date"),
// after
let output = format_description!("[month]/[year repr:last_two]");
...
date.format(output).unwrap_or_else(|e| {
tracing::warn!("failed to format sqlite date {date}: {e}");
date.to_string()
}), Defensive patterns
Strategy: validation
Validate before calling
// Before charting, confirm every sqlite bucket parses with the input format
// (catches the sibling 'invalid date string from sqlite' panic early)
use time::parsing::Parsable;
let input = time::macros::format_description!("[day]-[month]-[year]");
let clean: Vec<_> = durations
.iter()
.filter(|(d, _)| time::Date::parse(d.as_str(), &input).is_ok())
.collect();
assert_eq!(clean.len(), durations.len(), "unexpected sqlite date bucket"); Type guard
// Only date-level components are formattable on a time::Date; // keep the whitelist explicit when the format is configurable. const DATE_SAFE_COMPONENTS: [&str; 5] = ["year", "month", "day", "weekday", "week_number"];
Try / catch
// Treat formatting as fallible; degrade to a default rendering instead of panicking
let label = match date.format(output) {
Ok(s) => s,
Err(e) => {
tracing::warn!("failed to format sqlite date {date}: {e}");
date.to_string()
}
}; Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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').
Related errors
- Drawing inspector, but no stats
- invalid date string from sqlite
- Interactive mode requires a terminal
- bug in list query. please report
- bug in search query. please report
AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16).
Data as JSON: /api/errors/2f2a32a618a88c67.
Report an issue: GitHub.