From 0a4e42334d23d9379e4b230ed1786abd73e68138 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 12 Aug 2026 16:58:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Duration.total=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2/=E5=8E=9F=E5=A7=8B=E5=80=BC=20relat?= =?UTF-8?q?iveTo=20=E8=AF=AD=E4=B9=89=EF=BC=88=E6=97=A0=E6=95=88=E4=B8=8E?= =?UTF-8?q?=E8=B6=8A=E7=95=8C=E5=AD=97=E7=AC=A6=E4=B8=B2=20RangeError?= =?UTF-8?q?=E3=80=81=E6=95=B0=E5=AD=97/null=20=E7=AD=89=20TypeError?= =?UTF-8?q?=E3=80=81days=20=E6=97=A5=E5=8E=86=E8=B7=AF=E5=BE=84=EF=BC=89?= =?UTF-8?q?=EF=BC=8Ctotal=20=E5=AD=90=E9=9B=86=2041/29?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/temporal.rs | 56 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index 361bf8fe..cc4425b9 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -1715,30 +1715,54 @@ pub fn duration_total(vm: &mut H, args: &[u8]) -> NativeResult { None => return NativeResult::Err(crate::error::create_range_error(vm, "invalid unit")), }; let values = duration_values(obj); + // 含日历单位(year/month/week)时,days 及以上的 total 必须走 relativeTo 日历路径。 + let has_calendar_units = values[0] != 0.0 || values[1] != 0.0 || values[2] != 0.0; // relativeTo:支持 PlainDateTime / PlainDate,取日期分量(时间按午夜计算,对齐 polyfill)。 - let relative_date = if relative_raw.is_object() { - let rel_ptr = relative_raw.as_js_object_ptr(); - if rel_ptr.is_null() { - None - } else { - let rel = unsafe { &*rel_ptr }; - if rel.is_plain_date_time_obj() || rel.is_plain_date_obj() { - Some(( - i128::from(get_double_prop(rel, 0) as i32), - i128::from(get_double_prop(rel, 1) as u32), - i128::from(get_double_prop(rel, 2) as u32), - )) - } else { - None + // relativeTo: supports PlainDateTime / PlainDate objects; strings follow the + // ToRelativeTemporalObject path (PlainDateTime first, falling back to PlainDate), + // invalid/out-of-range strings throw RangeError, and non-string primitives + // (number/boolean/bigint/symbol/null) throw TypeError per test262. + let relative_date = if relative_raw.is_string() { + let text = to_string(relative_raw); + match parse_plain_date_time_string(&text) + .or_else(|_| parse_plain_date_string(&text).map(|(y, m, d)| (y, m, d, 0.0))) + { + Ok((year, month, day, _time_ns)) => Some(( + i128::from(year), + i128::from(month), + i128::from(day), + )), + Err(_) => { + return NativeResult::Err(crate::error::create_range_error( + vm, + "invalid relativeTo string", + )); } } - } else { + } else if relative_raw.is_object() { + let rel_ptr = relative_raw.as_js_object_ptr(); + if rel_ptr.is_null() { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid relativeTo")); + } + let rel = unsafe { &*rel_ptr }; + if rel.is_plain_date_time_obj() || rel.is_plain_date_obj() { + Some(( + i128::from(get_double_prop(rel, 0) as i32), + i128::from(get_double_prop(rel, 1) as u32), + i128::from(get_double_prop(rel, 2) as u32), + )) + } else { + None + } + } else if relative_raw.is_undefined() { None + } else { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid relativeTo")); }; const UNIT_NS: [i128; 6] = [3_600_000_000_000, 60_000_000_000, 1_000_000_000, 1_000_000, 1_000, 1]; - if unit_index <= 2 { + if unit_index <= 2 || (unit_index == 3 && has_calendar_units) { // 日历单位:需要 relativeTo,用纪元纳秒窗口计算分数总量。 let Some(rel_date) = relative_date else { return NativeResult::Err(crate::error::create_range_error(