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/Casting.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/EnumSet.h"
13 #include "mozilla/FloatingPoint.h"
14 #include "mozilla/Maybe.h"
19 #include <initializer_list>
21 #include <type_traits>
26 #include "NamespaceImports.h"
28 #include "builtin/temporal/Calendar.h"
29 #include "builtin/temporal/Instant.h"
30 #include "builtin/temporal/Int128.h"
31 #include "builtin/temporal/Int96.h"
32 #include "builtin/temporal/PlainDate.h"
33 #include "builtin/temporal/PlainDateTime.h"
34 #include "builtin/temporal/Temporal.h"
35 #include "builtin/temporal/TemporalFields.h"
36 #include "builtin/temporal/TemporalParser.h"
37 #include "builtin/temporal/TemporalRoundingMode.h"
38 #include "builtin/temporal/TemporalTypes.h"
39 #include "builtin/temporal/TemporalUnit.h"
40 #include "builtin/temporal/TimeZone.h"
41 #include "builtin/temporal/Wrapped.h"
42 #include "builtin/temporal/ZonedDateTime.h"
43 #include "gc/AllocKind.h"
44 #include "gc/Barrier.h"
45 #include "gc/GCEnum.h"
46 #include "js/CallArgs.h"
47 #include "js/CallNonGenericMethod.h"
49 #include "js/Conversions.h"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
54 #include "js/Printer.h"
55 #include "js/PropertyDescriptor.h"
56 #include "js/PropertySpec.h"
57 #include "js/RootingAPI.h"
59 #include "util/StringBuffer.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 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
87 milliseconds
, 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 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
100 milliseconds
, 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
;
116 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
117 * milliseconds, microseconds, nanoseconds )
119 int32_t js::temporal::DurationSign(const Duration
& duration
) {
120 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
122 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
123 milliseconds
, microseconds
, nanoseconds
] = duration
;
126 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
127 milliseconds
, microseconds
, nanoseconds
}) {
144 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
145 * milliseconds, microseconds, nanoseconds )
147 static int32_t DurationSign(const DateDuration
& duration
) {
148 const auto& [years
, months
, weeks
, days
] = duration
;
151 for (auto v
: {years
, months
, weeks
, days
}) {
168 * Normalize a nanoseconds amount into a time duration.
170 static NormalizedTimeDuration
NormalizeNanoseconds(const Int96
& nanoseconds
) {
171 // Split into seconds and nanoseconds.
172 auto [seconds
, nanos
] = nanoseconds
/ ToNanoseconds(TemporalUnit::Second
);
174 return {seconds
, nanos
};
178 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
179 * value is too large.
181 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeNanoseconds(
182 double nanoseconds
) {
183 MOZ_ASSERT(IsInteger(nanoseconds
));
185 if (auto int96
= Int96::fromInteger(nanoseconds
)) {
186 // The number of normalized seconds must not exceed `2**53 - 1`.
187 constexpr auto limit
=
188 Int96
{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second
);
190 if (int96
->abs() < limit
) {
191 return mozilla::Some(NormalizeNanoseconds(*int96
));
194 return mozilla::Nothing();
198 * Normalize a microseconds amount into a time duration.
200 static NormalizedTimeDuration
NormalizeMicroseconds(const Int96
& microseconds
) {
201 // Split into seconds and microseconds.
202 auto [seconds
, micros
] = microseconds
/ ToMicroseconds(TemporalUnit::Second
);
204 // Scale microseconds to nanoseconds.
205 int32_t nanos
= micros
* int32_t(ToNanoseconds(TemporalUnit::Microsecond
));
207 return {seconds
, nanos
};
211 * Normalize a microseconds amount into a time duration. Return Nothing if the
212 * value is too large.
214 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeMicroseconds(
215 double microseconds
) {
216 MOZ_ASSERT(IsInteger(microseconds
));
218 if (auto int96
= Int96::fromInteger(microseconds
)) {
219 // The number of normalized seconds must not exceed `2**53 - 1`.
220 constexpr auto limit
=
221 Int96
{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second
);
223 if (int96
->abs() < limit
) {
224 return mozilla::Some(NormalizeMicroseconds(*int96
));
227 return mozilla::Nothing();
231 * Normalize a duration into a time duration. Return Nothing if any duration
232 * value is too large.
234 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeSeconds(
235 const Duration
& duration
) {
237 auto nanoseconds
= NormalizeNanoseconds(duration
.nanoseconds
);
241 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds
));
243 auto microseconds
= NormalizeMicroseconds(duration
.microseconds
);
247 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds
));
249 // Overflows for millis/seconds/minutes/hours/days always result in an
250 // invalid normalized time duration.
252 int64_t milliseconds
;
253 if (!mozilla::NumberEqualsInt64(duration
.milliseconds
, &milliseconds
)) {
258 if (!mozilla::NumberEqualsInt64(duration
.seconds
, &seconds
)) {
263 if (!mozilla::NumberEqualsInt64(duration
.minutes
, &minutes
)) {
268 if (!mozilla::NumberEqualsInt64(duration
.hours
, &hours
)) {
273 if (!mozilla::NumberEqualsInt64(duration
.days
, &days
)) {
277 // Compute the overall amount of milliseconds.
278 mozilla::CheckedInt64 millis
= days
;
286 millis
+= milliseconds
;
287 if (!millis
.isValid()) {
291 auto milli
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
292 if (!IsValidNormalizedTimeDuration(milli
)) {
296 // Compute the overall time duration.
297 auto result
= milli
+ *microseconds
+ *nanoseconds
;
298 if (!IsValidNormalizedTimeDuration(result
)) {
302 return mozilla::Some(result
);
305 return mozilla::Nothing();
309 * Normalize a days amount into a time duration. Return Nothing if the value is
312 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeDays(int64_t days
) {
314 // Compute the overall amount of milliseconds.
316 mozilla::CheckedInt64(days
) * ToMilliseconds(TemporalUnit::Day
);
317 if (!millis
.isValid()) {
321 auto result
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
322 if (!IsValidNormalizedTimeDuration(result
)) {
326 return mozilla::Some(result
);
329 return mozilla::Nothing();
333 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
336 static NormalizedTimeDuration
NormalizeTimeDuration(
337 double hours
, double minutes
, double seconds
, double milliseconds
,
338 double microseconds
, double nanoseconds
) {
339 MOZ_ASSERT(IsInteger(hours
));
340 MOZ_ASSERT(IsInteger(minutes
));
341 MOZ_ASSERT(IsInteger(seconds
));
342 MOZ_ASSERT(IsInteger(milliseconds
));
343 MOZ_ASSERT(IsInteger(microseconds
));
344 MOZ_ASSERT(IsInteger(nanoseconds
));
347 mozilla::CheckedInt64 millis
= int64_t(hours
);
349 millis
+= int64_t(minutes
);
351 millis
+= int64_t(seconds
);
353 millis
+= int64_t(milliseconds
);
354 MOZ_ASSERT(millis
.isValid());
356 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
359 auto micros
= Int96::fromInteger(microseconds
);
362 normalized
+= NormalizeMicroseconds(*micros
);
365 auto nanos
= Int96::fromInteger(nanoseconds
);
368 normalized
+= NormalizeNanoseconds(*nanos
);
371 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
378 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
381 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
382 int32_t hours
, int32_t minutes
, int32_t seconds
, int32_t milliseconds
,
383 int32_t microseconds
, int32_t nanoseconds
) {
385 mozilla::CheckedInt64 millis
= int64_t(hours
);
387 millis
+= int64_t(minutes
);
389 millis
+= int64_t(seconds
);
391 millis
+= int64_t(milliseconds
);
392 MOZ_ASSERT(millis
.isValid());
394 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
397 normalized
+= NormalizeMicroseconds(Int96
{microseconds
});
400 normalized
+= NormalizeNanoseconds(Int96
{nanoseconds
});
403 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
410 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
413 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
414 const Duration
& duration
) {
415 MOZ_ASSERT(IsValidDuration(duration
));
417 return ::NormalizeTimeDuration(duration
.hours
, duration
.minutes
,
418 duration
.seconds
, duration
.milliseconds
,
419 duration
.microseconds
, duration
.nanoseconds
);
423 * AddNormalizedTimeDuration ( one, two )
425 static bool AddNormalizedTimeDuration(JSContext
* cx
,
426 const NormalizedTimeDuration
& one
,
427 const NormalizedTimeDuration
& two
,
428 NormalizedTimeDuration
* result
) {
429 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
430 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
433 auto sum
= one
+ two
;
436 if (!IsValidNormalizedTimeDuration(sum
)) {
437 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
438 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
448 * SubtractNormalizedTimeDuration ( one, two )
450 static bool SubtractNormalizedTimeDuration(JSContext
* cx
,
451 const NormalizedTimeDuration
& one
,
452 const NormalizedTimeDuration
& two
,
453 NormalizedTimeDuration
* result
) {
454 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
455 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
458 auto sum
= one
- two
;
461 if (!IsValidNormalizedTimeDuration(sum
)) {
462 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
463 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
473 * Add24HourDaysToNormalizedTimeDuration ( d, days )
475 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
476 JSContext
* cx
, const NormalizedTimeDuration
& d
, int64_t days
,
477 NormalizedTimeDuration
* result
) {
478 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
481 auto normalizedDays
= NormalizeDays(days
);
482 if (!normalizedDays
) {
483 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
484 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
489 auto sum
= d
+ *normalizedDays
;
490 if (!IsValidNormalizedTimeDuration(sum
)) {
491 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
492 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
502 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
504 bool js::temporal::CombineDateAndNormalizedTimeDuration(
505 JSContext
* cx
, const DateDuration
& date
, const NormalizedTimeDuration
& time
,
506 NormalizedDuration
* result
) {
507 MOZ_ASSERT(IsValidDuration(date
));
508 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
511 int32_t dateSign
= ::DurationSign(date
);
514 int32_t timeSign
= NormalizedTimeDurationSign(time
);
517 if ((dateSign
* timeSign
) < 0) {
518 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
519 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN
);
524 *result
= {date
, time
};
529 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
531 NormalizedTimeDuration
532 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
533 const Instant
& one
, const Instant
& two
) {
534 MOZ_ASSERT(IsValidEpochInstant(one
));
535 MOZ_ASSERT(IsValidEpochInstant(two
));
538 auto result
= one
- two
;
541 MOZ_ASSERT(IsValidInstantSpan(result
));
544 return result
.to
<NormalizedTimeDuration
>();
548 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
549 * milliseconds, microseconds, nanoseconds )
551 bool js::temporal::IsValidDuration(const Duration
& duration
) {
552 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
554 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
555 milliseconds
, microseconds
, nanoseconds
] = duration
;
558 int32_t sign
= DurationSign(duration
);
561 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
562 milliseconds
, microseconds
, nanoseconds
}) {
564 if (!std::isfinite(v
)) {
569 if (v
< 0 && sign
> 0) {
574 if (v
> 0 && sign
< 0) {
580 if (std::abs(years
) >= double(int64_t(1) << 32)) {
585 if (std::abs(months
) >= double(int64_t(1) << 32)) {
590 if (std::abs(weeks
) >= double(int64_t(1) << 32)) {
595 if (!NormalizeSeconds(duration
)) {
605 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
606 * milliseconds, microseconds, nanoseconds )
608 bool js::temporal::IsValidDuration(const DateDuration
& duration
) {
609 return IsValidDuration(duration
.toDuration());
613 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
614 * milliseconds, microseconds, nanoseconds )
616 bool js::temporal::IsValidDuration(const NormalizedDuration
& duration
) {
617 return IsValidDuration(duration
.date
) &&
618 IsValidNormalizedTimeDuration(duration
.time
) &&
619 (::DurationSign(duration
.date
) *
620 NormalizedTimeDurationSign(duration
.time
) >=
625 static bool ThrowInvalidDurationPart(JSContext
* cx
, double value
,
626 const char* name
, unsigned errorNumber
) {
628 const char* numStr
= NumberToCString(&cbuf
, value
);
630 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
, name
,
636 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
637 * milliseconds, microseconds, nanoseconds )
639 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
640 const Duration
& duration
) {
641 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
643 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
644 milliseconds
, microseconds
, nanoseconds
] = duration
;
647 int32_t sign
= DurationSign(duration
);
649 auto throwIfInvalid
= [&](double v
, const char* name
) {
651 if (!std::isfinite(v
)) {
652 return ThrowInvalidDurationPart(
653 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
657 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
658 return ThrowInvalidDurationPart(cx
, v
, name
,
659 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
665 auto throwIfTooLarge
= [&](double v
, const char* name
) {
666 if (std::abs(v
) >= double(int64_t(1) << 32)) {
667 return ThrowInvalidDurationPart(
668 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
674 if (!throwIfInvalid(years
, "years")) {
677 if (!throwIfInvalid(months
, "months")) {
680 if (!throwIfInvalid(weeks
, "weeks")) {
683 if (!throwIfInvalid(days
, "days")) {
686 if (!throwIfInvalid(hours
, "hours")) {
689 if (!throwIfInvalid(minutes
, "minutes")) {
692 if (!throwIfInvalid(seconds
, "seconds")) {
695 if (!throwIfInvalid(milliseconds
, "milliseconds")) {
698 if (!throwIfInvalid(microseconds
, "microseconds")) {
701 if (!throwIfInvalid(nanoseconds
, "nanoseconds")) {
706 if (!throwIfTooLarge(years
, "years")) {
711 if (!throwIfTooLarge(months
, "months")) {
716 if (!throwIfTooLarge(weeks
, "weeks")) {
721 if (!NormalizeSeconds(duration
)) {
722 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
723 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
727 MOZ_ASSERT(IsValidDuration(duration
));
734 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
735 * milliseconds, microseconds, nanoseconds )
737 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
738 const DateDuration
& duration
) {
739 const auto& [years
, months
, weeks
, days
] = duration
;
742 int32_t sign
= ::DurationSign(duration
);
744 auto throwIfInvalid
= [&](int64_t v
, const char* name
) {
745 // Step 2.a. (Not applicable)
748 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
749 return ThrowInvalidDurationPart(cx
, double(v
), name
,
750 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
756 auto throwIfTooLarge
= [&](int64_t v
, const char* name
) {
757 if (std::abs(v
) >= (int64_t(1) << 32)) {
758 return ThrowInvalidDurationPart(
759 cx
, double(v
), name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
765 if (!throwIfInvalid(years
, "years")) {
768 if (!throwIfInvalid(months
, "months")) {
771 if (!throwIfInvalid(weeks
, "weeks")) {
774 if (!throwIfInvalid(days
, "days")) {
779 if (!throwIfTooLarge(years
, "years")) {
784 if (!throwIfTooLarge(months
, "months")) {
789 if (!throwIfTooLarge(weeks
, "weeks")) {
794 if (std::abs(days
) > ((int64_t(1) << 53) / 86400)) {
795 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
796 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
800 MOZ_ASSERT(IsValidDuration(duration
));
807 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
808 * seconds, milliseconds, microseconds )
810 static TemporalUnit
DefaultTemporalLargestUnit(const Duration
& duration
) {
811 MOZ_ASSERT(IsIntegerDuration(duration
));
814 if (duration
.years
!= 0) {
815 return TemporalUnit::Year
;
819 if (duration
.months
!= 0) {
820 return TemporalUnit::Month
;
824 if (duration
.weeks
!= 0) {
825 return TemporalUnit::Week
;
829 if (duration
.days
!= 0) {
830 return TemporalUnit::Day
;
834 if (duration
.hours
!= 0) {
835 return TemporalUnit::Hour
;
839 if (duration
.minutes
!= 0) {
840 return TemporalUnit::Minute
;
844 if (duration
.seconds
!= 0) {
845 return TemporalUnit::Second
;
849 if (duration
.milliseconds
!= 0) {
850 return TemporalUnit::Millisecond
;
854 if (duration
.microseconds
!= 0) {
855 return TemporalUnit::Microsecond
;
859 return TemporalUnit::Nanosecond
;
863 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
864 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
866 static DurationObject
* CreateTemporalDuration(JSContext
* cx
,
867 const CallArgs
& args
,
868 const Duration
& duration
) {
869 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
870 milliseconds
, microseconds
, nanoseconds
] = duration
;
873 if (!ThrowIfInvalidDuration(cx
, duration
)) {
878 Rooted
<JSObject
*> proto(cx
);
879 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Duration
, &proto
)) {
883 auto* object
= NewObjectWithClassProto
<DurationObject
>(cx
, proto
);
889 // Add zero to convert -0 to +0.
890 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
891 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
892 NumberValue(months
+ (+0.0)));
893 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
894 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
895 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
896 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
897 NumberValue(minutes
+ (+0.0)));
898 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
899 NumberValue(seconds
+ (+0.0)));
900 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
901 NumberValue(milliseconds
+ (+0.0)));
902 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
903 NumberValue(microseconds
+ (+0.0)));
904 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
905 NumberValue(nanoseconds
+ (+0.0)));
912 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
913 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
915 DurationObject
* js::temporal::CreateTemporalDuration(JSContext
* cx
,
916 const Duration
& duration
) {
917 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
918 milliseconds
, microseconds
, nanoseconds
] = duration
;
920 MOZ_ASSERT(IsInteger(years
));
921 MOZ_ASSERT(IsInteger(months
));
922 MOZ_ASSERT(IsInteger(weeks
));
923 MOZ_ASSERT(IsInteger(days
));
924 MOZ_ASSERT(IsInteger(hours
));
925 MOZ_ASSERT(IsInteger(minutes
));
926 MOZ_ASSERT(IsInteger(seconds
));
927 MOZ_ASSERT(IsInteger(milliseconds
));
928 MOZ_ASSERT(IsInteger(microseconds
));
929 MOZ_ASSERT(IsInteger(nanoseconds
));
932 if (!ThrowIfInvalidDuration(cx
, duration
)) {
937 auto* object
= NewBuiltinClassInstance
<DurationObject
>(cx
);
943 // Add zero to convert -0 to +0.
944 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
945 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
946 NumberValue(months
+ (+0.0)));
947 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
948 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
949 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
950 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
951 NumberValue(minutes
+ (+0.0)));
952 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
953 NumberValue(seconds
+ (+0.0)));
954 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
955 NumberValue(milliseconds
+ (+0.0)));
956 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
957 NumberValue(microseconds
+ (+0.0)));
958 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
959 NumberValue(nanoseconds
+ (+0.0)));
966 * ToIntegerIfIntegral ( argument )
968 static bool ToIntegerIfIntegral(JSContext
* cx
, const char* name
,
969 Handle
<Value
> argument
, double* num
) {
972 if (!JS::ToNumber(cx
, argument
, &d
)) {
977 if (!js::IsInteger(d
)) {
979 const char* numStr
= NumberToCString(&cbuf
, d
);
981 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
982 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
993 * ToIntegerIfIntegral ( argument )
995 static bool ToIntegerIfIntegral(JSContext
* cx
, Handle
<PropertyName
*> name
,
996 Handle
<Value
> argument
, double* result
) {
999 if (!JS::ToNumber(cx
, argument
, &d
)) {
1004 if (!js::IsInteger(d
)) {
1005 if (auto nameStr
= js::QuoteString(cx
, name
)) {
1007 const char* numStr
= NumberToCString(&cbuf
, d
);
1009 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1010 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
1022 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1024 static bool ToTemporalPartialDurationRecord(
1025 JSContext
* cx
, Handle
<JSObject
*> temporalDurationLike
, Duration
* result
) {
1026 // Steps 1-3. (Not applicable in our implementation.)
1028 Rooted
<Value
> value(cx
);
1031 auto getDurationProperty
= [&](Handle
<PropertyName
*> name
, double* num
) {
1032 if (!GetProperty(cx
, temporalDurationLike
, temporalDurationLike
, name
,
1037 if (!value
.isUndefined()) {
1040 if (!ToIntegerIfIntegral(cx
, name
, value
, num
)) {
1048 if (!getDurationProperty(cx
->names().days
, &result
->days
)) {
1051 if (!getDurationProperty(cx
->names().hours
, &result
->hours
)) {
1054 if (!getDurationProperty(cx
->names().microseconds
, &result
->microseconds
)) {
1057 if (!getDurationProperty(cx
->names().milliseconds
, &result
->milliseconds
)) {
1060 if (!getDurationProperty(cx
->names().minutes
, &result
->minutes
)) {
1063 if (!getDurationProperty(cx
->names().months
, &result
->months
)) {
1066 if (!getDurationProperty(cx
->names().nanoseconds
, &result
->nanoseconds
)) {
1069 if (!getDurationProperty(cx
->names().seconds
, &result
->seconds
)) {
1072 if (!getDurationProperty(cx
->names().weeks
, &result
->weeks
)) {
1075 if (!getDurationProperty(cx
->names().years
, &result
->years
)) {
1081 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1082 JSMSG_TEMPORAL_DURATION_MISSING_UNIT
);
1091 * ToTemporalDurationRecord ( temporalDurationLike )
1093 bool js::temporal::ToTemporalDurationRecord(JSContext
* cx
,
1094 Handle
<Value
> temporalDurationLike
,
1097 if (!temporalDurationLike
.isObject()) {
1099 if (!temporalDurationLike
.isString()) {
1100 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
,
1101 temporalDurationLike
, nullptr, "not a string");
1104 Rooted
<JSString
*> string(cx
, temporalDurationLike
.toString());
1107 return ParseTemporalDurationString(cx
, string
, result
);
1110 Rooted
<JSObject
*> durationLike(cx
, &temporalDurationLike
.toObject());
1113 if (auto* duration
= durationLike
->maybeUnwrapIf
<DurationObject
>()) {
1114 *result
= ToDuration(duration
);
1119 Duration duration
= {};
1122 if (!ToTemporalPartialDurationRecord(cx
, durationLike
, &duration
)) {
1127 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1137 * ToTemporalDuration ( item )
1139 Wrapped
<DurationObject
*> js::temporal::ToTemporalDuration(JSContext
* cx
,
1140 Handle
<Value
> item
) {
1142 if (item
.isObject()) {
1143 JSObject
* itemObj
= &item
.toObject();
1144 if (itemObj
->canUnwrapAs
<DurationObject
>()) {
1151 if (!ToTemporalDurationRecord(cx
, item
, &result
)) {
1156 return CreateTemporalDuration(cx
, result
);
1160 * ToTemporalDuration ( item )
1162 bool js::temporal::ToTemporalDuration(JSContext
* cx
, Handle
<Value
> item
,
1164 auto obj
= ToTemporalDuration(cx
, item
);
1169 *result
= ToDuration(&obj
.unwrap());
1174 * DaysUntil ( earlier, later )
1176 int32_t js::temporal::DaysUntil(const PlainDate
& earlier
,
1177 const PlainDate
& later
) {
1178 MOZ_ASSERT(ISODateTimeWithinLimits(earlier
));
1179 MOZ_ASSERT(ISODateTimeWithinLimits(later
));
1182 int32_t epochDaysEarlier
= MakeDay(earlier
);
1183 MOZ_ASSERT(std::abs(epochDaysEarlier
) <= 100'000'000);
1186 int32_t epochDaysLater
= MakeDay(later
);
1187 MOZ_ASSERT(std::abs(epochDaysLater
) <= 100'000'000);
1190 return epochDaysLater
- epochDaysEarlier
;
1194 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1196 static bool MoveRelativeDate(
1197 JSContext
* cx
, Handle
<CalendarRecord
> calendar
,
1198 Handle
<Wrapped
<PlainDateObject
*>> relativeTo
, const DateDuration
& duration
,
1199 MutableHandle
<Wrapped
<PlainDateObject
*>> relativeToResult
,
1200 int32_t* daysResult
) {
1201 auto* unwrappedRelativeTo
= relativeTo
.unwrap(cx
);
1202 if (!unwrappedRelativeTo
) {
1205 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1208 auto newDate
= AddDate(cx
, calendar
, relativeTo
, duration
);
1212 auto later
= ToPlainDate(&newDate
.unwrap());
1213 relativeToResult
.set(newDate
);
1216 *daysResult
= DaysUntil(relativeToDate
, later
);
1217 MOZ_ASSERT(std::abs(*daysResult
) <= 200'000'000);
1224 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1225 * months, weeks, days, precalculatedPlainDateTime )
1227 static bool MoveRelativeZonedDateTime(
1228 JSContext
* cx
, Handle
<ZonedDateTime
> zonedDateTime
,
1229 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
1230 const DateDuration
& duration
,
1231 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1232 MutableHandle
<ZonedDateTime
> result
) {
1234 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1235 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1238 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1239 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1242 Instant intermediateNs
;
1243 if (precalculatedPlainDateTime
) {
1244 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1245 duration
, *precalculatedPlainDateTime
,
1250 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1251 duration
, &intermediateNs
)) {
1255 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
1258 result
.set(ZonedDateTime
{intermediateNs
, zonedDateTime
.timeZone(),
1259 zonedDateTime
.calendar()});
1264 * Split duration into full days and remainding nanoseconds.
1266 static NormalizedTimeAndDays
NormalizedTimeDurationToDays(
1267 const NormalizedTimeDuration
& duration
) {
1268 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1270 auto [seconds
, nanoseconds
] = duration
;
1271 if (seconds
< 0 && nanoseconds
> 0) {
1273 nanoseconds
-= 1'000'000'000;
1276 int64_t days
= seconds
/ ToSeconds(TemporalUnit::Day
);
1277 seconds
= seconds
% ToSeconds(TemporalUnit::Day
);
1279 int64_t time
= seconds
* ToNanoseconds(TemporalUnit::Second
) + nanoseconds
;
1281 constexpr int64_t dayLength
= ToNanoseconds(TemporalUnit::Day
);
1282 MOZ_ASSERT(std::abs(time
) < dayLength
);
1284 return {days
, time
, dayLength
};
1288 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1289 * microseconds, nanoseconds )
1291 static TimeDuration
CreateTimeDurationRecord(int64_t days
, int64_t hours
,
1292 int64_t minutes
, int64_t seconds
,
1293 int64_t milliseconds
,
1294 double microseconds
,
1295 double nanoseconds
) {
1297 MOZ_ASSERT(IsValidDuration(
1298 {0, 0, 0, double(days
), double(hours
), double(minutes
), double(seconds
),
1299 double(milliseconds
), microseconds
, nanoseconds
}));
1301 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1302 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1303 MOZ_ASSERT(IsSafeInteger(days
));
1304 MOZ_ASSERT(IsSafeInteger(hours
));
1305 MOZ_ASSERT(IsSafeInteger(minutes
));
1306 MOZ_ASSERT(IsSafeInteger(seconds
));
1308 // |milliseconds| is explicitly casted to double by consumers, so we can also
1309 // omit the `ℝ(𝔽(x))` conversion.
1312 // NB: Adds +0.0 to correctly handle negative zero.
1319 microseconds
+ (+0.0),
1320 nanoseconds
+ (+0.0),
1325 * BalanceTimeDuration ( norm, largestUnit )
1327 TimeDuration
js::temporal::BalanceTimeDuration(
1328 const NormalizedTimeDuration
& duration
, TemporalUnit largestUnit
) {
1329 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1331 auto [seconds
, nanoseconds
] = duration
;
1333 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1334 // Convert these back to their absolute value and adjust the seconds part
1337 // For example the nanoseconds duration |-1n| is represented as the
1338 // duration {seconds: -1, nanoseconds: 999'999'999}.
1339 if (seconds
< 0 && nanoseconds
> 0) {
1341 nanoseconds
-= ToNanoseconds(TemporalUnit::Second
);
1347 int64_t minutes
= 0;
1348 int64_t milliseconds
= 0;
1349 int64_t microseconds
= 0;
1351 // Steps 2-3. (Not applicable in our implementation.)
1353 // We don't need to convert to positive numbers, because integer division
1354 // truncates and the %-operator has modulo semantics.
1357 switch (largestUnit
) {
1359 case TemporalUnit::Year
:
1360 case TemporalUnit::Month
:
1361 case TemporalUnit::Week
:
1362 case TemporalUnit::Day
: {
1364 microseconds
= nanoseconds
/ 1000;
1367 nanoseconds
= nanoseconds
% 1000;
1370 milliseconds
= microseconds
/ 1000;
1373 microseconds
= microseconds
% 1000;
1375 // Steps 4.e-f. (Not applicable)
1376 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1379 minutes
= seconds
/ 60;
1382 seconds
= seconds
% 60;
1385 hours
= minutes
/ 60;
1388 minutes
= minutes
% 60;
1400 case TemporalUnit::Hour
: {
1402 microseconds
= nanoseconds
/ 1000;
1405 nanoseconds
= nanoseconds
% 1000;
1408 milliseconds
= microseconds
/ 1000;
1411 microseconds
= microseconds
% 1000;
1413 // Steps 5.e-f. (Not applicable)
1414 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1417 minutes
= seconds
/ 60;
1420 seconds
= seconds
% 60;
1423 hours
= minutes
/ 60;
1426 minutes
= minutes
% 60;
1431 case TemporalUnit::Minute
: {
1433 microseconds
= nanoseconds
/ 1000;
1436 nanoseconds
= nanoseconds
% 1000;
1439 milliseconds
= microseconds
/ 1000;
1442 microseconds
= microseconds
% 1000;
1444 // Steps 6.e-f. (Not applicable)
1445 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1448 minutes
= seconds
/ 60;
1451 seconds
= seconds
% 60;
1457 case TemporalUnit::Second
: {
1459 microseconds
= nanoseconds
/ 1000;
1462 nanoseconds
= nanoseconds
% 1000;
1465 milliseconds
= microseconds
/ 1000;
1468 microseconds
= microseconds
% 1000;
1470 // Steps 7.e-f. (Not applicable)
1471 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1477 case TemporalUnit::Millisecond
: {
1478 static_assert((NormalizedTimeDuration::max().seconds
+ 1) *
1479 ToMilliseconds(TemporalUnit::Second
) <=
1481 "total number duration milliseconds fits into int64");
1483 int64_t millis
= seconds
* ToMilliseconds(TemporalUnit::Second
);
1485 // Set to zero per step 1.
1489 microseconds
= nanoseconds
/ 1000;
1492 nanoseconds
= nanoseconds
% 1000;
1495 milliseconds
= microseconds
/ 1000;
1498 microseconds
= microseconds
% 1000;
1500 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1501 milliseconds
+= millis
;
1503 // The number of normalized seconds must not exceed `2**53 - 1`.
1504 constexpr auto limit
=
1505 (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second
);
1506 constexpr auto max
= int64_t(0x7cff'ffff'ffff'fdff);
1509 int64_t(double(max
)) < limit
&& int64_t(double(max
+ 1)) >= limit
,
1510 "max is the maximum allowed milliseconds value");
1513 (seconds
* ToMilliseconds(TemporalUnit::Second
)) + milliseconds
;
1514 if (totalMillis
> max
) {
1515 // FIXME: spec bug - handle too large duration values
1516 // https://github.com/tc39/proposal-temporal/issues/2785
1523 case TemporalUnit::Microsecond
: {
1525 int64_t microseconds
= nanoseconds
/ 1000;
1528 nanoseconds
= nanoseconds
% 1000;
1530 MOZ_ASSERT(std::abs(microseconds
) <= 999'999);
1532 std::fma(double(seconds
), ToMicroseconds(TemporalUnit::Second
),
1533 double(microseconds
));
1535 // The number of normalized seconds must not exceed `2**53 - 1`.
1536 constexpr auto limit
= Int128
{int64_t(1) << 53} *
1537 Int128
{ToMicroseconds(TemporalUnit::Second
)};
1538 constexpr auto max
=
1539 (Int128
{0x1e8} << 64) + Int128
{0x47ff'ffff'fff7'ffff};
1540 static_assert(max
< limit
);
1543 (Int128
{seconds
} * Int128
{ToMicroseconds(TemporalUnit::Second
)}) +
1544 Int128
{microseconds
};
1545 if (totalMicros
> max
) {
1546 // FIXME: spec bug - handle too large duration values
1547 // https://github.com/tc39/proposal-temporal/issues/2785
1551 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros
,
1552 double(nanoseconds
));
1556 case TemporalUnit::Nanosecond
: {
1557 MOZ_ASSERT(std::abs(nanoseconds
) <= 999'999'999);
1559 std::fma(double(seconds
), ToNanoseconds(TemporalUnit::Second
),
1560 double(nanoseconds
));
1562 // The number of normalized seconds must not exceed `2**53 - 1`.
1563 constexpr auto limit
= Int128
{int64_t(1) << 53} *
1564 Int128
{ToNanoseconds(TemporalUnit::Second
)};
1565 constexpr auto max
=
1566 (Int128
{0x77359} << 64) + Int128
{0x3fff'ffff'dfff'ffff};
1567 static_assert(max
< limit
);
1570 (Int128
{seconds
} * Int128
{ToNanoseconds(TemporalUnit::Second
)}) +
1571 Int128
{nanoseconds
};
1572 if (totalNanos
> max
) {
1573 // FIXME: spec bug - handle too large duration values
1574 // https://github.com/tc39/proposal-temporal/issues/2785
1578 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos
);
1581 case TemporalUnit::Auto
:
1582 MOZ_CRASH("Unexpected temporal unit");
1586 return CreateTimeDurationRecord(days
, hours
, minutes
, seconds
, milliseconds
,
1587 double(microseconds
), double(nanoseconds
));
1591 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1592 * timeZoneRec, precalculatedPlainDateTime )
1594 static bool BalanceTimeDurationRelative(
1595 JSContext
* cx
, const NormalizedDuration
& duration
, TemporalUnit largestUnit
,
1596 Handle
<ZonedDateTime
> relativeTo
, Handle
<TimeZoneRecord
> timeZone
,
1597 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1598 TimeDuration
* result
) {
1599 MOZ_ASSERT(IsValidDuration(duration
));
1602 const auto& startNs
= relativeTo
.instant();
1605 const auto& startInstant
= startNs
;
1608 auto intermediateNs
= startNs
;
1611 PlainDateTime startDateTime
;
1612 if (duration
.date
.days
!= 0) {
1614 if (!precalculatedPlainDateTime
) {
1615 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1618 precalculatedPlainDateTime
=
1619 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1623 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
1624 if (!AddDaysToZonedDateTime(cx
, startInstant
, *precalculatedPlainDateTime
,
1625 timeZone
, isoCalendar
, duration
.date
.days
,
1633 if (!AddInstant(cx
, intermediateNs
, duration
.time
, &endNs
)) {
1636 MOZ_ASSERT(IsValidEpochInstant(endNs
));
1640 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startInstant
);
1643 if (normalized
== NormalizedTimeDuration
{}) {
1650 if (TemporalUnit::Year
<= largestUnit
&& largestUnit
<= TemporalUnit::Day
) {
1652 if (!precalculatedPlainDateTime
) {
1653 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1656 precalculatedPlainDateTime
=
1657 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1661 NormalizedTimeAndDays timeAndDays
;
1662 if (!NormalizedTimeDurationToDays(cx
, normalized
, relativeTo
, timeZone
,
1663 *precalculatedPlainDateTime
,
1669 days
= timeAndDays
.days
;
1672 normalized
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
1673 MOZ_ASSERT_IF(days
> 0, normalized
>= NormalizedTimeDuration
{});
1674 MOZ_ASSERT_IF(days
< 0, normalized
<= NormalizedTimeDuration
{});
1677 largestUnit
= TemporalUnit::Hour
;
1681 auto balanceResult
= BalanceTimeDuration(normalized
, largestUnit
);
1686 balanceResult
.hours
,
1687 balanceResult
.minutes
,
1688 balanceResult
.seconds
,
1689 balanceResult
.milliseconds
,
1690 balanceResult
.microseconds
,
1691 balanceResult
.nanoseconds
,
1693 MOZ_ASSERT(IsValidDuration(result
->toDuration()));
1698 * CreateDateDurationRecord ( years, months, weeks, days )
1700 static DateDuration
CreateDateDurationRecord(int64_t years
, int64_t months
,
1701 int64_t weeks
, int64_t days
) {
1702 MOZ_ASSERT(IsValidDuration(Duration
{
1708 return {years
, months
, weeks
, days
};
1712 * CreateDateDurationRecord ( years, months, weeks, days )
1714 static bool CreateDateDurationRecord(JSContext
* cx
, int64_t years
,
1715 int64_t months
, int64_t weeks
,
1716 int64_t days
, DateDuration
* result
) {
1717 auto duration
= DateDuration
{years
, months
, weeks
, days
};
1718 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1726 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration
& duration
,
1727 TemporalUnit largestUnit
) {
1728 MOZ_ASSERT(largestUnit
!= TemporalUnit::Auto
);
1731 return (largestUnit
> TemporalUnit::Year
&& duration
.years
!= 0) ||
1732 (largestUnit
> TemporalUnit::Month
&& duration
.months
!= 0) ||
1733 (largestUnit
> TemporalUnit::Week
&& duration
.weeks
!= 0);
1737 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1738 * plainRelativeTo, calendarRec )
1740 static bool UnbalanceDateDurationRelative(
1741 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1742 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1743 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1744 MOZ_ASSERT(IsValidDuration(duration
));
1746 auto [years
, months
, weeks
, days
] = duration
;
1748 // Step 1. (Not applicable in our implementation.)
1751 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1757 MOZ_ASSERT(largestUnit
!= TemporalUnit::Year
);
1759 // Step 6. (Not applicable in our implementation.)
1763 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1766 if (largestUnit
== TemporalUnit::Month
) {
1769 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1772 auto yearsDuration
= DateDuration
{years
};
1775 Rooted
<Wrapped
<PlainDateObject
*>> later(
1776 cx
, CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsDuration
));
1782 Duration untilResult
;
1783 if (!CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
,
1784 TemporalUnit::Month
, &untilResult
)) {
1789 int64_t yearsInMonths
= int64_t(untilResult
.months
);
1792 return CreateDateDurationRecord(cx
, 0, months
+ yearsInMonths
, weeks
, days
,
1797 if (largestUnit
== TemporalUnit::Week
) {
1799 auto yearsMonthsDuration
= DateDuration
{years
, months
};
1803 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsDuration
);
1807 auto laterDate
= ToPlainDate(&later
.unwrap());
1809 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1810 if (!unwrappedRelativeTo
) {
1813 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1816 int32_t yearsMonthsInDays
= DaysUntil(relativeToDate
, laterDate
);
1819 return CreateDateDurationRecord(cx
, 0, 0, weeks
, days
+ yearsMonthsInDays
,
1823 // Step 10. (Not applicable in our implementation.)
1826 auto yearsMonthsWeeksDuration
= DateDuration
{years
, months
, weeks
};
1830 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsWeeksDuration
);
1834 auto laterDate
= ToPlainDate(&later
.unwrap());
1836 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1837 if (!unwrappedRelativeTo
) {
1840 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1843 int32_t yearsMonthsWeeksInDay
= DaysUntil(relativeToDate
, laterDate
);
1846 return CreateDateDurationRecord(cx
, 0, 0, 0, days
+ yearsMonthsWeeksInDay
,
1851 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1852 * plainRelativeTo, calendarRec )
1854 static bool UnbalanceDateDurationRelative(JSContext
* cx
,
1855 const DateDuration
& duration
,
1856 TemporalUnit largestUnit
,
1857 DateDuration
* result
) {
1858 MOZ_ASSERT(IsValidDuration(duration
));
1860 // Step 1. (Not applicable.)
1863 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1869 MOZ_ASSERT(largestUnit
!= TemporalUnit::Year
);
1872 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1873 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
, "calendar");
1878 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1879 * smallestUnit, plainRelativeTo, calendarRec )
1881 static bool BalanceDateDurationRelative(
1882 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1883 TemporalUnit smallestUnit
,
1884 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1885 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1886 MOZ_ASSERT(IsValidDuration(duration
));
1887 MOZ_ASSERT(largestUnit
<= smallestUnit
);
1889 auto [years
, months
, weeks
, days
] = duration
;
1891 // FIXME: spec issue - effectful code paths should be more fine-grained
1892 // similar to UnbalanceDateDurationRelative. For example:
1893 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1894 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1895 // 3. Else if days = 0, then no-op.
1897 // Also note that |weeks| is never balanced, even when non-zero.
1899 // Step 1. (Not applicable in our implementation.)
1902 if (largestUnit
> TemporalUnit::Week
||
1903 (years
== 0 && months
== 0 && weeks
== 0 && days
== 0)) {
1910 if (!plainRelativeTo
) {
1911 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1912 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
1919 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1923 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1925 // Steps 8-9. (Not applicable in our implementation.)
1927 auto untilAddedDate
= [&](const DateDuration
& duration
,
1928 Duration
* untilResult
) {
1929 Rooted
<Wrapped
<PlainDateObject
*>> later(
1930 cx
, AddDate(cx
, calendar
, plainRelativeTo
, duration
));
1935 return CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
, largestUnit
,
1940 if (largestUnit
== TemporalUnit::Year
) {
1942 if (smallestUnit
== TemporalUnit::Week
) {
1944 MOZ_ASSERT(days
== 0);
1947 auto yearsMonthsDuration
= DateDuration
{years
, months
};
1949 // Steps 10.a.iii-iv.
1950 Duration untilResult
;
1951 if (!untilAddedDate(yearsMonthsDuration
, &untilResult
)) {
1956 *result
= CreateDateDurationRecord(int64_t(untilResult
.years
),
1957 int64_t(untilResult
.months
), weeks
, 0);
1962 const auto& yearsMonthsWeeksDaysDuration
= duration
;
1965 Duration untilResult
;
1966 if (!untilAddedDate(yearsMonthsWeeksDaysDuration
, &untilResult
)) {
1971 *result
= CreateDateDurationRecord(
1972 int64_t(untilResult
.years
), int64_t(untilResult
.months
),
1973 int64_t(untilResult
.weeks
), int64_t(untilResult
.days
));
1978 if (largestUnit
== TemporalUnit::Month
) {
1980 MOZ_ASSERT(years
== 0);
1983 if (smallestUnit
== TemporalUnit::Week
) {
1985 MOZ_ASSERT(days
== 0);
1988 *result
= CreateDateDurationRecord(0, months
, weeks
, 0);
1993 const auto& monthsWeeksDaysDuration
= duration
;
1996 Duration untilResult
;
1997 if (!untilAddedDate(monthsWeeksDaysDuration
, &untilResult
)) {
2002 *result
= CreateDateDurationRecord(0, int64_t(untilResult
.months
),
2003 int64_t(untilResult
.weeks
),
2004 int64_t(untilResult
.days
));
2009 MOZ_ASSERT(largestUnit
== TemporalUnit::Week
);
2012 MOZ_ASSERT(years
== 0);
2015 MOZ_ASSERT(months
== 0);
2018 const auto& weeksDaysDuration
= duration
;
2021 Duration untilResult
;
2022 if (!untilAddedDate(weeksDaysDuration
, &untilResult
)) {
2027 *result
= CreateDateDurationRecord(0, 0, int64_t(untilResult
.weeks
),
2028 int64_t(untilResult
.days
));
2033 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2034 * smallestUnit, plainRelativeTo, calendarRec )
2036 bool js::temporal::BalanceDateDurationRelative(
2037 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
2038 TemporalUnit smallestUnit
,
2039 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2040 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
2041 MOZ_ASSERT(plainRelativeTo
);
2042 MOZ_ASSERT(calendar
.receiver());
2044 return ::BalanceDateDurationRelative(cx
, duration
, largestUnit
, smallestUnit
,
2045 plainRelativeTo
, calendar
, result
);
2049 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2050 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2051 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2053 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2055 MOZ_ASSERT(IsValidDuration(one
));
2056 MOZ_ASSERT(IsValidDuration(two
));
2058 // Steps 1-2. (Not applicable)
2061 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2064 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2067 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2070 if (largestUnit
<= TemporalUnit::Week
) {
2071 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2072 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
2078 auto normalized1
= NormalizeTimeDuration(one
);
2081 auto normalized2
= NormalizeTimeDuration(two
);
2084 NormalizedTimeDuration normalized
;
2085 if (!AddNormalizedTimeDuration(cx
, normalized1
, normalized2
, &normalized
)) {
2090 int64_t days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
2091 int64_t days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
2092 auto totalDays
= mozilla::CheckedInt64(days1
) + days2
;
2093 MOZ_ASSERT(totalDays
.isValid(), "adding two duration days can't overflow");
2095 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized
, totalDays
.value(),
2101 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2104 *result
= balanced
.toDuration();
2109 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2110 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2111 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2113 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2114 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2115 Handle
<CalendarRecord
> calendar
, Duration
* result
) {
2116 MOZ_ASSERT(IsValidDuration(one
));
2117 MOZ_ASSERT(IsValidDuration(two
));
2119 // Steps 1-2. (Not applicable)
2122 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2125 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2128 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2130 // Step 6. (Not applicable)
2132 // Step 7.a. (Not applicable in our implementation.)
2135 auto dateDuration1
= one
.toDateDuration();
2138 auto dateDuration2
= two
.toDateDuration();
2141 Rooted
<Wrapped
<PlainDateObject
*>> intermediate(
2142 cx
, AddDate(cx
, calendar
, plainRelativeTo
, dateDuration1
));
2143 if (!intermediate
) {
2148 Rooted
<Wrapped
<PlainDateObject
*>> end(
2149 cx
, AddDate(cx
, calendar
, intermediate
, dateDuration2
));
2155 auto dateLargestUnit
= std::min(TemporalUnit::Day
, largestUnit
);
2158 DateDuration dateDifference
;
2159 if (!DifferenceDate(cx
, calendar
, plainRelativeTo
, end
, dateLargestUnit
,
2165 auto normalized1
= NormalizeTimeDuration(one
);
2168 auto normalized2
= NormalizeTimeDuration(two
);
2171 NormalizedTimeDuration normalized1WithDays
;
2172 if (!Add24HourDaysToNormalizedTimeDuration(
2173 cx
, normalized1
, dateDifference
.days
, &normalized1WithDays
)) {
2178 NormalizedTimeDuration normalized
;
2179 if (!AddNormalizedTimeDuration(cx
, normalized1WithDays
, normalized2
,
2185 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2189 double(dateDifference
.years
), double(dateDifference
.months
),
2190 double(dateDifference
.weeks
), double(balanced
.days
),
2191 double(balanced
.hours
), double(balanced
.minutes
),
2192 double(balanced
.seconds
), double(balanced
.milliseconds
),
2193 balanced
.microseconds
, balanced
.nanoseconds
,
2195 MOZ_ASSERT(IsValidDuration(*result
));
2200 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2201 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2202 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2204 static bool AddDuration(
2205 JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2206 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2207 Handle
<TimeZoneRecord
> timeZone
,
2208 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2210 // Steps 1-2. (Not applicable)
2213 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2216 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2219 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2221 // Steps 6-7. (Not applicable)
2223 // Steps 8-9. (Not applicable in our implementation.)
2226 bool startDateTimeNeeded
= largestUnit
<= TemporalUnit::Day
;
2229 if (!startDateTimeNeeded
) {
2230 // Steps 11-12. (Not applicable)
2233 auto normalized1
= NormalizeTimeDuration(one
);
2236 auto normalized2
= NormalizeTimeDuration(two
);
2238 // Step 15. (Inlined AddZonedDateTime, step 6.)
2239 Instant intermediateNs
;
2240 if (!AddInstant(cx
, zonedRelativeTo
.instant(), normalized1
,
2244 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2246 // Step 16. (Inlined AddZonedDateTime, step 6.)
2248 if (!AddInstant(cx
, intermediateNs
, normalized2
, &endNs
)) {
2251 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2254 auto normalized
= NormalizedTimeDurationFromEpochNanosecondsDifference(
2255 endNs
, zonedRelativeTo
.instant());
2258 auto balanced
= BalanceTimeDuration(normalized
, largestUnit
);
2261 *result
= balanced
.toDuration();
2266 PlainDateTime startDateTime
;
2267 if (!precalculatedPlainDateTime
) {
2268 if (!GetPlainDateTimeFor(cx
, timeZone
, zonedRelativeTo
.instant(),
2273 startDateTime
= *precalculatedPlainDateTime
;
2277 auto normalized1
= CreateNormalizedDurationRecord(one
);
2280 auto normalized2
= CreateNormalizedDurationRecord(two
);
2283 Instant intermediateNs
;
2284 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2285 normalized1
, startDateTime
, &intermediateNs
)) {
2288 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2292 if (!AddZonedDateTime(cx
, intermediateNs
, timeZone
, calendar
, normalized2
,
2296 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2298 // Step 17. (Not applicable)
2301 NormalizedDuration difference
;
2302 if (!DifferenceZonedDateTime(cx
, zonedRelativeTo
.instant(), endNs
, timeZone
,
2303 calendar
, largestUnit
, startDateTime
,
2309 auto balanced
= BalanceTimeDuration(difference
.time
, TemporalUnit::Hour
);
2313 double(difference
.date
.years
), double(difference
.date
.months
),
2314 double(difference
.date
.weeks
), double(difference
.date
.days
),
2315 double(balanced
.hours
), double(balanced
.minutes
),
2316 double(balanced
.seconds
), double(balanced
.milliseconds
),
2317 balanced
.microseconds
, balanced
.nanoseconds
,
2319 MOZ_ASSERT(IsValidDuration(*result
));
2324 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2325 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2326 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2328 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2329 Handle
<ZonedDateTime
> zonedRelativeTo
,
2330 Handle
<CalendarRecord
> calendar
,
2331 Handle
<TimeZoneRecord
> timeZone
, Duration
* result
) {
2332 return AddDuration(cx
, one
, two
, zonedRelativeTo
, calendar
, timeZone
,
2333 mozilla::Nothing(), result
);
2337 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2338 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2339 * precalculatedPlainDateTime )
2341 static bool AdjustRoundedDurationDays(
2342 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2343 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2344 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2345 Handle
<TimeZoneRecord
> timeZone
,
2346 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2347 NormalizedDuration
* result
) {
2348 MOZ_ASSERT(IsValidDuration(duration
));
2351 if ((TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
) ||
2352 (unit
== TemporalUnit::Nanosecond
&& increment
== Increment
{1})) {
2357 // The increment is limited for all smaller temporal units.
2358 MOZ_ASSERT(increment
< MaximumTemporalDurationRoundingIncrement(unit
));
2361 MOZ_ASSERT(precalculatedPlainDateTime
);
2364 int32_t direction
= NormalizedTimeDurationSign(duration
.time
);
2368 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2369 duration
.date
, *precalculatedPlainDateTime
,
2373 MOZ_ASSERT(IsValidEpochInstant(dayStart
));
2376 PlainDateTime dayStartDateTime
;
2377 if (!GetPlainDateTimeFor(cx
, timeZone
, dayStart
, &dayStartDateTime
)) {
2383 if (!AddDaysToZonedDateTime(cx
, dayStart
, dayStartDateTime
, timeZone
,
2384 zonedRelativeTo
.calendar(), direction
, &dayEnd
)) {
2387 MOZ_ASSERT(IsValidEpochInstant(dayEnd
));
2391 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd
, dayStart
);
2392 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs
.to
<InstantSpan
>()));
2395 NormalizedTimeDuration oneDayLess
;
2396 if (!SubtractNormalizedTimeDuration(cx
, duration
.time
, dayLengthNs
,
2402 int32_t oneDayLessSign
= NormalizedTimeDurationSign(oneDayLess
);
2403 if ((direction
> 0 && oneDayLessSign
< 0) ||
2404 (direction
< 0 && oneDayLessSign
> 0)) {
2410 Duration adjustedDateDuration
;
2411 if (!AddDuration(cx
, duration
.date
.toDuration(), {0, 0, 0, double(direction
)},
2412 zonedRelativeTo
, calendar
, timeZone
,
2413 precalculatedPlainDateTime
, &adjustedDateDuration
)) {
2418 auto roundedTime
= RoundDuration(oneDayLess
, increment
, unit
, roundingMode
);
2421 return CombineDateAndNormalizedTimeDuration(
2422 cx
, adjustedDateDuration
.toDateDuration(), roundedTime
, result
);
2426 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2427 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2428 * precalculatedPlainDateTime )
2430 bool js::temporal::AdjustRoundedDurationDays(
2431 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2432 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2433 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2434 Handle
<TimeZoneRecord
> timeZone
,
2435 const PlainDateTime
& precalculatedPlainDateTime
,
2436 NormalizedDuration
* result
) {
2437 return ::AdjustRoundedDurationDays(
2438 cx
, duration
, increment
, unit
, roundingMode
, zonedRelativeTo
, calendar
,
2439 timeZone
, mozilla::SomeRef(precalculatedPlainDateTime
), result
);
2442 static bool NumberToStringBuilder(JSContext
* cx
, double num
,
2443 JSStringBuilder
& sb
) {
2444 MOZ_ASSERT(IsInteger(num
));
2445 MOZ_ASSERT(num
>= 0);
2446 MOZ_ASSERT(num
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2450 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
2452 return sb
.append(numStr
, length
);
2455 static Duration
AbsoluteDuration(const Duration
& duration
) {
2457 std::abs(duration
.years
), std::abs(duration
.months
),
2458 std::abs(duration
.weeks
), std::abs(duration
.days
),
2459 std::abs(duration
.hours
), std::abs(duration
.minutes
),
2460 std::abs(duration
.seconds
), std::abs(duration
.milliseconds
),
2461 std::abs(duration
.microseconds
), std::abs(duration
.nanoseconds
),
2466 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2468 [[nodiscard
]] static bool FormatFractionalSeconds(JSStringBuilder
& result
,
2469 int32_t subSecondNanoseconds
,
2470 Precision precision
) {
2471 MOZ_ASSERT(0 <= subSecondNanoseconds
&& subSecondNanoseconds
< 1'000'000'000);
2472 MOZ_ASSERT(precision
!= Precision::Minute());
2475 if (precision
== Precision::Auto()) {
2477 if (subSecondNanoseconds
== 0) {
2481 // Step 3. (Reordered)
2482 if (!result
.append('.')) {
2487 int32_t k
= 100'000'000;
2489 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2492 subSecondNanoseconds
%= k
;
2494 } while (subSecondNanoseconds
);
2497 uint8_t p
= precision
.value();
2502 // Step 3. (Reordered)
2503 if (!result
.append('.')) {
2508 int32_t k
= 100'000'000;
2509 for (uint8_t i
= 0; i
< precision
.value(); i
++) {
2510 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2513 subSecondNanoseconds
%= k
;
2522 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2523 * normSeconds, precision )
2525 static JSString
* TemporalDurationToString(JSContext
* cx
,
2526 const Duration
& duration
,
2527 Precision precision
) {
2528 MOZ_ASSERT(IsValidDuration(duration
));
2529 MOZ_ASSERT(precision
!= Precision::Minute());
2531 // Fast path for zero durations.
2532 if (duration
== Duration
{} &&
2533 (precision
== Precision::Auto() || precision
.value() == 0)) {
2534 return NewStringCopyZ
<CanGC
>(cx
, "PT0S");
2537 // Convert to absolute values up front. This is okay to do, because when the
2538 // duration is valid, all components have the same sign.
2539 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
2540 milliseconds
, microseconds
, nanoseconds
] =
2541 AbsoluteDuration(duration
);
2543 // Years to seconds parts are all safe integers for valid durations.
2544 MOZ_ASSERT(years
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2545 MOZ_ASSERT(months
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2546 MOZ_ASSERT(weeks
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2547 MOZ_ASSERT(days
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2548 MOZ_ASSERT(hours
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2549 MOZ_ASSERT(minutes
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2550 MOZ_ASSERT(seconds
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2552 auto secondsDuration
= NormalizeTimeDuration(0.0, 0.0, seconds
, milliseconds
,
2553 microseconds
, nanoseconds
);
2556 int32_t sign
= DurationSign(duration
);
2559 JSStringBuilder
result(cx
);
2561 // Step 13. (Reordered)
2563 if (!result
.append('-')) {
2568 // Step 14. (Reordered)
2569 if (!result
.append('P')) {
2575 if (!NumberToStringBuilder(cx
, years
, result
)) {
2578 if (!result
.append('Y')) {
2585 if (!NumberToStringBuilder(cx
, months
, result
)) {
2588 if (!result
.append('M')) {
2595 if (!NumberToStringBuilder(cx
, weeks
, result
)) {
2598 if (!result
.append('W')) {
2605 if (!NumberToStringBuilder(cx
, days
, result
)) {
2608 if (!result
.append('D')) {
2613 // Step 7. (Moved above)
2615 // Steps 10-11. (Reordered)
2616 bool zeroMinutesAndHigher
= years
== 0 && months
== 0 && weeks
== 0 &&
2617 days
== 0 && hours
== 0 && minutes
== 0;
2619 // Steps 8-9, 12, and 15.
2620 bool hasSecondsPart
= (secondsDuration
!= NormalizedTimeDuration
{}) ||
2621 zeroMinutesAndHigher
|| precision
!= Precision::Auto();
2622 if (hours
!= 0 || minutes
!= 0 || hasSecondsPart
) {
2623 // Step 15. (Reordered)
2624 if (!result
.append('T')) {
2630 if (!NumberToStringBuilder(cx
, hours
, result
)) {
2633 if (!result
.append('H')) {
2640 if (!NumberToStringBuilder(cx
, minutes
, result
)) {
2643 if (!result
.append('M')) {
2649 if (hasSecondsPart
) {
2651 if (!NumberToStringBuilder(cx
, double(secondsDuration
.seconds
), result
)) {
2656 if (!FormatFractionalSeconds(result
, secondsDuration
.nanoseconds
,
2662 if (!result
.append('S')) {
2668 // Steps 13-15. (Moved above)
2671 return result
.finishString();
2675 * ToRelativeTemporalObject ( options )
2677 static bool ToRelativeTemporalObject(
2678 JSContext
* cx
, Handle
<JSObject
*> options
,
2679 MutableHandle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2680 MutableHandle
<ZonedDateTime
> zonedRelativeTo
,
2681 MutableHandle
<TimeZoneRecord
> timeZoneRecord
) {
2683 Rooted
<Value
> value(cx
);
2684 if (!GetProperty(cx
, options
, options
, cx
->names().relativeTo
, &value
)) {
2689 if (value
.isUndefined()) {
2690 plainRelativeTo
.set(nullptr);
2691 zonedRelativeTo
.set(ZonedDateTime
{});
2692 timeZoneRecord
.set(TimeZoneRecord
{});
2697 auto offsetBehaviour
= OffsetBehaviour::Option
;
2700 auto matchBehaviour
= MatchBehaviour::MatchExactly
;
2703 PlainDateTime dateTime
;
2704 Rooted
<CalendarValue
> calendar(cx
);
2705 Rooted
<TimeZoneValue
> timeZone(cx
);
2707 if (value
.isObject()) {
2708 Rooted
<JSObject
*> obj(cx
, &value
.toObject());
2711 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
2712 auto instant
= ToInstant(zonedDateTime
);
2713 Rooted
<TimeZoneValue
> timeZone(cx
, zonedDateTime
->timeZone());
2714 Rooted
<CalendarValue
> calendar(cx
, zonedDateTime
->calendar());
2716 if (!timeZone
.wrap(cx
)) {
2719 if (!calendar
.wrap(cx
)) {
2724 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2725 if (!CreateTimeZoneMethodsRecord(
2728 TimeZoneMethod::GetOffsetNanosecondsFor
,
2729 TimeZoneMethod::GetPossibleInstantsFor
,
2736 plainRelativeTo
.set(nullptr);
2737 zonedRelativeTo
.set(ZonedDateTime
{instant
, timeZone
, calendar
});
2738 timeZoneRecord
.set(timeZoneRec
);
2743 if (obj
->canUnwrapAs
<PlainDateObject
>()) {
2744 plainRelativeTo
.set(obj
);
2745 zonedRelativeTo
.set(ZonedDateTime
{});
2746 timeZoneRecord
.set(TimeZoneRecord
{});
2751 if (auto* dateTime
= obj
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
2752 auto plainDateTime
= ToPlainDate(dateTime
);
2754 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
2755 if (!calendar
.wrap(cx
)) {
2760 auto* plainDate
= CreateTemporalDate(cx
, plainDateTime
, calendar
);
2766 plainRelativeTo
.set(plainDate
);
2767 zonedRelativeTo
.set(ZonedDateTime
{});
2768 timeZoneRecord
.set(TimeZoneRecord
{});
2773 if (!GetTemporalCalendarWithISODefault(cx
, obj
, &calendar
)) {
2778 Rooted
<CalendarRecord
> calendarRec(cx
);
2779 if (!CreateCalendarMethodsRecord(cx
, calendar
,
2781 CalendarMethod::DateFromFields
,
2782 CalendarMethod::Fields
,
2789 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2790 if (!CalendarFields(cx
, calendarRec
,
2791 {CalendarField::Day
, CalendarField::Month
,
2792 CalendarField::MonthCode
, CalendarField::Year
},
2798 if (!AppendSorted(cx
, fieldNames
.get(),
2800 TemporalField::Hour
,
2801 TemporalField::Microsecond
,
2802 TemporalField::Millisecond
,
2803 TemporalField::Minute
,
2804 TemporalField::Nanosecond
,
2805 TemporalField::Offset
,
2806 TemporalField::Second
,
2807 TemporalField::TimeZone
,
2813 Rooted
<PlainObject
*> fields(cx
, PrepareTemporalFields(cx
, obj
, fieldNames
));
2819 Rooted
<PlainObject
*> dateOptions(cx
, NewPlainObjectWithProto(cx
, nullptr));
2825 Rooted
<Value
> overflow(cx
, StringValue(cx
->names().constrain
));
2826 if (!DefineDataProperty(cx
, dateOptions
, cx
->names().overflow
, overflow
)) {
2831 if (!InterpretTemporalDateTimeFields(cx
, calendarRec
, fields
, dateOptions
,
2837 Rooted
<Value
> offset(cx
);
2838 if (!GetProperty(cx
, fields
, fields
, cx
->names().offset
, &offset
)) {
2843 Rooted
<Value
> timeZoneValue(cx
);
2844 if (!GetProperty(cx
, fields
, fields
, cx
->names().timeZone
,
2850 if (!timeZoneValue
.isUndefined()) {
2851 if (!ToTemporalTimeZone(cx
, timeZoneValue
, &timeZone
)) {
2857 if (offset
.isUndefined()) {
2858 offsetBehaviour
= OffsetBehaviour::Wall
;
2863 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2864 MOZ_ASSERT(!offset
.isUndefined());
2865 MOZ_ASSERT(offset
.isString());
2868 Rooted
<JSString
*> offsetString(cx
, offset
.toString());
2869 if (!offsetString
) {
2874 if (!ParseDateTimeUTCOffset(cx
, offsetString
, &offsetNs
)) {
2884 if (!value
.isString()) {
2885 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
2886 nullptr, "not a string");
2889 Rooted
<JSString
*> string(cx
, value
.toString());
2894 int64_t timeZoneOffset
;
2895 Rooted
<ParsedTimeZone
> timeZoneAnnotation(cx
);
2896 Rooted
<JSString
*> calendarString(cx
);
2897 if (!ParseTemporalRelativeToString(cx
, string
, &dateTime
, &isUTC
,
2898 &hasOffset
, &timeZoneOffset
,
2899 &timeZoneAnnotation
, &calendarString
)) {
2903 // Step 6.c. (Not applicable in our implementation.)
2906 if (timeZoneAnnotation
) {
2908 if (!ToTemporalTimeZone(cx
, timeZoneAnnotation
, &timeZone
)) {
2912 // Steps 6.f.ii-iii.
2914 offsetBehaviour
= OffsetBehaviour::Exact
;
2915 } else if (!hasOffset
) {
2916 offsetBehaviour
= OffsetBehaviour::Wall
;
2920 matchBehaviour
= MatchBehaviour::MatchMinutes
;
2922 MOZ_ASSERT(!timeZone
);
2926 if (calendarString
) {
2927 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
2931 calendar
.set(CalendarValue(cx
->names().iso8601
));
2936 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2937 MOZ_ASSERT(hasOffset
);
2940 offsetNs
= timeZoneOffset
;
2951 auto* plainDate
= CreateTemporalDate(cx
, dateTime
.date
, calendar
);
2956 plainRelativeTo
.set(plainDate
);
2957 zonedRelativeTo
.set(ZonedDateTime
{});
2958 timeZoneRecord
.set(TimeZoneRecord
{});
2962 // Steps 8-9. (Moved above)
2965 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2966 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2968 TimeZoneMethod::GetOffsetNanosecondsFor
,
2969 TimeZoneMethod::GetPossibleInstantsFor
,
2976 Instant epochNanoseconds
;
2977 if (!InterpretISODateTimeOffset(
2978 cx
, dateTime
, offsetBehaviour
, offsetNs
, timeZoneRec
,
2979 TemporalDisambiguation::Compatible
, TemporalOffset::Reject
,
2980 matchBehaviour
, &epochNanoseconds
)) {
2983 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds
));
2986 plainRelativeTo
.set(nullptr);
2987 zonedRelativeTo
.set(ZonedDateTime
{epochNanoseconds
, timeZone
, calendar
});
2988 timeZoneRecord
.set(timeZoneRec
);
2993 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
2996 static bool CreateCalendarMethodsRecordFromRelativeTo(
2997 JSContext
* cx
, Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2998 Handle
<ZonedDateTime
> zonedRelativeTo
,
2999 mozilla::EnumSet
<CalendarMethod
> methods
,
3000 MutableHandle
<CalendarRecord
> result
) {
3002 if (zonedRelativeTo
) {
3003 return CreateCalendarMethodsRecord(cx
, zonedRelativeTo
.calendar(), methods
,
3008 if (plainRelativeTo
) {
3009 auto* unwrapped
= plainRelativeTo
.unwrap(cx
);
3014 Rooted
<CalendarValue
> calendar(cx
, unwrapped
->calendar());
3015 if (!calendar
.wrap(cx
)) {
3019 return CreateCalendarMethodsRecord(cx
, calendar
, methods
, result
);
3026 struct RoundedDuration final
{
3027 NormalizedDuration duration
;
3031 enum class ComputeRemainder
: bool { No
, Yes
};
3034 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3036 static NormalizedTimeDuration
RoundNormalizedTimeDurationToIncrement(
3037 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
,
3038 Increment increment
, TemporalRoundingMode roundingMode
) {
3039 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3040 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3041 MOZ_ASSERT(increment
<= MaximumTemporalDurationRoundingIncrement(unit
));
3043 int64_t divisor
= ToNanoseconds(unit
) * increment
.value();
3044 MOZ_ASSERT(divisor
> 0);
3045 MOZ_ASSERT(divisor
<= ToNanoseconds(TemporalUnit::Day
));
3047 auto totalNanoseconds
= duration
.toNanoseconds();
3049 RoundNumberToIncrement(totalNanoseconds
, Int128
{divisor
}, roundingMode
);
3050 return NormalizedTimeDuration::fromNanoseconds(rounded
);
3054 * DivideNormalizedTimeDuration ( d, divisor )
3056 static double TotalNormalizedTimeDuration(
3057 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
) {
3058 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3059 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3061 auto numerator
= duration
.toNanoseconds();
3062 auto denominator
= Int128
{ToNanoseconds(unit
)};
3063 return FractionToDouble(numerator
, denominator
);
3067 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3068 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3069 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3071 NormalizedTimeDuration
js::temporal::RoundDuration(
3072 const NormalizedTimeDuration
& duration
, Increment increment
,
3073 TemporalUnit unit
, TemporalRoundingMode roundingMode
) {
3074 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3075 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3077 // Steps 1-13. (Not applicable)
3080 auto rounded
= RoundNormalizedTimeDurationToIncrement(
3081 duration
, unit
, increment
, roundingMode
);
3082 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded
));
3088 struct FractionalDays final
{
3091 int64_t dayLength
= 0;
3093 FractionalDays() = default;
3095 explicit FractionalDays(int64_t durationDays
,
3096 const NormalizedTimeAndDays
& timeAndDays
)
3097 : days(durationDays
+ timeAndDays
.days
),
3098 time(timeAndDays
.time
),
3099 dayLength(timeAndDays
.dayLength
) {
3100 MOZ_ASSERT(durationDays
<= (int64_t(1) << 53) / (24 * 60 * 60));
3101 MOZ_ASSERT(timeAndDays
.days
<= (int64_t(1) << 53) / (24 * 60 * 60));
3103 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3104 // positive and less than 2**53.
3105 MOZ_ASSERT(dayLength
> 0);
3106 MOZ_ASSERT(dayLength
< int64_t(1) << 53);
3108 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3109 // less than |timeAndDays.dayLength|.
3110 MOZ_ASSERT(std::abs(time
) < dayLength
);
3113 FractionalDays
operator+=(int32_t epochDays
) {
3114 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3119 FractionalDays
operator-=(int32_t epochDays
) {
3120 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3125 int64_t truncate() const {
3126 int64_t truncatedDays
= days
;
3128 // Round toward positive infinity when the integer days are negative and
3129 // the fractional part is positive.
3130 if (truncatedDays
< 0) {
3133 } else if (time
< 0) {
3134 // Round toward negative infinity when the integer days are positive and
3135 // the fractional part is negative.
3136 if (truncatedDays
> 0) {
3140 return truncatedDays
;
3143 int32_t sign() const {
3145 return days
< 0 ? -1 : 1;
3147 return time
< 0 ? -1 : time
> 0 ? 1 : 0;
3151 struct Fraction final
{
3152 int64_t numerator
= 0;
3153 int32_t denominator
= 0;
3155 constexpr Fraction() = default;
3157 constexpr Fraction(int64_t numerator
, int32_t denominator
)
3158 : numerator(numerator
), denominator(denominator
) {
3159 MOZ_ASSERT(denominator
> 0);
3163 struct RoundedNumber final
{
3168 static RoundedNumber
RoundNumberToIncrement(
3169 const Fraction
& fraction
, const FractionalDays
& fractionalDays
,
3170 Increment increment
, TemporalRoundingMode roundingMode
,
3171 ComputeRemainder computeRemainder
) {
3173 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3174 static constexpr int64_t maxDurationDays
=
3175 (int64_t(1) << 53) / (24 * 60 * 60);
3177 // Numbers of days between nsMinInstant and nsMaxInstant.
3178 static constexpr int32_t epochDays
= 200'000'000;
3180 // Maximum number of days in |fractionalDays|.
3181 static constexpr int64_t maxFractionalDays
=
3182 2 * maxDurationDays
+ 2 * epochDays
;
3185 MOZ_ASSERT(std::abs(fraction
.numerator
) < (int64_t(1) << 32) * 2);
3186 MOZ_ASSERT(fraction
.denominator
> 0);
3187 MOZ_ASSERT(fraction
.denominator
<= epochDays
);
3188 MOZ_ASSERT(std::abs(fractionalDays
.days
) <= maxFractionalDays
);
3189 MOZ_ASSERT(fractionalDays
.dayLength
> 0);
3190 MOZ_ASSERT(fractionalDays
.dayLength
< (int64_t(1) << 53));
3191 MOZ_ASSERT(std::abs(fractionalDays
.time
) < fractionalDays
.dayLength
);
3192 MOZ_ASSERT(increment
<= Increment::max());
3196 // Change the representation of |fractionalWeeks| from a real number to a
3197 // rational number, because we don't support arbitrary precision real
3200 // |fractionalWeeks| is defined as:
3203 // = weeks + days' / abs(oneWeekDays)
3205 // where days' = days + nanoseconds / dayLength.
3207 // The fractional part |nanoseconds / dayLength| is from step 7.
3209 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3212 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3213 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3214 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3216 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3217 // to omit the multiplication by |dayLength| when the rounding conditions are
3218 // appropriately modified to account for the |nanoseconds / dayLength| part.
3219 // This allows to implement rounding using only int64 values.
3221 // This optimization is currently only implemented when |nanoseconds| is zero.
3223 // Example how to expand this optimization for non-zero |nanoseconds|:
3225 // |Round(fraction / increment) * increment| with:
3226 // fraction = numerator / denominator
3227 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3228 // denominator = dayLength * abs(oneWeekDays)
3230 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3232 // |Round(fraction / increment) * increment| with:
3233 // fraction = numerator / denominator
3234 // numerator = weeks * abs(oneWeekDays) + days
3235 // denominator = abs(oneWeekDays)
3238 // fraction / increment
3239 // = (numerator / denominator) / increment
3240 // = numerator / (denominator * increment)
3242 // And |numerator| and |denominator * increment| both fit into int64.
3244 // The "ceiling" operation has to be modified from:
3246 // CeilDiv(dividend, divisor)
3247 // quot, rem = dividend / divisor
3248 // return quot + (rem > 0)
3252 // CeilDiv(dividend, divisor, fractional)
3253 // quot, rem = dividend / divisor
3254 // return quot + ((rem > 0) || (fractional > 0))
3256 // To properly account for the fractional |nanoseconds| part. Alternatively
3257 // |dividend| can be modified before calling `CeilDiv`.
3261 if (fractionalDays
.time
== 0) {
3262 auto [numerator
, denominator
] = fraction
;
3263 int64_t totalDays
= fractionalDays
.days
+ denominator
* numerator
;
3265 if (computeRemainder
== ComputeRemainder::Yes
) {
3266 constexpr auto rounded
= Int128
{0};
3267 double total
= FractionToDouble(totalDays
, denominator
);
3268 return {rounded
, total
};
3272 RoundNumberToIncrement(totalDays
, denominator
, increment
, roundingMode
);
3273 constexpr double total
= 0;
3274 return {rounded
, total
};
3278 auto dayLength
= mozilla::CheckedInt64(fractionalDays
.dayLength
);
3280 auto denominator
= dayLength
* fraction
.denominator
;
3281 if (!denominator
.isValid()) {
3285 auto amountNanos
= denominator
* fraction
.numerator
;
3286 if (!amountNanos
.isValid()) {
3290 auto totalNanoseconds
= dayLength
* fractionalDays
.days
;
3291 totalNanoseconds
+= fractionalDays
.time
;
3292 totalNanoseconds
+= amountNanos
;
3293 if (!totalNanoseconds
.isValid()) {
3297 if (computeRemainder
== ComputeRemainder::Yes
) {
3298 constexpr auto rounded
= Int128
{0};
3300 FractionToDouble(totalNanoseconds
.value(), denominator
.value());
3301 return {rounded
, total
};
3304 auto rounded
= RoundNumberToIncrement(
3305 totalNanoseconds
.value(), denominator
.value(), increment
, roundingMode
);
3306 constexpr double total
= 0;
3307 return {rounded
, total
};
3310 // Use int128 when values are too large for int64. Additionally assert all
3311 // values fit into int128.
3313 // `dayLength` < 2**53
3314 auto dayLength
= Int128
{fractionalDays
.dayLength
};
3315 MOZ_ASSERT(dayLength
< Int128
{1} << 53);
3317 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3318 auto denominator
= dayLength
* Int128
{fraction
.denominator
};
3319 MOZ_ASSERT(denominator
< Int128
{1} << (53 + 28));
3321 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3323 // `abs(maxFractionalDays)`
3324 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3325 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3326 // ≤ 2 * 2**37 + 2**29
3328 auto totalDays
= Int128
{fractionalDays
.days
};
3329 MOZ_ASSERT(totalDays
.abs() <= Uint128
{1} << 39);
3331 // `abs(fraction.numerator)` ≤ (2**33)
3332 auto totalAmount
= Int128
{fraction
.numerator
};
3333 MOZ_ASSERT(totalAmount
.abs() <= Uint128
{1} << 33);
3335 // `denominator` < 2**(53 + 28)
3336 // `abs(totalAmount)` <= 2**33
3338 // `denominator * totalAmount`
3339 // ≤ 2**(53 + 28) * 2**33
3340 // = 2**(53 + 28 + 33)
3342 auto amountNanos
= denominator
* totalAmount
;
3343 MOZ_ASSERT(amountNanos
.abs() <= Uint128
{1} << 114);
3345 // `dayLength` < 2**53
3346 // `totalDays` ≤ 2**39
3347 // `fractionalDays.time` < `dayLength` < 2**53
3348 // `amountNanos` ≤ 2**114
3350 // `dayLength * totalDays`
3351 // ≤ 2**(53 + 39) = 2**92
3353 // `dayLength * totalDays + fractionalDays.time`
3356 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3358 auto totalNanoseconds
= dayLength
* totalDays
;
3359 totalNanoseconds
+= Int128
{fractionalDays
.time
};
3360 totalNanoseconds
+= amountNanos
;
3361 MOZ_ASSERT(totalNanoseconds
.abs() <= Uint128
{1} << 115);
3363 if (computeRemainder
== ComputeRemainder::Yes
) {
3364 constexpr auto rounded
= Int128
{0};
3365 double total
= FractionToDouble(totalNanoseconds
, denominator
);
3366 return {rounded
, total
};
3369 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, denominator
,
3370 increment
, roundingMode
);
3371 constexpr double total
= 0;
3372 return {rounded
, total
};
3375 static bool RoundDurationYear(JSContext
* cx
, const NormalizedDuration
& duration
,
3376 FractionalDays fractionalDays
,
3377 Increment increment
,
3378 TemporalRoundingMode roundingMode
,
3379 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3380 Handle
<CalendarRecord
> calendar
,
3381 ComputeRemainder computeRemainder
,
3382 RoundedDuration
* result
) {
3384 // Numbers of days between nsMinInstant and nsMaxInstant.
3385 static constexpr int32_t epochDays
= 200'000'000;
3388 auto [years
, months
, weeks
, days
] = duration
.date
;
3391 auto yearsDuration
= DateDuration
{years
};
3394 auto yearsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsDuration
);
3398 auto yearsLaterDate
= ToPlainDate(&yearsLater
.unwrap());
3400 // Step 10.f. (Reordered)
3401 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsLater
);
3404 auto yearsMonthsWeeks
= DateDuration
{years
, months
, weeks
};
3407 PlainDate yearsMonthsWeeksLater
;
3408 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3409 &yearsMonthsWeeksLater
)) {
3414 int32_t monthsWeeksInDays
= DaysUntil(yearsLaterDate
, yearsMonthsWeeksLater
);
3415 MOZ_ASSERT(std::abs(monthsWeeksInDays
) <= epochDays
);
3417 // Step 10.f. (Moved up)
3420 fractionalDays
+= monthsWeeksInDays
;
3422 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3423 // https://github.com/tc39/proposal-temporal/issues/2540
3426 PlainDate isoResult
;
3427 if (!AddISODate(cx
, yearsLaterDate
, {0, 0, 0, fractionalDays
.truncate()},
3428 TemporalOverflow::Constrain
, &isoResult
)) {
3433 Rooted
<PlainDateObject
*> wholeDaysLater(
3434 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3435 if (!wholeDaysLater
) {
3440 DateDuration timePassed
;
3441 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3442 TemporalUnit::Year
, &timePassed
)) {
3447 int64_t yearsPassed
= timePassed
.years
;
3450 years
+= yearsPassed
;
3453 auto yearsPassedDuration
= DateDuration
{yearsPassed
};
3457 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, yearsPassedDuration
,
3458 &newRelativeTo
, &daysPassed
)) {
3461 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3464 fractionalDays
-= daysPassed
;
3467 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3470 auto oneYear
= DateDuration
{sign
};
3473 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3474 int32_t oneYearDays
;
3475 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneYear
,
3476 &moveResultIgnored
, &oneYearDays
)) {
3481 if (oneYearDays
== 0) {
3482 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3483 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3488 auto fractionalYears
= Fraction
{years
, std::abs(oneYearDays
)};
3491 auto [numYears
, total
] =
3492 RoundNumberToIncrement(fractionalYears
, fractionalDays
, increment
,
3493 roundingMode
, computeRemainder
);
3496 int64_t numMonths
= 0;
3497 int64_t numWeeks
= 0;
3500 constexpr auto time
= NormalizedTimeDuration
{};
3503 if (numYears
.abs() >= (Uint128
{1} << 32)) {
3504 return ThrowInvalidDurationPart(cx
, double(numYears
), "years",
3505 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3508 auto resultDuration
= DateDuration
{int64_t(numYears
), numMonths
, numWeeks
};
3509 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3513 *result
= {{resultDuration
, time
}, total
};
3517 static bool RoundDurationMonth(JSContext
* cx
,
3518 const NormalizedDuration
& duration
,
3519 FractionalDays fractionalDays
,
3520 Increment increment
,
3521 TemporalRoundingMode roundingMode
,
3522 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3523 Handle
<CalendarRecord
> calendar
,
3524 ComputeRemainder computeRemainder
,
3525 RoundedDuration
* result
) {
3527 // Numbers of days between nsMinInstant and nsMaxInstant.
3528 static constexpr int32_t epochDays
= 200'000'000;
3531 auto [years
, months
, weeks
, days
] = duration
.date
;
3534 auto yearsMonths
= DateDuration
{years
, months
};
3537 auto yearsMonthsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsMonths
);
3538 if (!yearsMonthsLater
) {
3541 auto yearsMonthsLaterDate
= ToPlainDate(&yearsMonthsLater
.unwrap());
3543 // Step 11.f. (Reordered)
3544 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsMonthsLater
);
3547 auto yearsMonthsWeeks
= DateDuration
{years
, months
, weeks
};
3550 PlainDate yearsMonthsWeeksLater
;
3551 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3552 &yearsMonthsWeeksLater
)) {
3557 int32_t weeksInDays
= DaysUntil(yearsMonthsLaterDate
, yearsMonthsWeeksLater
);
3558 MOZ_ASSERT(std::abs(weeksInDays
) <= epochDays
);
3560 // Step 11.f. (Moved up)
3563 fractionalDays
+= weeksInDays
;
3565 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3566 // https://github.com/tc39/proposal-temporal/issues/2540
3569 PlainDate isoResult
;
3570 if (!AddISODate(cx
, yearsMonthsLaterDate
,
3571 {0, 0, 0, fractionalDays
.truncate()},
3572 TemporalOverflow::Constrain
, &isoResult
)) {
3577 Rooted
<PlainDateObject
*> wholeDaysLater(
3578 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3579 if (!wholeDaysLater
) {
3584 DateDuration timePassed
;
3585 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3586 TemporalUnit::Month
, &timePassed
)) {
3591 int64_t monthsPassed
= timePassed
.months
;
3594 months
+= monthsPassed
;
3597 auto monthsPassedDuration
= DateDuration
{0, monthsPassed
};
3601 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, monthsPassedDuration
,
3602 &newRelativeTo
, &daysPassed
)) {
3605 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3608 fractionalDays
-= daysPassed
;
3611 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3614 auto oneMonth
= DateDuration
{0, sign
};
3617 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3618 int32_t oneMonthDays
;
3619 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneMonth
,
3620 &moveResultIgnored
, &oneMonthDays
)) {
3625 if (oneMonthDays
== 0) {
3626 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3627 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3632 auto fractionalMonths
= Fraction
{months
, std::abs(oneMonthDays
)};
3635 auto [numMonths
, total
] =
3636 RoundNumberToIncrement(fractionalMonths
, fractionalDays
, increment
,
3637 roundingMode
, computeRemainder
);
3640 int64_t numWeeks
= 0;
3643 constexpr auto time
= NormalizedTimeDuration
{};
3646 if (numMonths
.abs() >= (Uint128
{1} << 32)) {
3647 return ThrowInvalidDurationPart(cx
, double(numMonths
), "months",
3648 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3651 auto resultDuration
= DateDuration
{years
, int64_t(numMonths
), numWeeks
};
3652 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3656 *result
= {{resultDuration
, time
}, total
};
3660 static bool RoundDurationWeek(JSContext
* cx
, const NormalizedDuration
& duration
,
3661 FractionalDays fractionalDays
,
3662 Increment increment
,
3663 TemporalRoundingMode roundingMode
,
3664 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3665 Handle
<CalendarRecord
> calendar
,
3666 ComputeRemainder computeRemainder
,
3667 RoundedDuration
* result
) {
3669 // Numbers of days between nsMinInstant and nsMaxInstant.
3670 static constexpr int32_t epochDays
= 200'000'000;
3673 auto [years
, months
, weeks
, days
] = duration
.date
;
3675 auto* unwrappedRelativeTo
= dateRelativeTo
.unwrap(cx
);
3676 if (!unwrappedRelativeTo
) {
3679 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
3682 PlainDate isoResult
;
3683 if (!AddISODate(cx
, relativeToDate
, {0, 0, 0, fractionalDays
.truncate()},
3684 TemporalOverflow::Constrain
, &isoResult
)) {
3689 Rooted
<PlainDateObject
*> wholeDaysLater(
3690 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3691 if (!wholeDaysLater
) {
3696 DateDuration timePassed
;
3697 if (!DifferenceDate(cx
, calendar
, dateRelativeTo
, wholeDaysLater
,
3698 TemporalUnit::Week
, &timePassed
)) {
3703 int64_t weeksPassed
= timePassed
.weeks
;
3706 weeks
+= weeksPassed
;
3709 auto weeksPassedDuration
= DateDuration
{0, 0, weeksPassed
};
3712 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
);
3714 if (!MoveRelativeDate(cx
, calendar
, dateRelativeTo
, weeksPassedDuration
,
3715 &newRelativeTo
, &daysPassed
)) {
3718 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3721 fractionalDays
-= daysPassed
;
3724 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3727 auto oneWeek
= DateDuration
{0, 0, sign
};
3730 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3731 int32_t oneWeekDays
;
3732 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneWeek
,
3733 &moveResultIgnored
, &oneWeekDays
)) {
3738 if (oneWeekDays
== 0) {
3739 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3740 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3745 auto fractionalWeeks
= Fraction
{weeks
, std::abs(oneWeekDays
)};
3748 auto [numWeeks
, total
] =
3749 RoundNumberToIncrement(fractionalWeeks
, fractionalDays
, increment
,
3750 roundingMode
, computeRemainder
);
3753 constexpr auto time
= NormalizedTimeDuration
{};
3756 if (numWeeks
.abs() >= (Uint128
{1} << 32)) {
3757 return ThrowInvalidDurationPart(cx
, double(numWeeks
), "weeks",
3758 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3761 auto resultDuration
= DateDuration
{years
, months
, int64_t(numWeeks
)};
3762 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3766 *result
= {{resultDuration
, time
}, total
};
3770 static bool RoundDurationDay(JSContext
* cx
, const NormalizedDuration
& duration
,
3771 const FractionalDays
& fractionalDays
,
3772 Increment increment
,
3773 TemporalRoundingMode roundingMode
,
3774 ComputeRemainder computeRemainder
,
3775 RoundedDuration
* result
) {
3776 auto [years
, months
, weeks
, days
] = duration
.date
;
3778 // Pass zero fraction.
3779 constexpr auto zero
= Fraction
{0, 1};
3782 auto [numDays
, total
] = RoundNumberToIncrement(
3783 zero
, fractionalDays
, increment
, roundingMode
, computeRemainder
);
3785 MOZ_ASSERT(Int128
{INT64_MIN
} <= numDays
&& numDays
<= Int128
{INT64_MAX
},
3786 "rounded days fits in int64");
3789 constexpr auto time
= NormalizedTimeDuration
{};
3792 auto resultDuration
= DateDuration
{years
, months
, weeks
, int64_t(numDays
)};
3793 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3797 *result
= {{resultDuration
, time
}, total
};
3802 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3803 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3804 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3806 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
3807 Increment increment
, TemporalUnit unit
,
3808 TemporalRoundingMode roundingMode
,
3809 ComputeRemainder computeRemainder
,
3810 RoundedDuration
* result
) {
3811 // The remainder is only needed when called from |Duration_total|. And `total`
3812 // always passes |increment=1| and |roundingMode=trunc|.
3813 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3814 increment
== Increment
{1});
3815 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3816 roundingMode
== TemporalRoundingMode::Trunc
);
3818 // Steps 1-5. (Not applicable.)
3821 if (unit
<= TemporalUnit::Week
) {
3822 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3823 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3828 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3829 // because in that case this operation is a no-op. This case happens for
3830 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3833 // But maybe this can be even more efficiently handled in the callers. For
3834 // example when Temporal.PlainTime.prototype.{since,until} is called without
3835 // an options object, we can not only skip the RoundDuration call, but also
3836 // the following BalanceTimeDuration call.
3838 // Step 7. (Moved below.)
3840 // Steps 8-9. (Not applicable.)
3842 // Steps 10-12. (Not applicable.)
3845 if (unit
== TemporalUnit::Day
) {
3847 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3848 auto fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3850 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
3851 roundingMode
, computeRemainder
, result
);
3854 MOZ_ASSERT(TemporalUnit::Hour
<= unit
&& unit
<= TemporalUnit::Nanosecond
);
3857 auto time
= duration
.time
;
3859 if (computeRemainder
== ComputeRemainder::No
) {
3860 time
= RoundNormalizedTimeDurationToIncrement(time
, unit
, increment
,
3863 MOZ_ASSERT(increment
== Increment
{1});
3864 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
3866 total
= TotalNormalizedTimeDuration(duration
.time
, unit
);
3868 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
3871 MOZ_ASSERT(IsValidDuration(duration
.date
));
3872 *result
= {{duration
.date
, time
}, total
};
3877 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3878 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3879 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3881 static bool RoundDuration(
3882 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
3883 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
3884 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3885 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
3886 Handle
<TimeZoneRecord
> timeZone
,
3887 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
3888 ComputeRemainder computeRemainder
, RoundedDuration
* result
) {
3889 // Note: |duration.days| can have a different sign than the other date
3890 // components. The date and time components can have different signs, too.
3891 MOZ_ASSERT(IsValidDuration(Duration
{double(duration
.date
.years
),
3892 double(duration
.date
.months
),
3893 double(duration
.date
.weeks
)}));
3894 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
3896 MOZ_ASSERT(plainRelativeTo
|| zonedRelativeTo
,
3897 "Use RoundDuration without relativeTo when plainRelativeTo and "
3898 "zonedRelativeTo are both undefined");
3900 // The remainder is only needed when called from |Duration_total|. And `total`
3901 // always passes |increment=1| and |roundingMode=trunc|.
3902 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3903 increment
== Increment
{1});
3904 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3905 roundingMode
== TemporalRoundingMode::Trunc
);
3907 // Steps 1-5. (Not applicable in our implementation.)
3909 // Step 6.a. (Not applicable in our implementation.)
3910 MOZ_ASSERT_IF(unit
<= TemporalUnit::Week
, plainRelativeTo
);
3914 unit
<= TemporalUnit::Week
,
3915 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
3919 unit
<= TemporalUnit::Week
,
3920 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
3923 case TemporalUnit::Year
:
3924 case TemporalUnit::Month
:
3925 case TemporalUnit::Week
:
3927 case TemporalUnit::Day
:
3928 // We can't take the faster code path when |zonedRelativeTo| is present.
3929 if (zonedRelativeTo
) {
3933 case TemporalUnit::Hour
:
3934 case TemporalUnit::Minute
:
3935 case TemporalUnit::Second
:
3936 case TemporalUnit::Millisecond
:
3937 case TemporalUnit::Microsecond
:
3938 case TemporalUnit::Nanosecond
:
3939 // Steps 7-9 and 13-21.
3940 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
3941 computeRemainder
, result
);
3942 case TemporalUnit::Auto
:
3943 MOZ_CRASH("Unexpected temporal unit");
3947 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
);
3950 FractionalDays fractionalDays
;
3951 if (zonedRelativeTo
) {
3953 Rooted
<ZonedDateTime
> intermediate(cx
);
3954 if (!MoveRelativeZonedDateTime(cx
, zonedRelativeTo
, calendar
, timeZone
,
3955 duration
.date
, precalculatedPlainDateTime
,
3961 NormalizedTimeAndDays timeAndDays
;
3962 if (!NormalizedTimeDurationToDays(cx
, duration
.time
, intermediate
, timeZone
,
3968 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3971 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3972 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3975 // Step 7.c. (Moved below)
3977 // Step 8. (Not applicable)
3980 // FIXME: spec issue - `total` doesn't need be initialised.
3981 // https://github.com/tc39/proposal-temporal/issues/2784
3986 case TemporalUnit::Year
:
3987 return RoundDurationYear(cx
, duration
, fractionalDays
, increment
,
3988 roundingMode
, plainRelativeTo
, calendar
,
3989 computeRemainder
, result
);
3992 case TemporalUnit::Month
:
3993 return RoundDurationMonth(cx
, duration
, fractionalDays
, increment
,
3994 roundingMode
, plainRelativeTo
, calendar
,
3995 computeRemainder
, result
);
3998 case TemporalUnit::Week
:
3999 return RoundDurationWeek(cx
, duration
, fractionalDays
, increment
,
4000 roundingMode
, plainRelativeTo
, calendar
,
4001 computeRemainder
, result
);
4004 case TemporalUnit::Day
:
4005 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
4006 roundingMode
, computeRemainder
, result
);
4008 // Steps 14-19. (Handled elsewhere)
4009 case TemporalUnit::Auto
:
4010 case TemporalUnit::Hour
:
4011 case TemporalUnit::Minute
:
4012 case TemporalUnit::Second
:
4013 case TemporalUnit::Millisecond
:
4014 case TemporalUnit::Microsecond
:
4015 case TemporalUnit::Nanosecond
:
4019 MOZ_CRASH("Unexpected temporal unit");
4023 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4024 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4025 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4027 static bool RoundDuration(
4028 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4029 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4030 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4031 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4032 Handle
<TimeZoneRecord
> timeZone
,
4033 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4035 // Only called from |Duration_total|, which always passes |increment=1| and
4036 // |roundingMode=trunc|.
4037 MOZ_ASSERT(increment
== Increment
{1});
4038 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4040 RoundedDuration rounded
;
4041 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4042 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4043 precalculatedPlainDateTime
, ComputeRemainder::Yes
,
4048 *result
= rounded
.total
;
4053 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4054 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4055 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4057 static bool RoundDuration(
4058 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4059 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4060 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4061 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4062 Handle
<TimeZoneRecord
> timeZone
,
4063 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4064 NormalizedDuration
* result
) {
4065 RoundedDuration rounded
;
4066 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4067 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4068 precalculatedPlainDateTime
, ComputeRemainder::No
,
4073 *result
= rounded
.duration
;
4078 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4079 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4080 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4082 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4083 Increment increment
, TemporalUnit unit
,
4084 TemporalRoundingMode roundingMode
, double* result
) {
4085 MOZ_ASSERT(IsValidDuration(duration
));
4087 // Only called from |Duration_total|, which always passes |increment=1| and
4088 // |roundingMode=trunc|.
4089 MOZ_ASSERT(increment
== Increment
{1});
4090 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4092 RoundedDuration rounded
;
4093 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4094 ComputeRemainder::Yes
, &rounded
)) {
4098 *result
= rounded
.total
;
4103 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4104 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4105 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4107 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4108 Increment increment
, TemporalUnit unit
,
4109 TemporalRoundingMode roundingMode
,
4110 NormalizedDuration
* result
) {
4111 MOZ_ASSERT(IsValidDuration(duration
));
4113 RoundedDuration rounded
;
4114 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4115 ComputeRemainder::No
, &rounded
)) {
4119 *result
= rounded
.duration
;
4124 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4125 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4126 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4128 bool js::temporal::RoundDuration(
4129 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4130 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4131 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4132 Handle
<CalendarRecord
> calendar
, NormalizedDuration
* result
) {
4133 MOZ_ASSERT(IsValidDuration(duration
));
4135 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
, ZonedDateTime
{});
4136 Rooted
<TimeZoneRecord
> timeZone(cx
, TimeZoneRecord
{});
4137 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4138 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4139 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4140 precalculatedPlainDateTime
, result
);
4144 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4145 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4146 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4148 bool js::temporal::RoundDuration(
4149 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4150 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4151 Handle
<PlainDateObject
*> plainRelativeTo
, Handle
<CalendarRecord
> calendar
,
4152 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<TimeZoneRecord
> timeZone
,
4153 const PlainDateTime
& precalculatedPlainDateTime
,
4154 NormalizedDuration
* result
) {
4155 MOZ_ASSERT(IsValidDuration(duration
));
4157 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4158 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4159 mozilla::SomeRef(precalculatedPlainDateTime
), result
);
4162 enum class DurationOperation
{ Add
, Subtract
};
4165 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4168 static bool AddDurationToOrSubtractDurationFromDuration(
4169 JSContext
* cx
, DurationOperation operation
, const CallArgs
& args
) {
4170 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4171 auto duration
= ToDuration(durationObj
);
4173 // Step 1. (Not applicable in our implementation.)
4177 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
4181 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4182 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4183 Rooted
<TimeZoneRecord
> timeZone(cx
);
4184 if (args
.hasDefined(1)) {
4185 const char* name
= operation
== DurationOperation::Add
? "add" : "subtract";
4188 Rooted
<JSObject
*> options(cx
,
4189 RequireObjectArg(cx
, "options", name
, args
[1]));
4195 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4196 &zonedRelativeTo
, &timeZone
)) {
4199 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4200 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4204 Rooted
<CalendarRecord
> calendar(cx
);
4205 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4208 CalendarMethod::DateAdd
,
4209 CalendarMethod::DateUntil
,
4216 if (operation
== DurationOperation::Subtract
) {
4217 other
= other
.negate();
4221 if (plainRelativeTo
) {
4222 if (!AddDuration(cx
, duration
, other
, plainRelativeTo
, calendar
, &result
)) {
4225 } else if (zonedRelativeTo
) {
4226 if (!AddDuration(cx
, duration
, other
, zonedRelativeTo
, calendar
, timeZone
,
4231 if (!AddDuration(cx
, duration
, other
, &result
)) {
4237 auto* obj
= CreateTemporalDuration(cx
, result
);
4242 args
.rval().setObject(*obj
);
4247 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4248 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4251 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4252 CallArgs args
= CallArgsFromVp(argc
, vp
);
4255 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
4261 if (args
.hasDefined(0) &&
4262 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
4268 if (args
.hasDefined(1) &&
4269 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
4275 if (args
.hasDefined(2) &&
4276 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
4282 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
4288 if (args
.hasDefined(4) &&
4289 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
4295 if (args
.hasDefined(5) &&
4296 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
4302 if (args
.hasDefined(6) &&
4303 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
4308 double milliseconds
= 0;
4309 if (args
.hasDefined(7) &&
4310 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
4315 double microseconds
= 0;
4316 if (args
.hasDefined(8) &&
4317 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
4322 double nanoseconds
= 0;
4323 if (args
.hasDefined(9) &&
4324 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
4329 auto* duration
= CreateTemporalDuration(
4331 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
4332 microseconds
, nanoseconds
});
4337 args
.rval().setObject(*duration
);
4342 * Temporal.Duration.from ( item )
4344 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4345 CallArgs args
= CallArgsFromVp(argc
, vp
);
4347 Handle
<Value
> item
= args
.get(0);
4350 if (item
.isObject()) {
4351 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
4352 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
4357 args
.rval().setObject(*result
);
4363 auto result
= ToTemporalDuration(cx
, item
);
4368 args
.rval().setObject(*result
);
4373 * Temporal.Duration.compare ( one, two [ , options ] )
4375 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4376 CallArgs args
= CallArgsFromVp(argc
, vp
);
4380 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
4386 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
4391 Rooted
<JSObject
*> options(cx
);
4392 if (args
.hasDefined(2)) {
4393 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
4401 args
.rval().setInt32(0);
4406 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4407 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4408 Rooted
<TimeZoneRecord
> timeZone(cx
);
4410 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4411 &zonedRelativeTo
, &timeZone
)) {
4414 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4415 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4419 auto hasCalendarUnit
= [](const auto& d
) {
4420 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
4422 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
4425 Rooted
<CalendarRecord
> calendar(cx
);
4426 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4429 CalendarMethod::DateAdd
,
4436 if (zonedRelativeTo
&&
4437 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
4439 const auto& instant
= zonedRelativeTo
.instant();
4442 PlainDateTime dateTime
;
4443 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
4448 auto normalized1
= CreateNormalizedDurationRecord(one
);
4451 auto normalized2
= CreateNormalizedDurationRecord(two
);
4455 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
4456 dateTime
, &after1
)) {
4462 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
4463 dateTime
, &after2
)) {
4468 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
4473 int64_t days1
, days2
;
4474 if (calendarUnitsPresent
) {
4475 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4478 DateDuration unbalanceResult1
;
4479 if (plainRelativeTo
) {
4480 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
4481 TemporalUnit::Day
, plainRelativeTo
,
4482 calendar
, &unbalanceResult1
)) {
4486 if (!UnbalanceDateDurationRelative(
4487 cx
, one
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult1
)) {
4490 MOZ_ASSERT(one
.toDateDuration() == unbalanceResult1
);
4494 DateDuration unbalanceResult2
;
4495 if (plainRelativeTo
) {
4496 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
4497 TemporalUnit::Day
, plainRelativeTo
,
4498 calendar
, &unbalanceResult2
)) {
4502 if (!UnbalanceDateDurationRelative(
4503 cx
, two
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult2
)) {
4506 MOZ_ASSERT(two
.toDateDuration() == unbalanceResult2
);
4510 days1
= unbalanceResult1
.days
;
4513 days2
= unbalanceResult2
.days
;
4516 days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
4519 days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
4523 auto normalized1
= NormalizeTimeDuration(one
);
4526 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
4532 auto normalized2
= NormalizeTimeDuration(two
);
4535 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
4541 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
4546 * get Temporal.Duration.prototype.years
4548 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
4550 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4551 args
.rval().setNumber(duration
->years());
4556 * get Temporal.Duration.prototype.years
4558 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4560 CallArgs args
= CallArgsFromVp(argc
, vp
);
4561 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
4565 * get Temporal.Duration.prototype.months
4567 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
4569 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4570 args
.rval().setNumber(duration
->months());
4575 * get Temporal.Duration.prototype.months
4577 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4579 CallArgs args
= CallArgsFromVp(argc
, vp
);
4580 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
4584 * get Temporal.Duration.prototype.weeks
4586 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
4588 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4589 args
.rval().setNumber(duration
->weeks());
4594 * get Temporal.Duration.prototype.weeks
4596 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4598 CallArgs args
= CallArgsFromVp(argc
, vp
);
4599 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
4603 * get Temporal.Duration.prototype.days
4605 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
4607 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4608 args
.rval().setNumber(duration
->days());
4613 * get Temporal.Duration.prototype.days
4615 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4617 CallArgs args
= CallArgsFromVp(argc
, vp
);
4618 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
4622 * get Temporal.Duration.prototype.hours
4624 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
4626 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4627 args
.rval().setNumber(duration
->hours());
4632 * get Temporal.Duration.prototype.hours
4634 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4636 CallArgs args
= CallArgsFromVp(argc
, vp
);
4637 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
4641 * get Temporal.Duration.prototype.minutes
4643 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
4645 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4646 args
.rval().setNumber(duration
->minutes());
4651 * get Temporal.Duration.prototype.minutes
4653 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4655 CallArgs args
= CallArgsFromVp(argc
, vp
);
4656 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
4660 * get Temporal.Duration.prototype.seconds
4662 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
4664 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4665 args
.rval().setNumber(duration
->seconds());
4670 * get Temporal.Duration.prototype.seconds
4672 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4674 CallArgs args
= CallArgsFromVp(argc
, vp
);
4675 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
4679 * get Temporal.Duration.prototype.milliseconds
4681 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
4683 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4684 args
.rval().setNumber(duration
->milliseconds());
4689 * get Temporal.Duration.prototype.milliseconds
4691 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4693 CallArgs args
= CallArgsFromVp(argc
, vp
);
4694 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
4698 * get Temporal.Duration.prototype.microseconds
4700 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
4702 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4703 args
.rval().setNumber(duration
->microseconds());
4708 * get Temporal.Duration.prototype.microseconds
4710 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4712 CallArgs args
= CallArgsFromVp(argc
, vp
);
4713 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
4717 * get Temporal.Duration.prototype.nanoseconds
4719 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
4721 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4722 args
.rval().setNumber(duration
->nanoseconds());
4727 * get Temporal.Duration.prototype.nanoseconds
4729 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4731 CallArgs args
= CallArgsFromVp(argc
, vp
);
4732 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
4736 * get Temporal.Duration.prototype.sign
4738 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
4739 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4742 args
.rval().setInt32(DurationSign(duration
));
4747 * get Temporal.Duration.prototype.sign
4749 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4751 CallArgs args
= CallArgsFromVp(argc
, vp
);
4752 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
4756 * get Temporal.Duration.prototype.blank
4758 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
4759 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4762 args
.rval().setBoolean(duration
== Duration
{});
4767 * get Temporal.Duration.prototype.blank
4769 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4771 CallArgs args
= CallArgsFromVp(argc
, vp
);
4772 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
4776 * Temporal.Duration.prototype.with ( temporalDurationLike )
4778 * ToPartialDuration ( temporalDurationLike )
4780 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
4781 // Absent values default to the corresponding values of |this| object.
4782 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4785 Rooted
<JSObject
*> temporalDurationLike(
4786 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
4787 if (!temporalDurationLike
) {
4790 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
4795 auto* result
= CreateTemporalDuration(cx
, duration
);
4800 args
.rval().setObject(*result
);
4805 * Temporal.Duration.prototype.with ( temporalDurationLike )
4807 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4809 CallArgs args
= CallArgsFromVp(argc
, vp
);
4810 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
4814 * Temporal.Duration.prototype.negated ( )
4816 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
4817 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4820 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
4825 args
.rval().setObject(*result
);
4830 * Temporal.Duration.prototype.negated ( )
4832 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4834 CallArgs args
= CallArgsFromVp(argc
, vp
);
4835 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
4839 * Temporal.Duration.prototype.abs ( )
4841 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
4842 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4845 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
4850 args
.rval().setObject(*result
);
4855 * Temporal.Duration.prototype.abs ( )
4857 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4859 CallArgs args
= CallArgsFromVp(argc
, vp
);
4860 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
4864 * Temporal.Duration.prototype.add ( other [ , options ] )
4866 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
4867 return AddDurationToOrSubtractDurationFromDuration(cx
, DurationOperation::Add
,
4872 * Temporal.Duration.prototype.add ( other [ , options ] )
4874 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4876 CallArgs args
= CallArgsFromVp(argc
, vp
);
4877 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
4881 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4883 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
4884 return AddDurationToOrSubtractDurationFromDuration(
4885 cx
, DurationOperation::Subtract
, args
);
4889 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4891 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4893 CallArgs args
= CallArgsFromVp(argc
, vp
);
4894 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
4898 * Temporal.Duration.prototype.round ( roundTo )
4900 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4901 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4903 // Step 18. (Reordered)
4904 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
4907 auto smallestUnit
= TemporalUnit::Auto
;
4908 TemporalUnit largestUnit
;
4909 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
4910 auto roundingIncrement
= Increment
{1};
4911 Rooted
<JSObject
*> relativeTo(cx
);
4912 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4913 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4914 Rooted
<TimeZoneRecord
> timeZone(cx
);
4915 if (args
.get(0).isString()) {
4916 // Step 4. (Not applicable in our implementation.)
4918 // Steps 6-15. (Not applicable)
4921 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4922 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
4923 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
4927 // Step 17. (Not applicable)
4929 // Step 18. (Moved above)
4932 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4934 // Step 20. (Not applicable)
4936 // Step 20.a. (Not applicable)
4939 largestUnit
= defaultLargestUnit
;
4941 // Steps 21-25. (Not applicable)
4944 Rooted
<JSObject
*> options(
4945 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
4951 bool smallestUnitPresent
= true;
4954 bool largestUnitPresent
= true;
4958 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
4959 // absent "largestUnit" value.
4960 Rooted
<Value
> largestUnitValue(cx
);
4961 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
4962 &largestUnitValue
)) {
4966 if (!largestUnitValue
.isUndefined()) {
4967 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
4968 if (!largestUnitStr
) {
4972 largestUnit
= TemporalUnit::Auto
;
4973 if (!GetTemporalUnit(cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
4974 TemporalUnitGroup::DateTime
, &largestUnit
)) {
4980 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4981 &zonedRelativeTo
, &timeZone
)) {
4984 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4985 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4988 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
4993 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
4998 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
4999 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5004 if (smallestUnit
== TemporalUnit::Auto
) {
5006 smallestUnitPresent
= false;
5009 smallestUnit
= TemporalUnit::Nanosecond
;
5012 // Step 18. (Moved above)
5015 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5018 if (largestUnitValue
.isUndefined()) {
5020 largestUnitPresent
= false;
5023 largestUnit
= defaultLargestUnit
;
5024 } else if (largestUnit
== TemporalUnit::Auto
) {
5026 largestUnit
= defaultLargestUnit
;
5030 if (!smallestUnitPresent
&& !largestUnitPresent
) {
5031 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5032 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
5037 if (largestUnit
> smallestUnit
) {
5038 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5039 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
5044 if (smallestUnit
> TemporalUnit::Day
) {
5046 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
5049 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
5057 bool hoursToDaysConversionMayOccur
= false;
5060 if (duration
.days
!= 0 && zonedRelativeTo
) {
5061 hoursToDaysConversionMayOccur
= true;
5065 else if (std::abs(duration
.hours
) >= 24) {
5066 hoursToDaysConversionMayOccur
= true;
5070 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
5071 roundingIncrement
== Increment
{1};
5074 bool calendarUnitsPresent
=
5075 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
5078 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
5079 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
5080 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
5081 std::abs(duration
.milliseconds
) < 1000 &&
5082 std::abs(duration
.microseconds
) < 1000 &&
5083 std::abs(duration
.nanoseconds
) < 1000) {
5085 auto* obj
= CreateTemporalDuration(cx
, duration
);
5090 args
.rval().setObject(*obj
);
5095 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5098 bool plainDateTimeOrRelativeToWillBeUsed
=
5099 !roundingGranularityIsNoop
|| largestUnit
<= TemporalUnit::Day
||
5100 calendarUnitsPresent
|| duration
.days
!= 0;
5103 PlainDateTime relativeToDateTime
;
5104 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5106 const auto& instant
= zonedRelativeTo
.instant();
5109 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5112 precalculatedPlainDateTime
=
5113 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5116 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5117 zonedRelativeTo
.calendar());
5118 if (!plainRelativeTo
) {
5124 Rooted
<CalendarRecord
> calendar(cx
);
5125 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5128 CalendarMethod::DateAdd
,
5129 CalendarMethod::DateUntil
,
5136 DateDuration unbalanceResult
;
5137 if (plainRelativeTo
) {
5138 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5139 largestUnit
, plainRelativeTo
, calendar
,
5140 &unbalanceResult
)) {
5144 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5145 largestUnit
, &unbalanceResult
)) {
5148 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5153 NormalizedDuration
{unbalanceResult
, NormalizeTimeDuration(duration
)};
5154 NormalizedDuration roundResult
;
5155 if (plainRelativeTo
|| zonedRelativeTo
) {
5156 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5157 roundingMode
, plainRelativeTo
, calendar
,
5158 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5163 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5164 roundingMode
, &roundResult
)) {
5170 TimeDuration balanceResult
;
5171 if (zonedRelativeTo
) {
5173 NormalizedDuration adjustResult
;
5174 if (!AdjustRoundedDurationDays(cx
, roundResult
, roundingIncrement
,
5175 smallestUnit
, roundingMode
, zonedRelativeTo
,
5177 precalculatedPlainDateTime
, &adjustResult
)) {
5180 roundResult
= adjustResult
;
5183 if (!BalanceTimeDurationRelative(
5184 cx
, roundResult
, largestUnit
, zonedRelativeTo
, timeZone
,
5185 precalculatedPlainDateTime
, &balanceResult
)) {
5190 NormalizedTimeDuration withDays
;
5191 if (!Add24HourDaysToNormalizedTimeDuration(
5192 cx
, roundResult
.time
, roundResult
.date
.days
, &withDays
)) {
5197 balanceResult
= temporal::BalanceTimeDuration(withDays
, largestUnit
);
5201 auto balanceInput
= DateDuration
{
5202 roundResult
.date
.years
,
5203 roundResult
.date
.months
,
5204 roundResult
.date
.weeks
,
5207 DateDuration dateResult
;
5208 if (!::BalanceDateDurationRelative(cx
, balanceInput
, largestUnit
,
5209 smallestUnit
, plainRelativeTo
, calendar
,
5215 auto result
= Duration
{
5216 double(dateResult
.years
), double(dateResult
.months
),
5217 double(dateResult
.weeks
), double(dateResult
.days
),
5218 double(balanceResult
.hours
), double(balanceResult
.minutes
),
5219 double(balanceResult
.seconds
), double(balanceResult
.milliseconds
),
5220 balanceResult
.microseconds
, balanceResult
.nanoseconds
,
5223 auto* obj
= CreateTemporalDuration(cx
, result
);
5228 args
.rval().setObject(*obj
);
5233 * Temporal.Duration.prototype.round ( options )
5235 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5237 CallArgs args
= CallArgsFromVp(argc
, vp
);
5238 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
5242 * Temporal.Duration.prototype.total ( totalOf )
5244 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
5245 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
5246 auto duration
= ToDuration(durationObj
);
5249 Rooted
<JSObject
*> relativeTo(cx
);
5250 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5251 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5252 Rooted
<TimeZoneRecord
> timeZone(cx
);
5253 auto unit
= TemporalUnit::Auto
;
5254 if (args
.get(0).isString()) {
5255 // Step 4. (Not applicable in our implementation.)
5257 // Steps 6-10. (Implicit)
5258 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
5261 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5262 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::Unit
,
5263 TemporalUnitGroup::DateTime
, &unit
)) {
5268 Rooted
<JSObject
*> totalOf(
5269 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
5275 if (!ToRelativeTemporalObject(cx
, totalOf
, &plainRelativeTo
,
5276 &zonedRelativeTo
, &timeZone
)) {
5279 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5280 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5283 if (!GetTemporalUnit(cx
, totalOf
, TemporalUnitKey::Unit
,
5284 TemporalUnitGroup::DateTime
, &unit
)) {
5288 if (unit
== TemporalUnit::Auto
) {
5289 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5290 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
5296 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5299 bool plainDateTimeOrRelativeToWillBeUsed
=
5300 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
5303 PlainDateTime relativeToDateTime
;
5304 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5306 const auto& instant
= zonedRelativeTo
.instant();
5309 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5312 precalculatedPlainDateTime
=
5313 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5316 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5317 zonedRelativeTo
.calendar());
5318 if (!plainRelativeTo
) {
5324 Rooted
<CalendarRecord
> calendar(cx
);
5325 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5328 CalendarMethod::DateAdd
,
5329 CalendarMethod::DateUntil
,
5336 DateDuration unbalanceResult
;
5337 if (plainRelativeTo
) {
5338 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5339 plainRelativeTo
, calendar
,
5340 &unbalanceResult
)) {
5344 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5345 &unbalanceResult
)) {
5348 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5352 int64_t unbalancedDays
= unbalanceResult
.days
;
5356 NormalizedTimeDuration balanceResult
;
5357 if (zonedRelativeTo
) {
5359 Rooted
<ZonedDateTime
> intermediate(cx
);
5360 if (!MoveRelativeZonedDateTime(
5361 cx
, zonedRelativeTo
, calendar
, timeZone
,
5362 {unbalanceResult
.years
, unbalanceResult
.months
,
5363 unbalanceResult
.weeks
, 0},
5364 precalculatedPlainDateTime
, &intermediate
)) {
5369 auto timeDuration
= NormalizeTimeDuration(duration
);
5372 const auto& startNs
= intermediate
.instant();
5375 const auto& startInstant
= startNs
;
5378 mozilla::Maybe
<PlainDateTime
> startDateTime
{};
5381 Instant intermediateNs
;
5382 if (unbalancedDays
!= 0) {
5384 PlainDateTime dateTime
;
5385 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5388 startDateTime
= mozilla::Some(dateTime
);
5391 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
5393 if (!AddDaysToZonedDateTime(cx
, startInstant
, dateTime
, timeZone
,
5394 isoCalendar
, unbalancedDays
, &addResult
)) {
5399 intermediateNs
= addResult
;
5402 intermediateNs
= startNs
;
5407 if (!AddInstant(cx
, intermediateNs
, timeDuration
, &endNs
)) {
5413 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startNs
);
5417 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5418 if (TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
&&
5419 difference
!= NormalizedTimeDuration
{}) {
5421 if (!startDateTime
) {
5422 PlainDateTime dateTime
;
5423 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5426 startDateTime
= mozilla::Some(dateTime
);
5430 NormalizedTimeAndDays timeAndDays
;
5431 if (!NormalizedTimeDurationToDays(cx
, difference
, intermediate
, timeZone
,
5432 *startDateTime
, &timeAndDays
)) {
5437 balanceResult
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
5440 days
= timeAndDays
.days
;
5443 balanceResult
= difference
;
5448 auto timeDuration
= NormalizeTimeDuration(duration
);
5451 if (!Add24HourDaysToNormalizedTimeDuration(cx
, timeDuration
, unbalancedDays
,
5459 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult
));
5462 auto roundInput
= NormalizedDuration
{
5464 unbalanceResult
.years
,
5465 unbalanceResult
.months
,
5466 unbalanceResult
.weeks
,
5472 if (plainRelativeTo
|| zonedRelativeTo
) {
5473 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5474 TemporalRoundingMode::Trunc
, plainRelativeTo
, calendar
,
5475 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5480 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5481 TemporalRoundingMode::Trunc
, &total
)) {
5487 args
.rval().setNumber(total
);
5492 * Temporal.Duration.prototype.total ( totalOf )
5494 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5496 CallArgs args
= CallArgsFromVp(argc
, vp
);
5497 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
5501 * Temporal.Duration.prototype.toString ( [ options ] )
5503 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
5504 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5507 SecondsStringPrecision precision
= {Precision::Auto(),
5508 TemporalUnit::Nanosecond
, Increment
{1}};
5509 auto roundingMode
= TemporalRoundingMode::Trunc
;
5510 if (args
.hasDefined(0)) {
5512 Rooted
<JSObject
*> options(
5513 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
5519 auto digits
= Precision::Auto();
5520 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
5525 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5530 auto smallestUnit
= TemporalUnit::Auto
;
5531 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5532 TemporalUnitGroup::Time
, &smallestUnit
)) {
5537 if (smallestUnit
== TemporalUnit::Hour
||
5538 smallestUnit
== TemporalUnit::Minute
) {
5539 const char* smallestUnitStr
=
5540 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
5541 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5542 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
5543 smallestUnitStr
, "smallestUnit");
5548 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
5553 if (precision
.unit
!= TemporalUnit::Nanosecond
||
5554 precision
.increment
!= Increment
{1}) {
5556 auto timeDuration
= NormalizeTimeDuration(duration
);
5559 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
5562 auto rounded
= RoundDuration(timeDuration
, precision
.increment
,
5563 precision
.unit
, roundingMode
);
5566 auto balanced
= BalanceTimeDuration(
5567 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
5571 duration
.years
, duration
.months
,
5572 duration
.weeks
, duration
.days
+ double(balanced
.days
),
5573 double(balanced
.hours
), double(balanced
.minutes
),
5574 double(balanced
.seconds
), double(balanced
.milliseconds
),
5575 balanced
.microseconds
, balanced
.nanoseconds
,
5577 MOZ_ASSERT(IsValidDuration(duration
));
5584 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
5589 args
.rval().setString(str
);
5594 * Temporal.Duration.prototype.toString ( [ options ] )
5596 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5598 CallArgs args
= CallArgsFromVp(argc
, vp
);
5599 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
5603 * Temporal.Duration.prototype.toJSON ( )
5605 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
5606 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5609 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5614 args
.rval().setString(str
);
5619 * Temporal.Duration.prototype.toJSON ( )
5621 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5623 CallArgs args
= CallArgsFromVp(argc
, vp
);
5624 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
5628 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5630 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
5631 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5634 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5639 args
.rval().setString(str
);
5644 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5646 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5648 CallArgs args
= CallArgsFromVp(argc
, vp
);
5649 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
5653 * Temporal.Duration.prototype.valueOf ( )
5655 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5656 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
5657 "Duration", "primitive type");
5661 const JSClass
DurationObject::class_
= {
5662 "Temporal.Duration",
5663 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
5664 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
5666 &DurationObject::classSpec_
,
5669 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
5671 static const JSFunctionSpec Duration_methods
[] = {
5672 JS_FN("from", Duration_from
, 1, 0),
5673 JS_FN("compare", Duration_compare
, 2, 0),
5677 static const JSFunctionSpec Duration_prototype_methods
[] = {
5678 JS_FN("with", Duration_with
, 1, 0),
5679 JS_FN("negated", Duration_negated
, 0, 0),
5680 JS_FN("abs", Duration_abs
, 0, 0),
5681 JS_FN("add", Duration_add
, 1, 0),
5682 JS_FN("subtract", Duration_subtract
, 1, 0),
5683 JS_FN("round", Duration_round
, 1, 0),
5684 JS_FN("total", Duration_total
, 1, 0),
5685 JS_FN("toString", Duration_toString
, 0, 0),
5686 JS_FN("toJSON", Duration_toJSON
, 0, 0),
5687 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
5688 JS_FN("valueOf", Duration_valueOf
, 0, 0),
5692 static const JSPropertySpec Duration_prototype_properties
[] = {
5693 JS_PSG("years", Duration_years
, 0),
5694 JS_PSG("months", Duration_months
, 0),
5695 JS_PSG("weeks", Duration_weeks
, 0),
5696 JS_PSG("days", Duration_days
, 0),
5697 JS_PSG("hours", Duration_hours
, 0),
5698 JS_PSG("minutes", Duration_minutes
, 0),
5699 JS_PSG("seconds", Duration_seconds
, 0),
5700 JS_PSG("milliseconds", Duration_milliseconds
, 0),
5701 JS_PSG("microseconds", Duration_microseconds
, 0),
5702 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
5703 JS_PSG("sign", Duration_sign
, 0),
5704 JS_PSG("blank", Duration_blank
, 0),
5705 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
5709 const ClassSpec
DurationObject::classSpec_
= {
5710 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
5711 GenericCreatePrototype
<DurationObject
>,
5714 Duration_prototype_methods
,
5715 Duration_prototype_properties
,
5717 ClassSpec::DontDefineConstructor
,