risingwavelabs/risingwave · error · IntervalParseError

Invalid interval: {0}, expected format P<years>Y<months>M<da

Error message

Invalid interval: {0}, expected format P<years>Y<months>M<days>DT<hours>H<minutes>M<seconds>S

What it means

IntervalParseError::InvalidIso8601 is raised when parsing an ISO 8601 duration string fails the strict P<n>Y<n>M<n>DT<n>H<n>M<n>S shape. The parser attempts each component and, if any step fails or overflow checks fail, reports the whole string as invalid ISO 8601.

Source

Thrown at src/common/src/types/interval.rs:1019

    fn write<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
        write!(f, "{self}")
    }

    fn write_with_type<W: std::fmt::Write>(&self, ty: &DataType, f: &mut W) -> std::fmt::Result {
        match ty {
            DataType::Interval => self.write(f),
            _ => unreachable!(),
        }
    }
}

/// Error type for parsing an [`Interval`].
#[derive(thiserror::Error, Debug, thiserror_ext::Construct)]
pub enum IntervalParseError {
    #[error("Invalid interval: {0}")]
    Invalid(String),

    #[error(
        "Invalid interval: {0}, expected format P<years>Y<months>M<days>DT<hours>H<minutes>M<seconds>S"
    )]
    InvalidIso8601(String),

    #[error("Invalid unit: {0}")]
    InvalidUnit(String),

    #[error("{0}")]
    Uncategorized(String),
}

type ParseResult<T> = std::result::Result<T, IntervalParseError>;

impl Interval {
    pub fn as_iso_8601(&self) -> String {
        // ISO pattern - PnYnMnDTnHnMnS
        let years = self.months / 12;
        let months = self.months % 12;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Emit a strictly canonical ISO 8601 duration: starts with 'P', ordered Y/M/D then T H/M/S, uppercase.
  2. Remove empty components (e.g. use 'PT0S' instead of 'PT').
  3. Reduce the magnitude if component multiplication overflows i64 microseconds.

Example fix

// before
let iv = Interval::parse_iso8601("p1y")?;
// after
let iv = Interval::parse_iso8601("P1Y")?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_iso8601_duration(s: &str) -> bool {
    regex::Regex::new(r"^P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$").unwrap()
        .is_match(s) && s != "P"
}

Type guard

fn looks_like_iso_duration(s: &str) -> bool {
    s.starts_with('P') && s.len() > 1 && s.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '.')
}

Try / catch

let iv = Interval::parse_iso8601(s)
    .map_err(|e| anyhow!("'{s}' is not a valid ISO 8601 duration: {e}"))?;

Prevention

When it happens

Trigger: Strings like 'P1Y2M' handled fine but 'PT' (empty components), 'P' alone, '1Y2M' (missing leading 'P'), lowercase 'p1y', or values whose products overflow i64 (checked_mul/checked_add fail) in Interval::parse_iso8601.

Common situations: Feeding Java/Python datetime.timedelta-style strings ('1 day, 0:00:00') that only look ISO-like; decimal values in fields where the parser doesn't accept them; extremely large duration values overflowing i64 microseconds.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/c1c650fef064ae4a. Report an issue: GitHub.