pola-rs/polars · error

FixedOffset::east out of bounds

Error message

FixedOffset::east out of bounds

What it means

parse_offset splits an offset string like '+05:30' on ':', parses hours and minutes as i32, then calls FixedOffset::east_opt(hours*3600 + minutes*60). chrono requires the total to lie within (-86400, 86400) seconds; the parsing steps check well-formedness only, not magnitude, so a clean-looking '+24:00', '+99:00', or '+00:9999' reaches the .expect and panics.

Source

Thrown at crates/polars-arrow/src/temporal_conversions.rs:301

    }
    static ERR_MSG: &str = "timezone offset must be of the form [-]00:00";

    let mut a = offset.split(':');
    let first: &str = a
        .next()
        .ok_or_else(|| polars_err!(InvalidOperation: ERR_MSG))?;
    let last = a
        .next()
        .ok_or_else(|| polars_err!(InvalidOperation: ERR_MSG))?;
    let hours: i32 = first
        .parse()
        .map_err(|_| polars_err!(InvalidOperation: ERR_MSG))?;
    let minutes: i32 = last
        .parse()
        .map_err(|_| polars_err!(InvalidOperation: ERR_MSG))?;

    Ok(FixedOffset::east_opt(hours * 60 * 60 + minutes * 60)
        .expect("FixedOffset::east out of bounds"))
}

/// Parses `value` to a [`chrono_tz::Tz`] with the Arrow's definition of timestamp with a timezone.
#[cfg(feature = "chrono-tz")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono-tz")))]
pub fn parse_offset_tz(timezone: &str) -> PolarsResult<chrono_tz::Tz> {
    timezone
        .parse::<chrono_tz::Tz>()
        .map_err(|_| polars_err!(InvalidOperation: "timezone \"{timezone}\" cannot be parsed"))
}

View on GitHub (pinned to df599052da)

Solutions

  1. Validate before use: hours in -23..=23 and minutes in 0..=59
  2. Normalize '+24:00' to '+00:00' (UTC) upstream
  3. Prefer IANA timezone names ('UTC', 'Europe/Amsterdam') via parse_offset_tz, which returns Err instead of panicking
  4. Check the computed total seconds is within -86399..=86399

Example fix

// before
let tz = parse_offset("+25:00")?; // panics: FixedOffset::east out of bounds

// after
fn parse_offset_checked(s: &str) -> Option<FixedOffset> {
    let (h, m): (i32, i32) = s.strip_prefix(['+', '-'])?.split(':')...
    // require h in -23..=23, m in 0..=59, then FixedOffset::east_opt(h * 3600 + m * 60)
}
Defensive patterns

Strategy: validation

Validate before calling

fn offset_str_valid(tz: &str) -> bool {
    let Some((h, m)) = tz.split_once(':') else { return false };
    let (h, m): (i32, i32) = match (h.strip_prefix('+').or(h.strip_prefix('-')).unwrap_or(h).parse(),
                                    m.parse()) {
        (Ok(h), Ok(m)) => (h, m),
        _ => return false,
    };
    h.abs() < 24 && (0..=59).contains(&m)
}

Prevention

When it happens

Trigger: Passing a fixed-offset timezone string (Arrow timestamp tz metadata) with |hours| >= 24 or minutes that parse but push the total to >= 24h - e.g. schema metadata tz='+25:00' from a hand-written or converted schema.

Common situations: Hand-written parquet/arrow schemas with offset tz strings; producers emitting '+24:00' for UTC; typos in ETL config; sanitized/fuzzed metadata.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/655fd84955f11326. Report an issue: GitHub.