BoundaryML/baml · error

hour fits in u8

Error message

hour fits in u8

What it means

Internal invariant panic while stringifying a PlainTime. The nanoseconds are first normalized with rem_euclid(24h), so ns / NANOS_PER_HOUR is guaranteed < 24 and must fit in u8. Failure means the normalization or constant math is broken, not bad user input.

Solutions

  1. Report the failing time value to maintainers.
  2. Avoid constructing PlainTime via private fields with out-of-range nanoseconds.
  3. Upgrade to a build where the constants (MAX_TZ_OFFSET_NS, NANOS_PER_HOUR) are consistent.
Defensive patterns

Strategy: validation

Validate before calling

// Ensure PlainTime nanoseconds are in range before formatting
fn plain_time_in_range(ns: i64) -> bool { (0..86_400_000_000_000).contains(&ns) }

Try / catch

std::panic::catch_unwind(|| plaintime_to_string(pt)).unwrap_or_else(|_| Err(FormatError::Internal))

Prevention

When it happens

Trigger: Calling PlainTime.to_string()/to string conversion on a PlainTime whose hour bucket computes to >= 256 — only possible if rem_euclid(MAX_TZ_OFFSET_NS) reduction failed or constants changed inconsistently.

Common situations: Only seen during VM development or with corrupted manually-constructed PlainTime internals.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/6783964bcfc4fe53. Report an issue: GitHub.

Appendix: source

Thrown at baml_language/crates/bex_vm/src/package_baml/time.rs:434

            ))
        })?;
        let time_ns = i64::from(parsed.hour()) * NANOS_PER_HOUR
            + i64::from(parsed.minute()) * NANOS_PER_MINUTE
            + i64::from(parsed.second()) * NANOS_PER_SECOND
            + i64::from(parsed.nanosecond());
        Ok(copy::time::PlainTime {
            _nanoseconds: time_ns,
        }
        .to_value(vm))
    }

    fn _to_string_impl(plaintime: &view::time::PlainTime<'_>) -> bex_str::BexStr {
        // Normalize manually-constructed out-of-range values into [0, 24h).
        let ns = plaintime._nanoseconds().rem_euclid(MAX_TZ_OFFSET_NS);
        let mut out = String::new();
        format_clock_into(
            &mut out,
            u8::try_from(ns / NANOS_PER_HOUR).expect("hour fits in u8"),
            u8::try_from(ns / NANOS_PER_MINUTE % 60).expect("minute fits in u8"),
            u8::try_from(ns / NANOS_PER_SECOND % 60).expect("second fits in u8"),
            u32::try_from(ns % NANOS_PER_SECOND).expect("subsecond fits in u32"),
        );
        bex_str::BexStr::from(out)
    }
}

#[allow(clippy::used_underscore_items)] // Autogenerated from private BAML fields
impl BamlClassTimeTimeZoneOffset for PackageBamlImpl {
    fn from_duration(vm: &mut BexVm, duration: &Value) -> Result<Value, VmRustFnError> {
        let nanos = {
            let instance = vm
                .as_instance(duration)
                .expect("TimeZoneOffset.from_duration: expected Duration instance");
            view::time::Duration { instance }._nanoseconds()
        };
        let offset_ns = i64::try_from(&*nanos)

View on GitHub (pinned to bd85ce9dee)