Skip to main content

boa_engine/builtins/temporal/duration/
mod.rs

1// Boa's implementation of the `Temporal.Duration` built-in object.
2
3use super::{
4    DateTimeValues, get_relative_to_option,
5    options::{TemporalUnitGroup, get_digits_option, get_temporal_unit},
6};
7use crate::value::JsVariant;
8use crate::{
9    Context, JsArgs, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
10    JsValue,
11    builtins::{
12        BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
13        options::{get_option, get_options_object},
14    },
15    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
16    js_string,
17    object::internal_methods::get_prototype_from_constructor,
18    property::Attribute,
19    realm::Realm,
20    string::StaticJsStrings,
21};
22use boa_gc::{Finalize, Trace};
23use temporal_rs::{
24    Duration as InnerDuration,
25    options::{RoundingIncrement, RoundingMode, RoundingOptions, ToStringRoundingOptions, Unit},
26    partial::PartialDuration,
27};
28
29#[cfg(test)]
30mod tests;
31
32/// The `Temporal.Duration` built-in implementation
33///
34/// More information:
35///
36/// - [ECMAScript Temporal proposal][spec]
37/// - [MDN reference][mdn]
38/// - [`temporal_rs` documentation][temporal_rs-docs]
39///
40/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-duration-objects
41/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration
42/// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html
43#[derive(Debug, Clone, Trace, Finalize, JsData)]
44#[boa_gc(unsafe_empty_trace)] // Safety: Does not contain any traceable fields.
45pub struct Duration {
46    pub(crate) inner: Box<InnerDuration>,
47}
48
49impl Duration {
50    pub(crate) fn new(inner: InnerDuration) -> Self {
51        Self {
52            inner: Box::new(inner),
53        }
54    }
55}
56
57impl BuiltInObject for Duration {
58    const NAME: JsString = StaticJsStrings::DURATION_NAME;
59}
60
61impl IntrinsicObject for Duration {
62    fn init(realm: &Realm) {
63        let get_years = BuiltInBuilder::callable(realm, Self::get_years)
64            .name(js_string!("get Years"))
65            .build();
66
67        let get_months = BuiltInBuilder::callable(realm, Self::get_months)
68            .name(js_string!("get Months"))
69            .build();
70
71        let get_weeks = BuiltInBuilder::callable(realm, Self::get_weeks)
72            .name(js_string!("get Weeks"))
73            .build();
74
75        let get_days = BuiltInBuilder::callable(realm, Self::get_days)
76            .name(js_string!("get Days"))
77            .build();
78
79        let get_hours = BuiltInBuilder::callable(realm, Self::get_hours)
80            .name(js_string!("get Hours"))
81            .build();
82
83        let get_minutes = BuiltInBuilder::callable(realm, Self::get_minutes)
84            .name(js_string!("get Minutes"))
85            .build();
86
87        let get_seconds = BuiltInBuilder::callable(realm, Self::get_seconds)
88            .name(js_string!("get Seconds"))
89            .build();
90
91        let get_milliseconds = BuiltInBuilder::callable(realm, Self::get_milliseconds)
92            .name(js_string!("get Milliseconds"))
93            .build();
94
95        let get_microseconds = BuiltInBuilder::callable(realm, Self::get_microseconds)
96            .name(js_string!("get Microseconds"))
97            .build();
98
99        let get_nanoseconds = BuiltInBuilder::callable(realm, Self::get_nanoseconds)
100            .name(js_string!("get Nanoseconds"))
101            .build();
102
103        let get_sign = BuiltInBuilder::callable(realm, Self::get_sign)
104            .name(js_string!("get Sign"))
105            .build();
106
107        let is_blank = BuiltInBuilder::callable(realm, Self::get_blank)
108            .name(js_string!("get blank"))
109            .build();
110
111        BuiltInBuilder::from_standard_constructor::<Self>(realm)
112            .property(
113                JsSymbol::to_string_tag(),
114                StaticJsStrings::DURATION_TAG,
115                Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
116            )
117            .accessor(
118                js_string!("years"),
119                Some(get_years),
120                None,
121                Attribute::CONFIGURABLE,
122            )
123            .accessor(
124                js_string!("months"),
125                Some(get_months),
126                None,
127                Attribute::CONFIGURABLE,
128            )
129            .accessor(
130                js_string!("weeks"),
131                Some(get_weeks),
132                None,
133                Attribute::CONFIGURABLE,
134            )
135            .accessor(
136                js_string!("days"),
137                Some(get_days),
138                None,
139                Attribute::CONFIGURABLE,
140            )
141            .accessor(
142                js_string!("hours"),
143                Some(get_hours),
144                None,
145                Attribute::CONFIGURABLE,
146            )
147            .accessor(
148                js_string!("minutes"),
149                Some(get_minutes),
150                None,
151                Attribute::CONFIGURABLE,
152            )
153            .accessor(
154                js_string!("seconds"),
155                Some(get_seconds),
156                None,
157                Attribute::CONFIGURABLE,
158            )
159            .accessor(
160                js_string!("milliseconds"),
161                Some(get_milliseconds),
162                None,
163                Attribute::CONFIGURABLE,
164            )
165            .accessor(
166                js_string!("microseconds"),
167                Some(get_microseconds),
168                None,
169                Attribute::CONFIGURABLE,
170            )
171            .accessor(
172                js_string!("nanoseconds"),
173                Some(get_nanoseconds),
174                None,
175                Attribute::CONFIGURABLE,
176            )
177            .accessor(
178                js_string!("sign"),
179                Some(get_sign),
180                None,
181                Attribute::CONFIGURABLE,
182            )
183            .accessor(
184                js_string!("blank"),
185                Some(is_blank),
186                None,
187                Attribute::CONFIGURABLE,
188            )
189            .static_method(Self::from, js_string!("from"), 1)
190            .static_method(Self::compare, js_string!("compare"), 2)
191            .method(Self::with, js_string!("with"), 1)
192            .method(Self::negated, js_string!("negated"), 0)
193            .method(Self::abs, js_string!("abs"), 0)
194            .method(Self::add, js_string!("add"), 1)
195            .method(Self::subtract, js_string!("subtract"), 1)
196            .method(Self::round, js_string!("round"), 1)
197            .method(Self::total, js_string!("total"), 1)
198            .method(Self::to_string, js_string!("toString"), 0)
199            .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
200            .method(Self::to_json, js_string!("toJSON"), 0)
201            .method(Self::value_of, js_string!("valueOf"), 0)
202            .build();
203    }
204
205    fn get(intrinsics: &Intrinsics) -> JsObject {
206        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
207    }
208}
209
210impl BuiltInConstructor for Duration {
211    const CONSTRUCTOR_ARGUMENTS: usize = 0;
212    const PROTOTYPE_STORAGE_SLOTS: usize = 36;
213    const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
214
215    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
216        StandardConstructors::duration;
217
218    fn constructor(
219        new_target: &JsValue,
220        args: &[JsValue],
221        context: &mut Context,
222    ) -> JsResult<JsValue> {
223        // 1. If NewTarget is undefined, then
224        if new_target.is_undefined() {
225            // a. Throw a TypeError exception.
226            return Err(JsNativeError::typ()
227                .with_message("NewTarget cannot be undefined for Temporal.Duration constructor.")
228                .into());
229        }
230
231        // 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years).
232        let years = args.get_or_undefined(0).map_or(Ok(0), |v| {
233            let finite = v.to_finitef64(context)?;
234            finite
235                .as_integer_if_integral::<i64>()
236                .map_err(JsError::from)
237        })?;
238
239        // 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months).
240        let months = args.get_or_undefined(1).map_or(Ok(0), |v| {
241            let finite = v.to_finitef64(context)?;
242            finite
243                .as_integer_if_integral::<i64>()
244                .map_err(JsError::from)
245        })?;
246
247        // 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks).
248        let weeks = args.get_or_undefined(2).map_or(Ok(0), |v| {
249            let finite = v.to_finitef64(context)?;
250            finite
251                .as_integer_if_integral::<i64>()
252                .map_err(JsError::from)
253        })?;
254
255        // 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days).
256        let days = args.get_or_undefined(3).map_or(Ok(0), |v| {
257            let finite = v.to_finitef64(context)?;
258            finite
259                .as_integer_if_integral::<i64>()
260                .map_err(JsError::from)
261        })?;
262
263        // 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours).
264        let hours = args.get_or_undefined(4).map_or(Ok(0), |v| {
265            let finite = v.to_finitef64(context)?;
266            finite
267                .as_integer_if_integral::<i64>()
268                .map_err(JsError::from)
269        })?;
270
271        // 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes).
272        let minutes = args.get_or_undefined(5).map_or(Ok(0), |v| {
273            let finite = v.to_finitef64(context)?;
274            finite
275                .as_integer_if_integral::<i64>()
276                .map_err(JsError::from)
277        })?;
278
279        // 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds).
280        let seconds = args.get_or_undefined(6).map_or(Ok(0), |v| {
281            let finite = v.to_finitef64(context)?;
282            finite
283                .as_integer_if_integral::<i64>()
284                .map_err(JsError::from)
285        })?;
286
287        // 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds).
288        let milliseconds = args.get_or_undefined(7).map_or(Ok(0), |v| {
289            let finite = v.to_finitef64(context)?;
290            finite
291                .as_integer_if_integral::<i64>()
292                .map_err(JsError::from)
293        })?;
294
295        // 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds).
296        let microseconds = args.get_or_undefined(8).map_or(Ok(0), |v| {
297            let finite = v.to_finitef64(context)?;
298            finite
299                .as_integer_if_integral::<i128>()
300                .map_err(JsError::from)
301        })?;
302
303        // 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds).
304        let nanoseconds = args.get_or_undefined(9).map_or(Ok(0), |v| {
305            let finite = v.to_finitef64(context)?;
306            finite
307                .as_integer_if_integral::<i128>()
308                .map_err(JsError::from)
309        })?;
310
311        let record = InnerDuration::new(
312            years,
313            months,
314            weeks,
315            days,
316            hours,
317            minutes,
318            seconds,
319            milliseconds,
320            microseconds,
321            nanoseconds,
322        )?;
323
324        // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget).
325        create_temporal_duration(record, Some(new_target), context).map(Into::into)
326    }
327}
328
329// ==== Duration accessor property implementations ====
330
331impl Duration {
332    // Internal utility function for getting `Duration` field values.
333    fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult<JsValue> {
334        let object = this.as_object();
335        let duration = object
336            .as_ref()
337            .and_then(JsObject::downcast_ref::<Self>)
338            .ok_or_else(|| {
339                JsNativeError::typ().with_message("this value must be a Duration object.")
340            })?;
341
342        let inner = &duration.inner;
343
344        match field {
345            DateTimeValues::Year => Ok(JsValue::new(inner.years())),
346            DateTimeValues::Month => Ok(JsValue::new(inner.months())),
347            DateTimeValues::Week => Ok(JsValue::new(inner.weeks())),
348            DateTimeValues::Day => Ok(JsValue::new(inner.days())),
349            DateTimeValues::Hour => Ok(JsValue::new(inner.hours())),
350            DateTimeValues::Minute => Ok(JsValue::new(inner.minutes())),
351            DateTimeValues::Second => Ok(JsValue::new(inner.seconds())),
352            DateTimeValues::Millisecond => Ok(JsValue::new(inner.milliseconds())),
353            DateTimeValues::Microsecond => Ok(JsValue::new(inner.microseconds() as f64)),
354            DateTimeValues::Nanosecond => Ok(JsValue::new(inner.nanoseconds() as f64)),
355            DateTimeValues::MonthCode => unreachable!(
356                "Any other DateTimeValue fields on Duration would be an implementation error."
357            ),
358        }
359    }
360
361    /// 7.3.3 get `Temporal.Duration.prototype.years`
362    ///
363    /// More information:
364    ///
365    /// - [ECMAScript Temporal proposal][spec]
366    /// - [MDN reference][mdn]
367    /// - [`temporal_rs` documentation][temporal_rs-docs]
368    ///
369    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years
370    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/years
371    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.years
372    fn get_years(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
373        Self::get_internal_field(this, &DateTimeValues::Year)
374    }
375
376    // 7.3.4 get `Temporal.Duration.prototype.months`
377    ///
378    /// More information:
379    ///
380    /// - [ECMAScript Temporal proposal][spec]
381    /// - [MDN reference][mdn]
382    /// - [`temporal_rs` documentation][temporal_rs-docs]
383    ///
384    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.months
385    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/months
386    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.months
387    fn get_months(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
388        Self::get_internal_field(this, &DateTimeValues::Month)
389    }
390
391    /// 7.3.5 get `Temporal.Duration.prototype.weeks`
392    ///
393    /// More information:
394    ///
395    /// - [ECMAScript Temporal proposal][spec]
396    /// - [MDN reference][mdn]
397    /// - [`temporal_rs` documentation][temporal_rs-docs]
398    ///
399    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.weeks
400    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/weeks
401    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.weeks
402    fn get_weeks(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
403        Self::get_internal_field(this, &DateTimeValues::Week)
404    }
405
406    /// 7.3.6 get `Temporal.Duration.prototype.days`
407    ///
408    /// More information:
409    ///
410    /// - [ECMAScript Temporal proposal][spec]
411    /// - [MDN reference][mdn]
412    /// - [`temporal_rs` documentation][temporal_rs-docs]
413    ///
414    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.days
415    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/days
416    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.days
417    fn get_days(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
418        Self::get_internal_field(this, &DateTimeValues::Day)
419    }
420
421    /// 7.3.7 get `Temporal.Duration.prototype.hours`
422    ///
423    /// More information:
424    ///
425    /// - [ECMAScript Temporal proposal][spec]
426    /// - [MDN reference][mdn]
427    /// - [`temporal_rs` documentation][temporal_rs-docs]
428    ///
429    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.hours
430    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/hours
431    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.hours
432    fn get_hours(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
433        Self::get_internal_field(this, &DateTimeValues::Hour)
434    }
435
436    /// 7.3.8 get `Temporal.Duration.prototype.minutes`
437    ///
438    /// More information:
439    ///
440    /// - [ECMAScript Temporal proposal][spec]
441    /// - [MDN reference][mdn]
442    /// - [`temporal_rs` documentation][temporal_rs-docs]
443    ///
444    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.minutes
445    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/minutes
446    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.minutes
447    fn get_minutes(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
448        Self::get_internal_field(this, &DateTimeValues::Minute)
449    }
450
451    /// 7.3.9 get `Temporal.Duration.prototype.seconds`
452    ///
453    /// More information:
454    ///
455    /// - [ECMAScript Temporal proposal][spec]
456    /// - [MDN reference][mdn]
457    /// - [`temporal_rs` documentation][temporal_rs-docs]
458    ///
459    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.seconds
460    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/seconds
461    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.seconds
462    fn get_seconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
463        Self::get_internal_field(this, &DateTimeValues::Second)
464    }
465
466    /// 7.3.10 get `Temporal.Duration.prototype.milliseconds`
467    ///
468    /// More information:
469    ///
470    /// - [ECMAScript Temporal proposal][spec]
471    /// - [MDN reference][mdn]
472    /// - [`temporal_rs` documentation][temporal_rs-docs]
473    ///
474    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.milliseconds
475    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/milliseconds
476    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.milliseconds
477    fn get_milliseconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
478        Self::get_internal_field(this, &DateTimeValues::Millisecond)
479    }
480
481    /// 7.3.11 get `Temporal.Duration.prototype.microseconds`
482    ///
483    /// More information:
484    ///
485    /// - [ECMAScript Temporal proposal][spec]
486    /// - [MDN reference][mdn]
487    /// - [`temporal_rs` documentation][temporal_rs-docs]
488    ///
489    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.microseconds
490    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/microseconds
491    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.microseconds
492    fn get_microseconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
493        Self::get_internal_field(this, &DateTimeValues::Microsecond)
494    }
495
496    /// 7.3.12 get `Temporal.Duration.prototype.nanoseconds`
497    ///
498    /// More information:
499    ///
500    /// - [ECMAScript Temporal proposal][spec]
501    /// - [MDN reference][mdn]
502    /// - [`temporal_rs` documentation][temporal_rs-docs]
503    ///
504    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds
505    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/nanoseconds
506    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.nanoseconds
507    fn get_nanoseconds(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
508        Self::get_internal_field(this, &DateTimeValues::Nanosecond)
509    }
510
511    /// 7.3.13 get `Temporal.Duration.prototype.sign`
512    ///
513    /// More information:
514    ///
515    /// - [ECMAScript Temporal proposal][spec]
516    /// - [MDN reference][mdn]
517    /// - [`temporal_rs` documentation][temporal_rs-docs]
518    ///
519    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.sign
520    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/sign
521    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.sign
522    fn get_sign(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
523        // 1. Let duration be the this value.
524        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
525        let object = this.as_object();
526        let duration = object
527            .as_ref()
528            .and_then(JsObject::downcast_ref::<Self>)
529            .ok_or_else(|| {
530                JsNativeError::typ().with_message("this value must be a Duration object.")
531            })?;
532
533        // 3. Return 𝔽(! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]],
534        // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]],
535        // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]])).
536        Ok((duration.inner.sign() as i8).into())
537    }
538
539    /// 7.3.14 get `Temporal.Duration.prototype.blank`
540    ///
541    /// More information:
542    ///
543    /// - [ECMAScript Temporal proposal][spec]
544    /// - [MDN reference][mdn]
545    /// - [`temporal_rs` documentation][temporal_rs-docs]
546    ///
547    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.blank
548    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/blank
549    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.blank
550    fn get_blank(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
551        // 1. Let duration be the this value.
552        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
553        let object = this.as_object();
554        let duration = object
555            .as_ref()
556            .and_then(JsObject::downcast_ref::<Self>)
557            .ok_or_else(|| {
558                JsNativeError::typ().with_message("this value must be a Duration object.")
559            })?;
560
561        // 3. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]],
562        // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]],
563        // duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
564        // 4. If sign = 0, return true.
565        // 5. Return false.
566        Ok(duration.inner.is_zero().into())
567    }
568}
569
570// ==== Duration static methods implementation ====
571
572impl Duration {
573    /// 7.2.2 `Temporal.Duration.from ( item )`
574    ///
575    /// More information:
576    ///
577    /// - [ECMAScript Temporal proposal][spec]
578    /// - [MDN reference][mdn]
579    ///
580    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.from
581    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/from
582    fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
583        let item = args.get_or_undefined(0);
584        // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then
585        let object = item.as_object();
586        if let Some(duration) = object.as_ref().and_then(JsObject::downcast_ref::<Self>) {
587            // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]],
588            // item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]],
589            // item.[[Microseconds]], item.[[Nanoseconds]]).
590            return create_temporal_duration(*duration.inner, None, context).map(Into::into);
591        }
592
593        // 2. Return ? ToTemporalDuration(item).
594        create_temporal_duration(to_temporal_duration_record(item, context)?, None, context)
595            .map(Into::into)
596    }
597
598    /// 7.2.3 `Temporal.Duration.compare ( one, two [ , options ] )`
599    ///
600    /// More information:
601    ///
602    /// - [ECMAScript Temporal proposal][spec]
603    /// - [MDN reference][mdn]
604    /// - [`temporal_rs` documentation][temporal_rs-docs]
605    ///
606    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.compare
607    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/compare
608    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.compare
609    fn compare(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
610        // 1. Set one to ? ToTemporalDuration(one).
611        let one = to_temporal_duration(args.get_or_undefined(0), context)?;
612        // 2. Set two to ? ToTemporalDuration(two).
613        let two = to_temporal_duration(args.get_or_undefined(1), context)?;
614        // 3. Let resolvedOptions be ? GetOptionsObject(options).
615        let options = get_options_object(args.get_or_undefined(2))?;
616        // 4. Let relativeToRecord be ? GetTemporalRelativeToOption(resolvedOptions).
617        let relative_to = get_relative_to_option(&options, context)?;
618
619        Ok(
620            (one.compare_with_provider(&two, relative_to, context.timezone_provider())? as i8)
621                .into(),
622        )
623    }
624}
625
626// ==== Duration methods implementation ====
627
628impl Duration {
629    /// 7.3.15 `Temporal.Duration.prototype.with ( temporalDurationLike )`
630    ///
631    /// More information:
632    ///
633    /// - [ECMAScript Temporal proposal][spec]
634    /// - [MDN reference][mdn]
635    ///
636    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with
637    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/with
638    pub(crate) fn with(
639        this: &JsValue,
640        args: &[JsValue],
641        context: &mut Context,
642    ) -> JsResult<JsValue> {
643        // 1. Let duration be the this value.
644        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
645        let object = this.as_object();
646        let duration = object
647            .as_ref()
648            .and_then(JsObject::downcast_ref::<Self>)
649            .ok_or_else(|| {
650                JsNativeError::typ().with_message("this value must be a Duration object.")
651            })?;
652
653        // 3. Let temporalDurationLike be ? ToTemporalPartialDurationRecord(temporalDurationLike).
654        let temporal_duration_like =
655            to_temporal_partial_duration(args.get_or_undefined(0), context)?;
656
657        // 4. If temporalDurationLike.[[Years]] is not undefined, then
658        // a. Let years be temporalDurationLike.[[Years]].
659        // 5. Else,
660        // a. Let years be duration.[[Years]].
661        let years = temporal_duration_like
662            .years
663            .unwrap_or(duration.inner.years());
664
665        // 6. If temporalDurationLike.[[Months]] is not undefined, then
666        // a. Let months be temporalDurationLike.[[Months]].
667        // 7. Else,
668        // a. Let months be duration.[[Months]].
669        let months = temporal_duration_like
670            .months
671            .unwrap_or(duration.inner.months());
672
673        // 8. If temporalDurationLike.[[Weeks]] is not undefined, then
674        // a. Let weeks be temporalDurationLike.[[Weeks]].
675        // 9. Else,
676        // a. Let weeks be duration.[[Weeks]].
677        let weeks = temporal_duration_like
678            .weeks
679            .unwrap_or(duration.inner.weeks());
680
681        // 10. If temporalDurationLike.[[Days]] is not undefined, then
682        // a. Let days be temporalDurationLike.[[Days]].
683        // 11. Else,
684        // a. Let days be duration.[[Days]].
685        let days = temporal_duration_like.days.unwrap_or(duration.inner.days());
686
687        // 12. If temporalDurationLike.[[Hours]] is not undefined, then
688        // a. Let hours be temporalDurationLike.[[Hours]].
689        // 13. Else,
690        // a. Let hours be duration.[[Hours]].
691        let hours = temporal_duration_like
692            .hours
693            .unwrap_or(duration.inner.hours());
694
695        // 14. If temporalDurationLike.[[Minutes]] is not undefined, then
696        // a. Let minutes be temporalDurationLike.[[Minutes]].
697        // 15. Else,
698        // a. Let minutes be duration.[[Minutes]].
699        let minutes = temporal_duration_like
700            .minutes
701            .unwrap_or(duration.inner.minutes());
702
703        // 16. If temporalDurationLike.[[Seconds]] is not undefined, then
704        // a. Let seconds be temporalDurationLike.[[Seconds]].
705        // 17. Else,
706        // a. Let seconds be duration.[[Seconds]].
707        let seconds = temporal_duration_like
708            .seconds
709            .unwrap_or(duration.inner.seconds());
710
711        // 18. If temporalDurationLike.[[Milliseconds]] is not undefined, then
712        // a. Let milliseconds be temporalDurationLike.[[Milliseconds]].
713        // 19. Else,
714        // a. Let milliseconds be duration.[[Milliseconds]].
715        let milliseconds = temporal_duration_like
716            .milliseconds
717            .unwrap_or(duration.inner.milliseconds());
718
719        // 20. If temporalDurationLike.[[Microseconds]] is not undefined, then
720        // a. Let microseconds be temporalDurationLike.[[Microseconds]].
721        // 21. Else,
722        // a. Let microseconds be duration.[[Microseconds]].
723        let microseconds = temporal_duration_like
724            .microseconds
725            .unwrap_or(duration.inner.microseconds());
726
727        // 22. If temporalDurationLike.[[Nanoseconds]] is not undefined, then
728        // a. Let nanoseconds be temporalDurationLike.[[Nanoseconds]].
729        // 23. Else,
730        // a. Let nanoseconds be duration.[[Nanoseconds]].
731        let nanoseconds = temporal_duration_like
732            .nanoseconds
733            .unwrap_or(duration.inner.nanoseconds());
734
735        // 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
736        let new_duration = InnerDuration::new(
737            years,
738            months,
739            weeks,
740            days,
741            hours,
742            minutes,
743            seconds,
744            milliseconds,
745            microseconds,
746            nanoseconds,
747        )?;
748        create_temporal_duration(new_duration, None, context).map(Into::into)
749    }
750
751    /// 7.3.16 `Temporal.Duration.prototype.negated ( )`
752    ///
753    /// More information:
754    ///
755    /// - [ECMAScript Temporal proposal][spec]
756    /// - [MDN reference][mdn]
757    /// - [`temporal_rs` documentation][temporal_rs-docs]
758    ///
759    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.negated
760    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/negated
761    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.negated
762    pub(crate) fn negated(
763        this: &JsValue,
764        _: &[JsValue],
765        context: &mut Context,
766    ) -> JsResult<JsValue> {
767        // 1. Let duration be the this value.
768        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
769        // 3. Return ! CreateNegatedTemporalDuration(duration).
770        let object = this.as_object();
771        let duration = object
772            .as_ref()
773            .and_then(JsObject::downcast_ref::<Self>)
774            .ok_or_else(|| {
775                JsNativeError::typ().with_message("this value must be a Duration object.")
776            })?;
777
778        create_temporal_duration(duration.inner.negated(), None, context).map(Into::into)
779    }
780
781    /// 7.3.17 `Temporal.Duration.prototype.abs ( )`
782    ///
783    /// More information:
784    ///
785    /// - [ECMAScript Temporal proposal][spec]
786    /// - [MDN reference][mdn]
787    /// - [`temporal_rs` documentation][temporal_rs-docs]
788    ///
789    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.abs
790    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/abs
791    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.abs
792    pub(crate) fn abs(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
793        // 1. Let duration be the this value.
794        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
795        // 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]),
796        //    abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]),
797        //    abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])).
798        let object = this.as_object();
799        let duration = object
800            .as_ref()
801            .and_then(JsObject::downcast_ref::<Self>)
802            .ok_or_else(|| {
803                JsNativeError::typ().with_message("this value must be a Duration object.")
804            })?;
805
806        create_temporal_duration(duration.inner.abs(), None, context).map(Into::into)
807    }
808
809    /// 7.3.18 `Temporal.Duration.prototype.add ( other [ , options ] )`
810    ///
811    /// More information:
812    ///
813    /// - [ECMAScript Temporal proposal][spec]
814    /// - [MDN reference][mdn]
815    /// - [`temporal_rs` documentation][temporal_rs-docs]
816    ///
817    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add
818    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/add
819    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.add
820    pub(crate) fn add(
821        this: &JsValue,
822        args: &[JsValue],
823        context: &mut Context,
824    ) -> JsResult<JsValue> {
825        // 1.Let duration be the this value.
826        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
827        let object = this.as_object();
828        let duration = object
829            .as_ref()
830            .and_then(JsObject::downcast_ref::<Self>)
831            .ok_or_else(|| {
832                JsNativeError::typ().with_message("this value must be a Duration object.")
833            })?;
834
835        // 3. Return ? AddDurations(add, duration, other).
836        let other = to_temporal_duration_record(args.get_or_undefined(0), context)?;
837
838        create_temporal_duration(duration.inner.add(&other)?, None, context).map(Into::into)
839    }
840
841    /// 7.3.19 `Temporal.Duration.prototype.subtract ( other [ , options ] )`
842    ///
843    /// More information:
844    ///
845    /// - [ECMAScript Temporal proposal][spec]
846    /// - [MDN reference][mdn]
847    /// - [`temporal_rs` documentation][temporal_rs-docs]
848    ///
849    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract
850    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/subtract
851    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.subtract
852    pub(crate) fn subtract(
853        this: &JsValue,
854        args: &[JsValue],
855        context: &mut Context,
856    ) -> JsResult<JsValue> {
857        // 1.Let duration be the this value.
858        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
859        let object = this.as_object();
860        let duration = object
861            .as_ref()
862            .and_then(JsObject::downcast_ref::<Self>)
863            .ok_or_else(|| {
864                JsNativeError::typ().with_message("this value must be a Duration object.")
865            })?;
866
867        let other = to_temporal_duration_record(args.get_or_undefined(0), context)?;
868
869        // 3. Return ? AddDurations(add, duration, other).
870        create_temporal_duration(duration.inner.subtract(&other)?, None, context).map(Into::into)
871    }
872
873    /// 7.3.20 `Temporal.Duration.prototype.round ( roundTo )`
874    ///
875    /// More information:
876    ///
877    /// - [ECMAScript Temporal proposal][spec]
878    /// - [MDN reference][mdn]
879    /// - [`temporal_rs` documentation][temporal_rs-docs]
880    ///
881    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round
882    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/round
883    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.round
884    pub(crate) fn round(
885        this: &JsValue,
886        args: &[JsValue],
887        context: &mut Context,
888    ) -> JsResult<JsValue> {
889        // 1. Let duration be the this value.
890        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
891        let object = this.as_object();
892        let duration = object
893            .as_ref()
894            .and_then(JsObject::downcast_ref::<Self>)
895            .ok_or_else(|| {
896                JsNativeError::typ().with_message("this value must be a Duration object.")
897            })?;
898
899        // 3. If roundTo is undefined, then
900        let round_to_arg = args.get_or_undefined(0);
901        if round_to_arg.is_undefined() {
902            return Err(JsNativeError::typ()
903                .with_message("roundTo cannot be undefined.")
904                .into());
905        }
906        // 4. If Type(roundTo) is String, then
907        let round_to = if let Some(param_string) = round_to_arg.as_string() {
908            // a. Let paramString be roundTo.
909            let param_string = param_string.clone();
910            // b. Set roundTo to OrdinaryObjectCreate(null).
911            let new_round_to = JsObject::with_null_proto();
912            // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
913            new_round_to.create_data_property_or_throw(
914                js_string!("smallestUnit"),
915                param_string,
916                context,
917            )?;
918            new_round_to
919        } else {
920            // 5. Else,
921            // a. Set roundTo to ? GetOptionsObject(roundTo).
922            get_options_object(round_to_arg)?
923        };
924
925        // NOTE: 6 & 7 unused in favor of `is_none()`.
926        // 6. Let smallestUnitPresent be true.
927        // 7. Let largestUnitPresent be true.
928        let mut options = RoundingOptions::default();
929
930        // 8. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo", ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
931        // 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »).
932        options.largest_unit = get_temporal_unit(
933            &round_to,
934            js_string!("largestUnit"),
935            TemporalUnitGroup::DateTime,
936            Some([Unit::Auto].into()),
937            context,
938        )?;
939
940        // 10. Let relativeToRecord be ? ToRelativeTemporalObject(roundTo).
941        // 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
942        // 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
943        let relative_to = get_relative_to_option(&round_to, context)?;
944
945        // 13. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
946        options.increment =
947            get_option::<RoundingIncrement>(&round_to, js_string!("roundingIncrement"), context)?;
948
949        // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
950        options.rounding_mode =
951            get_option::<RoundingMode>(&round_to, js_string!("roundingMode"), context)?;
952
953        // 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined).
954        options.smallest_unit = get_temporal_unit(
955            &round_to,
956            js_string!("smallestUnit"),
957            TemporalUnitGroup::DateTime,
958            None,
959            context,
960        )?;
961
962        // NOTE: execute step 21 earlier before initial values are shadowed.
963        // 21. If smallestUnitPresent is false and largestUnitPresent is false, then
964
965        let rounded_duration = duration.inner.round_with_provider(
966            options,
967            relative_to,
968            context.timezone_provider(),
969        )?;
970        create_temporal_duration(rounded_duration, None, context).map(Into::into)
971    }
972
973    /// 7.3.21 `Temporal.Duration.prototype.total ( totalOf )`
974    ///
975    /// More information:
976    ///
977    /// - [ECMAScript Temporal proposal][spec]
978    /// - [MDN reference][mdn]
979    /// - [`temporal_rs` documentation][temporal_rs-docs]
980    ///
981    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total
982    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/total
983    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.total
984    pub(crate) fn total(
985        this: &JsValue,
986        args: &[JsValue],
987        context: &mut Context,
988    ) -> JsResult<JsValue> {
989        // 1. Let duration be the this value.
990        // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
991        let object = this.as_object();
992        let duration = object
993            .as_ref()
994            .and_then(JsObject::downcast_ref::<Self>)
995            .ok_or_else(|| {
996                JsNativeError::typ().with_message("this value must be a Duration object.")
997            })?;
998
999        let total_of = args.get_or_undefined(0);
1000
1001        let total_of = match total_of.variant() {
1002            // 3. If totalOf is undefined, throw a TypeError exception.
1003            JsVariant::Undefined => {
1004                return Err(JsNativeError::typ()
1005                    .with_message("totalOf cannot be undefined.")
1006                    .into());
1007            }
1008            // 4. If Type(totalOf) is String, then
1009            JsVariant::String(param_string) => {
1010                // a. Let paramString be totalOf.
1011                // b. Set totalOf to OrdinaryObjectCreate(null).
1012                let total_of = JsObject::with_null_proto();
1013                // c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString).
1014                total_of.create_data_property_or_throw(
1015                    js_string!("unit"),
1016                    param_string.clone(),
1017                    context,
1018                )?;
1019                total_of
1020            }
1021            // 5. Else,
1022            _ => {
1023                // a. Set totalOf to ? GetOptionsObject(totalOf).
1024                get_options_object(total_of)?
1025            }
1026        };
1027
1028        // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToRelativeTemporalObject reads "relativeTo").
1029        // 7. Let relativeToRecord be ? ToRelativeTemporalObject(totalOf).
1030        // 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
1031        // 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
1032        let relative_to = get_relative_to_option(&total_of, context)?;
1033
1034        // 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required).
1035        let unit = get_temporal_unit(
1036            &total_of,
1037            js_string!("unit"),
1038            TemporalUnitGroup::DateTime,
1039            None,
1040            context,
1041        )?
1042        .ok_or_else(|| JsNativeError::range().with_message("unit cannot be undefined."))?;
1043
1044        Ok(duration
1045            .inner
1046            .total_with_provider(unit, relative_to, context.timezone_provider())?
1047            .as_inner()
1048            .into())
1049    }
1050
1051    /// 7.3.22 `Temporal.Duration.prototype.toString ( [ options ] )`
1052    ///
1053    /// More information:
1054    ///
1055    /// - [ECMAScript Temporal proposal][spec]
1056    /// - [MDN reference][mdn]
1057    /// - [`temporal_rs` documentation][temporal_rs-docs]
1058    ///
1059    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
1060    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/toString
1061    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Duration.html#method.as_temporal_string
1062    pub(crate) fn to_string(
1063        this: &JsValue,
1064        args: &[JsValue],
1065        context: &mut Context,
1066    ) -> JsResult<JsValue> {
1067        let object = this.as_object();
1068        let duration = object
1069            .as_ref()
1070            .and_then(JsObject::downcast_ref::<Self>)
1071            .ok_or_else(|| {
1072                JsNativeError::typ().with_message("this value must be a Duration object.")
1073            })?;
1074
1075        let options = get_options_object(args.get_or_undefined(0))?;
1076        let precision = get_digits_option(&options, context)?;
1077        let rounding_mode =
1078            get_option::<RoundingMode>(&options, js_string!("roundingMode"), context)?;
1079        let smallest_unit = get_option::<Unit>(&options, js_string!("smallestUnit"), context)?;
1080
1081        let result = duration.inner.as_temporal_string(ToStringRoundingOptions {
1082            precision,
1083            smallest_unit,
1084            rounding_mode,
1085        })?;
1086
1087        Ok(JsString::from(result).into())
1088    }
1089
1090    /// 7.3.23 `Temporal.Duration.prototype.toJSON ( )`
1091    ///
1092    /// More information:
1093    ///
1094    /// - [ECMAScript Temporal proposal][spec]
1095    /// - [MDN reference][mdn]
1096    ///
1097    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tojson
1098    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/toJSON
1099    pub(crate) fn to_json(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1100        let object = this.as_object();
1101        let duration = object
1102            .as_ref()
1103            .and_then(JsObject::downcast_ref::<Self>)
1104            .ok_or_else(|| {
1105                JsNativeError::typ().with_message("this value must be a Duration object.")
1106            })?;
1107
1108        let result = duration
1109            .inner
1110            .as_temporal_string(ToStringRoundingOptions::default())?;
1111
1112        Ok(JsString::from(result).into())
1113    }
1114
1115    /// 7.3.24 `Temporal.Duration.prototype.toLocaleString ( )`
1116    ///
1117    /// More information:
1118    ///
1119    /// - [ECMAScript Temporal proposal][spec]
1120    /// - [MDN reference][mdn]
1121    ///
1122    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tolocalestring
1123    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/toLocaleString
1124    pub(crate) fn to_locale_string(
1125        this: &JsValue,
1126        _: &[JsValue],
1127        _: &mut Context,
1128    ) -> JsResult<JsValue> {
1129        // TODO: Update for ECMA-402 compliance
1130        let object = this.as_object();
1131        let duration = object
1132            .as_ref()
1133            .and_then(JsObject::downcast_ref::<Self>)
1134            .ok_or_else(|| {
1135                JsNativeError::typ().with_message("this value must be a Duration object.")
1136            })?;
1137
1138        let result = duration
1139            .inner
1140            .as_temporal_string(ToStringRoundingOptions::default())?;
1141
1142        Ok(JsString::from(result).into())
1143    }
1144
1145    /// 7.3.25 `Temporal.Duration.prototype.valueOf ( )`
1146    ///
1147    /// More information:
1148    ///
1149    /// - [ECMAScript Temporal proposal][spec]
1150    /// - [MDN reference][mdn]
1151    ///
1152    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.valueof
1153    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/valueOf
1154    pub(crate) fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1155        Err(JsNativeError::typ()
1156            .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`")
1157            .into())
1158    }
1159}
1160
1161// -- Duration Abstract Operations --
1162
1163/// 7.5.12 `ToTemporalDuration ( item )`
1164pub(crate) fn to_temporal_duration(
1165    item: &JsValue,
1166    context: &mut Context,
1167) -> JsResult<InnerDuration> {
1168    // 1a. If Type(item) is Object
1169    // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then
1170    let object = item.as_object();
1171    if let Some(duration) = object.as_ref().and_then(JsObject::downcast_ref::<Duration>) {
1172        return Ok(*duration.inner);
1173    }
1174
1175    // 2. Let result be ? ToTemporalDurationRecord(item).
1176    let result = to_temporal_duration_record(item, context)?;
1177    // 3. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
1178    Ok(result)
1179}
1180
1181/// 7.5.13 `ToTemporalDurationRecord ( temporalDurationLike )`
1182pub(crate) fn to_temporal_duration_record(
1183    temporal_duration_like: &JsValue,
1184    context: &mut Context,
1185) -> JsResult<InnerDuration> {
1186    // 1. If Type(temporalDurationLike) is not Object, then
1187    let Some(duration_obj) = temporal_duration_like.as_object() else {
1188        // a. If temporalDurationLike is not a String, throw a TypeError exception.
1189        let Some(duration_string) = temporal_duration_like.as_string() else {
1190            return Err(JsNativeError::typ()
1191                .with_message("Invalid TemporalDurationLike value.")
1192                .into());
1193        };
1194
1195        // b. Return ? ParseTemporalDurationString(temporalDurationLike).
1196        return duration_string
1197            .to_std_string_escaped()
1198            .parse::<InnerDuration>()
1199            .map_err(Into::into);
1200    };
1201
1202    // 2. If temporalDurationLike has an [[InitializedTemporalDuration]] internal slot, then
1203    if let Some(duration) = duration_obj.downcast_ref::<Duration>() {
1204        // a. Return ! CreateDurationRecord(temporalDurationLike.[[Years]], temporalDurationLike.[[Months]], temporalDurationLike.[[Weeks]], temporalDurationLike.[[Days]], temporalDurationLike.[[Hours]], temporalDurationLike.[[Minutes]], temporalDurationLike.[[Seconds]], temporalDurationLike.[[Milliseconds]], temporalDurationLike.[[Microseconds]], temporalDurationLike.[[Nanoseconds]]).
1205        return Ok(*duration.inner);
1206    }
1207
1208    // 3. Let result be a new Duration Record with each field set to 0.
1209    // 4. Let partial be ? ToTemporalPartialDurationRecord(temporalDurationLike).
1210    let partial = to_temporal_partial_duration(temporal_duration_like, context)?;
1211
1212    // 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]].
1213
1214    // 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]].
1215    // 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]].
1216    // 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]].
1217    // 9. If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]].
1218    // 10. If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]].
1219    // 11. If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]].
1220    // 12. If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]].
1221    // 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]].
1222    // 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]].
1223    // 15. If ! IsValidDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]) is false, then
1224    // a. Throw a RangeError exception.
1225    // 16. Return result.
1226    InnerDuration::from_partial_duration(partial).map_err(Into::into)
1227}
1228
1229/// 7.5.14 `CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )`
1230pub(crate) fn create_temporal_duration(
1231    inner: InnerDuration,
1232    new_target: Option<&JsValue>,
1233    context: &mut Context,
1234) -> JsResult<JsObject> {
1235    // 1. If ! IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception.
1236
1237    // 2. If newTarget is not present, set newTarget to %Temporal.Duration%.
1238    let new_target = if let Some(target) = new_target {
1239        target.clone()
1240    } else {
1241        context
1242            .realm()
1243            .intrinsics()
1244            .constructors()
1245            .duration()
1246            .constructor()
1247            .into()
1248    };
1249
1250    // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »).
1251    let prototype =
1252        get_prototype_from_constructor(&new_target, StandardConstructors::duration, context)?;
1253
1254    // 4. Set object.[[Years]] to ℝ(𝔽(years)).
1255    // 5. Set object.[[Months]] to ℝ(𝔽(months)).
1256    // 6. Set object.[[Weeks]] to ℝ(𝔽(weeks)).
1257    // 7. Set object.[[Days]] to ℝ(𝔽(days)).
1258    // 8. Set object.[[Hours]] to ℝ(𝔽(hours)).
1259    // 9. Set object.[[Minutes]] to ℝ(𝔽(minutes)).
1260    // 10. Set object.[[Seconds]] to ℝ(𝔽(seconds)).
1261    // 11. Set object.[[Milliseconds]] to ℝ(𝔽(milliseconds)).
1262    // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)).
1263    // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)).
1264
1265    let obj = JsObject::from_proto_and_data(prototype, Duration::new(inner));
1266    // 14. Return object.
1267    Ok(obj)
1268}
1269
1270/// Equivalent to 7.5.13 `ToTemporalPartialDurationRecord ( temporalDurationLike )`
1271pub(crate) fn to_temporal_partial_duration(
1272    duration_like: &JsValue,
1273    context: &mut Context,
1274) -> JsResult<PartialDuration> {
1275    // 1. If Type(temporalDurationLike) is not Object, then
1276    let Some(unknown_object) = duration_like.as_object() else {
1277        // a. Throw a TypeError exception.
1278        return Err(JsNativeError::typ()
1279            .with_message("temporalDurationLike must be an object.")
1280            .into());
1281    };
1282
1283    // 2. Let result be a new partial Duration Record with each field set to undefined.
1284    // 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
1285    // 4. Let days be ? Get(temporalDurationLike, "days").
1286    // 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
1287    let days = unknown_object
1288        .get(js_string!("days"), context)?
1289        .map(|v| {
1290            let finite = v.to_finitef64(context)?;
1291            finite
1292                .as_integer_if_integral::<i64>()
1293                .map_err(JsError::from)
1294        })
1295        .transpose()?;
1296
1297    // 6. Let hours be ? Get(temporalDurationLike, "hours").
1298    // 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
1299    let hours = unknown_object
1300        .get(js_string!("hours"), context)?
1301        .map(|v| {
1302            let finite = v.to_finitef64(context)?;
1303            finite
1304                .as_integer_if_integral::<i64>()
1305                .map_err(JsError::from)
1306        })
1307        .transpose()?;
1308
1309    // 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
1310    // 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
1311    let microseconds = unknown_object
1312        .get(js_string!("microseconds"), context)?
1313        .map(|v| {
1314            let finite = v.to_finitef64(context)?;
1315            finite
1316                .as_integer_if_integral::<i128>()
1317                .map_err(JsError::from)
1318        })
1319        .transpose()?;
1320
1321    // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
1322    // 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
1323    let milliseconds = unknown_object
1324        .get(js_string!("milliseconds"), context)?
1325        .map(|v| {
1326            let finite = v.to_finitef64(context)?;
1327            finite
1328                .as_integer_if_integral::<i64>()
1329                .map_err(JsError::from)
1330        })
1331        .transpose()?;
1332
1333    // 12. Let minutes be ? Get(temporalDurationLike, "minutes").
1334    // 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
1335    let minutes = unknown_object
1336        .get(js_string!("minutes"), context)?
1337        .map(|v| {
1338            let finite = v.to_finitef64(context)?;
1339            finite
1340                .as_integer_if_integral::<i64>()
1341                .map_err(JsError::from)
1342        })
1343        .transpose()?;
1344
1345    // 14. Let months be ? Get(temporalDurationLike, "months").
1346    // 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
1347    let months = unknown_object
1348        .get(js_string!("months"), context)?
1349        .map(|v| {
1350            let finite = v.to_finitef64(context)?;
1351            finite
1352                .as_integer_if_integral::<i64>()
1353                .map_err(JsError::from)
1354        })
1355        .transpose()?;
1356
1357    // 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
1358    // 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
1359    let nanoseconds = unknown_object
1360        .get(js_string!("nanoseconds"), context)?
1361        .map(|v| {
1362            let finite = v.to_finitef64(context)?;
1363            finite
1364                .as_integer_if_integral::<i128>()
1365                .map_err(JsError::from)
1366        })
1367        .transpose()?;
1368
1369    // 18. Let seconds be ? Get(temporalDurationLike, "seconds").
1370    // 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
1371    let seconds = unknown_object
1372        .get(js_string!("seconds"), context)?
1373        .map(|v| {
1374            let finite = v.to_finitef64(context)?;
1375            finite
1376                .as_integer_if_integral::<i64>()
1377                .map_err(JsError::from)
1378        })
1379        .transpose()?;
1380
1381    // 20. Let weeks be ? Get(temporalDurationLike, "weeks").
1382    // 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
1383    let weeks = unknown_object
1384        .get(js_string!("weeks"), context)?
1385        .map(|v| {
1386            let finite = v.to_finitef64(context)?;
1387            finite
1388                .as_integer_if_integral::<i64>()
1389                .map_err(JsError::from)
1390        })
1391        .transpose()?;
1392
1393    // 22. Let years be ? Get(temporalDurationLike, "years").
1394    // 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
1395    let years = unknown_object
1396        .get(js_string!("years"), context)?
1397        .map(|v| {
1398            let finite = v.to_finitef64(context)?;
1399            finite
1400                .as_integer_if_integral::<i64>()
1401                .map_err(JsError::from)
1402        })
1403        .transpose()?;
1404
1405    let partial = PartialDuration {
1406        years,
1407        months,
1408        weeks,
1409        days,
1410        hours,
1411        minutes,
1412        seconds,
1413        milliseconds,
1414        microseconds,
1415        nanoseconds,
1416    };
1417
1418    // 24. If years is undefined, and months is undefined, and weeks is undefined, and days
1419    // is undefined, and hours is undefined, and minutes is undefined, and seconds is
1420    // undefined, and milliseconds is undefined, and microseconds is undefined, and
1421    // nanoseconds is undefined, throw a TypeError exception.
1422    if partial.is_empty() {
1423        return Err(JsNativeError::typ()
1424            .with_message("PartialDurationRecord must have a defined field.")
1425            .into());
1426    }
1427
1428    // 25. Return result.
1429    Ok(partial)
1430}