From 9657cce3110e840345cfaeb5fa556fe31acd06a0 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 17 Aug 2026 02:34:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Temporal.PlainTime=20equals/compare=20?= =?UTF-8?q?=E6=AF=94=E8=BE=83=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/temporal.rs | 42 +++++++++++++++ crates/oxide_vm/src/bindings/bind_temporal.rs | 6 ++- .../oxide_vm/tests/builtins_temporal_tests.rs | 51 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index 2d9c6e6..928b034 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -4550,6 +4550,48 @@ pub fn plain_time_from(vm: &mut H, args: &[u8]) -> NativeResult { } } +/// `Temporal.PlainTime.prototype.equals(other)`:各分量全等返回 true,否则 false。 +/// +/// # 边界与前提 +/// - other 经 `plain_time_like_ns` 归一(实例/字符串/property bag),无法转换抛 TypeError/RangeError。 +pub fn plain_time_equals(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = match receiver_obj(vm, args) { + Ok(p) => p, + Err(error) => return NativeResult::Err(error), + }; + let obj = unsafe { &*ptr }; + if let Err(error) = ensure_plain_time(vm, obj) { + return NativeResult::Err(error); + } + let receiver_ns = get_double_prop(obj, 0); + let other_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let other_ns = match plain_time_like_ns(vm, other_val) { + Ok(ns) => ns, + Err(error) => return NativeResult::Err(error), + }; + NativeResult::Ok(JsValue::bool(receiver_ns as i128 == other_ns as i128)) +} + +/// `Temporal.PlainTime.compare(one, two)`:静态比较,按午夜后纳秒返回 -1、0 或 1。 +pub fn plain_time_compare(vm: &mut H, args: &[u8]) -> NativeResult { + let one = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let two = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + let one_ns = match plain_time_like_ns(vm, one) { + Ok(ns) => ns, + Err(error) => return NativeResult::Err(error), + }; + let two_ns = match plain_time_like_ns(vm, two) { + Ok(ns) => ns, + Err(error) => return NativeResult::Err(error), + }; + let result = match (one_ns as i128).cmp(&(two_ns as i128)) { + std::cmp::Ordering::Less => -1, + std::cmp::Ordering::Equal => 0, + std::cmp::Ordering::Greater => 1, + }; + NativeResult::Ok(JsValue::int(result)) +} + // ───────────────────── PlainDateTime 基础方法 ───────────────────── /// `Temporal.PlainDateTime` 构造器:保存 ISO 日期与午夜后纳秒。 diff --git a/crates/oxide_vm/src/bindings/bind_temporal.rs b/crates/oxide_vm/src/bindings/bind_temporal.rs index 28c1854..1eac0f6 100644 --- a/crates/oxide_vm/src/bindings/bind_temporal.rs +++ b/crates/oxide_vm/src/bindings/bind_temporal.rs @@ -624,7 +624,10 @@ pub fn bind_temporal(core: &Arc, session: &KernelSession, global: &m world, plain_time_ctor, core, - &[("from", oxide_builtins::temporal::plain_time_from:: as *const (), 1)], + &[ + ("from", oxide_builtins::temporal::plain_time_from:: as *const (), 1), + ("compare", oxide_builtins::temporal::plain_time_compare:: as *const (), 2), + ], ); let plain_time_proto_ptr = world.plain_time_proto.as_ptr() as *mut JsObject; @@ -688,6 +691,7 @@ pub fn bind_temporal(core: &Arc, session: &KernelSession, global: &m 0, ), ("valueOf", oxide_builtins::temporal::plain_time_value_of:: as *const (), 0), + ("equals", oxide_builtins::temporal::plain_time_equals:: as *const (), 1), ], ); bind_well_known_data_property( diff --git a/crates/oxide_vm/tests/builtins_temporal_tests.rs b/crates/oxide_vm/tests/builtins_temporal_tests.rs index 64da5fa..4643116 100644 --- a/crates/oxide_vm/tests/builtins_temporal_tests.rs +++ b/crates/oxide_vm/tests/builtins_temporal_tests.rs @@ -617,6 +617,57 @@ fn plain_time_range_validation() { assert_eq!(str_val(&vm, r), "RangeError"); } +#[test] +fn plain_time_equals_same_and_different_fields() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Temporal.PlainTime(12, 30, 5).equals(new Temporal.PlainTime(12, 30, 5))").unwrap(); + assert!(r.as_bool()); + let r = eval(&mut vm, "new Temporal.PlainTime(12, 30, 5).equals(new Temporal.PlainTime(12, 30, 6))").unwrap(); + assert!(!r.as_bool()); + let r = eval(&mut vm, "new Temporal.PlainTime(12, 30, 5).equals('12:30:05')").unwrap(); + assert!(r.as_bool()); +} + +#[test] +fn plain_time_equals_invalid_argument_throws() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "try { new Temporal.PlainTime(12).equals(42) } catch (e) { e.constructor.name }").unwrap(); + assert_eq!(str_val(&vm, r), "TypeError"); + let r = eval( + &mut vm, + "try { Temporal.PlainTime.prototype.equals.call({}) } catch (e) { e.constructor.name }", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "TypeError"); +} + +#[test] +fn plain_time_compare_epoch_order() { + let mut vm = Vm::new(); + assert_eq!( + num( + &mut vm, + "Temporal.PlainTime.compare(new Temporal.PlainTime(12, 30), new Temporal.PlainTime(13, 30))" + ), + -1.0 + ); + assert_eq!( + num( + &mut vm, + "Temporal.PlainTime.compare(new Temporal.PlainTime(12, 30), new Temporal.PlainTime(12, 30))" + ), + 0.0 + ); + assert_eq!( + num( + &mut vm, + "Temporal.PlainTime.compare(new Temporal.PlainTime(13, 30), new Temporal.PlainTime(12, 30))" + ), + 1.0 + ); + assert_eq!(num(&mut vm, "Temporal.PlainTime.compare('23:59', '00:00')"), 1.0); +} + // -- Temporal.PlainDateTime -- #[test]