1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/Temporal.h"
9 #include "mozilla/Casting.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/Likely.h"
12 #include "mozilla/MathAlgorithms.h"
13 #include "mozilla/Maybe.h"
18 #include <initializer_list>
22 #include <string_view>
23 #include <type_traits>
26 #include "jsfriendapi.h"
29 #include "NamespaceImports.h"
31 #include "builtin/temporal/Instant.h"
32 #include "builtin/temporal/Int128.h"
33 #include "builtin/temporal/PlainDate.h"
34 #include "builtin/temporal/PlainDateTime.h"
35 #include "builtin/temporal/PlainMonthDay.h"
36 #include "builtin/temporal/PlainTime.h"
37 #include "builtin/temporal/PlainYearMonth.h"
38 #include "builtin/temporal/TemporalRoundingMode.h"
39 #include "builtin/temporal/TemporalTypes.h"
40 #include "builtin/temporal/TemporalUnit.h"
41 #include "builtin/temporal/ZonedDateTime.h"
42 #include "gc/Barrier.h"
44 #include "js/Conversions.h"
45 #include "js/ErrorReport.h"
46 #include "js/friend/ErrorMessages.h"
47 #include "js/GCVector.h"
49 #include "js/Printer.h"
50 #include "js/PropertyDescriptor.h"
51 #include "js/PropertySpec.h"
52 #include "js/RootingAPI.h"
53 #include "js/String.h"
55 #include "vm/BytecodeUtil.h"
56 #include "vm/GlobalObject.h"
57 #include "vm/JSAtomState.h"
58 #include "vm/JSAtomUtils.h"
59 #include "vm/JSContext.h"
60 #include "vm/JSObject.h"
61 #include "vm/ObjectOperations.h"
63 #include "vm/PlainObject.h"
65 #include "vm/StringType.h"
67 #include "vm/JSObject-inl.h"
68 #include "vm/ObjectOperations-inl.h"
71 using namespace js::temporal
;
74 * GetOption ( options, property, type, values, default )
76 * GetOption specialization when `type=string`. Default value handling must
77 * happen in the caller, so we don't provide the `default` parameter here.
79 static bool GetStringOption(JSContext
* cx
, Handle
<JSObject
*> options
,
80 Handle
<PropertyName
*> property
,
81 MutableHandle
<JSString
*> string
) {
83 Rooted
<Value
> value(cx
);
84 if (!GetProperty(cx
, options
, options
, property
, &value
)) {
88 // Step 2. (Caller should fill in the fallback.)
89 if (value
.isUndefined()) {
93 // Steps 3-4. (Not applicable when type=string)
96 string
.set(JS::ToString(cx
, value
));
101 // Step 6. (Not applicable in our implementation)
108 * GetOption ( options, property, type, values, default )
110 static bool GetNumberOption(JSContext
* cx
, Handle
<JSObject
*> options
,
111 Handle
<PropertyName
*> property
, double* number
) {
113 Rooted
<Value
> value(cx
);
114 if (!GetProperty(cx
, options
, options
, property
, &value
)) {
118 // Step 2. (Caller should fill in the fallback.)
119 if (value
.isUndefined()) {
123 // Steps 3 and 5. (Not applicable in our implementation)
126 if (!JS::ToNumber(cx
, value
, number
)) {
130 // Step 4.b. (Caller must check for NaN values.)
132 // Step 7. (Not applicable in our implementation)
139 * ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive )
141 bool js::temporal::ToTemporalRoundingIncrement(JSContext
* cx
,
142 Handle
<JSObject
*> options
,
143 Increment
* increment
) {
146 if (!GetNumberOption(cx
, options
, cx
->names().roundingIncrement
, &number
)) {
150 // Step 3. (Reordered)
151 number
= std::trunc(number
);
154 if (!std::isfinite(number
) || number
< 1 || number
> 1'000'000'000) {
156 const char* numStr
= NumberToCString(&cbuf
, number
);
158 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
159 JSMSG_INVALID_OPTION_VALUE
, "roundingIncrement",
164 *increment
= Increment
{uint32_t(number
)};
169 * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )
171 bool js::temporal::ValidateTemporalRoundingIncrement(JSContext
* cx
,
175 MOZ_ASSERT(dividend
> 0);
176 MOZ_ASSERT_IF(!inclusive
, dividend
> 1);
179 int64_t maximum
= inclusive
? dividend
: dividend
- 1;
182 if (increment
.value() > maximum
|| dividend
% increment
.value() != 0) {
183 Int32ToCStringBuf cbuf
;
184 const char* numStr
= Int32ToCString(&cbuf
, int32_t(increment
.value()));
186 // TODO: Better error message could be helpful.
187 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
188 JSMSG_INVALID_OPTION_VALUE
, "roundingIncrement",
197 PropertyName
* js::temporal::TemporalUnitToString(JSContext
* cx
,
200 case TemporalUnit::Auto
:
202 case TemporalUnit::Year
:
203 return cx
->names().year
;
204 case TemporalUnit::Month
:
205 return cx
->names().month
;
206 case TemporalUnit::Week
:
207 return cx
->names().week
;
208 case TemporalUnit::Day
:
209 return cx
->names().day
;
210 case TemporalUnit::Hour
:
211 return cx
->names().hour
;
212 case TemporalUnit::Minute
:
213 return cx
->names().minute
;
214 case TemporalUnit::Second
:
215 return cx
->names().second
;
216 case TemporalUnit::Millisecond
:
217 return cx
->names().millisecond
;
218 case TemporalUnit::Microsecond
:
219 return cx
->names().microsecond
;
220 case TemporalUnit::Nanosecond
:
221 return cx
->names().nanosecond
;
223 MOZ_CRASH("invalid temporal unit");
226 static Handle
<PropertyName
*> ToPropertyName(JSContext
* cx
,
227 TemporalUnitKey key
) {
229 case TemporalUnitKey::SmallestUnit
:
230 return cx
->names().smallestUnit
;
231 case TemporalUnitKey::LargestUnit
:
232 return cx
->names().largestUnit
;
233 case TemporalUnitKey::Unit
:
234 return cx
->names().unit
;
236 MOZ_CRASH("invalid temporal unit group");
239 static const char* ToCString(TemporalUnitKey key
) {
241 case TemporalUnitKey::SmallestUnit
:
242 return "smallestUnit";
243 case TemporalUnitKey::LargestUnit
:
244 return "largestUnit";
245 case TemporalUnitKey::Unit
:
248 MOZ_CRASH("invalid temporal unit group");
251 static bool ToTemporalUnit(JSContext
* cx
, JSLinearString
* str
,
252 TemporalUnitKey key
, TemporalUnit
* unit
) {
254 std::string_view name
;
258 static constexpr UnitMap mapping
[] = {
259 {"year", TemporalUnit::Year
},
260 {"years", TemporalUnit::Year
},
261 {"month", TemporalUnit::Month
},
262 {"months", TemporalUnit::Month
},
263 {"week", TemporalUnit::Week
},
264 {"weeks", TemporalUnit::Week
},
265 {"day", TemporalUnit::Day
},
266 {"days", TemporalUnit::Day
},
267 {"hour", TemporalUnit::Hour
},
268 {"hours", TemporalUnit::Hour
},
269 {"minute", TemporalUnit::Minute
},
270 {"minutes", TemporalUnit::Minute
},
271 {"second", TemporalUnit::Second
},
272 {"seconds", TemporalUnit::Second
},
273 {"millisecond", TemporalUnit::Millisecond
},
274 {"milliseconds", TemporalUnit::Millisecond
},
275 {"microsecond", TemporalUnit::Microsecond
},
276 {"microseconds", TemporalUnit::Microsecond
},
277 {"nanosecond", TemporalUnit::Nanosecond
},
278 {"nanoseconds", TemporalUnit::Nanosecond
},
281 // Compute the length of the longest name.
282 constexpr size_t maxNameLength
=
283 std::max_element(std::begin(mapping
), std::end(mapping
),
284 [](const auto& x
, const auto& y
) {
285 return x
.name
.length() < y
.name
.length();
289 // Twenty StringEqualsLiteral calls for each possible combination seems a bit
290 // expensive, so let's instead copy the input name into a char array and rely
291 // on the compiler to generate optimized code for the comparisons.
293 size_t length
= str
->length();
294 if (length
<= maxNameLength
&& StringIsAscii(str
)) {
295 char chars
[maxNameLength
] = {};
296 JS::LossyCopyLinearStringChars(chars
, str
, length
);
298 for (const auto& m
: mapping
) {
299 if (m
.name
== std::string_view(chars
, length
)) {
306 if (auto chars
= QuoteString(cx
, str
, '"')) {
307 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
308 JSMSG_INVALID_OPTION_VALUE
, ToCString(key
),
314 static std::pair
<TemporalUnit
, TemporalUnit
> AllowedValues(
315 TemporalUnitGroup unitGroup
) {
317 case TemporalUnitGroup::Date
:
318 return {TemporalUnit::Year
, TemporalUnit::Day
};
319 case TemporalUnitGroup::Time
:
320 return {TemporalUnit::Hour
, TemporalUnit::Nanosecond
};
321 case TemporalUnitGroup::DateTime
:
322 return {TemporalUnit::Year
, TemporalUnit::Nanosecond
};
323 case TemporalUnitGroup::DayTime
:
324 return {TemporalUnit::Day
, TemporalUnit::Nanosecond
};
326 MOZ_CRASH("invalid temporal unit group");
330 * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
333 bool js::temporal::GetTemporalUnit(JSContext
* cx
, Handle
<JSObject
*> options
,
335 TemporalUnitGroup unitGroup
,
336 TemporalUnit
* unit
) {
337 // Steps 1-8. (Not applicable in our implementation.)
340 Rooted
<JSString
*> value(cx
);
341 if (!GetStringOption(cx
, options
, ToPropertyName(cx
, key
), &value
)) {
345 // Caller should fill in the fallback.
350 return GetTemporalUnit(cx
, value
, key
, unitGroup
, unit
);
354 * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
357 bool js::temporal::GetTemporalUnit(JSContext
* cx
, Handle
<JSString
*> value
,
359 TemporalUnitGroup unitGroup
,
360 TemporalUnit
* unit
) {
361 // Steps 1-9. (Not applicable in our implementation.)
363 // Step 10. (Handled in caller.)
365 Rooted
<JSLinearString
*> linear(cx
, value
->ensureLinear(cx
));
370 // Caller should fill in the fallback.
371 if (key
== TemporalUnitKey::LargestUnit
) {
372 if (StringEqualsLiteral(linear
, "auto")) {
378 if (!ToTemporalUnit(cx
, linear
, key
, unit
)) {
382 auto allowedValues
= AllowedValues(unitGroup
);
383 if (*unit
< allowedValues
.first
|| *unit
> allowedValues
.second
) {
384 if (auto chars
= QuoteString(cx
, linear
, '"')) {
385 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
386 JSMSG_INVALID_OPTION_VALUE
, ToCString(key
),
396 * ToTemporalRoundingMode ( normalizedOptions, fallback )
398 bool js::temporal::ToTemporalRoundingMode(JSContext
* cx
,
399 Handle
<JSObject
*> options
,
400 TemporalRoundingMode
* mode
) {
402 Rooted
<JSString
*> string(cx
);
403 if (!GetStringOption(cx
, options
, cx
->names().roundingMode
, &string
)) {
407 // Caller should fill in the fallback.
412 JSLinearString
* linear
= string
->ensureLinear(cx
);
417 if (StringEqualsLiteral(linear
, "ceil")) {
418 *mode
= TemporalRoundingMode::Ceil
;
419 } else if (StringEqualsLiteral(linear
, "floor")) {
420 *mode
= TemporalRoundingMode::Floor
;
421 } else if (StringEqualsLiteral(linear
, "expand")) {
422 *mode
= TemporalRoundingMode::Expand
;
423 } else if (StringEqualsLiteral(linear
, "trunc")) {
424 *mode
= TemporalRoundingMode::Trunc
;
425 } else if (StringEqualsLiteral(linear
, "halfCeil")) {
426 *mode
= TemporalRoundingMode::HalfCeil
;
427 } else if (StringEqualsLiteral(linear
, "halfFloor")) {
428 *mode
= TemporalRoundingMode::HalfFloor
;
429 } else if (StringEqualsLiteral(linear
, "halfExpand")) {
430 *mode
= TemporalRoundingMode::HalfExpand
;
431 } else if (StringEqualsLiteral(linear
, "halfTrunc")) {
432 *mode
= TemporalRoundingMode::HalfTrunc
;
433 } else if (StringEqualsLiteral(linear
, "halfEven")) {
434 *mode
= TemporalRoundingMode::HalfEven
;
436 if (auto chars
= QuoteString(cx
, linear
, '"')) {
437 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
438 JSMSG_INVALID_OPTION_VALUE
, "roundingMode",
447 template <typename T
>
448 static bool IsValidMul(const T
& x
, const T
& y
) {
449 return (mozilla::CheckedInt
<T
>(x
) * y
).isValid();
452 // Copied from mozilla::CheckedInt.
454 bool IsValidMul
<Int128
>(const Int128
& x
, const Int128
& y
) {
455 static constexpr auto min
= Int128
{1} << 127;
456 static constexpr auto max
= ~min
;
458 if (x
== Int128
{0} || y
== Int128
{0}) {
462 return y
> Int128
{0} ? x
<= max
/ y
: y
>= min
/ x
;
464 return y
> Int128
{0} ? x
>= min
/ y
: y
>= max
/ x
;
469 * RoundNumberToIncrement ( x, increment, roundingMode )
471 Int128
js::temporal::RoundNumberToIncrement(int64_t numerator
,
474 TemporalRoundingMode roundingMode
) {
475 MOZ_ASSERT(denominator
> 0);
476 MOZ_ASSERT(Increment::min() <= increment
&& increment
<= Increment::max());
478 // Dividing zero is always zero.
479 if (numerator
== 0) {
483 // We don't have to adjust the divisor when |increment=1|.
484 if (increment
== Increment
{1}) {
485 // Steps 1-8 and implicit step 9.
486 return Int128
{Divide(numerator
, denominator
, roundingMode
)};
489 // Fast-path when we can perform the whole computation with int64 values.
490 auto divisor
= mozilla::CheckedInt64(denominator
) * increment
.value();
491 if (MOZ_LIKELY(divisor
.isValid())) {
492 MOZ_ASSERT(divisor
.value() > 0);
495 int64_t rounded
= Divide(numerator
, divisor
.value(), roundingMode
);
498 auto result
= mozilla::CheckedInt64(rounded
) * increment
.value();
499 if (MOZ_LIKELY(result
.isValid())) {
500 return Int128
{result
.value()};
504 // Int128 path on overflow.
505 return RoundNumberToIncrement(Int128
{numerator
}, Int128
{denominator
},
506 increment
, roundingMode
);
510 * RoundNumberToIncrement ( x, increment, roundingMode )
512 Int128
js::temporal::RoundNumberToIncrement(const Int128
& numerator
,
513 const Int128
& denominator
,
515 TemporalRoundingMode roundingMode
) {
516 MOZ_ASSERT(denominator
> Int128
{0});
517 MOZ_ASSERT(Increment::min() <= increment
&& increment
<= Increment::max());
519 auto inc
= Int128
{increment
.value()};
520 MOZ_ASSERT(IsValidMul(denominator
, inc
), "unsupported overflow");
522 auto divisor
= denominator
* inc
;
523 MOZ_ASSERT(divisor
> Int128
{0});
526 auto rounded
= Divide(numerator
, divisor
, roundingMode
);
529 MOZ_ASSERT(IsValidMul(rounded
, inc
), "unsupported overflow");
530 return rounded
* inc
;
534 * RoundNumberToIncrement ( x, increment, roundingMode )
536 Int128
js::temporal::RoundNumberToIncrement(const Int128
& x
,
537 const Int128
& increment
,
538 TemporalRoundingMode roundingMode
) {
539 MOZ_ASSERT(increment
> Int128
{0});
542 auto rounded
= Divide(x
, increment
, roundingMode
);
545 MOZ_ASSERT(IsValidMul(rounded
, increment
), "unsupported overflow");
546 return rounded
* increment
;
549 template <typename IntT
>
550 static inline constexpr bool IsSafeInteger(const IntT
& x
) {
551 constexpr IntT MaxSafeInteger
= IntT
{int64_t(1) << 53};
552 constexpr IntT MinSafeInteger
= -MaxSafeInteger
;
553 return MinSafeInteger
< x
&& x
< MaxSafeInteger
;
557 * Return the real number value of the fraction |numerator / denominator|.
559 * As an optimization we multiply the remainder by 16 when computing the number
560 * of digits after the decimal point, i.e. we compute four instead of one bit of
561 * the fractional digits. The denominator is therefore required to not exceed
562 * 2**(N - log2(16)), where N is the number of non-sign bits in the mantissa.
564 template <typename T
>
565 static double FractionToDoubleSlow(const T
& numerator
, const T
& denominator
) {
566 MOZ_ASSERT(denominator
> T
{0}, "expected positive denominator");
567 MOZ_ASSERT(denominator
<= (T
{1} << (std::numeric_limits
<T
>::digits
- 4)),
568 "denominator too large");
570 auto absValue
= [](const T
& value
) {
571 if constexpr (std::is_same_v
<T
, Int128
>) {
574 // NB: Not std::abs, because std::abs(INT64_MIN) is undefined behavior.
575 return mozilla::Abs(value
);
579 using UnsignedT
= decltype(absValue(T
{0}));
580 static_assert(!std::numeric_limits
<UnsignedT
>::is_signed
);
582 auto divrem
= [](const UnsignedT
& x
, const UnsignedT
& y
) {
583 if constexpr (std::is_same_v
<T
, Int128
>) {
586 return std::pair
{x
/ y
, x
% y
};
591 divrem(absValue(numerator
), static_cast<UnsignedT
>(denominator
));
593 // Simple case when no remainder is present.
594 if (rem
== UnsignedT
{0}) {
595 double sign
= numerator
< T
{0} ? -1 : 1;
596 return sign
* double(quot
);
599 using Double
= mozilla::FloatingPoint
<double>;
601 // Significand including the implicit one of IEEE-754 floating point numbers.
602 static constexpr uint32_t SignificandWidthWithImplicitOne
=
603 Double::kSignificandWidth
+ 1;
605 // Number of leading zeros for a correctly adjusted significand.
606 static constexpr uint32_t SignificandLeadingZeros
=
607 64 - SignificandWidthWithImplicitOne
;
609 // Exponent bias for an integral significand. (`Double::kExponentBias` is the
610 // bias for the binary fraction `1.xyz * 2**exp`. For an integral significand
611 // the significand width has to be added to the bias.)
612 static constexpr int32_t ExponentBias
=
613 Double::kExponentBias
+ Double::kSignificandWidth
;
615 // Significand, possibly unnormalized.
616 uint64_t significand
= 0;
618 // Significand ignored msd bits.
619 uint32_t ignoredBits
= 0;
621 // Read quotient, from most to least significant digit. Stop when the
622 // significand got too large for double precision.
623 int32_t shift
= std::numeric_limits
<UnsignedT
>::digits
;
624 for (; shift
!= 0 && ignoredBits
== 0; shift
-= 4) {
625 uint64_t digit
= uint64_t(quot
>> (shift
- 4)) & 0xf;
627 significand
= significand
* 16 + digit
;
628 ignoredBits
= significand
>> SignificandWidthWithImplicitOne
;
631 // Read remainder, from most to least significant digit. Stop when the
632 // remainder is zero or the significand got too large.
633 int32_t fractionDigit
= 0;
634 for (; rem
!= UnsignedT
{0} && ignoredBits
== 0; fractionDigit
++) {
636 divrem(rem
* UnsignedT
{16}, static_cast<UnsignedT
>(denominator
));
639 significand
= significand
* 16 + uint64_t(digit
);
640 ignoredBits
= significand
>> SignificandWidthWithImplicitOne
;
643 // Unbiased exponent. (`shift` remaining bits in the quotient, minus the
644 // fractional digits.)
645 int32_t exponent
= shift
- (fractionDigit
* 4);
647 // Significand got too large and some bits are now ignored. Adjust the
648 // significand and exponent.
649 if (ignoredBits
!= 0) {
651 // ___________|__________
653 // [xxx················yyy|
656 // ignoredBits extraBits
658 // `ignoredBits` have to be shifted back into the 53 bits of the significand
659 // and `extraBits` has to be checked if the result has to be rounded up.
661 // Number of ignored/extra bits in the significand.
662 uint32_t extraBitsCount
= 32 - mozilla::CountLeadingZeroes32(ignoredBits
);
663 MOZ_ASSERT(extraBitsCount
> 0);
665 // Extra bits in the significand.
666 uint32_t extraBits
= uint32_t(significand
) & ((1 << extraBitsCount
) - 1);
668 // Move the ignored bits into the proper significand position and adjust the
669 // exponent to reflect the now moved out extra bits.
670 significand
>>= extraBitsCount
;
671 exponent
+= extraBitsCount
;
673 MOZ_ASSERT((significand
>> SignificandWidthWithImplicitOne
) == 0,
674 "no excess bits in the significand");
676 // When the most significant digit in the extra bits is set, we may need to
678 uint32_t msdExtraBit
= extraBits
>> (extraBitsCount
- 1);
679 if (msdExtraBit
!= 0) {
680 // Extra bits, excluding the most significant digit.
681 uint32_t extraBitExcludingMsdMask
= (1 << (extraBitsCount
- 1)) - 1;
683 // Unprocessed bits in the quotient.
684 auto bitsBelowExtraBits
= quot
& ((UnsignedT
{1} << shift
) - UnsignedT
{1});
686 // Round up if the extra bit's msd is set and either the significand is
687 // odd or any other bits below the extra bit's msd are non-zero.
689 // Bits below the extra bit's msd are:
690 // 1. The remaining bits of the extra bits.
691 // 2. Any bits below the extra bits.
692 // 3. Any rest of the remainder.
693 bool shouldRoundUp
= (significand
& 1) != 0 ||
694 (extraBits
& extraBitExcludingMsdMask
) != 0 ||
695 bitsBelowExtraBits
!= UnsignedT
{0} ||
698 // Add one to the significand bits.
701 // If they overflow, the exponent must also be increased.
702 if ((significand
>> SignificandWidthWithImplicitOne
) != 0) {
710 MOZ_ASSERT(significand
> 0, "significand is non-zero");
711 MOZ_ASSERT((significand
>> SignificandWidthWithImplicitOne
) == 0,
712 "no excess bits in the significand");
714 // Move the significand into the correct position and adjust the exponent
716 uint32_t significandZeros
= mozilla::CountLeadingZeroes64(significand
);
717 if (significandZeros
< SignificandLeadingZeros
) {
718 uint32_t shift
= SignificandLeadingZeros
- significandZeros
;
719 significand
>>= shift
;
721 } else if (significandZeros
> SignificandLeadingZeros
) {
722 uint32_t shift
= significandZeros
- SignificandLeadingZeros
;
723 significand
<<= shift
;
727 // Combine the individual bits of the double value and return it.
728 uint64_t signBit
= uint64_t(numerator
< T
{0} ? 1 : 0)
729 << (Double::kExponentWidth
+ Double::kSignificandWidth
);
730 uint64_t exponentBits
= static_cast<uint64_t>(exponent
+ ExponentBias
)
731 << Double::kExponentShift
;
732 uint64_t significandBits
= significand
& Double::kSignificandBits
;
733 return mozilla::BitwiseCast
<double>(signBit
| exponentBits
| significandBits
);
736 double js::temporal::FractionToDouble(int64_t numerator
, int64_t denominator
) {
737 MOZ_ASSERT(denominator
> 0);
739 // Zero divided by any divisor is still zero.
740 if (numerator
== 0) {
744 // When both values can be represented as doubles, use double division to
745 // compute the exact result. The result is exact, because double division is
746 // guaranteed to return the exact result.
747 if (MOZ_LIKELY(::IsSafeInteger(numerator
) && ::IsSafeInteger(denominator
))) {
748 return double(numerator
) / double(denominator
);
751 // Otherwise call into |FractionToDoubleSlow| to compute the exact result.
753 (int64_t(1) << (std::numeric_limits
<int64_t>::digits
- 4))) {
754 // Slightly faster, but still slow approach when |denominator| is small
755 // enough to allow computing on int64 values.
756 return FractionToDoubleSlow(numerator
, denominator
);
758 return FractionToDoubleSlow(Int128
{numerator
}, Int128
{denominator
});
761 double js::temporal::FractionToDouble(const Int128
& numerator
,
762 const Int128
& denominator
) {
763 MOZ_ASSERT(denominator
> Int128
{0});
765 // Zero divided by any divisor is still zero.
766 if (numerator
== Int128
{0}) {
770 // When both values can be represented as doubles, use double division to
771 // compute the exact result. The result is exact, because double division is
772 // guaranteed to return the exact result.
773 if (MOZ_LIKELY(::IsSafeInteger(numerator
) && ::IsSafeInteger(denominator
))) {
774 return double(numerator
) / double(denominator
);
777 // Otherwise call into |FractionToDoubleSlow| to compute the exact result.
778 return FractionToDoubleSlow(numerator
, denominator
);
782 * ToCalendarNameOption ( normalizedOptions )
784 bool js::temporal::ToCalendarNameOption(JSContext
* cx
,
785 Handle
<JSObject
*> options
,
786 CalendarOption
* result
) {
788 Rooted
<JSString
*> calendarName(cx
);
789 if (!GetStringOption(cx
, options
, cx
->names().calendarName
, &calendarName
)) {
793 // Caller should fill in the fallback.
798 JSLinearString
* linear
= calendarName
->ensureLinear(cx
);
803 if (StringEqualsLiteral(linear
, "auto")) {
804 *result
= CalendarOption::Auto
;
805 } else if (StringEqualsLiteral(linear
, "always")) {
806 *result
= CalendarOption::Always
;
807 } else if (StringEqualsLiteral(linear
, "never")) {
808 *result
= CalendarOption::Never
;
809 } else if (StringEqualsLiteral(linear
, "critical")) {
810 *result
= CalendarOption::Critical
;
812 if (auto chars
= QuoteString(cx
, linear
, '"')) {
813 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
814 JSMSG_INVALID_OPTION_VALUE
, "calendarName",
823 * ToFractionalSecondDigits ( normalizedOptions )
825 bool js::temporal::ToFractionalSecondDigits(JSContext
* cx
,
826 Handle
<JSObject
*> options
,
827 Precision
* precision
) {
829 Rooted
<Value
> digitsValue(cx
);
830 if (!GetProperty(cx
, options
, options
, cx
->names().fractionalSecondDigits
,
836 if (digitsValue
.isUndefined()) {
837 *precision
= Precision::Auto();
842 if (!digitsValue
.isNumber()) {
844 JSString
* string
= JS::ToString(cx
, digitsValue
);
849 JSLinearString
* linear
= string
->ensureLinear(cx
);
854 if (!StringEqualsLiteral(linear
, "auto")) {
855 if (auto chars
= QuoteString(cx
, linear
, '"')) {
856 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
857 JSMSG_INVALID_OPTION_VALUE
,
858 "fractionalSecondDigits", chars
.get());
864 *precision
= Precision::Auto();
869 double digitCount
= digitsValue
.toNumber();
870 if (!std::isfinite(digitCount
)) {
872 const char* numStr
= NumberToCString(&cbuf
, digitCount
);
874 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
875 JSMSG_INVALID_OPTION_VALUE
,
876 "fractionalSecondDigits", numStr
);
881 digitCount
= std::floor(digitCount
);
884 if (digitCount
< 0 || digitCount
> 9) {
886 const char* numStr
= NumberToCString(&cbuf
, digitCount
);
888 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
889 JSMSG_INVALID_OPTION_VALUE
,
890 "fractionalSecondDigits", numStr
);
895 *precision
= Precision
{uint8_t(digitCount
)};
900 * ToSecondsStringPrecisionRecord ( smallestUnit, fractionalDigitCount )
902 SecondsStringPrecision
js::temporal::ToSecondsStringPrecision(
903 TemporalUnit smallestUnit
, Precision fractionalDigitCount
) {
904 MOZ_ASSERT(smallestUnit
== TemporalUnit::Auto
||
905 smallestUnit
>= TemporalUnit::Minute
);
906 MOZ_ASSERT(fractionalDigitCount
== Precision::Auto() ||
907 fractionalDigitCount
.value() <= 9);
910 switch (smallestUnit
) {
912 case TemporalUnit::Minute
:
913 return {Precision::Minute(), TemporalUnit::Minute
, Increment
{1}};
916 case TemporalUnit::Second
:
917 return {Precision
{0}, TemporalUnit::Second
, Increment
{1}};
920 case TemporalUnit::Millisecond
:
921 return {Precision
{3}, TemporalUnit::Millisecond
, Increment
{1}};
924 case TemporalUnit::Microsecond
:
925 return {Precision
{6}, TemporalUnit::Microsecond
, Increment
{1}};
928 case TemporalUnit::Nanosecond
:
929 return {Precision
{9}, TemporalUnit::Nanosecond
, Increment
{1}};
931 case TemporalUnit::Auto
:
934 case TemporalUnit::Year
:
935 case TemporalUnit::Month
:
936 case TemporalUnit::Week
:
937 case TemporalUnit::Day
:
938 case TemporalUnit::Hour
:
939 MOZ_CRASH("Unexpected temporal unit");
942 // Step 6. (Not applicable in our implementation.)
945 if (fractionalDigitCount
== Precision::Auto()) {
946 return {Precision::Auto(), TemporalUnit::Nanosecond
, Increment
{1}};
949 static constexpr Increment increments
[] = {
955 uint8_t digitCount
= fractionalDigitCount
.value();
958 if (digitCount
== 0) {
959 return {Precision
{0}, TemporalUnit::Second
, Increment
{1}};
963 if (digitCount
<= 3) {
964 return {fractionalDigitCount
, TemporalUnit::Millisecond
,
965 increments
[3 - digitCount
]};
969 if (digitCount
<= 6) {
970 return {fractionalDigitCount
, TemporalUnit::Microsecond
,
971 increments
[6 - digitCount
]};
975 MOZ_ASSERT(digitCount
<= 9);
978 return {fractionalDigitCount
, TemporalUnit::Nanosecond
,
979 increments
[9 - digitCount
]};
983 * ToTemporalOverflow ( normalizedOptions )
985 bool js::temporal::ToTemporalOverflow(JSContext
* cx
, Handle
<JSObject
*> options
,
986 TemporalOverflow
* result
) {
988 Rooted
<JSString
*> overflow(cx
);
989 if (!GetStringOption(cx
, options
, cx
->names().overflow
, &overflow
)) {
993 // Caller should fill in the fallback.
998 JSLinearString
* linear
= overflow
->ensureLinear(cx
);
1003 if (StringEqualsLiteral(linear
, "constrain")) {
1004 *result
= TemporalOverflow::Constrain
;
1005 } else if (StringEqualsLiteral(linear
, "reject")) {
1006 *result
= TemporalOverflow::Reject
;
1008 if (auto chars
= QuoteString(cx
, linear
, '"')) {
1009 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
1010 JSMSG_INVALID_OPTION_VALUE
, "overflow",
1019 * ToTemporalDisambiguation ( options )
1021 bool js::temporal::ToTemporalDisambiguation(
1022 JSContext
* cx
, Handle
<JSObject
*> options
,
1023 TemporalDisambiguation
* disambiguation
) {
1024 // Step 1. (Not applicable)
1027 Rooted
<JSString
*> string(cx
);
1028 if (!GetStringOption(cx
, options
, cx
->names().disambiguation
, &string
)) {
1032 // Caller should fill in the fallback.
1037 JSLinearString
* linear
= string
->ensureLinear(cx
);
1042 if (StringEqualsLiteral(linear
, "compatible")) {
1043 *disambiguation
= TemporalDisambiguation::Compatible
;
1044 } else if (StringEqualsLiteral(linear
, "earlier")) {
1045 *disambiguation
= TemporalDisambiguation::Earlier
;
1046 } else if (StringEqualsLiteral(linear
, "later")) {
1047 *disambiguation
= TemporalDisambiguation::Later
;
1048 } else if (StringEqualsLiteral(linear
, "reject")) {
1049 *disambiguation
= TemporalDisambiguation::Reject
;
1051 if (auto chars
= QuoteString(cx
, linear
, '"')) {
1052 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
1053 JSMSG_INVALID_OPTION_VALUE
, "disambiguation",
1062 * ToTemporalOffset ( options, fallback )
1064 bool js::temporal::ToTemporalOffset(JSContext
* cx
, Handle
<JSObject
*> options
,
1065 TemporalOffset
* offset
) {
1066 // Step 1. (Not applicable in our implementation.)
1069 Rooted
<JSString
*> string(cx
);
1070 if (!GetStringOption(cx
, options
, cx
->names().offset
, &string
)) {
1074 // Caller should fill in the fallback.
1079 JSLinearString
* linear
= string
->ensureLinear(cx
);
1084 if (StringEqualsLiteral(linear
, "prefer")) {
1085 *offset
= TemporalOffset::Prefer
;
1086 } else if (StringEqualsLiteral(linear
, "use")) {
1087 *offset
= TemporalOffset::Use
;
1088 } else if (StringEqualsLiteral(linear
, "ignore")) {
1089 *offset
= TemporalOffset::Ignore
;
1090 } else if (StringEqualsLiteral(linear
, "reject")) {
1091 *offset
= TemporalOffset::Reject
;
1093 if (auto chars
= QuoteString(cx
, linear
, '"')) {
1094 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
1095 JSMSG_INVALID_OPTION_VALUE
, "offset",
1104 * ToTimeZoneNameOption ( normalizedOptions )
1106 bool js::temporal::ToTimeZoneNameOption(JSContext
* cx
,
1107 Handle
<JSObject
*> options
,
1108 TimeZoneNameOption
* result
) {
1110 Rooted
<JSString
*> timeZoneName(cx
);
1111 if (!GetStringOption(cx
, options
, cx
->names().timeZoneName
, &timeZoneName
)) {
1115 // Caller should fill in the fallback.
1116 if (!timeZoneName
) {
1120 JSLinearString
* linear
= timeZoneName
->ensureLinear(cx
);
1125 if (StringEqualsLiteral(linear
, "auto")) {
1126 *result
= TimeZoneNameOption::Auto
;
1127 } else if (StringEqualsLiteral(linear
, "never")) {
1128 *result
= TimeZoneNameOption::Never
;
1129 } else if (StringEqualsLiteral(linear
, "critical")) {
1130 *result
= TimeZoneNameOption::Critical
;
1132 if (auto chars
= QuoteString(cx
, linear
, '"')) {
1133 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
1134 JSMSG_INVALID_OPTION_VALUE
, "timeZoneName",
1143 * ToShowOffsetOption ( normalizedOptions )
1145 bool js::temporal::ToShowOffsetOption(JSContext
* cx
, Handle
<JSObject
*> options
,
1146 ShowOffsetOption
* result
) {
1147 // FIXME: spec issue - should be renamed to ToOffsetOption to match the other
1148 // operations ToCalendarNameOption and ToTimeZoneNameOption.
1150 // https://github.com/tc39/proposal-temporal/issues/2441
1153 Rooted
<JSString
*> offset(cx
);
1154 if (!GetStringOption(cx
, options
, cx
->names().offset
, &offset
)) {
1158 // Caller should fill in the fallback.
1163 JSLinearString
* linear
= offset
->ensureLinear(cx
);
1168 if (StringEqualsLiteral(linear
, "auto")) {
1169 *result
= ShowOffsetOption::Auto
;
1170 } else if (StringEqualsLiteral(linear
, "never")) {
1171 *result
= ShowOffsetOption::Never
;
1173 if (auto chars
= QuoteString(cx
, linear
, '"')) {
1174 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
1175 JSMSG_INVALID_OPTION_VALUE
, "offset",
1183 template <typename T
, typename
... Ts
>
1184 static JSObject
* MaybeUnwrapIf(JSObject
* object
) {
1185 if (auto* unwrapped
= object
->maybeUnwrapIf
<T
>()) {
1188 if constexpr (sizeof...(Ts
) > 0) {
1189 return MaybeUnwrapIf
<Ts
...>(object
);
1194 // FIXME: spec issue - "Reject" is exclusively used for Promise rejection. The
1195 // existing `RejectPromise` abstract operation unconditionally rejects, whereas
1196 // this operation conditionally rejects.
1197 // https://github.com/tc39/proposal-temporal/issues/2534
1200 * RejectTemporalLikeObject ( object )
1202 bool js::temporal::RejectTemporalLikeObject(JSContext
* cx
,
1203 Handle
<JSObject
*> object
) {
1205 if (auto* unwrapped
=
1206 MaybeUnwrapIf
<PlainDateObject
, PlainDateTimeObject
,
1207 PlainMonthDayObject
, PlainTimeObject
,
1208 PlainYearMonthObject
, ZonedDateTimeObject
>(object
)) {
1209 Rooted
<Value
> value(cx
, ObjectValue(*object
));
1210 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
1211 nullptr, unwrapped
->getClass()->name
);
1215 Rooted
<Value
> property(cx
);
1218 if (!GetProperty(cx
, object
, object
, cx
->names().calendar
, &property
)) {
1223 if (!property
.isUndefined()) {
1224 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1225 JSMSG_TEMPORAL_UNEXPECTED_PROPERTY
, "calendar");
1230 if (!GetProperty(cx
, object
, object
, cx
->names().timeZone
, &property
)) {
1235 if (!property
.isUndefined()) {
1236 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1237 JSMSG_TEMPORAL_UNEXPECTED_PROPERTY
, "timeZone");
1246 * ToPositiveIntegerWithTruncation ( argument )
1248 bool js::temporal::ToPositiveIntegerWithTruncation(JSContext
* cx
,
1249 Handle
<Value
> value
,
1254 if (!ToIntegerWithTruncation(cx
, value
, name
, &number
)) {
1260 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1261 JSMSG_TEMPORAL_INVALID_NUMBER
, name
);
1271 * ToIntegerWithTruncation ( argument )
1273 bool js::temporal::ToIntegerWithTruncation(JSContext
* cx
, Handle
<Value
> value
,
1274 const char* name
, double* result
) {
1277 if (!JS::ToNumber(cx
, value
, &number
)) {
1282 if (!std::isfinite(number
)) {
1283 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1284 JSMSG_TEMPORAL_INVALID_INTEGER
, name
);
1289 *result
= std::trunc(number
) + (+0.0); // Add zero to convert -0 to +0.
1294 * GetMethod ( V, P )
1296 JSObject
* js::temporal::GetMethod(JSContext
* cx
, Handle
<JSObject
*> object
,
1297 Handle
<PropertyName
*> name
) {
1299 Rooted
<Value
> value(cx
);
1300 if (!GetProperty(cx
, object
, object
, name
, &value
)) {
1305 if (!IsCallable(value
)) {
1306 if (auto chars
= StringToNewUTF8CharsZ(cx
, *name
)) {
1307 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr,
1308 JSMSG_PROPERTY_NOT_CALLABLE
, chars
.get());
1314 return &value
.toObject();
1318 * CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )
1320 * Implementation when |excludedKeys| and |excludedValues| are both empty lists.
1322 bool js::temporal::CopyDataProperties(JSContext
* cx
,
1323 Handle
<PlainObject
*> target
,
1324 Handle
<JSObject
*> source
) {
1325 // Optimization for the common case when |source| is a native object.
1326 if (source
->is
<NativeObject
>()) {
1327 bool optimized
= false;
1328 if (!CopyDataPropertiesNative(cx
, target
, source
.as
<NativeObject
>(),
1329 nullptr, &optimized
)) {
1337 // Step 1-2. (Not applicable)
1340 JS::RootedVector
<PropertyKey
> keys(cx
);
1341 if (!GetPropertyKeys(
1342 cx
, source
, JSITER_OWNONLY
| JSITER_HIDDEN
| JSITER_SYMBOLS
, &keys
)) {
1347 Rooted
<mozilla::Maybe
<PropertyDescriptor
>> desc(cx
);
1348 Rooted
<Value
> propValue(cx
);
1349 for (size_t i
= 0; i
< keys
.length(); i
++) {
1350 Handle
<PropertyKey
> key
= keys
[i
];
1352 // Steps 4.a-b. (Not applicable)
1355 if (!GetOwnPropertyDescriptor(cx
, source
, key
, &desc
)) {
1360 if (desc
.isNothing() || !desc
->enumerable()) {
1365 if (!GetProperty(cx
, source
, source
, key
, &propValue
)) {
1369 // Step 4.c.ii.2. (Not applicable)
1372 if (!DefineDataProperty(cx
, target
, key
, propValue
)) {
1382 * CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )
1384 * Implementation when |excludedKeys| is an empty list and |excludedValues| is
1385 * the list «undefined».
1387 static bool CopyDataPropertiesIgnoreUndefined(JSContext
* cx
,
1388 Handle
<PlainObject
*> target
,
1389 Handle
<JSObject
*> source
) {
1390 // Step 1-2. (Not applicable)
1393 JS::RootedVector
<PropertyKey
> keys(cx
);
1394 if (!GetPropertyKeys(
1395 cx
, source
, JSITER_OWNONLY
| JSITER_HIDDEN
| JSITER_SYMBOLS
, &keys
)) {
1400 Rooted
<mozilla::Maybe
<PropertyDescriptor
>> desc(cx
);
1401 Rooted
<Value
> propValue(cx
);
1402 for (size_t i
= 0; i
< keys
.length(); i
++) {
1403 Handle
<PropertyKey
> key
= keys
[i
];
1405 // Steps 4.a-b. (Not applicable)
1408 if (!GetOwnPropertyDescriptor(cx
, source
, key
, &desc
)) {
1413 if (desc
.isNothing() || !desc
->enumerable()) {
1418 if (!GetProperty(cx
, source
, source
, key
, &propValue
)) {
1423 if (propValue
.isUndefined()) {
1428 if (!DefineDataProperty(cx
, target
, key
, propValue
)) {
1438 * SnapshotOwnProperties ( source, proto [, excludedKeys [, excludedValues ] ] )
1440 PlainObject
* js::temporal::SnapshotOwnProperties(JSContext
* cx
,
1441 Handle
<JSObject
*> source
) {
1443 Rooted
<PlainObject
*> copy(cx
, NewPlainObjectWithProto(cx
, nullptr));
1449 if (!CopyDataProperties(cx
, copy
, source
)) {
1458 * SnapshotOwnProperties ( source, proto [, excludedKeys [, excludedValues ] ] )
1460 * Implementation when |excludedKeys| is an empty list and |excludedValues| is
1461 * the list «undefined».
1463 PlainObject
* js::temporal::SnapshotOwnPropertiesIgnoreUndefined(
1464 JSContext
* cx
, Handle
<JSObject
*> source
) {
1466 Rooted
<PlainObject
*> copy(cx
, NewPlainObjectWithProto(cx
, nullptr));
1472 if (!CopyDataPropertiesIgnoreUndefined(cx
, copy
, source
)) {
1481 * GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits,
1482 * fallbackSmallestUnit, smallestLargestDefaultUnit )
1484 bool js::temporal::GetDifferenceSettings(
1485 JSContext
* cx
, TemporalDifference operation
, Handle
<PlainObject
*> options
,
1486 TemporalUnitGroup unitGroup
, TemporalUnit smallestAllowedUnit
,
1487 TemporalUnit fallbackSmallestUnit
, TemporalUnit smallestLargestDefaultUnit
,
1488 DifferenceSettings
* result
) {
1490 auto largestUnit
= TemporalUnit::Auto
;
1491 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::LargestUnit
, unitGroup
,
1497 if (largestUnit
> smallestAllowedUnit
) {
1498 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1499 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
1500 TemporalUnitToString(largestUnit
), "largestUnit");
1505 auto roundingIncrement
= Increment
{1};
1506 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
1511 auto roundingMode
= TemporalRoundingMode::Trunc
;
1512 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1517 if (operation
== TemporalDifference::Since
) {
1518 roundingMode
= NegateTemporalRoundingMode(roundingMode
);
1522 auto smallestUnit
= fallbackSmallestUnit
;
1523 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
, unitGroup
,
1529 if (smallestUnit
> smallestAllowedUnit
) {
1530 JS_ReportErrorNumberASCII(
1531 cx
, GetErrorMessage
, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
1532 TemporalUnitToString(smallestUnit
), "smallestUnit");
1536 // Step 9. (Inlined call to LargerOfTwoTemporalUnits)
1537 auto defaultLargestUnit
= std::min(smallestLargestDefaultUnit
, smallestUnit
);
1540 if (largestUnit
== TemporalUnit::Auto
) {
1541 largestUnit
= defaultLargestUnit
;
1545 if (largestUnit
> smallestUnit
) {
1546 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1547 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
1552 if (smallestUnit
> TemporalUnit::Day
) {
1554 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
1557 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
1564 *result
= {smallestUnit
, largestUnit
, roundingMode
, roundingIncrement
};
1568 bool temporal::IsArrayIterationSane(JSContext
* cx
, bool* result
) {
1569 auto* stubChain
= ForOfPIC::getOrCreate(cx
);
1573 return stubChain
->tryOptimizeArray(cx
, result
);
1576 static JSObject
* CreateTemporalObject(JSContext
* cx
, JSProtoKey key
) {
1577 Rooted
<JSObject
*> proto(cx
, &cx
->global()->getObjectPrototype());
1579 // The |Temporal| object is just a plain object with some "static" data
1580 // properties and some constructor properties.
1581 return NewTenuredObjectWithGivenProto
<TemporalObject
>(cx
, proto
);
1585 * Initializes the Temporal Object and its standard built-in properties.
1587 static bool TemporalClassFinish(JSContext
* cx
, Handle
<JSObject
*> temporal
,
1588 Handle
<JSObject
*> proto
) {
1589 Rooted
<PropertyKey
> ctorId(cx
);
1590 Rooted
<Value
> ctorValue(cx
);
1591 auto defineProperty
= [&](JSProtoKey protoKey
, Handle
<PropertyName
*> name
) {
1592 JSObject
* ctor
= GlobalObject::getOrCreateConstructor(cx
, protoKey
);
1597 ctorId
= NameToId(name
);
1598 ctorValue
.setObject(*ctor
);
1599 return DefineDataProperty(cx
, temporal
, ctorId
, ctorValue
, 0);
1602 // Add the constructor properties.
1603 for (const auto& protoKey
: {
1608 JSProto_PlainDateTime
,
1609 JSProto_PlainMonthDay
,
1611 JSProto_PlainYearMonth
,
1613 JSProto_ZonedDateTime
,
1615 if (!defineProperty(protoKey
, ClassName(protoKey
, cx
))) {
1620 // ClassName(JSProto_TemporalNow) returns "TemporalNow", so we need to handle
1622 if (!defineProperty(JSProto_TemporalNow
, cx
->names().Now
)) {
1629 const JSClass
TemporalObject::class_
= {
1631 JSCLASS_HAS_CACHED_PROTO(JSProto_Temporal
),
1633 &TemporalObject::classSpec_
,
1636 static const JSPropertySpec Temporal_properties
[] = {
1637 JS_STRING_SYM_PS(toStringTag
, "Temporal", JSPROP_READONLY
),
1641 const ClassSpec
TemporalObject::classSpec_
= {
1642 CreateTemporalObject
, nullptr, nullptr,
1643 Temporal_properties
, nullptr, nullptr,
1644 TemporalClassFinish
,