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-7
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 // Steps 5-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 * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
556 * microseconds, nanoseconds )
558 bool js::temporal::AddInstant(JSContext
* cx
, const Instant
& instant
,
559 const NormalizedTimeDuration
& duration
,
561 MOZ_ASSERT(IsValidEpochInstant(instant
));
562 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
564 // Step 1. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
565 auto r
= instant
+ duration
.to
<InstantSpan
>();
568 if (!IsValidEpochInstant(r
)) {
569 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
570 JSMSG_TEMPORAL_INSTANT_INVALID
);
580 * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode )
582 NormalizedTimeDuration
js::temporal::DifferenceInstant(
583 const Instant
& ns1
, const Instant
& ns2
, Increment roundingIncrement
,
584 TemporalUnit smallestUnit
, TemporalRoundingMode roundingMode
) {
585 MOZ_ASSERT(IsValidEpochInstant(ns1
));
586 MOZ_ASSERT(IsValidEpochInstant(ns2
));
587 MOZ_ASSERT(smallestUnit
> TemporalUnit::Day
);
588 MOZ_ASSERT(roundingIncrement
<=
589 MaximumTemporalDurationRoundingIncrement(smallestUnit
));
592 auto diff
= NormalizedTimeDurationFromEpochNanosecondsDifference(ns2
, ns1
);
593 MOZ_ASSERT(IsValidInstantSpan(diff
.to
<InstantSpan
>()));
596 if (smallestUnit
== TemporalUnit::Nanosecond
&&
597 roundingIncrement
== Increment
{1}) {
602 return RoundDuration(diff
, roundingIncrement
, smallestUnit
, roundingMode
);
606 * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
608 static Instant
RoundNumberToIncrementAsIfPositive(
609 const Instant
& x
, int64_t increment
, TemporalRoundingMode roundingMode
) {
610 MOZ_ASSERT(IsValidEpochInstant(x
));
611 MOZ_ASSERT(increment
> 0);
612 MOZ_ASSERT(increment
<= ToNanoseconds(TemporalUnit::Day
));
614 // This operation is equivalent to adjusting the rounding mode through
615 // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
616 auto rounded
= RoundNumberToIncrement(x
.toNanoseconds(), Int128
{increment
},
617 ToPositiveRoundingMode(roundingMode
));
618 return Instant::fromNanoseconds(rounded
);
622 * RoundTemporalInstant ( ns, increment, unit, roundingMode )
624 Instant
js::temporal::RoundTemporalInstant(const Instant
& ns
,
627 TemporalRoundingMode roundingMode
) {
628 MOZ_ASSERT(IsValidEpochInstant(ns
));
629 MOZ_ASSERT(increment
>= Increment::min());
630 MOZ_ASSERT(uint64_t(increment
.value()) <= ToNanoseconds(TemporalUnit::Day
));
631 MOZ_ASSERT(unit
> TemporalUnit::Day
);
634 int64_t toNanoseconds
= ToNanoseconds(unit
);
636 (increment
.value() * toNanoseconds
) <= ToNanoseconds(TemporalUnit::Day
),
637 "increment * toNanoseconds shouldn't overflow instant resolution");
640 return RoundNumberToIncrementAsIfPositive(
641 ns
, increment
.value() * toNanoseconds
, roundingMode
);
645 * DifferenceTemporalInstant ( operation, instant, other, options )
647 static bool DifferenceTemporalInstant(JSContext
* cx
,
648 TemporalDifference operation
,
649 const CallArgs
& args
) {
650 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
652 // Step 1. (Not applicable in our implementation.)
656 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
661 DifferenceSettings settings
;
662 if (args
.hasDefined(1)) {
663 Rooted
<JSObject
*> options(
664 cx
, RequireObjectArg(cx
, "options", ToName(operation
), args
[1]));
670 Rooted
<PlainObject
*> resolvedOptions(cx
,
671 SnapshotOwnProperties(cx
, options
));
672 if (!resolvedOptions
) {
677 if (!GetDifferenceSettings(
678 cx
, operation
, resolvedOptions
, TemporalUnitGroup::Time
,
679 TemporalUnit::Nanosecond
, TemporalUnit::Second
, &settings
)) {
685 TemporalUnit::Nanosecond
,
686 TemporalUnit::Second
,
687 TemporalRoundingMode::Trunc
,
694 DifferenceInstant(instant
, other
, settings
.roundingIncrement
,
695 settings
.smallestUnit
, settings
.roundingMode
);
698 auto balanced
= BalanceTimeDuration(difference
, settings
.largestUnit
);
701 auto duration
= balanced
.toDuration();
702 if (operation
== TemporalDifference::Since
) {
703 duration
= duration
.negate();
706 auto* obj
= CreateTemporalDuration(cx
, duration
);
711 args
.rval().setObject(*obj
);
715 enum class InstantDuration
{ Add
, Subtract
};
718 * AddDurationToOrSubtractDurationFromInstant ( operation, instant,
719 * temporalDurationLike )
721 static bool AddDurationToOrSubtractDurationFromInstant(
722 JSContext
* cx
, InstantDuration operation
, const CallArgs
& args
) {
723 auto* instant
= &args
.thisv().toObject().as
<InstantObject
>();
724 auto epochNanoseconds
= ToInstant(instant
);
726 // Step 1. (Not applicable in our implementation.)
730 if (!ToTemporalDurationRecord(cx
, args
.get(0), &duration
)) {
735 if (duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0 ||
736 duration
.days
!= 0) {
737 const char* part
= duration
.years
!= 0 ? "years"
738 : duration
.months
!= 0 ? "months"
739 : duration
.weeks
!= 0 ? "weeks"
741 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
742 JSMSG_TEMPORAL_INSTANT_BAD_DURATION
, part
);
747 if (operation
== InstantDuration::Subtract
) {
748 duration
= duration
.negate();
750 auto timeDuration
= NormalizeTimeDuration(duration
);
754 if (!AddInstant(cx
, epochNanoseconds
, timeDuration
, &ns
)) {
759 auto* result
= CreateTemporalInstant(cx
, ns
);
764 args
.rval().setObject(*result
);
769 * Temporal.Instant ( epochNanoseconds )
771 static bool InstantConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
772 CallArgs args
= CallArgsFromVp(argc
, vp
);
775 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Instant")) {
780 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
781 if (!epochNanoseconds
) {
786 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
787 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
788 JSMSG_TEMPORAL_INSTANT_INVALID
);
793 auto* result
= CreateTemporalInstant(cx
, args
, epochNanoseconds
);
798 args
.rval().setObject(*result
);
803 * Temporal.Instant.from ( item )
805 static bool Instant_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
806 CallArgs args
= CallArgsFromVp(argc
, vp
);
809 Instant epochInstant
;
810 if (!ToTemporalInstant(cx
, args
.get(0), &epochInstant
)) {
814 auto* result
= CreateTemporalInstant(cx
, epochInstant
);
818 args
.rval().setObject(*result
);
823 * Temporal.Instant.fromEpochSeconds ( epochSeconds )
825 static bool Instant_fromEpochSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
826 CallArgs args
= CallArgsFromVp(argc
, vp
);
830 if (!JS::ToNumber(cx
, args
.get(0), &epochSeconds
)) {
836 // NumberToBigInt throws a RangeError for non-integral numbers.
837 if (!IsInteger(epochSeconds
)) {
839 const char* str
= NumberToCString(&cbuf
, epochSeconds
);
841 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
842 JSMSG_TEMPORAL_INSTANT_NONINTEGER
, str
);
846 // Step 3. (Not applicable)
849 if (!IsValidEpochSeconds(epochSeconds
)) {
850 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
851 JSMSG_TEMPORAL_INSTANT_INVALID
);
856 int64_t seconds
= mozilla::AssertedCast
<int64_t>(epochSeconds
);
857 auto* result
= CreateTemporalInstant(cx
, Instant::fromSeconds(seconds
));
861 args
.rval().setObject(*result
);
866 * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )
868 static bool Instant_fromEpochMilliseconds(JSContext
* cx
, unsigned argc
,
870 CallArgs args
= CallArgsFromVp(argc
, vp
);
873 double epochMilliseconds
;
874 if (!JS::ToNumber(cx
, args
.get(0), &epochMilliseconds
)) {
880 // NumberToBigInt throws a RangeError for non-integral numbers.
881 if (!IsInteger(epochMilliseconds
)) {
883 const char* str
= NumberToCString(&cbuf
, epochMilliseconds
);
885 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
886 JSMSG_TEMPORAL_INSTANT_NONINTEGER
, str
);
890 // Step 3. (Not applicable)
893 if (!IsValidEpochMilliseconds(epochMilliseconds
)) {
894 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
895 JSMSG_TEMPORAL_INSTANT_INVALID
);
900 int64_t milliseconds
= mozilla::AssertedCast
<int64_t>(epochMilliseconds
);
902 CreateTemporalInstant(cx
, Instant::fromMilliseconds(milliseconds
));
906 args
.rval().setObject(*result
);
911 * Temporal.Instant.fromEpochMicroseconds ( epochMicroseconds )
913 static bool Instant_fromEpochMicroseconds(JSContext
* cx
, unsigned argc
,
915 CallArgs args
= CallArgsFromVp(argc
, vp
);
918 Rooted
<BigInt
*> epochMicroseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
919 if (!epochMicroseconds
) {
923 // Step 2. (Not applicable)
926 if (!IsValidEpochMicroseconds(epochMicroseconds
)) {
927 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
928 JSMSG_TEMPORAL_INSTANT_INVALID
);
933 MOZ_ALWAYS_TRUE(BigInt::isInt64(epochMicroseconds
, &i
));
936 auto* result
= CreateTemporalInstant(cx
, Instant::fromMicroseconds(i
));
940 args
.rval().setObject(*result
);
945 * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )
947 static bool Instant_fromEpochNanoseconds(JSContext
* cx
, unsigned argc
,
949 CallArgs args
= CallArgsFromVp(argc
, vp
);
952 Rooted
<BigInt
*> epochNanoseconds(cx
, js::ToBigInt(cx
, args
.get(0)));
953 if (!epochNanoseconds
) {
958 if (!IsValidEpochNanoseconds(epochNanoseconds
)) {
959 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
960 JSMSG_TEMPORAL_INSTANT_INVALID
);
965 auto* result
= CreateTemporalInstant(cx
, ToInstant(epochNanoseconds
));
969 args
.rval().setObject(*result
);
974 * Temporal.Instant.compare ( one, two )
976 static bool Instant_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
977 CallArgs args
= CallArgsFromVp(argc
, vp
);
981 if (!ToTemporalInstant(cx
, args
.get(0), &one
)) {
987 if (!ToTemporalInstant(cx
, args
.get(1), &two
)) {
992 args
.rval().setInt32(CompareEpochNanoseconds(one
, two
));
997 * get Temporal.Instant.prototype.epochSeconds
999 static bool Instant_epochSeconds(JSContext
* cx
, const CallArgs
& args
) {
1001 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1004 args
.rval().setNumber(instant
.seconds
);
1009 * get Temporal.Instant.prototype.epochSeconds
1011 static bool Instant_epochSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1013 CallArgs args
= CallArgsFromVp(argc
, vp
);
1014 return CallNonGenericMethod
<IsInstant
, Instant_epochSeconds
>(cx
, args
);
1018 * get Temporal.Instant.prototype.epochMilliseconds
1020 static bool Instant_epochMilliseconds(JSContext
* cx
, const CallArgs
& args
) {
1022 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1025 args
.rval().setNumber(instant
.floorToMilliseconds());
1030 * get Temporal.Instant.prototype.epochMilliseconds
1032 static bool Instant_epochMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1034 CallArgs args
= CallArgsFromVp(argc
, vp
);
1035 return CallNonGenericMethod
<IsInstant
, Instant_epochMilliseconds
>(cx
, args
);
1039 * get Temporal.Instant.prototype.epochMicroseconds
1041 static bool Instant_epochMicroseconds(JSContext
* cx
, const CallArgs
& args
) {
1043 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1046 auto* microseconds
=
1047 BigInt::createFromInt64(cx
, instant
.floorToMicroseconds());
1048 if (!microseconds
) {
1053 args
.rval().setBigInt(microseconds
);
1058 * get Temporal.Instant.prototype.epochMicroseconds
1060 static bool Instant_epochMicroseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1062 CallArgs args
= CallArgsFromVp(argc
, vp
);
1063 return CallNonGenericMethod
<IsInstant
, Instant_epochMicroseconds
>(cx
, args
);
1067 * get Temporal.Instant.prototype.epochNanoseconds
1069 static bool Instant_epochNanoseconds(JSContext
* cx
, const CallArgs
& args
) {
1071 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1072 auto* nanoseconds
= ToEpochNanoseconds(cx
, instant
);
1078 args
.rval().setBigInt(nanoseconds
);
1083 * get Temporal.Instant.prototype.epochNanoseconds
1085 static bool Instant_epochNanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1087 CallArgs args
= CallArgsFromVp(argc
, vp
);
1088 return CallNonGenericMethod
<IsInstant
, Instant_epochNanoseconds
>(cx
, args
);
1092 * Temporal.Instant.prototype.add ( temporalDurationLike )
1094 static bool Instant_add(JSContext
* cx
, const CallArgs
& args
) {
1095 return AddDurationToOrSubtractDurationFromInstant(cx
, InstantDuration::Add
,
1100 * Temporal.Instant.prototype.add ( temporalDurationLike )
1102 static bool Instant_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1104 CallArgs args
= CallArgsFromVp(argc
, vp
);
1105 return CallNonGenericMethod
<IsInstant
, Instant_add
>(cx
, args
);
1109 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
1111 static bool Instant_subtract(JSContext
* cx
, const CallArgs
& args
) {
1112 return AddDurationToOrSubtractDurationFromInstant(
1113 cx
, InstantDuration::Subtract
, args
);
1117 * Temporal.Instant.prototype.subtract ( temporalDurationLike )
1119 static bool Instant_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1121 CallArgs args
= CallArgsFromVp(argc
, vp
);
1122 return CallNonGenericMethod
<IsInstant
, Instant_subtract
>(cx
, args
);
1126 * Temporal.Instant.prototype.until ( other [ , options ] )
1128 static bool Instant_until(JSContext
* cx
, const CallArgs
& args
) {
1129 return DifferenceTemporalInstant(cx
, TemporalDifference::Until
, args
);
1133 * Temporal.Instant.prototype.until ( other [ , options ] )
1135 static bool Instant_until(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1137 CallArgs args
= CallArgsFromVp(argc
, vp
);
1138 return CallNonGenericMethod
<IsInstant
, Instant_until
>(cx
, args
);
1142 * Temporal.Instant.prototype.since ( other [ , options ] )
1144 static bool Instant_since(JSContext
* cx
, const CallArgs
& args
) {
1145 return DifferenceTemporalInstant(cx
, TemporalDifference::Since
, args
);
1149 * Temporal.Instant.prototype.since ( other [ , options ] )
1151 static bool Instant_since(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1153 CallArgs args
= CallArgsFromVp(argc
, vp
);
1154 return CallNonGenericMethod
<IsInstant
, Instant_since
>(cx
, args
);
1158 * Temporal.Instant.prototype.round ( roundTo )
1160 static bool Instant_round(JSContext
* cx
, const CallArgs
& args
) {
1161 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1164 auto smallestUnit
= TemporalUnit::Auto
;
1165 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
1166 auto roundingIncrement
= Increment
{1};
1167 if (args
.get(0).isString()) {
1168 // Steps 4 and 6-8. (Not applicable in our implementation.)
1171 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
1172 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
1173 TemporalUnitGroup::Time
, &smallestUnit
)) {
1177 // Steps 10-16. (Not applicable in our implementation.)
1180 Rooted
<JSObject
*> options(
1181 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
1187 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
1192 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1197 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
1198 TemporalUnitGroup::Time
, &smallestUnit
)) {
1201 if (smallestUnit
== TemporalUnit::Auto
) {
1202 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1203 JSMSG_TEMPORAL_MISSING_OPTION
, "smallestUnit");
1208 int64_t maximum
= UnitsPerDay(smallestUnit
);
1211 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
1218 auto roundedNs
= RoundTemporalInstant(instant
, roundingIncrement
,
1219 smallestUnit
, roundingMode
);
1222 auto* result
= CreateTemporalInstant(cx
, roundedNs
);
1226 args
.rval().setObject(*result
);
1231 * Temporal.Instant.prototype.round ( options )
1233 static bool Instant_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1235 CallArgs args
= CallArgsFromVp(argc
, vp
);
1236 return CallNonGenericMethod
<IsInstant
, Instant_round
>(cx
, args
);
1240 * Temporal.Instant.prototype.equals ( other )
1242 static bool Instant_equals(JSContext
* cx
, const CallArgs
& args
) {
1243 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1247 if (!ToTemporalInstant(cx
, args
.get(0), &other
)) {
1252 args
.rval().setBoolean(instant
== other
);
1257 * Temporal.Instant.prototype.equals ( other )
1259 static bool Instant_equals(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1261 CallArgs args
= CallArgsFromVp(argc
, vp
);
1262 return CallNonGenericMethod
<IsInstant
, Instant_equals
>(cx
, args
);
1266 * Temporal.Instant.prototype.toString ( [ options ] )
1268 static bool Instant_toString(JSContext
* cx
, const CallArgs
& args
) {
1269 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1271 Rooted
<TimeZoneValue
> timeZone(cx
);
1272 auto roundingMode
= TemporalRoundingMode::Trunc
;
1273 SecondsStringPrecision precision
= {Precision::Auto(),
1274 TemporalUnit::Nanosecond
, Increment
{1}};
1275 if (args
.hasDefined(0)) {
1277 Rooted
<JSObject
*> options(
1278 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
1284 auto digits
= Precision::Auto();
1285 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
1290 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
1295 auto smallestUnit
= TemporalUnit::Auto
;
1296 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
1297 TemporalUnitGroup::Time
, &smallestUnit
)) {
1302 if (smallestUnit
== TemporalUnit::Hour
) {
1303 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1304 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
, "hour",
1310 Rooted
<Value
> value(cx
);
1311 if (!GetProperty(cx
, options
, options
, cx
->names().timeZone
, &value
)) {
1316 if (!value
.isUndefined()) {
1317 if (!ToTemporalTimeZone(cx
, value
, &timeZone
)) {
1323 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
1327 auto ns
= RoundTemporalInstant(instant
, precision
.increment
, precision
.unit
,
1331 Rooted
<InstantObject
*> roundedInstant(cx
, CreateTemporalInstant(cx
, ns
));
1332 if (!roundedInstant
) {
1337 JSString
* str
= TemporalInstantToString(cx
, roundedInstant
, timeZone
,
1338 precision
.precision
);
1343 args
.rval().setString(str
);
1348 * Temporal.Instant.prototype.toString ( [ options ] )
1350 static bool Instant_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1352 CallArgs args
= CallArgsFromVp(argc
, vp
);
1353 return CallNonGenericMethod
<IsInstant
, Instant_toString
>(cx
, args
);
1357 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1359 static bool Instant_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
1360 Rooted
<InstantObject
*> instant(cx
,
1361 &args
.thisv().toObject().as
<InstantObject
>());
1364 Rooted
<TimeZoneValue
> timeZone(cx
);
1366 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1371 args
.rval().setString(str
);
1376 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1378 static bool Instant_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1380 CallArgs args
= CallArgsFromVp(argc
, vp
);
1381 return CallNonGenericMethod
<IsInstant
, Instant_toLocaleString
>(cx
, args
);
1385 * Temporal.Instant.prototype.toJSON ( )
1387 static bool Instant_toJSON(JSContext
* cx
, const CallArgs
& args
) {
1388 Rooted
<InstantObject
*> instant(cx
,
1389 &args
.thisv().toObject().as
<InstantObject
>());
1392 Rooted
<TimeZoneValue
> timeZone(cx
);
1394 TemporalInstantToString(cx
, instant
, timeZone
, Precision::Auto());
1399 args
.rval().setString(str
);
1404 * Temporal.Instant.prototype.toJSON ( )
1406 static bool Instant_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1408 CallArgs args
= CallArgsFromVp(argc
, vp
);
1409 return CallNonGenericMethod
<IsInstant
, Instant_toJSON
>(cx
, args
);
1413 * Temporal.Instant.prototype.valueOf ( )
1415 static bool Instant_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1416 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
1417 "Instant", "primitive type");
1422 * Temporal.Instant.prototype.toZonedDateTime ( item )
1424 static bool Instant_toZonedDateTime(JSContext
* cx
, const CallArgs
& args
) {
1425 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1428 Rooted
<JSObject
*> item(
1429 cx
, RequireObjectArg(cx
, "item", "toZonedDateTime", args
.get(0)));
1435 Rooted
<Value
> calendarLike(cx
);
1436 if (!GetProperty(cx
, item
, item
, cx
->names().calendar
, &calendarLike
)) {
1441 if (calendarLike
.isUndefined()) {
1442 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1443 JSMSG_TEMPORAL_MISSING_PROPERTY
, "calendar");
1448 Rooted
<CalendarValue
> calendar(cx
);
1449 if (!ToTemporalCalendar(cx
, calendarLike
, &calendar
)) {
1454 Rooted
<Value
> timeZoneLike(cx
);
1455 if (!GetProperty(cx
, item
, item
, cx
->names().timeZone
, &timeZoneLike
)) {
1460 if (timeZoneLike
.isUndefined()) {
1461 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1462 JSMSG_TEMPORAL_MISSING_PROPERTY
, "timeZone");
1467 Rooted
<TimeZoneValue
> timeZone(cx
);
1468 if (!ToTemporalTimeZone(cx
, timeZoneLike
, &timeZone
)) {
1473 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1478 args
.rval().setObject(*result
);
1483 * Temporal.Instant.prototype.toZonedDateTime ( item )
1485 static bool Instant_toZonedDateTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1487 CallArgs args
= CallArgsFromVp(argc
, vp
);
1488 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTime
>(cx
, args
);
1492 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1494 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, const CallArgs
& args
) {
1495 auto instant
= ToInstant(&args
.thisv().toObject().as
<InstantObject
>());
1498 Rooted
<TimeZoneValue
> timeZone(cx
);
1499 if (!ToTemporalTimeZone(cx
, args
.get(0), &timeZone
)) {
1504 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(cx
->names().iso8601
));
1505 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
1510 args
.rval().setObject(*result
);
1515 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1517 static bool Instant_toZonedDateTimeISO(JSContext
* cx
, unsigned argc
,
1520 CallArgs args
= CallArgsFromVp(argc
, vp
);
1521 return CallNonGenericMethod
<IsInstant
, Instant_toZonedDateTimeISO
>(cx
, args
);
1524 const JSClass
InstantObject::class_
= {
1526 JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT
) |
1527 JSCLASS_HAS_CACHED_PROTO(JSProto_Instant
),
1529 &InstantObject::classSpec_
,
1532 const JSClass
& InstantObject::protoClass_
= PlainObject::class_
;
1534 static const JSFunctionSpec Instant_methods
[] = {
1535 JS_FN("from", Instant_from
, 1, 0),
1536 JS_FN("fromEpochSeconds", Instant_fromEpochSeconds
, 1, 0),
1537 JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds
, 1, 0),
1538 JS_FN("fromEpochMicroseconds", Instant_fromEpochMicroseconds
, 1, 0),
1539 JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds
, 1, 0),
1540 JS_FN("compare", Instant_compare
, 2, 0),
1544 static const JSFunctionSpec Instant_prototype_methods
[] = {
1545 JS_FN("add", Instant_add
, 1, 0),
1546 JS_FN("subtract", Instant_subtract
, 1, 0),
1547 JS_FN("until", Instant_until
, 1, 0),
1548 JS_FN("since", Instant_since
, 1, 0),
1549 JS_FN("round", Instant_round
, 1, 0),
1550 JS_FN("equals", Instant_equals
, 1, 0),
1551 JS_FN("toString", Instant_toString
, 0, 0),
1552 JS_FN("toLocaleString", Instant_toLocaleString
, 0, 0),
1553 JS_FN("toJSON", Instant_toJSON
, 0, 0),
1554 JS_FN("valueOf", Instant_valueOf
, 0, 0),
1555 JS_FN("toZonedDateTime", Instant_toZonedDateTime
, 1, 0),
1556 JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO
, 1, 0),
1560 static const JSPropertySpec Instant_prototype_properties
[] = {
1561 JS_PSG("epochSeconds", Instant_epochSeconds
, 0),
1562 JS_PSG("epochMilliseconds", Instant_epochMilliseconds
, 0),
1563 JS_PSG("epochMicroseconds", Instant_epochMicroseconds
, 0),
1564 JS_PSG("epochNanoseconds", Instant_epochNanoseconds
, 0),
1565 JS_STRING_SYM_PS(toStringTag
, "Temporal.Instant", JSPROP_READONLY
),
1569 const ClassSpec
InstantObject::classSpec_
= {
1570 GenericCreateConstructor
<InstantConstructor
, 1, gc::AllocKind::FUNCTION
>,
1571 GenericCreatePrototype
<InstantObject
>,
1574 Instant_prototype_methods
,
1575 Instant_prototype_properties
,
1577 ClassSpec::DontDefineConstructor
,