risingwavelabs/risingwave · error · InvalidParamsError

Invalid datetime: seconds: {secs}, nanoseconds: {nsecs}

Error message

Invalid datetime: seconds: {secs}, nanoseconds: {nsecs}

What it means

ErrorKind::DateTime is raised by Timestamp::with_secs_nsecs when chrono's DateTime::from_timestamp(secs, nsecs) returns None, i.e. the seconds/nanoseconds pair does not represent a representable UTC datetime (secs out of i64-supported range or nsecs >= 1_000_000_000). It is wrapped in InvalidParamsError and bubbles up through Timestamp deserialization.

Source

Thrown at src/common/src/types/datetime.rs:255

/// ```
impl From<Interval> for Time {
    fn from(interval: Interval) -> Self {
        let usecs = interval.usecs_of_day();
        let secs = (usecs / 1_000_000) as u32;
        let nano = (usecs % 1_000_000 * 1000) as u32;
        Time::from_num_seconds_from_midnight_uncheck(secs, nano)
    }
}

#[derive(Copy, Clone, Debug, Error)]
enum ErrorKind {
    #[error("Invalid date: days: {days}")]
    Date { days: i32 },
    #[error("Invalid time: secs: {secs}, nanoseconds: {nsecs}")]
    Time { secs: u32, nsecs: u32 },
    #[error("Invalid time: {value} {unit} is out of range for a time of day")]
    TimeOfDay { value: u64, unit: &'static str },
    #[error("Invalid datetime: seconds: {secs}, nanoseconds: {nsecs}")]
    DateTime { secs: i64, nsecs: u32 },
    #[error("Invalid datetime: {value} {unit} is out of range")]
    Timestamp { value: i64, unit: &'static str },
    #[error("Can't cast string to date (expected format is YYYY-MM-DD)")]
    ParseDate,
    #[error(
        "Can't cast string to time (expected format is HH:MM:SS[.D+{{up to 6 digits}}][Z] or HH:MM)"
    )]
    ParseTime,
    #[error(
        "Can't cast string to timestamp (expected format is YYYY-MM-DD HH:MM:SS[.D+{{up to 9 digits}}] or YYYY-MM-DD HH:MM or YYYY-MM-DD or ISO 8601 format)"
    )]
    ParseTimestamp,
}

#[derive(Debug, Error)]
#[error(transparent)]
pub struct InvalidParamsError(#[from] ErrorKind);

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Validate nsecs < 1_000_000_000 and that secs is within chrono's supported range before constructing.
  2. Normalize nanosecond overflow by carrying it into secs (secs += nsecs / 1e9; nsecs %= 1e9).
  3. Verify the protobuf encoder/writer version matches the decoder (V0 micros vs V1 secs+nsecs format).

Example fix

// before
let ts = Timestamp::with_secs_nsecs(secs, nsecs)?;
// after
let ts = Timestamp::with_secs_nsecs(secs + (nsecs / 1_000_000_000) as i64, nsecs % 1_000_000_000)?;
Defensive patterns

Strategy: validation

Validate before calling

fn can_be_timestamp(secs: i64, nsecs: u32) -> bool {
    nsecs < 1_000_000_000 && (-8334601228800..=8210266876799).contains(&secs)
}

Type guard

fn is_safe_epoch_pair(secs: i64, nsecs: u32) -> Option<(i64, u32)> {
    if nsecs >= 1_000_000_000 { None } else { Some((secs, nsecs)) }
}

Try / catch

match Timestamp::with_secs_nsecs(secs, nsecs) {
    Ok(ts) => ts,
    Err(e) => return Err(anyhow!("bad timestamp payload: {e}")),
}

Prevention

When it happens

Trigger: Calling Timestamp::with_secs_nsecs(secs, nsecs) with an out-of-range secs (near i64::MIN/MAX) or nsecs >= 1e9; Time-stamped protobuf decoding (Timestamp::from_protobuf, V1 format) of a corrupted payload.

Common situations: Corrupted or hand-crafted protobuf messages; external systems emitting epoch values far outside the supported range; off-by-one nanosecond arithmetic bugs producing nsecs == 1_000_000_000.

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 risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/c96ee8d71a229d83. Report an issue: GitHub.