From ac8c18e9e9f019c20641308bb3edd5b8b1be22dc Mon Sep 17 00:00:00 2001 From: youknowdot Date: Thu, 15 Oct 2026 13:22:13 +0900 Subject: [PATCH] Align native datetime conversion with Python 3.15 Prefer index conversion for timestamps, accept float-convertible values, validate ISO timezone components, and apply final local fold detection consistently. Implementation, source comparison, and bounded validation were assisted by OpenAI Codex. Assisted-by: OpenAI Codex:model version unavailable --- crates/stdlib/src/_datetime.rs | 90 ++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/crates/stdlib/src/_datetime.rs b/crates/stdlib/src/_datetime.rs index dc89b9bbdb0..d88a9565263 100644 --- a/crates/stdlib/src/_datetime.rs +++ b/crates/stdlib/src/_datetime.rs @@ -1527,7 +1527,8 @@ mod _datetime { vals: &mut [i32; 4], tzoffset: &mut i32, tzmicrosecond: &mut i32, - ) -> i32 { + vm: &VirtualMachine, + ) -> PyResult { let p_end = start + len; let mut tzinfo_pos = start; loop { @@ -1542,18 +1543,18 @@ mod _datetime { } let rv = parse_hh_mm_ss_ff(s, start, tzinfo_pos, vals); if rv < 0 { - return rv; + return Ok(rv); } else if tzinfo_pos == p_end { - return if rv == 1 { -5 } else { 0 }; + return Ok(if rv == 1 { -5 } else { 0 }); } if byte_at(s, tzinfo_pos) == b'Z' { *tzoffset = 0; *tzmicrosecond = 0; - return if byte_at(s, tzinfo_pos + 1) != 0 { + return Ok(if byte_at(s, tzinfo_pos + 1) != 0 { -5 } else { 1 - }; + }); } let tzsign = if byte_at(s, tzinfo_pos) == b'-' { -1 @@ -1562,9 +1563,10 @@ mod _datetime { }; let mut tz = [0; 4]; let rv = parse_hh_mm_ss_ff(s, tzinfo_pos + 1, p_end, &mut tz); + check_time_args(tz[0], tz[1], tz[2], tz[3], 0, vm)?; *tzoffset = tzsign * (tz[0] * 3600 + tz[1] * 60 + tz[2]); *tzmicrosecond = tzsign * tz[3]; - if rv != 0 { -5 } else { 1 } + Ok(if rv != 0 { -5 } else { 1 }) } fn tzinfo_from_isoformat_results( @@ -1748,18 +1750,15 @@ mod _datetime { /// `_PyTime_ObjectToTime_t` with floor rounding. fn object_to_time_t(obj: &PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(float) = obj.downcast_ref::() { - let d = float.to_f64(); - if d.is_nan() { - return Err(vm.new_value_error("Invalid value NaN (not a number)")); - } - let d = d.floor(); - if !(-9.223_372_036_854_776e18..9.223_372_036_854_776e18).contains(&d) { - return Err(time_t_overflow(vm)); - } - return Ok(d as i64); + if obj.number().is_index() { + return long_to_time_t(obj, vm); + } + let d = timestamp_as_double(obj, vm)?; + let d = d.floor(); + if !(-9.223_372_036_854_776e18..9.223_372_036_854_776e18).contains(&d) { + return Err(time_t_overflow(vm)); } - long_to_time_t(obj, vm) + Ok(d as i64) } fn time_t_overflow(vm: &VirtualMachine) -> crate::vm::builtins::PyBaseExceptionRef { @@ -1772,29 +1771,37 @@ mod _datetime { int.as_bigint().to_i64().ok_or_else(|| time_t_overflow(vm)) } + fn timestamp_as_double(obj: &PyObject, vm: &VirtualMachine) -> PyResult { + let d = match obj.downcast_ref::() { + Some(float) => float.to_f64(), + None => obj.try_float(vm)?.to_f64(), + }; + if d.is_nan() { + return Err(vm.new_value_error("Invalid value NaN (not a number)")); + } + Ok(d) + } + /// `_PyTime_ObjectToTimeval` with half-even rounding: whole seconds and microseconds. fn object_to_timeval(obj: &PyObject, vm: &VirtualMachine) -> PyResult<(i64, i32)> { - if let Some(float) = obj.downcast_ref::() { - let d = float.to_f64(); - if d.is_nan() { - return Err(vm.new_value_error("Invalid value NaN (not a number)")); - } - let mut intpart = d.trunc(); - let mut floatpart = (d - intpart) * 1e6; - floatpart = round_half_even(floatpart); - if floatpart >= 1e6 { - floatpart -= 1e6; - intpart += 1.0; - } else if floatpart < 0.0 { - floatpart += 1e6; - intpart -= 1.0; - } - if !(-9.223_372_036_854_776e18..9.223_372_036_854_776e18).contains(&intpart) { - return Err(time_t_overflow(vm)); - } - return Ok((intpart as i64, floatpart as i32)); + if obj.number().is_index() { + return Ok((long_to_time_t(obj, vm)?, 0)); + } + let d = timestamp_as_double(obj, vm)?; + let mut intpart = d.trunc(); + let mut floatpart = (d - intpart) * 1e6; + floatpart = round_half_even(floatpart); + if floatpart >= 1e6 { + floatpart -= 1e6; + intpart += 1.0; + } else if floatpart < 0.0 { + floatpart += 1e6; + intpart -= 1.0; + } + if !(-9.223_372_036_854_776e18..9.223_372_036_854_776e18).contains(&intpart) { + return Err(time_t_overflow(vm)); } - Ok((long_to_time_t(obj, vm)?, 0)) + Ok((intpart as i64, floatpart as i32)) } fn round_half_even(x: f64) -> f64 { @@ -3024,7 +3031,8 @@ mod _datetime { let len = bytes.len() - start; let mut vals = [0; 4]; let (mut tzoffset, mut tzusec) = (0, 0); - let rv = parse_isoformat_time(bytes, start, len, &mut vals, &mut tzoffset, &mut tzusec); + let rv = + parse_isoformat_time(bytes, start, len, &mut vals, &mut tzoffset, &mut tzusec, vm)?; if rv < 0 { return Err(invalid_isoformat(&tstr, vm)?); } @@ -3492,8 +3500,7 @@ mod _datetime { let tm = platform_time(timet, local_tm, vm)?; let second = tm.second.min(59); let mut fold = 0; - let skip_probe = cfg!(windows) && timet - MAX_FOLD_SECONDS <= 0; - if tzinfo.is_none() && local_tm && !skip_probe { + if tzinfo.is_none() && local_tm { let result_seconds = utc_to_seconds(tm.year, tm.month, tm.day, tm.hour, tm.minute, second, vm)?; let probe_seconds = local(EPOCH_SECONDS + timet - MAX_FOLD_SECONDS, vm)?; @@ -3896,7 +3903,8 @@ mod _datetime { } }; len = len.saturating_sub(p); - rv = parse_isoformat_time(bytes, p, len, &mut vals, &mut tzoffset, &mut tzusec); + rv = + parse_isoformat_time(bytes, p, len, &mut vals, &mut tzoffset, &mut tzusec, vm)?; } if rv < 0 { return Err(invalid_isoformat(&dtstr, vm)?);