Skip to main content

boa_engine/builtins/temporal/instant/
mod.rs

1//! Boa's implementation of ECMAScript's `Temporal.Instant` built-in object.
2
3use super::options::{get_difference_settings, get_digits_option};
4use super::{create_temporal_zoneddatetime, to_temporal_timezone_identifier};
5use crate::js_error;
6use crate::{
7    Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
8    JsValue,
9    builtins::{
10        BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
11        options::{get_option, get_options_object},
12        temporal::{
13            ZonedDateTime,
14            duration::{create_temporal_duration, to_temporal_duration_record},
15            options::{TemporalUnitGroup, get_temporal_unit},
16        },
17    },
18    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
19    js_string,
20    object::internal_methods::get_prototype_from_constructor,
21    property::Attribute,
22    realm::Realm,
23    string::StaticJsStrings,
24    value::PreferredType,
25};
26use boa_gc::{Finalize, Trace};
27use num_traits::ToPrimitive;
28use temporal_rs::options::{ToStringRoundingOptions, Unit};
29use temporal_rs::{
30    Instant as InnerInstant,
31    options::{RoundingIncrement, RoundingMode, RoundingOptions},
32};
33
34/// The `Temporal.Instant` built-in implementation
35///
36/// More information:
37///
38/// - [ECMAScript Temporal proposal][spec]
39/// - [MDN reference][mdn]
40/// - [`temporal_rs` documentation][temporal_rs-docs]
41///
42/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-instant-objects
43/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant
44/// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html
45#[derive(Debug, Clone, Trace, Finalize, JsData)]
46#[boa_gc(unsafe_empty_trace)] // Safety: Does not contain any traceable fields.
47pub struct Instant {
48    pub(crate) inner: Box<InnerInstant>,
49}
50
51impl Instant {
52    pub(crate) fn new(inner: InnerInstant) -> Self {
53        Self {
54            inner: Box::new(inner),
55        }
56    }
57}
58
59impl BuiltInObject for Instant {
60    const NAME: JsString = StaticJsStrings::INSTANT_NAME;
61}
62
63impl IntrinsicObject for Instant {
64    fn init(realm: &Realm) {
65        let get_millis = BuiltInBuilder::callable(realm, Self::get_epoch_milliseconds)
66            .name(js_string!("get epochMilliseconds"))
67            .build();
68
69        let get_nanos = BuiltInBuilder::callable(realm, Self::get_epoch_nanoseconds)
70            .name(js_string!("get epochNanoseconds"))
71            .build();
72
73        BuiltInBuilder::from_standard_constructor::<Self>(realm)
74            .property(
75                JsSymbol::to_string_tag(),
76                StaticJsStrings::INSTANT_TAG,
77                Attribute::CONFIGURABLE,
78            )
79            .accessor(
80                js_string!("epochMilliseconds"),
81                Some(get_millis),
82                None,
83                Attribute::CONFIGURABLE,
84            )
85            .accessor(
86                js_string!("epochNanoseconds"),
87                Some(get_nanos),
88                None,
89                Attribute::CONFIGURABLE,
90            )
91            .static_method(Self::from, js_string!("from"), 1)
92            .static_method(
93                Self::from_epoch_milliseconds,
94                js_string!("fromEpochMilliseconds"),
95                1,
96            )
97            .static_method(
98                Self::from_epoch_nanoseconds,
99                js_string!("fromEpochNanoseconds"),
100                1,
101            )
102            .static_method(Self::compare, js_string!("compare"), 2)
103            .method(Self::add, js_string!("add"), 1)
104            .method(Self::subtract, js_string!("subtract"), 1)
105            .method(Self::until, js_string!("until"), 1)
106            .method(Self::since, js_string!("since"), 1)
107            .method(Self::round, js_string!("round"), 1)
108            .method(Self::equals, js_string!("equals"), 1)
109            .method(Self::to_string, js_string!("toString"), 0)
110            .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
111            .method(Self::to_json, js_string!("toJSON"), 0)
112            .method(Self::value_of, js_string!("valueOf"), 0)
113            .method(
114                Self::to_zoned_date_time_iso,
115                js_string!("toZonedDateTimeISO"),
116                1,
117            )
118            .build();
119    }
120
121    fn get(intrinsics: &Intrinsics) -> JsObject {
122        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
123    }
124}
125
126impl BuiltInConstructor for Instant {
127    const CONSTRUCTOR_ARGUMENTS: usize = 1;
128    const PROTOTYPE_STORAGE_SLOTS: usize = 16;
129    const CONSTRUCTOR_STORAGE_SLOTS: usize = 4;
130
131    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
132        StandardConstructors::instant;
133
134    fn constructor(
135        new_target: &JsValue,
136        args: &[JsValue],
137        context: &mut Context,
138    ) -> JsResult<JsValue> {
139        // 1. If NewTarget is undefined, then
140        if new_target.is_undefined() {
141            // a. Throw a TypeError exception.
142            return Err(JsNativeError::typ()
143                .with_message("Temporal.Instant new target cannot be undefined.")
144                .into());
145        }
146
147        // 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds).
148        let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?;
149
150        // 3. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
151        // NOTE: temporal_rs::Instant asserts that the epochNanoseconds are valid.
152        let instant = InnerInstant::try_new(epoch_nanos.as_inner().to_i128().unwrap_or(i128::MAX))?;
153        // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget).
154        create_temporal_instant(instant, Some(new_target.clone()), context)
155    }
156}
157
158// ==== Instant static methods implementation ====
159
160impl Instant {
161    /// 8.2.2 Temporal.Instant.from ( item )
162    ///
163    /// More information:
164    ///
165    /// - [ECMAScript Temporal proposal][spec]
166    /// - [MDN reference][mdn]
167    ///
168    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.from
169    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/from
170    pub(crate) fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
171        // 1. If item is an Object and item has an [[InitializedTemporalInstant]] internal slot, then
172        // a. Return ! CreateTemporalInstant(item.[[Nanoseconds]]).
173        // 2. Return ? ToTemporalInstant(item).
174        create_temporal_instant(
175            to_temporal_instant(args.get_or_undefined(0), context)?,
176            None,
177            context,
178        )
179    }
180
181    /// 8.2.3 `Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )`
182    ///
183    /// More information:
184    ///
185    /// - [ECMAScript Temporal proposal][spec]
186    /// - [MDN reference][mdn]
187    /// - [`temporal_rs` documentation][temporal_rs-docs]
188    ///
189    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.from
190    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/from
191    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.from_epoch_milliseconds
192    pub(crate) fn from_epoch_milliseconds(
193        _: &JsValue,
194        args: &[JsValue],
195        context: &mut Context,
196    ) -> JsResult<JsValue> {
197        // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds).
198        let epoch_millis_f64 = args.get_or_undefined(0).to_number(context)?;
199        // NOTE: inline NumberToBigInt. It checks if the number is integral
200        // 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds).
201        if !epoch_millis_f64.is_finite() || epoch_millis_f64.fract() != 0.0 {
202            return Err(js_error!(RangeError: "number is not integral"));
203        }
204        // 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6).
205        // 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
206        // 5. Return ! CreateTemporalInstant(epochNanoseconds).
207        create_temporal_instant(
208            InnerInstant::from_epoch_milliseconds(epoch_millis_f64.to_i64().unwrap_or(i64::MAX))?,
209            None,
210            context,
211        )
212    }
213
214    /// 8.2.4 `Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )`
215    ///
216    /// More information:
217    ///
218    /// - [ECMAScript Temporal proposal][spec]
219    /// - [MDN reference][mdn]
220    /// - [`temporal_rs` documentation][temporal_rs-docs]
221    ///
222    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.from
223    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/from
224    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.try_new
225    pub(crate) fn from_epoch_nanoseconds(
226        _: &JsValue,
227        args: &[JsValue],
228        context: &mut Context,
229    ) -> JsResult<JsValue> {
230        // 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
231        let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?;
232        // 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
233        // 3. Return ! CreateTemporalInstant(epochNanoseconds).
234        let nanos = epoch_nanos.as_inner().to_i128();
235        create_temporal_instant(
236            InnerInstant::try_new(nanos.unwrap_or(i128::MAX))?,
237            None,
238            context,
239        )
240    }
241
242    /// 8.2.5 Temporal.Instant.compare ( one, two )
243    ///
244    /// More information:
245    ///
246    /// - [ECMAScript Temporal proposal][spec]
247    /// - [MDN reference][mdn]
248    /// - [`temporal_rs` documentation][temporal_rs-docs]
249    ///
250    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.compare
251    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/compare
252    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#impl-PartialOrd-for-Instant
253    pub(crate) fn compare(
254        _: &JsValue,
255        args: &[JsValue],
256        context: &mut Context,
257    ) -> JsResult<JsValue> {
258        // 1. Set one to ? ToTemporalInstant(one).
259        let one = to_temporal_instant(args.get_or_undefined(0), context)?;
260        // 2. Set two to ? ToTemporalInstant(two).
261        let two = to_temporal_instant(args.get_or_undefined(1), context)?;
262        // 3. Return 𝔽(CompareEpochNanoseconds(one.[[Nanoseconds]], two.[[Nanoseconds]])).
263        Ok((one.cmp(&two) as i8).into())
264    }
265}
266
267// ==== Instant accessors implementation ====
268
269impl Instant {
270    /// 8.3.4 get Temporal.Instant.prototype.epochMilliseconds
271    ///
272    /// More information:
273    ///
274    /// - [ECMAScript Temporal proposal][spec]
275    /// - [MDN reference][mdn]
276    /// - [`temporal_rs` documentation][temporal_rs-docs]
277    ///
278    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.instant.epochmilliseconds
279    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/epochmilliseconds
280    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.epoch_milliseconds
281    pub(crate) fn get_epoch_milliseconds(
282        this: &JsValue,
283        _: &[JsValue],
284        _: &mut Context,
285    ) -> JsResult<JsValue> {
286        // 1. Let instant be the this value.
287        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
288        let object = this.as_object();
289        let instant = object
290            .as_ref()
291            .and_then(JsObject::downcast_ref::<Self>)
292            .ok_or_else(|| {
293                JsNativeError::typ().with_message("the this object must be an instant object.")
294            })?;
295        // 3. Let ns be instant.[[Nanoseconds]].
296        // 4. Let ms be floor(ℝ(ns) / 10^6).
297        // 5. Return 𝔽(ms).
298        Ok(instant.inner.epoch_milliseconds().into())
299    }
300
301    /// 8.3.6 get Temporal.Instant.prototype.epochNanoseconds
302    ///
303    /// More information:
304    ///
305    /// - [ECMAScript Temporal proposal][spec]
306    /// - [MDN reference][mdn]
307    /// - [`temporal_rs` documentation][temporal_rs-docs]
308    ///
309    /// [spec]: https://tc39.es/proposal-temporal/#sec-get-temporal.instant.epochnanoseconds
310    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/epochNanoseconds
311    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.epoch_nanoseconds
312    pub(crate) fn get_epoch_nanoseconds(
313        this: &JsValue,
314        _: &[JsValue],
315        _: &mut Context,
316    ) -> JsResult<JsValue> {
317        // 1. Let instant be the this value.
318        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
319        let object = this.as_object();
320        let instant = object
321            .as_ref()
322            .and_then(JsObject::downcast_ref::<Self>)
323            .ok_or_else(|| {
324                JsNativeError::typ().with_message("the this object must be an instant object.")
325            })?;
326        // 3. Let ns be instant.[[Nanoseconds]].
327        // 4. Return ns.
328        Ok(JsBigInt::from(instant.inner.epoch_nanoseconds().as_i128()).into())
329    }
330}
331
332// ==== Instant methods implementation ====
333
334impl Instant {
335    /// 8.3.7 `Temporal.Instant.prototype.add ( temporalDurationLike )`
336    ///
337    /// More information:
338    ///
339    /// - [ECMAScript Temporal proposal][spec]
340    /// - [MDN reference][mdn]
341    /// - [`temporal_rs` documentation][temporal_rs-docs]
342    ///
343    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.add
344    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/add
345    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.add
346    pub(crate) fn add(
347        this: &JsValue,
348        args: &[JsValue],
349        context: &mut Context,
350    ) -> JsResult<JsValue> {
351        // 1. Let instant be the this value.
352        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
353        let object = this.as_object();
354        let instant = object
355            .as_ref()
356            .and_then(JsObject::downcast_ref::<Self>)
357            .ok_or_else(|| {
358                JsNativeError::typ().with_message("the this object must be an instant object.")
359            })?;
360
361        // 3. Return ? AddDurationToOrSubtractDurationFromInstant(add, instant, temporalDurationLike).
362        let temporal_duration_like =
363            to_temporal_duration_record(args.get_or_undefined(0), context)?;
364        let result = instant.inner.add(&temporal_duration_like)?;
365        create_temporal_instant(result, None, context)
366    }
367
368    /// 8.3.8 `Temporal.Instant.prototype.subtract ( temporalDurationLike )`
369    ///
370    /// More information:
371    ///
372    /// - [ECMAScript Temporal proposal][spec]
373    /// - [MDN reference][mdn]
374    /// - [`temporal_rs` documentation][temporal_rs-docs]
375    ///
376    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.subtract
377    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/subtract
378    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.subtract
379    pub(crate) fn subtract(
380        this: &JsValue,
381        args: &[JsValue],
382        context: &mut Context,
383    ) -> JsResult<JsValue> {
384        // 1. Let instant be the this value.
385        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
386        let object = this.as_object();
387        let instant = object
388            .as_ref()
389            .and_then(JsObject::downcast_ref::<Self>)
390            .ok_or_else(|| {
391                JsNativeError::typ().with_message("the this object must be an instant object.")
392            })?;
393
394        // 3. Return ? AddDurationToOrSubtractDurationFromInstant(subtract, instant, temporalDurationLike).
395        let temporal_duration_like =
396            to_temporal_duration_record(args.get_or_undefined(0), context)?;
397        let result = instant.inner.subtract(&temporal_duration_like)?;
398        create_temporal_instant(result, None, context)
399    }
400
401    /// 8.3.9 `Temporal.Instant.prototype.until ( other [ , options ] )`
402    ///
403    /// More information:
404    ///
405    /// - [ECMAScript Temporal proposal][spec]
406    /// - [MDN reference][mdn]
407    /// - [`temporal_rs` documentation][temporal_rs-docs]
408    ///
409    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.until
410    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/until
411    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.until
412    pub(crate) fn until(
413        this: &JsValue,
414        args: &[JsValue],
415        context: &mut Context,
416    ) -> JsResult<JsValue> {
417        // 1. Let instant be the this value.
418        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
419        let object = this.as_object();
420        let instant = object
421            .as_ref()
422            .and_then(JsObject::downcast_ref::<Self>)
423            .ok_or_else(|| {
424                JsNativeError::typ().with_message("the this object must be an instant object.")
425            })?;
426
427        // 3. Return ? DifferenceTemporalInstant(until, instant, other, options).
428        let other = to_temporal_instant(args.get_or_undefined(0), context)?;
429
430        // Fetch the necessary options.
431        let settings =
432            get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
433        let result = instant.inner.until(&other, settings)?;
434        create_temporal_duration(result, None, context).map(Into::into)
435    }
436
437    /// 8.3.10 `Temporal.Instant.prototype.since ( other [ , options ] )`
438    ///
439    /// More information:
440    ///
441    /// - [ECMAScript Temporal proposal][spec]
442    /// - [MDN reference][mdn]
443    /// - [`temporal_rs` documentation][temporal_rs-docs]
444    ///
445    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.since
446    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/since
447    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.since
448    pub(crate) fn since(
449        this: &JsValue,
450        args: &[JsValue],
451        context: &mut Context,
452    ) -> JsResult<JsValue> {
453        // 1. Let instant be the this value.
454        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
455        let object = this.as_object();
456        let instant = object
457            .as_ref()
458            .and_then(JsObject::downcast_ref::<Self>)
459            .ok_or_else(|| {
460                JsNativeError::typ().with_message("the this object must be an instant object.")
461            })?;
462
463        // 3. Return ? DifferenceTemporalInstant(since, instant, other, options).
464        let other = to_temporal_instant(args.get_or_undefined(0), context)?;
465        let settings =
466            get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?;
467        let result = instant.inner.since(&other, settings)?;
468        create_temporal_duration(result, None, context).map(Into::into)
469    }
470
471    /// 8.3.11 `Temporal.Instant.prototype.round ( roundTo )`
472    ///
473    /// More information:
474    ///
475    /// - [ECMAScript Temporal proposal][spec]
476    /// - [MDN reference][mdn]
477    /// - [`temporal_rs` documentation][temporal_rs-docs]
478    ///
479    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.round
480    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/round
481    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.round
482    pub(crate) fn round(
483        this: &JsValue,
484        args: &[JsValue],
485        context: &mut Context,
486    ) -> JsResult<JsValue> {
487        // 1. Let instant be the this value.
488        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
489        let object = this.as_object();
490        let instant = object
491            .as_ref()
492            .and_then(JsObject::downcast_ref::<Self>)
493            .ok_or_else(|| {
494                JsNativeError::typ().with_message("the this object must be an instant object.")
495            })?;
496
497        // 3. If roundTo is undefined, then
498        let round_to_arg = args.get_or_undefined(0);
499        if round_to_arg.is_undefined() {
500            return Err(JsNativeError::typ()
501                .with_message("roundTo cannot be undefined.")
502                .into());
503        }
504        // 4. If Type(roundTo) is String, then
505        let round_to = if let Some(param_string) = round_to_arg.as_string() {
506            // a. Let paramString be roundTo.
507            let param_string = param_string.clone();
508            // b. Set roundTo to OrdinaryObjectCreate(null).
509            let new_round_to = JsObject::with_null_proto();
510            // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
511            new_round_to.create_data_property_or_throw(
512                js_string!("smallestUnit"),
513                param_string,
514                context,
515            )?;
516            new_round_to
517        } else {
518            // 5. Else,
519            // a. Set roundTo to ? GetOptionsObject(roundTo).
520            get_options_object(round_to_arg)?
521        };
522
523        // 6. NOTE: The following steps read options and perform independent validation in
524        // alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode").
525        let mut options = RoundingOptions::default();
526        // 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
527        options.increment =
528            get_option::<RoundingIncrement>(&round_to, js_string!("roundingIncrement"), context)?;
529
530        // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
531        options.rounding_mode =
532            get_option::<RoundingMode>(&round_to, js_string!("roundingMode"), context)?;
533
534        // 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit"), time, required).
535        let smallest_unit = get_temporal_unit(
536            &round_to,
537            js_string!("smallestUnit"),
538            TemporalUnitGroup::Time,
539            None,
540            context,
541        )?
542        .ok_or_else(|| JsNativeError::range().with_message("smallestUnit cannot be undefined."))?;
543
544        options.smallest_unit = Some(smallest_unit);
545
546        // 10. If smallestUnit is "hour"), then
547        // a. Let maximum be HoursPerDay.
548        // 11. Else if smallestUnit is "minute"), then
549        // a. Let maximum be MinutesPerHour × HoursPerDay.
550        // 12. Else if smallestUnit is "second"), then
551        // a. Let maximum be SecondsPerMinute × MinutesPerHour × HoursPerDay.
552        // 13. Else if smallestUnit is "millisecond"), then
553        // a. Let maximum be ℝ(msPerDay).
554        // 14. Else if smallestUnit is "microsecond"), then
555        // a. Let maximum be 10^3 × ℝ(msPerDay).
556        // 15. Else,
557        // a. Assert: smallestUnit is "nanosecond".
558        // b. Let maximum be nsPerDay.
559        // unreachable here functions as 15.a.
560        // 16. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true).
561        // 17. Let roundedNs be RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode).
562        let result = instant.inner.round(options)?;
563
564        // 18. Return ! CreateTemporalInstant(roundedNs).
565        create_temporal_instant(result, None, context)
566    }
567
568    /// 8.3.12 `Temporal.Instant.prototype.equals ( other )`
569    ///
570    /// More information:
571    ///
572    /// - [ECMAScript Temporal proposal][spec]
573    /// - [MDN reference][mdn]
574    /// - [`temporal_rs` documentation][temporal_rs-docs]
575    ///
576    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.equals
577    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/equals
578    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#impl-PartialEq-for-Instant
579    pub(crate) fn equals(
580        this: &JsValue,
581        args: &[JsValue],
582        context: &mut Context,
583    ) -> JsResult<JsValue> {
584        // 1. Let instant be the this value.
585        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
586        // 4. If instant.[[Nanoseconds]] ≠ other.[[Nanoseconds]], return false.
587        // 5. Return true.
588        let object = this.as_object();
589        let instant = object
590            .as_ref()
591            .and_then(JsObject::downcast_ref::<Self>)
592            .ok_or_else(|| {
593                JsNativeError::typ().with_message("the this object must be an instant object.")
594            })?;
595
596        // 3. Set other to ? ToTemporalInstant(other).
597        let other = args.get_or_undefined(0);
598        let other_instant = to_temporal_instant(other, context)?;
599
600        if *instant.inner != other_instant {
601            return Ok(false.into());
602        }
603        Ok(true.into())
604    }
605
606    /// 8.3.11 `Temporal.Instant.prototype.toString ( [ options ] )`
607    ///
608    /// More information:
609    ///
610    /// - [ECMAScript Temporal proposal][spec]
611    /// - [MDN reference][mdn]
612    /// - [`temporal_rs` documentation][temporal_rs-docs]
613    ///
614    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.tostring
615    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/toString
616    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.to_ixdtf_string
617    fn to_string(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
618        let object = this.as_object();
619        let instant = object
620            .as_ref()
621            .and_then(JsObject::downcast_ref::<Self>)
622            .ok_or_else(|| {
623                JsNativeError::typ()
624                    .with_message("the this object must be a Temporal.Instant object.")
625            })?;
626
627        let options = get_options_object(args.get_or_undefined(0))?;
628
629        let precision = get_digits_option(&options, context)?;
630        let rounding_mode =
631            get_option::<RoundingMode>(&options, js_string!("roundingMode"), context)?;
632        let smallest_unit = get_option::<Unit>(&options, js_string!("smallestUnit"), context)?;
633        // NOTE: There may be an order-of-operations here due to a check on Unit groups and smallest_unit value.
634        let timezone = options
635            .get(js_string!("timeZone"), context)?
636            .map(|v| to_temporal_timezone_identifier(v, context))
637            .transpose()?;
638
639        let options = ToStringRoundingOptions {
640            precision,
641            smallest_unit,
642            rounding_mode,
643        };
644
645        let ixdtf = instant.inner.to_ixdtf_string_with_provider(
646            timezone,
647            options,
648            context.timezone_provider(),
649        )?;
650
651        Ok(JsString::from(ixdtf).into())
652    }
653
654    /// 8.3.12 `Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )`
655    ///
656    /// More information:
657    ///
658    /// - [ECMAScript Temporal proposal][spec]
659    /// - [MDN reference][mdn]
660    ///
661    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.tolocalestring
662    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/toLocaleString
663    fn to_locale_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
664        // TODO: Update for ECMA-402 compliance
665        let object = this.as_object();
666        let instant = object
667            .as_ref()
668            .and_then(JsObject::downcast_ref::<Self>)
669            .ok_or_else(|| {
670                JsNativeError::typ()
671                    .with_message("the this object must be a Temporal.Instant object.")
672            })?;
673
674        let ixdtf = instant.inner.to_ixdtf_string_with_provider(
675            None,
676            ToStringRoundingOptions::default(),
677            context.timezone_provider(),
678        )?;
679        Ok(JsString::from(ixdtf).into())
680    }
681
682    /// 8.3.13 `Temporal.Instant.prototype.toJSON ( )`
683    ///
684    /// More information:
685    ///
686    /// - [ECMAScript Temporal proposal][spec]
687    /// - [MDN reference][mdn]
688    ///
689    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.tojson
690    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/toJSON
691    fn to_json(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
692        let object = this.as_object();
693        let instant = object
694            .as_ref()
695            .and_then(JsObject::downcast_ref::<Self>)
696            .ok_or_else(|| {
697                JsNativeError::typ()
698                    .with_message("the this object must be a Temporal.Instant object.")
699            })?;
700
701        let ixdtf = instant.inner.to_ixdtf_string_with_provider(
702            None,
703            ToStringRoundingOptions::default(),
704            context.timezone_provider(),
705        )?;
706        Ok(JsString::from(ixdtf).into())
707    }
708
709    /// 8.3.14 `Temporal.Instant.prototype.valueOf ( )`
710    ///
711    /// More information:
712    ///
713    /// - [ECMAScript Temporal proposal][spec]
714    /// - [MDN reference][mdn]
715    ///
716    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.valueof
717    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/valueOf
718    fn value_of(_this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
719        Err(JsNativeError::typ()
720            .with_message("`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`")
721            .into())
722    }
723
724    /// 8.3.15 `Temporal.Instant.prototype.toZonedDateTimeISO ( timeZone )`
725    ///
726    /// More information:
727    ///
728    /// - [ECMAScript Temporal proposal][spec]
729    /// - [MDN reference][mdn]
730    /// - [`temporal_rs` documentation][temporal_rs-docs]
731    ///
732    /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.instant.tozoneddatetimeiso
733    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant/toZonedDateTimeISO
734    /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.Instant.html#method.to_zoned_date_time_iso
735    pub(crate) fn to_zoned_date_time_iso(
736        this: &JsValue,
737        args: &[JsValue],
738        context: &mut Context,
739    ) -> JsResult<JsValue> {
740        // 1. Let instant be the this value.
741        // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
742        let object = this.as_object();
743        let instant = object
744            .as_ref()
745            .and_then(JsObject::downcast_ref::<Self>)
746            .ok_or_else(|| {
747                JsNativeError::typ()
748                    .with_message("the this object must be a Temporal.Instant object.")
749            })?;
750
751        // 3. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone).
752        let timezone = to_temporal_timezone_identifier(args.get_or_undefined(0), context)?;
753
754        // 4. Return ! CreateTemporalZonedDateTime(instant.[[EpochNanoseconds]], timeZone, "iso8601").
755        let zdt = instant
756            .inner
757            .to_zoned_date_time_iso_with_provider(timezone, context.timezone_provider())?;
758        create_temporal_zoneddatetime(zdt, None, context).map(Into::into)
759    }
760}
761
762// ==== Instant Abstract Operations ====
763
764/// 8.5.2 `CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )`
765#[inline]
766pub(crate) fn create_temporal_instant(
767    instant: InnerInstant,
768    new_target: Option<JsValue>,
769    context: &mut Context,
770) -> JsResult<JsValue> {
771    // 1. Assert: ! IsValidEpochNanoseconds(epochNanoseconds) is true.
772    // 2. If newTarget is not present, set newTarget to %Temporal.Instant%.
773    let new_target = new_target.unwrap_or_else(|| {
774        context
775            .realm()
776            .intrinsics()
777            .constructors()
778            .instant()
779            .constructor()
780            .into()
781    });
782    // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%"), « [[InitializedTemporalInstant]], [[Nanoseconds]] »).
783    let proto =
784        get_prototype_from_constructor(&new_target, StandardConstructors::instant, context)?;
785
786    // 4. Set object.[[Nanoseconds]] to epochNanoseconds.
787    let obj = JsObject::from_proto_and_data(proto, Instant::new(instant));
788
789    // 5. Return object.
790    Ok(obj.into())
791}
792
793/// 8.5.3 `ToTemporalInstant ( item )`
794#[inline]
795fn to_temporal_instant(item: &JsValue, context: &mut Context) -> JsResult<InnerInstant> {
796    // 1.If item is an Object, then
797    let item = if let Some(obj) = item.as_object() {
798        // a. If item has an [[InitializedTemporalInstant]] internal slot, then
799        //     i. Return item.
800        // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
801        //     i. Return ! CreateTemporalInstant(item.[[Nanoseconds]]).
802        // c. NOTE: This use of ToPrimitive allows Instant-like objects to be converted.
803        // d. Set item to ? ToPrimitive(item, string).
804        if let Some(instant) = obj.downcast_ref::<Instant>() {
805            return Ok(*instant.inner);
806        } else if let Some(zdt) = obj.downcast_ref::<ZonedDateTime>() {
807            return Ok(zdt.inner.to_instant());
808        }
809        item.to_primitive(context, PreferredType::String)?
810    } else {
811        item.clone()
812    };
813
814    let Some(string_to_parse) = item.as_string() else {
815        return Err(JsNativeError::typ()
816            .with_message("Invalid type to convert to a Temporal.Instant.")
817            .into());
818    };
819
820    // 3. Let parsed be ? ParseTemporalInstantString(item).
821    // 4. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]).
822    // 5. If abs(ISODateToEpochDays(parsed.[[Year]], parsed.[[Month]] - 1, parsed.[[Day]])) > 10**8, throw a RangeError exception.
823    // 6. Let epochNanoseconds be GetUTCEpochNanoseconds(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], parsed.[[Hour]], parsed.[[Minute]], parsed.[[Second]], parsed.[[Millisecond]], parsed.[[Microsecond]], parsed.[[Nanosecond]], offsetNanoseconds).
824    // 7. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
825    // 8. Return ! CreateTemporalInstant(epochNanoseconds).
826    // 2. If item is not a String, throw a TypeError exception.
827    string_to_parse
828        .to_std_string_escaped()
829        .parse::<InnerInstant>()
830        .map_err(Into::into)
831}