Bug 1905842: apply code formatting via Lando
[gecko.git] / js / src / builtin / temporal / Instant.cpp
blob82be2e0a575d4595f346760e496dd3dd843935b5
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"
16 #include <algorithm>
17 #include <array>
18 #include <cstdlib>
19 #include <iterator>
20 #include <stddef.h>
21 #include <stdint.h>
22 #include <utility>
24 #include "jsnum.h"
25 #include "jspubtd.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"
45 #include "js/Class.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"
53 #include "js/Value.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"
67 using namespace js;
68 using namespace js::temporal;
70 static inline bool IsInstant(Handle<Value> v) {
71 return v.isObject() && v.toObject().is<InstantObject>();
74 /**
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)) {
83 return true;
86 // More digits than the limit, so definitely out of range.
87 if (length > std::size(digits)) {
88 return false;
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);
95 if (d < digit) {
96 return true;
98 if (d > digit) {
99 return false;
102 return true;
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) {
112 return std::array{
113 BigInt::Digit(0x1d4),
114 BigInt::Digit(0x6016'2f51'6f00'0000),
116 } else {
117 return std::array{
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) {
132 // Steps 1-3.
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);
149 // Steps 1-3.
150 return Instant::min() <= instant && instant <= Instant::max();
153 #ifdef DEBUG
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);
161 // Steps 1-3.
162 return InstantSpan::min() <= span && span <= InstantSpan::max();
164 #endif
167 * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of
168 * more than 96-bits.
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()) {
177 case 2:
178 y = digits[1];
179 [[fallthrough]];
180 case 1:
181 x = digits[0];
182 [[fallthrough]];
183 case 0:
184 break;
185 default:
186 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
188 return Int96{
189 Int96::Digits{Int96::Digit(x), Int96::Digit(x >> 32), Int96::Digit(y)},
190 ns->isNegative()};
191 } else {
192 BigInt::Digit x = 0, y = 0, z = 0;
193 switch (digits.size()) {
194 case 3:
195 z = digits[2];
196 [[fallthrough]];
197 case 2:
198 y = digits[1];
199 [[fallthrough]];
200 case 1:
201 x = digits[0];
202 [[fallthrough]];
203 case 0:
204 break;
205 default:
206 MOZ_ASSERT_UNREACHABLE("unexpected digit length");
208 return Int96{
209 Int96::Digits{Int96::Digit(x), Int96::Digit(y), Int96::Digit(z)},
210 ns->isNegative()};
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,
224 bool negative) {
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);
233 if (!result) {
234 return nullptr;
236 if (y) {
237 result->setDigit(1, y);
239 if (x) {
240 result->setDigit(0, x);
242 return result;
243 } else {
244 size_t length = digits[2] ? 3 : digits[1] ? 2 : digits[0] ? 1 : 0;
245 auto* result = BigInt::createUninitialized(cx, length, negative);
246 if (!result) {
247 return nullptr;
249 while (length--) {
250 result->setDigit(length, digits[length]);
252 return result;
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);
270 return result;
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()|.
283 uint32_t carry = 0;
285 uint32_t high = 0;
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);
293 uint32_t high = 0;
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);
302 return accumulator;
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;
321 // Step 1.
322 MOZ_ASSERT(IsValidISODateTime(dateTime));
324 // Additionally ensure the date-time value can be represented as an Instant.
325 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
327 // Steps 2-5.
328 int64_t ms = MakeDate(dateTime);
330 // Propagate the input range to the compiler.
331 int32_t nanos =
332 std::clamp(time.microsecond * 1'000 + time.nanosecond, 0, 999'999);
334 // Steps 6-8.
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)));
347 // Steps 1-6.
348 auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime);
350 // Steps 7-9.
351 return epochNanoseconds - offsetNanoseconds;
355 * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo )
357 static int32_t CompareEpochNanoseconds(const Instant& epochNanosecondsOne,
358 const Instant& epochNanosecondsTwo) {
359 // Step 1.
360 if (epochNanosecondsOne > epochNanosecondsTwo) {
361 return 1;
364 // Step 2.
365 if (epochNanosecondsOne < epochNanosecondsTwo) {
366 return -1;
369 // Step 3.
370 return 0;
374 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
376 InstantObject* js::temporal::CreateTemporalInstant(JSContext* cx,
377 const Instant& instant) {
378 // Step 1.
379 MOZ_ASSERT(IsValidEpochInstant(instant));
381 // Steps 2-3.
382 auto* object = NewBuiltinClassInstance<InstantObject>(cx);
383 if (!object) {
384 return nullptr;
387 // Step 4.
388 object->setFixedSlot(InstantObject::SECONDS_SLOT,
389 NumberValue(instant.seconds));
390 object->setFixedSlot(InstantObject::NANOSECONDS_SLOT,
391 Int32Value(instant.nanoseconds));
393 // Step 5.
394 return object;
398 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
400 static InstantObject* CreateTemporalInstant(JSContext* cx, const CallArgs& args,
401 Handle<BigInt*> epochNanoseconds) {
402 // Step 1.
403 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
405 // Steps 2-3.
406 Rooted<JSObject*> proto(cx);
407 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Instant, &proto)) {
408 return nullptr;
411 auto* object = NewObjectWithClassProto<InstantObject>(cx, proto);
412 if (!object) {
413 return nullptr;
416 // Step 4.
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));
423 // Step 5.
424 return object;
428 * ToTemporalInstant ( item )
430 Wrapped<InstantObject*> js::temporal::ToTemporalInstant(JSContext* cx,
431 Handle<Value> item) {
432 // Step 1.
433 if (item.isObject()) {
434 JSObject* itemObj = &item.toObject();
436 // Step 1.a.
437 if (itemObj->canUnwrapAs<InstantObject>()) {
438 return itemObj;
442 // Steps 1.b-d and 3-7
443 Instant epochNanoseconds;
444 if (!ToTemporalInstant(cx, item, &epochNanoseconds)) {
445 return nullptr;
448 // Step 8.
449 return CreateTemporalInstant(cx, epochNanoseconds);
453 * ToTemporalInstant ( item )
455 bool js::temporal::ToTemporalInstant(JSContext* cx, Handle<Value> item,
456 Instant* result) {
457 // Step 1.
458 Rooted<Value> primitiveValue(cx, item);
459 if (item.isObject()) {
460 JSObject* itemObj = &item.toObject();
462 // Step 1.a.
463 if (auto* instant = itemObj->maybeUnwrapIf<InstantObject>()) {
464 *result = ToInstant(instant);
465 return true;
468 // Step 1.b.
469 if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
470 *result = ToInstant(zonedDateTime);
471 return true;
474 // Steps 1.c-d.
475 if (!ToPrimitive(cx, JSTYPE_STRING, &primitiveValue)) {
476 return false;
480 // Step 2.
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");
486 return false;
488 Rooted<JSString*> string(cx, primitiveValue.toString());
490 // Steps 3-4.
491 PlainDateTime dateTime;
492 int64_t offset;
493 if (!ParseTemporalInstantString(cx, string, &dateTime, &offset)) {
494 return false;
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);
502 return false;
505 // Step 4.
506 auto epochNanoseconds =
507 GetUTCEpochNanoseconds(dateTime, InstantSpan::fromNanoseconds(offset));
509 // Step 7.
510 if (!IsValidEpochInstant(epochNanoseconds)) {
511 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
512 JSMSG_TEMPORAL_INSTANT_INVALID);
513 return false;
516 // Step 8.
517 *result = epochNanoseconds;
518 return true;
522 * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
523 * microseconds, nanoseconds )
525 bool js::temporal::AddInstant(JSContext* cx, const Instant& instant,
526 const NormalizedTimeDuration& duration,
527 Instant* result) {
528 MOZ_ASSERT(IsValidEpochInstant(instant));
529 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
531 // Step 1. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
532 auto r = instant + duration.to<InstantSpan>();
534 // Step 2.
535 if (!IsValidEpochInstant(r)) {
536 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
537 JSMSG_TEMPORAL_INSTANT_INVALID);
538 return false;
541 // Step 3.
542 *result = r;
543 return true;
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));
558 // Step 1.
559 auto diff = NormalizedTimeDurationFromEpochNanosecondsDifference(ns2, ns1);
560 MOZ_ASSERT(IsValidInstantSpan(diff.to<InstantSpan>()));
562 // Steps 2-3.
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,
586 Increment increment,
587 TemporalUnit unit,
588 TemporalRoundingMode roundingMode) {
589 MOZ_ASSERT(IsValidEpochInstant(ns));
590 MOZ_ASSERT(increment >= Increment::min());
591 MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day));
593 // Step 1.
594 MOZ_ASSERT(unit > TemporalUnit::Day);
596 // Step 2.
597 int64_t unitLength = ToNanoseconds(unit);
599 // Step 3.
600 int64_t incrementNs = increment.value() * unitLength;
601 MOZ_ASSERT(incrementNs <= ToNanoseconds(TemporalUnit::Day),
602 "incrementNs doesn't overflow instant resolution");
604 // Step 4.
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.)
618 // Step 2.
619 Instant other;
620 if (!ToTemporalInstant(cx, args.get(0), &other)) {
621 return false;
624 // Steps 3-4.
625 DifferenceSettings settings;
626 if (args.hasDefined(1)) {
627 Rooted<JSObject*> options(
628 cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
629 if (!options) {
630 return false;
633 // Step 3.
634 Rooted<PlainObject*> resolvedOptions(cx,
635 SnapshotOwnProperties(cx, options));
636 if (!resolvedOptions) {
637 return false;
640 // Step 4.
641 if (!GetDifferenceSettings(
642 cx, operation, resolvedOptions, TemporalUnitGroup::Time,
643 TemporalUnit::Nanosecond, TemporalUnit::Second, &settings)) {
644 return false;
646 } else {
647 // Steps 3-4.
648 settings = {
649 TemporalUnit::Nanosecond,
650 TemporalUnit::Second,
651 TemporalRoundingMode::Trunc,
652 Increment{1},
656 // Steps 5-6.
657 auto difference =
658 DifferenceInstant(instant, other, settings.roundingIncrement,
659 settings.smallestUnit, settings.roundingMode);
661 // Step 7.
662 TimeDuration balanced;
663 if (!BalanceTimeDuration(cx, difference, settings.largestUnit, &balanced)) {
664 return false;
667 // Step 8.
668 auto duration = balanced.toDuration();
669 if (operation == TemporalDifference::Since) {
670 duration = duration.negate();
673 auto* obj = CreateTemporalDuration(cx, duration);
674 if (!obj) {
675 return false;
678 args.rval().setObject(*obj);
679 return true;
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.)
695 // Step 2.
696 Duration duration;
697 if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
698 return false;
701 // Steps 3-6.
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"
707 : "days";
708 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
709 JSMSG_TEMPORAL_INSTANT_BAD_DURATION, part);
710 return false;
713 // Step 7.
714 if (operation == InstantDuration::Subtract) {
715 duration = duration.negate();
717 auto timeDuration = NormalizeTimeDuration(duration);
719 // Step 8.
720 Instant ns;
721 if (!AddInstant(cx, epochNanoseconds, timeDuration, &ns)) {
722 return false;
725 // Step 9.
726 auto* result = CreateTemporalInstant(cx, ns);
727 if (!result) {
728 return false;
731 args.rval().setObject(*result);
732 return true;
736 * Temporal.Instant ( epochNanoseconds )
738 static bool InstantConstructor(JSContext* cx, unsigned argc, Value* vp) {
739 CallArgs args = CallArgsFromVp(argc, vp);
741 // Step 1.
742 if (!ThrowIfNotConstructing(cx, args, "Temporal.Instant")) {
743 return false;
746 // Step 2.
747 Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
748 if (!epochNanoseconds) {
749 return false;
752 // Step 3.
753 if (!IsValidEpochNanoseconds(epochNanoseconds)) {
754 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
755 JSMSG_TEMPORAL_INSTANT_INVALID);
756 return false;
759 // Step 4.
760 auto* result = CreateTemporalInstant(cx, args, epochNanoseconds);
761 if (!result) {
762 return false;
765 args.rval().setObject(*result);
766 return true;
770 * Temporal.Instant.from ( item )
772 static bool Instant_from(JSContext* cx, unsigned argc, Value* vp) {
773 CallArgs args = CallArgsFromVp(argc, vp);
775 // Steps 1-2.
776 Instant epochInstant;
777 if (!ToTemporalInstant(cx, args.get(0), &epochInstant)) {
778 return false;
781 auto* result = CreateTemporalInstant(cx, epochInstant);
782 if (!result) {
783 return false;
785 args.rval().setObject(*result);
786 return true;
790 * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )
792 static bool Instant_fromEpochMilliseconds(JSContext* cx, unsigned argc,
793 Value* vp) {
794 CallArgs args = CallArgsFromVp(argc, vp);
796 // Step 1.
797 double epochMilliseconds;
798 if (!JS::ToNumber(cx, args.get(0), &epochMilliseconds)) {
799 return false;
802 // Step 2.
804 // NumberToBigInt throws a RangeError for non-integral numbers.
805 if (!IsInteger(epochMilliseconds)) {
806 ToCStringBuf cbuf;
807 const char* str = NumberToCString(&cbuf, epochMilliseconds);
809 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
810 JSMSG_TEMPORAL_INSTANT_NONINTEGER, str);
811 return false;
814 // Step 3. (Not applicable)
816 // Step 4.
817 if (!IsValidEpochMilliseconds(epochMilliseconds)) {
818 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
819 JSMSG_TEMPORAL_INSTANT_INVALID);
820 return false;
823 // Step 5.
824 int64_t milliseconds = mozilla::AssertedCast<int64_t>(epochMilliseconds);
825 auto* result =
826 CreateTemporalInstant(cx, Instant::fromMilliseconds(milliseconds));
827 if (!result) {
828 return false;
830 args.rval().setObject(*result);
831 return true;
835 * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )
837 static bool Instant_fromEpochNanoseconds(JSContext* cx, unsigned argc,
838 Value* vp) {
839 CallArgs args = CallArgsFromVp(argc, vp);
841 // Step 1.
842 Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
843 if (!epochNanoseconds) {
844 return false;
847 // Step 2.
848 if (!IsValidEpochNanoseconds(epochNanoseconds)) {
849 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
850 JSMSG_TEMPORAL_INSTANT_INVALID);
851 return false;
854 // Step 3.
855 auto* result = CreateTemporalInstant(cx, ToInstant(epochNanoseconds));
856 if (!result) {
857 return false;
859 args.rval().setObject(*result);
860 return true;
864 * Temporal.Instant.compare ( one, two )
866 static bool Instant_compare(JSContext* cx, unsigned argc, Value* vp) {
867 CallArgs args = CallArgsFromVp(argc, vp);
869 // Step 1.
870 Instant one;
871 if (!ToTemporalInstant(cx, args.get(0), &one)) {
872 return false;
875 // Step 2.
876 Instant two;
877 if (!ToTemporalInstant(cx, args.get(1), &two)) {
878 return false;
881 // Step 3.
882 args.rval().setInt32(CompareEpochNanoseconds(one, two));
883 return true;
887 * get Temporal.Instant.prototype.epochMilliseconds
889 static bool Instant_epochMilliseconds(JSContext* cx, const CallArgs& args) {
890 // Step 3.
891 auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
893 // Step 4-5.
894 args.rval().setNumber(instant.floorToMilliseconds());
895 return true;
899 * get Temporal.Instant.prototype.epochMilliseconds
901 static bool Instant_epochMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
902 // Steps 1-2.
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) {
911 // Step 3.
912 auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
913 auto* nanoseconds = ToEpochNanoseconds(cx, instant);
914 if (!nanoseconds) {
915 return false;
918 // Step 4.
919 args.rval().setBigInt(nanoseconds);
920 return true;
924 * get Temporal.Instant.prototype.epochNanoseconds
926 static bool Instant_epochNanoseconds(JSContext* cx, unsigned argc, Value* vp) {
927 // Steps 1-2.
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,
937 args);
941 * Temporal.Instant.prototype.add ( temporalDurationLike )
943 static bool Instant_add(JSContext* cx, unsigned argc, Value* vp) {
944 // Steps 1-2.
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) {
961 // Steps 1-2.
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) {
977 // Steps 1-2.
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) {
993 // Steps 1-2.
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>());
1004 // Steps 3-16.
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.)
1011 // Step 9.
1012 Rooted<JSString*> paramString(cx, args[0].toString());
1013 if (!GetTemporalUnitValuedOption(cx, paramString,
1014 TemporalUnitKey::SmallestUnit,
1015 TemporalUnitGroup::Time, &smallestUnit)) {
1016 return false;
1019 // Steps 10-16. (Not applicable in our implementation.)
1020 } else {
1021 // Steps 3 and 5.
1022 Rooted<JSObject*> options(
1023 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
1024 if (!options) {
1025 return false;
1028 // Steps 6-7.
1029 if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) {
1030 return false;
1033 // Step 8.
1034 if (!GetRoundingModeOption(cx, options, &roundingMode)) {
1035 return false;
1038 // Step 9.
1039 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
1040 TemporalUnitGroup::Time, &smallestUnit)) {
1041 return false;
1043 if (smallestUnit == TemporalUnit::Auto) {
1044 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1045 JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
1046 return false;
1049 // Steps 10-15.
1050 int64_t maximum = UnitsPerDay(smallestUnit);
1052 // Step 16.
1053 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
1054 true)) {
1055 return false;
1059 // Step 17.
1060 auto roundedNs = RoundTemporalInstant(instant, roundingIncrement,
1061 smallestUnit, roundingMode);
1063 // Step 18.
1064 auto* result = CreateTemporalInstant(cx, roundedNs);
1065 if (!result) {
1066 return false;
1068 args.rval().setObject(*result);
1069 return true;
1073 * Temporal.Instant.prototype.round ( options )
1075 static bool Instant_round(JSContext* cx, unsigned argc, Value* vp) {
1076 // Steps 1-2.
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>());
1087 // Step 3.
1088 Instant other;
1089 if (!ToTemporalInstant(cx, args.get(0), &other)) {
1090 return false;
1093 // Steps 4-5.
1094 args.rval().setBoolean(instant == other);
1095 return true;
1099 * Temporal.Instant.prototype.equals ( other )
1101 static bool Instant_equals(JSContext* cx, unsigned argc, Value* vp) {
1102 // Steps 1-2.
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)) {
1118 // Step 3.
1119 Rooted<JSObject*> options(
1120 cx, RequireObjectArg(cx, "options", "toString", args[0]));
1121 if (!options) {
1122 return false;
1125 // Steps 4-5.
1126 auto digits = Precision::Auto();
1127 if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) {
1128 return false;
1131 // Step 6.
1132 if (!GetRoundingModeOption(cx, options, &roundingMode)) {
1133 return false;
1136 // Step 7.
1137 auto smallestUnit = TemporalUnit::Auto;
1138 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
1139 TemporalUnitGroup::Time, &smallestUnit)) {
1140 return false;
1143 // Step 8.
1144 if (smallestUnit == TemporalUnit::Hour) {
1145 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1146 JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
1147 "smallestUnit");
1148 return false;
1151 // Step 9.
1152 Rooted<Value> value(cx);
1153 if (!GetProperty(cx, options, options, cx->names().timeZone, &value)) {
1154 return false;
1157 // Step 10.
1158 if (!value.isUndefined()) {
1159 if (!ToTemporalTimeZone(cx, value, &timeZone)) {
1160 return false;
1164 // Step 11.
1165 precision = ToSecondsStringPrecision(smallestUnit, digits);
1168 // Step 12.
1169 auto ns = RoundTemporalInstant(instant, precision.increment, precision.unit,
1170 roundingMode);
1172 // Step 13.
1173 Rooted<InstantObject*> roundedInstant(cx, CreateTemporalInstant(cx, ns));
1174 if (!roundedInstant) {
1175 return false;
1178 // Step 14.
1179 JSString* str = TemporalInstantToString(cx, roundedInstant, timeZone,
1180 precision.precision);
1181 if (!str) {
1182 return false;
1185 args.rval().setString(str);
1186 return true;
1190 * Temporal.Instant.prototype.toString ( [ options ] )
1192 static bool Instant_toString(JSContext* cx, unsigned argc, Value* vp) {
1193 // Steps 1-2.
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>());
1205 // Step 3.
1206 Rooted<TimeZoneValue> timeZone(cx);
1207 JSString* str =
1208 TemporalInstantToString(cx, instant, timeZone, Precision::Auto());
1209 if (!str) {
1210 return false;
1213 args.rval().setString(str);
1214 return true;
1218 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
1220 static bool Instant_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
1221 // Steps 1-2.
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>());
1233 // Step 3.
1234 Rooted<TimeZoneValue> timeZone(cx);
1235 JSString* str =
1236 TemporalInstantToString(cx, instant, timeZone, Precision::Auto());
1237 if (!str) {
1238 return false;
1241 args.rval().setString(str);
1242 return true;
1246 * Temporal.Instant.prototype.toJSON ( )
1248 static bool Instant_toJSON(JSContext* cx, unsigned argc, Value* vp) {
1249 // Steps 1-2.
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");
1260 return false;
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>());
1269 // Step 3.
1270 Rooted<TimeZoneValue> timeZone(cx);
1271 if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
1272 return false;
1275 // Step 4.
1276 Rooted<CalendarValue> calendar(cx, CalendarValue(CalendarId::ISO8601));
1277 auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
1278 if (!result) {
1279 return false;
1282 args.rval().setObject(*result);
1283 return true;
1287 * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
1289 static bool Instant_toZonedDateTimeISO(JSContext* cx, unsigned argc,
1290 Value* vp) {
1291 // Steps 1-2.
1292 CallArgs args = CallArgsFromVp(argc, vp);
1293 return CallNonGenericMethod<IsInstant, Instant_toZonedDateTimeISO>(cx, args);
1296 const JSClass InstantObject::class_ = {
1297 "Temporal.Instant",
1298 JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT) |
1299 JSCLASS_HAS_CACHED_PROTO(JSProto_Instant),
1300 JS_NULL_CLASS_OPS,
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),
1311 JS_FS_END,
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),
1326 JS_FS_END,
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),
1333 JS_PS_END,
1336 const ClassSpec InstantObject::classSpec_ = {
1337 GenericCreateConstructor<InstantConstructor, 1, gc::AllocKind::FUNCTION>,
1338 GenericCreatePrototype<InstantObject>,
1339 Instant_methods,
1340 nullptr,
1341 Instant_prototype_methods,
1342 Instant_prototype_properties,
1343 nullptr,
1344 ClassSpec::DontDefineConstructor,