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

  1. Check the system clock (date / timedatectl) and correct it to the current real date
  2. 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
  3. 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

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.