Bug 1874684 - Part 22: Compute the precise fraction in Duration.p.total and ZonedDate...
[gecko.git] / js / src / builtin / temporal / Temporal.cpp
blob6238f689105e7fc25b1e643efa5c07f62797d8b7
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"
15 #include <algorithm>
16 #include <cmath>
17 #include <cstdlib>
18 #include <initializer_list>
19 #include <iterator>
20 #include <limits>
21 #include <stdint.h>
22 #include <string_view>
23 #include <type_traits>
24 #include <utility>
26 #include "jsfriendapi.h"
27 #include "jsnum.h"
28 #include "jspubtd.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"
43 #include "js/Class.h"
44 #include "js/Conversions.h"
45 #include "js/ErrorReport.h"
46 #include "js/friend/ErrorMessages.h"
47 #include "js/GCVector.h"
48 #include "js/Id.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"
54 #include "js/Value.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"
62 #include "vm/PIC.h"
63 #include "vm/PlainObject.h"
64 #include "vm/Realm.h"
65 #include "vm/StringType.h"
67 #include "vm/JSObject-inl.h"
68 #include "vm/ObjectOperations-inl.h"
70 using namespace js;
71 using namespace js::temporal;
73 /**
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) {
82 // Step 1.
83 Rooted<Value> value(cx);
84 if (!GetProperty(cx, options, options, property, &value)) {
85 return false;
88 // Step 2. (Caller should fill in the fallback.)
89 if (value.isUndefined()) {
90 return true;
93 // Steps 3-4. (Not applicable when type=string)
95 // Step 5.
96 string.set(JS::ToString(cx, value));
97 if (!string) {
98 return false;
101 // Step 6. (Not applicable in our implementation)
103 // Step 7.
104 return true;
108 * GetOption ( options, property, type, values, default )
110 static bool GetNumberOption(JSContext* cx, Handle<JSObject*> options,
111 Handle<PropertyName*> property, double* number) {
112 // Step 1.
113 Rooted<Value> value(cx);
114 if (!GetProperty(cx, options, options, property, &value)) {
115 return false;
118 // Step 2. (Caller should fill in the fallback.)
119 if (value.isUndefined()) {
120 return true;
123 // Steps 3 and 5. (Not applicable in our implementation)
125 // Step 4.a.
126 if (!JS::ToNumber(cx, value, number)) {
127 return false;
130 // Step 4.b. (Caller must check for NaN values.)
132 // Step 7. (Not applicable in our implementation)
134 // Step 8.
135 return true;
139 * ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive )
141 bool js::temporal::ToTemporalRoundingIncrement(JSContext* cx,
142 Handle<JSObject*> options,
143 Increment* increment) {
144 // Step 1.
145 double number = 1;
146 if (!GetNumberOption(cx, options, cx->names().roundingIncrement, &number)) {
147 return false;
150 // Step 3. (Reordered)
151 number = std::trunc(number);
153 // Steps 2 and 4.
154 if (!std::isfinite(number) || number < 1 || number > 1'000'000'000) {
155 ToCStringBuf cbuf;
156 const char* numStr = NumberToCString(&cbuf, number);
158 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
159 JSMSG_INVALID_OPTION_VALUE, "roundingIncrement",
160 numStr);
161 return false;
164 *increment = Increment{uint32_t(number)};
165 return true;
169 * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )
171 bool js::temporal::ValidateTemporalRoundingIncrement(JSContext* cx,
172 Increment increment,
173 int64_t dividend,
174 bool inclusive) {
175 MOZ_ASSERT(dividend > 0);
176 MOZ_ASSERT_IF(!inclusive, dividend > 1);
178 // Steps 1-2.
179 int64_t maximum = inclusive ? dividend : dividend - 1;
181 // Steps 3-4.
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",
189 numStr);
190 return false;
193 // Step 5.
194 return true;
197 PropertyName* js::temporal::TemporalUnitToString(JSContext* cx,
198 TemporalUnit unit) {
199 switch (unit) {
200 case TemporalUnit::Auto:
201 break;
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) {
228 switch (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) {
240 switch (key) {
241 case TemporalUnitKey::SmallestUnit:
242 return "smallestUnit";
243 case TemporalUnitKey::LargestUnit:
244 return "largestUnit";
245 case TemporalUnitKey::Unit:
246 return "unit";
248 MOZ_CRASH("invalid temporal unit group");
251 static bool ToTemporalUnit(JSContext* cx, JSLinearString* str,
252 TemporalUnitKey key, TemporalUnit* unit) {
253 struct UnitMap {
254 std::string_view name;
255 TemporalUnit unit;
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();
287 ->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)) {
300 *unit = m.unit;
301 return true;
306 if (auto chars = QuoteString(cx, str, '"')) {
307 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
308 JSMSG_INVALID_OPTION_VALUE, ToCString(key),
309 chars.get());
311 return false;
314 static std::pair<TemporalUnit, TemporalUnit> AllowedValues(
315 TemporalUnitGroup unitGroup) {
316 switch (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
331 * ] )
333 bool js::temporal::GetTemporalUnit(JSContext* cx, Handle<JSObject*> options,
334 TemporalUnitKey key,
335 TemporalUnitGroup unitGroup,
336 TemporalUnit* unit) {
337 // Steps 1-8. (Not applicable in our implementation.)
339 // Step 9.
340 Rooted<JSString*> value(cx);
341 if (!GetStringOption(cx, options, ToPropertyName(cx, key), &value)) {
342 return false;
345 // Caller should fill in the fallback.
346 if (!value) {
347 return true;
350 return GetTemporalUnit(cx, value, key, unitGroup, unit);
354 * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
355 * ] )
357 bool js::temporal::GetTemporalUnit(JSContext* cx, Handle<JSString*> value,
358 TemporalUnitKey key,
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));
366 if (!linear) {
367 return false;
370 // Caller should fill in the fallback.
371 if (key == TemporalUnitKey::LargestUnit) {
372 if (StringEqualsLiteral(linear, "auto")) {
373 return true;
377 // Step 11.
378 if (!ToTemporalUnit(cx, linear, key, unit)) {
379 return false;
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),
387 chars.get());
389 return false;
392 return true;
396 * ToTemporalRoundingMode ( normalizedOptions, fallback )
398 bool js::temporal::ToTemporalRoundingMode(JSContext* cx,
399 Handle<JSObject*> options,
400 TemporalRoundingMode* mode) {
401 // Step 1.
402 Rooted<JSString*> string(cx);
403 if (!GetStringOption(cx, options, cx->names().roundingMode, &string)) {
404 return false;
407 // Caller should fill in the fallback.
408 if (!string) {
409 return true;
412 JSLinearString* linear = string->ensureLinear(cx);
413 if (!linear) {
414 return false;
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;
435 } else {
436 if (auto chars = QuoteString(cx, linear, '"')) {
437 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
438 JSMSG_INVALID_OPTION_VALUE, "roundingMode",
439 chars.get());
441 return false;
443 return true;
446 #ifdef DEBUG
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.
453 template <>
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}) {
459 return true;
461 if (x > Int128{0}) {
462 return y > Int128{0} ? x <= max / y : y >= min / x;
464 return y > Int128{0} ? x >= min / y : y >= max / x;
466 #endif
469 * RoundNumberToIncrement ( x, increment, roundingMode )
471 Int128 js::temporal::RoundNumberToIncrement(int64_t numerator,
472 int64_t denominator,
473 Increment increment,
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) {
480 return Int128{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);
494 // Steps 1-8.
495 int64_t rounded = Divide(numerator, divisor.value(), roundingMode);
497 // Step 9.
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,
514 Increment increment,
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});
525 // Steps 1-8.
526 auto rounded = Divide(numerator, divisor, roundingMode);
528 // Step 9.
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});
541 // Steps 1-8.
542 auto rounded = Divide(x, increment, roundingMode);
544 // Step 9.
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>) {
572 return value.abs();
573 } else {
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>) {
584 return x.divrem(y);
585 } else {
586 return std::pair{x / y, x % y};
590 auto [quot, rem] =
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++) {
635 auto [digit, next] =
636 divrem(rem * UnsignedT{16}, static_cast<UnsignedT>(denominator));
637 rem = next;
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) {
650 // significand
651 // ___________|__________
652 // / \
653 // [xxx················yyy|
654 // \_/ \_/
655 // | |
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
677 // round the result.
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} ||
696 rem != UnsignedT{0};
697 if (shouldRoundUp) {
698 // Add one to the significand bits.
699 significand += 1;
701 // If they overflow, the exponent must also be increased.
702 if ((significand >> SignificandWidthWithImplicitOne) != 0) {
703 exponent++;
704 significand >>= 1;
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
715 // accordingly.
716 uint32_t significandZeros = mozilla::CountLeadingZeroes64(significand);
717 if (significandZeros < SignificandLeadingZeros) {
718 uint32_t shift = SignificandLeadingZeros - significandZeros;
719 significand >>= shift;
720 exponent += shift;
721 } else if (significandZeros > SignificandLeadingZeros) {
722 uint32_t shift = significandZeros - SignificandLeadingZeros;
723 significand <<= shift;
724 exponent -= 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) {
741 return 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.
752 if (denominator <=
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}) {
767 return 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) {
787 // Step 1.
788 Rooted<JSString*> calendarName(cx);
789 if (!GetStringOption(cx, options, cx->names().calendarName, &calendarName)) {
790 return false;
793 // Caller should fill in the fallback.
794 if (!calendarName) {
795 return true;
798 JSLinearString* linear = calendarName->ensureLinear(cx);
799 if (!linear) {
800 return false;
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;
811 } else {
812 if (auto chars = QuoteString(cx, linear, '"')) {
813 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
814 JSMSG_INVALID_OPTION_VALUE, "calendarName",
815 chars.get());
817 return false;
819 return true;
823 * ToFractionalSecondDigits ( normalizedOptions )
825 bool js::temporal::ToFractionalSecondDigits(JSContext* cx,
826 Handle<JSObject*> options,
827 Precision* precision) {
828 // Step 1.
829 Rooted<Value> digitsValue(cx);
830 if (!GetProperty(cx, options, options, cx->names().fractionalSecondDigits,
831 &digitsValue)) {
832 return false;
835 // Step 2.
836 if (digitsValue.isUndefined()) {
837 *precision = Precision::Auto();
838 return true;
841 // Step 3.
842 if (!digitsValue.isNumber()) {
843 // Step 3.a.
844 JSString* string = JS::ToString(cx, digitsValue);
845 if (!string) {
846 return false;
849 JSLinearString* linear = string->ensureLinear(cx);
850 if (!linear) {
851 return false;
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());
860 return false;
863 // Step 3.b.
864 *precision = Precision::Auto();
865 return true;
868 // Step 4.
869 double digitCount = digitsValue.toNumber();
870 if (!std::isfinite(digitCount)) {
871 ToCStringBuf cbuf;
872 const char* numStr = NumberToCString(&cbuf, digitCount);
874 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
875 JSMSG_INVALID_OPTION_VALUE,
876 "fractionalSecondDigits", numStr);
877 return false;
880 // Step 5.
881 digitCount = std::floor(digitCount);
883 // Step 6.
884 if (digitCount < 0 || digitCount > 9) {
885 ToCStringBuf cbuf;
886 const char* numStr = NumberToCString(&cbuf, digitCount);
888 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
889 JSMSG_INVALID_OPTION_VALUE,
890 "fractionalSecondDigits", numStr);
891 return false;
894 // Step 7.
895 *precision = Precision{uint8_t(digitCount)};
896 return true;
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);
909 // Steps 1-5.
910 switch (smallestUnit) {
911 // Step 1.
912 case TemporalUnit::Minute:
913 return {Precision::Minute(), TemporalUnit::Minute, Increment{1}};
915 // Step 2.
916 case TemporalUnit::Second:
917 return {Precision{0}, TemporalUnit::Second, Increment{1}};
919 // Step 3.
920 case TemporalUnit::Millisecond:
921 return {Precision{3}, TemporalUnit::Millisecond, Increment{1}};
923 // Step 4.
924 case TemporalUnit::Microsecond:
925 return {Precision{6}, TemporalUnit::Microsecond, Increment{1}};
927 // Step 5.
928 case TemporalUnit::Nanosecond:
929 return {Precision{9}, TemporalUnit::Nanosecond, Increment{1}};
931 case TemporalUnit::Auto:
932 break;
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.)
944 // Step 7.
945 if (fractionalDigitCount == Precision::Auto()) {
946 return {Precision::Auto(), TemporalUnit::Nanosecond, Increment{1}};
949 static constexpr Increment increments[] = {
950 Increment{1},
951 Increment{10},
952 Increment{100},
955 uint8_t digitCount = fractionalDigitCount.value();
957 // Step 8.
958 if (digitCount == 0) {
959 return {Precision{0}, TemporalUnit::Second, Increment{1}};
962 // Step 9.
963 if (digitCount <= 3) {
964 return {fractionalDigitCount, TemporalUnit::Millisecond,
965 increments[3 - digitCount]};
968 // Step 10.
969 if (digitCount <= 6) {
970 return {fractionalDigitCount, TemporalUnit::Microsecond,
971 increments[6 - digitCount]};
974 // Step 11.
975 MOZ_ASSERT(digitCount <= 9);
977 // Step 12.
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) {
987 // Step 1.
988 Rooted<JSString*> overflow(cx);
989 if (!GetStringOption(cx, options, cx->names().overflow, &overflow)) {
990 return false;
993 // Caller should fill in the fallback.
994 if (!overflow) {
995 return true;
998 JSLinearString* linear = overflow->ensureLinear(cx);
999 if (!linear) {
1000 return false;
1003 if (StringEqualsLiteral(linear, "constrain")) {
1004 *result = TemporalOverflow::Constrain;
1005 } else if (StringEqualsLiteral(linear, "reject")) {
1006 *result = TemporalOverflow::Reject;
1007 } else {
1008 if (auto chars = QuoteString(cx, linear, '"')) {
1009 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1010 JSMSG_INVALID_OPTION_VALUE, "overflow",
1011 chars.get());
1013 return false;
1015 return true;
1019 * ToTemporalDisambiguation ( options )
1021 bool js::temporal::ToTemporalDisambiguation(
1022 JSContext* cx, Handle<JSObject*> options,
1023 TemporalDisambiguation* disambiguation) {
1024 // Step 1. (Not applicable)
1026 // Step 2.
1027 Rooted<JSString*> string(cx);
1028 if (!GetStringOption(cx, options, cx->names().disambiguation, &string)) {
1029 return false;
1032 // Caller should fill in the fallback.
1033 if (!string) {
1034 return true;
1037 JSLinearString* linear = string->ensureLinear(cx);
1038 if (!linear) {
1039 return false;
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;
1050 } else {
1051 if (auto chars = QuoteString(cx, linear, '"')) {
1052 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1053 JSMSG_INVALID_OPTION_VALUE, "disambiguation",
1054 chars.get());
1056 return false;
1058 return true;
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.)
1068 // Step 2.
1069 Rooted<JSString*> string(cx);
1070 if (!GetStringOption(cx, options, cx->names().offset, &string)) {
1071 return false;
1074 // Caller should fill in the fallback.
1075 if (!string) {
1076 return true;
1079 JSLinearString* linear = string->ensureLinear(cx);
1080 if (!linear) {
1081 return false;
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;
1092 } else {
1093 if (auto chars = QuoteString(cx, linear, '"')) {
1094 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1095 JSMSG_INVALID_OPTION_VALUE, "offset",
1096 chars.get());
1098 return false;
1100 return true;
1104 * ToTimeZoneNameOption ( normalizedOptions )
1106 bool js::temporal::ToTimeZoneNameOption(JSContext* cx,
1107 Handle<JSObject*> options,
1108 TimeZoneNameOption* result) {
1109 // Step 1.
1110 Rooted<JSString*> timeZoneName(cx);
1111 if (!GetStringOption(cx, options, cx->names().timeZoneName, &timeZoneName)) {
1112 return false;
1115 // Caller should fill in the fallback.
1116 if (!timeZoneName) {
1117 return true;
1120 JSLinearString* linear = timeZoneName->ensureLinear(cx);
1121 if (!linear) {
1122 return false;
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;
1131 } else {
1132 if (auto chars = QuoteString(cx, linear, '"')) {
1133 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1134 JSMSG_INVALID_OPTION_VALUE, "timeZoneName",
1135 chars.get());
1137 return false;
1139 return true;
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
1152 // Step 1.
1153 Rooted<JSString*> offset(cx);
1154 if (!GetStringOption(cx, options, cx->names().offset, &offset)) {
1155 return false;
1158 // Caller should fill in the fallback.
1159 if (!offset) {
1160 return true;
1163 JSLinearString* linear = offset->ensureLinear(cx);
1164 if (!linear) {
1165 return false;
1168 if (StringEqualsLiteral(linear, "auto")) {
1169 *result = ShowOffsetOption::Auto;
1170 } else if (StringEqualsLiteral(linear, "never")) {
1171 *result = ShowOffsetOption::Never;
1172 } else {
1173 if (auto chars = QuoteString(cx, linear, '"')) {
1174 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1175 JSMSG_INVALID_OPTION_VALUE, "offset",
1176 chars.get());
1178 return false;
1180 return true;
1183 template <typename T, typename... Ts>
1184 static JSObject* MaybeUnwrapIf(JSObject* object) {
1185 if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
1186 return unwrapped;
1188 if constexpr (sizeof...(Ts) > 0) {
1189 return MaybeUnwrapIf<Ts...>(object);
1191 return nullptr;
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) {
1204 // Step 1.
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);
1212 return false;
1215 Rooted<Value> property(cx);
1217 // Step 2.
1218 if (!GetProperty(cx, object, object, cx->names().calendar, &property)) {
1219 return false;
1222 // Step 3.
1223 if (!property.isUndefined()) {
1224 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1225 JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "calendar");
1226 return false;
1229 // Step 4.
1230 if (!GetProperty(cx, object, object, cx->names().timeZone, &property)) {
1231 return false;
1234 // Step 5.
1235 if (!property.isUndefined()) {
1236 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1237 JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "timeZone");
1238 return false;
1241 // Step 6.
1242 return true;
1246 * ToPositiveIntegerWithTruncation ( argument )
1248 bool js::temporal::ToPositiveIntegerWithTruncation(JSContext* cx,
1249 Handle<Value> value,
1250 const char* name,
1251 double* result) {
1252 // Step 1.
1253 double number;
1254 if (!ToIntegerWithTruncation(cx, value, name, &number)) {
1255 return false;
1258 // Step 2.
1259 if (number <= 0) {
1260 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1261 JSMSG_TEMPORAL_INVALID_NUMBER, name);
1262 return false;
1265 // Step 3.
1266 *result = number;
1267 return true;
1271 * ToIntegerWithTruncation ( argument )
1273 bool js::temporal::ToIntegerWithTruncation(JSContext* cx, Handle<Value> value,
1274 const char* name, double* result) {
1275 // Step 1.
1276 double number;
1277 if (!JS::ToNumber(cx, value, &number)) {
1278 return false;
1281 // Step 2.
1282 if (!std::isfinite(number)) {
1283 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1284 JSMSG_TEMPORAL_INVALID_INTEGER, name);
1285 return false;
1288 // Step 3.
1289 *result = std::trunc(number) + (+0.0); // Add zero to convert -0 to +0.
1290 return true;
1294 * GetMethod ( V, P )
1296 JSObject* js::temporal::GetMethod(JSContext* cx, Handle<JSObject*> object,
1297 Handle<PropertyName*> name) {
1298 // Step 1.
1299 Rooted<Value> value(cx);
1300 if (!GetProperty(cx, object, object, name, &value)) {
1301 return nullptr;
1304 // Steps 2-3.
1305 if (!IsCallable(value)) {
1306 if (auto chars = StringToNewUTF8CharsZ(cx, *name)) {
1307 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1308 JSMSG_PROPERTY_NOT_CALLABLE, chars.get());
1310 return nullptr;
1313 // Step 4.
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)) {
1330 return false;
1332 if (optimized) {
1333 return true;
1337 // Step 1-2. (Not applicable)
1339 // Step 3.
1340 JS::RootedVector<PropertyKey> keys(cx);
1341 if (!GetPropertyKeys(
1342 cx, source, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys)) {
1343 return false;
1346 // Step 4.
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)
1354 // Step 4.c.i.
1355 if (!GetOwnPropertyDescriptor(cx, source, key, &desc)) {
1356 return false;
1359 // Step 4.c.ii.
1360 if (desc.isNothing() || !desc->enumerable()) {
1361 continue;
1364 // Step 4.c.ii.1.
1365 if (!GetProperty(cx, source, source, key, &propValue)) {
1366 return false;
1369 // Step 4.c.ii.2. (Not applicable)
1371 // Step 4.c.ii.3.
1372 if (!DefineDataProperty(cx, target, key, propValue)) {
1373 return false;
1377 // Step 5.
1378 return true;
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)
1392 // Step 3.
1393 JS::RootedVector<PropertyKey> keys(cx);
1394 if (!GetPropertyKeys(
1395 cx, source, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys)) {
1396 return false;
1399 // Step 4.
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)
1407 // Step 4.c.i.
1408 if (!GetOwnPropertyDescriptor(cx, source, key, &desc)) {
1409 return false;
1412 // Step 4.c.ii.
1413 if (desc.isNothing() || !desc->enumerable()) {
1414 continue;
1417 // Step 4.c.ii.1.
1418 if (!GetProperty(cx, source, source, key, &propValue)) {
1419 return false;
1422 // Step 4.c.ii.2.
1423 if (propValue.isUndefined()) {
1424 continue;
1427 // Step 4.c.ii.3.
1428 if (!DefineDataProperty(cx, target, key, propValue)) {
1429 return false;
1433 // Step 5.
1434 return true;
1438 * SnapshotOwnProperties ( source, proto [, excludedKeys [, excludedValues ] ] )
1440 PlainObject* js::temporal::SnapshotOwnProperties(JSContext* cx,
1441 Handle<JSObject*> source) {
1442 // Step 1.
1443 Rooted<PlainObject*> copy(cx, NewPlainObjectWithProto(cx, nullptr));
1444 if (!copy) {
1445 return nullptr;
1448 // Steps 2-4.
1449 if (!CopyDataProperties(cx, copy, source)) {
1450 return nullptr;
1453 // Step 3.
1454 return copy;
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) {
1465 // Step 1.
1466 Rooted<PlainObject*> copy(cx, NewPlainObjectWithProto(cx, nullptr));
1467 if (!copy) {
1468 return nullptr;
1471 // Steps 2-4.
1472 if (!CopyDataPropertiesIgnoreUndefined(cx, copy, source)) {
1473 return nullptr;
1476 // Step 3.
1477 return copy;
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) {
1489 // Steps 1-2.
1490 auto largestUnit = TemporalUnit::Auto;
1491 if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit, unitGroup,
1492 &largestUnit)) {
1493 return false;
1496 // Step 3.
1497 if (largestUnit > smallestAllowedUnit) {
1498 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1499 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
1500 TemporalUnitToString(largestUnit), "largestUnit");
1501 return false;
1504 // Step 4.
1505 auto roundingIncrement = Increment{1};
1506 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
1507 return false;
1510 // Step 5.
1511 auto roundingMode = TemporalRoundingMode::Trunc;
1512 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
1513 return false;
1516 // Step 6.
1517 if (operation == TemporalDifference::Since) {
1518 roundingMode = NegateTemporalRoundingMode(roundingMode);
1521 // Step 7.
1522 auto smallestUnit = fallbackSmallestUnit;
1523 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit, unitGroup,
1524 &smallestUnit)) {
1525 return false;
1528 // Step 8.
1529 if (smallestUnit > smallestAllowedUnit) {
1530 JS_ReportErrorNumberASCII(
1531 cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
1532 TemporalUnitToString(smallestUnit), "smallestUnit");
1533 return false;
1536 // Step 9. (Inlined call to LargerOfTwoTemporalUnits)
1537 auto defaultLargestUnit = std::min(smallestLargestDefaultUnit, smallestUnit);
1539 // Step 10.
1540 if (largestUnit == TemporalUnit::Auto) {
1541 largestUnit = defaultLargestUnit;
1544 // Step 11.
1545 if (largestUnit > smallestUnit) {
1546 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1547 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
1548 return false;
1551 // Steps 12-13.
1552 if (smallestUnit > TemporalUnit::Day) {
1553 // Step 12.
1554 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
1556 // Step 13.
1557 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
1558 false)) {
1559 return false;
1563 // Step 14.
1564 *result = {smallestUnit, largestUnit, roundingMode, roundingIncrement};
1565 return true;
1568 bool temporal::IsArrayIterationSane(JSContext* cx, bool* result) {
1569 auto* stubChain = ForOfPIC::getOrCreate(cx);
1570 if (!stubChain) {
1571 return false;
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);
1593 if (!ctor) {
1594 return false;
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 : {
1604 JSProto_Calendar,
1605 JSProto_Duration,
1606 JSProto_Instant,
1607 JSProto_PlainDate,
1608 JSProto_PlainDateTime,
1609 JSProto_PlainMonthDay,
1610 JSProto_PlainTime,
1611 JSProto_PlainYearMonth,
1612 JSProto_TimeZone,
1613 JSProto_ZonedDateTime,
1614 }) {
1615 if (!defineProperty(protoKey, ClassName(protoKey, cx))) {
1616 return false;
1620 // ClassName(JSProto_TemporalNow) returns "TemporalNow", so we need to handle
1621 // it separately.
1622 if (!defineProperty(JSProto_TemporalNow, cx->names().Now)) {
1623 return false;
1626 return true;
1629 const JSClass TemporalObject::class_ = {
1630 "Temporal",
1631 JSCLASS_HAS_CACHED_PROTO(JSProto_Temporal),
1632 JS_NULL_CLASS_OPS,
1633 &TemporalObject::classSpec_,
1636 static const JSPropertySpec Temporal_properties[] = {
1637 JS_STRING_SYM_PS(toStringTag, "Temporal", JSPROP_READONLY),
1638 JS_PS_END,
1641 const ClassSpec TemporalObject::classSpec_ = {
1642 CreateTemporalObject, nullptr, nullptr,
1643 Temporal_properties, nullptr, nullptr,
1644 TemporalClassFinish,