pola-rs/polars · error

Invalid time unit '{tu:?}' for Time.

Error message

Invalid time unit '{tu:?}' for Time.

What it means

For Time64 columns the JSON writer only implements the Nanosecond converter; Time64(TimeUnit::Microsecond) - Arrow's other legal 64-bit time-of-day unit - hits the catch-all panic. Time32 columns go through a different branch, so this is specifically 64-bit micro-precision time data.

Source

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

        ),
        ArrowDataType::Duration(tu) => {
            let convert = match tu {
                TimeUnit::Nanosecond => duration_ns_to_duration,
                TimeUnit::Microsecond => duration_us_to_duration,
                TimeUnit::Millisecond => duration_ms_to_duration,
                tu => panic!("Invalid time unit '{tu:?}' for Duration."),
            };
            duration_serializer(
                array.as_any().downcast_ref().unwrap(),
                convert,
                offset,
                take,
            )
        },
        ArrowDataType::Time64(tu) => {
            let convert = match tu {
                TimeUnit::Nanosecond => time64ns_to_time,
                tu => panic!("Invalid time unit '{tu:?}' for Time."),
            };
            time_serializer(
                array.as_any().downcast_ref().unwrap(),
                convert,
                offset,
                take,
            )
        },
        ArrowDataType::Null => null_serializer(array.len(), offset, take),
        other => todo!("Writing {:?} to JSON", other),
    }
}

fn serialize_item<'a>(
    buffer: &mut Vec<u8>,
    record: impl Iterator<Item = (&'a str, &'a [u8])>,
    is_first_row: bool,
) {

View on GitHub (pinned to 9b5d73fd00)

Solutions

  1. Cast to Time64(Nanosecond) (or polars' DataType::Time, stored as ns i64) before write_json
  2. Or store as Int64 microseconds / a formatted string
  3. Inspect dtypes before exporting JSON

Example fix

// before: column typed Time64(Microsecond)
df.write_json(&mut out)?; // panic: Invalid time unit 'Microsecond' for Time.

// after
let df = df.with_column(col("t").cast(&DataType::Time))?; // polars Time = ns i64
df.write_json(&mut out)?;
Defensive patterns

Strategy: fallback

Validate before calling

fn normalize_time64(df: &DataFrame) -> PolarsResult<DataFrame> {
    let mut exprs = vec![];
    for (name, dt) in df.schema().iter() {
        if matches!(dt, DataType::Time64(TimeUnit::Microsecond)) {
            exprs.push(col(name).cast(&DataType::Time).alias(name)); // polars Time = i64 ns
        }
    }
    df.lazy().with_columns(exprs).collect()
}
// run before write_json

Type guard

fn has_writable_time64(df: &DataFrame) -> bool {
    df.schema().iter_values().all(|dt| match dt {
        DataType::Time64(tu) => matches!(tu, TimeUnit::Nanosecond),
        _ => true,
    })
}

Try / catch

catch_unwind around write_json only converts the panic to an error; casting Time64(Microsecond) to nanosecond Time beforehand avoids the failing match arm.

Prevention

When it happens

Trigger: write_json on a column typed ArrowDataType::Time64(TimeUnit::Microsecond), e.g. arrays built with arrow-rs' Time64MicrosecondType from another engine's time-of-day output.

Common situations: arrow-rs interop; time-of-day columns produced by engines that store micros since midnight.

Related errors


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