nova_vm/ecmascript/builtins/temporal/
instant.rs1mod 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#[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
91pub(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 let new_target = new_target.unwrap_or_else(|| {
107 agent
108 .current_realm_record()
109 .intrinsics()
110 .temporal_instant()
111 .into()
112 });
113 let object = agent.heap.create(InstantRecord {
117 object_index: None,
118 instant: epoch_nanoseconds,
120 });
121 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
134pub(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 let item = if let Ok(item) = Object::try_from(item) {
147 if let Ok(item) = TemporalInstant::try_from(item) {
151 return Ok(*item.inner_instant(agent));
153 }
154 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 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 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
193fn 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 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 unsafe {
217 instant = scoped_instant.take(agent);
218 }
219 res
220 };
221
222 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 Ok(create_temporal_instant(agent, ns_result, None, gc).unwrap())
238}
239
240fn 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 let other = to_temporal_instant(agent, other.unbind(), gc.reborrow())
261 .unbind()?
262 .bind(gc.nogc());
263 let resolved_options = get_options_object(agent, options.get(agent), gc.nogc())
265 .unbind()?
266 .bind(gc.nogc());
267 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 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}