atuinsh/atuin · error
invalid date string from sqlite
Error message
invalid date string from sqlite
What it means
The search inspector computes per-day duration stats: the sqlite layer produces day keys formatted dd-mm-yyyy, and sort_duration_over_time re-parses each with time::Date::parse using the [day]-[month]-[year] format description. Any key that does not match exactly (different separator, reordered fields, NULL rendered as empty, corrupt timestamp) makes the expect panic with 'invalid date string from sqlite'.
Source
Thrown at crates/atuin/src/command/client/search/inspector.rs:184
"1" => "Monday".to_string(),
"2" => "Tuesday".to_string(),
"3" => "Wednesday".to_string(),
"4" => "Thursday".to_string(),
"5" => "Friday".to_string(),
"6" => "Saturday".to_string(),
_ => "Invalid day".to_string(),
}
}
fn sort_duration_over_time(durations: &[(String, i64)]) -> Vec<(String, i64)> {
let format = format_description!("[day]-[month]-[year]");
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()
}
View on GitHub (pinned to 202f6ad98e)
Solutions
- Run the inspector's day-grouping query directly against the history DB (sqlite3) and look for keys that are not dd-mm-yyyy or are NULL
- Back up the DB, then fix or remove history rows with invalid timestamps
- Ensure only one atuin version touches the DB at a time (stop the daemon while inspecting)
Example fix
// before
time::Date::parse(d.0.as_str(), &format).expect("invalid date string from sqlite")
// after — skip malformed days instead of crashing the inspector
let Ok(date) = time::Date::parse(d.0.as_str(), &format) else {
eprintln!("skipping malformed date from sqlite: {d:?}");
continue;
}; Defensive patterns
Strategy: validation
Validate before calling
let format = time::macros::format_description!("[day]-[month]-[year]");
let clean: Vec<_> = durations
.iter()
.filter(|(day, _)| time::Date::parse(day.as_str(), &format).is_ok())
.cloned()
.collect();
// feed `clean` to sort_duration_over_time Type guard
fn is_dd_mm_yyyy(s: &str) -> bool {
let mut it = s.split('-');
matches!(
(it.next(), it.next(), it.next(), it.next()),
(Some(d), Some(m), Some(y), None)
if d.len() == 2 && m.len() == 2 && y.len() == 4
&& d.chars().chain(m.chars()).chain(y.chars()).all(|c| c.is_ascii_digit())
)
} Try / catch
let sorted = std::panic::catch_unwind(|| sort_duration_over_time(&durations));
match sorted {
Ok(rows) => render(rows),
Err(_) => eprintln!("history DB contains malformed day rows; run the inspector on a repaired copy"),
} Prevention
- Never hand-edit history timestamps; keep the dd-mm-yyyy grouping format intact
- Validate imported history for NULL or non-timestamp values
- Run the inspector against a copy of the DB when data was edited externally
When it happens
Trigger: sort_duration_over_time receives a first tuple element not shaped dd-mm-yyyy: history rows with NULL/garbage timestamps feeding the sqlite strftime grouping, a database written or edited by a different atuin version, or a change in the grouping SQL's date format.
Common situations: Imported or hand-edited history DBs; upgrades/downgrades that alter the stats query; rows with zero, negative, or missing timestamps.
Related errors
- failed to format sqlite date
- bug in list query. please report
- bug in search query. please report
- issue in stats previous query
- issue in stats next query
AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16).
Data as JSON: /api/errors/190b6dd114243e4a.
Report an issue: GitHub.