pola-rs/polars · error

Timezone {tz} is invalid or not supported

Error message

Timezone {tz} is invalid or not supported

What it means

timestamp_tz_serializer() first tries parse_offset(tz) (fixed +/-HH:MM form) and then, with the timezones feature, parse_offset_tz(tz) (IANA names via chrono-tz). When both fail, the string is neither a valid offset nor a known zone name, and the writer panics before emitting any JSON. The tz comes straight from the column's DataType::Datetime(_, Some(tz)).

Source

Thrown at crates/polars-json/src/json/write/serialize.rs:476

            materialize_serializer(f, array.iter(), offset, take)
        },
        #[cfg(feature = "timezones")]
        _ => match parse_offset_tz(tz) {
            Ok(parsed_tz) => {
                let f = move |x: Option<&i64>, buf: &mut Vec<u8>| {
                    if let Some(x) = x {
                        let dt_str = timestamp_to_datetime(*x, time_unit, &parsed_tz).to_rfc3339();
                        write!(buf, "\"{dt_str}\"").unwrap();
                    } else {
                        buf.extend_from_slice(b"null")
                    }
                };

                materialize_serializer(f, array.iter(), offset, take)
            },
            _ => {
                panic!("Timezone {tz} is invalid or not supported");
            },
        },
        #[cfg(not(feature = "timezones"))]
        _ => {
            panic!("Invalid Offset format (must be [-]00:00) or timezones feature not active");
        },
    }
}

pub fn new_serializer<'a>(
    array: &'a dyn Array,
    offset: usize,
    take: usize,
) -> Box<dyn JsonSerializer<Item = [u8]> + 'a + Send + Sync> {
    match array.dtype().to_storage() {
        ArrowDataType::Boolean => {
            boolean_serializer(array.as_any().downcast_ref().unwrap(), offset, take)
        },

View on GitHub (pinned to 9b5d73fd00)

Solutions

  1. Fix the tz string to a valid IANA name ("America/New_York") or a fixed offset with colon ("+05:30")
  2. Validate/normalize time zones at ingest and store canonical IANA ids
  3. If the zone is garbage anyway, drop it before writing: col("ts").dt().replace_time_zone(None)

Example fix

// before
let df = df.with_column(
    col("ts").cast(DataType::Datetime(TimeUnit::Microseconds, Some("UTC+2".into()))),
)?.collect()?;
df.write_json(&mut out)?; // panic: Timezone UTC+2 is invalid or not supported

// after
// Some("+02:00".into())  or  Some("Europe/Berlin".into())
Defensive patterns

Strategy: validation

Validate before calling

fn tz_writable_by_json(tz: &str) -> bool {
    // fixed offset form accepted in all builds
    if polars_time::prelude::temporal_conversions::parse_offset(tz).is_ok() { return true; }
    // otherwise it must be a known IANA zone (timezones feature)
    tz.parse::<chrono_tz::Tz>().is_ok()
}
fn validate_df_tz(df: &DataFrame) -> Result<(), String> {
    for (name, dt) in df.schema().iter() {
        if let DataType::Datetime(_, Some(tz)) = dt {
            if !tz_writable_by_json(tz) { return Err(format!("column {name} has unwritable tz {tz}")); }
        }
    }
    Ok(())
}

Type guard

fn has_json_safe_timezones(df: &DataFrame) -> bool {
    df.schema().iter_values().all(|dt| match dt {
        DataType::Datetime(_, Some(tz)) => tz.parse::<chrono_tz::Tz>().is_ok()
            || polars_time::prelude::temporal_conversions::parse_offset(tz).is_ok(),
        _ => true,
    })
}

Try / catch

catch_unwind(AssertUnwindSafe(|| df.write_json(&mut out))) to fail the export job with the offending column name from your own pre-check; the panic itself only names the tz string.

Prevention

When it happens

Trigger: write_json / JsonWriter on a Datetime column whose tz string is malformed or unknown: "UTC+2" (wrong form), "+0200" (missing colon), "america/new_york" (wrong case), "America/Nowhere" (unknown zone).

Common situations: tz strings captured from user input, HTTP headers, or dirty upstream columns; data round-tripped from systems that write non-IANA zone ids.

Related errors


AI-assisted analysis of pola-rs/polars@9b5d73fd00 (2026-08-19). Data as JSON: /api/errors/261d6ab4933f7c40. Report an issue: GitHub.