{"record":{"id":"261d6ab4933f7c40","repo":"pola-rs/polars","slug":"timezone-tz-is-invalid-or-not-supported","errorCode":null,"errorMessage":"Timezone {tz} is invalid or not supported","messagePattern":"Timezone (.+?) is invalid or not supported","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/polars-json/src/json/write/serialize.rs","lineNumber":476,"sourceCode":"\n            materialize_serializer(f, array.iter(), offset, take)\n        },\n        #[cfg(feature = \"timezones\")]\n        _ => match parse_offset_tz(tz) {\n            Ok(parsed_tz) => {\n                let f = move |x: Option<&i64>, buf: &mut Vec<u8>| {\n                    if let Some(x) = x {\n                        let dt_str = timestamp_to_datetime(*x, time_unit, &parsed_tz).to_rfc3339();\n                        write!(buf, \"\\\"{dt_str}\\\"\").unwrap();\n                    } else {\n                        buf.extend_from_slice(b\"null\")\n                    }\n                };\n\n                materialize_serializer(f, array.iter(), offset, take)\n            },\n            _ => {\n                panic!(\"Timezone {tz} is invalid or not supported\");\n            },\n        },\n        #[cfg(not(feature = \"timezones\"))]\n        _ => {\n            panic!(\"Invalid Offset format (must be [-]00:00) or timezones feature not active\");\n        },\n    }\n}\n\npub fn new_serializer<'a>(\n    array: &'a dyn Array,\n    offset: usize,\n    take: usize,\n) -> Box<dyn JsonSerializer<Item = [u8]> + 'a + Send + Sync> {\n    match array.dtype().to_storage() {\n        ArrowDataType::Boolean => {\n            boolean_serializer(array.as_any().downcast_ref().unwrap(), offset, take)\n        },","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/pola-rs/polars/blob/9b5d73fd00236295624374b075d16b1fe6ec6df9/crates/polars-json/src/json/write/serialize.rs#L458-L494","documentation":"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)).","triggerScenarios":"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).","commonSituations":"tz strings captured from user input, HTTP headers, or dirty upstream columns; data round-tripped from systems that write non-IANA zone ids.","solutions":["Fix the tz string to a valid IANA name (\"America/New_York\") or a fixed offset with colon (\"+05:30\")","Validate/normalize time zones at ingest and store canonical IANA ids","If the zone is garbage anyway, drop it before writing: col(\"ts\").dt().replace_time_zone(None)"],"exampleFix":"// before\nlet df = df.with_column(\n    col(\"ts\").cast(DataType::Datetime(TimeUnit::Microseconds, Some(\"UTC+2\".into()))),\n)?.collect()?;\ndf.write_json(&mut out)?; // panic: Timezone UTC+2 is invalid or not supported\n\n// after\n// Some(\"+02:00\".into())  or  Some(\"Europe/Berlin\".into())","handlingStrategy":"validation","validationCode":"fn tz_writable_by_json(tz: &str) -> bool {\n    // fixed offset form accepted in all builds\n    if polars_time::prelude::temporal_conversions::parse_offset(tz).is_ok() { return true; }\n    // otherwise it must be a known IANA zone (timezones feature)\n    tz.parse::<chrono_tz::Tz>().is_ok()\n}\nfn validate_df_tz(df: &DataFrame) -> Result<(), String> {\n    for (name, dt) in df.schema().iter() {\n        if let DataType::Datetime(_, Some(tz)) = dt {\n            if !tz_writable_by_json(tz) { return Err(format!(\"column {name} has unwritable tz {tz}\")); }\n        }\n    }\n    Ok(())\n}","typeGuard":"fn has_json_safe_timezones(df: &DataFrame) -> bool {\n    df.schema().iter_values().all(|dt| match dt {\n        DataType::Datetime(_, Some(tz)) => tz.parse::<chrono_tz::Tz>().is_ok()\n            || polars_time::prelude::temporal_conversions::parse_offset(tz).is_ok(),\n        _ => true,\n    })\n}","tryCatchPattern":"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.","preventionTips":["Normalize tz strings to canonical IANA ids at ingest (validate with chrono_tz::Tz::from_str)","Reject non-IANA zone ids from user input early","Drop zones you do not need: replace_time_zone(None) before export"],"tags":["polars","json","datetime","timezone","serialization","panic"],"backgroundTag":"invalid-timezone","analyzedSha":"9b5d73fd00236295624374b075d16b1fe6ec6df9","analyzedAt":"2026-08-19T12:15:06.350Z","contentChangedAt":"2026-08-19T12:15:06.350Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}