nikivdev/code · error
failed to calculate next day
Error message
failed to calculate next day
What it means
next_local_midnight_utc computes tomorrow's date via chrono's NaiveDate::succ_opt, which returns None only when advancing the date overflows the date range representable by chrono. The function throws this anyhow error when succ_opt fails so the caller (unlock_ssh_key, which sets an expiry at next local midnight) cannot compute a valid unlock deadline.
Source
Thrown at src/ssh_keys.rs:518
let path = ssh_unlock_path()?;
let entry = SshKeyUnlock {
expires_at: expires_at.timestamp(),
};
let content = serde_json::to_string_pretty(&entry)?;
fs::write(&path, content)?;
Ok(())
}
fn unlock_expires_at(entry: &SshKeyUnlock) -> Option<DateTime<Utc>> {
DateTime::<Utc>::from_timestamp(entry.expires_at, 0)
}
fn next_local_midnight_utc() -> Result<DateTime<Utc>> {
let now = Local::now();
let tomorrow = now
.date_naive()
.succ_opt()
.ok_or_else(|| anyhow::anyhow!("failed to calculate next day"))?;
let naive = tomorrow
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to build midnight time"))?;
let local_dt = Local
.from_local_datetime(&naive)
.single()
.or_else(|| Local.from_local_datetime(&naive).earliest())
.ok_or_else(|| anyhow::anyhow!("failed to resolve local midnight"))?;
Ok(local_dt.with_timezone(&Utc))
}
fn prompt_touch_id() -> Result<()> {
if !cfg!(target_os = "macos") {
bail!("Touch ID is not available on this OS");
}
if std::env::var("FLOW_NO_TOUCH_ID").is_ok() || !std::io::stdin().is_terminal() {
bail!("Touch ID prompt requires an interactive terminal");
}View on GitHub (pinned to a747e741ae)
Solutions
- Check the system clock (date / timedatectl) and correct it to the current real date
- Add a fallback that treats succ_opt() == None as 'use now + 1 day' via checked arithmetic and a hard error only if that also fails
- Pin/upgrade chrono to ensure date arithmetic behavior is as expected
Example fix
// before
.ok_or_else(|| anyhow::anyhow!("failed to calculate next day"))?
// after
let tomorrow = now.date_naive().succ_opt().unwrap_or_else(|| now.date_naive() + chrono::Duration::days(1)); Defensive patterns
Strategy: validation
Validate before calling
let today = chrono::Local::now().date_naive();
if today.succ_opt().is_none() {
eprintln!("system date is out of range; fix the clock before unlocking");
return;
} Try / catch
match unlock_ssh_key() {
Ok(expiry) => println!("key unlocks until {}", expiry),
Err(e) if e.to_string().contains("failed to calculate next day") => fix_system_clock(),
Err(e) => eprintln!("unlock failed: {}", e),
} Prevention
- Keep the system clock synchronized (NTP enabled)
- Treat any date-related error at unlock time as a clock misconfiguration symptom
- Prefer checked date arithmetic with sane fallbacks over hard errors
When it happens
Trigger: Calling next_local_midnight_utc (via unlock_ssh_key) when the current local date is at or near chrono's NaiveDate maximum boundary (year ~262143), so succ_opt() returns None.
Common situations: Extremely rare in practice; only seen with a corrupted system clock set to a far-future date, or running in an environment whose clock is misconfigured to chrono's max date.
Related errors
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/aa1fdf1beeda8540.
Report an issue: GitHub.