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 int64_t microseconds
,
1295 int64_t nanoseconds
) {
1297 MOZ_ASSERT(IsValidDuration(
1298 {0, 0, 0, double(days
), double(hours
), double(minutes
), double(seconds
),
1299 double(milliseconds
), double(microseconds
), double(nanoseconds
)}));
1301 // All values are safe integers, so we don't need to convert to `double` and
1302 // 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
));
1307 MOZ_ASSERT(IsSafeInteger(milliseconds
));
1308 MOZ_ASSERT(IsSafeInteger(microseconds
));
1309 MOZ_ASSERT(IsSafeInteger(nanoseconds
));
1318 double(microseconds
),
1319 double(nanoseconds
),
1324 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1325 * microseconds, nanoseconds )
1327 static TimeDuration
CreateTimeDurationRecord(int64_t milliseconds
,
1328 const Int128
& microseconds
,
1329 const Int128
& nanoseconds
) {
1331 MOZ_ASSERT(IsValidDuration({0, 0, 0, 0, 0, 0, 0, double(milliseconds
),
1332 double(microseconds
), double(nanoseconds
)}));
1336 0, 0, 0, 0, milliseconds
, double(microseconds
), double(nanoseconds
),
1341 * BalanceTimeDuration ( norm, largestUnit )
1343 TimeDuration
js::temporal::BalanceTimeDuration(
1344 const NormalizedTimeDuration
& duration
, TemporalUnit largestUnit
) {
1345 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1346 MOZ_ASSERT(largestUnit
<= TemporalUnit::Second
,
1347 "fallible fractional seconds units");
1349 auto [seconds
, nanoseconds
] = duration
;
1351 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1352 // Convert these back to their absolute value and adjust the seconds part
1355 // For example the nanoseconds duration |-1n| is represented as the
1356 // duration {seconds: -1, nanoseconds: 999'999'999}.
1357 if (seconds
< 0 && nanoseconds
> 0) {
1359 nanoseconds
-= ToNanoseconds(TemporalUnit::Second
);
1365 int64_t minutes
= 0;
1366 int64_t milliseconds
= 0;
1367 int64_t microseconds
= 0;
1369 // Steps 2-3. (Not applicable in our implementation.)
1371 // We don't need to convert to positive numbers, because integer division
1372 // truncates and the %-operator has modulo semantics.
1375 switch (largestUnit
) {
1377 case TemporalUnit::Year
:
1378 case TemporalUnit::Month
:
1379 case TemporalUnit::Week
:
1380 case TemporalUnit::Day
: {
1382 microseconds
= nanoseconds
/ 1000;
1385 nanoseconds
= nanoseconds
% 1000;
1388 milliseconds
= microseconds
/ 1000;
1391 microseconds
= microseconds
% 1000;
1393 // Steps 4.e-f. (Not applicable)
1394 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1397 minutes
= seconds
/ 60;
1400 seconds
= seconds
% 60;
1403 hours
= minutes
/ 60;
1406 minutes
= minutes
% 60;
1418 case TemporalUnit::Hour
: {
1420 microseconds
= nanoseconds
/ 1000;
1423 nanoseconds
= nanoseconds
% 1000;
1426 milliseconds
= microseconds
/ 1000;
1429 microseconds
= microseconds
% 1000;
1431 // Steps 5.e-f. (Not applicable)
1432 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1435 minutes
= seconds
/ 60;
1438 seconds
= seconds
% 60;
1441 hours
= minutes
/ 60;
1444 minutes
= minutes
% 60;
1449 case TemporalUnit::Minute
: {
1451 microseconds
= nanoseconds
/ 1000;
1454 nanoseconds
= nanoseconds
% 1000;
1457 milliseconds
= microseconds
/ 1000;
1460 microseconds
= microseconds
% 1000;
1462 // Steps 6.e-f. (Not applicable)
1463 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1466 minutes
= seconds
/ 60;
1469 seconds
= seconds
% 60;
1475 case TemporalUnit::Second
: {
1477 microseconds
= nanoseconds
/ 1000;
1480 nanoseconds
= nanoseconds
% 1000;
1483 milliseconds
= microseconds
/ 1000;
1486 microseconds
= microseconds
% 1000;
1488 // Steps 7.e-f. (Not applicable)
1489 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1494 case TemporalUnit::Millisecond
:
1495 case TemporalUnit::Microsecond
:
1496 case TemporalUnit::Nanosecond
:
1497 case TemporalUnit::Auto
:
1498 MOZ_CRASH("Unexpected temporal unit");
1502 return CreateTimeDurationRecord(days
, hours
, minutes
, seconds
, milliseconds
,
1503 microseconds
, nanoseconds
);
1507 * BalanceTimeDuration ( norm, largestUnit )
1509 bool js::temporal::BalanceTimeDuration(JSContext
* cx
,
1510 const NormalizedTimeDuration
& duration
,
1511 TemporalUnit largestUnit
,
1512 TimeDuration
* result
) {
1513 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1515 auto [seconds
, nanoseconds
] = duration
;
1517 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1518 // Convert these back to their absolute value and adjust the seconds part
1521 // For example the nanoseconds duration |-1n| is represented as the
1522 // duration {seconds: -1, nanoseconds: 999'999'999}.
1523 if (seconds
< 0 && nanoseconds
> 0) {
1525 nanoseconds
-= ToNanoseconds(TemporalUnit::Second
);
1528 // Steps 1-3. (Not applicable in our implementation.)
1530 // We don't need to convert to positive numbers, because integer division
1531 // truncates and the %-operator has modulo semantics.
1534 switch (largestUnit
) {
1536 case TemporalUnit::Year
:
1537 case TemporalUnit::Month
:
1538 case TemporalUnit::Week
:
1539 case TemporalUnit::Day
:
1540 case TemporalUnit::Hour
:
1541 case TemporalUnit::Minute
:
1542 case TemporalUnit::Second
:
1543 *result
= BalanceTimeDuration(duration
, largestUnit
);
1547 case TemporalUnit::Millisecond
: {
1548 // The number of normalized seconds must not exceed `2**53 - 1`.
1549 constexpr auto limit
=
1550 (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second
);
1552 // The largest possible milliseconds value whose double representation
1553 // doesn't exceed the normalized seconds limit.
1554 constexpr auto max
= int64_t(0x7cff'ffff'ffff'fdff);
1556 // Assert |max| is the maximum allowed milliseconds value.
1557 static_assert(double(max
) < double(limit
));
1558 static_assert(double(max
+ 1) >= double(limit
));
1560 static_assert((NormalizedTimeDuration::max().seconds
+ 1) *
1561 ToMilliseconds(TemporalUnit::Second
) <=
1563 "total number duration milliseconds fits into int64");
1566 int64_t microseconds
= nanoseconds
/ 1000;
1569 nanoseconds
= nanoseconds
% 1000;
1572 int64_t milliseconds
= microseconds
/ 1000;
1573 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1576 microseconds
= microseconds
% 1000;
1579 (seconds
* ToMilliseconds(TemporalUnit::Second
)) + milliseconds
;
1580 if (std::abs(millis
) > max
) {
1581 JS_ReportErrorNumberASCII(
1582 cx
, GetErrorMessage
, nullptr,
1583 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
1588 *result
= CreateTimeDurationRecord(millis
, Int128
{microseconds
},
1589 Int128
{nanoseconds
});
1594 case TemporalUnit::Microsecond
: {
1595 // The number of normalized seconds must not exceed `2**53 - 1`.
1596 constexpr auto limit
= Uint128
{int64_t(1) << 53} *
1597 Uint128
{ToMicroseconds(TemporalUnit::Second
)};
1599 // The largest possible microseconds value whose double representation
1600 // doesn't exceed the normalized seconds limit.
1601 constexpr auto max
=
1602 (Uint128
{0x1e8} << 64) + Uint128
{0x47ff'ffff'fff7'ffff};
1603 static_assert(max
< limit
);
1605 // Assert |max| is the maximum allowed microseconds value.
1606 MOZ_ASSERT(double(max
) < double(limit
));
1607 MOZ_ASSERT(double(max
+ Uint128
{1}) >= double(limit
));
1610 int64_t microseconds
= nanoseconds
/ 1000;
1611 MOZ_ASSERT(std::abs(microseconds
) <= 999'999);
1614 nanoseconds
= nanoseconds
% 1000;
1617 (Int128
{seconds
} * Int128
{ToMicroseconds(TemporalUnit::Second
)}) +
1618 Int128
{microseconds
};
1619 if (micros
.abs() > max
) {
1620 JS_ReportErrorNumberASCII(
1621 cx
, GetErrorMessage
, nullptr,
1622 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
1627 *result
= CreateTimeDurationRecord(0, micros
, Int128
{nanoseconds
});
1632 case TemporalUnit::Nanosecond
: {
1633 // The number of normalized seconds must not exceed `2**53 - 1`.
1634 constexpr auto limit
= Uint128
{int64_t(1) << 53} *
1635 Uint128
{ToNanoseconds(TemporalUnit::Second
)};
1637 // The largest possible nanoseconds value whose double representation
1638 // doesn't exceed the normalized seconds limit.
1639 constexpr auto max
=
1640 (Uint128
{0x77359} << 64) + Uint128
{0x3fff'ffff'dfff'ffff};
1641 static_assert(max
< limit
);
1643 // Assert |max| is the maximum allowed nanoseconds value.
1644 MOZ_ASSERT(double(max
) < double(limit
));
1645 MOZ_ASSERT(double(max
+ Uint128
{1}) >= double(limit
));
1647 MOZ_ASSERT(std::abs(nanoseconds
) <= 999'999'999);
1650 (Int128
{seconds
} * Int128
{ToNanoseconds(TemporalUnit::Second
)}) +
1651 Int128
{nanoseconds
};
1652 if (nanos
.abs() > max
) {
1653 JS_ReportErrorNumberASCII(
1654 cx
, GetErrorMessage
, nullptr,
1655 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
1660 *result
= CreateTimeDurationRecord(0, Int128
{}, nanos
);
1664 case TemporalUnit::Auto
:
1667 MOZ_CRASH("Unexpected temporal unit");
1671 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1672 * timeZoneRec, precalculatedPlainDateTime )
1674 static bool BalanceTimeDurationRelative(
1675 JSContext
* cx
, const NormalizedDuration
& duration
, TemporalUnit largestUnit
,
1676 Handle
<ZonedDateTime
> relativeTo
, Handle
<TimeZoneRecord
> timeZone
,
1677 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1678 TimeDuration
* result
) {
1679 MOZ_ASSERT(IsValidDuration(duration
));
1682 const auto& startNs
= relativeTo
.instant();
1685 const auto& startInstant
= startNs
;
1688 auto intermediateNs
= startNs
;
1691 PlainDateTime startDateTime
;
1692 if (duration
.date
.days
!= 0) {
1694 if (!precalculatedPlainDateTime
) {
1695 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1698 precalculatedPlainDateTime
=
1699 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1703 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
1704 if (!AddDaysToZonedDateTime(cx
, startInstant
, *precalculatedPlainDateTime
,
1705 timeZone
, isoCalendar
, duration
.date
.days
,
1713 if (!AddInstant(cx
, intermediateNs
, duration
.time
, &endNs
)) {
1716 MOZ_ASSERT(IsValidEpochInstant(endNs
));
1720 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startInstant
);
1723 if (normalized
== NormalizedTimeDuration
{}) {
1730 if (TemporalUnit::Year
<= largestUnit
&& largestUnit
<= TemporalUnit::Day
) {
1732 if (!precalculatedPlainDateTime
) {
1733 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1736 precalculatedPlainDateTime
=
1737 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1741 NormalizedTimeAndDays timeAndDays
;
1742 if (!NormalizedTimeDurationToDays(cx
, normalized
, relativeTo
, timeZone
,
1743 *precalculatedPlainDateTime
,
1749 days
= timeAndDays
.days
;
1752 normalized
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
1753 MOZ_ASSERT_IF(days
> 0, normalized
>= NormalizedTimeDuration
{});
1754 MOZ_ASSERT_IF(days
< 0, normalized
<= NormalizedTimeDuration
{});
1757 largestUnit
= TemporalUnit::Hour
;
1761 TimeDuration balanceResult
;
1762 if (!BalanceTimeDuration(cx
, normalized
, largestUnit
, &balanceResult
)) {
1769 balanceResult
.hours
,
1770 balanceResult
.minutes
,
1771 balanceResult
.seconds
,
1772 balanceResult
.milliseconds
,
1773 balanceResult
.microseconds
,
1774 balanceResult
.nanoseconds
,
1776 MOZ_ASSERT(IsValidDuration(result
->toDuration()));
1781 * CreateDateDurationRecord ( years, months, weeks, days )
1783 static DateDuration
CreateDateDurationRecord(int64_t years
, int64_t months
,
1784 int64_t weeks
, int64_t days
) {
1785 MOZ_ASSERT(IsValidDuration(Duration
{
1791 return {years
, months
, weeks
, days
};
1795 * CreateDateDurationRecord ( years, months, weeks, days )
1797 static bool CreateDateDurationRecord(JSContext
* cx
, int64_t years
,
1798 int64_t months
, int64_t weeks
,
1799 int64_t days
, DateDuration
* result
) {
1800 auto duration
= DateDuration
{years
, months
, weeks
, days
};
1801 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1809 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration
& duration
,
1810 TemporalUnit largestUnit
) {
1811 MOZ_ASSERT(largestUnit
!= TemporalUnit::Auto
);
1814 return (largestUnit
> TemporalUnit::Year
&& duration
.years
!= 0) ||
1815 (largestUnit
> TemporalUnit::Month
&& duration
.months
!= 0) ||
1816 (largestUnit
> TemporalUnit::Week
&& duration
.weeks
!= 0);
1820 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1821 * plainRelativeTo, calendarRec )
1823 static bool UnbalanceDateDurationRelative(
1824 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1825 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1826 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1827 MOZ_ASSERT(IsValidDuration(duration
));
1829 auto [years
, months
, weeks
, days
] = duration
;
1831 // Step 1. (Not applicable in our implementation.)
1834 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1840 MOZ_ASSERT(largestUnit
!= TemporalUnit::Year
);
1842 // Step 6. (Not applicable in our implementation.)
1846 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1849 if (largestUnit
== TemporalUnit::Month
) {
1852 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1855 auto yearsDuration
= DateDuration
{years
};
1858 Rooted
<Wrapped
<PlainDateObject
*>> later(
1859 cx
, CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsDuration
));
1865 Duration untilResult
;
1866 if (!CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
,
1867 TemporalUnit::Month
, &untilResult
)) {
1872 int64_t yearsInMonths
= int64_t(untilResult
.months
);
1875 return CreateDateDurationRecord(cx
, 0, months
+ yearsInMonths
, weeks
, days
,
1880 if (largestUnit
== TemporalUnit::Week
) {
1882 auto yearsMonthsDuration
= DateDuration
{years
, months
};
1886 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsDuration
);
1890 auto laterDate
= ToPlainDate(&later
.unwrap());
1892 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1893 if (!unwrappedRelativeTo
) {
1896 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1899 int32_t yearsMonthsInDays
= DaysUntil(relativeToDate
, laterDate
);
1902 return CreateDateDurationRecord(cx
, 0, 0, weeks
, days
+ yearsMonthsInDays
,
1906 // Step 10. (Not applicable in our implementation.)
1909 auto yearsMonthsWeeksDuration
= DateDuration
{years
, months
, weeks
};
1913 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsWeeksDuration
);
1917 auto laterDate
= ToPlainDate(&later
.unwrap());
1919 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1920 if (!unwrappedRelativeTo
) {
1923 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1926 int32_t yearsMonthsWeeksInDay
= DaysUntil(relativeToDate
, laterDate
);
1929 return CreateDateDurationRecord(cx
, 0, 0, 0, days
+ yearsMonthsWeeksInDay
,
1934 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1935 * plainRelativeTo, calendarRec )
1937 static bool UnbalanceDateDurationRelative(JSContext
* cx
,
1938 const DateDuration
& duration
,
1939 TemporalUnit largestUnit
,
1940 DateDuration
* result
) {
1941 MOZ_ASSERT(IsValidDuration(duration
));
1943 // Step 1. (Not applicable.)
1946 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1952 MOZ_ASSERT(largestUnit
!= TemporalUnit::Year
);
1955 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1956 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
, "calendar");
1961 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1962 * smallestUnit, plainRelativeTo, calendarRec )
1964 static bool BalanceDateDurationRelative(
1965 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1966 TemporalUnit smallestUnit
,
1967 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1968 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1969 MOZ_ASSERT(IsValidDuration(duration
));
1970 MOZ_ASSERT(largestUnit
<= smallestUnit
);
1972 auto [years
, months
, weeks
, days
] = duration
;
1974 // FIXME: spec issue - effectful code paths should be more fine-grained
1975 // similar to UnbalanceDateDurationRelative. For example:
1976 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1977 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1978 // 3. Else if days = 0, then no-op.
1980 // Also note that |weeks| is never balanced, even when non-zero.
1982 // Step 1. (Not applicable in our implementation.)
1985 if (largestUnit
> TemporalUnit::Week
||
1986 (years
== 0 && months
== 0 && weeks
== 0 && days
== 0)) {
1993 if (!plainRelativeTo
) {
1994 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1995 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
2002 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
2006 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
2008 // Steps 8-9. (Not applicable in our implementation.)
2010 auto untilAddedDate
= [&](const DateDuration
& duration
,
2011 Duration
* untilResult
) {
2012 Rooted
<Wrapped
<PlainDateObject
*>> later(
2013 cx
, AddDate(cx
, calendar
, plainRelativeTo
, duration
));
2018 return CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
, largestUnit
,
2023 if (largestUnit
== TemporalUnit::Year
) {
2025 if (smallestUnit
== TemporalUnit::Week
) {
2027 MOZ_ASSERT(days
== 0);
2030 auto yearsMonthsDuration
= DateDuration
{years
, months
};
2032 // Steps 10.a.iii-iv.
2033 Duration untilResult
;
2034 if (!untilAddedDate(yearsMonthsDuration
, &untilResult
)) {
2039 *result
= CreateDateDurationRecord(int64_t(untilResult
.years
),
2040 int64_t(untilResult
.months
), weeks
, 0);
2045 const auto& yearsMonthsWeeksDaysDuration
= duration
;
2048 Duration untilResult
;
2049 if (!untilAddedDate(yearsMonthsWeeksDaysDuration
, &untilResult
)) {
2054 *result
= CreateDateDurationRecord(
2055 int64_t(untilResult
.years
), int64_t(untilResult
.months
),
2056 int64_t(untilResult
.weeks
), int64_t(untilResult
.days
));
2061 if (largestUnit
== TemporalUnit::Month
) {
2063 MOZ_ASSERT(years
== 0);
2066 if (smallestUnit
== TemporalUnit::Week
) {
2068 MOZ_ASSERT(days
== 0);
2071 *result
= CreateDateDurationRecord(0, months
, weeks
, 0);
2076 const auto& monthsWeeksDaysDuration
= duration
;
2079 Duration untilResult
;
2080 if (!untilAddedDate(monthsWeeksDaysDuration
, &untilResult
)) {
2085 *result
= CreateDateDurationRecord(0, int64_t(untilResult
.months
),
2086 int64_t(untilResult
.weeks
),
2087 int64_t(untilResult
.days
));
2092 MOZ_ASSERT(largestUnit
== TemporalUnit::Week
);
2095 MOZ_ASSERT(years
== 0);
2098 MOZ_ASSERT(months
== 0);
2101 const auto& weeksDaysDuration
= duration
;
2104 Duration untilResult
;
2105 if (!untilAddedDate(weeksDaysDuration
, &untilResult
)) {
2110 *result
= CreateDateDurationRecord(0, 0, int64_t(untilResult
.weeks
),
2111 int64_t(untilResult
.days
));
2116 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2117 * smallestUnit, plainRelativeTo, calendarRec )
2119 bool js::temporal::BalanceDateDurationRelative(
2120 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
2121 TemporalUnit smallestUnit
,
2122 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2123 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
2124 MOZ_ASSERT(plainRelativeTo
);
2125 MOZ_ASSERT(calendar
.receiver());
2127 return ::BalanceDateDurationRelative(cx
, duration
, largestUnit
, smallestUnit
,
2128 plainRelativeTo
, calendar
, result
);
2132 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2133 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2134 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2136 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2138 MOZ_ASSERT(IsValidDuration(one
));
2139 MOZ_ASSERT(IsValidDuration(two
));
2141 // Steps 1-2. (Not applicable)
2144 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2147 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2150 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2153 if (largestUnit
<= TemporalUnit::Week
) {
2154 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2155 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
2161 auto normalized1
= NormalizeTimeDuration(one
);
2164 auto normalized2
= NormalizeTimeDuration(two
);
2167 NormalizedTimeDuration normalized
;
2168 if (!AddNormalizedTimeDuration(cx
, normalized1
, normalized2
, &normalized
)) {
2173 int64_t days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
2174 int64_t days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
2175 auto totalDays
= mozilla::CheckedInt64(days1
) + days2
;
2176 MOZ_ASSERT(totalDays
.isValid(), "adding two duration days can't overflow");
2178 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized
, totalDays
.value(),
2184 TimeDuration balanced
;
2185 if (!temporal::BalanceTimeDuration(cx
, normalized
, largestUnit
, &balanced
)) {
2190 *result
= balanced
.toDuration();
2195 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2196 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2197 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2199 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2200 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2201 Handle
<CalendarRecord
> calendar
, Duration
* result
) {
2202 MOZ_ASSERT(IsValidDuration(one
));
2203 MOZ_ASSERT(IsValidDuration(two
));
2205 // Steps 1-2. (Not applicable)
2208 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2211 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2214 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2216 // Step 6. (Not applicable)
2218 // Step 7.a. (Not applicable in our implementation.)
2221 auto dateDuration1
= one
.toDateDuration();
2224 auto dateDuration2
= two
.toDateDuration();
2227 Rooted
<Wrapped
<PlainDateObject
*>> intermediate(
2228 cx
, AddDate(cx
, calendar
, plainRelativeTo
, dateDuration1
));
2229 if (!intermediate
) {
2234 Rooted
<Wrapped
<PlainDateObject
*>> end(
2235 cx
, AddDate(cx
, calendar
, intermediate
, dateDuration2
));
2241 auto dateLargestUnit
= std::min(TemporalUnit::Day
, largestUnit
);
2244 DateDuration dateDifference
;
2245 if (!DifferenceDate(cx
, calendar
, plainRelativeTo
, end
, dateLargestUnit
,
2251 auto normalized1
= NormalizeTimeDuration(one
);
2254 auto normalized2
= NormalizeTimeDuration(two
);
2257 NormalizedTimeDuration normalized1WithDays
;
2258 if (!Add24HourDaysToNormalizedTimeDuration(
2259 cx
, normalized1
, dateDifference
.days
, &normalized1WithDays
)) {
2264 NormalizedTimeDuration normalized
;
2265 if (!AddNormalizedTimeDuration(cx
, normalized1WithDays
, normalized2
,
2271 TimeDuration balanced
;
2272 if (!temporal::BalanceTimeDuration(cx
, normalized
, largestUnit
, &balanced
)) {
2278 double(dateDifference
.years
), double(dateDifference
.months
),
2279 double(dateDifference
.weeks
), double(balanced
.days
),
2280 double(balanced
.hours
), double(balanced
.minutes
),
2281 double(balanced
.seconds
), double(balanced
.milliseconds
),
2282 balanced
.microseconds
, balanced
.nanoseconds
,
2284 MOZ_ASSERT(IsValidDuration(*result
));
2289 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2290 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2291 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2293 static bool AddDuration(
2294 JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2295 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2296 Handle
<TimeZoneRecord
> timeZone
,
2297 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2299 // Steps 1-2. (Not applicable)
2302 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2305 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2308 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2310 // Steps 6-7. (Not applicable)
2312 // Steps 8-9. (Not applicable in our implementation.)
2315 bool startDateTimeNeeded
= largestUnit
<= TemporalUnit::Day
;
2318 if (!startDateTimeNeeded
) {
2319 // Steps 11-12. (Not applicable)
2322 auto normalized1
= NormalizeTimeDuration(one
);
2325 auto normalized2
= NormalizeTimeDuration(two
);
2327 // Step 15. (Inlined AddZonedDateTime, step 6.)
2328 Instant intermediateNs
;
2329 if (!AddInstant(cx
, zonedRelativeTo
.instant(), normalized1
,
2333 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2335 // Step 16. (Inlined AddZonedDateTime, step 6.)
2337 if (!AddInstant(cx
, intermediateNs
, normalized2
, &endNs
)) {
2340 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2343 auto normalized
= NormalizedTimeDurationFromEpochNanosecondsDifference(
2344 endNs
, zonedRelativeTo
.instant());
2347 TimeDuration balanced
;
2348 if (!BalanceTimeDuration(cx
, normalized
, largestUnit
, &balanced
)) {
2353 *result
= balanced
.toDuration();
2358 PlainDateTime startDateTime
;
2359 if (!precalculatedPlainDateTime
) {
2360 if (!GetPlainDateTimeFor(cx
, timeZone
, zonedRelativeTo
.instant(),
2365 startDateTime
= *precalculatedPlainDateTime
;
2369 auto normalized1
= CreateNormalizedDurationRecord(one
);
2372 auto normalized2
= CreateNormalizedDurationRecord(two
);
2375 Instant intermediateNs
;
2376 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2377 normalized1
, startDateTime
, &intermediateNs
)) {
2380 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2384 if (!AddZonedDateTime(cx
, intermediateNs
, timeZone
, calendar
, normalized2
,
2388 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2390 // Step 17. (Not applicable)
2393 NormalizedDuration difference
;
2394 if (!DifferenceZonedDateTime(cx
, zonedRelativeTo
.instant(), endNs
, timeZone
,
2395 calendar
, largestUnit
, startDateTime
,
2401 auto balanced
= BalanceTimeDuration(difference
.time
, TemporalUnit::Hour
);
2405 double(difference
.date
.years
), double(difference
.date
.months
),
2406 double(difference
.date
.weeks
), double(difference
.date
.days
),
2407 double(balanced
.hours
), double(balanced
.minutes
),
2408 double(balanced
.seconds
), double(balanced
.milliseconds
),
2409 balanced
.microseconds
, balanced
.nanoseconds
,
2411 MOZ_ASSERT(IsValidDuration(*result
));
2416 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2417 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2418 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2420 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2421 Handle
<ZonedDateTime
> zonedRelativeTo
,
2422 Handle
<CalendarRecord
> calendar
,
2423 Handle
<TimeZoneRecord
> timeZone
, Duration
* result
) {
2424 return AddDuration(cx
, one
, two
, zonedRelativeTo
, calendar
, timeZone
,
2425 mozilla::Nothing(), result
);
2429 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2430 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2431 * precalculatedPlainDateTime )
2433 static bool AdjustRoundedDurationDays(
2434 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2435 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2436 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2437 Handle
<TimeZoneRecord
> timeZone
,
2438 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2439 NormalizedDuration
* result
) {
2440 MOZ_ASSERT(IsValidDuration(duration
));
2443 if ((TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
) ||
2444 (unit
== TemporalUnit::Nanosecond
&& increment
== Increment
{1})) {
2449 // The increment is limited for all smaller temporal units.
2450 MOZ_ASSERT(increment
< MaximumTemporalDurationRoundingIncrement(unit
));
2453 MOZ_ASSERT(precalculatedPlainDateTime
);
2456 int32_t direction
= NormalizedTimeDurationSign(duration
.time
);
2460 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2461 duration
.date
, *precalculatedPlainDateTime
,
2465 MOZ_ASSERT(IsValidEpochInstant(dayStart
));
2468 PlainDateTime dayStartDateTime
;
2469 if (!GetPlainDateTimeFor(cx
, timeZone
, dayStart
, &dayStartDateTime
)) {
2475 if (!AddDaysToZonedDateTime(cx
, dayStart
, dayStartDateTime
, timeZone
,
2476 zonedRelativeTo
.calendar(), direction
, &dayEnd
)) {
2479 MOZ_ASSERT(IsValidEpochInstant(dayEnd
));
2483 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd
, dayStart
);
2484 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs
.to
<InstantSpan
>()));
2487 NormalizedTimeDuration oneDayLess
;
2488 if (!SubtractNormalizedTimeDuration(cx
, duration
.time
, dayLengthNs
,
2494 int32_t oneDayLessSign
= NormalizedTimeDurationSign(oneDayLess
);
2495 if ((direction
> 0 && oneDayLessSign
< 0) ||
2496 (direction
< 0 && oneDayLessSign
> 0)) {
2502 Duration adjustedDateDuration
;
2503 if (!AddDuration(cx
, duration
.date
.toDuration(), {0, 0, 0, double(direction
)},
2504 zonedRelativeTo
, calendar
, timeZone
,
2505 precalculatedPlainDateTime
, &adjustedDateDuration
)) {
2509 // FIXME: spec bug - RoundDuration is fallible
2510 // https://github.com/tc39/proposal-temporal/issues/2801
2514 // let oneDaySeconds = Temporal.Duration.from({days: 1}).total("seconds");
2516 // let d = Temporal.Duration.from({
2518 // seconds: (2**53 - 1) - oneDaySeconds,
2519 // nanoseconds: 999_999_999,
2522 // let timeZone = new class extends Temporal.TimeZone {
2523 // #getPossibleInstantsFor = 0
2525 // getPossibleInstantsFor(dateTime) {
2526 // if (++this.#getPossibleInstantsFor === 2) {
2527 // return super.getPossibleInstantsFor(Temporal.PlainDateTime.from("1970-01-01T00:00:00.000000001"));
2529 // return super.getPossibleInstantsFor(dateTime);
2533 // let relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
2535 // let r = d.round({
2536 // largestUnit: "nanoseconds",
2537 // smallestUnit: "nanoseconds",
2538 // roundingIncrement: 2,
2545 NormalizedTimeDuration roundedTime
;
2546 if (!RoundDuration(cx
, oneDayLess
, increment
, unit
, roundingMode
,
2552 return CombineDateAndNormalizedTimeDuration(
2553 cx
, adjustedDateDuration
.toDateDuration(), roundedTime
, result
);
2557 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2558 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2559 * precalculatedPlainDateTime )
2561 bool js::temporal::AdjustRoundedDurationDays(
2562 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2563 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2564 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2565 Handle
<TimeZoneRecord
> timeZone
,
2566 const PlainDateTime
& precalculatedPlainDateTime
,
2567 NormalizedDuration
* result
) {
2568 return ::AdjustRoundedDurationDays(
2569 cx
, duration
, increment
, unit
, roundingMode
, zonedRelativeTo
, calendar
,
2570 timeZone
, mozilla::SomeRef(precalculatedPlainDateTime
), result
);
2573 static bool NumberToStringBuilder(JSContext
* cx
, double num
,
2574 JSStringBuilder
& sb
) {
2575 MOZ_ASSERT(IsInteger(num
));
2576 MOZ_ASSERT(num
>= 0);
2577 MOZ_ASSERT(num
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2581 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
2583 return sb
.append(numStr
, length
);
2586 static Duration
AbsoluteDuration(const Duration
& duration
) {
2588 std::abs(duration
.years
), std::abs(duration
.months
),
2589 std::abs(duration
.weeks
), std::abs(duration
.days
),
2590 std::abs(duration
.hours
), std::abs(duration
.minutes
),
2591 std::abs(duration
.seconds
), std::abs(duration
.milliseconds
),
2592 std::abs(duration
.microseconds
), std::abs(duration
.nanoseconds
),
2597 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2599 [[nodiscard
]] static bool FormatFractionalSeconds(JSStringBuilder
& result
,
2600 int32_t subSecondNanoseconds
,
2601 Precision precision
) {
2602 MOZ_ASSERT(0 <= subSecondNanoseconds
&& subSecondNanoseconds
< 1'000'000'000);
2603 MOZ_ASSERT(precision
!= Precision::Minute());
2606 if (precision
== Precision::Auto()) {
2608 if (subSecondNanoseconds
== 0) {
2612 // Step 3. (Reordered)
2613 if (!result
.append('.')) {
2618 int32_t k
= 100'000'000;
2620 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2623 subSecondNanoseconds
%= k
;
2625 } while (subSecondNanoseconds
);
2628 uint8_t p
= precision
.value();
2633 // Step 3. (Reordered)
2634 if (!result
.append('.')) {
2639 int32_t k
= 100'000'000;
2640 for (uint8_t i
= 0; i
< precision
.value(); i
++) {
2641 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2644 subSecondNanoseconds
%= k
;
2653 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2654 * normSeconds, precision )
2656 static JSString
* TemporalDurationToString(JSContext
* cx
,
2657 const Duration
& duration
,
2658 Precision precision
) {
2659 MOZ_ASSERT(IsValidDuration(duration
));
2660 MOZ_ASSERT(precision
!= Precision::Minute());
2662 // Fast path for zero durations.
2663 if (duration
== Duration
{} &&
2664 (precision
== Precision::Auto() || precision
.value() == 0)) {
2665 return NewStringCopyZ
<CanGC
>(cx
, "PT0S");
2668 // Convert to absolute values up front. This is okay to do, because when the
2669 // duration is valid, all components have the same sign.
2670 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
2671 milliseconds
, microseconds
, nanoseconds
] =
2672 AbsoluteDuration(duration
);
2674 // Years to seconds parts are all safe integers for valid durations.
2675 MOZ_ASSERT(years
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2676 MOZ_ASSERT(months
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2677 MOZ_ASSERT(weeks
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2678 MOZ_ASSERT(days
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2679 MOZ_ASSERT(hours
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2680 MOZ_ASSERT(minutes
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2681 MOZ_ASSERT(seconds
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2683 auto secondsDuration
= NormalizeTimeDuration(0.0, 0.0, seconds
, milliseconds
,
2684 microseconds
, nanoseconds
);
2687 int32_t sign
= DurationSign(duration
);
2690 JSStringBuilder
result(cx
);
2692 // Step 13. (Reordered)
2694 if (!result
.append('-')) {
2699 // Step 14. (Reordered)
2700 if (!result
.append('P')) {
2706 if (!NumberToStringBuilder(cx
, years
, result
)) {
2709 if (!result
.append('Y')) {
2716 if (!NumberToStringBuilder(cx
, months
, result
)) {
2719 if (!result
.append('M')) {
2726 if (!NumberToStringBuilder(cx
, weeks
, result
)) {
2729 if (!result
.append('W')) {
2736 if (!NumberToStringBuilder(cx
, days
, result
)) {
2739 if (!result
.append('D')) {
2744 // Step 7. (Moved above)
2746 // Steps 10-11. (Reordered)
2747 bool zeroMinutesAndHigher
= years
== 0 && months
== 0 && weeks
== 0 &&
2748 days
== 0 && hours
== 0 && minutes
== 0;
2750 // Steps 8-9, 12, and 15.
2751 bool hasSecondsPart
= (secondsDuration
!= NormalizedTimeDuration
{}) ||
2752 zeroMinutesAndHigher
|| precision
!= Precision::Auto();
2753 if (hours
!= 0 || minutes
!= 0 || hasSecondsPart
) {
2754 // Step 15. (Reordered)
2755 if (!result
.append('T')) {
2761 if (!NumberToStringBuilder(cx
, hours
, result
)) {
2764 if (!result
.append('H')) {
2771 if (!NumberToStringBuilder(cx
, minutes
, result
)) {
2774 if (!result
.append('M')) {
2780 if (hasSecondsPart
) {
2782 if (!NumberToStringBuilder(cx
, double(secondsDuration
.seconds
), result
)) {
2787 if (!FormatFractionalSeconds(result
, secondsDuration
.nanoseconds
,
2793 if (!result
.append('S')) {
2799 // Steps 13-15. (Moved above)
2802 return result
.finishString();
2806 * ToRelativeTemporalObject ( options )
2808 static bool ToRelativeTemporalObject(
2809 JSContext
* cx
, Handle
<JSObject
*> options
,
2810 MutableHandle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2811 MutableHandle
<ZonedDateTime
> zonedRelativeTo
,
2812 MutableHandle
<TimeZoneRecord
> timeZoneRecord
) {
2814 Rooted
<Value
> value(cx
);
2815 if (!GetProperty(cx
, options
, options
, cx
->names().relativeTo
, &value
)) {
2820 if (value
.isUndefined()) {
2821 plainRelativeTo
.set(nullptr);
2822 zonedRelativeTo
.set(ZonedDateTime
{});
2823 timeZoneRecord
.set(TimeZoneRecord
{});
2828 auto offsetBehaviour
= OffsetBehaviour::Option
;
2831 auto matchBehaviour
= MatchBehaviour::MatchExactly
;
2834 PlainDateTime dateTime
;
2835 Rooted
<CalendarValue
> calendar(cx
);
2836 Rooted
<TimeZoneValue
> timeZone(cx
);
2838 if (value
.isObject()) {
2839 Rooted
<JSObject
*> obj(cx
, &value
.toObject());
2842 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
2843 auto instant
= ToInstant(zonedDateTime
);
2844 Rooted
<TimeZoneValue
> timeZone(cx
, zonedDateTime
->timeZone());
2845 Rooted
<CalendarValue
> calendar(cx
, zonedDateTime
->calendar());
2847 if (!timeZone
.wrap(cx
)) {
2850 if (!calendar
.wrap(cx
)) {
2855 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2856 if (!CreateTimeZoneMethodsRecord(
2859 TimeZoneMethod::GetOffsetNanosecondsFor
,
2860 TimeZoneMethod::GetPossibleInstantsFor
,
2867 plainRelativeTo
.set(nullptr);
2868 zonedRelativeTo
.set(ZonedDateTime
{instant
, timeZone
, calendar
});
2869 timeZoneRecord
.set(timeZoneRec
);
2874 if (obj
->canUnwrapAs
<PlainDateObject
>()) {
2875 plainRelativeTo
.set(obj
);
2876 zonedRelativeTo
.set(ZonedDateTime
{});
2877 timeZoneRecord
.set(TimeZoneRecord
{});
2882 if (auto* dateTime
= obj
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
2883 auto plainDateTime
= ToPlainDate(dateTime
);
2885 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
2886 if (!calendar
.wrap(cx
)) {
2891 auto* plainDate
= CreateTemporalDate(cx
, plainDateTime
, calendar
);
2897 plainRelativeTo
.set(plainDate
);
2898 zonedRelativeTo
.set(ZonedDateTime
{});
2899 timeZoneRecord
.set(TimeZoneRecord
{});
2904 if (!GetTemporalCalendarWithISODefault(cx
, obj
, &calendar
)) {
2909 Rooted
<CalendarRecord
> calendarRec(cx
);
2910 if (!CreateCalendarMethodsRecord(cx
, calendar
,
2912 CalendarMethod::DateFromFields
,
2913 CalendarMethod::Fields
,
2920 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2921 if (!CalendarFields(cx
, calendarRec
,
2922 {CalendarField::Day
, CalendarField::Month
,
2923 CalendarField::MonthCode
, CalendarField::Year
},
2929 if (!AppendSorted(cx
, fieldNames
.get(),
2931 TemporalField::Hour
,
2932 TemporalField::Microsecond
,
2933 TemporalField::Millisecond
,
2934 TemporalField::Minute
,
2935 TemporalField::Nanosecond
,
2936 TemporalField::Offset
,
2937 TemporalField::Second
,
2938 TemporalField::TimeZone
,
2944 Rooted
<PlainObject
*> fields(cx
, PrepareTemporalFields(cx
, obj
, fieldNames
));
2950 Rooted
<PlainObject
*> dateOptions(cx
, NewPlainObjectWithProto(cx
, nullptr));
2956 Rooted
<Value
> overflow(cx
, StringValue(cx
->names().constrain
));
2957 if (!DefineDataProperty(cx
, dateOptions
, cx
->names().overflow
, overflow
)) {
2962 if (!InterpretTemporalDateTimeFields(cx
, calendarRec
, fields
, dateOptions
,
2968 Rooted
<Value
> offset(cx
);
2969 if (!GetProperty(cx
, fields
, fields
, cx
->names().offset
, &offset
)) {
2974 Rooted
<Value
> timeZoneValue(cx
);
2975 if (!GetProperty(cx
, fields
, fields
, cx
->names().timeZone
,
2981 if (!timeZoneValue
.isUndefined()) {
2982 if (!ToTemporalTimeZone(cx
, timeZoneValue
, &timeZone
)) {
2988 if (offset
.isUndefined()) {
2989 offsetBehaviour
= OffsetBehaviour::Wall
;
2994 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2995 MOZ_ASSERT(!offset
.isUndefined());
2996 MOZ_ASSERT(offset
.isString());
2999 Rooted
<JSString
*> offsetString(cx
, offset
.toString());
3000 if (!offsetString
) {
3005 if (!ParseDateTimeUTCOffset(cx
, offsetString
, &offsetNs
)) {
3015 if (!value
.isString()) {
3016 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
3017 nullptr, "not a string");
3020 Rooted
<JSString
*> string(cx
, value
.toString());
3025 int64_t timeZoneOffset
;
3026 Rooted
<ParsedTimeZone
> timeZoneAnnotation(cx
);
3027 Rooted
<JSString
*> calendarString(cx
);
3028 if (!ParseTemporalRelativeToString(cx
, string
, &dateTime
, &isUTC
,
3029 &hasOffset
, &timeZoneOffset
,
3030 &timeZoneAnnotation
, &calendarString
)) {
3034 // Step 6.c. (Not applicable in our implementation.)
3037 if (timeZoneAnnotation
) {
3039 if (!ToTemporalTimeZone(cx
, timeZoneAnnotation
, &timeZone
)) {
3043 // Steps 6.f.ii-iii.
3045 offsetBehaviour
= OffsetBehaviour::Exact
;
3046 } else if (!hasOffset
) {
3047 offsetBehaviour
= OffsetBehaviour::Wall
;
3051 matchBehaviour
= MatchBehaviour::MatchMinutes
;
3053 MOZ_ASSERT(!timeZone
);
3057 if (calendarString
) {
3058 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
3062 calendar
.set(CalendarValue(cx
->names().iso8601
));
3067 if (offsetBehaviour
== OffsetBehaviour::Option
) {
3068 MOZ_ASSERT(hasOffset
);
3071 offsetNs
= timeZoneOffset
;
3082 auto* plainDate
= CreateTemporalDate(cx
, dateTime
.date
, calendar
);
3087 plainRelativeTo
.set(plainDate
);
3088 zonedRelativeTo
.set(ZonedDateTime
{});
3089 timeZoneRecord
.set(TimeZoneRecord
{});
3093 // Steps 8-9. (Moved above)
3096 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
3097 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
3099 TimeZoneMethod::GetOffsetNanosecondsFor
,
3100 TimeZoneMethod::GetPossibleInstantsFor
,
3107 Instant epochNanoseconds
;
3108 if (!InterpretISODateTimeOffset(
3109 cx
, dateTime
, offsetBehaviour
, offsetNs
, timeZoneRec
,
3110 TemporalDisambiguation::Compatible
, TemporalOffset::Reject
,
3111 matchBehaviour
, &epochNanoseconds
)) {
3114 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds
));
3117 plainRelativeTo
.set(nullptr);
3118 zonedRelativeTo
.set(ZonedDateTime
{epochNanoseconds
, timeZone
, calendar
});
3119 timeZoneRecord
.set(timeZoneRec
);
3124 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
3127 static bool CreateCalendarMethodsRecordFromRelativeTo(
3128 JSContext
* cx
, Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3129 Handle
<ZonedDateTime
> zonedRelativeTo
,
3130 mozilla::EnumSet
<CalendarMethod
> methods
,
3131 MutableHandle
<CalendarRecord
> result
) {
3133 if (zonedRelativeTo
) {
3134 return CreateCalendarMethodsRecord(cx
, zonedRelativeTo
.calendar(), methods
,
3139 if (plainRelativeTo
) {
3140 auto* unwrapped
= plainRelativeTo
.unwrap(cx
);
3145 Rooted
<CalendarValue
> calendar(cx
, unwrapped
->calendar());
3146 if (!calendar
.wrap(cx
)) {
3150 return CreateCalendarMethodsRecord(cx
, calendar
, methods
, result
);
3157 struct RoundedDuration final
{
3158 NormalizedDuration duration
;
3162 enum class ComputeRemainder
: bool { No
, Yes
};
3165 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3167 static NormalizedTimeDuration
RoundNormalizedTimeDurationToIncrement(
3168 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
,
3169 Increment increment
, TemporalRoundingMode roundingMode
) {
3170 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3171 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3172 MOZ_ASSERT(increment
<= MaximumTemporalDurationRoundingIncrement(unit
));
3174 int64_t divisor
= ToNanoseconds(unit
) * increment
.value();
3175 MOZ_ASSERT(divisor
> 0);
3176 MOZ_ASSERT(divisor
<= ToNanoseconds(TemporalUnit::Day
));
3178 auto totalNanoseconds
= duration
.toNanoseconds();
3180 RoundNumberToIncrement(totalNanoseconds
, Int128
{divisor
}, roundingMode
);
3181 return NormalizedTimeDuration::fromNanoseconds(rounded
);
3185 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3187 static bool RoundNormalizedTimeDurationToIncrement(
3188 JSContext
* cx
, const NormalizedTimeDuration
& duration
,
3189 const TemporalUnit unit
, Increment increment
,
3190 TemporalRoundingMode roundingMode
, NormalizedTimeDuration
* result
) {
3192 auto rounded
= RoundNormalizedTimeDurationToIncrement(
3193 duration
, unit
, increment
, roundingMode
);
3196 if (!IsValidNormalizedTimeDuration(rounded
)) {
3197 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3198 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
3208 * DivideNormalizedTimeDuration ( d, divisor )
3210 static double TotalNormalizedTimeDuration(
3211 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
) {
3212 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3213 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3215 auto numerator
= duration
.toNanoseconds();
3216 auto denominator
= Int128
{ToNanoseconds(unit
)};
3217 return FractionToDouble(numerator
, denominator
);
3221 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3222 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3223 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3225 NormalizedTimeDuration
js::temporal::RoundDuration(
3226 const NormalizedTimeDuration
& duration
, Increment increment
,
3227 TemporalUnit unit
, TemporalRoundingMode roundingMode
) {
3228 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3229 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3231 // Steps 1-13. (Not applicable)
3234 auto rounded
= RoundNormalizedTimeDurationToIncrement(
3235 duration
, unit
, increment
, roundingMode
);
3236 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded
));
3243 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3244 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3245 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3247 bool js::temporal::RoundDuration(JSContext
* cx
,
3248 const NormalizedTimeDuration
& duration
,
3249 Increment increment
, TemporalUnit unit
,
3250 TemporalRoundingMode roundingMode
,
3251 NormalizedTimeDuration
* result
) {
3252 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3253 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3255 // Steps 1-13. (Not applicable)
3258 return RoundNormalizedTimeDurationToIncrement(cx
, duration
, unit
, increment
,
3259 roundingMode
, result
);
3262 struct FractionalDays final
{
3265 int64_t dayLength
= 0;
3267 FractionalDays() = default;
3269 explicit FractionalDays(int64_t durationDays
,
3270 const NormalizedTimeAndDays
& timeAndDays
)
3271 : days(durationDays
+ timeAndDays
.days
),
3272 time(timeAndDays
.time
),
3273 dayLength(timeAndDays
.dayLength
) {
3274 MOZ_ASSERT(durationDays
<= (int64_t(1) << 53) / (24 * 60 * 60));
3275 MOZ_ASSERT(timeAndDays
.days
<= (int64_t(1) << 53) / (24 * 60 * 60));
3277 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3278 // positive and less than 2**53.
3279 MOZ_ASSERT(dayLength
> 0);
3280 MOZ_ASSERT(dayLength
< int64_t(1) << 53);
3282 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3283 // less than |timeAndDays.dayLength|.
3284 MOZ_ASSERT(std::abs(time
) < dayLength
);
3287 FractionalDays
operator+=(int32_t epochDays
) {
3288 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3293 FractionalDays
operator-=(int32_t epochDays
) {
3294 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3299 int64_t truncate() const {
3300 int64_t truncatedDays
= days
;
3302 // Round toward positive infinity when the integer days are negative and
3303 // the fractional part is positive.
3304 if (truncatedDays
< 0) {
3307 } else if (time
< 0) {
3308 // Round toward negative infinity when the integer days are positive and
3309 // the fractional part is negative.
3310 if (truncatedDays
> 0) {
3314 return truncatedDays
;
3317 int32_t sign() const {
3319 return days
< 0 ? -1 : 1;
3321 return time
< 0 ? -1 : time
> 0 ? 1 : 0;
3325 struct Fraction final
{
3326 int64_t numerator
= 0;
3327 int32_t denominator
= 0;
3329 constexpr Fraction() = default;
3331 constexpr Fraction(int64_t numerator
, int32_t denominator
)
3332 : numerator(numerator
), denominator(denominator
) {
3333 MOZ_ASSERT(denominator
> 0);
3337 struct RoundedNumber final
{
3342 static RoundedNumber
RoundNumberToIncrement(
3343 const Fraction
& fraction
, const FractionalDays
& fractionalDays
,
3344 Increment increment
, TemporalRoundingMode roundingMode
,
3345 ComputeRemainder computeRemainder
) {
3347 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3348 static constexpr int64_t maxDurationDays
=
3349 (int64_t(1) << 53) / (24 * 60 * 60);
3351 // Numbers of days between nsMinInstant and nsMaxInstant.
3352 static constexpr int32_t epochDays
= 200'000'000;
3354 // Maximum number of days in |fractionalDays|.
3355 static constexpr int64_t maxFractionalDays
=
3356 2 * maxDurationDays
+ 2 * epochDays
;
3359 MOZ_ASSERT(std::abs(fraction
.numerator
) < (int64_t(1) << 32) * 2);
3360 MOZ_ASSERT(fraction
.denominator
> 0);
3361 MOZ_ASSERT(fraction
.denominator
<= epochDays
);
3362 MOZ_ASSERT(std::abs(fractionalDays
.days
) <= maxFractionalDays
);
3363 MOZ_ASSERT(fractionalDays
.dayLength
> 0);
3364 MOZ_ASSERT(fractionalDays
.dayLength
< (int64_t(1) << 53));
3365 MOZ_ASSERT(std::abs(fractionalDays
.time
) < fractionalDays
.dayLength
);
3366 MOZ_ASSERT(increment
<= Increment::max());
3370 // Change the representation of |fractionalWeeks| from a real number to a
3371 // rational number, because we don't support arbitrary precision real
3374 // |fractionalWeeks| is defined as:
3377 // = weeks + days' / abs(oneWeekDays)
3379 // where days' = days + nanoseconds / dayLength.
3381 // The fractional part |nanoseconds / dayLength| is from step 7.
3383 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3386 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3387 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3388 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3390 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3391 // to omit the multiplication by |dayLength| when the rounding conditions are
3392 // appropriately modified to account for the |nanoseconds / dayLength| part.
3393 // This allows to implement rounding using only int64 values.
3395 // This optimization is currently only implemented when |nanoseconds| is zero.
3397 // Example how to expand this optimization for non-zero |nanoseconds|:
3399 // |Round(fraction / increment) * increment| with:
3400 // fraction = numerator / denominator
3401 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3402 // denominator = dayLength * abs(oneWeekDays)
3404 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3406 // |Round(fraction / increment) * increment| with:
3407 // fraction = numerator / denominator
3408 // numerator = weeks * abs(oneWeekDays) + days
3409 // denominator = abs(oneWeekDays)
3412 // fraction / increment
3413 // = (numerator / denominator) / increment
3414 // = numerator / (denominator * increment)
3416 // And |numerator| and |denominator * increment| both fit into int64.
3418 // The "ceiling" operation has to be modified from:
3420 // CeilDiv(dividend, divisor)
3421 // quot, rem = dividend / divisor
3422 // return quot + (rem > 0)
3426 // CeilDiv(dividend, divisor, fractional)
3427 // quot, rem = dividend / divisor
3428 // return quot + ((rem > 0) || (fractional > 0))
3430 // To properly account for the fractional |nanoseconds| part. Alternatively
3431 // |dividend| can be modified before calling `CeilDiv`.
3435 if (fractionalDays
.time
== 0) {
3436 auto [numerator
, denominator
] = fraction
;
3437 int64_t totalDays
= fractionalDays
.days
+ denominator
* numerator
;
3439 if (computeRemainder
== ComputeRemainder::Yes
) {
3440 constexpr auto rounded
= Int128
{0};
3441 double total
= FractionToDouble(totalDays
, denominator
);
3442 return {rounded
, total
};
3446 RoundNumberToIncrement(totalDays
, denominator
, increment
, roundingMode
);
3447 constexpr double total
= 0;
3448 return {rounded
, total
};
3452 auto dayLength
= mozilla::CheckedInt64(fractionalDays
.dayLength
);
3454 auto denominator
= dayLength
* fraction
.denominator
;
3455 if (!denominator
.isValid()) {
3459 auto amountNanos
= denominator
* fraction
.numerator
;
3460 if (!amountNanos
.isValid()) {
3464 auto totalNanoseconds
= dayLength
* fractionalDays
.days
;
3465 totalNanoseconds
+= fractionalDays
.time
;
3466 totalNanoseconds
+= amountNanos
;
3467 if (!totalNanoseconds
.isValid()) {
3471 if (computeRemainder
== ComputeRemainder::Yes
) {
3472 constexpr auto rounded
= Int128
{0};
3474 FractionToDouble(totalNanoseconds
.value(), denominator
.value());
3475 return {rounded
, total
};
3478 auto rounded
= RoundNumberToIncrement(
3479 totalNanoseconds
.value(), denominator
.value(), increment
, roundingMode
);
3480 constexpr double total
= 0;
3481 return {rounded
, total
};
3484 // Use int128 when values are too large for int64. Additionally assert all
3485 // values fit into int128.
3487 // `dayLength` < 2**53
3488 auto dayLength
= Int128
{fractionalDays
.dayLength
};
3489 MOZ_ASSERT(dayLength
< Int128
{1} << 53);
3491 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3492 auto denominator
= dayLength
* Int128
{fraction
.denominator
};
3493 MOZ_ASSERT(denominator
< Int128
{1} << (53 + 28));
3495 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3497 // `abs(maxFractionalDays)`
3498 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3499 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3500 // ≤ 2 * 2**37 + 2**29
3502 auto totalDays
= Int128
{fractionalDays
.days
};
3503 MOZ_ASSERT(totalDays
.abs() <= Uint128
{1} << 39);
3505 // `abs(fraction.numerator)` ≤ (2**33)
3506 auto totalAmount
= Int128
{fraction
.numerator
};
3507 MOZ_ASSERT(totalAmount
.abs() <= Uint128
{1} << 33);
3509 // `denominator` < 2**(53 + 28)
3510 // `abs(totalAmount)` <= 2**33
3512 // `denominator * totalAmount`
3513 // ≤ 2**(53 + 28) * 2**33
3514 // = 2**(53 + 28 + 33)
3516 auto amountNanos
= denominator
* totalAmount
;
3517 MOZ_ASSERT(amountNanos
.abs() <= Uint128
{1} << 114);
3519 // `dayLength` < 2**53
3520 // `totalDays` ≤ 2**39
3521 // `fractionalDays.time` < `dayLength` < 2**53
3522 // `amountNanos` ≤ 2**114
3524 // `dayLength * totalDays`
3525 // ≤ 2**(53 + 39) = 2**92
3527 // `dayLength * totalDays + fractionalDays.time`
3530 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3532 auto totalNanoseconds
= dayLength
* totalDays
;
3533 totalNanoseconds
+= Int128
{fractionalDays
.time
};
3534 totalNanoseconds
+= amountNanos
;
3535 MOZ_ASSERT(totalNanoseconds
.abs() <= Uint128
{1} << 115);
3537 if (computeRemainder
== ComputeRemainder::Yes
) {
3538 constexpr auto rounded
= Int128
{0};
3539 double total
= FractionToDouble(totalNanoseconds
, denominator
);
3540 return {rounded
, total
};
3543 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, denominator
,
3544 increment
, roundingMode
);
3545 constexpr double total
= 0;
3546 return {rounded
, total
};
3549 static bool RoundDurationYear(JSContext
* cx
, const NormalizedDuration
& duration
,
3550 FractionalDays fractionalDays
,
3551 Increment increment
,
3552 TemporalRoundingMode roundingMode
,
3553 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3554 Handle
<CalendarRecord
> calendar
,
3555 ComputeRemainder computeRemainder
,
3556 RoundedDuration
* result
) {
3558 // Numbers of days between nsMinInstant and nsMaxInstant.
3559 static constexpr int32_t epochDays
= 200'000'000;
3562 auto [years
, months
, weeks
, days
] = duration
.date
;
3565 auto yearsDuration
= DateDuration
{years
};
3568 auto yearsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsDuration
);
3572 auto yearsLaterDate
= ToPlainDate(&yearsLater
.unwrap());
3574 // Step 10.f. (Reordered)
3575 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsLater
);
3578 auto yearsMonthsWeeks
= DateDuration
{years
, months
, weeks
};
3581 PlainDate yearsMonthsWeeksLater
;
3582 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3583 &yearsMonthsWeeksLater
)) {
3588 int32_t monthsWeeksInDays
= DaysUntil(yearsLaterDate
, yearsMonthsWeeksLater
);
3589 MOZ_ASSERT(std::abs(monthsWeeksInDays
) <= epochDays
);
3591 // Step 10.f. (Moved up)
3594 fractionalDays
+= monthsWeeksInDays
;
3596 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3597 // https://github.com/tc39/proposal-temporal/issues/2540
3600 PlainDate isoResult
;
3601 if (!AddISODate(cx
, yearsLaterDate
, {0, 0, 0, fractionalDays
.truncate()},
3602 TemporalOverflow::Constrain
, &isoResult
)) {
3607 Rooted
<PlainDateObject
*> wholeDaysLater(
3608 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3609 if (!wholeDaysLater
) {
3614 DateDuration timePassed
;
3615 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3616 TemporalUnit::Year
, &timePassed
)) {
3621 int64_t yearsPassed
= timePassed
.years
;
3624 years
+= yearsPassed
;
3627 auto yearsPassedDuration
= DateDuration
{yearsPassed
};
3631 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, yearsPassedDuration
,
3632 &newRelativeTo
, &daysPassed
)) {
3635 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3638 fractionalDays
-= daysPassed
;
3641 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3644 auto oneYear
= DateDuration
{sign
};
3647 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3648 int32_t oneYearDays
;
3649 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneYear
,
3650 &moveResultIgnored
, &oneYearDays
)) {
3655 if (oneYearDays
== 0) {
3656 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3657 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3662 auto fractionalYears
= Fraction
{years
, std::abs(oneYearDays
)};
3665 auto [numYears
, total
] =
3666 RoundNumberToIncrement(fractionalYears
, fractionalDays
, increment
,
3667 roundingMode
, computeRemainder
);
3670 int64_t numMonths
= 0;
3671 int64_t numWeeks
= 0;
3674 constexpr auto time
= NormalizedTimeDuration
{};
3677 if (numYears
.abs() >= (Uint128
{1} << 32)) {
3678 return ThrowInvalidDurationPart(cx
, double(numYears
), "years",
3679 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3682 auto resultDuration
= DateDuration
{int64_t(numYears
), numMonths
, numWeeks
};
3683 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3687 *result
= {{resultDuration
, time
}, total
};
3691 static bool RoundDurationMonth(JSContext
* cx
,
3692 const NormalizedDuration
& duration
,
3693 FractionalDays fractionalDays
,
3694 Increment increment
,
3695 TemporalRoundingMode roundingMode
,
3696 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3697 Handle
<CalendarRecord
> calendar
,
3698 ComputeRemainder computeRemainder
,
3699 RoundedDuration
* result
) {
3701 // Numbers of days between nsMinInstant and nsMaxInstant.
3702 static constexpr int32_t epochDays
= 200'000'000;
3705 auto [years
, months
, weeks
, days
] = duration
.date
;
3708 auto yearsMonths
= DateDuration
{years
, months
};
3711 auto yearsMonthsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsMonths
);
3712 if (!yearsMonthsLater
) {
3715 auto yearsMonthsLaterDate
= ToPlainDate(&yearsMonthsLater
.unwrap());
3717 // Step 11.f. (Reordered)
3718 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsMonthsLater
);
3721 auto yearsMonthsWeeks
= DateDuration
{years
, months
, weeks
};
3724 PlainDate yearsMonthsWeeksLater
;
3725 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3726 &yearsMonthsWeeksLater
)) {
3731 int32_t weeksInDays
= DaysUntil(yearsMonthsLaterDate
, yearsMonthsWeeksLater
);
3732 MOZ_ASSERT(std::abs(weeksInDays
) <= epochDays
);
3734 // Step 11.f. (Moved up)
3737 fractionalDays
+= weeksInDays
;
3739 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3740 // https://github.com/tc39/proposal-temporal/issues/2540
3743 PlainDate isoResult
;
3744 if (!AddISODate(cx
, yearsMonthsLaterDate
,
3745 {0, 0, 0, fractionalDays
.truncate()},
3746 TemporalOverflow::Constrain
, &isoResult
)) {
3751 Rooted
<PlainDateObject
*> wholeDaysLater(
3752 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3753 if (!wholeDaysLater
) {
3758 DateDuration timePassed
;
3759 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3760 TemporalUnit::Month
, &timePassed
)) {
3765 int64_t monthsPassed
= timePassed
.months
;
3768 months
+= monthsPassed
;
3771 auto monthsPassedDuration
= DateDuration
{0, monthsPassed
};
3775 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, monthsPassedDuration
,
3776 &newRelativeTo
, &daysPassed
)) {
3779 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3782 fractionalDays
-= daysPassed
;
3785 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3788 auto oneMonth
= DateDuration
{0, sign
};
3791 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3792 int32_t oneMonthDays
;
3793 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneMonth
,
3794 &moveResultIgnored
, &oneMonthDays
)) {
3799 if (oneMonthDays
== 0) {
3800 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3801 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3806 auto fractionalMonths
= Fraction
{months
, std::abs(oneMonthDays
)};
3809 auto [numMonths
, total
] =
3810 RoundNumberToIncrement(fractionalMonths
, fractionalDays
, increment
,
3811 roundingMode
, computeRemainder
);
3814 int64_t numWeeks
= 0;
3817 constexpr auto time
= NormalizedTimeDuration
{};
3820 if (numMonths
.abs() >= (Uint128
{1} << 32)) {
3821 return ThrowInvalidDurationPart(cx
, double(numMonths
), "months",
3822 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3825 auto resultDuration
= DateDuration
{years
, int64_t(numMonths
), numWeeks
};
3826 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3830 *result
= {{resultDuration
, time
}, total
};
3834 static bool RoundDurationWeek(JSContext
* cx
, const NormalizedDuration
& duration
,
3835 FractionalDays fractionalDays
,
3836 Increment increment
,
3837 TemporalRoundingMode roundingMode
,
3838 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3839 Handle
<CalendarRecord
> calendar
,
3840 ComputeRemainder computeRemainder
,
3841 RoundedDuration
* result
) {
3843 // Numbers of days between nsMinInstant and nsMaxInstant.
3844 static constexpr int32_t epochDays
= 200'000'000;
3847 auto [years
, months
, weeks
, days
] = duration
.date
;
3849 auto* unwrappedRelativeTo
= dateRelativeTo
.unwrap(cx
);
3850 if (!unwrappedRelativeTo
) {
3853 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
3856 PlainDate isoResult
;
3857 if (!AddISODate(cx
, relativeToDate
, {0, 0, 0, fractionalDays
.truncate()},
3858 TemporalOverflow::Constrain
, &isoResult
)) {
3863 Rooted
<PlainDateObject
*> wholeDaysLater(
3864 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3865 if (!wholeDaysLater
) {
3870 DateDuration timePassed
;
3871 if (!DifferenceDate(cx
, calendar
, dateRelativeTo
, wholeDaysLater
,
3872 TemporalUnit::Week
, &timePassed
)) {
3877 int64_t weeksPassed
= timePassed
.weeks
;
3880 weeks
+= weeksPassed
;
3883 auto weeksPassedDuration
= DateDuration
{0, 0, weeksPassed
};
3886 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
);
3888 if (!MoveRelativeDate(cx
, calendar
, dateRelativeTo
, weeksPassedDuration
,
3889 &newRelativeTo
, &daysPassed
)) {
3892 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3895 fractionalDays
-= daysPassed
;
3898 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3901 auto oneWeek
= DateDuration
{0, 0, sign
};
3904 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3905 int32_t oneWeekDays
;
3906 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneWeek
,
3907 &moveResultIgnored
, &oneWeekDays
)) {
3912 if (oneWeekDays
== 0) {
3913 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3914 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3919 auto fractionalWeeks
= Fraction
{weeks
, std::abs(oneWeekDays
)};
3922 auto [numWeeks
, total
] =
3923 RoundNumberToIncrement(fractionalWeeks
, fractionalDays
, increment
,
3924 roundingMode
, computeRemainder
);
3927 constexpr auto time
= NormalizedTimeDuration
{};
3930 if (numWeeks
.abs() >= (Uint128
{1} << 32)) {
3931 return ThrowInvalidDurationPart(cx
, double(numWeeks
), "weeks",
3932 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3935 auto resultDuration
= DateDuration
{years
, months
, int64_t(numWeeks
)};
3936 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3940 *result
= {{resultDuration
, time
}, total
};
3944 static bool RoundDurationDay(JSContext
* cx
, const NormalizedDuration
& duration
,
3945 const FractionalDays
& fractionalDays
,
3946 Increment increment
,
3947 TemporalRoundingMode roundingMode
,
3948 ComputeRemainder computeRemainder
,
3949 RoundedDuration
* result
) {
3950 auto [years
, months
, weeks
, days
] = duration
.date
;
3952 // Pass zero fraction.
3953 constexpr auto zero
= Fraction
{0, 1};
3956 auto [numDays
, total
] = RoundNumberToIncrement(
3957 zero
, fractionalDays
, increment
, roundingMode
, computeRemainder
);
3959 MOZ_ASSERT(Int128
{INT64_MIN
} <= numDays
&& numDays
<= Int128
{INT64_MAX
},
3960 "rounded days fits in int64");
3963 constexpr auto time
= NormalizedTimeDuration
{};
3966 auto resultDuration
= DateDuration
{years
, months
, weeks
, int64_t(numDays
)};
3967 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3971 *result
= {{resultDuration
, time
}, total
};
3976 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3977 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3978 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3980 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
3981 Increment increment
, TemporalUnit unit
,
3982 TemporalRoundingMode roundingMode
,
3983 ComputeRemainder computeRemainder
,
3984 RoundedDuration
* result
) {
3985 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
3986 MOZ_ASSERT_IF(unit
> TemporalUnit::Day
, IsValidDuration(duration
.date
));
3988 // The remainder is only needed when called from |Duration_total|. And `total`
3989 // always passes |increment=1| and |roundingMode=trunc|.
3990 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3991 increment
== Increment
{1});
3992 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3993 roundingMode
== TemporalRoundingMode::Trunc
);
3995 // Steps 1-5. (Not applicable.)
3998 if (unit
<= TemporalUnit::Week
) {
3999 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4000 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
4005 // TODO: We could directly return here if unit=nanoseconds and increment=1,
4006 // because in that case this operation is a no-op. This case happens for
4007 // example when calling Temporal.PlainTime.prototype.{since,until} without an
4010 // But maybe this can be even more efficiently handled in the callers. For
4011 // example when Temporal.PlainTime.prototype.{since,until} is called without
4012 // an options object, we can not only skip the RoundDuration call, but also
4013 // the following BalanceTimeDuration call.
4015 // Step 7. (Moved below.)
4017 // Steps 8-9. (Not applicable.)
4019 // Steps 10-12. (Not applicable.)
4022 if (unit
== TemporalUnit::Day
) {
4024 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
4025 auto fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
4027 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
4028 roundingMode
, computeRemainder
, result
);
4031 MOZ_ASSERT(TemporalUnit::Hour
<= unit
&& unit
<= TemporalUnit::Nanosecond
);
4034 auto time
= duration
.time
;
4036 if (computeRemainder
== ComputeRemainder::No
) {
4037 if (!RoundNormalizedTimeDurationToIncrement(cx
, time
, unit
, increment
,
4038 roundingMode
, &time
)) {
4042 MOZ_ASSERT(increment
== Increment
{1});
4043 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4045 total
= TotalNormalizedTimeDuration(duration
.time
, unit
);
4047 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
4050 MOZ_ASSERT(IsValidDuration(duration
.date
));
4051 *result
= {{duration
.date
, time
}, total
};
4056 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4057 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4058 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4060 static bool RoundDuration(
4061 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4062 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4063 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4064 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4065 Handle
<TimeZoneRecord
> timeZone
,
4066 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4067 ComputeRemainder computeRemainder
, RoundedDuration
* result
) {
4068 // Note: |duration.days| can have a different sign than the other date
4069 // components. The date and time components can have different signs, too.
4070 MOZ_ASSERT(IsValidDuration(Duration
{double(duration
.date
.years
),
4071 double(duration
.date
.months
),
4072 double(duration
.date
.weeks
)}));
4073 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
4074 MOZ_ASSERT_IF(unit
> TemporalUnit::Day
, IsValidDuration(duration
.date
));
4076 MOZ_ASSERT(plainRelativeTo
|| zonedRelativeTo
,
4077 "Use RoundDuration without relativeTo when plainRelativeTo and "
4078 "zonedRelativeTo are both undefined");
4080 // The remainder is only needed when called from |Duration_total|. And `total`
4081 // always passes |increment=1| and |roundingMode=trunc|.
4082 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
4083 increment
== Increment
{1});
4084 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
4085 roundingMode
== TemporalRoundingMode::Trunc
);
4087 // Steps 1-5. (Not applicable in our implementation.)
4089 // Step 6.a. (Not applicable in our implementation.)
4090 MOZ_ASSERT_IF(unit
<= TemporalUnit::Week
, plainRelativeTo
);
4094 unit
<= TemporalUnit::Week
,
4095 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
4099 unit
<= TemporalUnit::Week
,
4100 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
4103 case TemporalUnit::Year
:
4104 case TemporalUnit::Month
:
4105 case TemporalUnit::Week
:
4107 case TemporalUnit::Day
:
4108 // We can't take the faster code path when |zonedRelativeTo| is present.
4109 if (zonedRelativeTo
) {
4113 case TemporalUnit::Hour
:
4114 case TemporalUnit::Minute
:
4115 case TemporalUnit::Second
:
4116 case TemporalUnit::Millisecond
:
4117 case TemporalUnit::Microsecond
:
4118 case TemporalUnit::Nanosecond
:
4119 // Steps 7-9 and 13-21.
4120 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4121 computeRemainder
, result
);
4122 case TemporalUnit::Auto
:
4123 MOZ_CRASH("Unexpected temporal unit");
4127 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
);
4130 FractionalDays fractionalDays
;
4131 if (zonedRelativeTo
) {
4133 Rooted
<ZonedDateTime
> intermediate(cx
);
4134 if (!MoveRelativeZonedDateTime(cx
, zonedRelativeTo
, calendar
, timeZone
,
4135 duration
.date
, precalculatedPlainDateTime
,
4141 NormalizedTimeAndDays timeAndDays
;
4142 if (!NormalizedTimeDurationToDays(cx
, duration
.time
, intermediate
, timeZone
,
4148 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
4151 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
4152 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
4155 // Step 7.c. (Moved below)
4157 // Step 8. (Not applicable)
4160 // FIXME: spec issue - `total` doesn't need be initialised.
4161 // https://github.com/tc39/proposal-temporal/issues/2784
4166 case TemporalUnit::Year
:
4167 return RoundDurationYear(cx
, duration
, fractionalDays
, increment
,
4168 roundingMode
, plainRelativeTo
, calendar
,
4169 computeRemainder
, result
);
4172 case TemporalUnit::Month
:
4173 return RoundDurationMonth(cx
, duration
, fractionalDays
, increment
,
4174 roundingMode
, plainRelativeTo
, calendar
,
4175 computeRemainder
, result
);
4178 case TemporalUnit::Week
:
4179 return RoundDurationWeek(cx
, duration
, fractionalDays
, increment
,
4180 roundingMode
, plainRelativeTo
, calendar
,
4181 computeRemainder
, result
);
4184 case TemporalUnit::Day
:
4185 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
4186 roundingMode
, computeRemainder
, result
);
4188 // Steps 14-19. (Handled elsewhere)
4189 case TemporalUnit::Auto
:
4190 case TemporalUnit::Hour
:
4191 case TemporalUnit::Minute
:
4192 case TemporalUnit::Second
:
4193 case TemporalUnit::Millisecond
:
4194 case TemporalUnit::Microsecond
:
4195 case TemporalUnit::Nanosecond
:
4199 MOZ_CRASH("Unexpected temporal unit");
4203 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4204 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4205 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4207 bool js::temporal::RoundDuration(
4208 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4209 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4210 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4211 Handle
<CalendarRecord
> calendar
, NormalizedDuration
* result
) {
4212 MOZ_ASSERT(IsValidDuration(duration
));
4214 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
, ZonedDateTime
{});
4215 Rooted
<TimeZoneRecord
> timeZone(cx
, TimeZoneRecord
{});
4216 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4217 RoundedDuration rounded
;
4218 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4219 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4220 precalculatedPlainDateTime
, ComputeRemainder::No
,
4225 *result
= rounded
.duration
;
4230 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4231 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4232 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4234 bool js::temporal::RoundDuration(
4235 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4236 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4237 Handle
<PlainDateObject
*> plainRelativeTo
, Handle
<CalendarRecord
> calendar
,
4238 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<TimeZoneRecord
> timeZone
,
4239 const PlainDateTime
& precalculatedPlainDateTime
,
4240 NormalizedDuration
* result
) {
4241 MOZ_ASSERT(IsValidDuration(duration
));
4243 RoundedDuration rounded
;
4244 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4245 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4246 mozilla::SomeRef(precalculatedPlainDateTime
),
4247 ComputeRemainder::No
, &rounded
)) {
4251 *result
= rounded
.duration
;
4255 enum class DurationOperation
{ Add
, Subtract
};
4258 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4261 static bool AddDurationToOrSubtractDurationFromDuration(
4262 JSContext
* cx
, DurationOperation operation
, const CallArgs
& args
) {
4263 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4264 auto duration
= ToDuration(durationObj
);
4266 // Step 1. (Not applicable in our implementation.)
4270 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
4274 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4275 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4276 Rooted
<TimeZoneRecord
> timeZone(cx
);
4277 if (args
.hasDefined(1)) {
4278 const char* name
= operation
== DurationOperation::Add
? "add" : "subtract";
4281 Rooted
<JSObject
*> options(cx
,
4282 RequireObjectArg(cx
, "options", name
, args
[1]));
4288 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4289 &zonedRelativeTo
, &timeZone
)) {
4292 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4293 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4297 Rooted
<CalendarRecord
> calendar(cx
);
4298 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4301 CalendarMethod::DateAdd
,
4302 CalendarMethod::DateUntil
,
4309 if (operation
== DurationOperation::Subtract
) {
4310 other
= other
.negate();
4314 if (plainRelativeTo
) {
4315 if (!AddDuration(cx
, duration
, other
, plainRelativeTo
, calendar
, &result
)) {
4318 } else if (zonedRelativeTo
) {
4319 if (!AddDuration(cx
, duration
, other
, zonedRelativeTo
, calendar
, timeZone
,
4324 if (!AddDuration(cx
, duration
, other
, &result
)) {
4330 auto* obj
= CreateTemporalDuration(cx
, result
);
4335 args
.rval().setObject(*obj
);
4340 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4341 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4344 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4345 CallArgs args
= CallArgsFromVp(argc
, vp
);
4348 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
4354 if (args
.hasDefined(0) &&
4355 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
4361 if (args
.hasDefined(1) &&
4362 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
4368 if (args
.hasDefined(2) &&
4369 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
4375 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
4381 if (args
.hasDefined(4) &&
4382 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
4388 if (args
.hasDefined(5) &&
4389 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
4395 if (args
.hasDefined(6) &&
4396 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
4401 double milliseconds
= 0;
4402 if (args
.hasDefined(7) &&
4403 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
4408 double microseconds
= 0;
4409 if (args
.hasDefined(8) &&
4410 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
4415 double nanoseconds
= 0;
4416 if (args
.hasDefined(9) &&
4417 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
4422 auto* duration
= CreateTemporalDuration(
4424 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
4425 microseconds
, nanoseconds
});
4430 args
.rval().setObject(*duration
);
4435 * Temporal.Duration.from ( item )
4437 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4438 CallArgs args
= CallArgsFromVp(argc
, vp
);
4440 Handle
<Value
> item
= args
.get(0);
4443 if (item
.isObject()) {
4444 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
4445 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
4450 args
.rval().setObject(*result
);
4456 auto result
= ToTemporalDuration(cx
, item
);
4461 args
.rval().setObject(*result
);
4466 * Temporal.Duration.compare ( one, two [ , options ] )
4468 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4469 CallArgs args
= CallArgsFromVp(argc
, vp
);
4473 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
4479 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
4484 Rooted
<JSObject
*> options(cx
);
4485 if (args
.hasDefined(2)) {
4486 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
4494 args
.rval().setInt32(0);
4499 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4500 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4501 Rooted
<TimeZoneRecord
> timeZone(cx
);
4503 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4504 &zonedRelativeTo
, &timeZone
)) {
4507 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4508 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4512 auto hasCalendarUnit
= [](const auto& d
) {
4513 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
4515 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
4518 Rooted
<CalendarRecord
> calendar(cx
);
4519 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4522 CalendarMethod::DateAdd
,
4529 if (zonedRelativeTo
&&
4530 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
4532 const auto& instant
= zonedRelativeTo
.instant();
4535 PlainDateTime dateTime
;
4536 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
4541 auto normalized1
= CreateNormalizedDurationRecord(one
);
4544 auto normalized2
= CreateNormalizedDurationRecord(two
);
4548 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
4549 dateTime
, &after1
)) {
4555 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
4556 dateTime
, &after2
)) {
4561 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
4566 int64_t days1
, days2
;
4567 if (calendarUnitsPresent
) {
4568 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4571 DateDuration unbalanceResult1
;
4572 if (plainRelativeTo
) {
4573 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
4574 TemporalUnit::Day
, plainRelativeTo
,
4575 calendar
, &unbalanceResult1
)) {
4579 if (!UnbalanceDateDurationRelative(
4580 cx
, one
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult1
)) {
4583 MOZ_ASSERT(one
.toDateDuration() == unbalanceResult1
);
4587 DateDuration unbalanceResult2
;
4588 if (plainRelativeTo
) {
4589 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
4590 TemporalUnit::Day
, plainRelativeTo
,
4591 calendar
, &unbalanceResult2
)) {
4595 if (!UnbalanceDateDurationRelative(
4596 cx
, two
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult2
)) {
4599 MOZ_ASSERT(two
.toDateDuration() == unbalanceResult2
);
4603 days1
= unbalanceResult1
.days
;
4606 days2
= unbalanceResult2
.days
;
4609 days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
4612 days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
4616 auto normalized1
= NormalizeTimeDuration(one
);
4619 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
4625 auto normalized2
= NormalizeTimeDuration(two
);
4628 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
4634 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
4639 * get Temporal.Duration.prototype.years
4641 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
4643 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4644 args
.rval().setNumber(duration
->years());
4649 * get Temporal.Duration.prototype.years
4651 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4653 CallArgs args
= CallArgsFromVp(argc
, vp
);
4654 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
4658 * get Temporal.Duration.prototype.months
4660 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
4662 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4663 args
.rval().setNumber(duration
->months());
4668 * get Temporal.Duration.prototype.months
4670 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4672 CallArgs args
= CallArgsFromVp(argc
, vp
);
4673 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
4677 * get Temporal.Duration.prototype.weeks
4679 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
4681 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4682 args
.rval().setNumber(duration
->weeks());
4687 * get Temporal.Duration.prototype.weeks
4689 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4691 CallArgs args
= CallArgsFromVp(argc
, vp
);
4692 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
4696 * get Temporal.Duration.prototype.days
4698 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
4700 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4701 args
.rval().setNumber(duration
->days());
4706 * get Temporal.Duration.prototype.days
4708 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4710 CallArgs args
= CallArgsFromVp(argc
, vp
);
4711 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
4715 * get Temporal.Duration.prototype.hours
4717 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
4719 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4720 args
.rval().setNumber(duration
->hours());
4725 * get Temporal.Duration.prototype.hours
4727 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4729 CallArgs args
= CallArgsFromVp(argc
, vp
);
4730 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
4734 * get Temporal.Duration.prototype.minutes
4736 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
4738 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4739 args
.rval().setNumber(duration
->minutes());
4744 * get Temporal.Duration.prototype.minutes
4746 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4748 CallArgs args
= CallArgsFromVp(argc
, vp
);
4749 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
4753 * get Temporal.Duration.prototype.seconds
4755 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
4757 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4758 args
.rval().setNumber(duration
->seconds());
4763 * get Temporal.Duration.prototype.seconds
4765 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4767 CallArgs args
= CallArgsFromVp(argc
, vp
);
4768 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
4772 * get Temporal.Duration.prototype.milliseconds
4774 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
4776 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4777 args
.rval().setNumber(duration
->milliseconds());
4782 * get Temporal.Duration.prototype.milliseconds
4784 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4786 CallArgs args
= CallArgsFromVp(argc
, vp
);
4787 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
4791 * get Temporal.Duration.prototype.microseconds
4793 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
4795 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4796 args
.rval().setNumber(duration
->microseconds());
4801 * get Temporal.Duration.prototype.microseconds
4803 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4805 CallArgs args
= CallArgsFromVp(argc
, vp
);
4806 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
4810 * get Temporal.Duration.prototype.nanoseconds
4812 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
4814 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4815 args
.rval().setNumber(duration
->nanoseconds());
4820 * get Temporal.Duration.prototype.nanoseconds
4822 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4824 CallArgs args
= CallArgsFromVp(argc
, vp
);
4825 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
4829 * get Temporal.Duration.prototype.sign
4831 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
4832 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4835 args
.rval().setInt32(DurationSign(duration
));
4840 * get Temporal.Duration.prototype.sign
4842 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4844 CallArgs args
= CallArgsFromVp(argc
, vp
);
4845 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
4849 * get Temporal.Duration.prototype.blank
4851 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
4852 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4855 args
.rval().setBoolean(duration
== Duration
{});
4860 * get Temporal.Duration.prototype.blank
4862 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4864 CallArgs args
= CallArgsFromVp(argc
, vp
);
4865 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
4869 * Temporal.Duration.prototype.with ( temporalDurationLike )
4871 * ToPartialDuration ( temporalDurationLike )
4873 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
4874 // Absent values default to the corresponding values of |this| object.
4875 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4878 Rooted
<JSObject
*> temporalDurationLike(
4879 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
4880 if (!temporalDurationLike
) {
4883 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
4888 auto* result
= CreateTemporalDuration(cx
, duration
);
4893 args
.rval().setObject(*result
);
4898 * Temporal.Duration.prototype.with ( temporalDurationLike )
4900 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4902 CallArgs args
= CallArgsFromVp(argc
, vp
);
4903 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
4907 * Temporal.Duration.prototype.negated ( )
4909 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
4910 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4913 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
4918 args
.rval().setObject(*result
);
4923 * Temporal.Duration.prototype.negated ( )
4925 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4927 CallArgs args
= CallArgsFromVp(argc
, vp
);
4928 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
4932 * Temporal.Duration.prototype.abs ( )
4934 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
4935 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4938 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
4943 args
.rval().setObject(*result
);
4948 * Temporal.Duration.prototype.abs ( )
4950 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4952 CallArgs args
= CallArgsFromVp(argc
, vp
);
4953 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
4957 * Temporal.Duration.prototype.add ( other [ , options ] )
4959 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
4960 return AddDurationToOrSubtractDurationFromDuration(cx
, DurationOperation::Add
,
4965 * Temporal.Duration.prototype.add ( other [ , options ] )
4967 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4969 CallArgs args
= CallArgsFromVp(argc
, vp
);
4970 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
4974 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4976 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
4977 return AddDurationToOrSubtractDurationFromDuration(
4978 cx
, DurationOperation::Subtract
, args
);
4982 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4984 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4986 CallArgs args
= CallArgsFromVp(argc
, vp
);
4987 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
4991 * Temporal.Duration.prototype.round ( roundTo )
4993 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4994 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4996 // Step 18. (Reordered)
4997 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
5000 auto smallestUnit
= TemporalUnit::Auto
;
5001 TemporalUnit largestUnit
;
5002 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
5003 auto roundingIncrement
= Increment
{1};
5004 Rooted
<JSObject
*> relativeTo(cx
);
5005 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5006 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5007 Rooted
<TimeZoneRecord
> timeZone(cx
);
5008 if (args
.get(0).isString()) {
5009 // Step 4. (Not applicable in our implementation.)
5011 // Steps 6-15. (Not applicable)
5014 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5015 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
5016 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5020 // Step 17. (Not applicable)
5022 // Step 18. (Moved above)
5025 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5027 // Step 20. (Not applicable)
5029 // Step 20.a. (Not applicable)
5032 largestUnit
= defaultLargestUnit
;
5034 // Steps 21-25. (Not applicable)
5037 Rooted
<JSObject
*> options(
5038 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
5044 bool smallestUnitPresent
= true;
5047 bool largestUnitPresent
= true;
5051 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
5052 // absent "largestUnit" value.
5053 Rooted
<Value
> largestUnitValue(cx
);
5054 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
5055 &largestUnitValue
)) {
5059 if (!largestUnitValue
.isUndefined()) {
5060 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
5061 if (!largestUnitStr
) {
5065 largestUnit
= TemporalUnit::Auto
;
5066 if (!GetTemporalUnit(cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
5067 TemporalUnitGroup::DateTime
, &largestUnit
)) {
5073 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
5074 &zonedRelativeTo
, &timeZone
)) {
5077 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5078 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5081 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
5086 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5091 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5092 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5097 if (smallestUnit
== TemporalUnit::Auto
) {
5099 smallestUnitPresent
= false;
5102 smallestUnit
= TemporalUnit::Nanosecond
;
5105 // Step 18. (Moved above)
5108 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5111 if (largestUnitValue
.isUndefined()) {
5113 largestUnitPresent
= false;
5116 largestUnit
= defaultLargestUnit
;
5117 } else if (largestUnit
== TemporalUnit::Auto
) {
5119 largestUnit
= defaultLargestUnit
;
5123 if (!smallestUnitPresent
&& !largestUnitPresent
) {
5124 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5125 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
5130 if (largestUnit
> smallestUnit
) {
5131 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5132 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
5137 if (smallestUnit
> TemporalUnit::Day
) {
5139 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
5142 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
5150 bool hoursToDaysConversionMayOccur
= false;
5153 if (duration
.days
!= 0 && zonedRelativeTo
) {
5154 hoursToDaysConversionMayOccur
= true;
5158 else if (std::abs(duration
.hours
) >= 24) {
5159 hoursToDaysConversionMayOccur
= true;
5163 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
5164 roundingIncrement
== Increment
{1};
5167 bool calendarUnitsPresent
=
5168 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
5171 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
5172 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
5173 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
5174 std::abs(duration
.milliseconds
) < 1000 &&
5175 std::abs(duration
.microseconds
) < 1000 &&
5176 std::abs(duration
.nanoseconds
) < 1000) {
5178 auto* obj
= CreateTemporalDuration(cx
, duration
);
5183 args
.rval().setObject(*obj
);
5188 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5191 bool plainDateTimeOrRelativeToWillBeUsed
=
5192 !roundingGranularityIsNoop
|| largestUnit
<= TemporalUnit::Day
||
5193 calendarUnitsPresent
|| duration
.days
!= 0;
5196 PlainDateTime relativeToDateTime
;
5197 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5199 const auto& instant
= zonedRelativeTo
.instant();
5202 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5205 precalculatedPlainDateTime
=
5206 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5209 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5210 zonedRelativeTo
.calendar());
5211 if (!plainRelativeTo
) {
5217 Rooted
<CalendarRecord
> calendar(cx
);
5218 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5221 CalendarMethod::DateAdd
,
5222 CalendarMethod::DateUntil
,
5229 DateDuration unbalanceResult
;
5230 if (plainRelativeTo
) {
5231 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5232 largestUnit
, plainRelativeTo
, calendar
,
5233 &unbalanceResult
)) {
5237 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5238 largestUnit
, &unbalanceResult
)) {
5241 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5243 MOZ_ASSERT(IsValidDuration(unbalanceResult
));
5247 NormalizedDuration
{unbalanceResult
, NormalizeTimeDuration(duration
)};
5248 RoundedDuration rounded
;
5249 if (plainRelativeTo
|| zonedRelativeTo
) {
5250 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5251 roundingMode
, plainRelativeTo
, calendar
,
5252 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5253 ComputeRemainder::No
, &rounded
)) {
5257 MOZ_ASSERT(IsValidDuration(roundInput
));
5259 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5260 roundingMode
, ComputeRemainder::No
, &rounded
)) {
5266 auto roundResult
= rounded
.duration
;
5269 TimeDuration balanceResult
;
5270 if (zonedRelativeTo
) {
5272 NormalizedDuration adjustResult
;
5273 if (!AdjustRoundedDurationDays(cx
, roundResult
, roundingIncrement
,
5274 smallestUnit
, roundingMode
, zonedRelativeTo
,
5276 precalculatedPlainDateTime
, &adjustResult
)) {
5279 roundResult
= adjustResult
;
5282 if (!BalanceTimeDurationRelative(
5283 cx
, roundResult
, largestUnit
, zonedRelativeTo
, timeZone
,
5284 precalculatedPlainDateTime
, &balanceResult
)) {
5289 NormalizedTimeDuration withDays
;
5290 if (!Add24HourDaysToNormalizedTimeDuration(
5291 cx
, roundResult
.time
, roundResult
.date
.days
, &withDays
)) {
5296 if (!temporal::BalanceTimeDuration(cx
, withDays
, largestUnit
,
5303 auto balanceInput
= DateDuration
{
5304 roundResult
.date
.years
,
5305 roundResult
.date
.months
,
5306 roundResult
.date
.weeks
,
5309 DateDuration dateResult
;
5310 if (!::BalanceDateDurationRelative(cx
, balanceInput
, largestUnit
,
5311 smallestUnit
, plainRelativeTo
, calendar
,
5317 auto result
= Duration
{
5318 double(dateResult
.years
), double(dateResult
.months
),
5319 double(dateResult
.weeks
), double(dateResult
.days
),
5320 double(balanceResult
.hours
), double(balanceResult
.minutes
),
5321 double(balanceResult
.seconds
), double(balanceResult
.milliseconds
),
5322 balanceResult
.microseconds
, balanceResult
.nanoseconds
,
5325 auto* obj
= CreateTemporalDuration(cx
, result
);
5330 args
.rval().setObject(*obj
);
5335 * Temporal.Duration.prototype.round ( options )
5337 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5339 CallArgs args
= CallArgsFromVp(argc
, vp
);
5340 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
5344 * Temporal.Duration.prototype.total ( totalOf )
5346 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
5347 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
5348 auto duration
= ToDuration(durationObj
);
5351 Rooted
<JSObject
*> relativeTo(cx
);
5352 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5353 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5354 Rooted
<TimeZoneRecord
> timeZone(cx
);
5355 auto unit
= TemporalUnit::Auto
;
5356 if (args
.get(0).isString()) {
5357 // Step 4. (Not applicable in our implementation.)
5359 // Steps 6-10. (Implicit)
5360 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
5363 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5364 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::Unit
,
5365 TemporalUnitGroup::DateTime
, &unit
)) {
5370 Rooted
<JSObject
*> totalOf(
5371 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
5377 if (!ToRelativeTemporalObject(cx
, totalOf
, &plainRelativeTo
,
5378 &zonedRelativeTo
, &timeZone
)) {
5381 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5382 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5385 if (!GetTemporalUnit(cx
, totalOf
, TemporalUnitKey::Unit
,
5386 TemporalUnitGroup::DateTime
, &unit
)) {
5390 if (unit
== TemporalUnit::Auto
) {
5391 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5392 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
5398 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5401 bool plainDateTimeOrRelativeToWillBeUsed
=
5402 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
5405 PlainDateTime relativeToDateTime
;
5406 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5408 const auto& instant
= zonedRelativeTo
.instant();
5411 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5414 precalculatedPlainDateTime
=
5415 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5418 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5419 zonedRelativeTo
.calendar());
5420 if (!plainRelativeTo
) {
5426 Rooted
<CalendarRecord
> calendar(cx
);
5427 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5430 CalendarMethod::DateAdd
,
5431 CalendarMethod::DateUntil
,
5438 DateDuration unbalanceResult
;
5439 if (plainRelativeTo
) {
5440 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5441 plainRelativeTo
, calendar
,
5442 &unbalanceResult
)) {
5446 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5447 &unbalanceResult
)) {
5450 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5454 int64_t unbalancedDays
= unbalanceResult
.days
;
5458 NormalizedTimeDuration normTime
;
5459 if (zonedRelativeTo
) {
5461 Rooted
<ZonedDateTime
> intermediate(cx
);
5462 if (!MoveRelativeZonedDateTime(
5463 cx
, zonedRelativeTo
, calendar
, timeZone
,
5464 {unbalanceResult
.years
, unbalanceResult
.months
,
5465 unbalanceResult
.weeks
, 0},
5466 precalculatedPlainDateTime
, &intermediate
)) {
5471 auto timeDuration
= NormalizeTimeDuration(duration
);
5474 const auto& startNs
= intermediate
.instant();
5477 const auto& startInstant
= startNs
;
5480 mozilla::Maybe
<PlainDateTime
> startDateTime
{};
5483 Instant intermediateNs
;
5484 if (unbalancedDays
!= 0) {
5486 PlainDateTime dateTime
;
5487 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5490 startDateTime
= mozilla::Some(dateTime
);
5493 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
5495 if (!AddDaysToZonedDateTime(cx
, startInstant
, dateTime
, timeZone
,
5496 isoCalendar
, unbalancedDays
, &addResult
)) {
5501 intermediateNs
= addResult
;
5504 intermediateNs
= startNs
;
5509 if (!AddInstant(cx
, intermediateNs
, timeDuration
, &endNs
)) {
5515 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startNs
);
5519 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5520 if (TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
&&
5521 difference
!= NormalizedTimeDuration
{}) {
5523 if (!startDateTime
) {
5524 PlainDateTime dateTime
;
5525 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5528 startDateTime
= mozilla::Some(dateTime
);
5532 NormalizedTimeAndDays timeAndDays
;
5533 if (!NormalizedTimeDurationToDays(cx
, difference
, intermediate
, timeZone
,
5534 *startDateTime
, &timeAndDays
)) {
5539 normTime
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
5542 days
= timeAndDays
.days
;
5545 normTime
= difference
;
5550 auto timeDuration
= NormalizeTimeDuration(duration
);
5553 if (!Add24HourDaysToNormalizedTimeDuration(cx
, timeDuration
, unbalancedDays
,
5561 MOZ_ASSERT(IsValidNormalizedTimeDuration(normTime
));
5564 auto roundInput
= NormalizedDuration
{
5566 unbalanceResult
.years
,
5567 unbalanceResult
.months
,
5568 unbalanceResult
.weeks
,
5573 MOZ_ASSERT_IF(unit
> TemporalUnit::Day
, IsValidDuration(roundInput
.date
));
5575 RoundedDuration rounded
;
5576 if (plainRelativeTo
|| zonedRelativeTo
) {
5577 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5578 TemporalRoundingMode::Trunc
, plainRelativeTo
, calendar
,
5579 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5580 ComputeRemainder::Yes
, &rounded
)) {
5584 MOZ_ASSERT(IsValidDuration(roundInput
));
5586 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5587 TemporalRoundingMode::Trunc
, ComputeRemainder::Yes
,
5594 args
.rval().setNumber(rounded
.total
);
5599 * Temporal.Duration.prototype.total ( totalOf )
5601 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5603 CallArgs args
= CallArgsFromVp(argc
, vp
);
5604 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
5608 * Temporal.Duration.prototype.toString ( [ options ] )
5610 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
5611 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5614 SecondsStringPrecision precision
= {Precision::Auto(),
5615 TemporalUnit::Nanosecond
, Increment
{1}};
5616 auto roundingMode
= TemporalRoundingMode::Trunc
;
5617 if (args
.hasDefined(0)) {
5619 Rooted
<JSObject
*> options(
5620 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
5626 auto digits
= Precision::Auto();
5627 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
5632 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5637 auto smallestUnit
= TemporalUnit::Auto
;
5638 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5639 TemporalUnitGroup::Time
, &smallestUnit
)) {
5644 if (smallestUnit
== TemporalUnit::Hour
||
5645 smallestUnit
== TemporalUnit::Minute
) {
5646 const char* smallestUnitStr
=
5647 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
5648 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5649 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
5650 smallestUnitStr
, "smallestUnit");
5655 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
5660 if (precision
.unit
!= TemporalUnit::Nanosecond
||
5661 precision
.increment
!= Increment
{1}) {
5663 auto timeDuration
= NormalizeTimeDuration(duration
);
5666 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
5669 NormalizedTimeDuration rounded
;
5670 if (!RoundDuration(cx
, timeDuration
, precision
.increment
, precision
.unit
,
5671 roundingMode
, &rounded
)) {
5676 auto balanced
= BalanceTimeDuration(
5677 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
5681 duration
.years
, duration
.months
,
5682 duration
.weeks
, duration
.days
+ double(balanced
.days
),
5683 double(balanced
.hours
), double(balanced
.minutes
),
5684 double(balanced
.seconds
), double(balanced
.milliseconds
),
5685 balanced
.microseconds
, balanced
.nanoseconds
,
5687 MOZ_ASSERT(IsValidDuration(duration
));
5694 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
5699 args
.rval().setString(str
);
5704 * Temporal.Duration.prototype.toString ( [ options ] )
5706 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5708 CallArgs args
= CallArgsFromVp(argc
, vp
);
5709 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
5713 * Temporal.Duration.prototype.toJSON ( )
5715 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
5716 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5719 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5724 args
.rval().setString(str
);
5729 * Temporal.Duration.prototype.toJSON ( )
5731 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5733 CallArgs args
= CallArgsFromVp(argc
, vp
);
5734 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
5738 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5740 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
5741 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5744 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5749 args
.rval().setString(str
);
5754 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5756 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5758 CallArgs args
= CallArgsFromVp(argc
, vp
);
5759 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
5763 * Temporal.Duration.prototype.valueOf ( )
5765 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5766 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
5767 "Duration", "primitive type");
5771 const JSClass
DurationObject::class_
= {
5772 "Temporal.Duration",
5773 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
5774 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
5776 &DurationObject::classSpec_
,
5779 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
5781 static const JSFunctionSpec Duration_methods
[] = {
5782 JS_FN("from", Duration_from
, 1, 0),
5783 JS_FN("compare", Duration_compare
, 2, 0),
5787 static const JSFunctionSpec Duration_prototype_methods
[] = {
5788 JS_FN("with", Duration_with
, 1, 0),
5789 JS_FN("negated", Duration_negated
, 0, 0),
5790 JS_FN("abs", Duration_abs
, 0, 0),
5791 JS_FN("add", Duration_add
, 1, 0),
5792 JS_FN("subtract", Duration_subtract
, 1, 0),
5793 JS_FN("round", Duration_round
, 1, 0),
5794 JS_FN("total", Duration_total
, 1, 0),
5795 JS_FN("toString", Duration_toString
, 0, 0),
5796 JS_FN("toJSON", Duration_toJSON
, 0, 0),
5797 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
5798 JS_FN("valueOf", Duration_valueOf
, 0, 0),
5802 static const JSPropertySpec Duration_prototype_properties
[] = {
5803 JS_PSG("years", Duration_years
, 0),
5804 JS_PSG("months", Duration_months
, 0),
5805 JS_PSG("weeks", Duration_weeks
, 0),
5806 JS_PSG("days", Duration_days
, 0),
5807 JS_PSG("hours", Duration_hours
, 0),
5808 JS_PSG("minutes", Duration_minutes
, 0),
5809 JS_PSG("seconds", Duration_seconds
, 0),
5810 JS_PSG("milliseconds", Duration_milliseconds
, 0),
5811 JS_PSG("microseconds", Duration_microseconds
, 0),
5812 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
5813 JS_PSG("sign", Duration_sign
, 0),
5814 JS_PSG("blank", Duration_blank
, 0),
5815 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
5819 const ClassSpec
DurationObject::classSpec_
= {
5820 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
5821 GenericCreatePrototype
<DurationObject
>,
5824 Duration_prototype_methods
,
5825 Duration_prototype_properties
,
5827 ClassSpec::DontDefineConstructor
,