Skip to content
Prev Previous commit
Next Next commit
Fix duration conversion rounding for os.utime
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
  • Loading branch information
Copilot and youknowone committed Dec 25, 2025
commit da24a782e24c8b3b6bd3b5f2baa04a9922bb6a5c
33 changes: 29 additions & 4 deletions crates/vm/src/convert/try_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,7 @@ impl TryFromObject for std::time::Duration {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
if let Some(float) = obj.downcast_ref::<PyFloat>() {
let f = float.to_f64();
if f < 0.0 {
return Err(vm.new_value_error("negative duration"));
}
Ok(Self::from_secs_f64(f))
duration_from_f64_floor(f, vm)
} else if let Some(int) = obj.try_index_opt(vm) {
let int = int?;
let bigint = int.as_bigint();
Expand All @@ -149,3 +146,31 @@ impl TryFromObject for std::time::Duration {
}
}
}

fn duration_from_f64_floor(f: f64, vm: &VirtualMachine) -> PyResult<std::time::Duration> {
const NANOS_PER_SEC: f64 = 1_000_000_000.0;
const NANOS_PER_SEC_U128: u128 = 1_000_000_000;
const MAX_TOTAL_NANOS: u128 =
((u64::MAX as u128) * NANOS_PER_SEC_U128) + (NANOS_PER_SEC_U128 - 1);

if !f.is_finite() {
return Err(vm.new_value_error("value out of range"));
}
if f < 0.0 {
return Err(vm.new_value_error("negative duration"));
}

let total_nanos = (f * NANOS_PER_SEC).floor();
if total_nanos.is_sign_negative() {
return Err(vm.new_value_error("negative duration"));
}
if total_nanos > MAX_TOTAL_NANOS as f64 {
return Err(vm.new_value_error("value out of range"));
}

let total_nanos = total_nanos as u128;
let secs = (total_nanos / NANOS_PER_SEC_U128) as u64;
let nanos = (total_nanos % NANOS_PER_SEC_U128) as u32;

Ok(std::time::Duration::new(secs, nanos))
}