From 46cfc2724ada17d22cb81e3fc4a1c7e469801179 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 17 Aug 2026 02:40:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Temporal.PlainTime=20until/since=20?= =?UTF-8?q?=E5=B7=AE=E5=80=BC=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/temporal.rs | 44 +++++++++++++++++++ crates/oxide_vm/src/bindings/bind_temporal.rs | 2 + .../oxide_vm/tests/builtins_temporal_tests.rs | 38 ++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index 928b034..97355c9 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -4592,6 +4592,50 @@ pub fn plain_time_compare(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(result)) } +// ───────────────────── PlainTime until / since ───────────────────── + +/// until/since 核心:receiver 与 other 归一为午夜后纳秒,经 `difference_core` 计算差值。 +/// +/// # 步骤 +/// 1. branding receiver 取槽 0 ns,other 经 `plain_time_like_ns` 归一。 +/// 2. `parse_difference_settings` 解析 options(default_largest=4=hour)。 +/// 3. 日期固定为 epoch(0,0,0),调 `difference_core` 输出 Duration。 +/// +/// # 边界与前提 +/// - since 复用 same 语义并整体取反(difference_core 内部处理),不调换两端。 +fn plain_time_difference(vm: &mut H, args: &[u8], since: bool) -> 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), + }; + let options_value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + let settings = match parse_difference_settings(vm, options_value, false, 4) { + Ok(settings) => settings, + Err(error) => return NativeResult::Err(error), + }; + difference_core(vm, (0, 0, 0), receiver_ns as i128, (0, 0, 0), other_ns as i128, settings, since) +} + +/// `Temporal.PlainTime.prototype.until(other, options)`。 +pub fn plain_time_until(vm: &mut H, args: &[u8]) -> NativeResult { + plain_time_difference(vm, args, false) +} + +/// `Temporal.PlainTime.prototype.since(other, options)`。 +pub fn plain_time_since(vm: &mut H, args: &[u8]) -> NativeResult { + plain_time_difference(vm, args, true) +} + // ───────────────────── 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 1eac0f6..9243cee 100644 --- a/crates/oxide_vm/src/bindings/bind_temporal.rs +++ b/crates/oxide_vm/src/bindings/bind_temporal.rs @@ -692,6 +692,8 @@ pub fn bind_temporal(core: &Arc, session: &KernelSession, global: &m ), ("valueOf", oxide_builtins::temporal::plain_time_value_of:: as *const (), 0), ("equals", oxide_builtins::temporal::plain_time_equals:: as *const (), 1), + ("until", oxide_builtins::temporal::plain_time_until:: as *const (), 1), + ("since", oxide_builtins::temporal::plain_time_since:: 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 4643116..bc16fcc 100644 --- a/crates/oxide_vm/tests/builtins_temporal_tests.rs +++ b/crates/oxide_vm/tests/builtins_temporal_tests.rs @@ -668,6 +668,44 @@ fn plain_time_compare_epoch_order() { assert_eq!(num(&mut vm, "Temporal.PlainTime.compare('23:59', '00:00')"), 1.0); } +#[test] +fn plain_time_until_since_normal() { + let mut vm = Vm::new(); + let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).until(new Temporal.PlainTime(13, 30)).toString()").unwrap(); + assert_eq!(str_val(&vm, r), "PT1H"); + let r = eval(&mut vm, "new Temporal.PlainTime(13, 30).since(new Temporal.PlainTime(12, 30)).toString()").unwrap(); + assert_eq!(str_val(&vm, r), "PT1H"); + let r = eval( + &mut vm, + "new Temporal.PlainTime(12, 30).until(new Temporal.PlainTime(13, 30, 5, 500)).toString()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "PT1H5.5S"); +} + +#[test] +fn plain_time_until_since_options_and_direction() { + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "new Temporal.PlainTime(12, 30).until(new Temporal.PlainTime(13, 35), { largestUnit: 'minute' }).toString()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "PT65M"); + let r = eval( + &mut vm, + "new Temporal.PlainTime(12, 30).since(new Temporal.PlainTime(13, 35), { largestUnit: 'minute' }).toString()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "-PT65M"); + let r = eval( + &mut vm, + "new Temporal.PlainTime(12, 30).until(new Temporal.PlainTime(13, 30), { smallestUnit: 'hour' }).toString()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "PT1H"); +} + // -- Temporal.PlainDateTime -- #[test]