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 c30ce60fd..c5bd6dd33 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 @@ -2,16 +2,20 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use temporal_rs::options::{RoundingMode, RoundingOptions, Unit}; + use crate::{ ecmascript::{ - Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, JsResult, - PropertyKey, Realm, String, Value, + Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, + ExceptionType, JsResult, PropertyKey, Realm, String, StringOptionType, Value, builders::OrdinaryObjectBuilder, builtins::temporal::plain_time::{ add_duration_to_time, require_internal_slot_temporal_plain_time, }, + create_temporal_plain_time, get_options_object, get_rounding_increment_option, + get_rounding_mode_option, get_temporal_unit_valued_option, temporal_err_to_js_err, }, - engine::{Bindable, GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::WellKnownSymbols, }; @@ -90,6 +94,12 @@ impl Builtin for TemporalPlainTimePrototypeSubtract { const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::subtract); } +struct TemporalPlainTimePrototypeRound; +impl Builtin for TemporalPlainTimePrototypeRound { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.round; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::round); +} 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>( @@ -228,6 +238,98 @@ impl TemporalPlainTimePrototype { .map(Value::from) } + /// ### [5.3.14 Temporal.PlainTime.prototype.round ( roundTo )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round) + fn round<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let this_value = this_value.bind(gc.nogc()); + let round_to = args.get(0).bind(gc.nogc()); + // 1. Let plainTime be the this value. + // 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]). + let plain_time = require_internal_slot_temporal_plain_time(agent, this_value, gc.nogc()) + .unbind()? + .scope(agent, gc.nogc()); + // 3. If roundTo is undefined, throw a TypeError exception. + if round_to.is_undefined() { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "rountTo cannot be undefined", + gc.into_nogc(), + )); + } + + // 4. If roundTo is a String, then + let options = if let Ok(round_to) = String::try_from(round_to) { + // a. Let paramString be roundTo. + // b. Set roundTo to OrdinaryObjectCreate(null). + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). + let mut options = RoundingOptions::default(); + options.smallest_unit = + Some(Unit::from_string(agent, round_to.unbind(), gc.nogc()).unbind()?); + options + } else { + // 5. Else, + // a. Set roundTo to ? GetOptionsObject(roundTo). + let round_to = get_options_object(agent, round_to, gc.nogc()) + .unbind()? + .map(|r| r.scope(agent, gc.nogc())); + // 6. NOTE: The following steps read options and perform independent validation in alphabetical + // order (GetRoundingIncrementOption reads "roundingIncrement" and GetRoundingModeOption reads "roundingMode"). + let mut options = RoundingOptions::default(); + + let (rounding_increment, rounding_mode, smallest_unit) = + if let Some(round_to) = round_to { + // 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo). + let rounding_increment = + get_rounding_increment_option(agent, round_to.get(agent), gc.reborrow()) + .unbind()?; + // 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand). + let rounding_mode = get_rounding_mode_option( + agent, + round_to.get(agent), + RoundingMode::default(), + gc.reborrow(), + ) + .unbind()?; + // 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", required). + let smallest_unit = get_temporal_unit_valued_option( + agent, + round_to.get(agent), + BUILTIN_STRING_MEMORY.smallestUnit.into(), + gc.reborrow(), + ) + .unbind()?; + (rounding_increment, rounding_mode, smallest_unit) + } else { + Default::default() + }; + + options.increment = Some(rounding_increment); + options.rounding_mode = Some(rounding_mode); + options.smallest_unit = smallest_unit; + options + }; + + // 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time). + // 11. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). + // 12. Assert: maximum is not unset. + // 13. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + // 14. Let result be RoundTime(plainTime.[[Time]], roundingIncrement, smallestUnit, roundingMode). + let result = plain_time + .get(agent) + .inner_plain_time(agent) + .round(options) + .map_err(|e| temporal_err_to_js_err(agent, e, gc.nogc())) + .unbind()?; + // 15. Return ! CreateTemporalTime(result). + Ok(create_temporal_plain_time(agent, result, None, gc) + .unwrap() + .into()) + } + 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(); @@ -246,6 +348,7 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .with_builtin_function_property::() .with_builtin_function_property::() + .with_builtin_function_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into())