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}