From 9b3663bcb6d9c0cea984750287abf2e03fd36cf1 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Fri, 10 Apr 2026 11:40:06 +0200 Subject: [PATCH 1/2] PlainTime.prototype.add and add_duration_to_time --- .../builtins/temporal/plain_time.rs | 51 ++++++++++++++++++- .../plain_time/plain_time_prototype.rs | 40 +++++++++++++-- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index 29ad116e7..f7359f306 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -14,8 +14,9 @@ use crate::{ ecmascript::{ Agent, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, OrdinaryObject, ProtoIntrinsics, Value, object_handle, ordinary_populate_from_constructor, + temporal_err_to_js_err, to_temporal_duration, }, - engine::{Bindable, GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -133,3 +134,51 @@ pub(crate) fn create_temporal_plain_time<'gc>( .unwrap(), ) } + +/// [4.5.18 AddDurationToTime ( operation, temporalTime, temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtotime) +/// +/// The abstract operation AddDurationToTime takes arguments operation +/// (either add or subtract), temporalTime (a Temporal.PlainTime), and +/// temporalDurationLike (an ECMAScript language value) and returns either +/// a normal completion containing a Temporal.PlainTime or a throw completion. +/// It adds/subtracts temporalDurationLike to/from temporalTime, returning a +/// point in time that is in the future/past relative to temporalTime. +/// It performs the following steps when called: +fn add_duration_to_time<'gc, const IS_ADD: bool>( + agent: &mut Agent, + plan_time: TemporalPlainTime, + duration: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, TemporalPlainTime<'gc>> { + let duration = duration.bind(gc.nogc()); + let mut plain_time = plan_time.bind(gc.nogc()); + + // 1. Let duration be ? ToTemporalDuration(temporalDurationLike). + let duration = if let Value::Duration(duration) = duration { + duration.get(agent).duration + } else { + let scoped_instant = plain_time.scope(agent, gc.nogc()); + let res = to_temporal_duration(agent, duration.unbind(), gc.reborrow()).unbind()?; + // SAFETY: not shared + unsafe { + plain_time = scoped_instant.take(agent); + } + res + }; + + // If operation is subtract, set duration to CreateNegatedTemporalDuration(duration). + // 3. Let internalDuration be ToInternalDurationRecord(duration). + // 4. Let result be AddTime(temporalTime.[[Time]], internalDuration.[[Time]]). + let ns_result = if IS_ADD { + temporal_rs::PlainTime::add(plain_time.inner_plain_time(agent), &duration) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc())) + .unbind()? + } else { + temporal_rs::PlainTime::subtract(plain_time.inner_plain_time(agent), &duration) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc())) + .unbind()? + }; + + // 5. Return ! CreateTemporalTime(result). + Ok(create_temporal_plain_time(agent, ns_result, None, gc).unwrap()) +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs index 5b2aeb211..f3114480a 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs @@ -5,10 +5,13 @@ use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, JsResult, - PropertyKey, Realm, String, Value, builders::OrdinaryObjectBuilder, - builtins::temporal::plain_time::require_internal_slot_temporal_plain_time, + PropertyKey, Realm, String, Value, + builders::OrdinaryObjectBuilder, + builtins::temporal::plain_time::{ + add_duration_to_time, require_internal_slot_temporal_plain_time, + }, }, - engine::{GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope}, heap::WellKnownSymbols, }; @@ -73,6 +76,13 @@ impl Builtin for TemporalPlainTimePrototypeGetNanosecond { } impl BuiltinGetter for TemporalPlainTimePrototypeGetNanosecond {} +struct TemporalPlainTimePrototypeAdd; +impl Builtin for TemporalPlainTimePrototypeAdd { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.add; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::add); +} + impl TemporalPlainTimePrototype { /// ### [4.3.4 get Temporal.PlainTime.prototype.minute](https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute) pub(crate) fn get_minute<'gc>( @@ -169,6 +179,27 @@ impl TemporalPlainTimePrototype { Ok(value.into()) } + /// ### [4.3.9 Temporal.PlainTime.prototype.add ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.add) + fn add<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let duration = args.get(0).bind(gc.nogc()); + // 1. Let plainTime be the this value. + let plain_time = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]). + let plain_time = + require_internal_slot_temporal_plain_time(agent, plain_time.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? AddDurationToTime(add, plainTime, temporalDurationLike). + const ADD: bool = true; + add_duration_to_time::(agent, plain_time.unbind(), duration.unbind(), gc) + .map(Value::from) + } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let this = intrinsics.temporal_plain_time_prototype(); @@ -176,7 +207,7 @@ impl TemporalPlainTimePrototype { let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(8) + .with_property_capacity(9) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) .with_builtin_function_getter_property::() @@ -185,6 +216,7 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .with_builtin_function_getter_property::() .with_builtin_function_getter_property::() + .with_builtin_function_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into()) From e2abb16a2f926304ff27cabfbb18f72aa46e62a2 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Fri, 10 Apr 2026 11:46:25 +0200 Subject: [PATCH 2/2] PlainTime.prototype.subtract --- .../plain_time/plain_time_prototype.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs index f3114480a..e2f366e30 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs @@ -83,6 +83,13 @@ impl Builtin for TemporalPlainTimePrototypeAdd { const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::add); } +struct TemporalPlainTimePrototypeSubtract; +impl Builtin for TemporalPlainTimePrototypeSubtract { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.subtract; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::subtract); +} + impl TemporalPlainTimePrototype { /// ### [4.3.4 get Temporal.PlainTime.prototype.minute](https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute) pub(crate) fn get_minute<'gc>( @@ -200,6 +207,27 @@ impl TemporalPlainTimePrototype { .map(Value::from) } + /// ### [4.3.10 Temporal.PlainTime.prototype.subtract ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract) + fn subtract<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let duration = args.get(0).bind(gc.nogc()); + // 1. Let plainTime be the this value. + let plain_time = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]). + let plain_time = + require_internal_slot_temporal_plain_time(agent, plain_time.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? AddDurationToTime(subtract, plainTime, temporalDurationLike). + const ADD: bool = false; + add_duration_to_time::(agent, plain_time.unbind(), duration.unbind(), gc) + .map(Value::from) + } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let this = intrinsics.temporal_plain_time_prototype(); @@ -207,7 +235,7 @@ impl TemporalPlainTimePrototype { let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(9) + .with_property_capacity(10) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) .with_builtin_function_getter_property::() @@ -217,6 +245,7 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .with_builtin_function_getter_property::() .with_builtin_function_property::() + .with_builtin_function_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into())