diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index 29ad116e7..caea41f05 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -9,13 +9,20 @@ mod plain_time_prototype; pub(crate) use data::*; pub(crate) use plain_time_constructor::*; pub(crate) use plain_time_prototype::*; +use sonic_rs::{Object, value::object::IterMut}; +use temporal_rs::{ + duration, + options::{Unit, UnitGroup}, +}; use crate::{ ecmascript::{ - Agent, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, OrdinaryObject, - ProtoIntrinsics, Value, object_handle, ordinary_populate_from_constructor, + Agent, DurationRecord, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, + Object, OrdinaryObject, ProtoIntrinsics, String, TemporalDuration, Value, + get_difference_settings, get_options_object, object_handle, + ordinary_populate_from_constructor, temporal_err_to_js_err, }, - engine::{Bindable, GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -133,3 +140,162 @@ pub(crate) fn create_temporal_plain_time<'gc>( .unwrap(), ) } + +/// ### [4.5.6 ToTemporalTime ( item [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime) +/// +/// The abstract operation ToTemporalTime takes argument item (an ECMAScript language value) and optional argument +/// options (an ECMAScript language value) and returns either a normal completion containing a Temporal.PlainTime +/// or a throw Completion. Converts item to a new Temporal.PlainTime instance if possible, and throws otherwise. +pub(crate) fn to_temporal_time<'gc>( + agent: &mut Agent, + item: Value, + options: Option, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, temporal_rs::PlainTime> { + let item = item.bind(gc.nogc()); + + // 1. If options is not present, set options to undefined. + let options = options.unwrap_or(Value::Undefined); + + // 2. If item is an Object, then + if let Ok(item) = Object::try_from(item) { + // a. If item has an [[InitializedTemporalTime]] internal slot, then + if let Ok(time) = TemporalPlainTime::try_from(item) { + // i. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?; + // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). + get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?; + // iii. Return ! CreateTemporalTime(item.[[Time]]). + return Ok(*time.inner_plain_time(agent)); + } + + // b. If item has an [[InitializedTemporalDateTime]] internal slot, then + // i. Let resolvedOptions be ? GetOptionsObject(options). + // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). + // iii. Return ! CreateTemporalTime(item.[[ISODateTime]].[[Time]]). + + // c. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + // i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]). + // ii. Let resolvedOptions be ? GetOptionsObject(options). + // iii. Perform ? GetTemporalOverflowOption(resolvedOptions). + // iv. Return ! CreateTemporalTime(isoDateTime.[[Time]]). + + // d. Let result be ? ToTemporalTimeRecord(item). + let result = to_temporal_time_record(agent, item.unbind(), gc.reborrow()).unbind()?; + // e. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?; + // f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). + let overflow = + get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?; + // g. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], overflow). + return result + .regulate(overflow) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc())); + } + + // 3. Else, + // a. If item is not a String, throw a TypeError exception. + let Ok(item) = String::try_from(item) else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Item is not a String", + gc.into_nogc(), + )); + }; + + // b. Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »). + // c. Assert: parseResult.[[Time]] is not start-of-day. + // d. Set result to parseResult.[[Time]]. + // e. NOTE: A successful parse using TemporalTimeString guarantees absence of ambiguity with respect to any ISO 8601 date-only, year-month, or month-day representation. + let result = temporal_rs::PlainTime::from_utf8(item.as_bytes(agent)) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))?; + // f. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(agent, options, gc.reborrow()).unbind()?; + // g. Perform ? GetTemporalOverflowOption(resolvedOptions). + get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?; + + // 4. Return ! CreateTemporalTime(result). + Ok(result) +} + +/// ### [4.5.17 DifferenceTemporalPlainTime ( operation, temporalTime, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaintime) +/// The abstract operation DifferenceTemporalPlainTime takes arguments +/// operation (either since or until), temporalTime (a Temporal.PlainTime), +/// other (an ECMAScript language value), and options +/// (an ECMAScript language value) and returns either +/// a normal completion containing a Temporal.Duration or a +/// throw completion. It computes the difference between the +/// two times represented by temporalTime and other, optionally +/// rounds it, and returns it as a Temporal.Duration object. +fn difference_temporal_plain_time<'gc, const IS_UNTIL: bool>( + agent: &mut Agent, + plain_time: TemporalPlainTime, + other: Value, + options: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, TemporalDuration<'gc>> { + let plain_time = plain_time.scope(agent, gc.nogc()); + let other = other.bind(gc.nogc()); + let options = options.scope(agent, gc.nogc()); + // 1. Set other to ? ToTemporalTime(other). + let other = to_temporal_time(agent, other.unbind(), options, gc.reborrow()); + // 2. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_option = get_options_object(agent, options.get(agent), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, + // time, « », nanosecond, hour). + // 4. Let timeDuration be + // DifferenceTime(temporalTime.[[Time]], other.[[Time]]). + // 5. Set timeDuration to ! RoundTimeDuration(timeDuration, + // settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + // 6. Let duration be + // CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration). + // 7. Let result be ! + // TemporalDurationFromInternal(duration, settings.[[LargestUnit]]). + // 8. If operation is since, set result to + // CreateNegatedTemporalDuration(result). + let duration = if IS_UNTIL { + const UNTIL: bool = true; + let settings = get_difference_settings::( + agent, + resolved_option.unbind(), + UnitGroup::Time, + &[], + Unit::Nanosecond, + Unit::Hour, + gc.reborrow(), + ) + .unbind()?; + temporal_rs::PlainTime::until( + plain_time.get(agent).inner_plain_time(agent), + &other.unbind()?, + settings, + ) + } else { + const SINCE: bool = false; + let settings = get_difference_settings::( + agent, + resolved_option.unbind(), + UnitGroup::Time, + &[], + Unit::Nanosecond, + Unit::Hour, + gc.reborrow(), + ) + .unbind()?; + temporal_rs::PlainTime::since( + plain_time.get(agent).inner_plain_time(agent), + &other.unbind()?, + settings, + ) + }; + let gc = gc.into_nogc(); + let duration = duration.map_err(|err| temporal_err_to_js_err(agent, err, gc))?; + + // 9. Return result. + Ok(agent.heap.create(DurationRecord { + object_index: None, + duration, + })) +} 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..025acf640 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 @@ -6,9 +6,9 @@ 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, + builtins::temporal::plain_time::{self, require_internal_slot_temporal_plain_time}, }, - engine::{GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope}, heap::WellKnownSymbols, }; @@ -73,6 +73,20 @@ impl Builtin for TemporalPlainTimePrototypeGetNanosecond { } impl BuiltinGetter for TemporalPlainTimePrototypeGetNanosecond {} +struct TemporalPlainTimePrototypeUntil; +impl Builtin for TemporalPlainTimePrototypeUntil { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.until; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::until); +} + +struct TemporalPlainTimePrototypeSince; +impl Builtin for TemporalPlainTimePrototypeSince { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.since; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::since); +} + 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 +183,60 @@ impl TemporalPlainTimePrototype { Ok(value.into()) } + /// ### [4.3.12 Temporal.PlainTime.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until) + fn until<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>>{ + let other = args.get(0).bind(gc.nogc()); + let options = args.get(1).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 ? DifferenceTemporalPlainTime(until, plainTime, other, options). + const UNTIL: bool = true; + difference_temporal_plain_time::( + agent, + plain_time.unbind(), + other.unbind(), + options.unbind(), + gc, + ) + .map(Value::from) + } + + /// ### [4.3.13 Temporal.PlainTime.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since) + fn since<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let other = args.get(0).bind(gc.nogc()); + let options = args.get(1).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 ? DifferenceTemporalPlainTime(since, instant, other, options). + const SINCE: bool = false; + difference_temporal_plain_time::( + agent, + plain_time.unbind(), + other.unbind(), + options.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 +244,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(10) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) .with_builtin_function_getter_property::() @@ -185,6 +253,8 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .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())