nautechsystems/nautilus_trader · error

Invalid date

Error message

Invalid date

What it means

In last_weekday_nanos the i32 year cannot be converted to the i16 range Date::new requires, so the try_from fails and the function reports a generic "Invalid date". The library throws it because the year is outside the representable date range (roughly -32768..32767) and a real calendar date cannot be constructed. The message is intentionally generic; check which parameter is out of range.

Source

Thrown at crates/core/src/datetime.rs:497

    );
    out.push('Z');
    out
}

/// Floor the given UNIX nanoseconds to the nearest microsecond.
#[must_use]
pub const fn floor_to_nearest_microsecond(unix_nanos: u64) -> u64 {
    (unix_nanos / NANOSECONDS_IN_MICROSECOND) * NANOSECONDS_IN_MICROSECOND
}

/// Calculates the last weekday (Mon-Fri) from the given `year`, `month`, and `day`.
///
/// # Errors
///
/// Returns an error if the date is invalid.
pub fn last_weekday_nanos(year: i32, month: u32, day: u32) -> anyhow::Result<UnixNanos> {
    let date = Date::new(
        i16::try_from(year).map_err(|_| anyhow::anyhow!("Invalid date"))?,
        i8::try_from(month).map_err(|_| anyhow::anyhow!("Invalid date"))?,
        i8::try_from(day).map_err(|_| anyhow::anyhow!("Invalid date"))?,
    )
    .map_err(|_| anyhow::anyhow!("Invalid date"))?;
    let current_weekday = date.weekday().to_monday_one_offset();

    // Calculate the offset in days for closest weekday (Mon-Fri)
    let offset = match current_weekday {
        1..=5 => 0, // Monday to Friday, no adjustment needed
        6 => 1,     // Saturday, adjust to previous Friday
        _ => 2,     // Sunday, adjust to previous Friday
    };
    // Calculate last closest weekday
    let last_closest = date.checked_sub(Span::new().days(offset))?;

    // Convert to UNIX nanoseconds
    let unix_timestamp_ns = last_closest
        .at(0, 0, 0, 0)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Clamp or validate the year to the i16-representable calendar range before calling (practically 1..=9999).
  2. Verify the source of the year value; fix upstream arithmetic or parsing that produced an out-of-range year.
  3. Catch the error and surface the original year/month/day for diagnostics, since the message does not include them.

Example fix

// before
let ns = last_weekday_nanos(expiry_year, month, day)?; // expiry_year = 120000
// after
assert!((1..=9999).contains(&expiry_year));
let ns = last_weekday_nanos(expiry_year, month, day)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: check year range before calling
fn valid_year(y: i32) -> bool { (1..=9999).contains(&y) }

Try / catch

let ns = last_weekday_nanos(year, month, day)
    .map_err(|e| e.context(format!("last_weekday for y={year} m={month} d={day}")))?;

Prevention

When it happens

Trigger: Calling last_weekday_nanos(year, month, day) with a year that does not fit in i16, e.g. year <= -32769 or year >= 32768, such as year=99999 from a bad config or 0/uninitialized value.

Common situations: Uninitialized/default (0 is fine, but sentinel values like i32::MAX are not); reading a year column from CSV parsed as i32 with junk; computing expiry years with an off-by-thousands arithmetic bug.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/d365abc60d79f338. Report an issue: GitHub.