Skip to content

Commit 921e5ac

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

5 files changed

Lines changed: 363 additions & 7 deletions

File tree

nova_vm/src/builtin_strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ Object
332332
of
333333
#[cfg(feature = "atomics")]ok
334334
#[cfg(feature = "atomics")]or
335+
#[cfg(feature = "temporal")]overflow
335336
ownKeys
336337
padEnd
337338
padStart

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use std::str::FromStr;
66

7-
use temporal_rs::options::{RoundingIncrement, RoundingMode, Unit};
7+
use temporal_rs::options::{Overflow, RoundingIncrement, RoundingMode, Unit};
88

99
use crate::{
1010
ecmascript::{
@@ -68,6 +68,35 @@ impl StringOptionType for Unit {
6868
}
6969
}
7070

71+
impl StringOptionType for Overflow {
72+
fn from_string<'gc>(
73+
agent: &mut Agent,
74+
value: String,
75+
gc: NoGcScope<'gc, '_>,
76+
) -> JsResult<'gc, Self> {
77+
let value = value.as_str(agent).unwrap_or("");
78+
Self::from_str(value).map_err(|err| {
79+
agent.throw_exception(ExceptionType::RangeError, format!("{err}"), gc.into_nogc())
80+
})
81+
}
82+
}
83+
84+
/// ### [13.6 GetTemporalOverflowOption (options )](https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloverflowoption)
85+
///
86+
/// The abstract operation GetTemporalOverflowOption takes argument options (an Object) and returns either a normal completion containing either constrain or reject, or a throw completion. It fetches and validates the "overflow" property of options, returning a default if absent. It performs the following steps when called:
87+
88+
pub(crate) fn get_temporal_overflow_option<'gc>(
89+
agent: &mut Agent,
90+
options: Object<'gc>,
91+
mut gc: GcScope<'gc, '_>,
92+
) -> JsResult<'gc, temporal_rs::options::Overflow> {
93+
let value = get_option::<Overflow>(agent, options, BUILTIN_STRING_MEMORY.overflow.into(), gc)?;
94+
// 1. Let stringValue be ? GetOption(options, "overflow", string, « "constrain", "reject" », "constrain").
95+
// 2. If stringValue is "constrain", return constrain.
96+
// 3. Return reject.
97+
Ok(value.unwrap_or(Overflow::Reject))
98+
}
99+
71100
/// ### [14.5.2.1 GetOptionsObject ( options )](https://tc39.es/proposal-temporal/#sec-getoptionsobject)
72101
///
73102
/// The abstract operation GetOptionsObject takes argument options (an ECMAScript language value)

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

Lines changed: 254 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ 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 temporal_rs::options::{Overflow, Unit, UnitGroup};
1213

1314
use crate::{
1415
ecmascript::{
15-
Agent, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, OrdinaryObject,
16-
ProtoIntrinsics, Value, object_handle, ordinary_populate_from_constructor,
16+
Agent, BUILTIN_STRING_MEMORY, DurationRecord, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, Object, OrdinaryObject, ProtoIntrinsics, String, TemporalDuration, Value, get, get_difference_settings, get_options_object, get_temporal_overflow_option, object_handle, ordinary_populate_from_constructor, temporal_err_to_js_err, to_integer_with_truncation
1717
},
18-
engine::{Bindable, GcScope, NoGcScope},
18+
engine::{Bindable, GcScope, NoGcScope, Scopable},
1919
heap::{
2020
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
2121
HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
@@ -133,3 +133,254 @@ pub(crate) fn create_temporal_plain_time<'gc>(
133133
.unwrap(),
134134
)
135135
}
136+
137+
/// ### [4.5.6 ToTemporalTime ( item [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime)
138+
///
139+
/// The abstract operation ToTemporalTime takes argument item (an ECMAScript language value) and optional argument
140+
/// options (an ECMAScript language value) and returns either a normal completion containing a Temporal.PlainTime
141+
/// or a throw Completion. Converts item to a new Temporal.PlainTime instance if possible, and throws otherwise.
142+
pub(crate) fn to_temporal_time<'gc>(
143+
agent: &mut Agent,
144+
item: Value,
145+
options: Option<Value>,
146+
mut gc: GcScope<'gc, '_>,
147+
) -> JsResult<'gc, temporal_rs::PlainTime> {
148+
let item = item.bind(gc.nogc());
149+
150+
// 1. If options is not present, set options to undefined.
151+
let options = options.unwrap_or(Value::Undefined);
152+
// 2. If item is an Object, then
153+
// a. If item has an [[InitializedTemporalTime]] internal slot, then
154+
if let Value::PlainTime(time) = item {
155+
// i. Let resolvedOptions be ? GetOptionsObject(options).
156+
// let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?;
157+
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
158+
// get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;
159+
// iii. Return ! CreateTemporalTime(item.[[Time]]).
160+
return Ok(*time.inner_plain_time(agent));
161+
} else if let Value::Object(item) = item {
162+
// b. If item has an [[InitializedTemporalDateTime]] internal slot, then
163+
// i. Let resolvedOptions be ? GetOptionsObject(options).
164+
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
165+
// iii. Return ! CreateTemporalTime(item.[[ISODateTime]].[[Time]]).
166+
167+
// c. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
168+
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]).
169+
// ii. Let resolvedOptions be ? GetOptionsObject(options).
170+
// iii. Perform ? GetTemporalOverflowOption(resolvedOptions).
171+
// iv. Return ! CreateTemporalTime(isoDateTime.[[Time]]).
172+
173+
// d. Let result be ? ToTemporalTimeRecord(item).
174+
let result = to_temporal_time_record(agent, item.unbind().into(), gc.reborrow())
175+
.unbind()?
176+
.bind(gc.nogc());
177+
// e. Let resolvedOptions be ? GetOptionsObject(options).
178+
let resolved_options = get_options_object(agent, options, gc.nogc())
179+
.unbind()?;
180+
// f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
181+
let overflow = if let Some(resolved_options) = resolved_options {
182+
get_temporal_overflow_option(agent, resolved_options, gc.reborrow())
183+
.map_err(|e| e.unbind())?
184+
} else {
185+
Overflow::Constrain
186+
};
187+
// g. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], overflow).
188+
return temporal_rs::PlainTime::from_partial(result, Some(overflow))
189+
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()));
190+
}
191+
192+
// 3. Else,
193+
// a. If item is not a String, throw a TypeError exception.
194+
let Ok(item) = String::try_from(item) else {
195+
return Err(agent.throw_exception_with_static_message(
196+
ExceptionType::TypeError,
197+
"item is not a String",
198+
gc.into_nogc(),
199+
));
200+
};
201+
202+
// b. Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »).
203+
// c. Assert: parseResult.[[Time]] is not start-of-day.
204+
// d. Set result to parseResult.[[Time]].
205+
// 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.
206+
let result = match temporal_rs::PlainTime::from_utf8(item.as_bytes(agent)) {
207+
Ok(v) => v,
208+
Err(err) => return Err(temporal_err_to_js_err(agent, err, gc.into_nogc())),
209+
};
210+
// f. Let resolvedOptions be ? GetOptionsObject(options).
211+
let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?;
212+
// g. Perform ? GetTemporalOverflowOption(resolvedOptions).
213+
if let Some(resolved_options) = resolved_options {
214+
get_temporal_overflow_option(agent, resolved_options, gc.reborrow())
215+
.map_err(|e| e.unbind())?;
216+
}
217+
218+
// 4. Return ! CreateTemporalTime(result).
219+
Ok(result)
220+
}
221+
/// ### [4.5.12 ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )](https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimerecord)
222+
/// The abstract operation ToTemporalTimeRecord takes argument temporalTimeLike (an Object) and optional argument completeness (either partial or complete) and returns either a normal
223+
/// completion containing a TemporalTimeLike Record or a throw completion. It converts temporalTimeLike to a TemporalTimeLike Record by reading time component properties, with missing
224+
/// components set to 0 if completeness is complete or to unset if partial. It throws a TypeError if temporalTimeLike has no recognized time component properties. It performs the following steps when called:
225+
fn to_temporal_time_record<'gc>(
226+
agent: &mut Agent,
227+
item: Object,
228+
mut gc: GcScope<'gc, '_>,
229+
) -> JsResult<'gc, temporal_rs::partial::PartialTime> {
230+
let item = item.scope(agent, gc.nogc());
231+
// 1. If completeness is not present, set completeness to complete.
232+
// 2. If completeness is complete, then
233+
// a. Let result be a new TemporalTimeLike Record with each field set to 0.
234+
// 3. Else,
235+
// a. Let result be a new TemporalTimeLike Record with each field set to unset.
236+
let mut result = temporal_rs::partial::PartialTime::new();
237+
// 4. Let any be false.
238+
let mut any = false;
239+
// 5. Let hour be ? Get(temporalTimeLike, "hour").
240+
let hour = get(agent, item.get(agent), BUILTIN_STRING_MEMORY.hour.to_property_key(), gc.reborrow()).unbind()?.bind(gc.nogc());
241+
// 6. If hour is not undefined, then
242+
if !hour.is_undefined() {
243+
// a. Set result.[[Hour]] to ? ToIntegerWithTruncation(hour).
244+
result.hour = Some(u8::try_from(to_integer_with_truncation(agent, hour.unbind(), gc.reborrow()).unbind()?).unwrap_or(u8::MAX));
245+
// b. Set any to true.
246+
any = true;
247+
}
248+
// 7. Let microsecond be ? Get(temporalTimeLike, "microsecond").
249+
let microsecond = get(agent, item.get(agent), BUILTIN_STRING_MEMORY.microsecond.to_property_key(), gc.reborrow()).unbind()?.bind(gc.nogc());
250+
// 8. If microsecond is not undefined, then
251+
if !microsecond.is_undefined() {
252+
// a. Set result.[[Microsecond]] to ? ToIntegerWithTruncation(microsecond).
253+
result.microsecond = Some(u16::try_from(to_integer_with_truncation(agent, microsecond.unbind(), gc.reborrow()).unbind()?).unwrap_or(u16::MAX));
254+
// b. Set any to true.
255+
any = true;
256+
}
257+
// 9. Let millisecond be ? Get(temporalTimeLike, "millisecond").
258+
let millisecond = get(agent, item.get(agent), BUILTIN_STRING_MEMORY.millisecond.to_property_key(), gc.reborrow()).unbind()?.bind(gc.nogc());
259+
// 10. If millisecond is not undefined, then
260+
if !millisecond.is_undefined() {
261+
// a. Set result.[[Millisecond]] to ? ToIntegerWithTruncation(millisecond).
262+
result.millisecond = Some(u16::try_from(to_integer_with_truncation(agent, millisecond.unbind(), gc.reborrow()).unbind()?).unwrap_or(u16::MAX));
263+
// b. Set any to true.
264+
any = true;
265+
}
266+
// 11. Let minute be ? Get(temporalTimeLike, "minute").
267+
let minute = get(agent, item.get(agent), BUILTIN_STRING_MEMORY.minute.to_property_key(), gc.reborrow()).unbind()?.bind(gc.nogc());
268+
// 12. If minute is not undefined, then
269+
if !minute.is_undefined() {
270+
// a. Set result.[[Minute]] to ? ToIntegerWithTruncation(minute).
271+
result.minute = Some(u8::try_from(to_integer_with_truncation(agent, minute.unbind(), gc.reborrow()).unbind()?).unwrap_or(u8::MAX));
272+
// b. Set any to true.
273+
any = true;
274+
}
275+
// 13. Let nanosecond be ? Get(temporalTimeLike, "nanosecond").
276+
let nanosecond = get(agent, item.get(agent), BUILTIN_STRING_MEMORY.nanosecond.to_property_key(), gc.reborrow()).unbind()?.bind(gc.nogc());
277+
// 14. If nanosecond is not undefined, then
278+
if !nanosecond.is_undefined() {
279+
// a. Set result.[[Nanosecond]] to ? ToIntegerWithTruncation(nanosecond).
280+
result.nanosecond = Some(u16::try_from(to_integer_with_truncation(agent, nanosecond.unbind(), gc.reborrow()).unbind()?).unwrap_or(u16::MAX));
281+
// b. Set any to true.
282+
any = true;
283+
}
284+
// 15. Let second be ? Get(temporalTimeLike, "second").
285+
let second = get(agent, item.get(agent), BUILTIN_STRING_MEMORY.second.to_property_key(), gc.reborrow()).unbind()?.bind(gc.nogc());
286+
// 16. If second is not undefined, then
287+
if !second.is_undefined() {
288+
// a. Set result.[[Second]] to ? ToIntegerWithTruncation(second).
289+
result.second = Some(u8::try_from(to_integer_with_truncation(agent, second.unbind(), gc.reborrow()).unbind()?).unwrap_or(u8::MAX));
290+
// b. Set any to true.
291+
any = true;
292+
}
293+
// 17. If any is false, throw a TypeError exception.
294+
if !any {
295+
return Err(agent.throw_exception_with_static_message(
296+
ExceptionType::TypeError,
297+
"TemporalTimeLike must have at least one time field",
298+
gc.into_nogc(),
299+
));
300+
}
301+
// 18. Return result.
302+
Ok(result)
303+
}
304+
305+
/// ### [4.5.17 DifferenceTemporalPlainTime ( operation, temporalTime, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaintime)
306+
/// The abstract operation DifferenceTemporalPlainTime takes arguments
307+
/// operation (either since or until), temporalTime (a Temporal.PlainTime),
308+
/// other (an ECMAScript language value), and options
309+
/// (an ECMAScript language value) and returns either
310+
/// a normal completion containing a Temporal.Duration or a
311+
/// throw completion. It computes the difference between the
312+
/// two times represented by temporalTime and other, optionally
313+
/// rounds it, and returns it as a Temporal.Duration object.
314+
pub(super) fn difference_temporal_plain_time<'gc, const IS_UNTIL: bool>(
315+
agent: &mut Agent,
316+
plain_time: TemporalPlainTime,
317+
other: Value,
318+
options: Value,
319+
mut gc: GcScope<'gc, '_>,
320+
) -> JsResult<'gc, TemporalDuration<'gc>> {
321+
let plain_time = plain_time.scope(agent, gc.nogc());
322+
let other = other.bind(gc.nogc());
323+
let options = options.scope(agent, gc.nogc());
324+
// 1. Set other to ? ToTemporalTime(other).
325+
let other = to_temporal_time(agent, other.unbind(), None, gc.reborrow())
326+
.unbind()?;
327+
// 2. Let resolvedOptions be ? GetOptionsObject(options).
328+
let resolved_option = get_options_object(agent, options.get(agent), gc.nogc())
329+
.unbind()?
330+
.bind(gc.nogc());
331+
// 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions,
332+
// time, « », nanosecond, hour).
333+
// 4. Let timeDuration be
334+
// DifferenceTime(temporalTime.[[Time]], other.[[Time]]).
335+
// 5. Set timeDuration to ! RoundTimeDuration(timeDuration,
336+
// settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
337+
// 6. Let duration be
338+
// CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
339+
// 7. Let result be !
340+
// TemporalDurationFromInternal(duration, settings.[[LargestUnit]]).
341+
// 8. If operation is since, set result to
342+
// CreateNegatedTemporalDuration(result).
343+
let duration = if IS_UNTIL {
344+
const UNTIL: bool = true;
345+
let settings = get_difference_settings::<UNTIL>(
346+
agent,
347+
resolved_option.unbind(),
348+
UnitGroup::Time,
349+
&[],
350+
Unit::Nanosecond,
351+
Unit::Hour,
352+
gc.reborrow(),
353+
)
354+
.unbind()?;
355+
temporal_rs::PlainTime::until(
356+
plain_time.get(agent).inner_plain_time(agent),
357+
&other,
358+
settings,
359+
)
360+
} else {
361+
const SINCE: bool = false;
362+
let settings = get_difference_settings::<SINCE>(
363+
agent,
364+
resolved_option.unbind(),
365+
UnitGroup::Time,
366+
&[],
367+
Unit::Nanosecond,
368+
Unit::Hour,
369+
gc.reborrow(),
370+
)
371+
.unbind()?;
372+
temporal_rs::PlainTime::since(
373+
plain_time.get(agent).inner_plain_time(agent),
374+
&other,
375+
settings,
376+
)
377+
};
378+
let gc = gc.into_nogc();
379+
let duration = duration.map_err(|err| temporal_err_to_js_err(agent, err, gc))?;
380+
381+
// 9. Return result.
382+
Ok(agent.heap.create(DurationRecord {
383+
object_index: None,
384+
duration,
385+
}))
386+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ impl PlainTimeRecord<'_> {
2424
}
2525

2626
trivially_bindable!(temporal_rs::PlainTime);
27+
trivially_bindable!(temporal_rs::partial::PartialTime);
2728
bindable_handle!(PlainTimeRecord);
2829

2930
impl HeapMarkAndSweep for PlainTimeRecord<'static> {

0 commit comments

Comments
 (0)