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/Instant.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/FloatingPoint.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/Span.h"
26 #include "NamespaceImports.h"
28 #include "builtin/temporal/Calendar.h"
29 #include "builtin/temporal/Duration.h"
30 #include "builtin/temporal/Int96.h"
31 #include "builtin/temporal/PlainDateTime.h"
32 #include "builtin/temporal/Temporal.h"
33 #include "builtin/temporal/TemporalParser.h"
34 #include "builtin/temporal/TemporalRoundingMode.h"
35 #include "builtin/temporal/TemporalTypes.h"
36 #include "builtin/temporal/TemporalUnit.h"
37 #include "builtin/temporal/TimeZone.h"
38 #include "builtin/temporal/ToString.h"
39 #include "builtin/temporal/Wrapped.h"
40 #include "builtin/temporal/ZonedDateTime.h"
41 #include "gc/AllocKind.h"
42 #include "gc/Barrier.h"
43 #include "js/CallArgs.h"
44 #include "js/CallNonGenericMethod.h"
46 #include "js/Conversions.h"
47 #include "js/ErrorReport.h"
48 #include "js/friend/ErrorMessages.h"
49 #include "js/PropertyDescriptor.h"
50 #include "js/PropertySpec.h"
51 #include "js/RootingAPI.h"
52 #include "js/TypeDecls.h"
54 #include "vm/BigIntType.h"
55 #include "vm/BytecodeUtil.h"
56 #include "vm/GlobalObject.h"
57 #include "vm/JSAtomState.h"
58 #include "vm/JSContext.h"
59 #include "vm/JSObject.h"
60 #include "vm/PlainObject.h"
61 #include "vm/StringType.h"
63 #include "vm/JSObject-inl.h"
64 #include "vm/NativeObject-inl.h"
65 #include "vm/ObjectOperations-inl.h"
68 using namespace js::temporal
;
70 static inline bool IsInstant(Handle
<Value
> v
) {
71 return v
.isObject() && v
.toObject().is
<InstantObject
>();
75 * Check if the absolute value is less-or-equal to the given limit.
77 template <const auto& digits
>
78 static bool AbsoluteValueIsLessOrEqual(const BigInt
* bigInt
) {
79 size_t length
= bigInt
->digitLength();
81 // Fewer digits than the limit, so definitely in range.
82 if (length
< std::size(digits
)) {
86 // More digits than the limit, so definitely out of range.
87 if (length
> std::size(digits
)) {
91 // Compare each digit when the input has the same number of digits.
92 size_t index
= std::size(digits
);
93 for (auto digit
: digits
) {
94 auto d
= bigInt
->digit(--index
);
105 static constexpr auto NanosecondsMaxInstant() {
106 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
108 // ±8.64 × 10^21 is the nanoseconds from epoch limit.
109 // 8.64 × 10^21 is 86_40000_00000_00000_00000 or 0x1d4_60162f51_6f000000.
110 // Return the BigInt digits of that number for fast BigInt comparisons.
111 if constexpr (BigInt::DigitBits
== 64) {
113 BigInt::Digit(0x1d4),
114 BigInt::Digit(0x6016'2f51'6f00'0000),
118 BigInt::Digit(0x1d4),
119 BigInt::Digit(0x6016'2f51),
120 BigInt::Digit(0x6f00'0000),
126 * IsValidEpochNanoseconds ( epochNanoseconds )
128 bool js::temporal::IsValidEpochNanoseconds(const BigInt
* epochNanoseconds
) {
130 static constexpr auto epochLimit
= NanosecondsMaxInstant();
131 return AbsoluteValueIsLessOrEqual
<epochLimit
>(epochNanoseconds
);
134 static bool IsValidEpochMicroseconds(const BigInt
* epochMicroseconds
) {
136 if (!BigInt::isInt64(epochMicroseconds
, &i
)) {
140 constexpr int64_t MicrosecondsMaxInstant
= Instant::max().toMicroseconds();
141 return -MicrosecondsMaxInstant
<= i
&& i
<= MicrosecondsMaxInstant
;
144 static bool IsValidEpochMilliseconds(double epochMilliseconds
) {
145 MOZ_ASSERT(IsInteger(epochMilliseconds
));
147 constexpr int64_t MillisecondsMaxInstant
= Instant::max().toMilliseconds();
148 return std::abs(epochMilliseconds
) <= double(MillisecondsMaxInstant
);
151 static bool IsValidEpochSeconds(double epochSeconds
) {
152 MOZ_ASSERT(IsInteger(epochSeconds
));
154 constexpr int64_t SecondsMaxInstant
= Instant::max().toSeconds();
155 return std::abs(epochSeconds
) <= double(SecondsMaxInstant
);
159 * IsValidEpochNanoseconds ( epochNanoseconds )
161 bool js::temporal::IsValidEpochInstant(const Instant
& instant
) {
162 MOZ_ASSERT(0 <= instant
.nanoseconds
&& instant
.nanoseconds
<= 999'999'999);
165 return Instant::min() <= instant
&& instant
<= Instant::max();
170 * Validates a nanoseconds amount is at most as large as the difference
171 * between two valid nanoseconds from the epoch instants.
173 bool js::temporal::IsValidInstantSpan(const InstantSpan
& span
) {
174 MOZ_ASSERT(0 <= span
.nanoseconds
&& span
.nanoseconds
<= 999'999'999);
177 return InstantSpan::min() <= span
&& span
<= InstantSpan::max();
182 * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of
185 static Int96
ToInt96(const BigInt
* ns
) {
186 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
188 auto digits
= ns
->digits();
189 if constexpr (BigInt::DigitBits
== 64) {
190 BigInt::Digit x
= 0, y
= 0;
191 switch (digits
.size()) {
201 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
204 Int96::Digits
{Int96::Digit(x
), Int96::Digit(x
>> 32), Int96::Digit(y
)},
207 BigInt::Digit x
= 0, y
= 0, z
= 0;
208 switch (digits
.size()) {
221 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
224 Int96::Digits
{Int96::Digit(x
), Int96::Digit(y
), Int96::Digit(z
)},
229 Instant
js::temporal::ToInstant(const BigInt
* epochNanoseconds
) {
230 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds
));
232 auto [seconds
, nanos
] =
233 ToInt96(epochNanoseconds
) / ToNanoseconds(TemporalUnit::Second
);
234 return {seconds
, nanos
};
237 static BigInt
* CreateBigInt(JSContext
* cx
,
238 const std::array
<uint32_t, 3>& digits
,
240 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
242 if constexpr (BigInt::DigitBits
== 64) {
243 uint64_t x
= (uint64_t(digits
[1]) << 32) | digits
[0];
244 uint64_t y
= digits
[2];
246 size_t length
= y
? 2 : x
? 1 : 0;
247 auto* result
= BigInt::createUninitialized(cx
, length
, negative
);
252 result
->setDigit(1, y
);
255 result
->setDigit(0, x
);
259 size_t length
= digits
[2] ? 3 : digits
[1] ? 2 : digits
[0] ? 1 : 0;
260 auto* result
= BigInt::createUninitialized(cx
, length
, negative
);
265 result
->setDigit(length
, digits
[length
]);
271 static auto ToBigIntDigits(uint64_t seconds
, uint32_t nanoseconds
) {
272 // Multiplies two uint32_t values and returns the lower 32-bits. The higher
273 // 32-bits are stored in |high|.
274 auto digitMul
= [](uint32_t a
, uint32_t b
, uint32_t* high
) {
275 uint64_t result
= static_cast<uint64_t>(a
) * static_cast<uint64_t>(b
);
276 *high
= result
>> 32;
277 return static_cast<uint32_t>(result
);
280 // Adds two uint32_t values and returns the result. Overflow is added to the
281 // out-param |carry|.
282 auto digitAdd
= [](uint32_t a
, uint32_t b
, uint32_t* carry
) {
283 uint32_t result
= a
+ b
;
284 *carry
+= static_cast<uint32_t>(result
< a
);
288 constexpr uint32_t secToNanos
= ToNanoseconds(TemporalUnit::Second
);
290 // uint32_t digits stored in the same order as BigInt digits, i.e. the least
291 // significant digit is stored at index zero.
292 std::array
<uint32_t, 2> multiplicand
= {uint32_t(seconds
),
293 uint32_t(seconds
>> 32)};
294 std::array
<uint32_t, 3> accumulator
= {nanoseconds
, 0, 0};
296 // This code follows the implementation of |BigInt::multiplyAccumulate()|.
301 uint32_t low
= digitMul(secToNanos
, multiplicand
[0], &high
);
303 uint32_t newCarry
= 0;
304 accumulator
[0] = digitAdd(accumulator
[0], low
, &newCarry
);
305 accumulator
[1] = digitAdd(high
, newCarry
, &carry
);
309 uint32_t low
= digitMul(secToNanos
, multiplicand
[1], &high
);
311 uint32_t newCarry
= 0;
312 accumulator
[1] = digitAdd(accumulator
[1], low
, &carry
);
313 accumulator
[2] = digitAdd(high
, carry
, &newCarry
);
314 MOZ_ASSERT(newCarry
== 0);
320 template <typename T
>
321 static BigInt
* ToBigInt(JSContext
* cx
,
322 const SecondsAndNanoseconds
<T
>& secondsAndNanoseconds
) {
323 uint64_t seconds
= std::abs(secondsAndNanoseconds
.seconds
);
324 uint32_t nanoseconds
= secondsAndNanoseconds
.nanoseconds
;
326 // Negative nanoseconds are represented as the difference to 1'000'000'000.
327 // Convert these back to their absolute value and adjust the seconds part
330 // For example the nanoseconds from the epoch value |-1n| is represented as
331 // the instant {seconds: -1, nanoseconds: 999'999'999}.
332 if (secondsAndNanoseconds
.seconds
< 0 && nanoseconds
!= 0) {
333 nanoseconds
= ToNanoseconds(TemporalUnit::Second
) - nanoseconds
;
337 auto digits
= ToBigIntDigits(seconds
, nanoseconds
);
338 return CreateBigInt(cx
, digits
, secondsAndNanoseconds
.seconds
< 0);
341 BigInt
* js::temporal::ToEpochNanoseconds(JSContext
* cx
,
342 const Instant
& instant
) {
343 MOZ_ASSERT(IsValidEpochInstant(instant
));
344 return ::ToBigInt(cx
, instant
);
348 * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
349 * microsecond, nanosecond [ , offsetNanoseconds ] )
351 Instant
js::temporal::GetUTCEpochNanoseconds(const PlainDateTime
& dateTime
) {
352 const auto& [date
, time
] = dateTime
;
355 MOZ_ASSERT(IsValidISODateTime(dateTime
));
357 // Additionally ensure the date-time value can be represented as an Instant.
358 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
361 int64_t ms
= MakeDate(dateTime
);
363 // Propagate the input range to the compiler.
365 std::clamp(time
.microsecond
* 1'000 + time
.nanosecond
, 0, 999'999);
368 return Instant::fromMilliseconds(ms
) + InstantSpan
{0, nanos
};
372 * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
373 * microsecond, nanosecond [ , offsetNanoseconds ] )
375 Instant
js::temporal::GetUTCEpochNanoseconds(
376 const PlainDateTime
& dateTime
, const InstantSpan
& offsetNanoseconds
) {
377 MOZ_ASSERT(offsetNanoseconds
.abs() <
378 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day
)));
381 auto epochNanoseconds
= GetUTCEpochNanoseconds(dateTime
);
384 return epochNanoseconds
- offsetNanoseconds
;
388 * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo )
390 static int32_t CompareEpochNanoseconds(const Instant
& epochNanosecondsOne
,
391 const Instant
& epochNanosecondsTwo
) {
393 if (epochNanosecondsOne
> epochNanosecondsTwo
) {
398 if (epochNanosecondsOne
< epochNanosecondsTwo
) {
407 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
409 InstantObject
* js::temporal::CreateTemporalInstant(JSContext
* cx
,
410 const Instant
& instant
) {
412 MOZ_ASSERT(IsValidEpochInstant(instant
));
415 auto* object
= NewBuiltinClassInstance
<InstantObject
>(cx
);
421 object
->setFixedSlot(InstantObject::SECONDS_SLOT
,
422 NumberValue(instant
.seconds
));
423 object
->setFixedSlot(InstantObject::NANOSECONDS_SLOT
,
424 Int32Value(instant
.nanoseconds
));
431 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
433 static InstantObject
* CreateTemporalInstant(JSContext
* cx
, const CallArgs
& args
,
434 Handle
<BigInt
*> epochNanoseconds
) {
436 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds
));
439 Rooted
<JSObject
*> proto(cx
);
440 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Instant
, &proto
)) {
444 auto* object
= NewObjectWithClassProto
<InstantObject
>(cx
, proto
);
450 auto instant
= ToInstant(epochNanoseconds
);
451 object
->setFixedSlot(InstantObject::SECONDS_SLOT
,
452 NumberValue(instant
.seconds
));
453 object
->setFixedSlot(InstantObject::NANOSECONDS_SLOT
,
454 Int32Value(instant
.nanoseconds
));
461 * ToTemporalInstant ( item )
463 Wrapped
<InstantObject
*> js::temporal::ToTemporalInstant(JSContext
* cx
,
464 Handle
<Value
> item
) {
466 if (item
.isObject()) {
467 JSObject
* itemObj
= &item
.toObject();
470 if (itemObj
->canUnwrapAs
<InstantObject
>()) {
475 // Steps 1.b-d and 3-6
476 Instant epochNanoseconds
;
477 if (!ToTemporalInstant(cx
, item
, &epochNanoseconds
)) {
482 return CreateTemporalInstant(cx
, epochNanoseconds
);
486 * ToTemporalInstant ( item )
488 bool js::temporal::ToTemporalInstant(JSContext
* cx
, Handle
<Value
> item
,
491 Rooted
<Value
> primitiveValue(cx
, item
);
492 if (item
.isObject()) {
493 JSObject
* itemObj
= &item
.toObject();
496 if (auto* instant
= itemObj
->maybeUnwrapIf
<InstantObject
>()) {
497 *result
= ToInstant(instant
);
502 if (auto* zonedDateTime
= itemObj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
503 *result
= ToInstant(zonedDateTime
);
508 if (!ToPrimitive(cx
, JSTYPE_STRING
, &primitiveValue
)) {
514 if (!primitiveValue
.isString()) {
515 // The value is always on the stack, so JSDVG_SEARCH_STACK can be used for
516 // better error reporting.
517 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_SEARCH_STACK
,
518 primitiveValue
, nullptr, "not a string");
521 Rooted
<JSString
*> string(cx
, primitiveValue
.toString());
524 PlainDateTime dateTime
;
526 if (!ParseTemporalInstantString(cx
, string
, &dateTime
, &offset
)) {
529 MOZ_ASSERT(std::abs(offset
) < ToNanoseconds(TemporalUnit::Day
));
531 // Step 6. (Reordered)
532 if (!ISODateTimeWithinLimits(dateTime
)) {
533 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
534 JSMSG_TEMPORAL_INSTANT_INVALID
);
539 auto epochNanoseconds
=
540 GetUTCEpochNanoseconds(dateTime
, InstantSpan::fromNanoseconds(offset
));
543 if (!IsValidEpochInstant(epochNanoseconds
)) {
544 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
545 JSMSG_TEMPORAL_INSTANT_INVALID
);
550 *result
= epochNanoseconds
;
555 * AddNormalizedTimeDurationToEpochNanoseconds ( d, epochNs )
557 Instant
js::temporal::AddNormalizedTimeDurationToEpochNanoseconds(
558 const NormalizedTimeDuration
& d
, const Instant
& epochNs
) {
559 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
560 MOZ_ASSERT(IsValidEpochInstant(epochNs
));
563 return epochNs
+ d
.to
<InstantSpan
>();
567 * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
568 * microseconds, nanoseconds )
570 bool js::temporal::AddInstant(JSContext
* cx
, const Instant
& instant
,
571 const NormalizedTimeDuration
& duration
,
573 MOZ_ASSERT(IsValidEpochInstant(instant
));
574 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
577 auto r
= AddNormalizedTimeDurationToEpochNanoseconds(duration
, instant
);
580 if (!IsValidEpochInstant(r
)) {
581 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
582 JSMSG_TEMPORAL_INSTANT_INVALID
);
592 * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
594 NormalizedTimeDuration
js::temporal::DifferenceInstant(
595 const Instant
& ns1
, const Instant
& ns2
, Increment roundingIncrement
,
596 TemporalUnit smallestUnit
, TemporalRoundingMode roundingMode
) {
597 MOZ_ASSERT(IsValidEpochInstant(ns1
));
598 MOZ_ASSERT(IsValidEpochInstant(ns2
));
599 MOZ_ASSERT(smallestUnit
> TemporalUnit::Day
);
600 MOZ_ASSERT(roundingIncrement
<=
601 MaximumTemporalDurationRoundingIncrement(smallestUnit
));
604 auto diff
= NormalizedTimeDurationFromEpochNanosecondsDifference(ns2
, ns1
);
605 MOZ_ASSERT(IsValidInstantSpan(diff
.to
<InstantSpan
>()));
608 if (smallestUnit
== TemporalUnit::Nanosecond
&&
609 roundingIncrement
== Increment
{1}) {
614 return RoundDuration(diff
, roundingIncrement
, smallestUnit
, roundingMode
);
618 * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
620 static Instant
RoundNumberToIncrementAsIfPositive(
621 const Instant
& x
, int64_t increment
, TemporalRoundingMode roundingMode
) {
622 MOZ_ASSERT(IsValidEpochInstant(x
));
623 MOZ_ASSERT(increment
> 0);
624 MOZ_ASSERT(increment
<= ToNanoseconds(TemporalUnit::Day
));
626 // This operation is equivalent to adjusting the rounding mode through
627 // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
628 auto rounded
= RoundNumberToIncrement(x
.toNanoseconds(), Int128
{increment
},
629 ToPositiveRoundingMode(roundingMode
));
630 return Instant::fromNanoseconds(rounded
);
634 * RoundTemporalInstant ( ns, increment, unit, roundingMode )
636 Instant
js::temporal::RoundTemporalInstant(const Instant
& ns
,
639 TemporalRoundingMode roundingMode
) {
640 MOZ_ASSERT(IsValidEpochInstant(ns
));
641 MOZ_ASSERT(increment
>= Increment::min());
642 MOZ_ASSERT(uint64_t(increment
.value()) <= ToNanoseconds(TemporalUnit::Day
));
643 MOZ_ASSERT(unit
> TemporalUnit::Day
);
646 int64_t toNanoseconds
= ToNanoseconds(unit
);
648 (increment
.value() * toNanoseconds
) <= ToNanoseconds(TemporalUnit::Day
),
649 "increment * toNanoseconds shouldn't overflow instant resolution");
652 return RoundNumberToIncrementAsIfPositive(
653 ns
, increment
.value() * toNanoseconds
, roundingMode
);
657 * DifferenceTemporalInstant ( operation, instant, other, options )
659 static bool DifferenceTemporalInstant(JSContext
* cx
,
660 TemporalDifference operation
,
661 const CallArgs
& args
) {
662 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
664 // Step 1. (Not applicable in our implementation.)
668 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
673 DifferenceSettings settings
;
674 if (args
.hasDefined(1)) {
675 Rooted
<JSObject
*> options(
676 cx
, RequireObjectArg(cx
, "options", ToName(operation
), args
[1]));
682 Rooted
<PlainObject
*> resolvedOptions(cx
,
683 SnapshotOwnProperties(cx
, options
));
684 if (!resolvedOptions
) {
689 if (!GetDifferenceSettings(
690 cx
, operation
, resolvedOptions
, TemporalUnitGroup::Time
,
691 TemporalUnit::Nanosecond
, TemporalUnit::Second
, &settings
)) {
697 TemporalUnit::Nanosecond
,
698 TemporalUnit::Second
,
699 TemporalRoundingMode::Trunc
,
706 DifferenceInstant(instant
, other
, settings
.roundingIncrement
,
707 settings
.smallestUnit
, settings
.roundingMode
);
710 auto balanced
= BalanceTimeDuration(difference
, settings
.largestUnit
);
713 auto duration
= balanced
.toDuration();
714 if (operation
== TemporalDifference::Since
) {
715 duration
= duration
.negate();
718 auto* obj
= CreateTemporalDuration(cx
, duration
);
723 args
.rval().setObject(*obj
);
727 enum class InstantDuration
{ Add
, Subtract
};
730 * AddDurationToOrSubtractDurationFromInstant ( operation, instant,
731 * temporalDurationLike )
733 static bool AddDurationToOrSubtractDurationFromInstant(
734 JSContext
* cx
, InstantDuration operation
, const CallArgs
& args
) {
735 auto* instant
= &args
.thisv().toObject().as
<InstantObject
>();
736 auto epochNanoseconds
= ToInstant(instant
);
738 // Step 1. (Not applicable in our implementation.)
742 if (!ToTemporalDurationRecord(cx
, args
.get(0), &duration
)) {
747 if (duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0 ||
748 duration
.days
!= 0) {
749 const char* part
= duration
.years
!= 0 ? "years"
750 : duration
.months
!= 0 ? "months"
751 : duration
.weeks
!= 0 ? "weeks"
753 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
754 JSMSG_TEMPORAL_INSTANT_BAD_DURATION
, part
);
759 if (operation
== InstantDuration::Subtract
) {
760 duration
= duration
.negate();
762 auto timeDuration
= NormalizeTimeDuration(duration
);
766 if (!AddInstant(cx
, epochNanoseconds
, timeDuration
, &ns
)) {
771 auto* result
= CreateTemporalInstant(cx
, ns
);
776 args
.rval().setObject(*result
);
781 * Temporal.Instant ( epochNanoseconds )
783 static bool InstantConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
784 CallArgs args
= CallArgsFromVp(argc
, vp
);
787 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Instant")) {
792 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
793 if (!epochNanoseconds
) {
798 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
799 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
800 JSMSG_TEMPORAL_INSTANT_INVALID
);
805 auto* result
= CreateTemporalInstant(cx
, args
, epochNanoseconds
);
810 args
.rval().setObject(*result
);
815 * Temporal.Instant.from ( item )
817 static bool Instant_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
818 CallArgs args
= CallArgsFromVp(argc
, vp
);
821 Instant epochInstant
;
822 if (!ToTemporalInstant(cx
, args
.get(0), &epochInstant
)) {
826 auto* result
= CreateTemporalInstant(cx
, epochInstant
);
830 args
.rval().setObject(*result
);
835 * Temporal.Instant.fromEpochSeconds ( epochSeconds )
837 static bool Instant_fromEpochSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
838 CallArgs args
= CallArgsFromVp(argc
, vp
);
842 if (!JS::ToNumber(cx
, args
.get(0), &epochSeconds
)) {
848 // NumberToBigInt throws a RangeError for non-integral numbers.
849 if (!IsInteger(epochSeconds
)) {
851 const char* str
= NumberToCString(&cbuf
, epochSeconds
);
853 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
854 JSMSG_TEMPORAL_INSTANT_NONINTEGER
, str
);
858 // Step 3. (Not applicable)
861 if (!IsValidEpochSeconds(epochSeconds
)) {
862 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
863 JSMSG_TEMPORAL_INSTANT_INVALID
);
868 int64_t seconds
= mozilla::AssertedCast
<int64_t>(epochSeconds
);
869 auto* result
= CreateTemporalInstant(cx
, Instant::fromSeconds(seconds
));
873 args
.rval().setObject(*result
);
878 * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )
880 static bool Instant_fromEpochMilliseconds(JSContext
* cx
, unsigned argc
,
882 CallArgs args
= CallArgsFromVp(argc
, vp
);
885 double epochMilliseconds
;
886 if (!JS::ToNumber(cx
, args
.get(0), &epochMilliseconds
)) {
892 // NumberToBigInt throws a RangeError for non-integral numbers.
893 if (!IsInteger(epochMilliseconds
)) {
895 const char* str
= NumberToCString(&cbuf
, epochMilliseconds
);
897 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
898 JSMSG_TEMPORAL_INSTANT_NONINTEGER
, str
);
902 // Step 3. (Not applicable)
905 if (!IsValidEpochMilliseconds(epochMilliseconds
)) {
906 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
907 JSMSG_TEMPORAL_INSTANT_INVALID
);
912 int64_t milliseconds
= mozilla::AssertedCast
<int64_t>(epochMilliseconds
);
914 CreateTemporalInstant(cx
, Instant::fromMilliseconds(milliseconds
));
918 args
.rval().setObject(*result
);
923 * Temporal.Instant.fromEpochMicroseconds ( epochMicroseconds )
925 static bool Instant_fromEpochMicroseconds(JSContext
* cx
, unsigned argc
,
927 CallArgs args
= CallArgsFromVp(argc
, vp
);
930 Rooted
<BigInt
*> epochMicroseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
931 if (!epochMicroseconds
) {
935 // Step 2. (Not applicable)
938 if (!IsValidEpochMicroseconds(epochMicroseconds
)) {
939 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
940 JSMSG_TEMPORAL_INSTANT_INVALID
);
945 MOZ_ALWAYS_TRUE(BigInt::isInt64(epochMicroseconds
, &i
));
948 auto* result
= CreateTemporalInstant(cx
, Instant::fromMicroseconds(i
));
952 args
.rval().setObject(*result
);
957 * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )
959 static bool Instant_fromEpochNanoseconds(JSContext
* cx
, unsigned argc
,
961 CallArgs args
= CallArgsFromVp(argc
, vp
);
964 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
965 if (!epochNanoseconds
) {
970 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
971 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
972 JSMSG_TEMPORAL_INSTANT_INVALID
);
977 auto* result
= CreateTemporalInstant(cx
, ToInstant(epochNanoseconds
));
981 args
.rval().setObject(*result
);
986 * Temporal.Instant.compare ( one, two )
988 static bool Instant_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
989 CallArgs args
= CallArgsFromVp(argc
, vp
);
993 if (!ToTemporalInstant(cx
, args
.get(0), &one
)) {
999 if (!ToTemporalInstant(cx
, args
.get(1), &two
)) {
1004 args
.rval().setInt32(CompareEpochNanoseconds(one
, two
));
1009 * get Temporal.Instant.prototype.epochSeconds
1011 static bool Instant_epochSeconds(JSContext
* cx
, const CallArgs
& args
) {
1013 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1016 args
.rval().setNumber(instant
.seconds
);
1021 * get Temporal.Instant.prototype.epochSeconds
1023 static bool Instant_epochSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1025 CallArgs args
= CallArgsFromVp(argc
, vp
);
1026 return CallNonGenericMethod
<IsInstant
, Instant_epochSeconds
>(cx
, args
);
1030 * get Temporal.Instant.prototype.epochMilliseconds
1032 static bool Instant_epochMilliseconds(JSContext
* cx
, const CallArgs
& args
) {
1034 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1037 args
.rval().setNumber(instant
.floorToMilliseconds());
1042 * get Temporal.Instant.prototype.epochMilliseconds
1044 static bool Instant_epochMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1046 CallArgs args
= CallArgsFromVp(argc
, vp
);
1047 return CallNonGenericMethod
<IsInstant
, Instant_epochMilliseconds
>(cx
, args
);
1051 * get Temporal.Instant.prototype.epochMicroseconds
1053 static bool Instant_epochMicroseconds(JSContext
* cx
, const CallArgs
& args
) {
1055 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1058 auto* microseconds
=
1059 BigInt::createFromInt64(cx
, instant
.floorToMicroseconds());
1060 if (!microseconds
) {
1065 args
.rval().setBigInt(microseconds
);
1070 * get Temporal.Instant.prototype.epochMicroseconds
1072 static bool Instant_epochMicroseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1074 CallArgs args
= CallArgsFromVp(argc
, vp
);
1075 return CallNonGenericMethod
<IsInstant
, Instant_epochMicroseconds
>(cx
, args
);
1079 * get Temporal.Instant.prototype.epochNanoseconds
1081 static bool Instant_epochNanoseconds(JSContext
* cx
, const CallArgs
& args
) {
1083 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1084 auto* nanoseconds
= ToEpochNanoseconds(cx
, instant
);
1090 args
.rval().setBigInt(nanoseconds
);
1095 * get Temporal.Instant.prototype.epochNanoseconds
1097 static bool Instant_epochNanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1099 CallArgs args
= CallArgsFromVp(argc
, vp
);
1100 return CallNonGenericMethod
<IsInstant
, Instant_epochNanoseconds
>(cx
, args
);
1104 * Temporal.Instant.prototype.add ( temporalDurationLike )
1106 static bool Instant_add(JSContext
* cx
, const CallArgs
& args
) {
1107 return AddDurationToOrSubtractDurationFromInstant(cx
, InstantDuration::Add
,
1112 * Temporal.Instant.prototype.add ( temporalDurationLike )
1114 static bool Instant_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1116 CallArgs args
= CallArgsFromVp(argc
, vp
);
1117 return CallNonGenericMethod
<IsInstant
, Instant_add
>(cx
, args
);
1121 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
1123 static bool Instant_subtract(JSContext
* cx
, const CallArgs
& args
) {
1124 return AddDurationToOrSubtractDurationFromInstant(
1125 cx
, InstantDuration::Subtract
, args
);
1129 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
1131 static bool Instant_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1133 CallArgs args
= CallArgsFromVp(argc
, vp
);
1134 return CallNonGenericMethod
<IsInstant
, Instant_subtract
>(cx
, args
);
1138 * Temporal.Instant.prototype.until ( other [ , options ] )
1140 static bool Instant_until(JSContext
* cx
, const CallArgs
& args
) {
1141 return DifferenceTemporalInstant(cx
, TemporalDifference::Until
, args
);
1145 * Temporal.Instant.prototype.until ( other [ , options ] )
1147 static bool Instant_until(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1149 CallArgs args
= CallArgsFromVp(argc
, vp
);
1150 return CallNonGenericMethod
<IsInstant
, Instant_until
>(cx
, args
);
1154 * Temporal.Instant.prototype.since ( other [ , options ] )
1156 static bool Instant_since(JSContext
* cx
, const CallArgs
& args
) {
1157 return DifferenceTemporalInstant(cx
, TemporalDifference::Since
, args
);
1161 * Temporal.Instant.prototype.since ( other [ , options ] )
1163 static bool Instant_since(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1165 CallArgs args
= CallArgsFromVp(argc
, vp
);
1166 return CallNonGenericMethod
<IsInstant
, Instant_since
>(cx
, args
);
1170 * Temporal.Instant.prototype.round ( roundTo )
1172 static bool Instant_round(JSContext
* cx
, const CallArgs
& args
) {
1173 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1176 auto smallestUnit
= TemporalUnit::Auto
;
1177 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
1178 auto roundingIncrement
= Increment
{1};
1179 if (args
.get(0).isString()) {
1180 // Steps 4 and 6-8. (Not applicable in our implementation.)
1183 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
1184 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
1185 TemporalUnitGroup::Time
, &smallestUnit
)) {
1189 // Steps 10-16. (Not applicable in our implementation.)
1192 Rooted
<JSObject
*> options(
1193 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
1199 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
1204 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1209 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
1210 TemporalUnitGroup::Time
, &smallestUnit
)) {
1213 if (smallestUnit
== TemporalUnit::Auto
) {
1214 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1215 JSMSG_TEMPORAL_MISSING_OPTION
, "smallestUnit");
1220 int64_t maximum
= UnitsPerDay(smallestUnit
);
1223 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
1230 auto roundedNs
= RoundTemporalInstant(instant
, roundingIncrement
,
1231 smallestUnit
, roundingMode
);
1234 auto* result
= CreateTemporalInstant(cx
, roundedNs
);
1238 args
.rval().setObject(*result
);
1243 * Temporal.Instant.prototype.round ( options )
1245 static bool Instant_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1247 CallArgs args
= CallArgsFromVp(argc
, vp
);
1248 return CallNonGenericMethod
<IsInstant
, Instant_round
>(cx
, args
);
1252 * Temporal.Instant.prototype.equals ( other )
1254 static bool Instant_equals(JSContext
* cx
, const CallArgs
& args
) {
1255 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1259 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
1264 args
.rval().setBoolean(instant
== other
);
1269 * Temporal.Instant.prototype.equals ( other )
1271 static bool Instant_equals(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1273 CallArgs args
= CallArgsFromVp(argc
, vp
);
1274 return CallNonGenericMethod
<IsInstant
, Instant_equals
>(cx
, args
);
1278 * Temporal.Instant.prototype.toString ( [ options ] )
1280 static bool Instant_toString(JSContext
* cx
, const CallArgs
& args
) {
1281 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1283 Rooted
<TimeZoneValue
> timeZone(cx
);
1284 auto roundingMode
= TemporalRoundingMode::Trunc
;
1285 SecondsStringPrecision precision
= {Precision::Auto(),
1286 TemporalUnit::Nanosecond
, Increment
{1}};
1287 if (args
.hasDefined(0)) {
1289 Rooted
<JSObject
*> options(
1290 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
1296 auto digits
= Precision::Auto();
1297 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
1302 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1307 auto smallestUnit
= TemporalUnit::Auto
;
1308 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
1309 TemporalUnitGroup::Time
, &smallestUnit
)) {
1314 if (smallestUnit
== TemporalUnit::Hour
) {
1315 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1316 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
, "hour",
1322 Rooted
<Value
> value(cx
);
1323 if (!GetProperty(cx
, options
, options
, cx
->names().timeZone
, &value
)) {
1328 if (!value
.isUndefined()) {
1329 if (!ToTemporalTimeZone(cx
, value
, &timeZone
)) {
1335 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
1339 auto ns
= RoundTemporalInstant(instant
, precision
.increment
, precision
.unit
,
1343 Rooted
<InstantObject
*> roundedInstant(cx
, CreateTemporalInstant(cx
, ns
));
1344 if (!roundedInstant
) {
1349 JSString
* str
= TemporalInstantToString(cx
, roundedInstant
, timeZone
,
1350 precision
.precision
);
1355 args
.rval().setString(str
);
1360 * Temporal.Instant.prototype.toString ( [ options ] )
1362 static bool Instant_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1364 CallArgs args
= CallArgsFromVp(argc
, vp
);
1365 return CallNonGenericMethod
<IsInstant
, Instant_toString
>(cx
, args
);
1369 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1371 static bool Instant_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
1372 Rooted
<InstantObject
*> instant(cx
,
1373 &args
.thisv().toObject().as
<InstantObject
>());
1376 Rooted
<TimeZoneValue
> timeZone(cx
);
1378 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1383 args
.rval().setString(str
);
1388 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1390 static bool Instant_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1392 CallArgs args
= CallArgsFromVp(argc
, vp
);
1393 return CallNonGenericMethod
<IsInstant
, Instant_toLocaleString
>(cx
, args
);
1397 * Temporal.Instant.prototype.toJSON ( )
1399 static bool Instant_toJSON(JSContext
* cx
, const CallArgs
& args
) {
1400 Rooted
<InstantObject
*> instant(cx
,
1401 &args
.thisv().toObject().as
<InstantObject
>());
1404 Rooted
<TimeZoneValue
> timeZone(cx
);
1406 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1411 args
.rval().setString(str
);
1416 * Temporal.Instant.prototype.toJSON ( )
1418 static bool Instant_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1420 CallArgs args
= CallArgsFromVp(argc
, vp
);
1421 return CallNonGenericMethod
<IsInstant
, Instant_toJSON
>(cx
, args
);
1425 * Temporal.Instant.prototype.valueOf ( )
1427 static bool Instant_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1428 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
1429 "Instant", "primitive type");
1434 * Temporal.Instant.prototype.toZonedDateTime ( item )
1436 static bool Instant_toZonedDateTime(JSContext
* cx
, const CallArgs
& args
) {
1437 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1440 Rooted
<JSObject
*> item(
1441 cx
, RequireObjectArg(cx
, "item", "toZonedDateTime", args
.get(0)));
1447 Rooted
<Value
> calendarLike(cx
);
1448 if (!GetProperty(cx
, item
, item
, cx
->names().calendar
, &calendarLike
)) {
1453 if (calendarLike
.isUndefined()) {
1454 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1455 JSMSG_TEMPORAL_MISSING_PROPERTY
, "calendar");
1460 Rooted
<CalendarValue
> calendar(cx
);
1461 if (!ToTemporalCalendar(cx
, calendarLike
, &calendar
)) {
1466 Rooted
<Value
> timeZoneLike(cx
);
1467 if (!GetProperty(cx
, item
, item
, cx
->names().timeZone
, &timeZoneLike
)) {
1472 if (timeZoneLike
.isUndefined()) {
1473 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1474 JSMSG_TEMPORAL_MISSING_PROPERTY
, "timeZone");
1479 Rooted
<TimeZoneValue
> timeZone(cx
);
1480 if (!ToTemporalTimeZone(cx
, timeZoneLike
, &timeZone
)) {
1485 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1490 args
.rval().setObject(*result
);
1495 * Temporal.Instant.prototype.toZonedDateTime ( item )
1497 static bool Instant_toZonedDateTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1499 CallArgs args
= CallArgsFromVp(argc
, vp
);
1500 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTime
>(cx
, args
);
1504 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1506 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, const CallArgs
& args
) {
1507 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1510 Rooted
<TimeZoneValue
> timeZone(cx
);
1511 if (!ToTemporalTimeZone(cx
, args
.get(0), &timeZone
)) {
1516 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(cx
->names().iso8601
));
1517 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1522 args
.rval().setObject(*result
);
1527 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1529 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, unsigned argc
,
1532 CallArgs args
= CallArgsFromVp(argc
, vp
);
1533 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTimeISO
>(cx
, args
);
1536 const JSClass
InstantObject::class_
= {
1538 JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT
) |
1539 JSCLASS_HAS_CACHED_PROTO(JSProto_Instant
),
1541 &InstantObject::classSpec_
,
1544 const JSClass
& InstantObject::protoClass_
= PlainObject::class_
;
1546 static const JSFunctionSpec Instant_methods
[] = {
1547 JS_FN("from", Instant_from
, 1, 0),
1548 JS_FN("fromEpochSeconds", Instant_fromEpochSeconds
, 1, 0),
1549 JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds
, 1, 0),
1550 JS_FN("fromEpochMicroseconds", Instant_fromEpochMicroseconds
, 1, 0),
1551 JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds
, 1, 0),
1552 JS_FN("compare", Instant_compare
, 2, 0),
1556 static const JSFunctionSpec Instant_prototype_methods
[] = {
1557 JS_FN("add", Instant_add
, 1, 0),
1558 JS_FN("subtract", Instant_subtract
, 1, 0),
1559 JS_FN("until", Instant_until
, 1, 0),
1560 JS_FN("since", Instant_since
, 1, 0),
1561 JS_FN("round", Instant_round
, 1, 0),
1562 JS_FN("equals", Instant_equals
, 1, 0),
1563 JS_FN("toString", Instant_toString
, 0, 0),
1564 JS_FN("toLocaleString", Instant_toLocaleString
, 0, 0),
1565 JS_FN("toJSON", Instant_toJSON
, 0, 0),
1566 JS_FN("valueOf", Instant_valueOf
, 0, 0),
1567 JS_FN("toZonedDateTime", Instant_toZonedDateTime
, 1, 0),
1568 JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO
, 1, 0),
1572 static const JSPropertySpec Instant_prototype_properties
[] = {
1573 JS_PSG("epochSeconds", Instant_epochSeconds
, 0),
1574 JS_PSG("epochMilliseconds", Instant_epochMilliseconds
, 0),
1575 JS_PSG("epochMicroseconds", Instant_epochMicroseconds
, 0),
1576 JS_PSG("epochNanoseconds", Instant_epochNanoseconds
, 0),
1577 JS_STRING_SYM_PS(toStringTag
, "Temporal.Instant", JSPROP_READONLY
),
1581 const ClassSpec
InstantObject::classSpec_
= {
1582 GenericCreateConstructor
<InstantConstructor
, 1, gc::AllocKind::FUNCTION
>,
1583 GenericCreatePrototype
<InstantObject
>,
1586 Instant_prototype_methods
,
1587 Instant_prototype_properties
,
1589 ClassSpec::DontDefineConstructor
,