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}