Skip to main content

nova_vm/ecmascript/builtins/temporal/
instant.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 instant_constructor;
7mod instant_prototype;
8
9pub(crate) use data::*;
10pub(crate) use instant_constructor::*;
11pub(crate) use instant_prototype::*;
12
13use temporal_rs::options::{Unit, UnitGroup};
14
15use crate::{
16    ecmascript::{
17        Agent, DurationRecord, ExceptionType, Function, InternalMethods, InternalSlots, JsResult,
18        Object, OrdinaryObject, PreferredType, Primitive, ProtoIntrinsics, String,
19        TemporalDuration, Value, get_difference_settings, get_options_object, object_handle,
20        ordinary_populate_from_constructor, temporal_err_to_js_err, to_primitive_object,
21        to_temporal_duration,
22    },
23    engine::{Bindable, GcScope, NoGcScope, Scopable},
24    heap::{
25        ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
26        HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
27    },
28};
29
30/// # [8 Temporal.Instant Objects](https://tc39.es/proposal-temporal/#sec-temporal-instant-objects)
31///
32/// A Temporal.Instant object is an Object referencing a fixed point in time
33/// with nanoseconds precision.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35#[repr(transparent)]
36pub struct TemporalInstant<'a>(BaseIndex<'a, InstantRecord<'static>>);
37object_handle!(TemporalInstant, Instant);
38arena_vec_access!(
39    TemporalInstant,
40    'a,
41    InstantRecord,
42    instants
43);
44
45impl TemporalInstant<'_> {
46    pub(crate) fn inner_instant(self, agent: &Agent) -> &temporal_rs::Instant {
47        &self.unbind().get(agent).instant
48    }
49}
50
51impl<'a> InternalSlots<'a> for TemporalInstant<'a> {
52    const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant;
53    fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
54        self.get(agent).object_index.unbind()
55    }
56    fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
57        assert!(
58            self.get_mut(agent)
59                .object_index
60                .replace(backing_object)
61                .is_none()
62        );
63    }
64}
65
66impl<'a> InternalMethods<'a> for TemporalInstant<'a> {}
67
68impl HeapMarkAndSweep for TemporalInstant<'static> {
69    fn mark_values(&self, queues: &mut WorkQueues) {
70        queues.instants.push(*self);
71    }
72    fn sweep_values(&mut self, compactions: &CompactionLists) {
73        compactions.instants.shift_index(&mut self.0);
74    }
75}
76
77impl HeapSweepWeakReference for TemporalInstant<'static> {
78    fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
79        compactions.instants.shift_weak_index(self.0).map(Self)
80    }
81}
82
83impl<'a> CreateHeapData<InstantRecord<'a>, TemporalInstant<'a>> for Heap {
84    fn create(&mut self, data: InstantRecord<'a>) -> TemporalInstant<'a> {
85        self.instants.push(data.unbind());
86        self.alloc_counter += core::mem::size_of::<InstantRecord<'static>>();
87        TemporalInstant(BaseIndex::last(&self.instants))
88    }
89}
90
91/// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
92///
93/// The abstract operation CreateTemporalInstant takes argument
94/// epochNanoseconds (a BigInt) and optional argument newTarget (a constructor)
95/// and returns either a normal completion containing a Temporal.Instant or a
96/// throw completion. It creates a Temporal.Instant instance and fills the
97/// internal slots with valid values.
98pub(crate) fn create_temporal_instant<'gc>(
99    agent: &mut Agent,
100    epoch_nanoseconds: temporal_rs::Instant,
101    new_target: Option<Function>,
102    gc: GcScope<'gc, '_>,
103) -> JsResult<'gc, TemporalInstant<'gc>> {
104    // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true.
105    // 2. If newTarget is not present, set newTarget to %Temporal.Instant%.
106    let new_target = new_target.unwrap_or_else(|| {
107        agent
108            .current_realm_record()
109            .intrinsics()
110            .temporal_instant()
111            .into()
112    });
113    // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget,
114    // "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]],
115    // [[EpochNanoseconds]] »).
116    let object = agent.heap.create(InstantRecord {
117        object_index: None,
118        // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds.
119        instant: epoch_nanoseconds,
120    });
121    // 5. Return object.
122    Ok(
123        TemporalInstant::try_from(ordinary_populate_from_constructor(
124            agent,
125            object.unbind().into(),
126            new_target,
127            ProtoIntrinsics::TemporalInstant,
128            gc,
129        )?)
130        .unwrap(),
131    )
132}
133
134/// ### [8.5.3 ToTemporalInstant ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant)
135///
136/// The abstract operation ToTemporalInstant takes argument item (an ECMAScript language value) and
137/// returns either a normal completion containing a Temporal.Instant or a throw completion.
138/// Converts item to a new Temporal.Instant instance if possible, and throws otherwise.
139pub(crate) fn to_temporal_instant<'gc>(
140    agent: &mut Agent,
141    item: Value,
142    mut gc: GcScope<'gc, '_>,
143) -> JsResult<'gc, temporal_rs::Instant> {
144    let item = item.bind(gc.nogc());
145    // 1. If item is an Object, then
146    let item = if let Ok(item) = Object::try_from(item) {
147        // a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]]
148        // internal slot, then
149        // TODO: TemporalZonedDateTime::try_from(item)
150        if let Ok(item) = TemporalInstant::try_from(item) {
151            // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]).
152            return Ok(*item.inner_instant(agent));
153        }
154        // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted.
155        // c. Set item to ? ToPrimitive(item, string).
156        to_primitive_object(
157            agent,
158            item.unbind(),
159            Some(PreferredType::String),
160            gc.reborrow(),
161        )
162        .unbind()?
163        .bind(gc.nogc())
164    } else {
165        Primitive::try_from(item).unwrap()
166    };
167    // 2. If item is not a String, throw a TypeError exception.
168    let Ok(item) = String::try_from(item) else {
169        return Err(agent.throw_exception_with_static_message(
170            ExceptionType::TypeError,
171            "Item is not a String",
172            gc.into_nogc(),
173        ));
174    };
175    // 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »).
176    // 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or
177    //    parsed.[[TimeZone]].[[Z]] is true, but not both.
178    // 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let
179    //    offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]).
180    // 6. If parsed.[[Time]] is start-of-day, let time be MidnightTimeRecord(); else let time be
181    //    parsed.[[Time]].
182    // 7. Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]],
183    //    time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]],
184    //    time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds).
185    // 8. Perform ? CheckISODaysRange(balanced.[[ISODate]]).
186    // 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
187    // 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
188    // 11. Return ! CreateTemporalInstant(epochNanoseconds).
189    temporal_rs::Instant::from_utf8(item.as_bytes(agent))
190        .map_err(|e| temporal_err_to_js_err(agent, e, gc.into_nogc()))
191}
192
193/// [8.5.10 AddDurationToInstant ( operation, instant, temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoinstant)
194///
195/// The abstract operation AddDurationToInstant takes arguments operation (add
196/// or subtract), instant (a Temporal.Instant), and temporalDurationLike (an
197/// ECMAScript language value) and returns either a normal completion containing
198/// a Temporal.Instant or a throw completion. It adds/subtracts
199/// temporalDurationLike to/from instant.
200fn add_duration_to_instant<'gc, const IS_ADD: bool>(
201    agent: &mut Agent,
202    instant: TemporalInstant,
203    duration: Value,
204    mut gc: GcScope<'gc, '_>,
205) -> JsResult<'gc, TemporalInstant<'gc>> {
206    let duration = duration.bind(gc.nogc());
207    let mut instant = instant.bind(gc.nogc());
208    // 1. Let duration be ? ToTemporalDuration(temporalDurationLike).
209
210    let duration = if let Value::Duration(duration) = duration {
211        duration.get(agent).duration
212    } else {
213        let scoped_instant = instant.scope(agent, gc.nogc());
214        let res = to_temporal_duration(agent, duration.unbind(), gc.reborrow()).unbind()?;
215        // SAFETY: not shared
216        unsafe {
217            instant = scoped_instant.take(agent);
218        }
219        res
220    };
221
222    // 2. If operation is subtract, set duration to CreateNegatedTemporalDuration(duration).
223    // 3. Let largestUnit be DefaultTemporalLargestUnit(duration).
224    // 4. If TemporalUnitCategory(largestUnit) is date, throw a RangeError exception.
225    // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
226    // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]).
227    let ns_result = if IS_ADD {
228        temporal_rs::Instant::add(instant.inner_instant(agent), &duration)
229            .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc()))
230            .unbind()?
231    } else {
232        temporal_rs::Instant::subtract(instant.inner_instant(agent), &duration)
233            .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc()))
234            .unbind()?
235    };
236    // 7. Return ! CreateTemporalInstant(ns).
237    Ok(create_temporal_instant(agent, ns_result, None, gc).unwrap())
238}
239
240/// [8.5.9 DifferenceTemporalInstant ( operation, instant, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant)
241/// The abstract operation DifferenceTemporalInstant takes arguments
242/// operation (since or until), instant (a Temporal.Instant),
243/// other (an ECMAScript language value), and options
244/// (an ECMAScript language value) and returns either
245/// a normal completion containing a Temporal.Duration or a
246/// throw completion. It computes the difference between the
247/// two times represented by instant and other, optionally
248/// rounds it, and returns it as a Temporal.Duration object.
249fn difference_temporal_instant<'gc, const IS_UNTIL: bool>(
250    agent: &mut Agent,
251    instant: TemporalInstant,
252    other: Value,
253    options: Value,
254    mut gc: GcScope<'gc, '_>,
255) -> JsResult<'gc, TemporalDuration<'gc>> {
256    let instant = instant.scope(agent, gc.nogc());
257    let other = other.bind(gc.nogc());
258    let options = options.scope(agent, gc.nogc());
259    // 1. Set other to ? ToTemporalInstant(other).
260    let other = to_temporal_instant(agent, other.unbind(), gc.reborrow())
261        .unbind()?
262        .bind(gc.nogc());
263    // 2. Let resolvedOptions be ? GetOptionsObject(options).
264    let resolved_options = get_options_object(agent, options.get(agent), gc.nogc())
265        .unbind()?
266        .bind(gc.nogc());
267    // 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions,
268    //    time, « », nanosecond, second).
269    // 4. Let internalDuration be
270    //    DifferenceInstant(instant.[[EpochNanoseconds]],
271    //    other.[[EpochNanoseconds]], settings.[[RoundingIncrement]],
272    //    settings.[[SmallestUnit]], settings.[[RoundingMode]]).
273    // 5. Let result be ! TemporalDurationFromInternal(internalDuration,
274    //    settings.[[LargestUnit]]).
275    // 6. If operation is since, set result to
276    //    CreateNegatedTemporalDuration(result).
277    let duration = if IS_UNTIL {
278        const UNTIL: bool = true;
279        let settings = get_difference_settings::<UNTIL>(
280            agent,
281            resolved_options.unbind(),
282            UnitGroup::Time,
283            &[],
284            Unit::Nanosecond,
285            Unit::Second,
286            gc.reborrow(),
287        )
288        .unbind()?;
289        temporal_rs::Instant::until(instant.get(agent).inner_instant(agent), &other, settings)
290    } else {
291        const SINCE: bool = false;
292        let settings = get_difference_settings::<SINCE>(
293            agent,
294            resolved_options.unbind(),
295            UnitGroup::Time,
296            &[],
297            Unit::Nanosecond,
298            Unit::Second,
299            gc.reborrow(),
300        )
301        .unbind()?;
302        temporal_rs::Instant::since(instant.get(agent).inner_instant(agent), &other, settings)
303    };
304    let gc = gc.into_nogc();
305    let duration = duration.map_err(|err| temporal_err_to_js_err(agent, err, gc))?;
306
307    // 7. Return result.
308    Ok(agent.heap.create(DurationRecord {
309        object_index: None,
310        duration,
311    }))
312}
313
314#[inline(always)]
315fn require_internal_slot_temporal_instant<'a>(
316    agent: &mut Agent,
317    value: Value,
318    gc: NoGcScope<'a, '_>,
319) -> JsResult<'a, TemporalInstant<'a>> {
320    match value {
321        Value::Instant(instant) => Ok(instant.bind(gc)),
322        _ => Err(agent.throw_exception_with_static_message(
323            ExceptionType::TypeError,
324            "Object is not a Temporal Instant",
325            gc,
326        )),
327    }
328}