Skip to main content

nova_vm/ecmascript/builtins/temporal/
duration.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5mod data;
6mod duration_constructor;
7mod duration_prototype;
8
9pub(crate) use data::*;
10pub(crate) use duration_constructor::*;
11pub(crate) use duration_prototype::*;
12
13use crate::{
14    ecmascript::{
15        Agent, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods, InternalSlots,
16        JsResult, Object, OrdinaryObject, ProtoIntrinsics, String, Value, get, object_handle,
17        ordinary_populate_from_constructor, temporal_err_to_js_err, to_integer_if_integral,
18    },
19    engine::{Bindable, GcScope, NoGcScope, Scopable},
20    heap::{
21        ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
22        HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
23    },
24};
25
26/// # [7 Temporal.Duration Objects](https://tc39.es/proposal-temporal/#sec-temporal-duration-objects)
27///
28/// A Temporal.Duration object describes the difference in elapsed time between
29/// two other Temporal objects of the same type: Instant, PlainDate,
30/// PlainDateTime, PlainTime, PlainYearMonth, or ZonedDateTime. Objects of this
31/// type are only created via the _`.since()`_ and _`.until()`_ methods of these
32/// objects.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[repr(transparent)]
35pub struct TemporalDuration<'a>(BaseIndex<'a, DurationRecord<'static>>);
36object_handle!(TemporalDuration, Duration);
37arena_vec_access!(
38    TemporalDuration,
39    'a,
40    DurationRecord,
41    durations
42);
43
44impl TemporalDuration<'_> {
45    pub(crate) fn inner_duration(self, agent: &Agent) -> &temporal_rs::Duration {
46        &self.unbind().get(agent).duration
47    }
48}
49
50impl<'a> InternalSlots<'a> for TemporalDuration<'a> {
51    const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalDuration;
52    fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
53        self.unbind().get(agent).object_index
54    }
55    fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
56        assert!(
57            self.get_mut(agent)
58                .object_index
59                .replace(backing_object)
60                .is_none()
61        );
62    }
63}
64
65impl<'a> InternalMethods<'a> for TemporalDuration<'a> {}
66
67impl HeapMarkAndSweep for TemporalDuration<'static> {
68    fn mark_values(&self, queues: &mut WorkQueues) {
69        queues.durations.push(*self);
70    }
71    fn sweep_values(&mut self, compactions: &CompactionLists) {
72        compactions.durations.shift_index(&mut self.0);
73    }
74}
75
76impl HeapSweepWeakReference for TemporalDuration<'static> {
77    fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
78        compactions.durations.shift_weak_index(self.0).map(Self)
79    }
80}
81
82impl<'a> CreateHeapData<DurationRecord<'a>, TemporalDuration<'a>> for Heap {
83    fn create(&mut self, data: DurationRecord<'a>) -> TemporalDuration<'a> {
84        self.durations.push(data.unbind());
85        self.alloc_counter += core::mem::size_of::<DurationRecord<'static>>();
86        TemporalDuration(BaseIndex::last(&self.durations))
87    }
88}
89/// [7.5.19 CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )](https://tc39.es/proposal-temporal/#sec-temporal-createtemporalduration)
90/// The abstract operation CreateTemporalDuration takes arguments
91/// years (an integer), months (an integer),
92/// weeks (an integer), days (an integer),
93/// hours (an integer), minutes (an integer),
94/// seconds (an integer), milliseconds (an integer),
95/// microseconds (an integer), and nanoseconds (an integer)
96/// and optional argument newTarget (a constructor)
97/// and returns either a normal completion containing
98/// a Temporal.Duration or a throw completion.
99/// It creates a Temporal.Duration instance and fills
100/// the internal slots with valid values.
101pub(crate) fn create_temporal_duration<'gc>(
102    // years,
103    agent: &mut Agent,
104    duration: temporal_rs::Duration,
105    new_target: Option<Function>,
106    gc: GcScope<'gc, '_>,
107) -> JsResult<'gc, TemporalDuration<'gc>> {
108    // 1. If IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception.
109    // 2. If newTarget is not present, set newTarget to %Temporal.Duration%.
110    let new_target = new_target.unwrap_or_else(|| {
111        agent
112            .current_realm_record()
113            .intrinsics()
114            .temporal_duration()
115            .into()
116    });
117    // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget,
118    // "%Temporal.Duration.prototype%", ยซ [[InitializedTemporalDuration]],
119    // [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]],
120    // [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] ยป).
121    let object = agent.heap.create(DurationRecord {
122        object_index: None,
123        // 4. Set object.[[Years]] to โ„(๐”ฝ(years)).
124        // 5. Set object.[[Months]] to โ„(๐”ฝ(months)).
125        // 6. Set object.[[Weeks]] to โ„(๐”ฝ(weeks)).
126        // 7. Set object.[[Days]] to โ„(๐”ฝ(days)).
127        // 8. Set object.[[Hours]] to โ„(๐”ฝ(hours)).
128        // 9. Set object.[[Minutes]] to โ„(๐”ฝ(minutes)).
129        // 10. Set object.[[Seconds]] to โ„(๐”ฝ(seconds)).
130        // 11. Set object.[[Milliseconds]] to โ„(๐”ฝ(milliseconds)).
131        // 12. Set object.[[Microseconds]] to โ„(๐”ฝ(microseconds)).
132        // 13. Set object.[[Nanoseconds]] to โ„(๐”ฝ(nanoseconds)).
133        duration,
134    });
135    // 14. Return object.
136    Ok(
137        TemporalDuration::try_from(ordinary_populate_from_constructor(
138            agent,
139            object.unbind().into(),
140            new_target,
141            ProtoIntrinsics::TemporalDuration,
142            gc,
143        )?)
144        .unwrap(),
145    )
146}
147
148// Abstract Operations <--->
149
150/// [7.5.12 ToTemporalDuration ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration)
151///
152/// The abstract operation ToTemporalDuration takes argument item (an ECMAScript
153/// language value) and returns either a normal completion containing a
154/// Temporal.Duration or a throw completion. Converts item to a new
155/// Temporal.Duration instance if possible and returns that, and throws
156/// otherwise.
157pub(crate) fn to_temporal_duration<'gc>(
158    agent: &mut Agent,
159    item: Value,
160    mut gc: GcScope<'gc, '_>,
161) -> JsResult<'gc, temporal_rs::Duration> {
162    let item = item.bind(gc.nogc());
163    // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then
164    if let Value::Duration(item) = item {
165        // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]],
166        // item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]],
167        // item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]],
168        // item.[[Nanoseconds]]).
169        return Ok(*item.inner_duration(agent));
170    }
171
172    // 2. If item is not an Object, then
173    let Ok(item) = Object::try_from(item) else {
174        // a. If item is not a String,
175        let Ok(item) = String::try_from(item) else {
176            // throw a TypeError exception.
177            return Err(agent.throw_exception_with_static_message(
178                ExceptionType::TypeError,
179                "item is not a string",
180                gc.into_nogc(),
181            ));
182        };
183        // b. Return ? ParseTemporalDurationString(item).
184        return temporal_rs::Duration::from_utf8(item.as_bytes(agent))
185            .map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()));
186    };
187    // 3. Let result be a new Partial Duration Record with each field set to 0.
188    // 4. Let partial be ? ToTemporalPartialDurationRecord(item).
189    let partial =
190        to_temporal_partial_duration_record(agent, item.unbind(), gc.reborrow()).unbind()?;
191    // 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]].
192    // 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]].
193    // 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]].
194    // 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]].
195    // 9. If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]].
196    // 10. If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]].
197    // 11. If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]].
198    // 12. If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]].
199    // 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]].
200    // 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]].
201    //
202    // 15. Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]],
203    // result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]],
204    // result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]],
205    // result.[[Nanoseconds]]).
206    temporal_rs::Duration::from_partial_duration(partial)
207        .map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))
208}
209
210/// [7.5.18 ToTemporalPartialDurationRecord ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalpartialdurationrecord)
211/// The abstract operation ToTemporalPartialDurationRecord takes argument temporalDurationLike
212/// (an ECMAScript language value) and returns either a normal completion containing a
213/// partial Duration Record or a throw completion. The returned Record has its fields
214/// set according to the properties of temporalDurationLike.
215pub(crate) fn to_temporal_partial_duration_record<'gc>(
216    agent: &mut Agent,
217    temporal_duration_like: Object,
218    mut gc: GcScope<'gc, '_>,
219) -> JsResult<'gc, temporal_rs::partial::PartialDuration> {
220    let temporal_duration_like = temporal_duration_like.scope(agent, gc.nogc());
221    // 1. If temporalDurationLike is not an Object, then
222    //    a. Throw a TypeError exception.
223    // 2. Let result be a new partial Duration Record with each field set to undefined.
224    let mut result = temporal_rs::partial::PartialDuration::empty();
225    // 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
226    // 4. Let days be ? Get(temporalDurationLike, "days").
227    let days = get(
228        agent,
229        temporal_duration_like.get(agent),
230        BUILTIN_STRING_MEMORY.days.to_property_key(),
231        gc.reborrow(),
232    )
233    .unbind()?
234    .bind(gc.nogc());
235    // 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
236    if !days.is_undefined() {
237        let days = to_integer_if_integral(agent, days.unbind(), gc.reborrow()).unbind()? as i64;
238        result.days = Some(days)
239    }
240    // 6. Let hours be ? Get(temporalDurationLike, "hours").
241    let hours = get(
242        agent,
243        temporal_duration_like.get(agent),
244        BUILTIN_STRING_MEMORY.hours.to_property_key(),
245        gc.reborrow(),
246    )
247    .unbind()?
248    .bind(gc.nogc());
249    // 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
250    if !hours.is_undefined() {
251        let hours = to_integer_if_integral(agent, hours.unbind(), gc.reborrow()).unbind()? as i64;
252        result.hours = Some(hours)
253    }
254    // 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
255    let microseconds = get(
256        agent,
257        temporal_duration_like.get(agent),
258        BUILTIN_STRING_MEMORY.microseconds.to_property_key(),
259        gc.reborrow(),
260    )
261    .unbind()?
262    .bind(gc.nogc());
263    // 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
264    if !microseconds.is_undefined() {
265        let microseconds =
266            to_integer_if_integral(agent, microseconds.unbind(), gc.reborrow()).unbind()?;
267        result.microseconds = Some(microseconds as i128);
268    }
269    // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
270    let milliseconds = get(
271        agent,
272        temporal_duration_like.get(agent),
273        BUILTIN_STRING_MEMORY.milliseconds.to_property_key(),
274        gc.reborrow(),
275    )
276    .unbind()?
277    .bind(gc.nogc());
278    // 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
279    if !milliseconds.is_undefined() {
280        let milliseconds =
281            to_integer_if_integral(agent, milliseconds.unbind(), gc.reborrow()).unbind()? as i64;
282        result.milliseconds = Some(milliseconds)
283    }
284    // 12. Let minutes be ? Get(temporalDurationLike, "minutes").
285    let minutes = get(
286        agent,
287        temporal_duration_like.get(agent),
288        BUILTIN_STRING_MEMORY.minutes.to_property_key(),
289        gc.reborrow(),
290    )
291    .unbind()?
292    .bind(gc.nogc());
293    // 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
294    if !minutes.is_undefined() {
295        let minutes =
296            to_integer_if_integral(agent, minutes.unbind(), gc.reborrow()).unbind()? as i64;
297        result.minutes = Some(minutes)
298    }
299    // 14. Let months be ? Get(temporalDurationLike, "months").
300    let months = get(
301        agent,
302        temporal_duration_like.get(agent),
303        BUILTIN_STRING_MEMORY.months.to_property_key(),
304        gc.reborrow(),
305    )
306    .unbind()?
307    .bind(gc.nogc());
308    // 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
309    if !months.is_undefined() {
310        let months = to_integer_if_integral(agent, months.unbind(), gc.reborrow()).unbind()? as i64;
311        result.months = Some(months)
312    }
313    // 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
314    let nanoseconds = get(
315        agent,
316        temporal_duration_like.get(agent),
317        BUILTIN_STRING_MEMORY.nanoseconds.to_property_key(),
318        gc.reborrow(),
319    )
320    .unbind()?
321    .bind(gc.nogc());
322    // 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
323    if !nanoseconds.is_undefined() {
324        let nanoseconds =
325            to_integer_if_integral(agent, nanoseconds.unbind(), gc.reborrow()).unbind()?;
326        result.nanoseconds = Some(nanoseconds as i128);
327    }
328    // 18. Let seconds be ? Get(temporalDurationLike, "seconds").
329    let seconds = get(
330        agent,
331        temporal_duration_like.get(agent),
332        BUILTIN_STRING_MEMORY.seconds.to_property_key(),
333        gc.reborrow(),
334    )
335    .unbind()?
336    .bind(gc.nogc());
337    // 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
338    if !seconds.is_undefined() {
339        let seconds =
340            to_integer_if_integral(agent, seconds.unbind(), gc.reborrow()).unbind()? as i64;
341        result.seconds = Some(seconds)
342    }
343    // 20. Let weeks be ? Get(temporalDurationLike, "weeks").
344    let weeks = get(
345        agent,
346        temporal_duration_like.get(agent),
347        BUILTIN_STRING_MEMORY.weeks.to_property_key(),
348        gc.reborrow(),
349    )
350    .unbind()?
351    .bind(gc.nogc());
352    // 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
353    if !weeks.is_undefined() {
354        let weeks = to_integer_if_integral(agent, weeks.unbind(), gc.reborrow()).unbind()? as i64;
355        result.weeks = Some(weeks)
356    }
357    // 22. Let years be ? Get(temporalDurationLike, "years").
358    let years = get(
359        agent,
360        temporal_duration_like.get(agent),
361        BUILTIN_STRING_MEMORY.years.to_property_key(),
362        gc.reborrow(),
363    )
364    .unbind()?
365    .bind(gc.nogc());
366    // 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
367    if !years.is_undefined() {
368        let years = to_integer_if_integral(agent, years.unbind(), gc.reborrow()).unbind()? as i64;
369        result.years = Some(years)
370    }
371    // 24. If years is undefined, and months is undefined, and weeks is
372    // undefined, and days is undefined, and hours is undefined, and minutes is
373    // undefined, and seconds is undefined, and milliseconds is undefined, and
374    // microseconds is undefined, and nanoseconds is undefined, throw a
375    // TypeError exception.
376    if result.is_empty() {
377        return Err(agent.throw_exception_with_static_message(
378            ExceptionType::TypeError,
379            "Duration must have at least one unit",
380            gc.into_nogc(),
381        ));
382    }
383    // 25. Return result.
384    Ok(result)
385}
386
387#[inline(always)]
388pub(crate) fn _require_internal_slot_temporal_duration<'a>(
389    agent: &mut Agent,
390    value: Value,
391    gc: NoGcScope<'a, '_>,
392) -> JsResult<'a, TemporalDuration<'a>> {
393    match value {
394        Value::Duration(duration) => Ok(duration.bind(gc)),
395        _ => Err(agent.throw_exception_with_static_message(
396            ExceptionType::TypeError,
397            "Object is not a Temporal Duration",
398            gc,
399        )),
400    }
401}