Skip to content

Commit 1e33056

Browse files
committed
echmascript(temporal): PlainTime.prototype.since + until
1 parent b5548ba commit 1e33056

2 files changed

Lines changed: 242 additions & 6 deletions

File tree

nova_vm/src/ecmascript/builtins/temporal/plain_time.rs

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ mod plain_time_prototype;
99
pub(crate) use data::*;
1010
pub(crate) use plain_time_constructor::*;
1111
pub(crate) use plain_time_prototype::*;
12+
use sonic_rs::{Object, value::object::IterMut};
13+
use temporal_rs::{
14+
duration,
15+
options::{Unit, UnitGroup},
16+
};
1217

1318
use crate::{
1419
ecmascript::{
15-
Agent, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, OrdinaryObject,
16-
ProtoIntrinsics, Value, object_handle, ordinary_populate_from_constructor,
20+
Agent, DurationRecord, ExceptionType, Function, InternalMethods, InternalSlots, JsResult,
21+
Object, OrdinaryObject, ProtoIntrinsics, String, TemporalDuration, Value,
22+
get_difference_settings, get_options_object, object_handle,
23+
ordinary_populate_from_constructor, temporal_err_to_js_err,
1724
},
18-
engine::{Bindable, GcScope, NoGcScope},
25+
engine::{Bindable, GcScope, NoGcScope, Scopable},
1926
heap::{
2027
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
2128
HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
@@ -133,3 +140,162 @@ pub(crate) fn create_temporal_plain_time<'gc>(
133140
.unwrap(),
134141
)
135142
}
143+
144+
/// ### [4.5.6 ToTemporalTime ( item [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime)
145+
///
146+
/// The abstract operation ToTemporalTime takes argument item (an ECMAScript language value) and optional argument
147+
/// options (an ECMAScript language value) and returns either a normal completion containing a Temporal.PlainTime
148+
/// or a throw Completion. Converts item to a new Temporal.PlainTime instance if possible, and throws otherwise.
149+
pub(crate) fn to_temporal_time<'gc>(
150+
agent: &mut Agent,
151+
item: Value,
152+
options: Option<Value>,
153+
mut gc: GcScope<'gc, '_>,
154+
) -> JsResult<'gc, temporal_rs::PlainTime> {
155+
let item = item.bind(gc.nogc());
156+
157+
// 1. If options is not present, set options to undefined.
158+
let options = options.unwrap_or(Value::Undefined);
159+
160+
// 2. If item is an Object, then
161+
if let Ok(item) = Object::try_from(item) {
162+
// a. If item has an [[InitializedTemporalTime]] internal slot, then
163+
if let Ok(time) = TemporalPlainTime::try_from(item) {
164+
// i. Let resolvedOptions be ? GetOptionsObject(options).
165+
let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?;
166+
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
167+
get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;
168+
// iii. Return ! CreateTemporalTime(item.[[Time]]).
169+
return Ok(*time.inner_plain_time(agent));
170+
}
171+
172+
// b. If item has an [[InitializedTemporalDateTime]] internal slot, then
173+
// i. Let resolvedOptions be ? GetOptionsObject(options).
174+
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
175+
// iii. Return ! CreateTemporalTime(item.[[ISODateTime]].[[Time]]).
176+
177+
// c. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
178+
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]).
179+
// ii. Let resolvedOptions be ? GetOptionsObject(options).
180+
// iii. Perform ? GetTemporalOverflowOption(resolvedOptions).
181+
// iv. Return ! CreateTemporalTime(isoDateTime.[[Time]]).
182+
183+
// d. Let result be ? ToTemporalTimeRecord(item).
184+
let result = to_temporal_time_record(agent, item.unbind(), gc.reborrow()).unbind()?;
185+
// e. Let resolvedOptions be ? GetOptionsObject(options).
186+
let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?;
187+
// f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
188+
let overflow =
189+
get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;
190+
// g. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], overflow).
191+
return result
192+
.regulate(overflow)
193+
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()));
194+
}
195+
196+
// 3. Else,
197+
// a. If item is not a String, throw a TypeError exception.
198+
let Ok(item) = String::try_from(item) else {
199+
return Err(agent.throw_exception_with_static_message(
200+
ExceptionType::TypeError,
201+
"Item is not a String",
202+
gc.into_nogc(),
203+
));
204+
};
205+
206+
// b. Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »).
207+
// c. Assert: parseResult.[[Time]] is not start-of-day.
208+
// d. Set result to parseResult.[[Time]].
209+
// 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.
210+
let result = temporal_rs::PlainTime::from_utf8(item.as_bytes(agent))
211+
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))?;
212+
// f. Let resolvedOptions be ? GetOptionsObject(options).
213+
let resolved_options = get_options_object(agent, options, gc.reborrow()).unbind()?;
214+
// g. Perform ? GetTemporalOverflowOption(resolvedOptions).
215+
get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;
216+
217+
// 4. Return ! CreateTemporalTime(result).
218+
Ok(result)
219+
}
220+
221+
/// ### [4.5.17 DifferenceTemporalPlainTime ( operation, temporalTime, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaintime)
222+
/// The abstract operation DifferenceTemporalPlainTime takes arguments
223+
/// operation (either since or until), temporalTime (a Temporal.PlainTime),
224+
/// other (an ECMAScript language value), and options
225+
/// (an ECMAScript language value) and returns either
226+
/// a normal completion containing a Temporal.Duration or a
227+
/// throw completion. It computes the difference between the
228+
/// two times represented by temporalTime and other, optionally
229+
/// rounds it, and returns it as a Temporal.Duration object.
230+
fn difference_temporal_plain_time<'gc, const IS_UNTIL: bool>(
231+
agent: &mut Agent,
232+
plain_time: TemporalPlainTime,
233+
other: Value,
234+
options: Value,
235+
mut gc: GcScope<'gc, '_>,
236+
) -> JsResult<'gc, TemporalDuration<'gc>> {
237+
let plain_time = plain_time.scope(agent, gc.nogc());
238+
let other = other.bind(gc.nogc());
239+
let options = options.scope(agent, gc.nogc());
240+
// 1. Set other to ? ToTemporalTime(other).
241+
let other = to_temporal_time(agent, other.unbind(), options, gc.reborrow());
242+
// 2. Let resolvedOptions be ? GetOptionsObject(options).
243+
let resolved_option = get_options_object(agent, options.get(agent), gc.nogc())
244+
.unbind()?
245+
.bind(gc.nogc());
246+
// 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions,
247+
// time, « », nanosecond, hour).
248+
// 4. Let timeDuration be
249+
// DifferenceTime(temporalTime.[[Time]], other.[[Time]]).
250+
// 5. Set timeDuration to ! RoundTimeDuration(timeDuration,
251+
// settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
252+
// 6. Let duration be
253+
// CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
254+
// 7. Let result be !
255+
// TemporalDurationFromInternal(duration, settings.[[LargestUnit]]).
256+
// 8. If operation is since, set result to
257+
// CreateNegatedTemporalDuration(result).
258+
let duration = if IS_UNTIL {
259+
const UNTIL: bool = true;
260+
let settings = get_difference_settings::<UNTIL>(
261+
agent,
262+
resolved_option.unbind(),
263+
UnitGroup::Time,
264+
&[],
265+
Unit::Nanosecond,
266+
Unit::Hour,
267+
gc.reborrow(),
268+
)
269+
.unbind()?;
270+
temporal_rs::PlainTime::until(
271+
plain_time.get(agent).inner_plain_time(agent),
272+
&other.unbind()?,
273+
settings,
274+
)
275+
} else {
276+
const SINCE: bool = false;
277+
let settings = get_difference_settings::<SINCE>(
278+
agent,
279+
resolved_option.unbind(),
280+
UnitGroup::Time,
281+
&[],
282+
Unit::Nanosecond,
283+
Unit::Hour,
284+
gc.reborrow(),
285+
)
286+
.unbind()?;
287+
temporal_rs::PlainTime::since(
288+
plain_time.get(agent).inner_plain_time(agent),
289+
&other.unbind()?,
290+
settings,
291+
)
292+
};
293+
let gc = gc.into_nogc();
294+
let duration = duration.map_err(|err| temporal_err_to_js_err(agent, err, gc))?;
295+
296+
// 9. Return result.
297+
Ok(agent.heap.create(DurationRecord {
298+
object_index: None,
299+
duration,
300+
}))
301+
}

nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::{
66
ecmascript::{
77
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, JsResult,
88
PropertyKey, Realm, String, Value, builders::OrdinaryObjectBuilder,
9-
builtins::temporal::plain_time::require_internal_slot_temporal_plain_time,
9+
builtins::temporal::plain_time::{self, require_internal_slot_temporal_plain_time},
1010
},
11-
engine::{GcScope, NoGcScope},
11+
engine::{Bindable, GcScope, NoGcScope},
1212
heap::WellKnownSymbols,
1313
};
1414

@@ -73,6 +73,20 @@ impl Builtin for TemporalPlainTimePrototypeGetNanosecond {
7373
}
7474
impl BuiltinGetter for TemporalPlainTimePrototypeGetNanosecond {}
7575

76+
struct TemporalPlainTimePrototypeUntil;
77+
impl Builtin for TemporalPlainTimePrototypeUntil {
78+
const NAME: String<'static> = BUILTIN_STRING_MEMORY.until;
79+
const LENGTH: u8 = 1;
80+
const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::until);
81+
}
82+
83+
struct TemporalPlainTimePrototypeSince;
84+
impl Builtin for TemporalPlainTimePrototypeSince {
85+
const NAME: String<'static> = BUILTIN_STRING_MEMORY.since;
86+
const LENGTH: u8 = 1;
87+
const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::since);
88+
}
89+
7690
impl TemporalPlainTimePrototype {
7791
/// ### [4.3.4 get Temporal.PlainTime.prototype.minute](https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute)
7892
pub(crate) fn get_minute<'gc>(
@@ -169,14 +183,68 @@ impl TemporalPlainTimePrototype {
169183
Ok(value.into())
170184
}
171185

186+
/// ### [4.3.12 Temporal.PlainTime.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until)
187+
fn until<'gc>(
188+
agent: &mut Agent,
189+
this_value: Value,
190+
args: ArgumentsList,
191+
gc: GcScope<'gc, '_>,
192+
) -> JsResult<'gc, Value<'gc>>{
193+
let other = args.get(0).bind(gc.nogc());
194+
let options = args.get(1).bind(gc.nogc());
195+
// 1. Let plainTime be the this value.
196+
let plain_time = this_value.bind(gc.nogc());
197+
// 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]).
198+
let plain_time = require_internal_slot_temporal_plain_time(agent, plain_time.unbind(), gc.nogc())
199+
.unbind()?
200+
.bind(gc.nogc());
201+
// 3. Return ? DifferenceTemporalPlainTime(until, plainTime, other, options).
202+
const UNTIL: bool = true;
203+
difference_temporal_plain_time::<UNTIL>(
204+
agent,
205+
plain_time.unbind(),
206+
other.unbind(),
207+
options.unbind(),
208+
gc,
209+
)
210+
.map(Value::from)
211+
}
212+
213+
/// ### [4.3.13 Temporal.PlainTime.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since)
214+
fn since<'gc>(
215+
agent: &mut Agent,
216+
this_value: Value,
217+
args: ArgumentsList,
218+
gc: GcScope<'gc, '_>,
219+
) -> JsResult<'gc, Value<'gc>> {
220+
let other = args.get(0).bind(gc.nogc());
221+
let options = args.get(1).bind(gc.nogc());
222+
// 1. Let plainTime be the this value.
223+
let plain_time = this_value.bind(gc.nogc());
224+
// 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]).
225+
let plain_time = require_internal_slot_temporal_plain_time(agent, plain_time.unbind(), gc.nogc())
226+
.unbind()?
227+
.bind(gc.nogc());
228+
// 3. Return ? DifferenceTemporalPlainTime(since, instant, other, options).
229+
const SINCE: bool = false;
230+
difference_temporal_plain_time::<SINCE>(
231+
agent,
232+
plain_time.unbind(),
233+
other.unbind(),
234+
options.unbind(),
235+
gc,
236+
)
237+
.map(Value::from)
238+
}
239+
172240
pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) {
173241
let intrinsics = agent.get_realm_record_by_id(realm).intrinsics();
174242
let this = intrinsics.temporal_plain_time_prototype();
175243
let object_prototype = intrinsics.object_prototype();
176244
let plain_time_constructor = intrinsics.temporal_plain_time();
177245

178246
OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this)
179-
.with_property_capacity(8)
247+
.with_property_capacity(10)
180248
.with_prototype(object_prototype)
181249
.with_constructor_property(plain_time_constructor)
182250
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetHour>()
@@ -185,6 +253,8 @@ impl TemporalPlainTimePrototype {
185253
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetMicrosecond>()
186254
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetNanosecond>()
187255
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetMillisecond>()
256+
.with_builtin_function_property::<TemporalPlainTimePrototypeSince>()
257+
.with_builtin_function_property::<TemporalPlainTimePrototypeUntil>()
188258
.with_property(|builder| {
189259
builder
190260
.with_key(WellKnownSymbols::ToStringTag.into())

0 commit comments

Comments
 (0)