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}