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),
125 // Can't be defined in IsValidEpochNanoseconds when compiling with GCC 8.
126 static constexpr auto EpochLimitBigIntDigits
= NanosecondsMaxInstant();
129 * IsValidEpochNanoseconds ( epochNanoseconds )
131 bool js::temporal::IsValidEpochNanoseconds(const BigInt
* epochNanoseconds
) {
133 return AbsoluteValueIsLessOrEqual
<EpochLimitBigIntDigits
>(epochNanoseconds
);
136 static bool IsValidEpochMilliseconds(double epochMilliseconds
) {
137 MOZ_ASSERT(IsInteger(epochMilliseconds
));
139 constexpr int64_t MillisecondsMaxInstant
= Instant::max().toMilliseconds();
140 return std::abs(epochMilliseconds
) <= double(MillisecondsMaxInstant
);
144 * IsValidEpochNanoseconds ( epochNanoseconds )
146 bool js::temporal::IsValidEpochInstant(const Instant
& instant
) {
147 MOZ_ASSERT(0 <= instant
.nanoseconds
&& instant
.nanoseconds
<= 999'999'999);
150 return Instant::min() <= instant
&& instant
<= Instant::max();
155 * Validates a nanoseconds amount is at most as large as the difference
156 * between two valid nanoseconds from the epoch instants.
158 bool js::temporal::IsValidInstantSpan(const InstantSpan
& span
) {
159 MOZ_ASSERT(0 <= span
.nanoseconds
&& span
.nanoseconds
<= 999'999'999);
162 return InstantSpan::min() <= span
&& span
<= InstantSpan::max();
167 * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of
170 static Int96
ToInt96(const BigInt
* ns
) {
171 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
173 auto digits
= ns
->digits();
174 if constexpr (BigInt::DigitBits
== 64) {
175 BigInt::Digit x
= 0, y
= 0;
176 switch (digits
.size()) {
186 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
189 Int96::Digits
{Int96::Digit(x
), Int96::Digit(x
>> 32), Int96::Digit(y
)},
192 BigInt::Digit x
= 0, y
= 0, z
= 0;
193 switch (digits
.size()) {
206 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
209 Int96::Digits
{Int96::Digit(x
), Int96::Digit(y
), Int96::Digit(z
)},
214 Instant
js::temporal::ToInstant(const BigInt
* epochNanoseconds
) {
215 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds
));
217 auto [seconds
, nanos
] =
218 ToInt96(epochNanoseconds
) / ToNanoseconds(TemporalUnit::Second
);
219 return {seconds
, nanos
};
222 static BigInt
* CreateBigInt(JSContext
* cx
,
223 const std::array
<uint32_t, 3>& digits
,
225 static_assert(BigInt::DigitBits
== 64 || BigInt::DigitBits
== 32);
227 if constexpr (BigInt::DigitBits
== 64) {
228 uint64_t x
= (uint64_t(digits
[1]) << 32) | digits
[0];
229 uint64_t y
= digits
[2];
231 size_t length
= y
? 2 : x
? 1 : 0;
232 auto* result
= BigInt::createUninitialized(cx
, length
, negative
);
237 result
->setDigit(1, y
);
240 result
->setDigit(0, x
);
244 size_t length
= digits
[2] ? 3 : digits
[1] ? 2 : digits
[0] ? 1 : 0;
245 auto* result
= BigInt::createUninitialized(cx
, length
, negative
);
250 result
->setDigit(length
, digits
[length
]);
256 static auto ToBigIntDigits(uint64_t seconds
, uint32_t nanoseconds
) {
257 // Multiplies two uint32_t values and returns the lower 32-bits. The higher
258 // 32-bits are stored in |high|.
259 auto digitMul
= [](uint32_t a
, uint32_t b
, uint32_t* high
) {
260 uint64_t result
= static_cast<uint64_t>(a
) * static_cast<uint64_t>(b
);
261 *high
= result
>> 32;
262 return static_cast<uint32_t>(result
);
265 // Adds two uint32_t values and returns the result. Overflow is added to the
266 // out-param |carry|.
267 auto digitAdd
= [](uint32_t a
, uint32_t b
, uint32_t* carry
) {
268 uint32_t result
= a
+ b
;
269 *carry
+= static_cast<uint32_t>(result
< a
);
273 constexpr uint32_t secToNanos
= ToNanoseconds(TemporalUnit::Second
);
275 // uint32_t digits stored in the same order as BigInt digits, i.e. the least
276 // significant digit is stored at index zero.
277 std::array
<uint32_t, 2> multiplicand
= {uint32_t(seconds
),
278 uint32_t(seconds
>> 32)};
279 std::array
<uint32_t, 3> accumulator
= {nanoseconds
, 0, 0};
281 // This code follows the implementation of |BigInt::multiplyAccumulate()|.
286 uint32_t low
= digitMul(secToNanos
, multiplicand
[0], &high
);
288 uint32_t newCarry
= 0;
289 accumulator
[0] = digitAdd(accumulator
[0], low
, &newCarry
);
290 accumulator
[1] = digitAdd(high
, newCarry
, &carry
);
294 uint32_t low
= digitMul(secToNanos
, multiplicand
[1], &high
);
296 uint32_t newCarry
= 0;
297 accumulator
[1] = digitAdd(accumulator
[1], low
, &carry
);
298 accumulator
[2] = digitAdd(high
, carry
, &newCarry
);
299 MOZ_ASSERT(newCarry
== 0);
305 BigInt
* js::temporal::ToEpochNanoseconds(JSContext
* cx
,
306 const Instant
& instant
) {
307 MOZ_ASSERT(IsValidEpochInstant(instant
));
309 auto [seconds
, nanoseconds
] = instant
.abs();
310 auto digits
= ToBigIntDigits(uint64_t(seconds
), uint32_t(nanoseconds
));
311 return CreateBigInt(cx
, digits
, instant
.seconds
< 0);
315 * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
316 * microsecond, nanosecond [ , offsetNanoseconds ] )
318 Instant
js::temporal::GetUTCEpochNanoseconds(const PlainDateTime
& dateTime
) {
319 const auto& [date
, time
] = dateTime
;
322 MOZ_ASSERT(IsValidISODateTime(dateTime
));
324 // Additionally ensure the date-time value can be represented as an Instant.
325 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
328 int64_t ms
= MakeDate(dateTime
);
330 // Propagate the input range to the compiler.
332 std::clamp(time
.microsecond
* 1'000 + time
.nanosecond
, 0, 999'999);
335 return Instant::fromMilliseconds(ms
) + InstantSpan
{0, nanos
};
339 * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
340 * microsecond, nanosecond [ , offsetNanoseconds ] )
342 Instant
js::temporal::GetUTCEpochNanoseconds(
343 const PlainDateTime
& dateTime
, const InstantSpan
& offsetNanoseconds
) {
344 MOZ_ASSERT(offsetNanoseconds
.abs() <
345 InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day
)));
348 auto epochNanoseconds
= GetUTCEpochNanoseconds(dateTime
);
351 return epochNanoseconds
- offsetNanoseconds
;
355 * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo )
357 static int32_t CompareEpochNanoseconds(const Instant
& epochNanosecondsOne
,
358 const Instant
& epochNanosecondsTwo
) {
360 if (epochNanosecondsOne
> epochNanosecondsTwo
) {
365 if (epochNanosecondsOne
< epochNanosecondsTwo
) {
374 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
376 InstantObject
* js::temporal::CreateTemporalInstant(JSContext
* cx
,
377 const Instant
& instant
) {
379 MOZ_ASSERT(IsValidEpochInstant(instant
));
382 auto* object
= NewBuiltinClassInstance
<InstantObject
>(cx
);
388 object
->setFixedSlot(InstantObject::SECONDS_SLOT
,
389 NumberValue(instant
.seconds
));
390 object
->setFixedSlot(InstantObject::NANOSECONDS_SLOT
,
391 Int32Value(instant
.nanoseconds
));
398 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
400 static InstantObject
* CreateTemporalInstant(JSContext
* cx
, const CallArgs
& args
,
401 Handle
<BigInt
*> epochNanoseconds
) {
403 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds
));
406 Rooted
<JSObject
*> proto(cx
);
407 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Instant
, &proto
)) {
411 auto* object
= NewObjectWithClassProto
<InstantObject
>(cx
, proto
);
417 auto instant
= ToInstant(epochNanoseconds
);
418 object
->setFixedSlot(InstantObject::SECONDS_SLOT
,
419 NumberValue(instant
.seconds
));
420 object
->setFixedSlot(InstantObject::NANOSECONDS_SLOT
,
421 Int32Value(instant
.nanoseconds
));
428 * ToTemporalInstant ( item )
430 Wrapped
<InstantObject
*> js::temporal::ToTemporalInstant(JSContext
* cx
,
431 Handle
<Value
> item
) {
433 if (item
.isObject()) {
434 JSObject
* itemObj
= &item
.toObject();
437 if (itemObj
->canUnwrapAs
<InstantObject
>()) {
442 // Steps 1.b-d and 3-7
443 Instant epochNanoseconds
;
444 if (!ToTemporalInstant(cx
, item
, &epochNanoseconds
)) {
449 return CreateTemporalInstant(cx
, epochNanoseconds
);
453 * ToTemporalInstant ( item )
455 bool js::temporal::ToTemporalInstant(JSContext
* cx
, Handle
<Value
> item
,
458 Rooted
<Value
> primitiveValue(cx
, item
);
459 if (item
.isObject()) {
460 JSObject
* itemObj
= &item
.toObject();
463 if (auto* instant
= itemObj
->maybeUnwrapIf
<InstantObject
>()) {
464 *result
= ToInstant(instant
);
469 if (auto* zonedDateTime
= itemObj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
470 *result
= ToInstant(zonedDateTime
);
475 if (!ToPrimitive(cx
, JSTYPE_STRING
, &primitiveValue
)) {
481 if (!primitiveValue
.isString()) {
482 // The value is always on the stack, so JSDVG_SEARCH_STACK can be used for
483 // better error reporting.
484 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_SEARCH_STACK
,
485 primitiveValue
, nullptr, "not a string");
488 Rooted
<JSString
*> string(cx
, primitiveValue
.toString());
491 PlainDateTime dateTime
;
493 if (!ParseTemporalInstantString(cx
, string
, &dateTime
, &offset
)) {
496 MOZ_ASSERT(std::abs(offset
) < ToNanoseconds(TemporalUnit::Day
));
498 // Steps 5-6. (Reordered)
499 if (!ISODateTimeWithinLimits(dateTime
)) {
500 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
501 JSMSG_TEMPORAL_INSTANT_INVALID
);
506 auto epochNanoseconds
=
507 GetUTCEpochNanoseconds(dateTime
, InstantSpan::fromNanoseconds(offset
));
510 if (!IsValidEpochInstant(epochNanoseconds
)) {
511 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
512 JSMSG_TEMPORAL_INSTANT_INVALID
);
517 *result
= epochNanoseconds
;
522 * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
523 * microseconds, nanoseconds )
525 bool js::temporal::AddInstant(JSContext
* cx
, const Instant
& instant
,
526 const NormalizedTimeDuration
& duration
,
528 MOZ_ASSERT(IsValidEpochInstant(instant
));
529 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
531 // Step 1. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
532 auto r
= instant
+ duration
.to
<InstantSpan
>();
535 if (!IsValidEpochInstant(r
)) {
536 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
537 JSMSG_TEMPORAL_INSTANT_INVALID
);
547 * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
549 NormalizedTimeDuration
js::temporal::DifferenceInstant(
550 const Instant
& ns1
, const Instant
& ns2
, Increment roundingIncrement
,
551 TemporalUnit smallestUnit
, TemporalRoundingMode roundingMode
) {
552 MOZ_ASSERT(IsValidEpochInstant(ns1
));
553 MOZ_ASSERT(IsValidEpochInstant(ns2
));
554 MOZ_ASSERT(smallestUnit
> TemporalUnit::Day
);
555 MOZ_ASSERT(roundingIncrement
<=
556 MaximumTemporalDurationRoundingIncrement(smallestUnit
));
559 auto diff
= NormalizedTimeDurationFromEpochNanosecondsDifference(ns2
, ns1
);
560 MOZ_ASSERT(IsValidInstantSpan(diff
.to
<InstantSpan
>()));
563 return RoundTimeDuration(diff
, roundingIncrement
, smallestUnit
, roundingMode
);
567 * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
569 static Instant
RoundNumberToIncrementAsIfPositive(
570 const Instant
& x
, int64_t increment
, TemporalRoundingMode roundingMode
) {
571 MOZ_ASSERT(IsValidEpochInstant(x
));
572 MOZ_ASSERT(increment
> 0);
573 MOZ_ASSERT(increment
<= ToNanoseconds(TemporalUnit::Day
));
575 // This operation is equivalent to adjusting the rounding mode through
576 // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
577 auto rounded
= RoundNumberToIncrement(x
.toNanoseconds(), Int128
{increment
},
578 ToPositiveRoundingMode(roundingMode
));
579 return Instant::fromNanoseconds(rounded
);
583 * RoundTemporalInstant ( ns, increment, unit, roundingMode )
585 Instant
js::temporal::RoundTemporalInstant(const Instant
& ns
,
588 TemporalRoundingMode roundingMode
) {
589 MOZ_ASSERT(IsValidEpochInstant(ns
));
590 MOZ_ASSERT(increment
>= Increment::min());
591 MOZ_ASSERT(uint64_t(increment
.value()) <= ToNanoseconds(TemporalUnit::Day
));
594 MOZ_ASSERT(unit
> TemporalUnit::Day
);
597 int64_t unitLength
= ToNanoseconds(unit
);
600 int64_t incrementNs
= increment
.value() * unitLength
;
601 MOZ_ASSERT(incrementNs
<= ToNanoseconds(TemporalUnit::Day
),
602 "incrementNs doesn't overflow instant resolution");
605 return RoundNumberToIncrementAsIfPositive(ns
, incrementNs
, roundingMode
);
609 * DifferenceTemporalInstant ( operation, instant, other, options )
611 static bool DifferenceTemporalInstant(JSContext
* cx
,
612 TemporalDifference operation
,
613 const CallArgs
& args
) {
614 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
616 // Step 1. (Not applicable in our implementation.)
620 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
625 DifferenceSettings settings
;
626 if (args
.hasDefined(1)) {
627 Rooted
<JSObject
*> options(
628 cx
, RequireObjectArg(cx
, "options", ToName(operation
), args
[1]));
634 Rooted
<PlainObject
*> resolvedOptions(cx
,
635 SnapshotOwnProperties(cx
, options
));
636 if (!resolvedOptions
) {
641 if (!GetDifferenceSettings(
642 cx
, operation
, resolvedOptions
, TemporalUnitGroup::Time
,
643 TemporalUnit::Nanosecond
, TemporalUnit::Second
, &settings
)) {
649 TemporalUnit::Nanosecond
,
650 TemporalUnit::Second
,
651 TemporalRoundingMode::Trunc
,
658 DifferenceInstant(instant
, other
, settings
.roundingIncrement
,
659 settings
.smallestUnit
, settings
.roundingMode
);
662 TimeDuration balanced
;
663 if (!BalanceTimeDuration(cx
, difference
, settings
.largestUnit
, &balanced
)) {
668 auto duration
= balanced
.toDuration();
669 if (operation
== TemporalDifference::Since
) {
670 duration
= duration
.negate();
673 auto* obj
= CreateTemporalDuration(cx
, duration
);
678 args
.rval().setObject(*obj
);
682 enum class InstantDuration
{ Add
, Subtract
};
685 * AddDurationToOrSubtractDurationFromInstant ( operation, instant,
686 * temporalDurationLike )
688 static bool AddDurationToOrSubtractDurationFromInstant(
689 JSContext
* cx
, InstantDuration operation
, const CallArgs
& args
) {
690 auto* instant
= &args
.thisv().toObject().as
<InstantObject
>();
691 auto epochNanoseconds
= ToInstant(instant
);
693 // Step 1. (Not applicable in our implementation.)
697 if (!ToTemporalDurationRecord(cx
, args
.get(0), &duration
)) {
702 if (duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0 ||
703 duration
.days
!= 0) {
704 const char* part
= duration
.years
!= 0 ? "years"
705 : duration
.months
!= 0 ? "months"
706 : duration
.weeks
!= 0 ? "weeks"
708 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
709 JSMSG_TEMPORAL_INSTANT_BAD_DURATION
, part
);
714 if (operation
== InstantDuration::Subtract
) {
715 duration
= duration
.negate();
717 auto timeDuration
= NormalizeTimeDuration(duration
);
721 if (!AddInstant(cx
, epochNanoseconds
, timeDuration
, &ns
)) {
726 auto* result
= CreateTemporalInstant(cx
, ns
);
731 args
.rval().setObject(*result
);
736 * Temporal.Instant ( epochNanoseconds )
738 static bool InstantConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
739 CallArgs args
= CallArgsFromVp(argc
, vp
);
742 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Instant")) {
747 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
748 if (!epochNanoseconds
) {
753 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
754 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
755 JSMSG_TEMPORAL_INSTANT_INVALID
);
760 auto* result
= CreateTemporalInstant(cx
, args
, epochNanoseconds
);
765 args
.rval().setObject(*result
);
770 * Temporal.Instant.from ( item )
772 static bool Instant_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
773 CallArgs args
= CallArgsFromVp(argc
, vp
);
776 Instant epochInstant
;
777 if (!ToTemporalInstant(cx
, args
.get(0), &epochInstant
)) {
781 auto* result
= CreateTemporalInstant(cx
, epochInstant
);
785 args
.rval().setObject(*result
);
790 * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )
792 static bool Instant_fromEpochMilliseconds(JSContext
* cx
, unsigned argc
,
794 CallArgs args
= CallArgsFromVp(argc
, vp
);
797 double epochMilliseconds
;
798 if (!JS::ToNumber(cx
, args
.get(0), &epochMilliseconds
)) {
804 // NumberToBigInt throws a RangeError for non-integral numbers.
805 if (!IsInteger(epochMilliseconds
)) {
807 const char* str
= NumberToCString(&cbuf
, epochMilliseconds
);
809 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
810 JSMSG_TEMPORAL_INSTANT_NONINTEGER
, str
);
814 // Step 3. (Not applicable)
817 if (!IsValidEpochMilliseconds(epochMilliseconds
)) {
818 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
819 JSMSG_TEMPORAL_INSTANT_INVALID
);
824 int64_t milliseconds
= mozilla::AssertedCast
<int64_t>(epochMilliseconds
);
826 CreateTemporalInstant(cx
, Instant::fromMilliseconds(milliseconds
));
830 args
.rval().setObject(*result
);
835 * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )
837 static bool Instant_fromEpochNanoseconds(JSContext
* cx
, unsigned argc
,
839 CallArgs args
= CallArgsFromVp(argc
, vp
);
842 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
843 if (!epochNanoseconds
) {
848 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
849 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
850 JSMSG_TEMPORAL_INSTANT_INVALID
);
855 auto* result
= CreateTemporalInstant(cx
, ToInstant(epochNanoseconds
));
859 args
.rval().setObject(*result
);
864 * Temporal.Instant.compare ( one, two )
866 static bool Instant_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
867 CallArgs args
= CallArgsFromVp(argc
, vp
);
871 if (!ToTemporalInstant(cx
, args
.get(0), &one
)) {
877 if (!ToTemporalInstant(cx
, args
.get(1), &two
)) {
882 args
.rval().setInt32(CompareEpochNanoseconds(one
, two
));
887 * get Temporal.Instant.prototype.epochMilliseconds
889 static bool Instant_epochMilliseconds(JSContext
* cx
, const CallArgs
& args
) {
891 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
894 args
.rval().setNumber(instant
.floorToMilliseconds());
899 * get Temporal.Instant.prototype.epochMilliseconds
901 static bool Instant_epochMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
903 CallArgs args
= CallArgsFromVp(argc
, vp
);
904 return CallNonGenericMethod
<IsInstant
, Instant_epochMilliseconds
>(cx
, args
);
908 * get Temporal.Instant.prototype.epochNanoseconds
910 static bool Instant_epochNanoseconds(JSContext
* cx
, const CallArgs
& args
) {
912 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
913 auto* nanoseconds
= ToEpochNanoseconds(cx
, instant
);
919 args
.rval().setBigInt(nanoseconds
);
924 * get Temporal.Instant.prototype.epochNanoseconds
926 static bool Instant_epochNanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
928 CallArgs args
= CallArgsFromVp(argc
, vp
);
929 return CallNonGenericMethod
<IsInstant
, Instant_epochNanoseconds
>(cx
, args
);
933 * Temporal.Instant.prototype.add ( temporalDurationLike )
935 static bool Instant_add(JSContext
* cx
, const CallArgs
& args
) {
936 return AddDurationToOrSubtractDurationFromInstant(cx
, InstantDuration::Add
,
941 * Temporal.Instant.prototype.add ( temporalDurationLike )
943 static bool Instant_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
945 CallArgs args
= CallArgsFromVp(argc
, vp
);
946 return CallNonGenericMethod
<IsInstant
, Instant_add
>(cx
, args
);
950 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
952 static bool Instant_subtract(JSContext
* cx
, const CallArgs
& args
) {
953 return AddDurationToOrSubtractDurationFromInstant(
954 cx
, InstantDuration::Subtract
, args
);
958 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
960 static bool Instant_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
962 CallArgs args
= CallArgsFromVp(argc
, vp
);
963 return CallNonGenericMethod
<IsInstant
, Instant_subtract
>(cx
, args
);
967 * Temporal.Instant.prototype.until ( other [ , options ] )
969 static bool Instant_until(JSContext
* cx
, const CallArgs
& args
) {
970 return DifferenceTemporalInstant(cx
, TemporalDifference::Until
, args
);
974 * Temporal.Instant.prototype.until ( other [ , options ] )
976 static bool Instant_until(JSContext
* cx
, unsigned argc
, Value
* vp
) {
978 CallArgs args
= CallArgsFromVp(argc
, vp
);
979 return CallNonGenericMethod
<IsInstant
, Instant_until
>(cx
, args
);
983 * Temporal.Instant.prototype.since ( other [ , options ] )
985 static bool Instant_since(JSContext
* cx
, const CallArgs
& args
) {
986 return DifferenceTemporalInstant(cx
, TemporalDifference::Since
, args
);
990 * Temporal.Instant.prototype.since ( other [ , options ] )
992 static bool Instant_since(JSContext
* cx
, unsigned argc
, Value
* vp
) {
994 CallArgs args
= CallArgsFromVp(argc
, vp
);
995 return CallNonGenericMethod
<IsInstant
, Instant_since
>(cx
, args
);
999 * Temporal.Instant.prototype.round ( roundTo )
1001 static bool Instant_round(JSContext
* cx
, const CallArgs
& args
) {
1002 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1005 auto smallestUnit
= TemporalUnit::Auto
;
1006 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
1007 auto roundingIncrement
= Increment
{1};
1008 if (args
.get(0).isString()) {
1009 // Steps 4 and 6-8. (Not applicable in our implementation.)
1012 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
1013 if (!GetTemporalUnitValuedOption(cx
, paramString
,
1014 TemporalUnitKey::SmallestUnit
,
1015 TemporalUnitGroup::Time
, &smallestUnit
)) {
1019 // Steps 10-16. (Not applicable in our implementation.)
1022 Rooted
<JSObject
*> options(
1023 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
1029 if (!GetRoundingIncrementOption(cx
, options
, &roundingIncrement
)) {
1034 if (!GetRoundingModeOption(cx
, options
, &roundingMode
)) {
1039 if (!GetTemporalUnitValuedOption(cx
, options
, TemporalUnitKey::SmallestUnit
,
1040 TemporalUnitGroup::Time
, &smallestUnit
)) {
1043 if (smallestUnit
== TemporalUnit::Auto
) {
1044 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1045 JSMSG_TEMPORAL_MISSING_OPTION
, "smallestUnit");
1050 int64_t maximum
= UnitsPerDay(smallestUnit
);
1053 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
1060 auto roundedNs
= RoundTemporalInstant(instant
, roundingIncrement
,
1061 smallestUnit
, roundingMode
);
1064 auto* result
= CreateTemporalInstant(cx
, roundedNs
);
1068 args
.rval().setObject(*result
);
1073 * Temporal.Instant.prototype.round ( options )
1075 static bool Instant_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1077 CallArgs args
= CallArgsFromVp(argc
, vp
);
1078 return CallNonGenericMethod
<IsInstant
, Instant_round
>(cx
, args
);
1082 * Temporal.Instant.prototype.equals ( other )
1084 static bool Instant_equals(JSContext
* cx
, const CallArgs
& args
) {
1085 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1089 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
1094 args
.rval().setBoolean(instant
== other
);
1099 * Temporal.Instant.prototype.equals ( other )
1101 static bool Instant_equals(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1103 CallArgs args
= CallArgsFromVp(argc
, vp
);
1104 return CallNonGenericMethod
<IsInstant
, Instant_equals
>(cx
, args
);
1108 * Temporal.Instant.prototype.toString ( [ options ] )
1110 static bool Instant_toString(JSContext
* cx
, const CallArgs
& args
) {
1111 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1113 Rooted
<TimeZoneValue
> timeZone(cx
);
1114 auto roundingMode
= TemporalRoundingMode::Trunc
;
1115 SecondsStringPrecision precision
= {Precision::Auto(),
1116 TemporalUnit::Nanosecond
, Increment
{1}};
1117 if (args
.hasDefined(0)) {
1119 Rooted
<JSObject
*> options(
1120 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
1126 auto digits
= Precision::Auto();
1127 if (!GetTemporalFractionalSecondDigitsOption(cx
, options
, &digits
)) {
1132 if (!GetRoundingModeOption(cx
, options
, &roundingMode
)) {
1137 auto smallestUnit
= TemporalUnit::Auto
;
1138 if (!GetTemporalUnitValuedOption(cx
, options
, TemporalUnitKey::SmallestUnit
,
1139 TemporalUnitGroup::Time
, &smallestUnit
)) {
1144 if (smallestUnit
== TemporalUnit::Hour
) {
1145 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1146 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
, "hour",
1152 Rooted
<Value
> value(cx
);
1153 if (!GetProperty(cx
, options
, options
, cx
->names().timeZone
, &value
)) {
1158 if (!value
.isUndefined()) {
1159 if (!ToTemporalTimeZone(cx
, value
, &timeZone
)) {
1165 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
1169 auto ns
= RoundTemporalInstant(instant
, precision
.increment
, precision
.unit
,
1173 Rooted
<InstantObject
*> roundedInstant(cx
, CreateTemporalInstant(cx
, ns
));
1174 if (!roundedInstant
) {
1179 JSString
* str
= TemporalInstantToString(cx
, roundedInstant
, timeZone
,
1180 precision
.precision
);
1185 args
.rval().setString(str
);
1190 * Temporal.Instant.prototype.toString ( [ options ] )
1192 static bool Instant_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1194 CallArgs args
= CallArgsFromVp(argc
, vp
);
1195 return CallNonGenericMethod
<IsInstant
, Instant_toString
>(cx
, args
);
1199 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1201 static bool Instant_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
1202 Rooted
<InstantObject
*> instant(cx
,
1203 &args
.thisv().toObject().as
<InstantObject
>());
1206 Rooted
<TimeZoneValue
> timeZone(cx
);
1208 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1213 args
.rval().setString(str
);
1218 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1220 static bool Instant_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1222 CallArgs args
= CallArgsFromVp(argc
, vp
);
1223 return CallNonGenericMethod
<IsInstant
, Instant_toLocaleString
>(cx
, args
);
1227 * Temporal.Instant.prototype.toJSON ( )
1229 static bool Instant_toJSON(JSContext
* cx
, const CallArgs
& args
) {
1230 Rooted
<InstantObject
*> instant(cx
,
1231 &args
.thisv().toObject().as
<InstantObject
>());
1234 Rooted
<TimeZoneValue
> timeZone(cx
);
1236 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1241 args
.rval().setString(str
);
1246 * Temporal.Instant.prototype.toJSON ( )
1248 static bool Instant_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1250 CallArgs args
= CallArgsFromVp(argc
, vp
);
1251 return CallNonGenericMethod
<IsInstant
, Instant_toJSON
>(cx
, args
);
1255 * Temporal.Instant.prototype.valueOf ( )
1257 static bool Instant_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1258 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
1259 "Instant", "primitive type");
1264 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1266 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, const CallArgs
& args
) {
1267 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1270 Rooted
<TimeZoneValue
> timeZone(cx
);
1271 if (!ToTemporalTimeZone(cx
, args
.get(0), &timeZone
)) {
1276 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(CalendarId::ISO8601
));
1277 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1282 args
.rval().setObject(*result
);
1287 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1289 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, unsigned argc
,
1292 CallArgs args
= CallArgsFromVp(argc
, vp
);
1293 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTimeISO
>(cx
, args
);
1296 const JSClass
InstantObject::class_
= {
1298 JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT
) |
1299 JSCLASS_HAS_CACHED_PROTO(JSProto_Instant
),
1301 &InstantObject::classSpec_
,
1304 const JSClass
& InstantObject::protoClass_
= PlainObject::class_
;
1306 static const JSFunctionSpec Instant_methods
[] = {
1307 JS_FN("from", Instant_from
, 1, 0),
1308 JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds
, 1, 0),
1309 JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds
, 1, 0),
1310 JS_FN("compare", Instant_compare
, 2, 0),
1314 static const JSFunctionSpec Instant_prototype_methods
[] = {
1315 JS_FN("add", Instant_add
, 1, 0),
1316 JS_FN("subtract", Instant_subtract
, 1, 0),
1317 JS_FN("until", Instant_until
, 1, 0),
1318 JS_FN("since", Instant_since
, 1, 0),
1319 JS_FN("round", Instant_round
, 1, 0),
1320 JS_FN("equals", Instant_equals
, 1, 0),
1321 JS_FN("toString", Instant_toString
, 0, 0),
1322 JS_FN("toLocaleString", Instant_toLocaleString
, 0, 0),
1323 JS_FN("toJSON", Instant_toJSON
, 0, 0),
1324 JS_FN("valueOf", Instant_valueOf
, 0, 0),
1325 JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO
, 1, 0),
1329 static const JSPropertySpec Instant_prototype_properties
[] = {
1330 JS_PSG("epochMilliseconds", Instant_epochMilliseconds
, 0),
1331 JS_PSG("epochNanoseconds", Instant_epochNanoseconds
, 0),
1332 JS_STRING_SYM_PS(toStringTag
, "Temporal.Instant", JSPROP_READONLY
),
1336 const ClassSpec
InstantObject::classSpec_
= {
1337 GenericCreateConstructor
<InstantConstructor
, 1, gc::AllocKind::FUNCTION
>,
1338 GenericCreatePrototype
<InstantObject
>,
1341 Instant_prototype_methods
,
1342 Instant_prototype_properties
,
1344 ClassSpec::DontDefineConstructor
,