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/Duration.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/EnumSet.h"
12 #include "mozilla/FloatingPoint.h"
13 #include "mozilla/Maybe.h"
18 #include <initializer_list>
20 #include <type_traits>
25 #include "NamespaceImports.h"
27 #include "builtin/temporal/Calendar.h"
28 #include "builtin/temporal/Instant.h"
29 #include "builtin/temporal/Int128.h"
30 #include "builtin/temporal/Int96.h"
31 #include "builtin/temporal/PlainDate.h"
32 #include "builtin/temporal/PlainDateTime.h"
33 #include "builtin/temporal/Temporal.h"
34 #include "builtin/temporal/TemporalFields.h"
35 #include "builtin/temporal/TemporalParser.h"
36 #include "builtin/temporal/TemporalRoundingMode.h"
37 #include "builtin/temporal/TemporalTypes.h"
38 #include "builtin/temporal/TemporalUnit.h"
39 #include "builtin/temporal/TimeZone.h"
40 #include "builtin/temporal/Wrapped.h"
41 #include "builtin/temporal/ZonedDateTime.h"
42 #include "gc/AllocKind.h"
43 #include "gc/Barrier.h"
44 #include "gc/GCEnum.h"
45 #include "js/CallArgs.h"
46 #include "js/CallNonGenericMethod.h"
48 #include "js/Conversions.h"
49 #include "js/ErrorReport.h"
50 #include "js/friend/ErrorMessages.h"
51 #include "js/GCVector.h"
53 #include "js/Printer.h"
54 #include "js/PropertyDescriptor.h"
55 #include "js/PropertySpec.h"
56 #include "js/RootingAPI.h"
58 #include "util/StringBuffer.h"
59 #include "vm/BigIntType.h"
60 #include "vm/BytecodeUtil.h"
61 #include "vm/GlobalObject.h"
62 #include "vm/JSAtomState.h"
63 #include "vm/JSContext.h"
64 #include "vm/JSObject.h"
65 #include "vm/ObjectOperations.h"
66 #include "vm/PlainObject.h"
67 #include "vm/StringType.h"
69 #include "vm/JSObject-inl.h"
70 #include "vm/NativeObject-inl.h"
71 #include "vm/ObjectOperations-inl.h"
74 using namespace js::temporal
;
76 static inline bool IsDuration(Handle
<Value
> v
) {
77 return v
.isObject() && v
.toObject().is
<DurationObject
>();
81 static bool IsIntegerOrInfinity(double d
) {
82 return IsInteger(d
) || std::isinf(d
);
85 static bool IsIntegerOrInfinityDuration(const Duration
& duration
) {
86 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
87 microseconds
, nanoseconds
] = duration
;
89 // Integers exceeding the Number range are represented as infinity.
91 return IsIntegerOrInfinity(years
) && IsIntegerOrInfinity(months
) &&
92 IsIntegerOrInfinity(weeks
) && IsIntegerOrInfinity(days
) &&
93 IsIntegerOrInfinity(hours
) && IsIntegerOrInfinity(minutes
) &&
94 IsIntegerOrInfinity(seconds
) && IsIntegerOrInfinity(milliseconds
) &&
95 IsIntegerOrInfinity(microseconds
) && IsIntegerOrInfinity(nanoseconds
);
98 static bool IsIntegerDuration(const Duration
& duration
) {
99 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
100 microseconds
, nanoseconds
] = duration
;
102 return IsInteger(years
) && IsInteger(months
) && IsInteger(weeks
) &&
103 IsInteger(days
) && IsInteger(hours
) && IsInteger(minutes
) &&
104 IsInteger(seconds
) && IsInteger(milliseconds
) &&
105 IsInteger(microseconds
) && IsInteger(nanoseconds
);
109 static constexpr bool IsSafeInteger(int64_t x
) {
110 constexpr int64_t MaxSafeInteger
= int64_t(1) << 53;
111 constexpr int64_t MinSafeInteger
= -MaxSafeInteger
;
112 return MinSafeInteger
< x
&& x
< MaxSafeInteger
;
115 static constexpr bool IsSafeInteger(const Int128
& x
) {
116 constexpr Int128 MaxSafeInteger
= Int128
{int64_t(1) << 53};
117 constexpr Int128 MinSafeInteger
= -MaxSafeInteger
;
118 return MinSafeInteger
< x
&& x
< MaxSafeInteger
;
122 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
123 * milliseconds, microseconds, nanoseconds )
125 int32_t js::temporal::DurationSign(const Duration
& duration
) {
126 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
128 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
129 microseconds
, nanoseconds
] = duration
;
132 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
133 milliseconds
, microseconds
, nanoseconds
}) {
150 * Normalize a nanoseconds amount into a time duration.
152 static NormalizedTimeDuration
NormalizeNanoseconds(const Int96
& nanoseconds
) {
153 // Split into seconds and nanoseconds.
154 auto [seconds
, nanos
] = nanoseconds
/ ToNanoseconds(TemporalUnit::Second
);
156 return {seconds
, nanos
};
160 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
161 * value is too large.
163 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeNanoseconds(
164 double nanoseconds
) {
165 MOZ_ASSERT(IsInteger(nanoseconds
));
167 if (auto int96
= Int96::fromInteger(nanoseconds
)) {
168 // The number of normalized seconds must not exceed `2**53 - 1`.
169 constexpr auto limit
=
170 Int96
{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second
);
172 if (int96
->abs() < limit
) {
173 return mozilla::Some(NormalizeNanoseconds(*int96
));
176 return mozilla::Nothing();
180 * Normalize a microseconds amount into a time duration.
182 static NormalizedTimeDuration
NormalizeMicroseconds(const Int96
& microseconds
) {
183 // Split into seconds and microseconds.
184 auto [seconds
, micros
] = microseconds
/ ToMicroseconds(TemporalUnit::Second
);
186 // Scale microseconds to nanoseconds.
187 int32_t nanos
= micros
* ToNanoseconds(TemporalUnit::Microsecond
);
189 return {seconds
, nanos
};
193 * Normalize a microseconds amount into a time duration. Return Nothing if the
194 * value is too large.
196 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeMicroseconds(
197 double microseconds
) {
198 MOZ_ASSERT(IsInteger(microseconds
));
200 if (auto int96
= Int96::fromInteger(microseconds
)) {
201 // The number of normalized seconds must not exceed `2**53 - 1`.
202 constexpr auto limit
=
203 Int96
{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second
);
205 if (int96
->abs() < limit
) {
206 return mozilla::Some(NormalizeMicroseconds(*int96
));
209 return mozilla::Nothing();
213 * Normalize a duration into a time duration. Return Nothing if any duration
214 * value is too large.
216 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeSeconds(
217 const Duration
& duration
) {
219 auto nanoseconds
= NormalizeNanoseconds(duration
.nanoseconds
);
223 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds
));
225 auto microseconds
= NormalizeMicroseconds(duration
.microseconds
);
229 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds
));
231 // Overflows for millis/seconds/minutes/hours/days always result in an
232 // invalid normalized time duration.
234 int64_t milliseconds
;
235 if (!mozilla::NumberEqualsInt64(duration
.milliseconds
, &milliseconds
)) {
240 if (!mozilla::NumberEqualsInt64(duration
.seconds
, &seconds
)) {
245 if (!mozilla::NumberEqualsInt64(duration
.minutes
, &minutes
)) {
250 if (!mozilla::NumberEqualsInt64(duration
.hours
, &hours
)) {
255 if (!mozilla::NumberEqualsInt64(duration
.days
, &days
)) {
259 // Compute the overall amount of milliseconds.
260 mozilla::CheckedInt64 millis
= days
;
268 millis
+= milliseconds
;
269 if (!millis
.isValid()) {
273 auto milli
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
274 if (!IsValidNormalizedTimeDuration(milli
)) {
278 // Compute the overall time duration.
279 auto result
= milli
+ *microseconds
+ *nanoseconds
;
280 if (!IsValidNormalizedTimeDuration(result
)) {
284 return mozilla::Some(result
);
287 return mozilla::Nothing();
291 * Normalize a days amount into a time duration. Return Nothing if the value is
294 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeDays(double days
) {
295 MOZ_ASSERT(IsInteger(days
));
299 if (!mozilla::NumberEqualsInt64(days
, &intDays
)) {
303 // Compute the overall amount of milliseconds.
305 mozilla::CheckedInt64(intDays
) * ToMilliseconds(TemporalUnit::Day
);
306 if (!millis
.isValid()) {
310 auto result
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
311 if (!IsValidNormalizedTimeDuration(result
)) {
315 return mozilla::Some(result
);
318 return mozilla::Nothing();
322 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
325 static NormalizedTimeDuration
NormalizeTimeDuration(
326 double hours
, double minutes
, double seconds
, double milliseconds
,
327 double microseconds
, double nanoseconds
) {
328 MOZ_ASSERT(IsInteger(hours
));
329 MOZ_ASSERT(IsInteger(minutes
));
330 MOZ_ASSERT(IsInteger(seconds
));
331 MOZ_ASSERT(IsInteger(milliseconds
));
332 MOZ_ASSERT(IsInteger(microseconds
));
333 MOZ_ASSERT(IsInteger(nanoseconds
));
336 mozilla::CheckedInt64 millis
= int64_t(hours
);
338 millis
+= int64_t(minutes
);
340 millis
+= int64_t(seconds
);
342 millis
+= int64_t(milliseconds
);
343 MOZ_ASSERT(millis
.isValid());
345 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
348 auto micros
= Int96::fromInteger(microseconds
);
351 normalized
+= NormalizeMicroseconds(*micros
);
354 auto nanos
= Int96::fromInteger(nanoseconds
);
357 normalized
+= NormalizeNanoseconds(*nanos
);
360 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
367 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
370 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
371 int32_t hours
, int32_t minutes
, int32_t seconds
, int32_t milliseconds
,
372 int32_t microseconds
, int32_t nanoseconds
) {
374 mozilla::CheckedInt64 millis
= int64_t(hours
);
376 millis
+= int64_t(minutes
);
378 millis
+= int64_t(seconds
);
380 millis
+= int64_t(milliseconds
);
381 MOZ_ASSERT(millis
.isValid());
383 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
386 normalized
+= NormalizeMicroseconds(Int96
{microseconds
});
389 normalized
+= NormalizeNanoseconds(Int96
{nanoseconds
});
392 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
399 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
402 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
403 const Duration
& duration
) {
404 MOZ_ASSERT(IsValidDuration(duration
));
406 return ::NormalizeTimeDuration(duration
.hours
, duration
.minutes
,
407 duration
.seconds
, duration
.milliseconds
,
408 duration
.microseconds
, duration
.nanoseconds
);
412 * AddNormalizedTimeDuration ( one, two )
414 static bool AddNormalizedTimeDuration(JSContext
* cx
,
415 const NormalizedTimeDuration
& one
,
416 const NormalizedTimeDuration
& two
,
417 NormalizedTimeDuration
* result
) {
418 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
419 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
422 auto sum
= one
+ two
;
425 if (!IsValidNormalizedTimeDuration(sum
)) {
426 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
427 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
437 * SubtractNormalizedTimeDuration ( one, two )
439 static bool SubtractNormalizedTimeDuration(JSContext
* cx
,
440 const NormalizedTimeDuration
& one
,
441 const NormalizedTimeDuration
& two
,
442 NormalizedTimeDuration
* result
) {
443 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
444 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
447 auto sum
= one
- two
;
450 if (!IsValidNormalizedTimeDuration(sum
)) {
451 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
452 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
462 * Add24HourDaysToNormalizedTimeDuration ( d, days )
464 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
465 JSContext
* cx
, const NormalizedTimeDuration
& d
, double days
,
466 NormalizedTimeDuration
* result
) {
467 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
468 MOZ_ASSERT(IsInteger(days
));
471 auto normalizedDays
= NormalizeDays(days
);
472 if (!normalizedDays
) {
473 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
474 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
479 auto sum
= d
+ *normalizedDays
;
480 if (!IsValidNormalizedTimeDuration(sum
)) {
481 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
482 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
492 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
494 bool js::temporal::CombineDateAndNormalizedTimeDuration(
495 JSContext
* cx
, const DateDuration
& date
, const NormalizedTimeDuration
& time
,
496 NormalizedDuration
* result
) {
497 MOZ_ASSERT(IsValidDuration(date
.toDuration()));
498 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
501 int32_t dateSign
= ::DurationSign(date
.toDuration());
504 int32_t timeSign
= NormalizedTimeDurationSign(time
);
507 if ((dateSign
* timeSign
) < 0) {
508 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
509 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN
);
514 *result
= {date
, time
};
519 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
521 NormalizedTimeDuration
522 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
523 const Instant
& one
, const Instant
& two
) {
524 MOZ_ASSERT(IsValidEpochInstant(one
));
525 MOZ_ASSERT(IsValidEpochInstant(two
));
528 auto result
= one
- two
;
531 MOZ_ASSERT(IsValidInstantSpan(result
));
534 return result
.to
<NormalizedTimeDuration
>();
538 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
539 * milliseconds, microseconds, nanoseconds )
541 bool js::temporal::IsValidDuration(const Duration
& duration
) {
542 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
544 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
545 microseconds
, nanoseconds
] = duration
;
548 int32_t sign
= DurationSign(duration
);
551 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
552 milliseconds
, microseconds
, nanoseconds
}) {
554 if (!std::isfinite(v
)) {
559 if (v
< 0 && sign
> 0) {
564 if (v
> 0 && sign
< 0) {
570 if (std::abs(years
) >= double(int64_t(1) << 32)) {
575 if (std::abs(months
) >= double(int64_t(1) << 32)) {
580 if (std::abs(weeks
) >= double(int64_t(1) << 32)) {
585 if (!NormalizeSeconds(duration
)) {
595 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
596 * milliseconds, microseconds, nanoseconds )
598 bool js::temporal::IsValidDuration(const NormalizedDuration
& duration
) {
599 auto date
= duration
.date
.toDuration();
600 return IsValidDuration(date
) &&
601 IsValidNormalizedTimeDuration(duration
.time
) &&
602 (DurationSign(date
) * NormalizedTimeDurationSign(duration
.time
) >= 0);
606 static bool ThrowInvalidDurationPart(JSContext
* cx
, double value
,
607 const char* name
, unsigned errorNumber
) {
609 const char* numStr
= NumberToCString(&cbuf
, value
);
611 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
, name
,
617 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
618 * milliseconds, microseconds, nanoseconds )
620 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
621 const Duration
& duration
) {
622 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
624 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
625 microseconds
, nanoseconds
] = duration
;
628 int32_t sign
= DurationSign(duration
);
630 auto throwIfInvalid
= [&](double v
, const char* name
) {
632 if (!std::isfinite(v
)) {
633 return ThrowInvalidDurationPart(
634 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
638 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
639 return ThrowInvalidDurationPart(cx
, v
, name
,
640 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
646 auto throwIfTooLarge
= [&](double v
, const char* name
) {
647 if (std::abs(v
) >= double(int64_t(1) << 32)) {
648 return ThrowInvalidDurationPart(
649 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
655 if (!throwIfInvalid(years
, "years")) {
658 if (!throwIfInvalid(months
, "months")) {
661 if (!throwIfInvalid(weeks
, "weeks")) {
664 if (!throwIfInvalid(days
, "days")) {
667 if (!throwIfInvalid(hours
, "hours")) {
670 if (!throwIfInvalid(minutes
, "minutes")) {
673 if (!throwIfInvalid(seconds
, "seconds")) {
676 if (!throwIfInvalid(milliseconds
, "milliseconds")) {
679 if (!throwIfInvalid(microseconds
, "microseconds")) {
682 if (!throwIfInvalid(nanoseconds
, "nanoseconds")) {
687 if (!throwIfTooLarge(years
, "years")) {
692 if (!throwIfTooLarge(months
, "months")) {
697 if (!throwIfTooLarge(weeks
, "weeks")) {
702 if (!NormalizeSeconds(duration
)) {
703 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
704 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
708 MOZ_ASSERT(IsValidDuration(duration
));
715 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
716 * milliseconds, microseconds, nanoseconds )
718 static bool ThrowIfInvalidDuration(JSContext
* cx
,
719 const DateDuration
& duration
) {
720 auto& [years
, months
, weeks
, days
] = duration
;
723 int32_t sign
= DurationSign(duration
.toDuration());
725 auto throwIfInvalid
= [&](int64_t v
, const char* name
) {
726 // Step 2.a. (Not applicable)
729 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
730 return ThrowInvalidDurationPart(cx
, v
, name
,
731 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
737 auto throwIfTooLarge
= [&](int64_t v
, const char* name
) {
738 if (std::abs(v
) >= (int64_t(1) << 32)) {
739 return ThrowInvalidDurationPart(
740 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
746 if (!throwIfInvalid(years
, "years")) {
749 if (!throwIfInvalid(months
, "months")) {
752 if (!throwIfInvalid(weeks
, "weeks")) {
755 if (!throwIfInvalid(days
, "days")) {
760 if (!throwIfTooLarge(years
, "years")) {
765 if (!throwIfTooLarge(months
, "months")) {
770 if (!throwIfTooLarge(weeks
, "weeks")) {
775 if (std::abs(days
) > ((int64_t(1) << 53) / 86400)) {
776 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
777 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
781 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
788 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
789 * seconds, milliseconds, microseconds )
791 static TemporalUnit
DefaultTemporalLargestUnit(const Duration
& duration
) {
792 MOZ_ASSERT(IsIntegerDuration(duration
));
795 if (duration
.years
!= 0) {
796 return TemporalUnit::Year
;
800 if (duration
.months
!= 0) {
801 return TemporalUnit::Month
;
805 if (duration
.weeks
!= 0) {
806 return TemporalUnit::Week
;
810 if (duration
.days
!= 0) {
811 return TemporalUnit::Day
;
815 if (duration
.hours
!= 0) {
816 return TemporalUnit::Hour
;
820 if (duration
.minutes
!= 0) {
821 return TemporalUnit::Minute
;
825 if (duration
.seconds
!= 0) {
826 return TemporalUnit::Second
;
830 if (duration
.milliseconds
!= 0) {
831 return TemporalUnit::Millisecond
;
835 if (duration
.microseconds
!= 0) {
836 return TemporalUnit::Microsecond
;
840 return TemporalUnit::Nanosecond
;
844 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
845 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
847 static DurationObject
* CreateTemporalDuration(JSContext
* cx
,
848 const CallArgs
& args
,
849 const Duration
& duration
) {
850 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
851 microseconds
, nanoseconds
] = duration
;
854 if (!ThrowIfInvalidDuration(cx
, duration
)) {
859 Rooted
<JSObject
*> proto(cx
);
860 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Duration
, &proto
)) {
864 auto* object
= NewObjectWithClassProto
<DurationObject
>(cx
, proto
);
870 // Add zero to convert -0 to +0.
871 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
872 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
873 NumberValue(months
+ (+0.0)));
874 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
875 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
876 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
877 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
878 NumberValue(minutes
+ (+0.0)));
879 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
880 NumberValue(seconds
+ (+0.0)));
881 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
882 NumberValue(milliseconds
+ (+0.0)));
883 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
884 NumberValue(microseconds
+ (+0.0)));
885 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
886 NumberValue(nanoseconds
+ (+0.0)));
893 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
894 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
896 DurationObject
* js::temporal::CreateTemporalDuration(JSContext
* cx
,
897 const Duration
& duration
) {
898 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
899 microseconds
, nanoseconds
] = duration
;
901 MOZ_ASSERT(IsInteger(years
));
902 MOZ_ASSERT(IsInteger(months
));
903 MOZ_ASSERT(IsInteger(weeks
));
904 MOZ_ASSERT(IsInteger(days
));
905 MOZ_ASSERT(IsInteger(hours
));
906 MOZ_ASSERT(IsInteger(minutes
));
907 MOZ_ASSERT(IsInteger(seconds
));
908 MOZ_ASSERT(IsInteger(milliseconds
));
909 MOZ_ASSERT(IsInteger(microseconds
));
910 MOZ_ASSERT(IsInteger(nanoseconds
));
913 if (!ThrowIfInvalidDuration(cx
, duration
)) {
918 auto* object
= NewBuiltinClassInstance
<DurationObject
>(cx
);
924 // Add zero to convert -0 to +0.
925 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
926 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
927 NumberValue(months
+ (+0.0)));
928 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
929 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
930 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
931 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
932 NumberValue(minutes
+ (+0.0)));
933 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
934 NumberValue(seconds
+ (+0.0)));
935 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
936 NumberValue(milliseconds
+ (+0.0)));
937 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
938 NumberValue(microseconds
+ (+0.0)));
939 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
940 NumberValue(nanoseconds
+ (+0.0)));
947 * ToIntegerIfIntegral ( argument )
949 static bool ToIntegerIfIntegral(JSContext
* cx
, const char* name
,
950 Handle
<Value
> argument
, double* num
) {
953 if (!JS::ToNumber(cx
, argument
, &d
)) {
958 if (!js::IsInteger(d
)) {
960 const char* numStr
= NumberToCString(&cbuf
, d
);
962 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
963 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
974 * ToIntegerIfIntegral ( argument )
976 static bool ToIntegerIfIntegral(JSContext
* cx
, Handle
<PropertyName
*> name
,
977 Handle
<Value
> argument
, double* result
) {
980 if (!JS::ToNumber(cx
, argument
, &d
)) {
985 if (!js::IsInteger(d
)) {
986 if (auto nameStr
= js::QuoteString(cx
, name
)) {
988 const char* numStr
= NumberToCString(&cbuf
, d
);
990 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
991 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
1003 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1005 static bool ToTemporalPartialDurationRecord(
1006 JSContext
* cx
, Handle
<JSObject
*> temporalDurationLike
, Duration
* result
) {
1007 // Steps 1-3. (Not applicable in our implementation.)
1009 Rooted
<Value
> value(cx
);
1012 auto getDurationProperty
= [&](Handle
<PropertyName
*> name
, double* num
) {
1013 if (!GetProperty(cx
, temporalDurationLike
, temporalDurationLike
, name
,
1018 if (!value
.isUndefined()) {
1021 if (!ToIntegerIfIntegral(cx
, name
, value
, num
)) {
1029 if (!getDurationProperty(cx
->names().days
, &result
->days
)) {
1032 if (!getDurationProperty(cx
->names().hours
, &result
->hours
)) {
1035 if (!getDurationProperty(cx
->names().microseconds
, &result
->microseconds
)) {
1038 if (!getDurationProperty(cx
->names().milliseconds
, &result
->milliseconds
)) {
1041 if (!getDurationProperty(cx
->names().minutes
, &result
->minutes
)) {
1044 if (!getDurationProperty(cx
->names().months
, &result
->months
)) {
1047 if (!getDurationProperty(cx
->names().nanoseconds
, &result
->nanoseconds
)) {
1050 if (!getDurationProperty(cx
->names().seconds
, &result
->seconds
)) {
1053 if (!getDurationProperty(cx
->names().weeks
, &result
->weeks
)) {
1056 if (!getDurationProperty(cx
->names().years
, &result
->years
)) {
1062 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1063 JSMSG_TEMPORAL_DURATION_MISSING_UNIT
);
1072 * ToTemporalDurationRecord ( temporalDurationLike )
1074 bool js::temporal::ToTemporalDurationRecord(JSContext
* cx
,
1075 Handle
<Value
> temporalDurationLike
,
1078 if (!temporalDurationLike
.isObject()) {
1080 if (!temporalDurationLike
.isString()) {
1081 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
,
1082 temporalDurationLike
, nullptr, "not a string");
1085 Rooted
<JSString
*> string(cx
, temporalDurationLike
.toString());
1088 return ParseTemporalDurationString(cx
, string
, result
);
1091 Rooted
<JSObject
*> durationLike(cx
, &temporalDurationLike
.toObject());
1094 if (auto* duration
= durationLike
->maybeUnwrapIf
<DurationObject
>()) {
1095 *result
= ToDuration(duration
);
1100 Duration duration
= {};
1103 if (!ToTemporalPartialDurationRecord(cx
, durationLike
, &duration
)) {
1108 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1118 * ToTemporalDuration ( item )
1120 Wrapped
<DurationObject
*> js::temporal::ToTemporalDuration(JSContext
* cx
,
1121 Handle
<Value
> item
) {
1123 if (item
.isObject()) {
1124 JSObject
* itemObj
= &item
.toObject();
1125 if (itemObj
->canUnwrapAs
<DurationObject
>()) {
1132 if (!ToTemporalDurationRecord(cx
, item
, &result
)) {
1137 return CreateTemporalDuration(cx
, result
);
1141 * ToTemporalDuration ( item )
1143 bool js::temporal::ToTemporalDuration(JSContext
* cx
, Handle
<Value
> item
,
1145 auto obj
= ToTemporalDuration(cx
, item
);
1150 *result
= ToDuration(&obj
.unwrap());
1155 * DaysUntil ( earlier, later )
1157 int32_t js::temporal::DaysUntil(const PlainDate
& earlier
,
1158 const PlainDate
& later
) {
1159 MOZ_ASSERT(ISODateTimeWithinLimits(earlier
));
1160 MOZ_ASSERT(ISODateTimeWithinLimits(later
));
1163 int32_t epochDaysEarlier
= MakeDay(earlier
);
1164 MOZ_ASSERT(std::abs(epochDaysEarlier
) <= 100'000'000);
1167 int32_t epochDaysLater
= MakeDay(later
);
1168 MOZ_ASSERT(std::abs(epochDaysLater
) <= 100'000'000);
1171 return epochDaysLater
- epochDaysEarlier
;
1175 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1177 static bool MoveRelativeDate(
1178 JSContext
* cx
, Handle
<CalendarRecord
> calendar
,
1179 Handle
<Wrapped
<PlainDateObject
*>> relativeTo
, const Duration
& duration
,
1180 MutableHandle
<Wrapped
<PlainDateObject
*>> relativeToResult
,
1181 int32_t* daysResult
) {
1182 auto* unwrappedRelativeTo
= relativeTo
.unwrap(cx
);
1183 if (!unwrappedRelativeTo
) {
1186 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1189 auto newDate
= AddDate(cx
, calendar
, relativeTo
, duration
);
1193 auto later
= ToPlainDate(&newDate
.unwrap());
1194 relativeToResult
.set(newDate
);
1197 *daysResult
= DaysUntil(relativeToDate
, later
);
1198 MOZ_ASSERT(std::abs(*daysResult
) <= 200'000'000);
1205 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1206 * months, weeks, days, precalculatedPlainDateTime )
1208 static bool MoveRelativeZonedDateTime(
1209 JSContext
* cx
, Handle
<ZonedDateTime
> zonedDateTime
,
1210 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
1211 const DateDuration
& duration
,
1212 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1213 MutableHandle
<ZonedDateTime
> result
) {
1215 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1216 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1219 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1220 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1223 Instant intermediateNs
;
1224 if (precalculatedPlainDateTime
) {
1225 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1226 duration
, *precalculatedPlainDateTime
,
1231 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1232 duration
, &intermediateNs
)) {
1236 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
1239 result
.set(ZonedDateTime
{intermediateNs
, zonedDateTime
.timeZone(),
1240 zonedDateTime
.calendar()});
1245 * Split duration into full days and remainding nanoseconds.
1247 static NormalizedTimeAndDays
NormalizedTimeDurationToDays(
1248 const NormalizedTimeDuration
& duration
) {
1249 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1251 auto [seconds
, nanoseconds
] = duration
;
1252 if (seconds
< 0 && nanoseconds
> 0) {
1254 nanoseconds
-= 1'000'000'000;
1257 int64_t days
= seconds
/ ToSeconds(TemporalUnit::Day
);
1258 seconds
= seconds
% ToSeconds(TemporalUnit::Day
);
1260 int64_t time
= seconds
* ToNanoseconds(TemporalUnit::Second
) + nanoseconds
;
1262 constexpr int64_t dayLength
= ToNanoseconds(TemporalUnit::Day
);
1263 MOZ_ASSERT(std::abs(time
) < dayLength
);
1265 return {days
, time
, dayLength
};
1269 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1270 * microseconds, nanoseconds )
1272 static TimeDuration
CreateTimeDurationRecord(int64_t days
, int64_t hours
,
1273 int64_t minutes
, int64_t seconds
,
1274 int64_t milliseconds
,
1275 double microseconds
,
1276 double nanoseconds
) {
1278 MOZ_ASSERT(IsValidDuration(
1279 {0, 0, 0, double(days
), double(hours
), double(minutes
), double(seconds
),
1280 double(milliseconds
), microseconds
, nanoseconds
}));
1282 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1283 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1284 MOZ_ASSERT(IsSafeInteger(days
));
1285 MOZ_ASSERT(IsSafeInteger(hours
));
1286 MOZ_ASSERT(IsSafeInteger(minutes
));
1287 MOZ_ASSERT(IsSafeInteger(seconds
));
1289 // |milliseconds| is explicitly casted to double by consumers, so we can also
1290 // omit the `ℝ(𝔽(x))` conversion.
1293 // NB: Adds +0.0 to correctly handle negative zero.
1300 microseconds
+ (+0.0),
1301 nanoseconds
+ (+0.0),
1306 * BalanceTimeDuration ( norm, largestUnit )
1308 TimeDuration
js::temporal::BalanceTimeDuration(
1309 const NormalizedTimeDuration
& duration
, TemporalUnit largestUnit
) {
1310 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1312 auto [seconds
, nanoseconds
] = duration
;
1314 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1315 // Convert these back to their absolute value and adjust the seconds part
1318 // For example the nanoseconds duration |-1n| is represented as the
1319 // duration {seconds: -1, nanoseconds: 999'999'999}.
1320 if (seconds
< 0 && nanoseconds
> 0) {
1322 nanoseconds
-= ToNanoseconds(TemporalUnit::Second
);
1328 int64_t minutes
= 0;
1329 int64_t milliseconds
= 0;
1330 int64_t microseconds
= 0;
1332 // Steps 2-3. (Not applicable in our implementation.)
1334 // We don't need to convert to positive numbers, because integer division
1335 // truncates and the %-operator has modulo semantics.
1338 switch (largestUnit
) {
1340 case TemporalUnit::Year
:
1341 case TemporalUnit::Month
:
1342 case TemporalUnit::Week
:
1343 case TemporalUnit::Day
: {
1345 microseconds
= nanoseconds
/ 1000;
1348 nanoseconds
= nanoseconds
% 1000;
1351 milliseconds
= microseconds
/ 1000;
1354 microseconds
= microseconds
% 1000;
1356 // Steps 4.e-f. (Not applicable)
1357 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1360 minutes
= seconds
/ 60;
1363 seconds
= seconds
% 60;
1366 hours
= minutes
/ 60;
1369 minutes
= minutes
% 60;
1381 case TemporalUnit::Hour
: {
1383 microseconds
= nanoseconds
/ 1000;
1386 nanoseconds
= nanoseconds
% 1000;
1389 milliseconds
= microseconds
/ 1000;
1392 microseconds
= microseconds
% 1000;
1394 // Steps 5.e-f. (Not applicable)
1395 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1398 minutes
= seconds
/ 60;
1401 seconds
= seconds
% 60;
1404 hours
= minutes
/ 60;
1407 minutes
= minutes
% 60;
1412 case TemporalUnit::Minute
: {
1414 microseconds
= nanoseconds
/ 1000;
1417 nanoseconds
= nanoseconds
% 1000;
1420 milliseconds
= microseconds
/ 1000;
1423 microseconds
= microseconds
% 1000;
1425 // Steps 6.e-f. (Not applicable)
1426 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1429 minutes
= seconds
/ 60;
1432 seconds
= seconds
% 60;
1438 case TemporalUnit::Second
: {
1440 microseconds
= nanoseconds
/ 1000;
1443 nanoseconds
= nanoseconds
% 1000;
1446 milliseconds
= microseconds
/ 1000;
1449 microseconds
= microseconds
% 1000;
1451 // Steps 7.e-f. (Not applicable)
1452 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1458 case TemporalUnit::Millisecond
: {
1459 static_assert((NormalizedTimeDuration::max().seconds
+ 1) *
1460 ToMilliseconds(TemporalUnit::Second
) <=
1462 "total number duration milliseconds fits into int64");
1464 int64_t millis
= seconds
* ToMilliseconds(TemporalUnit::Second
);
1466 // Set to zero per step 1.
1470 microseconds
= nanoseconds
/ 1000;
1473 nanoseconds
= nanoseconds
% 1000;
1476 milliseconds
= microseconds
/ 1000;
1479 microseconds
= microseconds
% 1000;
1481 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1482 milliseconds
+= millis
;
1488 case TemporalUnit::Microsecond
: {
1490 int64_t microseconds
= nanoseconds
/ 1000;
1493 nanoseconds
= nanoseconds
% 1000;
1495 MOZ_ASSERT(std::abs(microseconds
) <= 999'999);
1497 std::fma(double(seconds
), ToMicroseconds(TemporalUnit::Second
),
1498 double(microseconds
));
1501 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros
,
1502 double(nanoseconds
));
1506 case TemporalUnit::Nanosecond
: {
1507 MOZ_ASSERT(std::abs(nanoseconds
) <= 999'999'999);
1509 std::fma(double(seconds
), ToNanoseconds(TemporalUnit::Second
),
1510 double(nanoseconds
));
1513 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos
);
1516 case TemporalUnit::Auto
:
1517 MOZ_CRASH("Unexpected temporal unit");
1521 return CreateTimeDurationRecord(days
, hours
, minutes
, seconds
, milliseconds
,
1522 double(microseconds
), double(nanoseconds
));
1526 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1527 * timeZoneRec, precalculatedPlainDateTime )
1529 static bool BalanceTimeDurationRelative(
1530 JSContext
* cx
, const NormalizedDuration
& duration
, TemporalUnit largestUnit
,
1531 Handle
<ZonedDateTime
> relativeTo
, Handle
<TimeZoneRecord
> timeZone
,
1532 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1533 TimeDuration
* result
) {
1534 MOZ_ASSERT(IsValidDuration(duration
));
1537 const auto& startNs
= relativeTo
.instant();
1540 const auto& startInstant
= startNs
;
1543 auto intermediateNs
= startNs
;
1546 PlainDateTime startDateTime
;
1547 if (duration
.date
.days
!= 0) {
1549 if (!precalculatedPlainDateTime
) {
1550 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1553 precalculatedPlainDateTime
=
1554 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1558 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
1559 if (!AddDaysToZonedDateTime(cx
, startInstant
, *precalculatedPlainDateTime
,
1560 timeZone
, isoCalendar
, duration
.date
.days
,
1568 if (!AddInstant(cx
, intermediateNs
, duration
.time
, &endNs
)) {
1571 MOZ_ASSERT(IsValidEpochInstant(endNs
));
1575 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startInstant
);
1578 if (normalized
== NormalizedTimeDuration
{}) {
1585 if (TemporalUnit::Year
<= largestUnit
&& largestUnit
<= TemporalUnit::Day
) {
1587 if (!precalculatedPlainDateTime
) {
1588 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1591 precalculatedPlainDateTime
=
1592 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1596 NormalizedTimeAndDays timeAndDays
;
1597 if (!NormalizedTimeDurationToDays(cx
, normalized
, relativeTo
, timeZone
,
1598 *precalculatedPlainDateTime
,
1604 days
= timeAndDays
.days
;
1607 normalized
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
1608 MOZ_ASSERT_IF(days
> 0, normalized
>= NormalizedTimeDuration
{});
1609 MOZ_ASSERT_IF(days
< 0, normalized
<= NormalizedTimeDuration
{});
1612 largestUnit
= TemporalUnit::Hour
;
1616 auto balanceResult
= BalanceTimeDuration(normalized
, largestUnit
);
1621 balanceResult
.hours
,
1622 balanceResult
.minutes
,
1623 balanceResult
.seconds
,
1624 balanceResult
.milliseconds
,
1625 balanceResult
.microseconds
,
1626 balanceResult
.nanoseconds
,
1628 MOZ_ASSERT(IsValidDuration(result
->toDuration()));
1633 * CreateDateDurationRecord ( years, months, weeks, days )
1635 static DateDuration
CreateDateDurationRecord(int64_t years
, int64_t months
,
1636 int64_t weeks
, int64_t days
) {
1637 MOZ_ASSERT(IsValidDuration(Duration
{
1643 return {years
, months
, weeks
, days
};
1647 * CreateDateDurationRecord ( years, months, weeks, days )
1649 static bool CreateDateDurationRecord(JSContext
* cx
, int64_t years
,
1650 int64_t months
, int64_t weeks
,
1651 int64_t days
, DateDuration
* result
) {
1652 auto duration
= DateDuration
{years
, months
, weeks
, days
};
1653 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1661 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration
& duration
,
1662 TemporalUnit largestUnit
) {
1663 MOZ_ASSERT(largestUnit
!= TemporalUnit::Auto
);
1665 // Steps 2, 3.a-b, 4.a-b, 6-7.
1666 return (largestUnit
> TemporalUnit::Year
&& duration
.years
!= 0) ||
1667 (largestUnit
> TemporalUnit::Month
&& duration
.months
!= 0) ||
1668 (largestUnit
> TemporalUnit::Week
&& duration
.weeks
!= 0);
1672 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1673 * plainRelativeTo, calendarRec )
1675 static bool UnbalanceDateDurationRelative(
1676 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1677 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1678 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1679 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
1681 auto [years
, months
, weeks
, days
] = duration
;
1683 // Step 1. (Not applicable in our implementation.)
1685 // Steps 2, 3.a, 4.a, and 6.
1686 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1687 // Steps 2.a, 3.a, 4.a, and 6.
1693 if (largestUnit
== TemporalUnit::Month
) {
1694 // Step 3.a. (Handled above)
1695 MOZ_ASSERT(years
!= 0);
1697 // Step 3.b. (Not applicable in our implementation.)
1701 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1705 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1708 auto yearsDuration
= Duration
{double(years
)};
1711 Rooted
<Wrapped
<PlainDateObject
*>> later(
1712 cx
, CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsDuration
));
1718 Duration untilResult
;
1719 if (!CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
,
1720 TemporalUnit::Month
, &untilResult
)) {
1725 int64_t yearsInMonths
= int64_t(untilResult
.months
);
1728 return CreateDateDurationRecord(cx
, 0, months
+ yearsInMonths
, weeks
, days
,
1733 if (largestUnit
== TemporalUnit::Week
) {
1734 // Step 4.a. (Handled above)
1735 MOZ_ASSERT(years
!= 0 || months
!= 0);
1737 // Step 4.b. (Not applicable in our implementation.)
1741 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1744 auto yearsMonthsDuration
= Duration
{double(years
), double(months
)};
1748 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsDuration
);
1752 auto laterDate
= ToPlainDate(&later
.unwrap());
1754 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1755 if (!unwrappedRelativeTo
) {
1758 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1761 int32_t yearsMonthsInDays
= DaysUntil(relativeToDate
, laterDate
);
1764 return CreateDateDurationRecord(cx
, 0, 0, weeks
, days
+ yearsMonthsInDays
,
1768 // Step 5. (Not applicable in our implementation.)
1770 // Step 6. (Handled above)
1771 MOZ_ASSERT(years
!= 0 || months
!= 0 || weeks
!= 0);
1773 // FIXME: why don't we unconditionally throw an error for missing calendars?
1775 // Step 7. (Not applicable in our implementation.)
1779 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1782 auto yearsMonthsWeeksDuration
=
1783 Duration
{double(years
), double(months
), double(weeks
)};
1787 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsWeeksDuration
);
1791 auto laterDate
= ToPlainDate(&later
.unwrap());
1793 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1794 if (!unwrappedRelativeTo
) {
1797 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1800 int32_t yearsMonthsWeeksInDay
= DaysUntil(relativeToDate
, laterDate
);
1803 return CreateDateDurationRecord(cx
, 0, 0, 0, days
+ yearsMonthsWeeksInDay
,
1808 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1809 * plainRelativeTo, calendarRec )
1811 static bool UnbalanceDateDurationRelative(JSContext
* cx
,
1812 const DateDuration
& duration
,
1813 TemporalUnit largestUnit
,
1814 DateDuration
* result
) {
1815 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
1817 // Step 1. (Not applicable.)
1819 // Steps 2, 3.a, 4.a, and 6.
1820 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1821 // Steps 2.a, 3.a, 4.a, and 6.
1826 // Step 5. (Not applicable.)
1828 // Steps 3.b, 4.b, and 7.
1829 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1830 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
, "calendar");
1835 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1836 * smallestUnit, plainRelativeTo, calendarRec )
1838 static bool BalanceDateDurationRelative(
1839 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1840 TemporalUnit smallestUnit
,
1841 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1842 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1843 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
1844 MOZ_ASSERT(largestUnit
<= smallestUnit
);
1846 auto [years
, months
, weeks
, days
] = duration
;
1848 // FIXME: spec issue - effectful code paths should be more fine-grained
1849 // similar to UnbalanceDateDurationRelative. For example:
1850 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1851 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1852 // 3. Else if days = 0, then no-op.
1854 // Also note that |weeks| is never balanced, even when non-zero.
1856 // Step 1. (Not applicable in our implementation.)
1859 if (largestUnit
> TemporalUnit::Week
||
1860 (years
== 0 && months
== 0 && weeks
== 0 && days
== 0)) {
1867 if (!plainRelativeTo
) {
1868 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1869 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
1876 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1880 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1882 // Steps 8-9. (Not applicable in our implementation.)
1884 auto untilAddedDate
= [&](const Duration
& duration
, Duration
* untilResult
) {
1885 Rooted
<Wrapped
<PlainDateObject
*>> later(
1886 cx
, AddDate(cx
, calendar
, plainRelativeTo
, duration
));
1891 return CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
, largestUnit
,
1896 if (largestUnit
== TemporalUnit::Year
) {
1898 if (smallestUnit
== TemporalUnit::Week
) {
1900 MOZ_ASSERT(days
== 0);
1903 auto yearsMonthsDuration
= Duration
{double(years
), double(months
)};
1905 // Steps 10.a.iii-iv.
1906 Duration untilResult
;
1907 if (!untilAddedDate(yearsMonthsDuration
, &untilResult
)) {
1912 *result
= CreateDateDurationRecord(int64_t(untilResult
.years
),
1913 int64_t(untilResult
.months
), weeks
, 0);
1918 auto yearsMonthsWeeksDaysDuration
=
1919 Duration
{double(years
), double(months
), double(weeks
), double(days
)};
1922 Duration untilResult
;
1923 if (!untilAddedDate(yearsMonthsWeeksDaysDuration
, &untilResult
)) {
1927 // FIXME: spec bug - CreateDateDurationRecord is infallible
1928 // https://github.com/tc39/proposal-temporal/issues/2750
1931 *result
= CreateDateDurationRecord(
1932 int64_t(untilResult
.years
), int64_t(untilResult
.months
),
1933 int64_t(untilResult
.weeks
), int64_t(untilResult
.days
));
1938 if (largestUnit
== TemporalUnit::Month
) {
1940 MOZ_ASSERT(years
== 0);
1943 if (smallestUnit
== TemporalUnit::Week
) {
1945 MOZ_ASSERT(days
== 0);
1948 *result
= CreateDateDurationRecord(0, months
, weeks
, 0);
1953 auto monthsWeeksDaysDuration
=
1954 Duration
{0, double(months
), double(weeks
), double(days
)};
1957 Duration untilResult
;
1958 if (!untilAddedDate(monthsWeeksDaysDuration
, &untilResult
)) {
1962 // FIXME: spec bug - CreateDateDurationRecord is infallible
1963 // https://github.com/tc39/proposal-temporal/issues/2750
1966 *result
= CreateDateDurationRecord(0, int64_t(untilResult
.months
),
1967 int64_t(untilResult
.weeks
),
1968 int64_t(untilResult
.days
));
1973 MOZ_ASSERT(largestUnit
== TemporalUnit::Week
);
1976 MOZ_ASSERT(years
== 0);
1979 MOZ_ASSERT(months
== 0);
1982 auto weeksDaysDuration
= Duration
{0, 0, double(weeks
), double(days
)};
1985 Duration untilResult
;
1986 if (!untilAddedDate(weeksDaysDuration
, &untilResult
)) {
1990 // FIXME: spec bug - CreateDateDurationRecord is infallible
1991 // https://github.com/tc39/proposal-temporal/issues/2750
1994 *result
= CreateDateDurationRecord(0, 0, int64_t(untilResult
.weeks
),
1995 int64_t(untilResult
.days
));
2000 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2001 * smallestUnit, plainRelativeTo, calendarRec )
2003 bool js::temporal::BalanceDateDurationRelative(
2004 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
2005 TemporalUnit smallestUnit
,
2006 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2007 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
2008 MOZ_ASSERT(plainRelativeTo
);
2009 MOZ_ASSERT(calendar
.receiver());
2011 return ::BalanceDateDurationRelative(cx
, duration
, largestUnit
, smallestUnit
,
2012 plainRelativeTo
, calendar
, result
);
2016 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2017 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2018 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2020 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2022 MOZ_ASSERT(IsValidDuration(one
));
2023 MOZ_ASSERT(IsValidDuration(two
));
2025 // Steps 1-2. (Not applicable)
2028 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2031 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2034 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2037 if (largestUnit
<= TemporalUnit::Week
) {
2038 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2039 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
2045 auto normalized1
= NormalizeTimeDuration(one
);
2048 auto normalized2
= NormalizeTimeDuration(two
);
2051 NormalizedTimeDuration normalized
;
2052 if (!AddNormalizedTimeDuration(cx
, normalized1
, normalized2
, &normalized
)) {
2057 if (!Add24HourDaysToNormalizedTimeDuration(
2058 cx
, normalized
, one
.days
+ two
.days
, &normalized
)) {
2063 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2066 *result
= balanced
.toDuration();
2071 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2072 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2073 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2075 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2076 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2077 Handle
<CalendarRecord
> calendar
, Duration
* result
) {
2078 MOZ_ASSERT(IsValidDuration(one
));
2079 MOZ_ASSERT(IsValidDuration(two
));
2081 // Steps 1-2. (Not applicable)
2083 // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
2087 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2090 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2093 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2095 // Step 6. (Not applicable)
2097 // Step 7.a. (Not applicable in our implementation.)
2100 auto dateDuration1
= Duration
{one
.years
, one
.months
, one
.weeks
, one
.days
};
2103 auto dateDuration2
= Duration
{two
.years
, two
.months
, two
.weeks
, two
.days
};
2105 // FIXME: spec issue - calendarUnitsPresent is unused.
2108 [[maybe_unused
]] bool calendarUnitsPresent
= true;
2111 if (dateDuration1
.years
== 0 && dateDuration1
.months
== 0 &&
2112 dateDuration1
.weeks
== 0 && dateDuration2
.years
== 0 &&
2113 dateDuration2
.months
== 0 && dateDuration2
.weeks
== 0) {
2114 calendarUnitsPresent
= false;
2118 Rooted
<Wrapped
<PlainDateObject
*>> intermediate(
2119 cx
, AddDate(cx
, calendar
, plainRelativeTo
, dateDuration1
));
2120 if (!intermediate
) {
2125 Rooted
<Wrapped
<PlainDateObject
*>> end(
2126 cx
, AddDate(cx
, calendar
, intermediate
, dateDuration2
));
2132 auto dateLargestUnit
= std::min(TemporalUnit::Day
, largestUnit
);
2135 Duration dateDifference
;
2136 if (!DifferenceDate(cx
, calendar
, plainRelativeTo
, end
, dateLargestUnit
,
2142 auto normalized1
= NormalizeTimeDuration(one
);
2145 auto normalized2
= NormalizeTimeDuration(two
);
2148 NormalizedTimeDuration normalized1WithDays
;
2149 if (!Add24HourDaysToNormalizedTimeDuration(
2150 cx
, normalized1
, dateDifference
.days
, &normalized1WithDays
)) {
2155 NormalizedTimeDuration normalized
;
2156 if (!AddNormalizedTimeDuration(cx
, normalized1WithDays
, normalized2
,
2162 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2166 dateDifference
.years
, dateDifference
.months
,
2167 dateDifference
.weeks
, double(balanced
.days
),
2168 double(balanced
.hours
), double(balanced
.minutes
),
2169 double(balanced
.seconds
), double(balanced
.milliseconds
),
2170 balanced
.microseconds
, balanced
.nanoseconds
,
2172 MOZ_ASSERT(IsValidDuration(*result
));
2177 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2178 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2179 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2181 static bool AddDuration(
2182 JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2183 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2184 Handle
<TimeZoneRecord
> timeZone
,
2185 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2187 // Steps 1-2. (Not applicable)
2190 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2193 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2196 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2198 // Steps 6-7. (Not applicable)
2200 // Steps 8-9. (Not applicable in our implementation.)
2202 // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
2206 // 10. If largestUnit is one of "year", "month", "week", or "day", then
2207 // a. If precalculatedPlainDateTime is undefined, then
2208 // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
2210 // i. Let startDateTime be precalculatedPlainDateTime.
2211 // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
2212 // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
2213 // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
2214 // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
2215 // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
2216 // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
2217 // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
2222 bool startDateTimeNeeded
= largestUnit
<= TemporalUnit::Day
;
2225 if (!startDateTimeNeeded
) {
2226 // Steps 11-12. (Not applicable)
2229 auto normalized1
= NormalizeTimeDuration(one
);
2232 auto normalized2
= NormalizeTimeDuration(two
);
2234 // Step 15. (Inlined AddZonedDateTime, step 6.)
2235 Instant intermediateNs
;
2236 if (!AddInstant(cx
, zonedRelativeTo
.instant(), normalized1
,
2240 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2242 // Step 16. (Inlined AddZonedDateTime, step 6.)
2244 if (!AddInstant(cx
, intermediateNs
, normalized2
, &endNs
)) {
2247 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2250 auto normalized
= NormalizedTimeDurationFromEpochNanosecondsDifference(
2251 endNs
, zonedRelativeTo
.instant());
2254 auto balanced
= BalanceTimeDuration(normalized
, largestUnit
);
2257 *result
= balanced
.toDuration();
2262 PlainDateTime startDateTime
;
2263 if (!precalculatedPlainDateTime
) {
2264 if (!GetPlainDateTimeFor(cx
, timeZone
, zonedRelativeTo
.instant(),
2269 startDateTime
= *precalculatedPlainDateTime
;
2273 auto normalized1
= CreateNormalizedDurationRecord(one
);
2276 auto normalized2
= CreateNormalizedDurationRecord(two
);
2279 Instant intermediateNs
;
2280 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2281 normalized1
, startDateTime
, &intermediateNs
)) {
2284 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2288 if (!AddZonedDateTime(cx
, intermediateNs
, timeZone
, calendar
, normalized2
,
2292 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2294 // Step 17. (Not applicable)
2297 NormalizedDuration difference
;
2298 if (!DifferenceZonedDateTime(cx
, zonedRelativeTo
.instant(), endNs
, timeZone
,
2299 calendar
, largestUnit
, startDateTime
,
2305 auto balanced
= BalanceTimeDuration(difference
.time
, TemporalUnit::Hour
);
2309 double(difference
.date
.years
), double(difference
.date
.months
),
2310 double(difference
.date
.weeks
), double(difference
.date
.days
),
2311 double(balanced
.hours
), double(balanced
.minutes
),
2312 double(balanced
.seconds
), double(balanced
.milliseconds
),
2313 balanced
.microseconds
, balanced
.nanoseconds
,
2315 MOZ_ASSERT(IsValidDuration(*result
));
2320 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2321 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2322 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2324 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2325 Handle
<ZonedDateTime
> zonedRelativeTo
,
2326 Handle
<CalendarRecord
> calendar
,
2327 Handle
<TimeZoneRecord
> timeZone
, Duration
* result
) {
2328 return AddDuration(cx
, one
, two
, zonedRelativeTo
, calendar
, timeZone
,
2329 mozilla::Nothing(), result
);
2333 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2334 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2335 * precalculatedPlainDateTime )
2337 static bool AdjustRoundedDurationDays(
2338 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2339 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2340 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2341 Handle
<TimeZoneRecord
> timeZone
,
2342 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2343 NormalizedDuration
* result
) {
2344 MOZ_ASSERT(IsValidDuration(duration
));
2347 if ((TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
) ||
2348 (unit
== TemporalUnit::Nanosecond
&& increment
== Increment
{1})) {
2353 // The increment is limited for all smaller temporal units.
2354 MOZ_ASSERT(increment
< MaximumTemporalDurationRoundingIncrement(unit
));
2357 MOZ_ASSERT(precalculatedPlainDateTime
);
2360 int32_t direction
= NormalizedTimeDurationSign(duration
.time
);
2364 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2365 duration
.date
, *precalculatedPlainDateTime
,
2369 MOZ_ASSERT(IsValidEpochInstant(dayStart
));
2372 PlainDateTime dayStartDateTime
;
2373 if (!GetPlainDateTimeFor(cx
, timeZone
, dayStart
, &dayStartDateTime
)) {
2379 if (!AddDaysToZonedDateTime(cx
, dayStart
, dayStartDateTime
, timeZone
,
2380 zonedRelativeTo
.calendar(), direction
, &dayEnd
)) {
2383 MOZ_ASSERT(IsValidEpochInstant(dayEnd
));
2387 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd
, dayStart
);
2388 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs
.to
<InstantSpan
>()));
2391 NormalizedTimeDuration oneDayLess
;
2392 if (!SubtractNormalizedTimeDuration(cx
, duration
.time
, dayLengthNs
,
2398 int32_t oneDayLessSign
= NormalizedTimeDurationSign(oneDayLess
);
2399 if ((direction
> 0 && oneDayLessSign
< 0) ||
2400 (direction
< 0 && oneDayLessSign
> 0)) {
2406 Duration adjustedDateDuration
;
2407 if (!AddDuration(cx
, duration
.date
.toDuration(), {0, 0, 0, double(direction
)},
2408 zonedRelativeTo
, calendar
, timeZone
,
2409 precalculatedPlainDateTime
, &adjustedDateDuration
)) {
2414 auto roundedTime
= RoundDuration(oneDayLess
, increment
, unit
, roundingMode
);
2417 return CombineDateAndNormalizedTimeDuration(
2418 cx
, adjustedDateDuration
.toDateDuration(), roundedTime
, result
);
2422 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2423 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2424 * precalculatedPlainDateTime )
2426 bool js::temporal::AdjustRoundedDurationDays(
2427 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2428 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2429 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2430 Handle
<TimeZoneRecord
> timeZone
,
2431 const PlainDateTime
& precalculatedPlainDateTime
,
2432 NormalizedDuration
* result
) {
2433 return ::AdjustRoundedDurationDays(
2434 cx
, duration
, increment
, unit
, roundingMode
, zonedRelativeTo
, calendar
,
2435 timeZone
, mozilla::SomeRef(precalculatedPlainDateTime
), result
);
2438 static bool BigIntToStringBuilder(JSContext
* cx
, Handle
<BigInt
*> num
,
2439 JSStringBuilder
& sb
) {
2440 MOZ_ASSERT(!num
->isNegative());
2442 JSLinearString
* str
= BigInt::toString
<CanGC
>(cx
, num
, 10);
2446 return sb
.append(str
);
2449 static bool NumberToStringBuilder(JSContext
* cx
, int64_t num
,
2450 JSStringBuilder
& sb
) {
2451 MOZ_ASSERT(num
>= 0);
2452 MOZ_ASSERT(num
< int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT
));
2456 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
2458 return sb
.append(numStr
, length
);
2461 static bool NumberToStringBuilder(JSContext
* cx
, double num
,
2462 JSStringBuilder
& sb
) {
2463 MOZ_ASSERT(IsInteger(num
));
2464 MOZ_ASSERT(num
>= 0);
2466 if (num
< DOUBLE_INTEGRAL_PRECISION_LIMIT
) {
2469 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
2471 return sb
.append(numStr
, length
);
2474 Rooted
<BigInt
*> bi(cx
, BigInt::createFromDouble(cx
, num
));
2478 return BigIntToStringBuilder(cx
, bi
, sb
);
2481 static Duration
AbsoluteDuration(const Duration
& duration
) {
2483 std::abs(duration
.years
), std::abs(duration
.months
),
2484 std::abs(duration
.weeks
), std::abs(duration
.days
),
2485 std::abs(duration
.hours
), std::abs(duration
.minutes
),
2486 std::abs(duration
.seconds
), std::abs(duration
.milliseconds
),
2487 std::abs(duration
.microseconds
), std::abs(duration
.nanoseconds
),
2492 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2494 [[nodiscard
]] static bool FormatFractionalSeconds(JSStringBuilder
& result
,
2495 int32_t subSecondNanoseconds
,
2496 Precision precision
) {
2497 MOZ_ASSERT(0 <= subSecondNanoseconds
&& subSecondNanoseconds
< 1'000'000'000);
2498 MOZ_ASSERT(precision
!= Precision::Minute());
2501 if (precision
== Precision::Auto()) {
2503 if (subSecondNanoseconds
== 0) {
2507 // Step 3. (Reordered)
2508 if (!result
.append('.')) {
2513 uint32_t k
= 100'000'000;
2515 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2518 subSecondNanoseconds
%= k
;
2520 } while (subSecondNanoseconds
);
2523 uint8_t p
= precision
.value();
2528 // Step 3. (Reordered)
2529 if (!result
.append('.')) {
2534 uint32_t k
= 100'000'000;
2535 for (uint8_t i
= 0; i
< precision
.value(); i
++) {
2536 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2539 subSecondNanoseconds
%= k
;
2548 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2549 * normSeconds, precision )
2551 static JSString
* TemporalDurationToString(JSContext
* cx
,
2552 const Duration
& duration
,
2553 Precision precision
) {
2554 MOZ_ASSERT(IsValidDuration(duration
));
2555 MOZ_ASSERT(precision
!= Precision::Minute());
2557 // Convert to absolute values up front. This is okay to do, because when the
2558 // duration is valid, all components have the same sign.
2559 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
2560 milliseconds
, microseconds
, nanoseconds
] =
2561 AbsoluteDuration(duration
);
2563 // Fast path for zero durations.
2564 if (years
== 0 && months
== 0 && weeks
== 0 && days
== 0 && hours
== 0 &&
2565 minutes
== 0 && seconds
== 0 && milliseconds
== 0 && microseconds
== 0 &&
2567 (precision
== Precision::Auto() || precision
.value() == 0)) {
2568 return NewStringCopyZ
<CanGC
>(cx
, "PT0S");
2571 auto secondsDuration
= NormalizeTimeDuration(0.0, 0.0, seconds
, milliseconds
,
2572 microseconds
, nanoseconds
);
2575 int32_t sign
= DurationSign(duration
);
2578 JSStringBuilder
result(cx
);
2580 // Step 13. (Reordered)
2582 if (!result
.append('-')) {
2587 // Step 14. (Reordered)
2588 if (!result
.append('P')) {
2594 if (!NumberToStringBuilder(cx
, years
, result
)) {
2597 if (!result
.append('Y')) {
2604 if (!NumberToStringBuilder(cx
, months
, result
)) {
2607 if (!result
.append('M')) {
2614 if (!NumberToStringBuilder(cx
, weeks
, result
)) {
2617 if (!result
.append('W')) {
2624 if (!NumberToStringBuilder(cx
, days
, result
)) {
2627 if (!result
.append('D')) {
2632 // Step 7. (Moved above)
2634 // Steps 10-11. (Reordered)
2635 bool zeroMinutesAndHigher
= years
== 0 && months
== 0 && weeks
== 0 &&
2636 days
== 0 && hours
== 0 && minutes
== 0;
2638 // Steps 8-9, 12, and 15.
2639 bool hasSecondsPart
= (secondsDuration
!= NormalizedTimeDuration
{}) ||
2640 zeroMinutesAndHigher
|| precision
!= Precision::Auto();
2641 if (hours
!= 0 || minutes
!= 0 || hasSecondsPart
) {
2642 // Step 15. (Reordered)
2643 if (!result
.append('T')) {
2649 if (!NumberToStringBuilder(cx
, hours
, result
)) {
2652 if (!result
.append('H')) {
2659 if (!NumberToStringBuilder(cx
, minutes
, result
)) {
2662 if (!result
.append('M')) {
2668 if (hasSecondsPart
) {
2670 if (!NumberToStringBuilder(cx
, secondsDuration
.seconds
, result
)) {
2675 if (!FormatFractionalSeconds(result
, secondsDuration
.nanoseconds
,
2681 if (!result
.append('S')) {
2687 // Steps 13-15. (Moved above)
2690 return result
.finishString();
2694 * ToRelativeTemporalObject ( options )
2696 static bool ToRelativeTemporalObject(
2697 JSContext
* cx
, Handle
<JSObject
*> options
,
2698 MutableHandle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2699 MutableHandle
<ZonedDateTime
> zonedRelativeTo
,
2700 MutableHandle
<TimeZoneRecord
> timeZoneRecord
) {
2702 Rooted
<Value
> value(cx
);
2703 if (!GetProperty(cx
, options
, options
, cx
->names().relativeTo
, &value
)) {
2708 if (value
.isUndefined()) {
2709 // FIXME: spec issue - switch return record fields for consistency.
2710 // FIXME: spec bug - [[TimeZoneRec]] field not created
2712 plainRelativeTo
.set(nullptr);
2713 zonedRelativeTo
.set(ZonedDateTime
{});
2714 timeZoneRecord
.set(TimeZoneRecord
{});
2719 auto offsetBehaviour
= OffsetBehaviour::Option
;
2722 auto matchBehaviour
= MatchBehaviour::MatchExactly
;
2725 PlainDateTime dateTime
;
2726 Rooted
<CalendarValue
> calendar(cx
);
2727 Rooted
<TimeZoneValue
> timeZone(cx
);
2729 if (value
.isObject()) {
2730 Rooted
<JSObject
*> obj(cx
, &value
.toObject());
2733 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
2734 auto instant
= ToInstant(zonedDateTime
);
2735 Rooted
<TimeZoneValue
> timeZone(cx
, zonedDateTime
->timeZone());
2736 Rooted
<CalendarValue
> calendar(cx
, zonedDateTime
->calendar());
2738 if (!timeZone
.wrap(cx
)) {
2741 if (!calendar
.wrap(cx
)) {
2746 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2747 if (!CreateTimeZoneMethodsRecord(
2750 TimeZoneMethod::GetOffsetNanosecondsFor
,
2751 TimeZoneMethod::GetPossibleInstantsFor
,
2758 plainRelativeTo
.set(nullptr);
2759 zonedRelativeTo
.set(ZonedDateTime
{instant
, timeZone
, calendar
});
2760 timeZoneRecord
.set(timeZoneRec
);
2765 if (obj
->canUnwrapAs
<PlainDateObject
>()) {
2766 plainRelativeTo
.set(obj
);
2767 zonedRelativeTo
.set(ZonedDateTime
{});
2768 timeZoneRecord
.set(TimeZoneRecord
{});
2773 if (auto* dateTime
= obj
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
2774 auto plainDateTime
= ToPlainDate(dateTime
);
2776 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
2777 if (!calendar
.wrap(cx
)) {
2782 auto* plainDate
= CreateTemporalDate(cx
, plainDateTime
, calendar
);
2788 plainRelativeTo
.set(plainDate
);
2789 zonedRelativeTo
.set(ZonedDateTime
{});
2790 timeZoneRecord
.set(TimeZoneRecord
{});
2795 if (!GetTemporalCalendarWithISODefault(cx
, obj
, &calendar
)) {
2800 Rooted
<CalendarRecord
> calendarRec(cx
);
2801 if (!CreateCalendarMethodsRecord(cx
, calendar
,
2803 CalendarMethod::DateFromFields
,
2804 CalendarMethod::Fields
,
2811 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2812 if (!CalendarFields(cx
, calendarRec
,
2813 {CalendarField::Day
, CalendarField::Month
,
2814 CalendarField::MonthCode
, CalendarField::Year
},
2820 if (!AppendSorted(cx
, fieldNames
.get(),
2822 TemporalField::Hour
,
2823 TemporalField::Microsecond
,
2824 TemporalField::Millisecond
,
2825 TemporalField::Minute
,
2826 TemporalField::Nanosecond
,
2827 TemporalField::Offset
,
2828 TemporalField::Second
,
2829 TemporalField::TimeZone
,
2835 Rooted
<PlainObject
*> fields(cx
, PrepareTemporalFields(cx
, obj
, fieldNames
));
2841 Rooted
<PlainObject
*> dateOptions(cx
, NewPlainObjectWithProto(cx
, nullptr));
2847 Rooted
<Value
> overflow(cx
, StringValue(cx
->names().constrain
));
2848 if (!DefineDataProperty(cx
, dateOptions
, cx
->names().overflow
, overflow
)) {
2853 if (!InterpretTemporalDateTimeFields(cx
, calendarRec
, fields
, dateOptions
,
2859 Rooted
<Value
> offset(cx
);
2860 if (!GetProperty(cx
, fields
, fields
, cx
->names().offset
, &offset
)) {
2865 Rooted
<Value
> timeZoneValue(cx
);
2866 if (!GetProperty(cx
, fields
, fields
, cx
->names().timeZone
,
2872 if (!timeZoneValue
.isUndefined()) {
2873 if (!ToTemporalTimeZone(cx
, timeZoneValue
, &timeZone
)) {
2879 if (offset
.isUndefined()) {
2880 offsetBehaviour
= OffsetBehaviour::Wall
;
2885 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2886 MOZ_ASSERT(!offset
.isUndefined());
2887 MOZ_ASSERT(offset
.isString());
2890 Rooted
<JSString
*> offsetString(cx
, offset
.toString());
2891 if (!offsetString
) {
2896 if (!ParseDateTimeUTCOffset(cx
, offsetString
, &offsetNs
)) {
2906 if (!value
.isString()) {
2907 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
2908 nullptr, "not a string");
2911 Rooted
<JSString
*> string(cx
, value
.toString());
2916 int64_t timeZoneOffset
;
2917 Rooted
<ParsedTimeZone
> timeZoneName(cx
);
2918 Rooted
<JSString
*> calendarString(cx
);
2919 if (!ParseTemporalRelativeToString(cx
, string
, &dateTime
, &isUTC
,
2920 &hasOffset
, &timeZoneOffset
,
2921 &timeZoneName
, &calendarString
)) {
2925 // Step 6.c. (Not applicable in our implementation.)
2930 if (!ToTemporalTimeZone(cx
, timeZoneName
, &timeZone
)) {
2934 // Steps 6.f.ii-iii.
2936 offsetBehaviour
= OffsetBehaviour::Exact
;
2937 } else if (!hasOffset
) {
2938 offsetBehaviour
= OffsetBehaviour::Wall
;
2942 matchBehaviour
= MatchBehaviour::MatchMinutes
;
2944 MOZ_ASSERT(!timeZone
);
2948 if (calendarString
) {
2949 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
2953 calendar
.set(CalendarValue(cx
->names().iso8601
));
2958 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2959 MOZ_ASSERT(hasOffset
);
2962 offsetNs
= timeZoneOffset
;
2973 auto* plainDate
= CreateTemporalDate(cx
, dateTime
.date
, calendar
);
2978 plainRelativeTo
.set(plainDate
);
2979 zonedRelativeTo
.set(ZonedDateTime
{});
2980 timeZoneRecord
.set(TimeZoneRecord
{});
2984 // Steps 8-9. (Moved above)
2987 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2988 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2990 TimeZoneMethod::GetOffsetNanosecondsFor
,
2991 TimeZoneMethod::GetPossibleInstantsFor
,
2998 Instant epochNanoseconds
;
2999 if (!InterpretISODateTimeOffset(
3000 cx
, dateTime
, offsetBehaviour
, offsetNs
, timeZoneRec
,
3001 TemporalDisambiguation::Compatible
, TemporalOffset::Reject
,
3002 matchBehaviour
, &epochNanoseconds
)) {
3005 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds
));
3008 plainRelativeTo
.set(nullptr);
3009 zonedRelativeTo
.set(ZonedDateTime
{epochNanoseconds
, timeZone
, calendar
});
3010 timeZoneRecord
.set(timeZoneRec
);
3015 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
3018 static bool CreateCalendarMethodsRecordFromRelativeTo(
3019 JSContext
* cx
, Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3020 Handle
<ZonedDateTime
> zonedRelativeTo
,
3021 mozilla::EnumSet
<CalendarMethod
> methods
,
3022 MutableHandle
<CalendarRecord
> result
) {
3024 if (zonedRelativeTo
) {
3025 return CreateCalendarMethodsRecord(cx
, zonedRelativeTo
.calendar(), methods
,
3030 if (plainRelativeTo
) {
3031 auto* unwrapped
= plainRelativeTo
.unwrap(cx
);
3036 Rooted
<CalendarValue
> calendar(cx
, unwrapped
->calendar());
3037 if (!calendar
.wrap(cx
)) {
3041 return CreateCalendarMethodsRecord(cx
, calendar
, methods
, result
);
3048 struct RoundedNumber final
{
3054 * RoundNumberToIncrement ( x, increment, roundingMode )
3056 static RoundedNumber
TruncateNumber(int64_t numerator
, int64_t denominator
) {
3057 // Computes the quotient and real number value of the rational number
3058 // |numerator / denominator|.
3060 // Int64 division truncates.
3061 int64_t quot
= numerator
/ denominator
;
3062 int64_t rem
= numerator
% denominator
;
3064 // The total value is stored as a mathematical number in the draft proposal,
3065 // so we can't convert it to a double without loss of precision. We use two
3066 // different approaches to compute the total value based on the input range.
3070 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3071 // is |16.66668333...| and the best possible approximation is
3072 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3073 // numerator and denominator to doubles and then performing a double division.
3075 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3076 // can't use double division, because |14400000000000001| can't be represented
3077 // as an exact double value. The exact result is |4000.0000000000002777...|.
3079 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3080 // be computed through |q + r / denominator|.
3082 if (::IsSafeInteger(numerator
) && ::IsSafeInteger(denominator
)) {
3083 total
= double(numerator
) / double(denominator
);
3085 total
= double(quot
) + double(rem
) / double(denominator
);
3087 return {Int128
{quot
}, total
};
3091 * RoundNumberToIncrement ( x, increment, roundingMode )
3093 static RoundedNumber
TruncateNumber(const Int128
& numerator
,
3094 const Int128
& denominator
) {
3095 MOZ_ASSERT(denominator
> Int128
{});
3096 MOZ_ASSERT(numerator
> Int128
{INT64_MAX
} || denominator
> Int128
{INT64_MAX
},
3097 "small values use the int64 overload");
3099 // Int128 division truncates.
3100 auto [quot
, rem
] = numerator
.divrem(denominator
);
3102 double total
= double(quot
) + double(rem
) / double(denominator
);
3103 return {quot
, total
};
3106 struct RoundedDuration final
{
3107 NormalizedDuration duration
;
3111 enum class ComputeRemainder
: bool { No
, Yes
};
3114 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3116 static NormalizedTimeDuration
RoundNormalizedTimeDurationToIncrement(
3117 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
,
3118 Increment increment
, TemporalRoundingMode roundingMode
) {
3119 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3120 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3121 MOZ_ASSERT(increment
<= MaximumTemporalDurationRoundingIncrement(unit
));
3123 int64_t divisor
= ToNanoseconds(unit
) * increment
.value();
3124 MOZ_ASSERT(divisor
> 0);
3125 MOZ_ASSERT(divisor
<= ToNanoseconds(TemporalUnit::Day
));
3127 auto totalNanoseconds
= duration
.toTotalNanoseconds();
3129 RoundNumberToIncrement(totalNanoseconds
, Int128
{divisor
}, roundingMode
);
3130 return NormalizedTimeDuration::fromNanoseconds(rounded
);
3134 * DivideNormalizedTimeDuration ( d, divisor )
3136 static double TotalNormalizedTimeDuration(
3137 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
) {
3138 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3139 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3141 // Compute real number value of the rational number |numerator / denominator|.
3143 auto numerator
= duration
.toTotalNanoseconds();
3144 auto denominator
= ToNanoseconds(unit
);
3145 MOZ_ASSERT(::IsSafeInteger(denominator
));
3147 // The total value is stored as a mathematical number in the draft proposal,
3148 // so we can't convert it to a double without loss of precision. We use two
3149 // different approaches to compute the total value based on the input range.
3153 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3154 // is |16.66668333...| and the best possible approximation is
3155 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3156 // numerator and denominator to doubles and then performing a double division.
3158 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3159 // can't use double division, because |14400000000000001| can't be represented
3160 // as an exact double value. The exact result is |4000.0000000000002777...|.
3162 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3163 // be computed through |q + r / denominator|.
3164 if (::IsSafeInteger(numerator
)) {
3165 return double(numerator
) / double(denominator
);
3168 auto [q
, r
] = numerator
.divrem(Int128
{denominator
});
3169 return double(q
) + double(r
) / double(denominator
);
3173 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3174 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3175 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3177 NormalizedTimeDuration
js::temporal::RoundDuration(
3178 const NormalizedTimeDuration
& duration
, Increment increment
,
3179 TemporalUnit unit
, TemporalRoundingMode roundingMode
) {
3180 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3181 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3183 // Steps 1-13. (Not applicable)
3186 auto rounded
= RoundNormalizedTimeDurationToIncrement(
3187 duration
, unit
, increment
, roundingMode
);
3188 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded
));
3194 static int64_t TruncateDays(const NormalizedTimeAndDays
& timeAndDays
,
3195 int64_t days
, int32_t daysToAdd
) {
3197 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3198 static constexpr int64_t durationDays
= (int64_t(1) << 53) / (24 * 60 * 60);
3200 // Numbers of days between nsMinInstant and nsMaxInstant.
3201 static constexpr int32_t epochDays
= 200'000'000;
3204 MOZ_ASSERT(std::abs(days
) <= durationDays
);
3205 MOZ_ASSERT(std::abs(timeAndDays
.days
) <= durationDays
);
3206 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
);
3208 static_assert(durationDays
+ durationDays
+ epochDays
<= INT64_MAX
,
3209 "addition can't overflow");
3211 int64_t totalDays
= days
+ timeAndDays
.days
+ daysToAdd
;
3213 int64_t truncatedDays
= totalDays
;
3214 if (timeAndDays
.time
> 0) {
3215 // Round toward positive infinity when the integer days are negative and
3216 // the fractional part is positive.
3217 if (truncatedDays
< 0) {
3220 } else if (timeAndDays
.time
< 0) {
3221 // Round toward negative infinity when the integer days are positive and
3222 // the fractional part is negative.
3223 if (truncatedDays
> 0) {
3228 return truncatedDays
;
3231 static bool DaysIsNegative(int64_t days
,
3232 const NormalizedTimeAndDays
& timeAndDays
,
3233 int32_t daysToAdd
) {
3234 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3235 static constexpr int64_t durationDays
= (int64_t(1) << 53) / (24 * 60 * 60);
3237 // Numbers of days between nsMinInstant and nsMaxInstant.
3238 static constexpr int32_t epochDays
= 200'000'000;
3240 MOZ_ASSERT(std::abs(days
) <= durationDays
);
3241 MOZ_ASSERT(std::abs(timeAndDays
.days
) <= durationDays
);
3242 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3244 static_assert(durationDays
+ durationDays
+ epochDays
* 2 <= INT64_MAX
,
3245 "addition can't overflow");
3247 int64_t totalDays
= days
+ timeAndDays
.days
+ daysToAdd
;
3248 return totalDays
< 0 || (totalDays
== 0 && timeAndDays
.time
< 0);
3251 static RoundedNumber
RoundNumberToIncrement(
3252 int64_t durationAmount
, int64_t amountPassed
, int64_t durationDays
,
3253 int32_t daysToAdd
, const NormalizedTimeAndDays
& timeAndDays
,
3254 int32_t oneUnitDays
, Increment increment
, TemporalRoundingMode roundingMode
,
3255 ComputeRemainder computeRemainder
) {
3257 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3258 static constexpr int64_t maxDurationDays
=
3259 (int64_t(1) << 53) / (24 * 60 * 60);
3261 // Numbers of days between nsMinInstant and nsMaxInstant.
3262 static constexpr int32_t epochDays
= 200'000'000;
3265 MOZ_ASSERT(std::abs(durationAmount
) < (int64_t(1) << 32));
3266 MOZ_ASSERT(std::abs(amountPassed
) < (int64_t(1) << 32));
3267 MOZ_ASSERT(std::abs(durationDays
) <= maxDurationDays
);
3268 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3269 MOZ_ASSERT(timeAndDays
.dayLength
> 0);
3270 MOZ_ASSERT(timeAndDays
.dayLength
< (int64_t(1) << 53));
3271 MOZ_ASSERT(std::abs(timeAndDays
.time
) < timeAndDays
.dayLength
);
3272 MOZ_ASSERT(std::abs(timeAndDays
.days
) <= maxDurationDays
);
3273 MOZ_ASSERT(oneUnitDays
!= 0);
3274 MOZ_ASSERT(std::abs(oneUnitDays
) <= epochDays
);
3275 MOZ_ASSERT(increment
<= Increment::max());
3279 // Change the representation of |fractionalWeeks| from a real number to a
3280 // rational number, because we don't support arbitrary precision real
3283 // |fractionalWeeks| is defined as:
3286 // = weeks + days' / abs(oneWeekDays)
3288 // where days' = days + nanoseconds / dayLength.
3290 // The fractional part |nanoseconds / dayLength| is from step 4.
3292 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3295 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3296 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3297 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3302 auto dayLength
= mozilla::CheckedInt64(timeAndDays
.dayLength
);
3304 auto denominator
= dayLength
* std::abs(oneUnitDays
);
3305 if (!denominator
.isValid()) {
3309 auto totalDays
= mozilla::CheckedInt64(durationDays
);
3310 totalDays
+= timeAndDays
.days
;
3311 totalDays
+= daysToAdd
;
3312 MOZ_ASSERT(totalDays
.isValid());
3314 auto totalAmount
= mozilla::CheckedInt64(durationAmount
) + amountPassed
;
3315 MOZ_ASSERT(totalAmount
.isValid());
3317 auto amountNanos
= denominator
* totalAmount
;
3318 if (!amountNanos
.isValid()) {
3322 auto totalNanoseconds
= dayLength
* totalDays
;
3323 totalNanoseconds
+= timeAndDays
.time
;
3324 totalNanoseconds
+= amountNanos
;
3325 if (!totalNanoseconds
.isValid()) {
3329 if (computeRemainder
== ComputeRemainder::Yes
) {
3330 return TruncateNumber(totalNanoseconds
.value(), denominator
.value());
3333 auto rounded
= RoundNumberToIncrement(
3334 totalNanoseconds
.value(), denominator
.value(), increment
, roundingMode
);
3335 constexpr double total
= 0;
3336 return {rounded
, total
};
3339 // Use int128 when values are too large for int64. Additionally assert all
3340 // values fit into int128.
3342 // `dayLength` < 2**53
3343 auto dayLength
= Int128
{timeAndDays
.dayLength
};
3344 MOZ_ASSERT(dayLength
< Int128
{1} << 53);
3346 // `abs(oneUnitDays)` < 200'000'000, log2(200'000'000) = ~27.57.
3347 auto denominator
= dayLength
* Int128
{std::abs(oneUnitDays
)};
3348 MOZ_ASSERT(denominator
< Int128
{1} << (53 + 28));
3350 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3352 // `abs(maxDurationDays)` ≤ 2**(53 - 16).
3353 // `abs(timeAndDays.days)` ≤ 2**(53 - 16).
3354 // `abs(daysToAdd)` ≤ 2**29.
3356 // 2**(53 - 16) + 2**(53 - 16) + 2**29
3357 // = 2**37 + 2**37 + 2**29
3360 auto totalDays
= Int128
{durationDays
};
3361 totalDays
+= Int128
{timeAndDays
.days
};
3362 totalDays
+= Int128
{daysToAdd
};
3363 MOZ_ASSERT(totalDays
.abs() <= Uint128
{1} << 39);
3365 // `abs(durationAmount)` ≤ 2**32
3366 // `abs(amountPassed)` ≤ 2**32
3367 auto totalAmount
= Int128
{durationAmount
} + Int128
{amountPassed
};
3368 MOZ_ASSERT(totalAmount
.abs() <= Uint128
{1} << 33);
3370 // `denominator` < 2**(53 + 28)
3371 // `abs(totalAmount)` <= 2**33
3373 // `denominator * totalAmount`
3374 // ≤ 2**(53 + 28) * 2**33
3375 // = 2**(53 + 28 + 33)
3377 auto amountNanos
= denominator
* totalAmount
;
3378 MOZ_ASSERT(amountNanos
.abs() <= Uint128
{1} << 114);
3380 // `dayLength` < 2**53
3381 // `totalDays` ≤ 2**39
3382 // `timeAndDays.time` < `dayLength` < 2**53
3383 // `amountNanos` ≤ 2**114
3385 // `dayLength * totalDays`
3386 // ≤ 2**(53 + 39) = 2**92
3388 // `dayLength * totalDays + timeAndDays.time`
3391 // `dayLength * totalDays + timeAndDays.time + amountNanos`
3393 auto totalNanoseconds
= dayLength
* totalDays
;
3394 totalNanoseconds
+= Int128
{timeAndDays
.time
};
3395 totalNanoseconds
+= amountNanos
;
3396 MOZ_ASSERT(totalNanoseconds
.abs() <= Uint128
{1} << 115);
3398 if (computeRemainder
== ComputeRemainder::Yes
) {
3399 return TruncateNumber(totalNanoseconds
, denominator
);
3402 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, denominator
,
3403 increment
, roundingMode
);
3404 constexpr double total
= 0;
3405 return {rounded
, total
};
3408 static RoundedNumber
RoundNumberToIncrement(
3409 double durationDays
, const NormalizedTimeAndDays
& timeAndDays
,
3410 Increment increment
, TemporalRoundingMode roundingMode
,
3411 ComputeRemainder computeRemainder
) {
3412 constexpr int64_t daysAmount
= 0;
3413 constexpr int64_t daysPassed
= 0;
3414 constexpr int32_t oneDayDays
= 1;
3415 constexpr int32_t daysToAdd
= 0;
3417 return RoundNumberToIncrement(daysAmount
, daysPassed
, durationDays
, daysToAdd
,
3418 timeAndDays
, oneDayDays
, increment
,
3419 roundingMode
, computeRemainder
);
3422 static bool RoundDurationYear(JSContext
* cx
, const NormalizedDuration
& duration
,
3423 const NormalizedTimeAndDays
& timeAndDays
,
3424 Increment increment
,
3425 TemporalRoundingMode roundingMode
,
3426 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3427 Handle
<CalendarRecord
> calendar
,
3428 ComputeRemainder computeRemainder
,
3429 RoundedDuration
* result
) {
3430 // Numbers of days between nsMinInstant and nsMaxInstant.
3431 static constexpr int32_t epochDays
= 200'000'000;
3433 auto [years
, months
, weeks
, days
] = duration
.date
;
3436 Duration yearsDuration
= {double(years
)};
3439 auto yearsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsDuration
);
3443 auto yearsLaterDate
= ToPlainDate(&yearsLater
.unwrap());
3445 // Step 10.f. (Reordered)
3446 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsLater
);
3449 Duration yearsMonthsWeeks
= {double(years
), double(months
), double(weeks
)};
3452 PlainDate yearsMonthsWeeksLater
;
3453 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3454 &yearsMonthsWeeksLater
)) {
3459 int32_t monthsWeeksInDays
= DaysUntil(yearsLaterDate
, yearsMonthsWeeksLater
);
3460 MOZ_ASSERT(std::abs(monthsWeeksInDays
) <= epochDays
);
3462 // Step 10.f. (Moved up)
3465 // Our implementation keeps |days| and |monthsWeeksInDays| separate.
3467 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3468 // https://github.com/tc39/proposal-temporal/issues/2540
3471 int64_t truncatedDays
= TruncateDays(timeAndDays
, days
, monthsWeeksInDays
);
3473 PlainDate isoResult
;
3474 if (!AddISODate(cx
, yearsLaterDate
, {0, 0, 0, truncatedDays
},
3475 TemporalOverflow::Constrain
, &isoResult
)) {
3480 Rooted
<PlainDateObject
*> wholeDaysLater(
3481 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3482 if (!wholeDaysLater
) {
3487 Duration timePassed
;
3488 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3489 TemporalUnit::Year
, &timePassed
)) {
3494 int64_t yearsPassed
= int64_t(timePassed
.years
);
3497 // Our implementation keeps |years| and |yearsPassed| separate.
3500 Duration yearsPassedDuration
= {double(yearsPassed
)};
3504 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, yearsPassedDuration
,
3505 &newRelativeTo
, &daysPassed
)) {
3508 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3512 // Our implementation keeps |days| and |daysPassed| separate.
3513 int32_t daysToAdd
= monthsWeeksInDays
- daysPassed
;
3514 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3517 double sign
= DaysIsNegative(days
, timeAndDays
, daysToAdd
) ? -1 : 1;
3520 Duration oneYear
= {sign
};
3523 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3524 int32_t oneYearDays
;
3525 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneYear
,
3526 &moveResultIgnored
, &oneYearDays
)) {
3531 if (oneYearDays
== 0) {
3532 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3533 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3538 auto [numYears
, total
] = RoundNumberToIncrement(
3539 years
, yearsPassed
, days
, daysToAdd
, timeAndDays
, oneYearDays
, increment
,
3540 roundingMode
, computeRemainder
);
3543 int64_t numMonths
= 0;
3544 int64_t numWeeks
= 0;
3547 constexpr auto time
= NormalizedTimeDuration
{};
3550 if (numYears
.abs() >= (Uint128
{1} << 32)) {
3551 return ThrowInvalidDurationPart(cx
, double(numYears
), "years",
3552 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3555 auto resultDuration
= DateDuration
{int64_t(numYears
), numMonths
, numWeeks
};
3556 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3560 *result
= {{resultDuration
, time
}, total
};
3564 static bool RoundDurationMonth(JSContext
* cx
,
3565 const NormalizedDuration
& duration
,
3566 const NormalizedTimeAndDays
& timeAndDays
,
3567 Increment increment
,
3568 TemporalRoundingMode roundingMode
,
3569 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3570 Handle
<CalendarRecord
> calendar
,
3571 ComputeRemainder computeRemainder
,
3572 RoundedDuration
* result
) {
3573 // Numbers of days between nsMinInstant and nsMaxInstant.
3574 static constexpr int32_t epochDays
= 200'000'000;
3576 auto [years
, months
, weeks
, days
] = duration
.date
;
3579 Duration yearsMonths
= {double(years
), double(months
)};
3582 auto yearsMonthsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsMonths
);
3583 if (!yearsMonthsLater
) {
3586 auto yearsMonthsLaterDate
= ToPlainDate(&yearsMonthsLater
.unwrap());
3588 // Step 11.f. (Reordered)
3589 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsMonthsLater
);
3592 Duration yearsMonthsWeeks
= {double(years
), double(months
), double(weeks
)};
3595 PlainDate yearsMonthsWeeksLater
;
3596 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3597 &yearsMonthsWeeksLater
)) {
3602 int32_t weeksInDays
= DaysUntil(yearsMonthsLaterDate
, yearsMonthsWeeksLater
);
3603 MOZ_ASSERT(std::abs(weeksInDays
) <= epochDays
);
3605 // Step 11.f. (Moved up)
3608 // Our implementation keeps |days| and |weeksInDays| separate.
3610 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3611 // https://github.com/tc39/proposal-temporal/issues/2540
3614 int64_t truncatedDays
= TruncateDays(timeAndDays
, days
, weeksInDays
);
3616 PlainDate isoResult
;
3617 if (!AddISODate(cx
, yearsMonthsLaterDate
, {0, 0, 0, truncatedDays
},
3618 TemporalOverflow::Constrain
, &isoResult
)) {
3623 Rooted
<PlainDateObject
*> wholeDaysLater(
3624 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3625 if (!wholeDaysLater
) {
3630 Duration timePassed
;
3631 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3632 TemporalUnit::Month
, &timePassed
)) {
3637 int64_t monthsPassed
= int64_t(timePassed
.months
);
3640 // Our implementation keeps |months| and |monthsPassed| separate.
3643 Duration monthsPassedDuration
= {0, double(monthsPassed
)};
3647 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, monthsPassedDuration
,
3648 &newRelativeTo
, &daysPassed
)) {
3651 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3655 // Our implementation keeps |days| and |daysPassed| separate.
3656 int32_t daysToAdd
= weeksInDays
- daysPassed
;
3657 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3660 double sign
= DaysIsNegative(days
, timeAndDays
, daysToAdd
) ? -1 : 1;
3663 Duration oneMonth
= {0, sign
};
3666 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3667 int32_t oneMonthDays
;
3668 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneMonth
,
3669 &moveResultIgnored
, &oneMonthDays
)) {
3674 if (oneMonthDays
== 0) {
3675 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3676 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3681 auto [numMonths
, total
] = RoundNumberToIncrement(
3682 months
, monthsPassed
, days
, daysToAdd
, timeAndDays
, oneMonthDays
,
3683 increment
, roundingMode
, computeRemainder
);
3686 int64_t numWeeks
= 0;
3689 constexpr auto time
= NormalizedTimeDuration
{};
3692 if (numMonths
.abs() >= (Uint128
{1} << 32)) {
3693 return ThrowInvalidDurationPart(cx
, double(numMonths
), "months",
3694 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3697 auto resultDuration
= DateDuration
{years
, int64_t(numMonths
), numWeeks
};
3698 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3702 *result
= {{resultDuration
, time
}, total
};
3706 static bool RoundDurationWeek(JSContext
* cx
, const NormalizedDuration
& duration
,
3707 const NormalizedTimeAndDays
& timeAndDays
,
3708 Increment increment
,
3709 TemporalRoundingMode roundingMode
,
3710 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3711 Handle
<CalendarRecord
> calendar
,
3712 ComputeRemainder computeRemainder
,
3713 RoundedDuration
* result
) {
3714 // Numbers of days between nsMinInstant and nsMaxInstant.
3715 static constexpr int32_t epochDays
= 200'000'000;
3717 auto [years
, months
, weeks
, days
] = duration
.date
;
3719 auto* unwrappedRelativeTo
= dateRelativeTo
.unwrap(cx
);
3720 if (!unwrappedRelativeTo
) {
3723 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
3726 int64_t truncatedDays
= TruncateDays(timeAndDays
, days
, 0);
3728 PlainDate isoResult
;
3729 if (!AddISODate(cx
, relativeToDate
, {0, 0, 0, truncatedDays
},
3730 TemporalOverflow::Constrain
, &isoResult
)) {
3735 Rooted
<PlainDateObject
*> wholeDaysLater(
3736 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3737 if (!wholeDaysLater
) {
3742 Duration timePassed
;
3743 if (!DifferenceDate(cx
, calendar
, dateRelativeTo
, wholeDaysLater
,
3744 TemporalUnit::Week
, &timePassed
)) {
3749 int64_t weeksPassed
= int64_t(timePassed
.weeks
);
3752 // Our implementation keeps |weeks| and |weeksPassed| separate.
3755 Duration weeksPassedDuration
= {0, 0, double(weeksPassed
)};
3758 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
);
3760 if (!MoveRelativeDate(cx
, calendar
, dateRelativeTo
, weeksPassedDuration
,
3761 &newRelativeTo
, &daysPassed
)) {
3764 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3768 // Our implementation keeps |days| and |daysPassed| separate.
3769 int32_t daysToAdd
= -daysPassed
;
3770 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
);
3773 double sign
= DaysIsNegative(days
, timeAndDays
, daysToAdd
) ? -1 : 1;
3776 Duration oneWeek
= {0, 0, sign
};
3779 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3780 int32_t oneWeekDays
;
3781 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneWeek
,
3782 &moveResultIgnored
, &oneWeekDays
)) {
3787 if (oneWeekDays
== 0) {
3788 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3789 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3794 auto [numWeeks
, total
] = RoundNumberToIncrement(
3795 weeks
, weeksPassed
, days
, daysToAdd
, timeAndDays
, oneWeekDays
, increment
,
3796 roundingMode
, computeRemainder
);
3799 constexpr auto time
= NormalizedTimeDuration
{};
3802 if (numWeeks
.abs() >= (Uint128
{1} << 32)) {
3803 return ThrowInvalidDurationPart(cx
, double(numWeeks
), "weeks",
3804 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3807 auto resultDuration
= DateDuration
{years
, months
, int64_t(numWeeks
)};
3808 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3812 *result
= {{resultDuration
, time
}, total
};
3816 static bool RoundDurationDay(JSContext
* cx
, const NormalizedDuration
& duration
,
3817 const NormalizedTimeAndDays
& timeAndDays
,
3818 Increment increment
,
3819 TemporalRoundingMode roundingMode
,
3820 ComputeRemainder computeRemainder
,
3821 RoundedDuration
* result
) {
3822 auto [years
, months
, weeks
, days
] = duration
.date
;
3825 auto [numDays
, total
] = RoundNumberToIncrement(
3826 days
, timeAndDays
, increment
, roundingMode
, computeRemainder
);
3828 MOZ_ASSERT(Int128
{INT64_MIN
} <= numDays
&& numDays
<= Int128
{INT64_MAX
},
3829 "rounded days fits in int64");
3832 constexpr auto time
= NormalizedTimeDuration
{};
3835 auto resultDuration
= DateDuration
{years
, months
, weeks
, int64_t(numDays
)};
3836 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3840 *result
= {{resultDuration
, time
}, total
};
3845 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3846 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3847 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3849 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
3850 Increment increment
, TemporalUnit unit
,
3851 TemporalRoundingMode roundingMode
,
3852 ComputeRemainder computeRemainder
,
3853 RoundedDuration
* result
) {
3854 // The remainder is only needed when called from |Duration_total|. And `total`
3855 // always passes |increment=1| and |roundingMode=trunc|.
3856 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3857 increment
== Increment
{1});
3858 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3859 roundingMode
== TemporalRoundingMode::Trunc
);
3861 // Steps 1-5. (Not applicable.)
3864 if (unit
<= TemporalUnit::Week
) {
3865 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3866 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3871 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3872 // because in that case this operation is a no-op. This case happens for
3873 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3876 // But maybe this can be even more efficiently handled in the callers. For
3877 // example when Temporal.PlainTime.prototype.{since,until} is called without
3878 // an options object, we can not only skip the RoundDuration call, but also
3879 // the following BalanceTimeDuration call.
3881 // Step 7. (Moved below.)
3883 // Steps 8-9. (Not applicable.)
3885 // Steps 10-12. (Not applicable.)
3888 if (unit
== TemporalUnit::Day
) {
3890 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3892 return RoundDurationDay(cx
, duration
, timeAndDays
, increment
, roundingMode
,
3893 computeRemainder
, result
);
3896 MOZ_ASSERT(TemporalUnit::Hour
<= unit
&& unit
<= TemporalUnit::Nanosecond
);
3899 auto time
= duration
.time
;
3901 if (computeRemainder
== ComputeRemainder::No
) {
3902 time
= RoundNormalizedTimeDurationToIncrement(time
, unit
, increment
,
3905 MOZ_ASSERT(increment
== Increment
{1});
3906 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
3908 total
= TotalNormalizedTimeDuration(duration
.time
, unit
);
3910 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
3913 MOZ_ASSERT(IsValidDuration(duration
.date
.toDuration()));
3914 *result
= {{duration
.date
, time
}, total
};
3919 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3920 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3921 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3923 static bool RoundDuration(
3924 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
3925 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
3926 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3927 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
3928 Handle
<TimeZoneRecord
> timeZone
,
3929 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
3930 ComputeRemainder computeRemainder
, RoundedDuration
* result
) {
3931 // Note: |duration.days| can have a different sign than the other date
3932 // components. The date and time components can have different signs, too.
3933 MOZ_ASSERT(IsValidDuration(Duration
{double(duration
.date
.years
),
3934 double(duration
.date
.months
),
3935 double(duration
.date
.weeks
)}));
3936 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
3938 MOZ_ASSERT(plainRelativeTo
|| zonedRelativeTo
,
3939 "Use RoundDuration without relativeTo when plainRelativeTo and "
3940 "zonedRelativeTo are both undefined");
3942 // The remainder is only needed when called from |Duration_total|. And `total`
3943 // always passes |increment=1| and |roundingMode=trunc|.
3944 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3945 increment
== Increment
{1});
3946 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3947 roundingMode
== TemporalRoundingMode::Trunc
);
3949 // Steps 1-5. (Not applicable in our implementation.)
3951 // Step 6.a. (Not applicable in our implementation.)
3952 MOZ_ASSERT_IF(unit
<= TemporalUnit::Week
, plainRelativeTo
);
3956 unit
<= TemporalUnit::Week
,
3957 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
3961 unit
<= TemporalUnit::Week
,
3962 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
3965 case TemporalUnit::Year
:
3966 case TemporalUnit::Month
:
3967 case TemporalUnit::Week
:
3969 case TemporalUnit::Day
:
3970 // We can't take the faster code path when |zonedRelativeTo| is present.
3971 if (zonedRelativeTo
) {
3975 case TemporalUnit::Hour
:
3976 case TemporalUnit::Minute
:
3977 case TemporalUnit::Second
:
3978 case TemporalUnit::Millisecond
:
3979 case TemporalUnit::Microsecond
:
3980 case TemporalUnit::Nanosecond
:
3981 // Steps 7-9 and 13-21.
3982 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
3983 computeRemainder
, result
);
3984 case TemporalUnit::Auto
:
3985 MOZ_CRASH("Unexpected temporal unit");
3989 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
);
3992 NormalizedTimeAndDays timeAndDays
;
3993 if (zonedRelativeTo
) {
3995 Rooted
<ZonedDateTime
> intermediate(cx
);
3996 if (!MoveRelativeZonedDateTime(cx
, zonedRelativeTo
, calendar
, timeZone
,
3997 duration
.date
, precalculatedPlainDateTime
,
4003 if (!NormalizedTimeDurationToDays(cx
, duration
.time
, intermediate
, timeZone
,
4008 // Step 7.a.iii. (Not applicable in our implementation.)
4010 // Step 7.b. (Partial)
4011 timeAndDays
= ::NormalizedTimeDurationToDays(duration
.time
);
4014 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
4015 // less than |timeAndDays.dayLength|.
4016 MOZ_ASSERT(std::abs(timeAndDays
.time
) < timeAndDays
.dayLength
);
4018 // Step 7.c. (Moved below)
4020 // Step 8. (Not applicable)
4023 // FIXME: spec issue - `total` doesn't need be initialised.
4028 case TemporalUnit::Year
:
4029 return RoundDurationYear(cx
, duration
, timeAndDays
, increment
,
4030 roundingMode
, plainRelativeTo
, calendar
,
4031 computeRemainder
, result
);
4034 case TemporalUnit::Month
:
4035 return RoundDurationMonth(cx
, duration
, timeAndDays
, increment
,
4036 roundingMode
, plainRelativeTo
, calendar
,
4037 computeRemainder
, result
);
4040 case TemporalUnit::Week
:
4041 return RoundDurationWeek(cx
, duration
, timeAndDays
, increment
,
4042 roundingMode
, plainRelativeTo
, calendar
,
4043 computeRemainder
, result
);
4046 case TemporalUnit::Day
:
4047 return RoundDurationDay(cx
, duration
, timeAndDays
, increment
,
4048 roundingMode
, computeRemainder
, result
);
4050 // Steps 14-19. (Handled elsewhere)
4051 case TemporalUnit::Auto
:
4052 case TemporalUnit::Hour
:
4053 case TemporalUnit::Minute
:
4054 case TemporalUnit::Second
:
4055 case TemporalUnit::Millisecond
:
4056 case TemporalUnit::Microsecond
:
4057 case TemporalUnit::Nanosecond
:
4061 MOZ_CRASH("Unexpected temporal unit");
4065 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4066 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4067 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4069 static bool RoundDuration(
4070 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4071 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4072 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4073 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4074 Handle
<TimeZoneRecord
> timeZone
,
4075 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4077 // Only called from |Duration_total|, which always passes |increment=1| and
4078 // |roundingMode=trunc|.
4079 MOZ_ASSERT(increment
== Increment
{1});
4080 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4082 RoundedDuration rounded
;
4083 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4084 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4085 precalculatedPlainDateTime
, ComputeRemainder::Yes
,
4090 *result
= rounded
.total
;
4095 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4096 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4097 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4099 static bool RoundDuration(
4100 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4101 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4102 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4103 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4104 Handle
<TimeZoneRecord
> timeZone
,
4105 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4106 NormalizedDuration
* result
) {
4107 RoundedDuration rounded
;
4108 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4109 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4110 precalculatedPlainDateTime
, ComputeRemainder::No
,
4115 *result
= rounded
.duration
;
4120 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4121 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4122 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4124 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4125 Increment increment
, TemporalUnit unit
,
4126 TemporalRoundingMode roundingMode
, double* result
) {
4127 MOZ_ASSERT(IsValidDuration(duration
));
4129 // Only called from |Duration_total|, which always passes |increment=1| and
4130 // |roundingMode=trunc|.
4131 MOZ_ASSERT(increment
== Increment
{1});
4132 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4134 RoundedDuration rounded
;
4135 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4136 ComputeRemainder::Yes
, &rounded
)) {
4140 *result
= rounded
.total
;
4145 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4146 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4147 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4149 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4150 Increment increment
, TemporalUnit unit
,
4151 TemporalRoundingMode roundingMode
,
4152 NormalizedDuration
* result
) {
4153 MOZ_ASSERT(IsValidDuration(duration
));
4155 RoundedDuration rounded
;
4156 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4157 ComputeRemainder::No
, &rounded
)) {
4161 *result
= rounded
.duration
;
4166 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4167 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4168 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4170 bool js::temporal::RoundDuration(
4171 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4172 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4173 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4174 Handle
<CalendarRecord
> calendar
, NormalizedDuration
* result
) {
4175 MOZ_ASSERT(IsValidDuration(duration
));
4177 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
, ZonedDateTime
{});
4178 Rooted
<TimeZoneRecord
> timeZone(cx
, TimeZoneRecord
{});
4179 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4180 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4181 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4182 precalculatedPlainDateTime
, result
);
4186 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4187 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4188 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4190 bool js::temporal::RoundDuration(
4191 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4192 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4193 Handle
<PlainDateObject
*> plainRelativeTo
, Handle
<CalendarRecord
> calendar
,
4194 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<TimeZoneRecord
> timeZone
,
4195 const PlainDateTime
& precalculatedPlainDateTime
,
4196 NormalizedDuration
* result
) {
4197 MOZ_ASSERT(IsValidDuration(duration
));
4199 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4200 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4201 mozilla::SomeRef(precalculatedPlainDateTime
), result
);
4204 enum class DurationOperation
{ Add
, Subtract
};
4207 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4210 static bool AddDurationToOrSubtractDurationFromDuration(
4211 JSContext
* cx
, DurationOperation operation
, const CallArgs
& args
) {
4212 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4213 auto duration
= ToDuration(durationObj
);
4215 // Step 1. (Not applicable in our implementation.)
4219 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
4223 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4224 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4225 Rooted
<TimeZoneRecord
> timeZone(cx
);
4226 if (args
.hasDefined(1)) {
4227 const char* name
= operation
== DurationOperation::Add
? "add" : "subtract";
4230 Rooted
<JSObject
*> options(cx
,
4231 RequireObjectArg(cx
, "options", name
, args
[1]));
4237 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4238 &zonedRelativeTo
, &timeZone
)) {
4241 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4242 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4246 Rooted
<CalendarRecord
> calendar(cx
);
4247 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4250 CalendarMethod::DateAdd
,
4251 CalendarMethod::DateUntil
,
4258 if (operation
== DurationOperation::Subtract
) {
4259 other
= other
.negate();
4263 if (plainRelativeTo
) {
4264 if (!AddDuration(cx
, duration
, other
, plainRelativeTo
, calendar
, &result
)) {
4267 } else if (zonedRelativeTo
) {
4268 if (!AddDuration(cx
, duration
, other
, zonedRelativeTo
, calendar
, timeZone
,
4273 if (!AddDuration(cx
, duration
, other
, &result
)) {
4279 auto* obj
= CreateTemporalDuration(cx
, result
);
4284 args
.rval().setObject(*obj
);
4289 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4290 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4293 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4294 CallArgs args
= CallArgsFromVp(argc
, vp
);
4297 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
4303 if (args
.hasDefined(0) &&
4304 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
4310 if (args
.hasDefined(1) &&
4311 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
4317 if (args
.hasDefined(2) &&
4318 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
4324 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
4330 if (args
.hasDefined(4) &&
4331 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
4337 if (args
.hasDefined(5) &&
4338 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
4344 if (args
.hasDefined(6) &&
4345 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
4350 double milliseconds
= 0;
4351 if (args
.hasDefined(7) &&
4352 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
4357 double microseconds
= 0;
4358 if (args
.hasDefined(8) &&
4359 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
4364 double nanoseconds
= 0;
4365 if (args
.hasDefined(9) &&
4366 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
4371 auto* duration
= CreateTemporalDuration(
4373 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
4374 microseconds
, nanoseconds
});
4379 args
.rval().setObject(*duration
);
4384 * Temporal.Duration.from ( item )
4386 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4387 CallArgs args
= CallArgsFromVp(argc
, vp
);
4389 Handle
<Value
> item
= args
.get(0);
4392 if (item
.isObject()) {
4393 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
4394 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
4399 args
.rval().setObject(*result
);
4405 auto result
= ToTemporalDuration(cx
, item
);
4410 args
.rval().setObject(*result
);
4415 * Temporal.Duration.compare ( one, two [ , options ] )
4417 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4418 CallArgs args
= CallArgsFromVp(argc
, vp
);
4422 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
4428 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
4433 Rooted
<JSObject
*> options(cx
);
4434 if (args
.hasDefined(2)) {
4435 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
4443 args
.rval().setInt32(0);
4448 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4449 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4450 Rooted
<TimeZoneRecord
> timeZone(cx
);
4452 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4453 &zonedRelativeTo
, &timeZone
)) {
4456 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4457 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4461 auto hasCalendarUnit
= [](const auto& d
) {
4462 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
4464 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
4467 Rooted
<CalendarRecord
> calendar(cx
);
4468 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4471 CalendarMethod::DateAdd
,
4478 if (zonedRelativeTo
&&
4479 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
4481 const auto& instant
= zonedRelativeTo
.instant();
4484 PlainDateTime dateTime
;
4485 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
4490 auto normalized1
= CreateNormalizedDurationRecord(one
);
4493 auto normalized2
= CreateNormalizedDurationRecord(two
);
4497 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
4498 dateTime
, &after1
)) {
4504 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
4505 dateTime
, &after2
)) {
4510 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
4515 double days1
, days2
;
4516 if (calendarUnitsPresent
) {
4517 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4520 DateDuration unbalanceResult1
;
4521 if (plainRelativeTo
) {
4522 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
4523 TemporalUnit::Day
, plainRelativeTo
,
4524 calendar
, &unbalanceResult1
)) {
4528 if (!UnbalanceDateDurationRelative(
4529 cx
, one
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult1
)) {
4532 MOZ_ASSERT(one
.toDateDuration() == unbalanceResult1
);
4536 DateDuration unbalanceResult2
;
4537 if (plainRelativeTo
) {
4538 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
4539 TemporalUnit::Day
, plainRelativeTo
,
4540 calendar
, &unbalanceResult2
)) {
4544 if (!UnbalanceDateDurationRelative(
4545 cx
, two
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult2
)) {
4548 MOZ_ASSERT(two
.toDateDuration() == unbalanceResult2
);
4552 days1
= unbalanceResult1
.days
;
4555 days2
= unbalanceResult2
.days
;
4565 auto normalized1
= NormalizeTimeDuration(one
);
4568 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
4574 auto normalized2
= NormalizeTimeDuration(two
);
4577 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
4583 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
4588 * get Temporal.Duration.prototype.years
4590 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
4592 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4593 args
.rval().setNumber(duration
->years());
4598 * get Temporal.Duration.prototype.years
4600 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4602 CallArgs args
= CallArgsFromVp(argc
, vp
);
4603 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
4607 * get Temporal.Duration.prototype.months
4609 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
4611 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4612 args
.rval().setNumber(duration
->months());
4617 * get Temporal.Duration.prototype.months
4619 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4621 CallArgs args
= CallArgsFromVp(argc
, vp
);
4622 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
4626 * get Temporal.Duration.prototype.weeks
4628 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
4630 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4631 args
.rval().setNumber(duration
->weeks());
4636 * get Temporal.Duration.prototype.weeks
4638 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4640 CallArgs args
= CallArgsFromVp(argc
, vp
);
4641 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
4645 * get Temporal.Duration.prototype.days
4647 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
4649 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4650 args
.rval().setNumber(duration
->days());
4655 * get Temporal.Duration.prototype.days
4657 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4659 CallArgs args
= CallArgsFromVp(argc
, vp
);
4660 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
4664 * get Temporal.Duration.prototype.hours
4666 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
4668 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4669 args
.rval().setNumber(duration
->hours());
4674 * get Temporal.Duration.prototype.hours
4676 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4678 CallArgs args
= CallArgsFromVp(argc
, vp
);
4679 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
4683 * get Temporal.Duration.prototype.minutes
4685 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
4687 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4688 args
.rval().setNumber(duration
->minutes());
4693 * get Temporal.Duration.prototype.minutes
4695 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4697 CallArgs args
= CallArgsFromVp(argc
, vp
);
4698 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
4702 * get Temporal.Duration.prototype.seconds
4704 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
4706 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4707 args
.rval().setNumber(duration
->seconds());
4712 * get Temporal.Duration.prototype.seconds
4714 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4716 CallArgs args
= CallArgsFromVp(argc
, vp
);
4717 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
4721 * get Temporal.Duration.prototype.milliseconds
4723 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
4725 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4726 args
.rval().setNumber(duration
->milliseconds());
4731 * get Temporal.Duration.prototype.milliseconds
4733 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4735 CallArgs args
= CallArgsFromVp(argc
, vp
);
4736 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
4740 * get Temporal.Duration.prototype.microseconds
4742 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
4744 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4745 args
.rval().setNumber(duration
->microseconds());
4750 * get Temporal.Duration.prototype.microseconds
4752 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4754 CallArgs args
= CallArgsFromVp(argc
, vp
);
4755 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
4759 * get Temporal.Duration.prototype.nanoseconds
4761 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
4763 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4764 args
.rval().setNumber(duration
->nanoseconds());
4769 * get Temporal.Duration.prototype.nanoseconds
4771 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4773 CallArgs args
= CallArgsFromVp(argc
, vp
);
4774 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
4778 * get Temporal.Duration.prototype.sign
4780 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
4781 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4784 args
.rval().setInt32(DurationSign(duration
));
4789 * get Temporal.Duration.prototype.sign
4791 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4793 CallArgs args
= CallArgsFromVp(argc
, vp
);
4794 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
4798 * get Temporal.Duration.prototype.blank
4800 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
4801 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4804 args
.rval().setBoolean(duration
== Duration
{});
4809 * get Temporal.Duration.prototype.blank
4811 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4813 CallArgs args
= CallArgsFromVp(argc
, vp
);
4814 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
4818 * Temporal.Duration.prototype.with ( temporalDurationLike )
4820 * ToPartialDuration ( temporalDurationLike )
4822 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
4823 // Absent values default to the corresponding values of |this| object.
4824 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4827 Rooted
<JSObject
*> temporalDurationLike(
4828 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
4829 if (!temporalDurationLike
) {
4832 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
4837 auto* result
= CreateTemporalDuration(cx
, duration
);
4842 args
.rval().setObject(*result
);
4847 * Temporal.Duration.prototype.with ( temporalDurationLike )
4849 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4851 CallArgs args
= CallArgsFromVp(argc
, vp
);
4852 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
4856 * Temporal.Duration.prototype.negated ( )
4858 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
4859 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4862 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
4867 args
.rval().setObject(*result
);
4872 * Temporal.Duration.prototype.negated ( )
4874 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4876 CallArgs args
= CallArgsFromVp(argc
, vp
);
4877 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
4881 * Temporal.Duration.prototype.abs ( )
4883 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
4884 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4887 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
4892 args
.rval().setObject(*result
);
4897 * Temporal.Duration.prototype.abs ( )
4899 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4901 CallArgs args
= CallArgsFromVp(argc
, vp
);
4902 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
4906 * Temporal.Duration.prototype.add ( other [ , options ] )
4908 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
4909 return AddDurationToOrSubtractDurationFromDuration(cx
, DurationOperation::Add
,
4914 * Temporal.Duration.prototype.add ( other [ , options ] )
4916 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4918 CallArgs args
= CallArgsFromVp(argc
, vp
);
4919 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
4923 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4925 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
4926 return AddDurationToOrSubtractDurationFromDuration(
4927 cx
, DurationOperation::Subtract
, args
);
4931 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4933 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4935 CallArgs args
= CallArgsFromVp(argc
, vp
);
4936 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
4940 * Temporal.Duration.prototype.round ( roundTo )
4942 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4943 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4945 // Step 18. (Reordered)
4946 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
4949 auto smallestUnit
= TemporalUnit::Auto
;
4950 TemporalUnit largestUnit
;
4951 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
4952 auto roundingIncrement
= Increment
{1};
4953 Rooted
<JSObject
*> relativeTo(cx
);
4954 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4955 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4956 Rooted
<TimeZoneRecord
> timeZone(cx
);
4957 if (args
.get(0).isString()) {
4958 // Step 4. (Not applicable in our implementation.)
4960 // Steps 6-15. (Not applicable)
4963 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4964 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
4965 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
4969 // Step 17. (Not applicable)
4971 // Step 18. (Moved above)
4974 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4976 // Step 20. (Not applicable)
4978 // Step 20.a. (Not applicable)
4981 largestUnit
= defaultLargestUnit
;
4983 // Steps 21-25. (Not applicable)
4986 Rooted
<JSObject
*> options(
4987 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
4993 bool smallestUnitPresent
= true;
4996 bool largestUnitPresent
= true;
5000 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
5001 // absent "largestUnit" value.
5002 Rooted
<Value
> largestUnitValue(cx
);
5003 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
5004 &largestUnitValue
)) {
5008 if (!largestUnitValue
.isUndefined()) {
5009 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
5010 if (!largestUnitStr
) {
5014 largestUnit
= TemporalUnit::Auto
;
5015 if (!GetTemporalUnit(cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
5016 TemporalUnitGroup::DateTime
, &largestUnit
)) {
5022 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
5023 &zonedRelativeTo
, &timeZone
)) {
5026 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5027 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5030 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
5035 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5040 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5041 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5046 if (smallestUnit
== TemporalUnit::Auto
) {
5048 smallestUnitPresent
= false;
5051 smallestUnit
= TemporalUnit::Nanosecond
;
5054 // Step 18. (Moved above)
5057 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5060 if (largestUnitValue
.isUndefined()) {
5062 largestUnitPresent
= false;
5065 largestUnit
= defaultLargestUnit
;
5066 } else if (largestUnit
== TemporalUnit::Auto
) {
5068 largestUnit
= defaultLargestUnit
;
5072 if (!smallestUnitPresent
&& !largestUnitPresent
) {
5073 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5074 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
5079 if (largestUnit
> smallestUnit
) {
5080 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5081 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
5086 if (smallestUnit
> TemporalUnit::Day
) {
5088 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
5091 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
5099 bool hoursToDaysConversionMayOccur
= false;
5102 if (duration
.days
!= 0 && zonedRelativeTo
) {
5103 hoursToDaysConversionMayOccur
= true;
5107 else if (std::abs(duration
.hours
) >= 24) {
5108 hoursToDaysConversionMayOccur
= true;
5112 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
5113 roundingIncrement
== Increment
{1};
5116 bool calendarUnitsPresent
=
5117 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
5120 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
5121 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
5122 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
5123 std::abs(duration
.milliseconds
) < 1000 &&
5124 std::abs(duration
.microseconds
) < 1000 &&
5125 std::abs(duration
.nanoseconds
) < 1000) {
5127 auto* obj
= CreateTemporalDuration(cx
, duration
);
5132 args
.rval().setObject(*obj
);
5137 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5140 bool plainDateTimeOrRelativeToWillBeUsed
=
5141 !roundingGranularityIsNoop
|| largestUnit
<= TemporalUnit::Day
||
5142 calendarUnitsPresent
|| duration
.days
!= 0;
5145 PlainDateTime relativeToDateTime
;
5146 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5148 const auto& instant
= zonedRelativeTo
.instant();
5151 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5154 precalculatedPlainDateTime
=
5155 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5158 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5159 zonedRelativeTo
.calendar());
5160 if (!plainRelativeTo
) {
5166 Rooted
<CalendarRecord
> calendar(cx
);
5167 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5170 CalendarMethod::DateAdd
,
5171 CalendarMethod::DateUntil
,
5178 DateDuration unbalanceResult
;
5179 if (plainRelativeTo
) {
5180 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5181 largestUnit
, plainRelativeTo
, calendar
,
5182 &unbalanceResult
)) {
5186 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5187 largestUnit
, &unbalanceResult
)) {
5190 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5195 NormalizedDuration
{unbalanceResult
, NormalizeTimeDuration(duration
)};
5196 NormalizedDuration roundResult
;
5197 if (plainRelativeTo
|| zonedRelativeTo
) {
5198 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5199 roundingMode
, plainRelativeTo
, calendar
,
5200 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5205 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5206 roundingMode
, &roundResult
)) {
5212 TimeDuration balanceResult
;
5213 if (zonedRelativeTo
) {
5215 NormalizedDuration adjustResult
;
5216 if (!AdjustRoundedDurationDays(cx
, roundResult
, roundingIncrement
,
5217 smallestUnit
, roundingMode
, zonedRelativeTo
,
5219 precalculatedPlainDateTime
, &adjustResult
)) {
5222 roundResult
= adjustResult
;
5225 if (!BalanceTimeDurationRelative(
5226 cx
, roundResult
, largestUnit
, zonedRelativeTo
, timeZone
,
5227 precalculatedPlainDateTime
, &balanceResult
)) {
5232 NormalizedTimeDuration withDays
;
5233 if (!Add24HourDaysToNormalizedTimeDuration(
5234 cx
, roundResult
.time
, roundResult
.date
.days
, &withDays
)) {
5239 balanceResult
= temporal::BalanceTimeDuration(withDays
, largestUnit
);
5243 auto balanceInput
= DateDuration
{
5244 roundResult
.date
.years
,
5245 roundResult
.date
.months
,
5246 roundResult
.date
.weeks
,
5249 DateDuration dateResult
;
5250 if (!::BalanceDateDurationRelative(cx
, balanceInput
, largestUnit
,
5251 smallestUnit
, plainRelativeTo
, calendar
,
5257 auto result
= Duration
{
5258 double(dateResult
.years
), double(dateResult
.months
),
5259 double(dateResult
.weeks
), double(dateResult
.days
),
5260 double(balanceResult
.hours
), double(balanceResult
.minutes
),
5261 double(balanceResult
.seconds
), double(balanceResult
.milliseconds
),
5262 balanceResult
.microseconds
, balanceResult
.nanoseconds
,
5264 MOZ_ASSERT(IsValidDuration(result
));
5265 auto* obj
= CreateTemporalDuration(cx
, result
);
5270 args
.rval().setObject(*obj
);
5275 * Temporal.Duration.prototype.round ( options )
5277 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5279 CallArgs args
= CallArgsFromVp(argc
, vp
);
5280 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
5284 * Temporal.Duration.prototype.total ( totalOf )
5286 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
5287 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
5288 auto duration
= ToDuration(durationObj
);
5291 Rooted
<JSObject
*> relativeTo(cx
);
5292 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5293 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5294 Rooted
<TimeZoneRecord
> timeZone(cx
);
5295 auto unit
= TemporalUnit::Auto
;
5296 if (args
.get(0).isString()) {
5297 // Step 4. (Not applicable in our implementation.)
5299 // Steps 6-10. (Implicit)
5300 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
5303 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5304 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::Unit
,
5305 TemporalUnitGroup::DateTime
, &unit
)) {
5310 Rooted
<JSObject
*> totalOf(
5311 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
5317 if (!ToRelativeTemporalObject(cx
, totalOf
, &plainRelativeTo
,
5318 &zonedRelativeTo
, &timeZone
)) {
5321 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5322 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5325 if (!GetTemporalUnit(cx
, totalOf
, TemporalUnitKey::Unit
,
5326 TemporalUnitGroup::DateTime
, &unit
)) {
5330 if (unit
== TemporalUnit::Auto
) {
5331 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5332 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
5338 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5341 bool plainDateTimeOrRelativeToWillBeUsed
=
5342 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
5345 PlainDateTime relativeToDateTime
;
5346 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5348 const auto& instant
= zonedRelativeTo
.instant();
5351 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5354 precalculatedPlainDateTime
=
5355 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5358 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5359 zonedRelativeTo
.calendar());
5360 if (!plainRelativeTo
) {
5366 Rooted
<CalendarRecord
> calendar(cx
);
5367 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5370 CalendarMethod::DateAdd
,
5371 CalendarMethod::DateUntil
,
5378 DateDuration unbalanceResult
;
5379 if (plainRelativeTo
) {
5380 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5381 plainRelativeTo
, calendar
,
5382 &unbalanceResult
)) {
5386 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5387 &unbalanceResult
)) {
5390 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5394 int64_t unbalancedDays
= unbalanceResult
.days
;
5398 NormalizedTimeDuration balanceResult
;
5399 if (zonedRelativeTo
) {
5401 Rooted
<ZonedDateTime
> intermediate(cx
);
5402 if (!MoveRelativeZonedDateTime(
5403 cx
, zonedRelativeTo
, calendar
, timeZone
,
5404 {unbalanceResult
.years
, unbalanceResult
.months
,
5405 unbalanceResult
.weeks
, 0},
5406 precalculatedPlainDateTime
, &intermediate
)) {
5411 auto timeDuration
= NormalizeTimeDuration(duration
);
5414 const auto& startNs
= intermediate
.instant();
5417 const auto& startInstant
= startNs
;
5420 mozilla::Maybe
<PlainDateTime
> startDateTime
{};
5423 Instant intermediateNs
;
5424 if (unbalancedDays
!= 0) {
5426 PlainDateTime dateTime
;
5427 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5430 startDateTime
= mozilla::Some(dateTime
);
5433 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
5435 if (!AddDaysToZonedDateTime(cx
, startInstant
, dateTime
, timeZone
,
5436 isoCalendar
, unbalancedDays
, &addResult
)) {
5441 intermediateNs
= addResult
;
5444 intermediateNs
= startNs
;
5449 if (!AddInstant(cx
, intermediateNs
, timeDuration
, &endNs
)) {
5455 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startNs
);
5459 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5460 if (TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
&&
5461 difference
!= NormalizedTimeDuration
{}) {
5463 if (!startDateTime
) {
5464 PlainDateTime dateTime
;
5465 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5468 startDateTime
= mozilla::Some(dateTime
);
5472 NormalizedTimeAndDays timeAndDays
;
5473 if (!NormalizedTimeDurationToDays(cx
, difference
, intermediate
, timeZone
,
5474 *startDateTime
, &timeAndDays
)) {
5479 balanceResult
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
5482 days
= timeAndDays
.days
;
5485 balanceResult
= difference
;
5490 auto timeDuration
= NormalizeTimeDuration(duration
);
5493 if (!Add24HourDaysToNormalizedTimeDuration(cx
, timeDuration
, unbalancedDays
,
5501 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult
));
5504 auto roundInput
= NormalizedDuration
{
5506 unbalanceResult
.years
,
5507 unbalanceResult
.months
,
5508 unbalanceResult
.weeks
,
5514 if (plainRelativeTo
|| zonedRelativeTo
) {
5515 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5516 TemporalRoundingMode::Trunc
, plainRelativeTo
, calendar
,
5517 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5522 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5523 TemporalRoundingMode::Trunc
, &total
)) {
5529 args
.rval().setNumber(total
);
5534 * Temporal.Duration.prototype.total ( totalOf )
5536 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5538 CallArgs args
= CallArgsFromVp(argc
, vp
);
5539 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
5543 * Temporal.Duration.prototype.toString ( [ options ] )
5545 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
5546 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5549 SecondsStringPrecision precision
= {Precision::Auto(),
5550 TemporalUnit::Nanosecond
, Increment
{1}};
5551 auto roundingMode
= TemporalRoundingMode::Trunc
;
5552 if (args
.hasDefined(0)) {
5554 Rooted
<JSObject
*> options(
5555 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
5561 auto digits
= Precision::Auto();
5562 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
5567 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5572 auto smallestUnit
= TemporalUnit::Auto
;
5573 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5574 TemporalUnitGroup::Time
, &smallestUnit
)) {
5579 if (smallestUnit
== TemporalUnit::Hour
||
5580 smallestUnit
== TemporalUnit::Minute
) {
5581 const char* smallestUnitStr
=
5582 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
5583 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5584 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
5585 smallestUnitStr
, "smallestUnit");
5590 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
5595 if (precision
.unit
!= TemporalUnit::Nanosecond
||
5596 precision
.increment
!= Increment
{1}) {
5598 auto timeDuration
= NormalizeTimeDuration(duration
);
5601 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
5604 auto rounded
= RoundDuration(timeDuration
, precision
.increment
,
5605 precision
.unit
, roundingMode
);
5608 auto balanced
= BalanceTimeDuration(
5609 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
5613 duration
.years
, duration
.months
,
5614 duration
.weeks
, duration
.days
+ double(balanced
.days
),
5615 double(balanced
.hours
), double(balanced
.minutes
),
5616 double(balanced
.seconds
), double(balanced
.milliseconds
),
5617 balanced
.microseconds
, balanced
.nanoseconds
,
5619 MOZ_ASSERT(IsValidDuration(duration
));
5626 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
5631 args
.rval().setString(str
);
5636 * Temporal.Duration.prototype.toString ( [ options ] )
5638 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5640 CallArgs args
= CallArgsFromVp(argc
, vp
);
5641 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
5645 * Temporal.Duration.prototype.toJSON ( )
5647 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
5648 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5651 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5656 args
.rval().setString(str
);
5661 * Temporal.Duration.prototype.toJSON ( )
5663 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5665 CallArgs args
= CallArgsFromVp(argc
, vp
);
5666 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
5670 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5672 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
5673 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5676 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5681 args
.rval().setString(str
);
5686 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5688 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5690 CallArgs args
= CallArgsFromVp(argc
, vp
);
5691 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
5695 * Temporal.Duration.prototype.valueOf ( )
5697 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5698 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
5699 "Duration", "primitive type");
5703 const JSClass
DurationObject::class_
= {
5704 "Temporal.Duration",
5705 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
5706 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
5708 &DurationObject::classSpec_
,
5711 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
5713 static const JSFunctionSpec Duration_methods
[] = {
5714 JS_FN("from", Duration_from
, 1, 0),
5715 JS_FN("compare", Duration_compare
, 2, 0),
5719 static const JSFunctionSpec Duration_prototype_methods
[] = {
5720 JS_FN("with", Duration_with
, 1, 0),
5721 JS_FN("negated", Duration_negated
, 0, 0),
5722 JS_FN("abs", Duration_abs
, 0, 0),
5723 JS_FN("add", Duration_add
, 1, 0),
5724 JS_FN("subtract", Duration_subtract
, 1, 0),
5725 JS_FN("round", Duration_round
, 1, 0),
5726 JS_FN("total", Duration_total
, 1, 0),
5727 JS_FN("toString", Duration_toString
, 0, 0),
5728 JS_FN("toJSON", Duration_toJSON
, 0, 0),
5729 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
5730 JS_FN("valueOf", Duration_valueOf
, 0, 0),
5734 static const JSPropertySpec Duration_prototype_properties
[] = {
5735 JS_PSG("years", Duration_years
, 0),
5736 JS_PSG("months", Duration_months
, 0),
5737 JS_PSG("weeks", Duration_weeks
, 0),
5738 JS_PSG("days", Duration_days
, 0),
5739 JS_PSG("hours", Duration_hours
, 0),
5740 JS_PSG("minutes", Duration_minutes
, 0),
5741 JS_PSG("seconds", Duration_seconds
, 0),
5742 JS_PSG("milliseconds", Duration_milliseconds
, 0),
5743 JS_PSG("microseconds", Duration_microseconds
, 0),
5744 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
5745 JS_PSG("sign", Duration_sign
, 0),
5746 JS_PSG("blank", Duration_blank
, 0),
5747 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
5751 const ClassSpec
DurationObject::classSpec_
= {
5752 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
5753 GenericCreatePrototype
<DurationObject
>,
5756 Duration_prototype_methods
,
5757 Duration_prototype_properties
,
5759 ClassSpec::DontDefineConstructor
,