{"record":{"id":"c07ff6c21977a4d9","repo":"ogham/exa","slug":"offset-out-of-range","errorCode":null,"errorMessage":"Offset out of range","messagePattern":"Offset out of range","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/output/time.rs","lineNumber":130,"sourceCode":"            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute())\n}\n\n#[allow(trivial_numeric_casts)]\nfn full_local(time: SystemTime) -> String {\n    let date = LocalDateTime::at(systemtime_epoch(time));\n    format!(\"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}\",\n            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute(), date.second(), systemtime_nanos(time))\n}\n\n#[allow(trivial_numeric_casts)]\nfn full_zoned(time: SystemTime, zone: &TimeZone) -> String {\n    use datetime::Offset;\n\n    let local = LocalDateTime::at(systemtime_epoch(time));\n    let date = zone.to_zoned(local);\n    let offset = Offset::of_seconds(zone.offset(local) as i32).expect(\"Offset out of range\");\n    format!(\"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {:+03}{:02}\",\n            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute(), date.second(), systemtime_nanos(time),\n            offset.hours(), offset.minutes().abs())\n}\n\n#[allow(trivial_numeric_casts)]\nfn iso_local(time: SystemTime) -> String {\n    let date = LocalDateTime::at(systemtime_epoch(time));\n\n    if is_recent(&date) {\n        format!(\"{:02}-{:02} {:02}:{:02}\",\n                date.month() as usize, date.day(),\n                date.hour(), date.minute())\n    }\n    else {\n        format!(\"{:04}-{:02}-{:02}\",\n                date.year(), date.month() as usize, date.day())","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/ogham/exa/blob/3d1edbb47052cb416ef9478106c3907586da5150/src/output/time.rs#L112-L148","documentation":"This is a Rust panic from expect() in src/output/time.rs:130 inside full_zoned(). The datetime crate's Offset::of_seconds() returns Option<Offset>: it is None when the offset in seconds cannot be represented as a ±24-hour UTC offset (or when the i64-to-i32 cast mangles an absurd value). exa turns that None into a panic while formatting a full-iso timestamp with a timezone. The offset value itself comes from the TimeZone loaded at startup from the $TZ environment variable or /etc/localtime (src/output/table.rs:342-360), so the panic signals that the loaded zone data produced an impossible offset.","triggerScenarios":"Running exa with --time-style=full-iso (full_zoned is the only formatting path that calls Offset::of_seconds) on Unix, in a process where determine_time_zone() succeeded but the zone data is broken: a corrupt or truncated TZif file, a hand-crafted/custom zone file with offsets at or beyond ±86400 seconds, or $TZ pointing at such a file. The panic happens per file, at the moment its mtime is formatted.","commonSituations":"Systems with a damaged /etc/localtime (interrupted tzdata update, container image with a truncated zoneinfo file), $TZ pointing to a stale custom file, or minimal containers where /usr/share/zoneinfo is partially copied. Rare with stock distro tzdata, because real zone offsets stay within ±14 hours; essentially always an environment/data-integrity problem rather than a code bug in the caller.","solutions":["Test the environment first: run with TZ=UTC (for example TZ=UTC exa --time-style=full-iso). If that works, your previous $TZ or /etc/localtime is the culprit.","Point $TZ at a known-good zone file or reinstall tzdata (Debian/Ubuntu: apt install --reinstall tzdata; Fedora: dnf reinstall tzdata), and verify with file /etc/localtime and by diffing against /usr/share/zoneinfo/UTC.","If you embed this code: replace the expect with a safe fallback (offset 0 or fall back to the unzoned full_local format) so one bad zone cannot abort the whole listing.","If the panic persists on valid tzdata, report it upstream with the exact $TZ value and the zone file, since zone.offset() returning an out-of-range value from a valid TZif would be a datetime-crate bug."],"exampleFix":"// before (src/output/time.rs)\nlet offset = Offset::of_seconds(zone.offset(local) as i32).expect(\"Offset out of range\");\n\n// after: degrade gracefully instead of panicking\nlet offset = Offset::of_seconds(zone.offset(local) as i32)\n    .unwrap_or_else(|| Offset::of_seconds(0).expect(\"UTC offset is always in range\"));\n// or: fall back to the unzoned formatter\n// if Offset::of_seconds(zone.offset(local) as i32).is_none() {\n//     return full_local(time);\n// }","handlingStrategy":"fallback","validationCode":"// Before rendering with --time-style=full-iso, sanity-check the active zone:\nuse datetime::{LocalDateTime, TimeZone, Offset};\nuse std::time::SystemTime;\n\nfn zone_offset_is_representable(zone: &TimeZone) -> bool {\n    let now = LocalDateTime::at(\n        SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap()\n    );\n    let secs = zone.offset(now);\n    // Offset::of_seconds rejects anything beyond +/- 24 hours (i32 range)\n    secs.abs() < 24 * 60 * 60 && Offset::of_seconds(secs as i32).is_some()\n}\n\nif !zone_offset_is_representable(&tz) {\n    // fall back to UTC or to the unzoned formatter before rendering\n}","typeGuard":"fn usable_for_full_iso(zone: &TimeZone) -> bool {\n    // Probe the exact conversion that full_zoned() (src/output/time.rs:130)\n    // performs with .expect(); if it would be None, exa would panic.\n    let local = LocalDateTime::at(std::time::Duration::from_secs(0));\n    Offset::of_seconds(zone.offset(local) as i32).is_some()\n}","tryCatchPattern":"// The failure is a panic, not an Err; guard with catch_unwind and keep listing:\nlet formatted = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    TimeFormat::FullISO.format_zoned(time, &zone)\n})).unwrap_or_else(|_| {\n    TimeFormat::FullISO.format_local(time)  // fallback: drop the zone suffix\n});","preventionTips":["Pin $TZ to a known-good zone (TZ=UTC) in scripts and containers that parse exa output, instead of relying on inherited /etc/localtime.","After building minimal container images, verify the tzdata payload (file /etc/localtime; compare checksums with /usr/share/zoneinfo/UTC) before shipping.","Treat 'Offset out of range' as a data-integrity signal: reinstall tzdata rather than working around individual files.","If you embed exa's time formatting, prefer format_local() when the environment's zone file is not under your control, or wrap format_zoned() in the catch_unwind fallback shown above."],"tags":["rust","panic","timezone","datetime","tzdata","formatting"],"backgroundTag":"invalid-timezone-data","analyzedSha":"3d1edbb47052cb416ef9478106c3907586da5150","analyzedAt":"2026-08-16T22:11:59.736Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}