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/CheckedInt.h"
11 #include "mozilla/FloatingPoint.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/Span.h"
25 #include "NamespaceImports.h"
27 #include "builtin/temporal/Calendar.h"
28 #include "builtin/temporal/Duration.h"
29 #include "builtin/temporal/Int96.h"
30 #include "builtin/temporal/PlainDateTime.h"
31 #include "builtin/temporal/Temporal.h"
32 #include "builtin/temporal/TemporalParser.h"
33 #include "builtin/temporal/TemporalRoundingMode.h"
34 #include "builtin/temporal/TemporalTypes.h"
35 #include "builtin/temporal/TemporalUnit.h"
36 #include "builtin/temporal/TimeZone.h"
37 #include "builtin/temporal/ToString.h"
38 #include "builtin/temporal/Wrapped.h"
39 #include "builtin/temporal/ZonedDateTime.h"
40 #include "gc/AllocKind.h"
41 #include "gc/Barrier.h"
42 #include "js/CallArgs.h"
43 #include "js/CallNonGenericMethod.h"
45 #include "js/Conversions.h"
46 #include "js/ErrorReport.h"
47 #include "js/friend/ErrorMessages.h"
48 #include "js/PropertyDescriptor.h"
49 #include "js/PropertySpec.h"
50 #include "js/RootingAPI.h"
51 #include "js/TypeDecls.h"
53 #include "vm/BigIntType.h"
54 #include "vm/BytecodeUtil.h"
55 #include "vm/GlobalObject.h"
56 #include "vm/JSAtomState.h"
57 #include "vm/JSContext.h"
58 #include "vm/JSObject.h"
59 #include "vm/PlainObject.h"
60 #include "vm/StringType.h"
62 #include "vm/JSObject-inl.h"
63 #include "vm/NativeObject-inl.h"
64 #include "vm/ObjectOperations-inl.h"
67 using namespace js::temporal
;
69 static inline bool IsInstant(Handle
<Value
> v
) {
70 return v
.isObject() && v
.toObject().is
<InstantObject
>();
74 * Check if the absolute value is less-or-equal to the given limit.
76 template <const auto& digits
>
77 static bool AbsoluteValueIsLessOrEqual(const BigInt
* bigInt
) {
78 size_t length
= bigInt
->digitLength();
80 // Fewer digits than the limit, so definitely in range.
81 if (length
< std::size(digits
)) {
85 // More digits than the limit, so definitely out of range.
86 if (length
> std::size(digits
)) {
90 // Compare each digit when the input has the same number of digits.
91 size_t index
= std::size(digits
);
92 for (auto digit
: digits
) {
93 auto d
= bigInt
->digit(--index
);
104 static constexpr auto NanosecondsMaxInstant() {
105 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
107 // ±8.64 × 10^21 is the nanoseconds from epoch limit.
108 // 8.64 × 10^21 is 86_40000_00000_00000_00000 or 0x1d4_60162f51_6f000000.
109 // Return the BigInt digits of that number for fast BigInt comparisons.
110 if constexpr (BigInt::DigitBits
== 64) {
112 BigInt::Digit(0x1d4),
113 BigInt::Digit(0x6016'2f51'6f00'0000),
117 BigInt::Digit(0x1d4),
118 BigInt::Digit(0x6016'2f51),
119 BigInt::Digit(0x6f00'0000),
125 * IsValidEpochNanoseconds ( epochNanoseconds )
127 bool js::temporal::IsValidEpochNanoseconds(const BigInt
* epochNanoseconds
) {
129 static constexpr auto epochLimit
= NanosecondsMaxInstant();
130 return AbsoluteValueIsLessOrEqual
<epochLimit
>(epochNanoseconds
);
133 static bool IsValidEpochMicroseconds(const BigInt
* epochMicroseconds
) {
135 if (!BigInt::isInt64(epochMicroseconds
, &i
)) {
139 constexpr int64_t MicrosecondsMaxInstant
= Instant::max().toMicroseconds();
140 return -MicrosecondsMaxInstant
<= i
&& i
<= MicrosecondsMaxInstant
;
143 static bool IsValidEpochMilliseconds(double epochMilliseconds
) {
144 MOZ_ASSERT(IsInteger(epochMilliseconds
));
146 constexpr int64_t MillisecondsMaxInstant
= Instant::max().toMilliseconds();
147 return std::abs(epochMilliseconds
) <= double(MillisecondsMaxInstant
);
150 static bool IsValidEpochSeconds(double epochSeconds
) {
151 MOZ_ASSERT(IsInteger(epochSeconds
));
153 constexpr int64_t SecondsMaxInstant
= Instant::max().toSeconds();
154 return std::abs(epochSeconds
) <= double(SecondsMaxInstant
);
158 * IsValidEpochNanoseconds ( epochNanoseconds )
160 bool js::temporal::IsValidEpochInstant(const Instant
& instant
) {
161 MOZ_ASSERT(0 <= instant
.nanoseconds
&& instant
.nanoseconds
<= 999'999'999);
164 return Instant::min() <= instant
&& instant
<= Instant::max();
169 * Validates a nanoseconds amount is at most as large as the difference
170 * between two valid nanoseconds from the epoch instants.
172 bool js::temporal::IsValidInstantSpan(const InstantSpan
& span
) {
173 MOZ_ASSERT(0 <= span
.nanoseconds
&& span
.nanoseconds
<= 999'999'999);
176 return InstantSpan::min() <= span
&& span
<= InstantSpan::max();
181 * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of
184 static Int96
ToInt96(const BigInt
* ns
) {
185 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
187 auto digits
= ns
->digits();
188 if constexpr (BigInt::DigitBits
== 64) {
189 BigInt::Digit x
= 0, y
= 0;
190 switch (digits
.size()) {
200 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
203 Int96::Digits
{Int96::Digit(x
), Int96::Digit(x
>> 32), Int96::Digit(y
)},
206 BigInt::Digit x
= 0, y
= 0, z
= 0;
207 switch (digits
.size()) {
220 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
223 Int96::Digits
{Int96::Digit(x
), Int96::Digit(y
), Int96::Digit(z
)},
228 Instant
js::temporal::ToInstant(const BigInt
* epochNanoseconds
) {
229 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds
));
231 auto [seconds
, nanos
] =
232 ToInt96(epochNanoseconds
) / ToNanoseconds(TemporalUnit::Second
);
233 return {seconds
, nanos
};
236 static BigInt
* CreateBigInt(JSContext
* cx
,
237 const std::array
<uint32_t, 3>& digits
,
239 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
241 if constexpr (BigInt::DigitBits
== 64) {
242 uint64_t x
= (uint64_t(digits
[1]) << 32) | digits
[0];
243 uint64_t y
= digits
[2];
245 size_t length
= y
? 2 : x
? 1 : 0;
246 auto* result
= BigInt::createUninitialized(cx
, length
, negative
);
251 result
->setDigit(1, y
);
254 result
->setDigit(0, x
);
258 size_t length
= digits
[2] ? 3 : digits
[1] ? 2 : digits
[0] ? 1 : 0;
259 auto* result
= BigInt::createUninitialized(cx
, length
, negative
);
264 result
->setDigit(length
, digits
[length
]);
270 static auto ToBigIntDigits(uint64_t seconds
, uint32_t nanoseconds
) {
271 // Multiplies two uint32_t values and returns the lower 32-bits. The higher
272 // 32-bits are stored in |high|.
273 auto digitMul
= [](uint32_t a
, uint32_t b
, uint32_t* high
) {
274 uint64_t result
= static_cast<uint64_t>(a
) * static_cast<uint64_t>(b
);
275 *high
= result
>> 32;
276 return static_cast<uint32_t>(result
);
279 // Adds two uint32_t values and returns the result. Overflow is added to the
280 // out-param |carry|.
281 auto digitAdd
= [](uint32_t a
, uint32_t b
, uint32_t* carry
) {
282 uint32_t result
= a
+ b
;
283 *carry
+= static_cast<uint32_t>(result
< a
);
287 constexpr uint32_t secToNanos
= ToNanoseconds(TemporalUnit::Second
);
289 // uint32_t digits stored in the same order as BigInt digits, i.e. the least
290 // significant digit is stored at index zero.
291 std::array
<uint32_t, 2> multiplicand
= {uint32_t(seconds
),
292 uint32_t(seconds
>> 32)};
293 std::array
<uint32_t, 3> accumulator
= {nanoseconds
, 0, 0};
295 // This code follows the implementation of |BigInt::multiplyAccumulate()|.
300 uint32_t low
= digitMul(secToNanos
, multiplicand
[0], &high
);
302 uint32_t newCarry
= 0;
303 accumulator
[0] = digitAdd(accumulator
[0], low
, &newCarry
);
304 accumulator
[1] = digitAdd(high
, newCarry
, &carry
);
308 uint32_t low
= digitMul(secToNanos
, multiplicand
[1], &high
);
310 uint32_t newCarry
= 0;
311 accumulator
[1] = digitAdd(accumulator
[1], low
, &carry
);
312 accumulator
[2] = digitAdd(high
, carry
, &newCarry
);
313 MOZ_ASSERT(newCarry
== 0);
319 template <typename T
>
320 static BigInt
* ToBigInt(JSContext
* cx
,
321 const SecondsAndNanoseconds
<T
>& secondsAndNanoseconds
) {
322 uint64_t seconds
= std::abs(secondsAndNanoseconds
.seconds
);
323 uint32_t nanoseconds
= secondsAndNanoseconds
.nanoseconds
;
325 // Negative nanoseconds are represented as the difference to 1'000'000'000.
326 // Convert these back to their absolute value and adjust the seconds part
329 // For example the nanoseconds from the epoch value |-1n| is represented as
330 // the instant {seconds: -1, nanoseconds: 999'999'999}.
331 if (secondsAndNanoseconds
.seconds
< 0 && nanoseconds
!= 0) {
332 nanoseconds
= ToNanoseconds(TemporalUnit::Second
) - nanoseconds
;
336 auto digits
= ToBigIntDigits(seconds
, nanoseconds
);
337 return CreateBigInt(cx
, digits
, secondsAndNanoseconds
.seconds
< 0);
340 BigInt
* js::temporal::ToEpochNanoseconds(JSContext
* cx
,
341 const Instant
& instant
) {
342 MOZ_ASSERT(IsValidEpochInstant(instant
));
343 return ::ToBigInt(cx
, instant
);
347 * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
348 * microsecond, nanosecond [ , offsetNanoseconds ] )
350 Instant
js::temporal::GetUTCEpochNanoseconds(const PlainDateTime
& dateTime
) {
351 auto& [date
, time
] = dateTime
;
354 MOZ_ASSERT(IsValidISODateTime(dateTime
));
356 // Additionally ensure the date-time value can be represented as an Instant.
357 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
360 int64_t ms
= MakeDate(dateTime
);
362 // Propagate the input range to the compiler.
364 std::clamp(time
.microsecond
* 1'000 + time
.nanosecond
, 0, 999'999);
367 return Instant::fromMilliseconds(ms
) + InstantSpan
{0, nanos
};
371 * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
372 * microsecond, nanosecond [ , offsetNanoseconds ] )
374 Instant
js::temporal::GetUTCEpochNanoseconds(
375 const PlainDateTime
& dateTime
, const InstantSpan
& offsetNanoseconds
) {
376 MOZ_ASSERT(offsetNanoseconds
.abs() <
377 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day
)));
380 auto epochNanoseconds
= GetUTCEpochNanoseconds(dateTime
);
383 return epochNanoseconds
- offsetNanoseconds
;
387 * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo )
389 static int32_t CompareEpochNanoseconds(const Instant
& epochNanosecondsOne
,
390 const Instant
& epochNanosecondsTwo
) {
392 if (epochNanosecondsOne
> epochNanosecondsTwo
) {
397 if (epochNanosecondsOne
< epochNanosecondsTwo
) {
406 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
408 InstantObject
* js::temporal::CreateTemporalInstant(JSContext
* cx
,
409 const Instant
& instant
) {
411 MOZ_ASSERT(IsValidEpochInstant(instant
));
414 auto* object
= NewBuiltinClassInstance
<InstantObject
>(cx
);
420 object
->setFixedSlot(InstantObject::SECONDS_SLOT
,
421 NumberValue(instant
.seconds
));
422 object
->setFixedSlot(InstantObject::NANOSECONDS_SLOT
,
423 Int32Value(instant
.nanoseconds
));
430 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
432 static InstantObject
* CreateTemporalInstant(JSContext
* cx
, const CallArgs
& args
,
433 Handle
<BigInt
*> epochNanoseconds
) {
435 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds
));
438 Rooted
<JSObject
*> proto(cx
);
439 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Instant
, &proto
)) {
443 auto* object
= NewObjectWithClassProto
<InstantObject
>(cx
, proto
);
449 auto instant
= ToInstant(epochNanoseconds
);
450 object
->setFixedSlot(InstantObject::SECONDS_SLOT
,
451 NumberValue(instant
.seconds
));
452 object
->setFixedSlot(InstantObject::NANOSECONDS_SLOT
,
453 Int32Value(instant
.nanoseconds
));
460 * ToTemporalInstant ( item )
462 Wrapped
<InstantObject
*> js::temporal::ToTemporalInstant(JSContext
* cx
,
463 Handle
<Value
> item
) {
465 if (item
.isObject()) {
466 JSObject
* itemObj
= &item
.toObject();
469 if (itemObj
->canUnwrapAs
<InstantObject
>()) {
474 // Steps 1.b-d and 3-6
475 Instant epochNanoseconds
;
476 if (!ToTemporalInstant(cx
, item
, &epochNanoseconds
)) {
481 return CreateTemporalInstant(cx
, epochNanoseconds
);
485 * ToTemporalInstant ( item )
487 bool js::temporal::ToTemporalInstant(JSContext
* cx
, Handle
<Value
> item
,
490 Rooted
<Value
> primitiveValue(cx
, item
);
491 if (item
.isObject()) {
492 JSObject
* itemObj
= &item
.toObject();
495 if (auto* instant
= itemObj
->maybeUnwrapIf
<InstantObject
>()) {
496 *result
= ToInstant(instant
);
501 if (auto* zonedDateTime
= itemObj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
502 *result
= ToInstant(zonedDateTime
);
507 if (!ToPrimitive(cx
, JSTYPE_STRING
, &primitiveValue
)) {
513 if (!primitiveValue
.isString()) {
514 // The value is always on the stack, so JSDVG_SEARCH_STACK can be used for
515 // better error reporting.
516 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_SEARCH_STACK
,
517 primitiveValue
, nullptr, "not a string");
520 Rooted
<JSString
*> string(cx
, primitiveValue
.toString());
523 PlainDateTime dateTime
;
525 if (!ParseTemporalInstantString(cx
, string
, &dateTime
, &offset
)) {
528 MOZ_ASSERT(std::abs(offset
) < ToNanoseconds(TemporalUnit::Day
));
530 // Step 6. (Reordered)
531 if (!ISODateTimeWithinLimits(dateTime
)) {
532 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
533 JSMSG_TEMPORAL_INSTANT_INVALID
);
538 auto epochNanoseconds
=
539 GetUTCEpochNanoseconds(dateTime
, InstantSpan::fromNanoseconds(offset
));
542 if (!IsValidEpochInstant(epochNanoseconds
)) {
543 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
544 JSMSG_TEMPORAL_INSTANT_INVALID
);
549 *result
= epochNanoseconds
;
554 * AddNormalizedTimeDurationToEpochNanoseconds ( d, epochNs )
556 Instant
js::temporal::AddNormalizedTimeDurationToEpochNanoseconds(
557 const NormalizedTimeDuration
& d
, const Instant
& epochNs
) {
558 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
559 MOZ_ASSERT(IsValidEpochInstant(epochNs
));
562 return epochNs
+ d
.to
<InstantSpan
>();
566 * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
567 * microseconds, nanoseconds )
569 bool js::temporal::AddInstant(JSContext
* cx
, const Instant
& instant
,
570 const NormalizedTimeDuration
& duration
,
572 MOZ_ASSERT(IsValidEpochInstant(instant
));
573 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
576 auto r
= AddNormalizedTimeDurationToEpochNanoseconds(duration
, instant
);
579 if (!IsValidEpochInstant(r
)) {
580 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
581 JSMSG_TEMPORAL_INSTANT_INVALID
);
591 * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
593 NormalizedTimeDuration
js::temporal::DifferenceInstant(
594 const Instant
& ns1
, const Instant
& ns2
, Increment roundingIncrement
,
595 TemporalUnit smallestUnit
, TemporalRoundingMode roundingMode
) {
596 MOZ_ASSERT(IsValidEpochInstant(ns1
));
597 MOZ_ASSERT(IsValidEpochInstant(ns2
));
598 MOZ_ASSERT(smallestUnit
> TemporalUnit::Day
);
599 MOZ_ASSERT(roundingIncrement
<=
600 MaximumTemporalDurationRoundingIncrement(smallestUnit
));
603 auto diff
= NormalizedTimeDurationFromEpochNanosecondsDifference(ns2
, ns1
);
604 MOZ_ASSERT(IsValidInstantSpan(diff
.to
<InstantSpan
>()));
607 if (smallestUnit
== TemporalUnit::Nanosecond
&&
608 roundingIncrement
== Increment
{1}) {
613 return RoundDuration(diff
, roundingIncrement
, smallestUnit
, roundingMode
);
617 * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
619 static Instant
RoundNumberToIncrementAsIfPositive(
620 const Instant
& x
, int64_t increment
, TemporalRoundingMode roundingMode
) {
621 MOZ_ASSERT(IsValidEpochInstant(x
));
622 MOZ_ASSERT(increment
> 0);
623 MOZ_ASSERT(increment
<= ToNanoseconds(TemporalUnit::Day
));
625 // This operation is equivalent to adjusting the rounding mode through
626 // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
628 RoundNumberToIncrement(x
.toTotalNanoseconds(), 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 auto* result
= CreateTemporalInstant(cx
, Instant::fromSeconds(epochSeconds
));
872 args
.rval().setObject(*result
);
877 * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )
879 static bool Instant_fromEpochMilliseconds(JSContext
* cx
, unsigned argc
,
881 CallArgs args
= CallArgsFromVp(argc
, vp
);
884 double epochMilliseconds
;
885 if (!JS::ToNumber(cx
, args
.get(0), &epochMilliseconds
)) {
891 // NumberToBigInt throws a RangeError for non-integral numbers.
892 if (!IsInteger(epochMilliseconds
)) {
894 const char* str
= NumberToCString(&cbuf
, epochMilliseconds
);
896 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
897 JSMSG_TEMPORAL_INSTANT_NONINTEGER
, str
);
901 // Step 3. (Not applicable)
904 if (!IsValidEpochMilliseconds(epochMilliseconds
)) {
905 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
906 JSMSG_TEMPORAL_INSTANT_INVALID
);
912 CreateTemporalInstant(cx
, Instant::fromMilliseconds(epochMilliseconds
));
916 args
.rval().setObject(*result
);
921 * Temporal.Instant.fromEpochMicroseconds ( epochMicroseconds )
923 static bool Instant_fromEpochMicroseconds(JSContext
* cx
, unsigned argc
,
925 CallArgs args
= CallArgsFromVp(argc
, vp
);
928 Rooted
<BigInt
*> epochMicroseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
929 if (!epochMicroseconds
) {
933 // Step 2. (Not applicable)
936 if (!IsValidEpochMicroseconds(epochMicroseconds
)) {
937 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
938 JSMSG_TEMPORAL_INSTANT_INVALID
);
943 MOZ_ALWAYS_TRUE(BigInt::isInt64(epochMicroseconds
, &i
));
946 auto* result
= CreateTemporalInstant(cx
, Instant::fromMicroseconds(i
));
950 args
.rval().setObject(*result
);
955 * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )
957 static bool Instant_fromEpochNanoseconds(JSContext
* cx
, unsigned argc
,
959 CallArgs args
= CallArgsFromVp(argc
, vp
);
962 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
963 if (!epochNanoseconds
) {
968 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
969 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
970 JSMSG_TEMPORAL_INSTANT_INVALID
);
975 auto* result
= CreateTemporalInstant(cx
, ToInstant(epochNanoseconds
));
979 args
.rval().setObject(*result
);
984 * Temporal.Instant.compare ( one, two )
986 static bool Instant_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
987 CallArgs args
= CallArgsFromVp(argc
, vp
);
991 if (!ToTemporalInstant(cx
, args
.get(0), &one
)) {
997 if (!ToTemporalInstant(cx
, args
.get(1), &two
)) {
1002 args
.rval().setInt32(CompareEpochNanoseconds(one
, two
));
1007 * get Temporal.Instant.prototype.epochSeconds
1009 static bool Instant_epochSeconds(JSContext
* cx
, const CallArgs
& args
) {
1011 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1014 args
.rval().setNumber(instant
.seconds
);
1019 * get Temporal.Instant.prototype.epochSeconds
1021 static bool Instant_epochSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1023 CallArgs args
= CallArgsFromVp(argc
, vp
);
1024 return CallNonGenericMethod
<IsInstant
, Instant_epochSeconds
>(cx
, args
);
1028 * get Temporal.Instant.prototype.epochMilliseconds
1030 static bool Instant_epochMilliseconds(JSContext
* cx
, const CallArgs
& args
) {
1032 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1035 args
.rval().setNumber(instant
.floorToMilliseconds());
1040 * get Temporal.Instant.prototype.epochMilliseconds
1042 static bool Instant_epochMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1044 CallArgs args
= CallArgsFromVp(argc
, vp
);
1045 return CallNonGenericMethod
<IsInstant
, Instant_epochMilliseconds
>(cx
, args
);
1049 * get Temporal.Instant.prototype.epochMicroseconds
1051 static bool Instant_epochMicroseconds(JSContext
* cx
, const CallArgs
& args
) {
1053 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1056 auto* microseconds
=
1057 BigInt::createFromInt64(cx
, instant
.floorToMicroseconds());
1058 if (!microseconds
) {
1063 args
.rval().setBigInt(microseconds
);
1068 * get Temporal.Instant.prototype.epochMicroseconds
1070 static bool Instant_epochMicroseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1072 CallArgs args
= CallArgsFromVp(argc
, vp
);
1073 return CallNonGenericMethod
<IsInstant
, Instant_epochMicroseconds
>(cx
, args
);
1077 * get Temporal.Instant.prototype.epochNanoseconds
1079 static bool Instant_epochNanoseconds(JSContext
* cx
, const CallArgs
& args
) {
1081 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1082 auto* nanoseconds
= ToEpochNanoseconds(cx
, instant
);
1088 args
.rval().setBigInt(nanoseconds
);
1093 * get Temporal.Instant.prototype.epochNanoseconds
1095 static bool Instant_epochNanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1097 CallArgs args
= CallArgsFromVp(argc
, vp
);
1098 return CallNonGenericMethod
<IsInstant
, Instant_epochNanoseconds
>(cx
, args
);
1102 * Temporal.Instant.prototype.add ( temporalDurationLike )
1104 static bool Instant_add(JSContext
* cx
, const CallArgs
& args
) {
1105 return AddDurationToOrSubtractDurationFromInstant(cx
, InstantDuration::Add
,
1110 * Temporal.Instant.prototype.add ( temporalDurationLike )
1112 static bool Instant_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1114 CallArgs args
= CallArgsFromVp(argc
, vp
);
1115 return CallNonGenericMethod
<IsInstant
, Instant_add
>(cx
, args
);
1119 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
1121 static bool Instant_subtract(JSContext
* cx
, const CallArgs
& args
) {
1122 return AddDurationToOrSubtractDurationFromInstant(
1123 cx
, InstantDuration::Subtract
, args
);
1127 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
1129 static bool Instant_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1131 CallArgs args
= CallArgsFromVp(argc
, vp
);
1132 return CallNonGenericMethod
<IsInstant
, Instant_subtract
>(cx
, args
);
1136 * Temporal.Instant.prototype.until ( other [ , options ] )
1138 static bool Instant_until(JSContext
* cx
, const CallArgs
& args
) {
1139 return DifferenceTemporalInstant(cx
, TemporalDifference::Until
, args
);
1143 * Temporal.Instant.prototype.until ( other [ , options ] )
1145 static bool Instant_until(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1147 CallArgs args
= CallArgsFromVp(argc
, vp
);
1148 return CallNonGenericMethod
<IsInstant
, Instant_until
>(cx
, args
);
1152 * Temporal.Instant.prototype.since ( other [ , options ] )
1154 static bool Instant_since(JSContext
* cx
, const CallArgs
& args
) {
1155 return DifferenceTemporalInstant(cx
, TemporalDifference::Since
, args
);
1159 * Temporal.Instant.prototype.since ( other [ , options ] )
1161 static bool Instant_since(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1163 CallArgs args
= CallArgsFromVp(argc
, vp
);
1164 return CallNonGenericMethod
<IsInstant
, Instant_since
>(cx
, args
);
1168 * Temporal.Instant.prototype.round ( roundTo )
1170 static bool Instant_round(JSContext
* cx
, const CallArgs
& args
) {
1171 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1174 auto smallestUnit
= TemporalUnit::Auto
;
1175 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
1176 auto roundingIncrement
= Increment
{1};
1177 if (args
.get(0).isString()) {
1178 // Steps 4 and 6-8. (Not applicable in our implementation.)
1181 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
1182 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
1183 TemporalUnitGroup::Time
, &smallestUnit
)) {
1187 // Steps 10-16. (Not applicable in our implementation.)
1190 Rooted
<JSObject
*> options(
1191 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
1197 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
1202 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1207 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
1208 TemporalUnitGroup::Time
, &smallestUnit
)) {
1211 if (smallestUnit
== TemporalUnit::Auto
) {
1212 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1213 JSMSG_TEMPORAL_MISSING_OPTION
, "smallestUnit");
1218 uint64_t maximum
= UnitsPerDay(smallestUnit
);
1221 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
1228 auto roundedNs
= RoundTemporalInstant(instant
, roundingIncrement
,
1229 smallestUnit
, roundingMode
);
1232 auto* result
= CreateTemporalInstant(cx
, roundedNs
);
1236 args
.rval().setObject(*result
);
1241 * Temporal.Instant.prototype.round ( options )
1243 static bool Instant_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1245 CallArgs args
= CallArgsFromVp(argc
, vp
);
1246 return CallNonGenericMethod
<IsInstant
, Instant_round
>(cx
, args
);
1250 * Temporal.Instant.prototype.equals ( other )
1252 static bool Instant_equals(JSContext
* cx
, const CallArgs
& args
) {
1253 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1257 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
1262 args
.rval().setBoolean(instant
== other
);
1267 * Temporal.Instant.prototype.equals ( other )
1269 static bool Instant_equals(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1271 CallArgs args
= CallArgsFromVp(argc
, vp
);
1272 return CallNonGenericMethod
<IsInstant
, Instant_equals
>(cx
, args
);
1276 * Temporal.Instant.prototype.toString ( [ options ] )
1278 static bool Instant_toString(JSContext
* cx
, const CallArgs
& args
) {
1279 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1281 Rooted
<TimeZoneValue
> timeZone(cx
);
1282 auto roundingMode
= TemporalRoundingMode::Trunc
;
1283 SecondsStringPrecision precision
= {Precision::Auto(),
1284 TemporalUnit::Nanosecond
, Increment
{1}};
1285 if (args
.hasDefined(0)) {
1287 Rooted
<JSObject
*> options(
1288 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
1294 auto digits
= Precision::Auto();
1295 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
1300 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1305 auto smallestUnit
= TemporalUnit::Auto
;
1306 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
1307 TemporalUnitGroup::Time
, &smallestUnit
)) {
1312 if (smallestUnit
== TemporalUnit::Hour
) {
1313 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1314 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
, "hour",
1320 Rooted
<Value
> value(cx
);
1321 if (!GetProperty(cx
, options
, options
, cx
->names().timeZone
, &value
)) {
1326 if (!value
.isUndefined()) {
1327 if (!ToTemporalTimeZone(cx
, value
, &timeZone
)) {
1333 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
1337 auto ns
= RoundTemporalInstant(instant
, precision
.increment
, precision
.unit
,
1341 Rooted
<InstantObject
*> roundedInstant(cx
, CreateTemporalInstant(cx
, ns
));
1342 if (!roundedInstant
) {
1347 JSString
* str
= TemporalInstantToString(cx
, roundedInstant
, timeZone
,
1348 precision
.precision
);
1353 args
.rval().setString(str
);
1358 * Temporal.Instant.prototype.toString ( [ options ] )
1360 static bool Instant_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1362 CallArgs args
= CallArgsFromVp(argc
, vp
);
1363 return CallNonGenericMethod
<IsInstant
, Instant_toString
>(cx
, args
);
1367 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1369 static bool Instant_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
1370 Rooted
<InstantObject
*> instant(cx
,
1371 &args
.thisv().toObject().as
<InstantObject
>());
1374 Rooted
<TimeZoneValue
> timeZone(cx
);
1376 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1381 args
.rval().setString(str
);
1386 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1388 static bool Instant_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1390 CallArgs args
= CallArgsFromVp(argc
, vp
);
1391 return CallNonGenericMethod
<IsInstant
, Instant_toLocaleString
>(cx
, args
);
1395 * Temporal.Instant.prototype.toJSON ( )
1397 static bool Instant_toJSON(JSContext
* cx
, const CallArgs
& args
) {
1398 Rooted
<InstantObject
*> instant(cx
,
1399 &args
.thisv().toObject().as
<InstantObject
>());
1402 Rooted
<TimeZoneValue
> timeZone(cx
);
1404 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1409 args
.rval().setString(str
);
1414 * Temporal.Instant.prototype.toJSON ( )
1416 static bool Instant_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1418 CallArgs args
= CallArgsFromVp(argc
, vp
);
1419 return CallNonGenericMethod
<IsInstant
, Instant_toJSON
>(cx
, args
);
1423 * Temporal.Instant.prototype.valueOf ( )
1425 static bool Instant_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1426 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
1427 "Instant", "primitive type");
1432 * Temporal.Instant.prototype.toZonedDateTime ( item )
1434 static bool Instant_toZonedDateTime(JSContext
* cx
, const CallArgs
& args
) {
1435 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1438 Rooted
<JSObject
*> item(
1439 cx
, RequireObjectArg(cx
, "item", "toZonedDateTime", args
.get(0)));
1445 Rooted
<Value
> calendarLike(cx
);
1446 if (!GetProperty(cx
, item
, item
, cx
->names().calendar
, &calendarLike
)) {
1451 if (calendarLike
.isUndefined()) {
1452 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1453 JSMSG_TEMPORAL_MISSING_PROPERTY
, "calendar");
1458 Rooted
<CalendarValue
> calendar(cx
);
1459 if (!ToTemporalCalendar(cx
, calendarLike
, &calendar
)) {
1464 Rooted
<Value
> timeZoneLike(cx
);
1465 if (!GetProperty(cx
, item
, item
, cx
->names().timeZone
, &timeZoneLike
)) {
1470 if (timeZoneLike
.isUndefined()) {
1471 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1472 JSMSG_TEMPORAL_MISSING_PROPERTY
, "timeZone");
1477 Rooted
<TimeZoneValue
> timeZone(cx
);
1478 if (!ToTemporalTimeZone(cx
, timeZoneLike
, &timeZone
)) {
1483 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1488 args
.rval().setObject(*result
);
1493 * Temporal.Instant.prototype.toZonedDateTime ( item )
1495 static bool Instant_toZonedDateTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1497 CallArgs args
= CallArgsFromVp(argc
, vp
);
1498 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTime
>(cx
, args
);
1502 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1504 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, const CallArgs
& args
) {
1505 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1508 Rooted
<TimeZoneValue
> timeZone(cx
);
1509 if (!ToTemporalTimeZone(cx
, args
.get(0), &timeZone
)) {
1514 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(cx
->names().iso8601
));
1515 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1520 args
.rval().setObject(*result
);
1525 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1527 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, unsigned argc
,
1530 CallArgs args
= CallArgsFromVp(argc
, vp
);
1531 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTimeISO
>(cx
, args
);
1534 const JSClass
InstantObject::class_
= {
1536 JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT
) |
1537 JSCLASS_HAS_CACHED_PROTO(JSProto_Instant
),
1539 &InstantObject::classSpec_
,
1542 const JSClass
& InstantObject::protoClass_
= PlainObject::class_
;
1544 static const JSFunctionSpec Instant_methods
[] = {
1545 JS_FN("from", Instant_from
, 1, 0),
1546 JS_FN("fromEpochSeconds", Instant_fromEpochSeconds
, 1, 0),
1547 JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds
, 1, 0),
1548 JS_FN("fromEpochMicroseconds", Instant_fromEpochMicroseconds
, 1, 0),
1549 JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds
, 1, 0),
1550 JS_FN("compare", Instant_compare
, 2, 0),
1554 static const JSFunctionSpec Instant_prototype_methods
[] = {
1555 JS_FN("add", Instant_add
, 1, 0),
1556 JS_FN("subtract", Instant_subtract
, 1, 0),
1557 JS_FN("until", Instant_until
, 1, 0),
1558 JS_FN("since", Instant_since
, 1, 0),
1559 JS_FN("round", Instant_round
, 1, 0),
1560 JS_FN("equals", Instant_equals
, 1, 0),
1561 JS_FN("toString", Instant_toString
, 0, 0),
1562 JS_FN("toLocaleString", Instant_toLocaleString
, 0, 0),
1563 JS_FN("toJSON", Instant_toJSON
, 0, 0),
1564 JS_FN("valueOf", Instant_valueOf
, 0, 0),
1565 JS_FN("toZonedDateTime", Instant_toZonedDateTime
, 1, 0),
1566 JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO
, 1, 0),
1570 static const JSPropertySpec Instant_prototype_properties
[] = {
1571 JS_PSG("epochSeconds", Instant_epochSeconds
, 0),
1572 JS_PSG("epochMilliseconds", Instant_epochMilliseconds
, 0),
1573 JS_PSG("epochMicroseconds", Instant_epochMicroseconds
, 0),
1574 JS_PSG("epochNanoseconds", Instant_epochNanoseconds
, 0),
1575 JS_STRING_SYM_PS(toStringTag
, "Temporal.Instant", JSPROP_READONLY
),
1579 const ClassSpec
InstantObject::classSpec_
= {
1580 GenericCreateConstructor
<InstantConstructor
, 1, gc::AllocKind::FUNCTION
>,
1581 GenericCreatePrototype
<InstantObject
>,
1584 Instant_prototype_methods
,
1585 Instant_prototype_properties
,
1587 ClassSpec::DontDefineConstructor
,