1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/Duration.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/EnumSet.h"
12 #include "mozilla/FloatingPoint.h"
13 #include "mozilla/Maybe.h"
18 #include <initializer_list>
20 #include <type_traits>
25 #include "NamespaceImports.h"
27 #include "builtin/temporal/Calendar.h"
28 #include "builtin/temporal/Instant.h"
29 #include "builtin/temporal/Int128.h"
30 #include "builtin/temporal/Int96.h"
31 #include "builtin/temporal/PlainDate.h"
32 #include "builtin/temporal/PlainDateTime.h"
33 #include "builtin/temporal/Temporal.h"
34 #include "builtin/temporal/TemporalFields.h"
35 #include "builtin/temporal/TemporalParser.h"
36 #include "builtin/temporal/TemporalRoundingMode.h"
37 #include "builtin/temporal/TemporalTypes.h"
38 #include "builtin/temporal/TemporalUnit.h"
39 #include "builtin/temporal/TimeZone.h"
40 #include "builtin/temporal/Wrapped.h"
41 #include "builtin/temporal/ZonedDateTime.h"
42 #include "gc/AllocKind.h"
43 #include "gc/Barrier.h"
44 #include "gc/GCEnum.h"
45 #include "js/CallArgs.h"
46 #include "js/CallNonGenericMethod.h"
48 #include "js/Conversions.h"
49 #include "js/ErrorReport.h"
50 #include "js/friend/ErrorMessages.h"
51 #include "js/GCVector.h"
53 #include "js/Printer.h"
54 #include "js/PropertyDescriptor.h"
55 #include "js/PropertySpec.h"
56 #include "js/RootingAPI.h"
58 #include "util/StringBuffer.h"
59 #include "vm/BytecodeUtil.h"
60 #include "vm/GlobalObject.h"
61 #include "vm/JSAtomState.h"
62 #include "vm/JSContext.h"
63 #include "vm/JSObject.h"
64 #include "vm/ObjectOperations.h"
65 #include "vm/PlainObject.h"
66 #include "vm/StringType.h"
68 #include "vm/JSObject-inl.h"
69 #include "vm/NativeObject-inl.h"
70 #include "vm/ObjectOperations-inl.h"
73 using namespace js::temporal
;
75 static inline bool IsDuration(Handle
<Value
> v
) {
76 return v
.isObject() && v
.toObject().is
<DurationObject
>();
80 static bool IsIntegerOrInfinity(double d
) {
81 return IsInteger(d
) || std::isinf(d
);
84 static bool IsIntegerOrInfinityDuration(const Duration
& duration
) {
85 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
86 microseconds
, nanoseconds
] = duration
;
88 // Integers exceeding the Number range are represented as infinity.
90 return IsIntegerOrInfinity(years
) && IsIntegerOrInfinity(months
) &&
91 IsIntegerOrInfinity(weeks
) && IsIntegerOrInfinity(days
) &&
92 IsIntegerOrInfinity(hours
) && IsIntegerOrInfinity(minutes
) &&
93 IsIntegerOrInfinity(seconds
) && IsIntegerOrInfinity(milliseconds
) &&
94 IsIntegerOrInfinity(microseconds
) && IsIntegerOrInfinity(nanoseconds
);
97 static bool IsIntegerDuration(const Duration
& duration
) {
98 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
99 microseconds
, nanoseconds
] = duration
;
101 return IsInteger(years
) && IsInteger(months
) && IsInteger(weeks
) &&
102 IsInteger(days
) && IsInteger(hours
) && IsInteger(minutes
) &&
103 IsInteger(seconds
) && IsInteger(milliseconds
) &&
104 IsInteger(microseconds
) && IsInteger(nanoseconds
);
108 static constexpr bool IsSafeInteger(int64_t x
) {
109 constexpr int64_t MaxSafeInteger
= int64_t(1) << 53;
110 constexpr int64_t MinSafeInteger
= -MaxSafeInteger
;
111 return MinSafeInteger
< x
&& x
< MaxSafeInteger
;
114 static constexpr bool IsSafeInteger(const Int128
& x
) {
115 constexpr Int128 MaxSafeInteger
= Int128
{int64_t(1) << 53};
116 constexpr Int128 MinSafeInteger
= -MaxSafeInteger
;
117 return MinSafeInteger
< x
&& x
< MaxSafeInteger
;
121 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
122 * milliseconds, microseconds, nanoseconds )
124 int32_t js::temporal::DurationSign(const Duration
& duration
) {
125 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
127 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
128 microseconds
, nanoseconds
] = duration
;
131 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
132 milliseconds
, microseconds
, nanoseconds
}) {
149 * Normalize a nanoseconds amount into a time duration.
151 static NormalizedTimeDuration
NormalizeNanoseconds(const Int96
& nanoseconds
) {
152 // Split into seconds and nanoseconds.
153 auto [seconds
, nanos
] = nanoseconds
/ ToNanoseconds(TemporalUnit::Second
);
155 return {seconds
, nanos
};
159 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
160 * value is too large.
162 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeNanoseconds(
163 double nanoseconds
) {
164 MOZ_ASSERT(IsInteger(nanoseconds
));
166 if (auto int96
= Int96::fromInteger(nanoseconds
)) {
167 // The number of normalized seconds must not exceed `2**53 - 1`.
168 constexpr auto limit
=
169 Int96
{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second
);
171 if (int96
->abs() < limit
) {
172 return mozilla::Some(NormalizeNanoseconds(*int96
));
175 return mozilla::Nothing();
179 * Normalize a microseconds amount into a time duration.
181 static NormalizedTimeDuration
NormalizeMicroseconds(const Int96
& microseconds
) {
182 // Split into seconds and microseconds.
183 auto [seconds
, micros
] = microseconds
/ ToMicroseconds(TemporalUnit::Second
);
185 // Scale microseconds to nanoseconds.
186 int32_t nanos
= micros
* ToNanoseconds(TemporalUnit::Microsecond
);
188 return {seconds
, nanos
};
192 * Normalize a microseconds amount into a time duration. Return Nothing if the
193 * value is too large.
195 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeMicroseconds(
196 double microseconds
) {
197 MOZ_ASSERT(IsInteger(microseconds
));
199 if (auto int96
= Int96::fromInteger(microseconds
)) {
200 // The number of normalized seconds must not exceed `2**53 - 1`.
201 constexpr auto limit
=
202 Int96
{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second
);
204 if (int96
->abs() < limit
) {
205 return mozilla::Some(NormalizeMicroseconds(*int96
));
208 return mozilla::Nothing();
212 * Normalize a duration into a time duration. Return Nothing if any duration
213 * value is too large.
215 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeSeconds(
216 const Duration
& duration
) {
218 auto nanoseconds
= NormalizeNanoseconds(duration
.nanoseconds
);
222 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds
));
224 auto microseconds
= NormalizeMicroseconds(duration
.microseconds
);
228 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds
));
230 // Overflows for millis/seconds/minutes/hours/days always result in an
231 // invalid normalized time duration.
233 int64_t milliseconds
;
234 if (!mozilla::NumberEqualsInt64(duration
.milliseconds
, &milliseconds
)) {
239 if (!mozilla::NumberEqualsInt64(duration
.seconds
, &seconds
)) {
244 if (!mozilla::NumberEqualsInt64(duration
.minutes
, &minutes
)) {
249 if (!mozilla::NumberEqualsInt64(duration
.hours
, &hours
)) {
254 if (!mozilla::NumberEqualsInt64(duration
.days
, &days
)) {
258 // Compute the overall amount of milliseconds.
259 mozilla::CheckedInt64 millis
= days
;
267 millis
+= milliseconds
;
268 if (!millis
.isValid()) {
272 auto milli
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
273 if (!IsValidNormalizedTimeDuration(milli
)) {
277 // Compute the overall time duration.
278 auto result
= milli
+ *microseconds
+ *nanoseconds
;
279 if (!IsValidNormalizedTimeDuration(result
)) {
283 return mozilla::Some(result
);
286 return mozilla::Nothing();
290 * Normalize a days amount into a time duration. Return Nothing if the value is
293 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeDays(double days
) {
294 MOZ_ASSERT(IsInteger(days
));
298 if (!mozilla::NumberEqualsInt64(days
, &intDays
)) {
302 // Compute the overall amount of milliseconds.
304 mozilla::CheckedInt64(intDays
) * ToMilliseconds(TemporalUnit::Day
);
305 if (!millis
.isValid()) {
309 auto result
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
310 if (!IsValidNormalizedTimeDuration(result
)) {
314 return mozilla::Some(result
);
317 return mozilla::Nothing();
321 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
324 static NormalizedTimeDuration
NormalizeTimeDuration(
325 double hours
, double minutes
, double seconds
, double milliseconds
,
326 double microseconds
, double nanoseconds
) {
327 MOZ_ASSERT(IsInteger(hours
));
328 MOZ_ASSERT(IsInteger(minutes
));
329 MOZ_ASSERT(IsInteger(seconds
));
330 MOZ_ASSERT(IsInteger(milliseconds
));
331 MOZ_ASSERT(IsInteger(microseconds
));
332 MOZ_ASSERT(IsInteger(nanoseconds
));
335 mozilla::CheckedInt64 millis
= int64_t(hours
);
337 millis
+= int64_t(minutes
);
339 millis
+= int64_t(seconds
);
341 millis
+= int64_t(milliseconds
);
342 MOZ_ASSERT(millis
.isValid());
344 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
347 auto micros
= Int96::fromInteger(microseconds
);
350 normalized
+= NormalizeMicroseconds(*micros
);
353 auto nanos
= Int96::fromInteger(nanoseconds
);
356 normalized
+= NormalizeNanoseconds(*nanos
);
359 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
366 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
369 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
370 int32_t hours
, int32_t minutes
, int32_t seconds
, int32_t milliseconds
,
371 int32_t microseconds
, int32_t nanoseconds
) {
373 mozilla::CheckedInt64 millis
= int64_t(hours
);
375 millis
+= int64_t(minutes
);
377 millis
+= int64_t(seconds
);
379 millis
+= int64_t(milliseconds
);
380 MOZ_ASSERT(millis
.isValid());
382 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
385 normalized
+= NormalizeMicroseconds(Int96
{microseconds
});
388 normalized
+= NormalizeNanoseconds(Int96
{nanoseconds
});
391 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
398 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
401 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
402 const Duration
& duration
) {
403 MOZ_ASSERT(IsValidDuration(duration
));
405 return ::NormalizeTimeDuration(duration
.hours
, duration
.minutes
,
406 duration
.seconds
, duration
.milliseconds
,
407 duration
.microseconds
, duration
.nanoseconds
);
411 * AddNormalizedTimeDuration ( one, two )
413 static bool AddNormalizedTimeDuration(JSContext
* cx
,
414 const NormalizedTimeDuration
& one
,
415 const NormalizedTimeDuration
& two
,
416 NormalizedTimeDuration
* result
) {
417 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
418 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
421 auto sum
= one
+ two
;
424 if (!IsValidNormalizedTimeDuration(sum
)) {
425 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
426 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
436 * SubtractNormalizedTimeDuration ( one, two )
438 static bool SubtractNormalizedTimeDuration(JSContext
* cx
,
439 const NormalizedTimeDuration
& one
,
440 const NormalizedTimeDuration
& two
,
441 NormalizedTimeDuration
* result
) {
442 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
443 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
446 auto sum
= one
- two
;
449 if (!IsValidNormalizedTimeDuration(sum
)) {
450 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
451 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
461 * Add24HourDaysToNormalizedTimeDuration ( d, days )
463 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
464 JSContext
* cx
, const NormalizedTimeDuration
& d
, double days
,
465 NormalizedTimeDuration
* result
) {
466 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
467 MOZ_ASSERT(IsInteger(days
));
470 auto normalizedDays
= NormalizeDays(days
);
471 if (!normalizedDays
) {
472 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
473 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
478 auto sum
= d
+ *normalizedDays
;
479 if (!IsValidNormalizedTimeDuration(sum
)) {
480 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
481 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
491 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
493 bool js::temporal::CombineDateAndNormalizedTimeDuration(
494 JSContext
* cx
, const DateDuration
& date
, const NormalizedTimeDuration
& time
,
495 NormalizedDuration
* result
) {
496 MOZ_ASSERT(IsValidDuration(date
.toDuration()));
497 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
500 int32_t dateSign
= ::DurationSign(date
.toDuration());
503 int32_t timeSign
= NormalizedTimeDurationSign(time
);
506 if ((dateSign
* timeSign
) < 0) {
507 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
508 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN
);
513 *result
= {date
, time
};
518 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
520 NormalizedTimeDuration
521 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
522 const Instant
& one
, const Instant
& two
) {
523 MOZ_ASSERT(IsValidEpochInstant(one
));
524 MOZ_ASSERT(IsValidEpochInstant(two
));
527 auto result
= one
- two
;
530 MOZ_ASSERT(IsValidInstantSpan(result
));
533 return result
.to
<NormalizedTimeDuration
>();
537 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
538 * milliseconds, microseconds, nanoseconds )
540 bool js::temporal::IsValidDuration(const Duration
& duration
) {
541 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
543 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
544 microseconds
, nanoseconds
] = duration
;
547 int32_t sign
= DurationSign(duration
);
550 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
551 milliseconds
, microseconds
, nanoseconds
}) {
553 if (!std::isfinite(v
)) {
558 if (v
< 0 && sign
> 0) {
563 if (v
> 0 && sign
< 0) {
569 if (std::abs(years
) >= double(int64_t(1) << 32)) {
574 if (std::abs(months
) >= double(int64_t(1) << 32)) {
579 if (std::abs(weeks
) >= double(int64_t(1) << 32)) {
584 if (!NormalizeSeconds(duration
)) {
594 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
595 * milliseconds, microseconds, nanoseconds )
597 bool js::temporal::IsValidDuration(const NormalizedDuration
& duration
) {
598 auto date
= duration
.date
.toDuration();
599 return IsValidDuration(date
) &&
600 IsValidNormalizedTimeDuration(duration
.time
) &&
601 (DurationSign(date
) * NormalizedTimeDurationSign(duration
.time
) >= 0);
605 static bool ThrowInvalidDurationPart(JSContext
* cx
, double value
,
606 const char* name
, unsigned errorNumber
) {
608 const char* numStr
= NumberToCString(&cbuf
, value
);
610 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
, name
,
616 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
617 * milliseconds, microseconds, nanoseconds )
619 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
620 const Duration
& duration
) {
621 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
623 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
624 microseconds
, nanoseconds
] = duration
;
627 int32_t sign
= DurationSign(duration
);
629 auto throwIfInvalid
= [&](double v
, const char* name
) {
631 if (!std::isfinite(v
)) {
632 return ThrowInvalidDurationPart(
633 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
637 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
638 return ThrowInvalidDurationPart(cx
, v
, name
,
639 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
645 auto throwIfTooLarge
= [&](double v
, const char* name
) {
646 if (std::abs(v
) >= double(int64_t(1) << 32)) {
647 return ThrowInvalidDurationPart(
648 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
654 if (!throwIfInvalid(years
, "years")) {
657 if (!throwIfInvalid(months
, "months")) {
660 if (!throwIfInvalid(weeks
, "weeks")) {
663 if (!throwIfInvalid(days
, "days")) {
666 if (!throwIfInvalid(hours
, "hours")) {
669 if (!throwIfInvalid(minutes
, "minutes")) {
672 if (!throwIfInvalid(seconds
, "seconds")) {
675 if (!throwIfInvalid(milliseconds
, "milliseconds")) {
678 if (!throwIfInvalid(microseconds
, "microseconds")) {
681 if (!throwIfInvalid(nanoseconds
, "nanoseconds")) {
686 if (!throwIfTooLarge(years
, "years")) {
691 if (!throwIfTooLarge(months
, "months")) {
696 if (!throwIfTooLarge(weeks
, "weeks")) {
701 if (!NormalizeSeconds(duration
)) {
702 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
703 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
707 MOZ_ASSERT(IsValidDuration(duration
));
714 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
715 * milliseconds, microseconds, nanoseconds )
717 static bool ThrowIfInvalidDuration(JSContext
* cx
,
718 const DateDuration
& duration
) {
719 auto& [years
, months
, weeks
, days
] = duration
;
722 int32_t sign
= DurationSign(duration
.toDuration());
724 auto throwIfInvalid
= [&](int64_t v
, const char* name
) {
725 // Step 2.a. (Not applicable)
728 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
729 return ThrowInvalidDurationPart(cx
, v
, name
,
730 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
736 auto throwIfTooLarge
= [&](int64_t v
, const char* name
) {
737 if (std::abs(v
) >= (int64_t(1) << 32)) {
738 return ThrowInvalidDurationPart(
739 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
745 if (!throwIfInvalid(years
, "years")) {
748 if (!throwIfInvalid(months
, "months")) {
751 if (!throwIfInvalid(weeks
, "weeks")) {
754 if (!throwIfInvalid(days
, "days")) {
759 if (!throwIfTooLarge(years
, "years")) {
764 if (!throwIfTooLarge(months
, "months")) {
769 if (!throwIfTooLarge(weeks
, "weeks")) {
774 if (std::abs(days
) > ((int64_t(1) << 53) / 86400)) {
775 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
776 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
780 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
787 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
788 * seconds, milliseconds, microseconds )
790 static TemporalUnit
DefaultTemporalLargestUnit(const Duration
& duration
) {
791 MOZ_ASSERT(IsIntegerDuration(duration
));
794 if (duration
.years
!= 0) {
795 return TemporalUnit::Year
;
799 if (duration
.months
!= 0) {
800 return TemporalUnit::Month
;
804 if (duration
.weeks
!= 0) {
805 return TemporalUnit::Week
;
809 if (duration
.days
!= 0) {
810 return TemporalUnit::Day
;
814 if (duration
.hours
!= 0) {
815 return TemporalUnit::Hour
;
819 if (duration
.minutes
!= 0) {
820 return TemporalUnit::Minute
;
824 if (duration
.seconds
!= 0) {
825 return TemporalUnit::Second
;
829 if (duration
.milliseconds
!= 0) {
830 return TemporalUnit::Millisecond
;
834 if (duration
.microseconds
!= 0) {
835 return TemporalUnit::Microsecond
;
839 return TemporalUnit::Nanosecond
;
843 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
844 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
846 static DurationObject
* CreateTemporalDuration(JSContext
* cx
,
847 const CallArgs
& args
,
848 const Duration
& duration
) {
849 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
850 microseconds
, nanoseconds
] = duration
;
853 if (!ThrowIfInvalidDuration(cx
, duration
)) {
858 Rooted
<JSObject
*> proto(cx
);
859 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Duration
, &proto
)) {
863 auto* object
= NewObjectWithClassProto
<DurationObject
>(cx
, proto
);
869 // Add zero to convert -0 to +0.
870 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
871 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
872 NumberValue(months
+ (+0.0)));
873 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
874 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
875 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
876 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
877 NumberValue(minutes
+ (+0.0)));
878 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
879 NumberValue(seconds
+ (+0.0)));
880 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
881 NumberValue(milliseconds
+ (+0.0)));
882 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
883 NumberValue(microseconds
+ (+0.0)));
884 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
885 NumberValue(nanoseconds
+ (+0.0)));
892 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
893 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
895 DurationObject
* js::temporal::CreateTemporalDuration(JSContext
* cx
,
896 const Duration
& duration
) {
897 auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
898 microseconds
, nanoseconds
] = duration
;
900 MOZ_ASSERT(IsInteger(years
));
901 MOZ_ASSERT(IsInteger(months
));
902 MOZ_ASSERT(IsInteger(weeks
));
903 MOZ_ASSERT(IsInteger(days
));
904 MOZ_ASSERT(IsInteger(hours
));
905 MOZ_ASSERT(IsInteger(minutes
));
906 MOZ_ASSERT(IsInteger(seconds
));
907 MOZ_ASSERT(IsInteger(milliseconds
));
908 MOZ_ASSERT(IsInteger(microseconds
));
909 MOZ_ASSERT(IsInteger(nanoseconds
));
912 if (!ThrowIfInvalidDuration(cx
, duration
)) {
917 auto* object
= NewBuiltinClassInstance
<DurationObject
>(cx
);
923 // Add zero to convert -0 to +0.
924 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
925 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
926 NumberValue(months
+ (+0.0)));
927 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
928 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
929 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
930 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
931 NumberValue(minutes
+ (+0.0)));
932 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
933 NumberValue(seconds
+ (+0.0)));
934 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
935 NumberValue(milliseconds
+ (+0.0)));
936 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
937 NumberValue(microseconds
+ (+0.0)));
938 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
939 NumberValue(nanoseconds
+ (+0.0)));
946 * ToIntegerIfIntegral ( argument )
948 static bool ToIntegerIfIntegral(JSContext
* cx
, const char* name
,
949 Handle
<Value
> argument
, double* num
) {
952 if (!JS::ToNumber(cx
, argument
, &d
)) {
957 if (!js::IsInteger(d
)) {
959 const char* numStr
= NumberToCString(&cbuf
, d
);
961 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
962 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
973 * ToIntegerIfIntegral ( argument )
975 static bool ToIntegerIfIntegral(JSContext
* cx
, Handle
<PropertyName
*> name
,
976 Handle
<Value
> argument
, double* result
) {
979 if (!JS::ToNumber(cx
, argument
, &d
)) {
984 if (!js::IsInteger(d
)) {
985 if (auto nameStr
= js::QuoteString(cx
, name
)) {
987 const char* numStr
= NumberToCString(&cbuf
, d
);
989 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
990 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
1002 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1004 static bool ToTemporalPartialDurationRecord(
1005 JSContext
* cx
, Handle
<JSObject
*> temporalDurationLike
, Duration
* result
) {
1006 // Steps 1-3. (Not applicable in our implementation.)
1008 Rooted
<Value
> value(cx
);
1011 auto getDurationProperty
= [&](Handle
<PropertyName
*> name
, double* num
) {
1012 if (!GetProperty(cx
, temporalDurationLike
, temporalDurationLike
, name
,
1017 if (!value
.isUndefined()) {
1020 if (!ToIntegerIfIntegral(cx
, name
, value
, num
)) {
1028 if (!getDurationProperty(cx
->names().days
, &result
->days
)) {
1031 if (!getDurationProperty(cx
->names().hours
, &result
->hours
)) {
1034 if (!getDurationProperty(cx
->names().microseconds
, &result
->microseconds
)) {
1037 if (!getDurationProperty(cx
->names().milliseconds
, &result
->milliseconds
)) {
1040 if (!getDurationProperty(cx
->names().minutes
, &result
->minutes
)) {
1043 if (!getDurationProperty(cx
->names().months
, &result
->months
)) {
1046 if (!getDurationProperty(cx
->names().nanoseconds
, &result
->nanoseconds
)) {
1049 if (!getDurationProperty(cx
->names().seconds
, &result
->seconds
)) {
1052 if (!getDurationProperty(cx
->names().weeks
, &result
->weeks
)) {
1055 if (!getDurationProperty(cx
->names().years
, &result
->years
)) {
1061 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1062 JSMSG_TEMPORAL_DURATION_MISSING_UNIT
);
1071 * ToTemporalDurationRecord ( temporalDurationLike )
1073 bool js::temporal::ToTemporalDurationRecord(JSContext
* cx
,
1074 Handle
<Value
> temporalDurationLike
,
1077 if (!temporalDurationLike
.isObject()) {
1079 if (!temporalDurationLike
.isString()) {
1080 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
,
1081 temporalDurationLike
, nullptr, "not a string");
1084 Rooted
<JSString
*> string(cx
, temporalDurationLike
.toString());
1087 return ParseTemporalDurationString(cx
, string
, result
);
1090 Rooted
<JSObject
*> durationLike(cx
, &temporalDurationLike
.toObject());
1093 if (auto* duration
= durationLike
->maybeUnwrapIf
<DurationObject
>()) {
1094 *result
= ToDuration(duration
);
1099 Duration duration
= {};
1102 if (!ToTemporalPartialDurationRecord(cx
, durationLike
, &duration
)) {
1107 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1117 * ToTemporalDuration ( item )
1119 Wrapped
<DurationObject
*> js::temporal::ToTemporalDuration(JSContext
* cx
,
1120 Handle
<Value
> item
) {
1122 if (item
.isObject()) {
1123 JSObject
* itemObj
= &item
.toObject();
1124 if (itemObj
->canUnwrapAs
<DurationObject
>()) {
1131 if (!ToTemporalDurationRecord(cx
, item
, &result
)) {
1136 return CreateTemporalDuration(cx
, result
);
1140 * ToTemporalDuration ( item )
1142 bool js::temporal::ToTemporalDuration(JSContext
* cx
, Handle
<Value
> item
,
1144 auto obj
= ToTemporalDuration(cx
, item
);
1149 *result
= ToDuration(&obj
.unwrap());
1154 * DaysUntil ( earlier, later )
1156 int32_t js::temporal::DaysUntil(const PlainDate
& earlier
,
1157 const PlainDate
& later
) {
1158 MOZ_ASSERT(ISODateTimeWithinLimits(earlier
));
1159 MOZ_ASSERT(ISODateTimeWithinLimits(later
));
1162 int32_t epochDaysEarlier
= MakeDay(earlier
);
1163 MOZ_ASSERT(std::abs(epochDaysEarlier
) <= 100'000'000);
1166 int32_t epochDaysLater
= MakeDay(later
);
1167 MOZ_ASSERT(std::abs(epochDaysLater
) <= 100'000'000);
1170 return epochDaysLater
- epochDaysEarlier
;
1174 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1176 static bool MoveRelativeDate(
1177 JSContext
* cx
, Handle
<CalendarRecord
> calendar
,
1178 Handle
<Wrapped
<PlainDateObject
*>> relativeTo
, const Duration
& duration
,
1179 MutableHandle
<Wrapped
<PlainDateObject
*>> relativeToResult
,
1180 int32_t* daysResult
) {
1181 auto* unwrappedRelativeTo
= relativeTo
.unwrap(cx
);
1182 if (!unwrappedRelativeTo
) {
1185 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1188 auto newDate
= AddDate(cx
, calendar
, relativeTo
, duration
);
1192 auto later
= ToPlainDate(&newDate
.unwrap());
1193 relativeToResult
.set(newDate
);
1196 *daysResult
= DaysUntil(relativeToDate
, later
);
1197 MOZ_ASSERT(std::abs(*daysResult
) <= 200'000'000);
1204 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1205 * months, weeks, days, precalculatedPlainDateTime )
1207 static bool MoveRelativeZonedDateTime(
1208 JSContext
* cx
, Handle
<ZonedDateTime
> zonedDateTime
,
1209 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
1210 const DateDuration
& duration
,
1211 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1212 MutableHandle
<ZonedDateTime
> result
) {
1214 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1215 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1218 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1219 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1222 Instant intermediateNs
;
1223 if (precalculatedPlainDateTime
) {
1224 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1225 duration
, *precalculatedPlainDateTime
,
1230 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1231 duration
, &intermediateNs
)) {
1235 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
1238 result
.set(ZonedDateTime
{intermediateNs
, zonedDateTime
.timeZone(),
1239 zonedDateTime
.calendar()});
1244 * Split duration into full days and remainding nanoseconds.
1246 static NormalizedTimeAndDays
NormalizedTimeDurationToDays(
1247 const NormalizedTimeDuration
& duration
) {
1248 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1250 auto [seconds
, nanoseconds
] = duration
;
1251 if (seconds
< 0 && nanoseconds
> 0) {
1253 nanoseconds
-= 1'000'000'000;
1256 int64_t days
= seconds
/ ToSeconds(TemporalUnit::Day
);
1257 seconds
= seconds
% ToSeconds(TemporalUnit::Day
);
1259 int64_t time
= seconds
* ToNanoseconds(TemporalUnit::Second
) + nanoseconds
;
1261 constexpr int64_t dayLength
= ToNanoseconds(TemporalUnit::Day
);
1262 MOZ_ASSERT(std::abs(time
) < dayLength
);
1264 return {days
, time
, dayLength
};
1268 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1269 * microseconds, nanoseconds )
1271 static TimeDuration
CreateTimeDurationRecord(int64_t days
, int64_t hours
,
1272 int64_t minutes
, int64_t seconds
,
1273 int64_t milliseconds
,
1274 double microseconds
,
1275 double nanoseconds
) {
1277 MOZ_ASSERT(IsValidDuration(
1278 {0, 0, 0, double(days
), double(hours
), double(minutes
), double(seconds
),
1279 double(milliseconds
), microseconds
, nanoseconds
}));
1281 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1282 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1283 MOZ_ASSERT(IsSafeInteger(days
));
1284 MOZ_ASSERT(IsSafeInteger(hours
));
1285 MOZ_ASSERT(IsSafeInteger(minutes
));
1286 MOZ_ASSERT(IsSafeInteger(seconds
));
1288 // |milliseconds| is explicitly casted to double by consumers, so we can also
1289 // omit the `ℝ(𝔽(x))` conversion.
1292 // NB: Adds +0.0 to correctly handle negative zero.
1299 microseconds
+ (+0.0),
1300 nanoseconds
+ (+0.0),
1305 * BalanceTimeDuration ( norm, largestUnit )
1307 TimeDuration
js::temporal::BalanceTimeDuration(
1308 const NormalizedTimeDuration
& duration
, TemporalUnit largestUnit
) {
1309 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1311 auto [seconds
, nanoseconds
] = duration
;
1313 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1314 // Convert these back to their absolute value and adjust the seconds part
1317 // For example the nanoseconds duration |-1n| is represented as the
1318 // duration {seconds: -1, nanoseconds: 999'999'999}.
1319 if (seconds
< 0 && nanoseconds
> 0) {
1321 nanoseconds
-= ToNanoseconds(TemporalUnit::Second
);
1327 int64_t minutes
= 0;
1328 int64_t milliseconds
= 0;
1329 int64_t microseconds
= 0;
1331 // Steps 2-3. (Not applicable in our implementation.)
1333 // We don't need to convert to positive numbers, because integer division
1334 // truncates and the %-operator has modulo semantics.
1337 switch (largestUnit
) {
1339 case TemporalUnit::Year
:
1340 case TemporalUnit::Month
:
1341 case TemporalUnit::Week
:
1342 case TemporalUnit::Day
: {
1344 microseconds
= nanoseconds
/ 1000;
1347 nanoseconds
= nanoseconds
% 1000;
1350 milliseconds
= microseconds
/ 1000;
1353 microseconds
= microseconds
% 1000;
1355 // Steps 4.e-f. (Not applicable)
1356 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1359 minutes
= seconds
/ 60;
1362 seconds
= seconds
% 60;
1365 hours
= minutes
/ 60;
1368 minutes
= minutes
% 60;
1380 case TemporalUnit::Hour
: {
1382 microseconds
= nanoseconds
/ 1000;
1385 nanoseconds
= nanoseconds
% 1000;
1388 milliseconds
= microseconds
/ 1000;
1391 microseconds
= microseconds
% 1000;
1393 // Steps 5.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;
1411 case TemporalUnit::Minute
: {
1413 microseconds
= nanoseconds
/ 1000;
1416 nanoseconds
= nanoseconds
% 1000;
1419 milliseconds
= microseconds
/ 1000;
1422 microseconds
= microseconds
% 1000;
1424 // Steps 6.e-f. (Not applicable)
1425 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1428 minutes
= seconds
/ 60;
1431 seconds
= seconds
% 60;
1437 case TemporalUnit::Second
: {
1439 microseconds
= nanoseconds
/ 1000;
1442 nanoseconds
= nanoseconds
% 1000;
1445 milliseconds
= microseconds
/ 1000;
1448 microseconds
= microseconds
% 1000;
1450 // Steps 7.e-f. (Not applicable)
1451 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1457 case TemporalUnit::Millisecond
: {
1458 static_assert((NormalizedTimeDuration::max().seconds
+ 1) *
1459 ToMilliseconds(TemporalUnit::Second
) <=
1461 "total number duration milliseconds fits into int64");
1463 int64_t millis
= seconds
* ToMilliseconds(TemporalUnit::Second
);
1465 // Set to zero per step 1.
1469 microseconds
= nanoseconds
/ 1000;
1472 nanoseconds
= nanoseconds
% 1000;
1475 milliseconds
= microseconds
/ 1000;
1478 microseconds
= microseconds
% 1000;
1480 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1481 milliseconds
+= millis
;
1487 case TemporalUnit::Microsecond
: {
1489 int64_t microseconds
= nanoseconds
/ 1000;
1492 nanoseconds
= nanoseconds
% 1000;
1494 MOZ_ASSERT(std::abs(microseconds
) <= 999'999);
1496 std::fma(double(seconds
), ToMicroseconds(TemporalUnit::Second
),
1497 double(microseconds
));
1500 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros
,
1501 double(nanoseconds
));
1505 case TemporalUnit::Nanosecond
: {
1506 MOZ_ASSERT(std::abs(nanoseconds
) <= 999'999'999);
1508 std::fma(double(seconds
), ToNanoseconds(TemporalUnit::Second
),
1509 double(nanoseconds
));
1512 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos
);
1515 case TemporalUnit::Auto
:
1516 MOZ_CRASH("Unexpected temporal unit");
1520 return CreateTimeDurationRecord(days
, hours
, minutes
, seconds
, milliseconds
,
1521 double(microseconds
), double(nanoseconds
));
1525 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1526 * timeZoneRec, precalculatedPlainDateTime )
1528 static bool BalanceTimeDurationRelative(
1529 JSContext
* cx
, const NormalizedDuration
& duration
, TemporalUnit largestUnit
,
1530 Handle
<ZonedDateTime
> relativeTo
, Handle
<TimeZoneRecord
> timeZone
,
1531 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1532 TimeDuration
* result
) {
1533 MOZ_ASSERT(IsValidDuration(duration
));
1536 const auto& startNs
= relativeTo
.instant();
1539 const auto& startInstant
= startNs
;
1542 auto intermediateNs
= startNs
;
1545 PlainDateTime startDateTime
;
1546 if (duration
.date
.days
!= 0) {
1548 if (!precalculatedPlainDateTime
) {
1549 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1552 precalculatedPlainDateTime
=
1553 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1557 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
1558 if (!AddDaysToZonedDateTime(cx
, startInstant
, *precalculatedPlainDateTime
,
1559 timeZone
, isoCalendar
, duration
.date
.days
,
1567 if (!AddInstant(cx
, intermediateNs
, duration
.time
, &endNs
)) {
1570 MOZ_ASSERT(IsValidEpochInstant(endNs
));
1574 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startInstant
);
1577 if (normalized
== NormalizedTimeDuration
{}) {
1584 if (TemporalUnit::Year
<= largestUnit
&& largestUnit
<= TemporalUnit::Day
) {
1586 if (!precalculatedPlainDateTime
) {
1587 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1590 precalculatedPlainDateTime
=
1591 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1595 NormalizedTimeAndDays timeAndDays
;
1596 if (!NormalizedTimeDurationToDays(cx
, normalized
, relativeTo
, timeZone
,
1597 *precalculatedPlainDateTime
,
1603 days
= timeAndDays
.days
;
1606 normalized
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
1607 MOZ_ASSERT_IF(days
> 0, normalized
>= NormalizedTimeDuration
{});
1608 MOZ_ASSERT_IF(days
< 0, normalized
<= NormalizedTimeDuration
{});
1611 largestUnit
= TemporalUnit::Hour
;
1615 auto balanceResult
= BalanceTimeDuration(normalized
, largestUnit
);
1620 balanceResult
.hours
,
1621 balanceResult
.minutes
,
1622 balanceResult
.seconds
,
1623 balanceResult
.milliseconds
,
1624 balanceResult
.microseconds
,
1625 balanceResult
.nanoseconds
,
1627 MOZ_ASSERT(IsValidDuration(result
->toDuration()));
1632 * CreateDateDurationRecord ( years, months, weeks, days )
1634 static DateDuration
CreateDateDurationRecord(int64_t years
, int64_t months
,
1635 int64_t weeks
, int64_t days
) {
1636 MOZ_ASSERT(IsValidDuration(Duration
{
1642 return {years
, months
, weeks
, days
};
1646 * CreateDateDurationRecord ( years, months, weeks, days )
1648 static bool CreateDateDurationRecord(JSContext
* cx
, int64_t years
,
1649 int64_t months
, int64_t weeks
,
1650 int64_t days
, DateDuration
* result
) {
1651 auto duration
= DateDuration
{years
, months
, weeks
, days
};
1652 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1660 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration
& duration
,
1661 TemporalUnit largestUnit
) {
1662 MOZ_ASSERT(largestUnit
!= TemporalUnit::Auto
);
1664 // Steps 2, 3.a-b, 4.a-b, 6-7.
1665 return (largestUnit
> TemporalUnit::Year
&& duration
.years
!= 0) ||
1666 (largestUnit
> TemporalUnit::Month
&& duration
.months
!= 0) ||
1667 (largestUnit
> TemporalUnit::Week
&& duration
.weeks
!= 0);
1671 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1672 * plainRelativeTo, calendarRec )
1674 static bool UnbalanceDateDurationRelative(
1675 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1676 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1677 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1678 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
1680 auto [years
, months
, weeks
, days
] = duration
;
1682 // Step 1. (Not applicable in our implementation.)
1684 // Steps 2, 3.a, 4.a, and 6.
1685 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1686 // Steps 2.a, 3.a, 4.a, and 6.
1692 if (largestUnit
== TemporalUnit::Month
) {
1693 // Step 3.a. (Handled above)
1694 MOZ_ASSERT(years
!= 0);
1696 // Step 3.b. (Not applicable in our implementation.)
1700 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1704 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1707 auto yearsDuration
= Duration
{double(years
)};
1710 Rooted
<Wrapped
<PlainDateObject
*>> later(
1711 cx
, CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsDuration
));
1717 Duration untilResult
;
1718 if (!CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
,
1719 TemporalUnit::Month
, &untilResult
)) {
1724 int64_t yearsInMonths
= int64_t(untilResult
.months
);
1727 return CreateDateDurationRecord(cx
, 0, months
+ yearsInMonths
, weeks
, days
,
1732 if (largestUnit
== TemporalUnit::Week
) {
1733 // Step 4.a. (Handled above)
1734 MOZ_ASSERT(years
!= 0 || months
!= 0);
1736 // Step 4.b. (Not applicable in our implementation.)
1740 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1743 auto yearsMonthsDuration
= Duration
{double(years
), double(months
)};
1747 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsDuration
);
1751 auto laterDate
= ToPlainDate(&later
.unwrap());
1753 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1754 if (!unwrappedRelativeTo
) {
1757 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1760 int32_t yearsMonthsInDays
= DaysUntil(relativeToDate
, laterDate
);
1763 return CreateDateDurationRecord(cx
, 0, 0, weeks
, days
+ yearsMonthsInDays
,
1767 // Step 5. (Not applicable in our implementation.)
1769 // Step 6. (Handled above)
1770 MOZ_ASSERT(years
!= 0 || months
!= 0 || weeks
!= 0);
1772 // FIXME: why don't we unconditionally throw an error for missing calendars?
1774 // Step 7. (Not applicable in our implementation.)
1778 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1781 auto yearsMonthsWeeksDuration
=
1782 Duration
{double(years
), double(months
), double(weeks
)};
1786 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsWeeksDuration
);
1790 auto laterDate
= ToPlainDate(&later
.unwrap());
1792 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1793 if (!unwrappedRelativeTo
) {
1796 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1799 int32_t yearsMonthsWeeksInDay
= DaysUntil(relativeToDate
, laterDate
);
1802 return CreateDateDurationRecord(cx
, 0, 0, 0, days
+ yearsMonthsWeeksInDay
,
1807 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1808 * plainRelativeTo, calendarRec )
1810 static bool UnbalanceDateDurationRelative(JSContext
* cx
,
1811 const DateDuration
& duration
,
1812 TemporalUnit largestUnit
,
1813 DateDuration
* result
) {
1814 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
1816 // Step 1. (Not applicable.)
1818 // Steps 2, 3.a, 4.a, and 6.
1819 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1820 // Steps 2.a, 3.a, 4.a, and 6.
1825 // Step 5. (Not applicable.)
1827 // Steps 3.b, 4.b, and 7.
1828 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1829 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
, "calendar");
1834 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1835 * smallestUnit, plainRelativeTo, calendarRec )
1837 static bool BalanceDateDurationRelative(
1838 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1839 TemporalUnit smallestUnit
,
1840 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1841 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1842 MOZ_ASSERT(IsValidDuration(duration
.toDuration()));
1843 MOZ_ASSERT(largestUnit
<= smallestUnit
);
1845 auto [years
, months
, weeks
, days
] = duration
;
1847 // FIXME: spec issue - effectful code paths should be more fine-grained
1848 // similar to UnbalanceDateDurationRelative. For example:
1849 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1850 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1851 // 3. Else if days = 0, then no-op.
1853 // Also note that |weeks| is never balanced, even when non-zero.
1855 // Step 1. (Not applicable in our implementation.)
1858 if (largestUnit
> TemporalUnit::Week
||
1859 (years
== 0 && months
== 0 && weeks
== 0 && days
== 0)) {
1866 if (!plainRelativeTo
) {
1867 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1868 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
1875 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1879 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1881 // Steps 8-9. (Not applicable in our implementation.)
1883 auto untilAddedDate
= [&](const Duration
& duration
, Duration
* untilResult
) {
1884 Rooted
<Wrapped
<PlainDateObject
*>> later(
1885 cx
, AddDate(cx
, calendar
, plainRelativeTo
, duration
));
1890 return CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
, largestUnit
,
1895 if (largestUnit
== TemporalUnit::Year
) {
1897 if (smallestUnit
== TemporalUnit::Week
) {
1899 MOZ_ASSERT(days
== 0);
1902 auto yearsMonthsDuration
= Duration
{double(years
), double(months
)};
1904 // Steps 10.a.iii-iv.
1905 Duration untilResult
;
1906 if (!untilAddedDate(yearsMonthsDuration
, &untilResult
)) {
1911 *result
= CreateDateDurationRecord(int64_t(untilResult
.years
),
1912 int64_t(untilResult
.months
), weeks
, 0);
1917 auto yearsMonthsWeeksDaysDuration
=
1918 Duration
{double(years
), double(months
), double(weeks
), double(days
)};
1921 Duration untilResult
;
1922 if (!untilAddedDate(yearsMonthsWeeksDaysDuration
, &untilResult
)) {
1926 // FIXME: spec bug - CreateDateDurationRecord is infallible
1927 // https://github.com/tc39/proposal-temporal/issues/2750
1930 *result
= CreateDateDurationRecord(
1931 int64_t(untilResult
.years
), int64_t(untilResult
.months
),
1932 int64_t(untilResult
.weeks
), int64_t(untilResult
.days
));
1937 if (largestUnit
== TemporalUnit::Month
) {
1939 MOZ_ASSERT(years
== 0);
1942 if (smallestUnit
== TemporalUnit::Week
) {
1944 MOZ_ASSERT(days
== 0);
1947 *result
= CreateDateDurationRecord(0, months
, weeks
, 0);
1952 auto monthsWeeksDaysDuration
=
1953 Duration
{0, double(months
), double(weeks
), double(days
)};
1956 Duration untilResult
;
1957 if (!untilAddedDate(monthsWeeksDaysDuration
, &untilResult
)) {
1961 // FIXME: spec bug - CreateDateDurationRecord is infallible
1962 // https://github.com/tc39/proposal-temporal/issues/2750
1965 *result
= CreateDateDurationRecord(0, int64_t(untilResult
.months
),
1966 int64_t(untilResult
.weeks
),
1967 int64_t(untilResult
.days
));
1972 MOZ_ASSERT(largestUnit
== TemporalUnit::Week
);
1975 MOZ_ASSERT(years
== 0);
1978 MOZ_ASSERT(months
== 0);
1981 auto weeksDaysDuration
= Duration
{0, 0, double(weeks
), double(days
)};
1984 Duration untilResult
;
1985 if (!untilAddedDate(weeksDaysDuration
, &untilResult
)) {
1989 // FIXME: spec bug - CreateDateDurationRecord is infallible
1990 // https://github.com/tc39/proposal-temporal/issues/2750
1993 *result
= CreateDateDurationRecord(0, 0, int64_t(untilResult
.weeks
),
1994 int64_t(untilResult
.days
));
1999 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2000 * smallestUnit, plainRelativeTo, calendarRec )
2002 bool js::temporal::BalanceDateDurationRelative(
2003 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
2004 TemporalUnit smallestUnit
,
2005 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2006 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
2007 MOZ_ASSERT(plainRelativeTo
);
2008 MOZ_ASSERT(calendar
.receiver());
2010 return ::BalanceDateDurationRelative(cx
, duration
, largestUnit
, smallestUnit
,
2011 plainRelativeTo
, calendar
, result
);
2015 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2016 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2017 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2019 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2021 MOZ_ASSERT(IsValidDuration(one
));
2022 MOZ_ASSERT(IsValidDuration(two
));
2024 // Steps 1-2. (Not applicable)
2027 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2030 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2033 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2036 if (largestUnit
<= TemporalUnit::Week
) {
2037 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2038 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
2044 auto normalized1
= NormalizeTimeDuration(one
);
2047 auto normalized2
= NormalizeTimeDuration(two
);
2050 NormalizedTimeDuration normalized
;
2051 if (!AddNormalizedTimeDuration(cx
, normalized1
, normalized2
, &normalized
)) {
2056 if (!Add24HourDaysToNormalizedTimeDuration(
2057 cx
, normalized
, one
.days
+ two
.days
, &normalized
)) {
2062 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2065 *result
= balanced
.toDuration();
2070 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2071 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2072 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2074 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2075 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2076 Handle
<CalendarRecord
> calendar
, Duration
* result
) {
2077 MOZ_ASSERT(IsValidDuration(one
));
2078 MOZ_ASSERT(IsValidDuration(two
));
2080 // Steps 1-2. (Not applicable)
2082 // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
2086 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2089 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2092 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2094 // Step 6. (Not applicable)
2096 // Step 7.a. (Not applicable in our implementation.)
2099 auto dateDuration1
= Duration
{one
.years
, one
.months
, one
.weeks
, one
.days
};
2102 auto dateDuration2
= Duration
{two
.years
, two
.months
, two
.weeks
, two
.days
};
2104 // FIXME: spec issue - calendarUnitsPresent is unused.
2107 [[maybe_unused
]] bool calendarUnitsPresent
= true;
2110 if (dateDuration1
.years
== 0 && dateDuration1
.months
== 0 &&
2111 dateDuration1
.weeks
== 0 && dateDuration2
.years
== 0 &&
2112 dateDuration2
.months
== 0 && dateDuration2
.weeks
== 0) {
2113 calendarUnitsPresent
= false;
2117 Rooted
<Wrapped
<PlainDateObject
*>> intermediate(
2118 cx
, AddDate(cx
, calendar
, plainRelativeTo
, dateDuration1
));
2119 if (!intermediate
) {
2124 Rooted
<Wrapped
<PlainDateObject
*>> end(
2125 cx
, AddDate(cx
, calendar
, intermediate
, dateDuration2
));
2131 auto dateLargestUnit
= std::min(TemporalUnit::Day
, largestUnit
);
2134 Duration dateDifference
;
2135 if (!DifferenceDate(cx
, calendar
, plainRelativeTo
, end
, dateLargestUnit
,
2141 auto normalized1
= NormalizeTimeDuration(one
);
2144 auto normalized2
= NormalizeTimeDuration(two
);
2147 NormalizedTimeDuration normalized1WithDays
;
2148 if (!Add24HourDaysToNormalizedTimeDuration(
2149 cx
, normalized1
, dateDifference
.days
, &normalized1WithDays
)) {
2154 NormalizedTimeDuration normalized
;
2155 if (!AddNormalizedTimeDuration(cx
, normalized1WithDays
, normalized2
,
2161 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2165 dateDifference
.years
, dateDifference
.months
,
2166 dateDifference
.weeks
, double(balanced
.days
),
2167 double(balanced
.hours
), double(balanced
.minutes
),
2168 double(balanced
.seconds
), double(balanced
.milliseconds
),
2169 balanced
.microseconds
, balanced
.nanoseconds
,
2171 MOZ_ASSERT(IsValidDuration(*result
));
2176 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2177 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2178 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2180 static bool AddDuration(
2181 JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2182 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2183 Handle
<TimeZoneRecord
> timeZone
,
2184 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2186 // Steps 1-2. (Not applicable)
2189 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2192 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2195 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2197 // Steps 6-7. (Not applicable)
2199 // Steps 8-9. (Not applicable in our implementation.)
2201 // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
2205 // 10. If largestUnit is one of "year", "month", "week", or "day", then
2206 // a. If precalculatedPlainDateTime is undefined, then
2207 // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
2209 // i. Let startDateTime be precalculatedPlainDateTime.
2210 // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
2211 // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
2212 // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
2213 // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
2214 // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
2215 // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
2216 // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
2221 bool startDateTimeNeeded
= largestUnit
<= TemporalUnit::Day
;
2224 if (!startDateTimeNeeded
) {
2225 // Steps 11-12. (Not applicable)
2228 auto normalized1
= NormalizeTimeDuration(one
);
2231 auto normalized2
= NormalizeTimeDuration(two
);
2233 // Step 15. (Inlined AddZonedDateTime, step 6.)
2234 Instant intermediateNs
;
2235 if (!AddInstant(cx
, zonedRelativeTo
.instant(), normalized1
,
2239 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2241 // Step 16. (Inlined AddZonedDateTime, step 6.)
2243 if (!AddInstant(cx
, intermediateNs
, normalized2
, &endNs
)) {
2246 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2249 auto normalized
= NormalizedTimeDurationFromEpochNanosecondsDifference(
2250 endNs
, zonedRelativeTo
.instant());
2253 auto balanced
= BalanceTimeDuration(normalized
, largestUnit
);
2256 *result
= balanced
.toDuration();
2261 PlainDateTime startDateTime
;
2262 if (!precalculatedPlainDateTime
) {
2263 if (!GetPlainDateTimeFor(cx
, timeZone
, zonedRelativeTo
.instant(),
2268 startDateTime
= *precalculatedPlainDateTime
;
2272 auto normalized1
= CreateNormalizedDurationRecord(one
);
2275 auto normalized2
= CreateNormalizedDurationRecord(two
);
2278 Instant intermediateNs
;
2279 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2280 normalized1
, startDateTime
, &intermediateNs
)) {
2283 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2287 if (!AddZonedDateTime(cx
, intermediateNs
, timeZone
, calendar
, normalized2
,
2291 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2293 // Step 17. (Not applicable)
2296 NormalizedDuration difference
;
2297 if (!DifferenceZonedDateTime(cx
, zonedRelativeTo
.instant(), endNs
, timeZone
,
2298 calendar
, largestUnit
, startDateTime
,
2304 auto balanced
= BalanceTimeDuration(difference
.time
, TemporalUnit::Hour
);
2308 double(difference
.date
.years
), double(difference
.date
.months
),
2309 double(difference
.date
.weeks
), double(difference
.date
.days
),
2310 double(balanced
.hours
), double(balanced
.minutes
),
2311 double(balanced
.seconds
), double(balanced
.milliseconds
),
2312 balanced
.microseconds
, balanced
.nanoseconds
,
2314 MOZ_ASSERT(IsValidDuration(*result
));
2319 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2320 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2321 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2323 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2324 Handle
<ZonedDateTime
> zonedRelativeTo
,
2325 Handle
<CalendarRecord
> calendar
,
2326 Handle
<TimeZoneRecord
> timeZone
, Duration
* result
) {
2327 return AddDuration(cx
, one
, two
, zonedRelativeTo
, calendar
, timeZone
,
2328 mozilla::Nothing(), result
);
2332 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2333 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2334 * precalculatedPlainDateTime )
2336 static bool AdjustRoundedDurationDays(
2337 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2338 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2339 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2340 Handle
<TimeZoneRecord
> timeZone
,
2341 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2342 NormalizedDuration
* result
) {
2343 MOZ_ASSERT(IsValidDuration(duration
));
2346 if ((TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
) ||
2347 (unit
== TemporalUnit::Nanosecond
&& increment
== Increment
{1})) {
2352 // The increment is limited for all smaller temporal units.
2353 MOZ_ASSERT(increment
< MaximumTemporalDurationRoundingIncrement(unit
));
2356 MOZ_ASSERT(precalculatedPlainDateTime
);
2359 int32_t direction
= NormalizedTimeDurationSign(duration
.time
);
2363 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2364 duration
.date
, *precalculatedPlainDateTime
,
2368 MOZ_ASSERT(IsValidEpochInstant(dayStart
));
2371 PlainDateTime dayStartDateTime
;
2372 if (!GetPlainDateTimeFor(cx
, timeZone
, dayStart
, &dayStartDateTime
)) {
2378 if (!AddDaysToZonedDateTime(cx
, dayStart
, dayStartDateTime
, timeZone
,
2379 zonedRelativeTo
.calendar(), direction
, &dayEnd
)) {
2382 MOZ_ASSERT(IsValidEpochInstant(dayEnd
));
2386 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd
, dayStart
);
2387 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs
.to
<InstantSpan
>()));
2390 NormalizedTimeDuration oneDayLess
;
2391 if (!SubtractNormalizedTimeDuration(cx
, duration
.time
, dayLengthNs
,
2397 int32_t oneDayLessSign
= NormalizedTimeDurationSign(oneDayLess
);
2398 if ((direction
> 0 && oneDayLessSign
< 0) ||
2399 (direction
< 0 && oneDayLessSign
> 0)) {
2405 Duration adjustedDateDuration
;
2406 if (!AddDuration(cx
, duration
.date
.toDuration(), {0, 0, 0, double(direction
)},
2407 zonedRelativeTo
, calendar
, timeZone
,
2408 precalculatedPlainDateTime
, &adjustedDateDuration
)) {
2413 auto roundedTime
= RoundDuration(oneDayLess
, increment
, unit
, roundingMode
);
2416 return CombineDateAndNormalizedTimeDuration(
2417 cx
, adjustedDateDuration
.toDateDuration(), roundedTime
, result
);
2421 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2422 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2423 * precalculatedPlainDateTime )
2425 bool js::temporal::AdjustRoundedDurationDays(
2426 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2427 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2428 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2429 Handle
<TimeZoneRecord
> timeZone
,
2430 const PlainDateTime
& precalculatedPlainDateTime
,
2431 NormalizedDuration
* result
) {
2432 return ::AdjustRoundedDurationDays(
2433 cx
, duration
, increment
, unit
, roundingMode
, zonedRelativeTo
, calendar
,
2434 timeZone
, mozilla::SomeRef(precalculatedPlainDateTime
), result
);
2437 static bool NumberToStringBuilder(JSContext
* cx
, double num
,
2438 JSStringBuilder
& sb
) {
2439 MOZ_ASSERT(IsInteger(num
));
2440 MOZ_ASSERT(num
>= 0);
2441 MOZ_ASSERT(num
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2445 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
2447 return sb
.append(numStr
, length
);
2450 static Duration
AbsoluteDuration(const Duration
& duration
) {
2452 std::abs(duration
.years
), std::abs(duration
.months
),
2453 std::abs(duration
.weeks
), std::abs(duration
.days
),
2454 std::abs(duration
.hours
), std::abs(duration
.minutes
),
2455 std::abs(duration
.seconds
), std::abs(duration
.milliseconds
),
2456 std::abs(duration
.microseconds
), std::abs(duration
.nanoseconds
),
2461 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2463 [[nodiscard
]] static bool FormatFractionalSeconds(JSStringBuilder
& result
,
2464 int32_t subSecondNanoseconds
,
2465 Precision precision
) {
2466 MOZ_ASSERT(0 <= subSecondNanoseconds
&& subSecondNanoseconds
< 1'000'000'000);
2467 MOZ_ASSERT(precision
!= Precision::Minute());
2470 if (precision
== Precision::Auto()) {
2472 if (subSecondNanoseconds
== 0) {
2476 // Step 3. (Reordered)
2477 if (!result
.append('.')) {
2482 uint32_t k
= 100'000'000;
2484 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2487 subSecondNanoseconds
%= k
;
2489 } while (subSecondNanoseconds
);
2492 uint8_t p
= precision
.value();
2497 // Step 3. (Reordered)
2498 if (!result
.append('.')) {
2503 uint32_t k
= 100'000'000;
2504 for (uint8_t i
= 0; i
< precision
.value(); i
++) {
2505 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2508 subSecondNanoseconds
%= k
;
2517 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2518 * normSeconds, precision )
2520 static JSString
* TemporalDurationToString(JSContext
* cx
,
2521 const Duration
& duration
,
2522 Precision precision
) {
2523 MOZ_ASSERT(IsValidDuration(duration
));
2524 MOZ_ASSERT(precision
!= Precision::Minute());
2526 // Fast path for zero durations.
2527 if (duration
== Duration
{} &&
2528 (precision
== Precision::Auto() || precision
.value() == 0)) {
2529 return NewStringCopyZ
<CanGC
>(cx
, "PT0S");
2532 // Convert to absolute values up front. This is okay to do, because when the
2533 // duration is valid, all components have the same sign.
2534 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
2535 milliseconds
, microseconds
, nanoseconds
] =
2536 AbsoluteDuration(duration
);
2538 // Years to seconds parts are all safe integers for valid durations.
2539 MOZ_ASSERT(years
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2540 MOZ_ASSERT(months
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2541 MOZ_ASSERT(weeks
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2542 MOZ_ASSERT(days
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2543 MOZ_ASSERT(hours
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2544 MOZ_ASSERT(minutes
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2545 MOZ_ASSERT(seconds
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2547 auto secondsDuration
= NormalizeTimeDuration(0.0, 0.0, seconds
, milliseconds
,
2548 microseconds
, nanoseconds
);
2551 int32_t sign
= DurationSign(duration
);
2554 JSStringBuilder
result(cx
);
2556 // Step 13. (Reordered)
2558 if (!result
.append('-')) {
2563 // Step 14. (Reordered)
2564 if (!result
.append('P')) {
2570 if (!NumberToStringBuilder(cx
, years
, result
)) {
2573 if (!result
.append('Y')) {
2580 if (!NumberToStringBuilder(cx
, months
, result
)) {
2583 if (!result
.append('M')) {
2590 if (!NumberToStringBuilder(cx
, weeks
, result
)) {
2593 if (!result
.append('W')) {
2600 if (!NumberToStringBuilder(cx
, days
, result
)) {
2603 if (!result
.append('D')) {
2608 // Step 7. (Moved above)
2610 // Steps 10-11. (Reordered)
2611 bool zeroMinutesAndHigher
= years
== 0 && months
== 0 && weeks
== 0 &&
2612 days
== 0 && hours
== 0 && minutes
== 0;
2614 // Steps 8-9, 12, and 15.
2615 bool hasSecondsPart
= (secondsDuration
!= NormalizedTimeDuration
{}) ||
2616 zeroMinutesAndHigher
|| precision
!= Precision::Auto();
2617 if (hours
!= 0 || minutes
!= 0 || hasSecondsPart
) {
2618 // Step 15. (Reordered)
2619 if (!result
.append('T')) {
2625 if (!NumberToStringBuilder(cx
, hours
, result
)) {
2628 if (!result
.append('H')) {
2635 if (!NumberToStringBuilder(cx
, minutes
, result
)) {
2638 if (!result
.append('M')) {
2644 if (hasSecondsPart
) {
2646 if (!NumberToStringBuilder(cx
, double(secondsDuration
.seconds
), result
)) {
2651 if (!FormatFractionalSeconds(result
, secondsDuration
.nanoseconds
,
2657 if (!result
.append('S')) {
2663 // Steps 13-15. (Moved above)
2666 return result
.finishString();
2670 * ToRelativeTemporalObject ( options )
2672 static bool ToRelativeTemporalObject(
2673 JSContext
* cx
, Handle
<JSObject
*> options
,
2674 MutableHandle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2675 MutableHandle
<ZonedDateTime
> zonedRelativeTo
,
2676 MutableHandle
<TimeZoneRecord
> timeZoneRecord
) {
2678 Rooted
<Value
> value(cx
);
2679 if (!GetProperty(cx
, options
, options
, cx
->names().relativeTo
, &value
)) {
2684 if (value
.isUndefined()) {
2685 // FIXME: spec issue - switch return record fields for consistency.
2686 // FIXME: spec bug - [[TimeZoneRec]] field not created
2688 plainRelativeTo
.set(nullptr);
2689 zonedRelativeTo
.set(ZonedDateTime
{});
2690 timeZoneRecord
.set(TimeZoneRecord
{});
2695 auto offsetBehaviour
= OffsetBehaviour::Option
;
2698 auto matchBehaviour
= MatchBehaviour::MatchExactly
;
2701 PlainDateTime dateTime
;
2702 Rooted
<CalendarValue
> calendar(cx
);
2703 Rooted
<TimeZoneValue
> timeZone(cx
);
2705 if (value
.isObject()) {
2706 Rooted
<JSObject
*> obj(cx
, &value
.toObject());
2709 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
2710 auto instant
= ToInstant(zonedDateTime
);
2711 Rooted
<TimeZoneValue
> timeZone(cx
, zonedDateTime
->timeZone());
2712 Rooted
<CalendarValue
> calendar(cx
, zonedDateTime
->calendar());
2714 if (!timeZone
.wrap(cx
)) {
2717 if (!calendar
.wrap(cx
)) {
2722 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2723 if (!CreateTimeZoneMethodsRecord(
2726 TimeZoneMethod::GetOffsetNanosecondsFor
,
2727 TimeZoneMethod::GetPossibleInstantsFor
,
2734 plainRelativeTo
.set(nullptr);
2735 zonedRelativeTo
.set(ZonedDateTime
{instant
, timeZone
, calendar
});
2736 timeZoneRecord
.set(timeZoneRec
);
2741 if (obj
->canUnwrapAs
<PlainDateObject
>()) {
2742 plainRelativeTo
.set(obj
);
2743 zonedRelativeTo
.set(ZonedDateTime
{});
2744 timeZoneRecord
.set(TimeZoneRecord
{});
2749 if (auto* dateTime
= obj
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
2750 auto plainDateTime
= ToPlainDate(dateTime
);
2752 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
2753 if (!calendar
.wrap(cx
)) {
2758 auto* plainDate
= CreateTemporalDate(cx
, plainDateTime
, calendar
);
2764 plainRelativeTo
.set(plainDate
);
2765 zonedRelativeTo
.set(ZonedDateTime
{});
2766 timeZoneRecord
.set(TimeZoneRecord
{});
2771 if (!GetTemporalCalendarWithISODefault(cx
, obj
, &calendar
)) {
2776 Rooted
<CalendarRecord
> calendarRec(cx
);
2777 if (!CreateCalendarMethodsRecord(cx
, calendar
,
2779 CalendarMethod::DateFromFields
,
2780 CalendarMethod::Fields
,
2787 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2788 if (!CalendarFields(cx
, calendarRec
,
2789 {CalendarField::Day
, CalendarField::Month
,
2790 CalendarField::MonthCode
, CalendarField::Year
},
2796 if (!AppendSorted(cx
, fieldNames
.get(),
2798 TemporalField::Hour
,
2799 TemporalField::Microsecond
,
2800 TemporalField::Millisecond
,
2801 TemporalField::Minute
,
2802 TemporalField::Nanosecond
,
2803 TemporalField::Offset
,
2804 TemporalField::Second
,
2805 TemporalField::TimeZone
,
2811 Rooted
<PlainObject
*> fields(cx
, PrepareTemporalFields(cx
, obj
, fieldNames
));
2817 Rooted
<PlainObject
*> dateOptions(cx
, NewPlainObjectWithProto(cx
, nullptr));
2823 Rooted
<Value
> overflow(cx
, StringValue(cx
->names().constrain
));
2824 if (!DefineDataProperty(cx
, dateOptions
, cx
->names().overflow
, overflow
)) {
2829 if (!InterpretTemporalDateTimeFields(cx
, calendarRec
, fields
, dateOptions
,
2835 Rooted
<Value
> offset(cx
);
2836 if (!GetProperty(cx
, fields
, fields
, cx
->names().offset
, &offset
)) {
2841 Rooted
<Value
> timeZoneValue(cx
);
2842 if (!GetProperty(cx
, fields
, fields
, cx
->names().timeZone
,
2848 if (!timeZoneValue
.isUndefined()) {
2849 if (!ToTemporalTimeZone(cx
, timeZoneValue
, &timeZone
)) {
2855 if (offset
.isUndefined()) {
2856 offsetBehaviour
= OffsetBehaviour::Wall
;
2861 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2862 MOZ_ASSERT(!offset
.isUndefined());
2863 MOZ_ASSERT(offset
.isString());
2866 Rooted
<JSString
*> offsetString(cx
, offset
.toString());
2867 if (!offsetString
) {
2872 if (!ParseDateTimeUTCOffset(cx
, offsetString
, &offsetNs
)) {
2882 if (!value
.isString()) {
2883 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
2884 nullptr, "not a string");
2887 Rooted
<JSString
*> string(cx
, value
.toString());
2892 int64_t timeZoneOffset
;
2893 Rooted
<ParsedTimeZone
> timeZoneName(cx
);
2894 Rooted
<JSString
*> calendarString(cx
);
2895 if (!ParseTemporalRelativeToString(cx
, string
, &dateTime
, &isUTC
,
2896 &hasOffset
, &timeZoneOffset
,
2897 &timeZoneName
, &calendarString
)) {
2901 // Step 6.c. (Not applicable in our implementation.)
2906 if (!ToTemporalTimeZone(cx
, timeZoneName
, &timeZone
)) {
2910 // Steps 6.f.ii-iii.
2912 offsetBehaviour
= OffsetBehaviour::Exact
;
2913 } else if (!hasOffset
) {
2914 offsetBehaviour
= OffsetBehaviour::Wall
;
2918 matchBehaviour
= MatchBehaviour::MatchMinutes
;
2920 MOZ_ASSERT(!timeZone
);
2924 if (calendarString
) {
2925 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
2929 calendar
.set(CalendarValue(cx
->names().iso8601
));
2934 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2935 MOZ_ASSERT(hasOffset
);
2938 offsetNs
= timeZoneOffset
;
2949 auto* plainDate
= CreateTemporalDate(cx
, dateTime
.date
, calendar
);
2954 plainRelativeTo
.set(plainDate
);
2955 zonedRelativeTo
.set(ZonedDateTime
{});
2956 timeZoneRecord
.set(TimeZoneRecord
{});
2960 // Steps 8-9. (Moved above)
2963 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2964 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2966 TimeZoneMethod::GetOffsetNanosecondsFor
,
2967 TimeZoneMethod::GetPossibleInstantsFor
,
2974 Instant epochNanoseconds
;
2975 if (!InterpretISODateTimeOffset(
2976 cx
, dateTime
, offsetBehaviour
, offsetNs
, timeZoneRec
,
2977 TemporalDisambiguation::Compatible
, TemporalOffset::Reject
,
2978 matchBehaviour
, &epochNanoseconds
)) {
2981 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds
));
2984 plainRelativeTo
.set(nullptr);
2985 zonedRelativeTo
.set(ZonedDateTime
{epochNanoseconds
, timeZone
, calendar
});
2986 timeZoneRecord
.set(timeZoneRec
);
2991 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
2994 static bool CreateCalendarMethodsRecordFromRelativeTo(
2995 JSContext
* cx
, Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2996 Handle
<ZonedDateTime
> zonedRelativeTo
,
2997 mozilla::EnumSet
<CalendarMethod
> methods
,
2998 MutableHandle
<CalendarRecord
> result
) {
3000 if (zonedRelativeTo
) {
3001 return CreateCalendarMethodsRecord(cx
, zonedRelativeTo
.calendar(), methods
,
3006 if (plainRelativeTo
) {
3007 auto* unwrapped
= plainRelativeTo
.unwrap(cx
);
3012 Rooted
<CalendarValue
> calendar(cx
, unwrapped
->calendar());
3013 if (!calendar
.wrap(cx
)) {
3017 return CreateCalendarMethodsRecord(cx
, calendar
, methods
, result
);
3024 struct RoundedNumber final
{
3030 * RoundNumberToIncrement ( x, increment, roundingMode )
3032 static RoundedNumber
TruncateNumber(int64_t numerator
, int64_t denominator
) {
3033 // Computes the quotient and real number value of the rational number
3034 // |numerator / denominator|.
3036 // Int64 division truncates.
3037 int64_t quot
= numerator
/ denominator
;
3038 int64_t rem
= numerator
% denominator
;
3040 // The total value is stored as a mathematical number in the draft proposal,
3041 // so we can't convert it to a double without loss of precision. We use two
3042 // different approaches to compute the total value based on the input range.
3046 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3047 // is |16.66668333...| and the best possible approximation is
3048 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3049 // numerator and denominator to doubles and then performing a double division.
3051 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3052 // can't use double division, because |14400000000000001| can't be represented
3053 // as an exact double value. The exact result is |4000.0000000000002777...|.
3055 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3056 // be computed through |q + r / denominator|.
3058 if (::IsSafeInteger(numerator
) && ::IsSafeInteger(denominator
)) {
3059 total
= double(numerator
) / double(denominator
);
3061 total
= double(quot
) + double(rem
) / double(denominator
);
3063 return {Int128
{quot
}, total
};
3067 * RoundNumberToIncrement ( x, increment, roundingMode )
3069 static RoundedNumber
TruncateNumber(const Int128
& numerator
,
3070 const Int128
& denominator
) {
3071 MOZ_ASSERT(denominator
> Int128
{});
3072 MOZ_ASSERT(numerator
> Int128
{INT64_MAX
} || denominator
> Int128
{INT64_MAX
},
3073 "small values use the int64 overload");
3075 // Int128 division truncates.
3076 auto [quot
, rem
] = numerator
.divrem(denominator
);
3078 double total
= double(quot
) + double(rem
) / double(denominator
);
3079 return {quot
, total
};
3082 struct RoundedDuration final
{
3083 NormalizedDuration duration
;
3087 enum class ComputeRemainder
: bool { No
, Yes
};
3090 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3092 static NormalizedTimeDuration
RoundNormalizedTimeDurationToIncrement(
3093 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
,
3094 Increment increment
, TemporalRoundingMode roundingMode
) {
3095 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3096 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3097 MOZ_ASSERT(increment
<= MaximumTemporalDurationRoundingIncrement(unit
));
3099 int64_t divisor
= ToNanoseconds(unit
) * increment
.value();
3100 MOZ_ASSERT(divisor
> 0);
3101 MOZ_ASSERT(divisor
<= ToNanoseconds(TemporalUnit::Day
));
3103 auto totalNanoseconds
= duration
.toTotalNanoseconds();
3105 RoundNumberToIncrement(totalNanoseconds
, Int128
{divisor
}, roundingMode
);
3106 return NormalizedTimeDuration::fromNanoseconds(rounded
);
3110 * DivideNormalizedTimeDuration ( d, divisor )
3112 static double TotalNormalizedTimeDuration(
3113 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
) {
3114 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3115 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3117 // Compute real number value of the rational number |numerator / denominator|.
3119 auto numerator
= duration
.toTotalNanoseconds();
3120 auto denominator
= ToNanoseconds(unit
);
3121 MOZ_ASSERT(::IsSafeInteger(denominator
));
3123 // The total value is stored as a mathematical number in the draft proposal,
3124 // so we can't convert it to a double without loss of precision. We use two
3125 // different approaches to compute the total value based on the input range.
3129 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3130 // is |16.66668333...| and the best possible approximation is
3131 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3132 // numerator and denominator to doubles and then performing a double division.
3134 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3135 // can't use double division, because |14400000000000001| can't be represented
3136 // as an exact double value. The exact result is |4000.0000000000002777...|.
3138 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3139 // be computed through |q + r / denominator|.
3140 if (::IsSafeInteger(numerator
)) {
3141 return double(numerator
) / double(denominator
);
3144 auto [q
, r
] = numerator
.divrem(Int128
{denominator
});
3145 return double(q
) + double(r
) / double(denominator
);
3149 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3150 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3151 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3153 NormalizedTimeDuration
js::temporal::RoundDuration(
3154 const NormalizedTimeDuration
& duration
, Increment increment
,
3155 TemporalUnit unit
, TemporalRoundingMode roundingMode
) {
3156 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3157 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3159 // Steps 1-13. (Not applicable)
3162 auto rounded
= RoundNormalizedTimeDurationToIncrement(
3163 duration
, unit
, increment
, roundingMode
);
3164 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded
));
3170 static int64_t TruncateDays(const NormalizedTimeAndDays
& timeAndDays
,
3171 int64_t days
, int32_t daysToAdd
) {
3173 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3174 static constexpr int64_t durationDays
= (int64_t(1) << 53) / (24 * 60 * 60);
3176 // Numbers of days between nsMinInstant and nsMaxInstant.
3177 static constexpr int32_t epochDays
= 200'000'000;
3180 MOZ_ASSERT(std::abs(days
) <= durationDays
);
3181 MOZ_ASSERT(std::abs(timeAndDays
.days
) <= durationDays
);
3182 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
);
3184 static_assert(durationDays
+ durationDays
+ epochDays
<= INT64_MAX
,
3185 "addition can't overflow");
3187 int64_t totalDays
= days
+ timeAndDays
.days
+ daysToAdd
;
3189 int64_t truncatedDays
= totalDays
;
3190 if (timeAndDays
.time
> 0) {
3191 // Round toward positive infinity when the integer days are negative and
3192 // the fractional part is positive.
3193 if (truncatedDays
< 0) {
3196 } else if (timeAndDays
.time
< 0) {
3197 // Round toward negative infinity when the integer days are positive and
3198 // the fractional part is negative.
3199 if (truncatedDays
> 0) {
3204 return truncatedDays
;
3207 static bool DaysIsNegative(int64_t days
,
3208 const NormalizedTimeAndDays
& timeAndDays
,
3209 int32_t daysToAdd
) {
3210 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3211 static constexpr int64_t durationDays
= (int64_t(1) << 53) / (24 * 60 * 60);
3213 // Numbers of days between nsMinInstant and nsMaxInstant.
3214 static constexpr int32_t epochDays
= 200'000'000;
3216 MOZ_ASSERT(std::abs(days
) <= durationDays
);
3217 MOZ_ASSERT(std::abs(timeAndDays
.days
) <= durationDays
);
3218 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3220 static_assert(durationDays
+ durationDays
+ epochDays
* 2 <= INT64_MAX
,
3221 "addition can't overflow");
3223 int64_t totalDays
= days
+ timeAndDays
.days
+ daysToAdd
;
3224 return totalDays
< 0 || (totalDays
== 0 && timeAndDays
.time
< 0);
3227 static RoundedNumber
RoundNumberToIncrement(
3228 int64_t durationAmount
, int64_t amountPassed
, int64_t durationDays
,
3229 int32_t daysToAdd
, const NormalizedTimeAndDays
& timeAndDays
,
3230 int32_t oneUnitDays
, Increment increment
, TemporalRoundingMode roundingMode
,
3231 ComputeRemainder computeRemainder
) {
3233 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3234 static constexpr int64_t maxDurationDays
=
3235 (int64_t(1) << 53) / (24 * 60 * 60);
3237 // Numbers of days between nsMinInstant and nsMaxInstant.
3238 static constexpr int32_t epochDays
= 200'000'000;
3241 MOZ_ASSERT(std::abs(durationAmount
) < (int64_t(1) << 32));
3242 MOZ_ASSERT(std::abs(amountPassed
) < (int64_t(1) << 32));
3243 MOZ_ASSERT(std::abs(durationDays
) <= maxDurationDays
);
3244 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3245 MOZ_ASSERT(timeAndDays
.dayLength
> 0);
3246 MOZ_ASSERT(timeAndDays
.dayLength
< (int64_t(1) << 53));
3247 MOZ_ASSERT(std::abs(timeAndDays
.time
) < timeAndDays
.dayLength
);
3248 MOZ_ASSERT(std::abs(timeAndDays
.days
) <= maxDurationDays
);
3249 MOZ_ASSERT(oneUnitDays
!= 0);
3250 MOZ_ASSERT(std::abs(oneUnitDays
) <= epochDays
);
3251 MOZ_ASSERT(increment
<= Increment::max());
3255 // Change the representation of |fractionalWeeks| from a real number to a
3256 // rational number, because we don't support arbitrary precision real
3259 // |fractionalWeeks| is defined as:
3262 // = weeks + days' / abs(oneWeekDays)
3264 // where days' = days + nanoseconds / dayLength.
3266 // The fractional part |nanoseconds / dayLength| is from step 4.
3268 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3271 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3272 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3273 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3278 auto dayLength
= mozilla::CheckedInt64(timeAndDays
.dayLength
);
3280 auto denominator
= dayLength
* std::abs(oneUnitDays
);
3281 if (!denominator
.isValid()) {
3285 auto totalDays
= mozilla::CheckedInt64(durationDays
);
3286 totalDays
+= timeAndDays
.days
;
3287 totalDays
+= daysToAdd
;
3288 MOZ_ASSERT(totalDays
.isValid());
3290 auto totalAmount
= mozilla::CheckedInt64(durationAmount
) + amountPassed
;
3291 MOZ_ASSERT(totalAmount
.isValid());
3293 auto amountNanos
= denominator
* totalAmount
;
3294 if (!amountNanos
.isValid()) {
3298 auto totalNanoseconds
= dayLength
* totalDays
;
3299 totalNanoseconds
+= timeAndDays
.time
;
3300 totalNanoseconds
+= amountNanos
;
3301 if (!totalNanoseconds
.isValid()) {
3305 if (computeRemainder
== ComputeRemainder::Yes
) {
3306 return TruncateNumber(totalNanoseconds
.value(), denominator
.value());
3309 auto rounded
= RoundNumberToIncrement(
3310 totalNanoseconds
.value(), denominator
.value(), increment
, roundingMode
);
3311 constexpr double total
= 0;
3312 return {rounded
, total
};
3315 // Use int128 when values are too large for int64. Additionally assert all
3316 // values fit into int128.
3318 // `dayLength` < 2**53
3319 auto dayLength
= Int128
{timeAndDays
.dayLength
};
3320 MOZ_ASSERT(dayLength
< Int128
{1} << 53);
3322 // `abs(oneUnitDays)` < 200'000'000, log2(200'000'000) = ~27.57.
3323 auto denominator
= dayLength
* Int128
{std::abs(oneUnitDays
)};
3324 MOZ_ASSERT(denominator
< Int128
{1} << (53 + 28));
3326 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3328 // `abs(maxDurationDays)` ≤ 2**(53 - 16).
3329 // `abs(timeAndDays.days)` ≤ 2**(53 - 16).
3330 // `abs(daysToAdd)` ≤ 2**29.
3332 // 2**(53 - 16) + 2**(53 - 16) + 2**29
3333 // = 2**37 + 2**37 + 2**29
3336 auto totalDays
= Int128
{durationDays
};
3337 totalDays
+= Int128
{timeAndDays
.days
};
3338 totalDays
+= Int128
{daysToAdd
};
3339 MOZ_ASSERT(totalDays
.abs() <= Uint128
{1} << 39);
3341 // `abs(durationAmount)` ≤ 2**32
3342 // `abs(amountPassed)` ≤ 2**32
3343 auto totalAmount
= Int128
{durationAmount
} + Int128
{amountPassed
};
3344 MOZ_ASSERT(totalAmount
.abs() <= Uint128
{1} << 33);
3346 // `denominator` < 2**(53 + 28)
3347 // `abs(totalAmount)` <= 2**33
3349 // `denominator * totalAmount`
3350 // ≤ 2**(53 + 28) * 2**33
3351 // = 2**(53 + 28 + 33)
3353 auto amountNanos
= denominator
* totalAmount
;
3354 MOZ_ASSERT(amountNanos
.abs() <= Uint128
{1} << 114);
3356 // `dayLength` < 2**53
3357 // `totalDays` ≤ 2**39
3358 // `timeAndDays.time` < `dayLength` < 2**53
3359 // `amountNanos` ≤ 2**114
3361 // `dayLength * totalDays`
3362 // ≤ 2**(53 + 39) = 2**92
3364 // `dayLength * totalDays + timeAndDays.time`
3367 // `dayLength * totalDays + timeAndDays.time + amountNanos`
3369 auto totalNanoseconds
= dayLength
* totalDays
;
3370 totalNanoseconds
+= Int128
{timeAndDays
.time
};
3371 totalNanoseconds
+= amountNanos
;
3372 MOZ_ASSERT(totalNanoseconds
.abs() <= Uint128
{1} << 115);
3374 if (computeRemainder
== ComputeRemainder::Yes
) {
3375 return TruncateNumber(totalNanoseconds
, denominator
);
3378 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, denominator
,
3379 increment
, roundingMode
);
3380 constexpr double total
= 0;
3381 return {rounded
, total
};
3384 static RoundedNumber
RoundNumberToIncrement(
3385 double durationDays
, const NormalizedTimeAndDays
& timeAndDays
,
3386 Increment increment
, TemporalRoundingMode roundingMode
,
3387 ComputeRemainder computeRemainder
) {
3388 constexpr int64_t daysAmount
= 0;
3389 constexpr int64_t daysPassed
= 0;
3390 constexpr int32_t oneDayDays
= 1;
3391 constexpr int32_t daysToAdd
= 0;
3393 return RoundNumberToIncrement(daysAmount
, daysPassed
, durationDays
, daysToAdd
,
3394 timeAndDays
, oneDayDays
, increment
,
3395 roundingMode
, computeRemainder
);
3398 static bool RoundDurationYear(JSContext
* cx
, const NormalizedDuration
& duration
,
3399 const NormalizedTimeAndDays
& timeAndDays
,
3400 Increment increment
,
3401 TemporalRoundingMode roundingMode
,
3402 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3403 Handle
<CalendarRecord
> calendar
,
3404 ComputeRemainder computeRemainder
,
3405 RoundedDuration
* result
) {
3406 // Numbers of days between nsMinInstant and nsMaxInstant.
3407 static constexpr int32_t epochDays
= 200'000'000;
3409 auto [years
, months
, weeks
, days
] = duration
.date
;
3412 Duration yearsDuration
= {double(years
)};
3415 auto yearsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsDuration
);
3419 auto yearsLaterDate
= ToPlainDate(&yearsLater
.unwrap());
3421 // Step 10.f. (Reordered)
3422 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsLater
);
3425 Duration yearsMonthsWeeks
= {double(years
), double(months
), double(weeks
)};
3428 PlainDate yearsMonthsWeeksLater
;
3429 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3430 &yearsMonthsWeeksLater
)) {
3435 int32_t monthsWeeksInDays
= DaysUntil(yearsLaterDate
, yearsMonthsWeeksLater
);
3436 MOZ_ASSERT(std::abs(monthsWeeksInDays
) <= epochDays
);
3438 // Step 10.f. (Moved up)
3441 // Our implementation keeps |days| and |monthsWeeksInDays| separate.
3443 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3444 // https://github.com/tc39/proposal-temporal/issues/2540
3447 int64_t truncatedDays
= TruncateDays(timeAndDays
, days
, monthsWeeksInDays
);
3449 PlainDate isoResult
;
3450 if (!AddISODate(cx
, yearsLaterDate
, {0, 0, 0, truncatedDays
},
3451 TemporalOverflow::Constrain
, &isoResult
)) {
3456 Rooted
<PlainDateObject
*> wholeDaysLater(
3457 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3458 if (!wholeDaysLater
) {
3463 Duration timePassed
;
3464 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3465 TemporalUnit::Year
, &timePassed
)) {
3470 int64_t yearsPassed
= int64_t(timePassed
.years
);
3473 // Our implementation keeps |years| and |yearsPassed| separate.
3476 Duration yearsPassedDuration
= {double(yearsPassed
)};
3480 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, yearsPassedDuration
,
3481 &newRelativeTo
, &daysPassed
)) {
3484 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3488 // Our implementation keeps |days| and |daysPassed| separate.
3489 int32_t daysToAdd
= monthsWeeksInDays
- daysPassed
;
3490 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3493 double sign
= DaysIsNegative(days
, timeAndDays
, daysToAdd
) ? -1 : 1;
3496 Duration oneYear
= {sign
};
3499 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3500 int32_t oneYearDays
;
3501 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneYear
,
3502 &moveResultIgnored
, &oneYearDays
)) {
3507 if (oneYearDays
== 0) {
3508 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3509 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3514 auto [numYears
, total
] = RoundNumberToIncrement(
3515 years
, yearsPassed
, days
, daysToAdd
, timeAndDays
, oneYearDays
, increment
,
3516 roundingMode
, computeRemainder
);
3519 int64_t numMonths
= 0;
3520 int64_t numWeeks
= 0;
3523 constexpr auto time
= NormalizedTimeDuration
{};
3526 if (numYears
.abs() >= (Uint128
{1} << 32)) {
3527 return ThrowInvalidDurationPart(cx
, double(numYears
), "years",
3528 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3531 auto resultDuration
= DateDuration
{int64_t(numYears
), numMonths
, numWeeks
};
3532 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3536 *result
= {{resultDuration
, time
}, total
};
3540 static bool RoundDurationMonth(JSContext
* cx
,
3541 const NormalizedDuration
& duration
,
3542 const NormalizedTimeAndDays
& timeAndDays
,
3543 Increment increment
,
3544 TemporalRoundingMode roundingMode
,
3545 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3546 Handle
<CalendarRecord
> calendar
,
3547 ComputeRemainder computeRemainder
,
3548 RoundedDuration
* result
) {
3549 // Numbers of days between nsMinInstant and nsMaxInstant.
3550 static constexpr int32_t epochDays
= 200'000'000;
3552 auto [years
, months
, weeks
, days
] = duration
.date
;
3555 Duration yearsMonths
= {double(years
), double(months
)};
3558 auto yearsMonthsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsMonths
);
3559 if (!yearsMonthsLater
) {
3562 auto yearsMonthsLaterDate
= ToPlainDate(&yearsMonthsLater
.unwrap());
3564 // Step 11.f. (Reordered)
3565 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsMonthsLater
);
3568 Duration yearsMonthsWeeks
= {double(years
), double(months
), double(weeks
)};
3571 PlainDate yearsMonthsWeeksLater
;
3572 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3573 &yearsMonthsWeeksLater
)) {
3578 int32_t weeksInDays
= DaysUntil(yearsMonthsLaterDate
, yearsMonthsWeeksLater
);
3579 MOZ_ASSERT(std::abs(weeksInDays
) <= epochDays
);
3581 // Step 11.f. (Moved up)
3584 // Our implementation keeps |days| and |weeksInDays| separate.
3586 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3587 // https://github.com/tc39/proposal-temporal/issues/2540
3590 int64_t truncatedDays
= TruncateDays(timeAndDays
, days
, weeksInDays
);
3592 PlainDate isoResult
;
3593 if (!AddISODate(cx
, yearsMonthsLaterDate
, {0, 0, 0, truncatedDays
},
3594 TemporalOverflow::Constrain
, &isoResult
)) {
3599 Rooted
<PlainDateObject
*> wholeDaysLater(
3600 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3601 if (!wholeDaysLater
) {
3606 Duration timePassed
;
3607 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3608 TemporalUnit::Month
, &timePassed
)) {
3613 int64_t monthsPassed
= int64_t(timePassed
.months
);
3616 // Our implementation keeps |months| and |monthsPassed| separate.
3619 Duration monthsPassedDuration
= {0, double(monthsPassed
)};
3623 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, monthsPassedDuration
,
3624 &newRelativeTo
, &daysPassed
)) {
3627 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3631 // Our implementation keeps |days| and |daysPassed| separate.
3632 int32_t daysToAdd
= weeksInDays
- daysPassed
;
3633 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
* 2);
3636 double sign
= DaysIsNegative(days
, timeAndDays
, daysToAdd
) ? -1 : 1;
3639 Duration oneMonth
= {0, sign
};
3642 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3643 int32_t oneMonthDays
;
3644 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneMonth
,
3645 &moveResultIgnored
, &oneMonthDays
)) {
3650 if (oneMonthDays
== 0) {
3651 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3652 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3657 auto [numMonths
, total
] = RoundNumberToIncrement(
3658 months
, monthsPassed
, days
, daysToAdd
, timeAndDays
, oneMonthDays
,
3659 increment
, roundingMode
, computeRemainder
);
3662 int64_t numWeeks
= 0;
3665 constexpr auto time
= NormalizedTimeDuration
{};
3668 if (numMonths
.abs() >= (Uint128
{1} << 32)) {
3669 return ThrowInvalidDurationPart(cx
, double(numMonths
), "months",
3670 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3673 auto resultDuration
= DateDuration
{years
, int64_t(numMonths
), numWeeks
};
3674 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3678 *result
= {{resultDuration
, time
}, total
};
3682 static bool RoundDurationWeek(JSContext
* cx
, const NormalizedDuration
& duration
,
3683 const NormalizedTimeAndDays
& timeAndDays
,
3684 Increment increment
,
3685 TemporalRoundingMode roundingMode
,
3686 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3687 Handle
<CalendarRecord
> calendar
,
3688 ComputeRemainder computeRemainder
,
3689 RoundedDuration
* result
) {
3690 // Numbers of days between nsMinInstant and nsMaxInstant.
3691 static constexpr int32_t epochDays
= 200'000'000;
3693 auto [years
, months
, weeks
, days
] = duration
.date
;
3695 auto* unwrappedRelativeTo
= dateRelativeTo
.unwrap(cx
);
3696 if (!unwrappedRelativeTo
) {
3699 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
3702 int64_t truncatedDays
= TruncateDays(timeAndDays
, days
, 0);
3704 PlainDate isoResult
;
3705 if (!AddISODate(cx
, relativeToDate
, {0, 0, 0, truncatedDays
},
3706 TemporalOverflow::Constrain
, &isoResult
)) {
3711 Rooted
<PlainDateObject
*> wholeDaysLater(
3712 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3713 if (!wholeDaysLater
) {
3718 Duration timePassed
;
3719 if (!DifferenceDate(cx
, calendar
, dateRelativeTo
, wholeDaysLater
,
3720 TemporalUnit::Week
, &timePassed
)) {
3725 int64_t weeksPassed
= int64_t(timePassed
.weeks
);
3728 // Our implementation keeps |weeks| and |weeksPassed| separate.
3731 Duration weeksPassedDuration
= {0, 0, double(weeksPassed
)};
3734 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
);
3736 if (!MoveRelativeDate(cx
, calendar
, dateRelativeTo
, weeksPassedDuration
,
3737 &newRelativeTo
, &daysPassed
)) {
3740 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3744 // Our implementation keeps |days| and |daysPassed| separate.
3745 int32_t daysToAdd
= -daysPassed
;
3746 MOZ_ASSERT(std::abs(daysToAdd
) <= epochDays
);
3749 double sign
= DaysIsNegative(days
, timeAndDays
, daysToAdd
) ? -1 : 1;
3752 Duration oneWeek
= {0, 0, sign
};
3755 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3756 int32_t oneWeekDays
;
3757 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneWeek
,
3758 &moveResultIgnored
, &oneWeekDays
)) {
3763 if (oneWeekDays
== 0) {
3764 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3765 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3770 auto [numWeeks
, total
] = RoundNumberToIncrement(
3771 weeks
, weeksPassed
, days
, daysToAdd
, timeAndDays
, oneWeekDays
, increment
,
3772 roundingMode
, computeRemainder
);
3775 constexpr auto time
= NormalizedTimeDuration
{};
3778 if (numWeeks
.abs() >= (Uint128
{1} << 32)) {
3779 return ThrowInvalidDurationPart(cx
, double(numWeeks
), "weeks",
3780 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3783 auto resultDuration
= DateDuration
{years
, months
, int64_t(numWeeks
)};
3784 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3788 *result
= {{resultDuration
, time
}, total
};
3792 static bool RoundDurationDay(JSContext
* cx
, const NormalizedDuration
& duration
,
3793 const NormalizedTimeAndDays
& timeAndDays
,
3794 Increment increment
,
3795 TemporalRoundingMode roundingMode
,
3796 ComputeRemainder computeRemainder
,
3797 RoundedDuration
* result
) {
3798 auto [years
, months
, weeks
, days
] = duration
.date
;
3801 auto [numDays
, total
] = RoundNumberToIncrement(
3802 days
, timeAndDays
, increment
, roundingMode
, computeRemainder
);
3804 MOZ_ASSERT(Int128
{INT64_MIN
} <= numDays
&& numDays
<= Int128
{INT64_MAX
},
3805 "rounded days fits in int64");
3808 constexpr auto time
= NormalizedTimeDuration
{};
3811 auto resultDuration
= DateDuration
{years
, months
, weeks
, int64_t(numDays
)};
3812 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3816 *result
= {{resultDuration
, time
}, total
};
3821 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3822 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3823 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3825 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
3826 Increment increment
, TemporalUnit unit
,
3827 TemporalRoundingMode roundingMode
,
3828 ComputeRemainder computeRemainder
,
3829 RoundedDuration
* result
) {
3830 // The remainder is only needed when called from |Duration_total|. And `total`
3831 // always passes |increment=1| and |roundingMode=trunc|.
3832 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3833 increment
== Increment
{1});
3834 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3835 roundingMode
== TemporalRoundingMode::Trunc
);
3837 // Steps 1-5. (Not applicable.)
3840 if (unit
<= TemporalUnit::Week
) {
3841 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3842 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3847 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3848 // because in that case this operation is a no-op. This case happens for
3849 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3852 // But maybe this can be even more efficiently handled in the callers. For
3853 // example when Temporal.PlainTime.prototype.{since,until} is called without
3854 // an options object, we can not only skip the RoundDuration call, but also
3855 // the following BalanceTimeDuration call.
3857 // Step 7. (Moved below.)
3859 // Steps 8-9. (Not applicable.)
3861 // Steps 10-12. (Not applicable.)
3864 if (unit
== TemporalUnit::Day
) {
3866 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3868 return RoundDurationDay(cx
, duration
, timeAndDays
, increment
, roundingMode
,
3869 computeRemainder
, result
);
3872 MOZ_ASSERT(TemporalUnit::Hour
<= unit
&& unit
<= TemporalUnit::Nanosecond
);
3875 auto time
= duration
.time
;
3877 if (computeRemainder
== ComputeRemainder::No
) {
3878 time
= RoundNormalizedTimeDurationToIncrement(time
, unit
, increment
,
3881 MOZ_ASSERT(increment
== Increment
{1});
3882 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
3884 total
= TotalNormalizedTimeDuration(duration
.time
, unit
);
3886 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
3889 MOZ_ASSERT(IsValidDuration(duration
.date
.toDuration()));
3890 *result
= {{duration
.date
, time
}, total
};
3895 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3896 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3897 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3899 static bool RoundDuration(
3900 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
3901 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
3902 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3903 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
3904 Handle
<TimeZoneRecord
> timeZone
,
3905 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
3906 ComputeRemainder computeRemainder
, RoundedDuration
* result
) {
3907 // Note: |duration.days| can have a different sign than the other date
3908 // components. The date and time components can have different signs, too.
3909 MOZ_ASSERT(IsValidDuration(Duration
{double(duration
.date
.years
),
3910 double(duration
.date
.months
),
3911 double(duration
.date
.weeks
)}));
3912 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
3914 MOZ_ASSERT(plainRelativeTo
|| zonedRelativeTo
,
3915 "Use RoundDuration without relativeTo when plainRelativeTo and "
3916 "zonedRelativeTo are both undefined");
3918 // The remainder is only needed when called from |Duration_total|. And `total`
3919 // always passes |increment=1| and |roundingMode=trunc|.
3920 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3921 increment
== Increment
{1});
3922 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3923 roundingMode
== TemporalRoundingMode::Trunc
);
3925 // Steps 1-5. (Not applicable in our implementation.)
3927 // Step 6.a. (Not applicable in our implementation.)
3928 MOZ_ASSERT_IF(unit
<= TemporalUnit::Week
, plainRelativeTo
);
3932 unit
<= TemporalUnit::Week
,
3933 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
3937 unit
<= TemporalUnit::Week
,
3938 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
3941 case TemporalUnit::Year
:
3942 case TemporalUnit::Month
:
3943 case TemporalUnit::Week
:
3945 case TemporalUnit::Day
:
3946 // We can't take the faster code path when |zonedRelativeTo| is present.
3947 if (zonedRelativeTo
) {
3951 case TemporalUnit::Hour
:
3952 case TemporalUnit::Minute
:
3953 case TemporalUnit::Second
:
3954 case TemporalUnit::Millisecond
:
3955 case TemporalUnit::Microsecond
:
3956 case TemporalUnit::Nanosecond
:
3957 // Steps 7-9 and 13-21.
3958 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
3959 computeRemainder
, result
);
3960 case TemporalUnit::Auto
:
3961 MOZ_CRASH("Unexpected temporal unit");
3965 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
);
3968 NormalizedTimeAndDays timeAndDays
;
3969 if (zonedRelativeTo
) {
3971 Rooted
<ZonedDateTime
> intermediate(cx
);
3972 if (!MoveRelativeZonedDateTime(cx
, zonedRelativeTo
, calendar
, timeZone
,
3973 duration
.date
, precalculatedPlainDateTime
,
3979 if (!NormalizedTimeDurationToDays(cx
, duration
.time
, intermediate
, timeZone
,
3984 // Step 7.a.iii. (Not applicable in our implementation.)
3986 // Step 7.b. (Partial)
3987 timeAndDays
= ::NormalizedTimeDurationToDays(duration
.time
);
3990 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3991 // less than |timeAndDays.dayLength|.
3992 MOZ_ASSERT(std::abs(timeAndDays
.time
) < timeAndDays
.dayLength
);
3994 // Step 7.c. (Moved below)
3996 // Step 8. (Not applicable)
3999 // FIXME: spec issue - `total` doesn't need be initialised.
4004 case TemporalUnit::Year
:
4005 return RoundDurationYear(cx
, duration
, timeAndDays
, increment
,
4006 roundingMode
, plainRelativeTo
, calendar
,
4007 computeRemainder
, result
);
4010 case TemporalUnit::Month
:
4011 return RoundDurationMonth(cx
, duration
, timeAndDays
, increment
,
4012 roundingMode
, plainRelativeTo
, calendar
,
4013 computeRemainder
, result
);
4016 case TemporalUnit::Week
:
4017 return RoundDurationWeek(cx
, duration
, timeAndDays
, increment
,
4018 roundingMode
, plainRelativeTo
, calendar
,
4019 computeRemainder
, result
);
4022 case TemporalUnit::Day
:
4023 return RoundDurationDay(cx
, duration
, timeAndDays
, increment
,
4024 roundingMode
, computeRemainder
, result
);
4026 // Steps 14-19. (Handled elsewhere)
4027 case TemporalUnit::Auto
:
4028 case TemporalUnit::Hour
:
4029 case TemporalUnit::Minute
:
4030 case TemporalUnit::Second
:
4031 case TemporalUnit::Millisecond
:
4032 case TemporalUnit::Microsecond
:
4033 case TemporalUnit::Nanosecond
:
4037 MOZ_CRASH("Unexpected temporal unit");
4041 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4042 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4043 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4045 static bool RoundDuration(
4046 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4047 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4048 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4049 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4050 Handle
<TimeZoneRecord
> timeZone
,
4051 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4053 // Only called from |Duration_total|, which always passes |increment=1| and
4054 // |roundingMode=trunc|.
4055 MOZ_ASSERT(increment
== Increment
{1});
4056 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4058 RoundedDuration rounded
;
4059 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4060 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4061 precalculatedPlainDateTime
, ComputeRemainder::Yes
,
4066 *result
= rounded
.total
;
4071 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4072 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4073 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4075 static bool RoundDuration(
4076 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4077 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4078 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4079 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4080 Handle
<TimeZoneRecord
> timeZone
,
4081 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4082 NormalizedDuration
* result
) {
4083 RoundedDuration rounded
;
4084 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4085 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4086 precalculatedPlainDateTime
, ComputeRemainder::No
,
4091 *result
= rounded
.duration
;
4096 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4097 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4098 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4100 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4101 Increment increment
, TemporalUnit unit
,
4102 TemporalRoundingMode roundingMode
, double* result
) {
4103 MOZ_ASSERT(IsValidDuration(duration
));
4105 // Only called from |Duration_total|, which always passes |increment=1| and
4106 // |roundingMode=trunc|.
4107 MOZ_ASSERT(increment
== Increment
{1});
4108 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4110 RoundedDuration rounded
;
4111 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4112 ComputeRemainder::Yes
, &rounded
)) {
4116 *result
= rounded
.total
;
4121 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4122 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4123 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4125 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4126 Increment increment
, TemporalUnit unit
,
4127 TemporalRoundingMode roundingMode
,
4128 NormalizedDuration
* result
) {
4129 MOZ_ASSERT(IsValidDuration(duration
));
4131 RoundedDuration rounded
;
4132 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4133 ComputeRemainder::No
, &rounded
)) {
4137 *result
= rounded
.duration
;
4142 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4143 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4144 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4146 bool js::temporal::RoundDuration(
4147 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4148 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4149 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4150 Handle
<CalendarRecord
> calendar
, NormalizedDuration
* result
) {
4151 MOZ_ASSERT(IsValidDuration(duration
));
4153 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
, ZonedDateTime
{});
4154 Rooted
<TimeZoneRecord
> timeZone(cx
, TimeZoneRecord
{});
4155 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4156 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4157 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4158 precalculatedPlainDateTime
, result
);
4162 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4163 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4164 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4166 bool js::temporal::RoundDuration(
4167 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4168 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4169 Handle
<PlainDateObject
*> plainRelativeTo
, Handle
<CalendarRecord
> calendar
,
4170 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<TimeZoneRecord
> timeZone
,
4171 const PlainDateTime
& precalculatedPlainDateTime
,
4172 NormalizedDuration
* result
) {
4173 MOZ_ASSERT(IsValidDuration(duration
));
4175 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4176 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4177 mozilla::SomeRef(precalculatedPlainDateTime
), result
);
4180 enum class DurationOperation
{ Add
, Subtract
};
4183 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4186 static bool AddDurationToOrSubtractDurationFromDuration(
4187 JSContext
* cx
, DurationOperation operation
, const CallArgs
& args
) {
4188 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4189 auto duration
= ToDuration(durationObj
);
4191 // Step 1. (Not applicable in our implementation.)
4195 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
4199 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4200 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4201 Rooted
<TimeZoneRecord
> timeZone(cx
);
4202 if (args
.hasDefined(1)) {
4203 const char* name
= operation
== DurationOperation::Add
? "add" : "subtract";
4206 Rooted
<JSObject
*> options(cx
,
4207 RequireObjectArg(cx
, "options", name
, args
[1]));
4213 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4214 &zonedRelativeTo
, &timeZone
)) {
4217 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4218 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4222 Rooted
<CalendarRecord
> calendar(cx
);
4223 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4226 CalendarMethod::DateAdd
,
4227 CalendarMethod::DateUntil
,
4234 if (operation
== DurationOperation::Subtract
) {
4235 other
= other
.negate();
4239 if (plainRelativeTo
) {
4240 if (!AddDuration(cx
, duration
, other
, plainRelativeTo
, calendar
, &result
)) {
4243 } else if (zonedRelativeTo
) {
4244 if (!AddDuration(cx
, duration
, other
, zonedRelativeTo
, calendar
, timeZone
,
4249 if (!AddDuration(cx
, duration
, other
, &result
)) {
4255 auto* obj
= CreateTemporalDuration(cx
, result
);
4260 args
.rval().setObject(*obj
);
4265 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4266 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4269 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4270 CallArgs args
= CallArgsFromVp(argc
, vp
);
4273 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
4279 if (args
.hasDefined(0) &&
4280 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
4286 if (args
.hasDefined(1) &&
4287 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
4293 if (args
.hasDefined(2) &&
4294 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
4300 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
4306 if (args
.hasDefined(4) &&
4307 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
4313 if (args
.hasDefined(5) &&
4314 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
4320 if (args
.hasDefined(6) &&
4321 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
4326 double milliseconds
= 0;
4327 if (args
.hasDefined(7) &&
4328 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
4333 double microseconds
= 0;
4334 if (args
.hasDefined(8) &&
4335 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
4340 double nanoseconds
= 0;
4341 if (args
.hasDefined(9) &&
4342 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
4347 auto* duration
= CreateTemporalDuration(
4349 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
4350 microseconds
, nanoseconds
});
4355 args
.rval().setObject(*duration
);
4360 * Temporal.Duration.from ( item )
4362 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4363 CallArgs args
= CallArgsFromVp(argc
, vp
);
4365 Handle
<Value
> item
= args
.get(0);
4368 if (item
.isObject()) {
4369 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
4370 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
4375 args
.rval().setObject(*result
);
4381 auto result
= ToTemporalDuration(cx
, item
);
4386 args
.rval().setObject(*result
);
4391 * Temporal.Duration.compare ( one, two [ , options ] )
4393 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4394 CallArgs args
= CallArgsFromVp(argc
, vp
);
4398 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
4404 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
4409 Rooted
<JSObject
*> options(cx
);
4410 if (args
.hasDefined(2)) {
4411 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
4419 args
.rval().setInt32(0);
4424 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4425 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4426 Rooted
<TimeZoneRecord
> timeZone(cx
);
4428 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4429 &zonedRelativeTo
, &timeZone
)) {
4432 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4433 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4437 auto hasCalendarUnit
= [](const auto& d
) {
4438 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
4440 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
4443 Rooted
<CalendarRecord
> calendar(cx
);
4444 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4447 CalendarMethod::DateAdd
,
4454 if (zonedRelativeTo
&&
4455 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
4457 const auto& instant
= zonedRelativeTo
.instant();
4460 PlainDateTime dateTime
;
4461 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
4466 auto normalized1
= CreateNormalizedDurationRecord(one
);
4469 auto normalized2
= CreateNormalizedDurationRecord(two
);
4473 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
4474 dateTime
, &after1
)) {
4480 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
4481 dateTime
, &after2
)) {
4486 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
4491 double days1
, days2
;
4492 if (calendarUnitsPresent
) {
4493 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4496 DateDuration unbalanceResult1
;
4497 if (plainRelativeTo
) {
4498 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
4499 TemporalUnit::Day
, plainRelativeTo
,
4500 calendar
, &unbalanceResult1
)) {
4504 if (!UnbalanceDateDurationRelative(
4505 cx
, one
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult1
)) {
4508 MOZ_ASSERT(one
.toDateDuration() == unbalanceResult1
);
4512 DateDuration unbalanceResult2
;
4513 if (plainRelativeTo
) {
4514 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
4515 TemporalUnit::Day
, plainRelativeTo
,
4516 calendar
, &unbalanceResult2
)) {
4520 if (!UnbalanceDateDurationRelative(
4521 cx
, two
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult2
)) {
4524 MOZ_ASSERT(two
.toDateDuration() == unbalanceResult2
);
4528 days1
= unbalanceResult1
.days
;
4531 days2
= unbalanceResult2
.days
;
4541 auto normalized1
= NormalizeTimeDuration(one
);
4544 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
4550 auto normalized2
= NormalizeTimeDuration(two
);
4553 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
4559 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
4564 * get Temporal.Duration.prototype.years
4566 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
4568 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4569 args
.rval().setNumber(duration
->years());
4574 * get Temporal.Duration.prototype.years
4576 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4578 CallArgs args
= CallArgsFromVp(argc
, vp
);
4579 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
4583 * get Temporal.Duration.prototype.months
4585 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
4587 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4588 args
.rval().setNumber(duration
->months());
4593 * get Temporal.Duration.prototype.months
4595 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4597 CallArgs args
= CallArgsFromVp(argc
, vp
);
4598 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
4602 * get Temporal.Duration.prototype.weeks
4604 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
4606 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4607 args
.rval().setNumber(duration
->weeks());
4612 * get Temporal.Duration.prototype.weeks
4614 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4616 CallArgs args
= CallArgsFromVp(argc
, vp
);
4617 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
4621 * get Temporal.Duration.prototype.days
4623 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
4625 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4626 args
.rval().setNumber(duration
->days());
4631 * get Temporal.Duration.prototype.days
4633 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4635 CallArgs args
= CallArgsFromVp(argc
, vp
);
4636 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
4640 * get Temporal.Duration.prototype.hours
4642 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
4644 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4645 args
.rval().setNumber(duration
->hours());
4650 * get Temporal.Duration.prototype.hours
4652 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4654 CallArgs args
= CallArgsFromVp(argc
, vp
);
4655 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
4659 * get Temporal.Duration.prototype.minutes
4661 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
4663 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4664 args
.rval().setNumber(duration
->minutes());
4669 * get Temporal.Duration.prototype.minutes
4671 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4673 CallArgs args
= CallArgsFromVp(argc
, vp
);
4674 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
4678 * get Temporal.Duration.prototype.seconds
4680 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
4682 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4683 args
.rval().setNumber(duration
->seconds());
4688 * get Temporal.Duration.prototype.seconds
4690 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4692 CallArgs args
= CallArgsFromVp(argc
, vp
);
4693 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
4697 * get Temporal.Duration.prototype.milliseconds
4699 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
4701 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4702 args
.rval().setNumber(duration
->milliseconds());
4707 * get Temporal.Duration.prototype.milliseconds
4709 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4711 CallArgs args
= CallArgsFromVp(argc
, vp
);
4712 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
4716 * get Temporal.Duration.prototype.microseconds
4718 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
4720 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4721 args
.rval().setNumber(duration
->microseconds());
4726 * get Temporal.Duration.prototype.microseconds
4728 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4730 CallArgs args
= CallArgsFromVp(argc
, vp
);
4731 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
4735 * get Temporal.Duration.prototype.nanoseconds
4737 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
4739 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4740 args
.rval().setNumber(duration
->nanoseconds());
4745 * get Temporal.Duration.prototype.nanoseconds
4747 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4749 CallArgs args
= CallArgsFromVp(argc
, vp
);
4750 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
4754 * get Temporal.Duration.prototype.sign
4756 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
4757 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4760 args
.rval().setInt32(DurationSign(duration
));
4765 * get Temporal.Duration.prototype.sign
4767 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4769 CallArgs args
= CallArgsFromVp(argc
, vp
);
4770 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
4774 * get Temporal.Duration.prototype.blank
4776 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
4777 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4780 args
.rval().setBoolean(duration
== Duration
{});
4785 * get Temporal.Duration.prototype.blank
4787 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4789 CallArgs args
= CallArgsFromVp(argc
, vp
);
4790 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
4794 * Temporal.Duration.prototype.with ( temporalDurationLike )
4796 * ToPartialDuration ( temporalDurationLike )
4798 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
4799 // Absent values default to the corresponding values of |this| object.
4800 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4803 Rooted
<JSObject
*> temporalDurationLike(
4804 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
4805 if (!temporalDurationLike
) {
4808 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
4813 auto* result
= CreateTemporalDuration(cx
, duration
);
4818 args
.rval().setObject(*result
);
4823 * Temporal.Duration.prototype.with ( temporalDurationLike )
4825 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4827 CallArgs args
= CallArgsFromVp(argc
, vp
);
4828 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
4832 * Temporal.Duration.prototype.negated ( )
4834 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
4835 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4838 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
4843 args
.rval().setObject(*result
);
4848 * Temporal.Duration.prototype.negated ( )
4850 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4852 CallArgs args
= CallArgsFromVp(argc
, vp
);
4853 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
4857 * Temporal.Duration.prototype.abs ( )
4859 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
4860 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4863 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
4868 args
.rval().setObject(*result
);
4873 * Temporal.Duration.prototype.abs ( )
4875 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4877 CallArgs args
= CallArgsFromVp(argc
, vp
);
4878 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
4882 * Temporal.Duration.prototype.add ( other [ , options ] )
4884 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
4885 return AddDurationToOrSubtractDurationFromDuration(cx
, DurationOperation::Add
,
4890 * Temporal.Duration.prototype.add ( other [ , options ] )
4892 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4894 CallArgs args
= CallArgsFromVp(argc
, vp
);
4895 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
4899 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4901 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
4902 return AddDurationToOrSubtractDurationFromDuration(
4903 cx
, DurationOperation::Subtract
, args
);
4907 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4909 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4911 CallArgs args
= CallArgsFromVp(argc
, vp
);
4912 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
4916 * Temporal.Duration.prototype.round ( roundTo )
4918 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4919 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4921 // Step 18. (Reordered)
4922 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
4925 auto smallestUnit
= TemporalUnit::Auto
;
4926 TemporalUnit largestUnit
;
4927 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
4928 auto roundingIncrement
= Increment
{1};
4929 Rooted
<JSObject
*> relativeTo(cx
);
4930 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4931 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4932 Rooted
<TimeZoneRecord
> timeZone(cx
);
4933 if (args
.get(0).isString()) {
4934 // Step 4. (Not applicable in our implementation.)
4936 // Steps 6-15. (Not applicable)
4939 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4940 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
4941 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
4945 // Step 17. (Not applicable)
4947 // Step 18. (Moved above)
4950 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4952 // Step 20. (Not applicable)
4954 // Step 20.a. (Not applicable)
4957 largestUnit
= defaultLargestUnit
;
4959 // Steps 21-25. (Not applicable)
4962 Rooted
<JSObject
*> options(
4963 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
4969 bool smallestUnitPresent
= true;
4972 bool largestUnitPresent
= true;
4976 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
4977 // absent "largestUnit" value.
4978 Rooted
<Value
> largestUnitValue(cx
);
4979 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
4980 &largestUnitValue
)) {
4984 if (!largestUnitValue
.isUndefined()) {
4985 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
4986 if (!largestUnitStr
) {
4990 largestUnit
= TemporalUnit::Auto
;
4991 if (!GetTemporalUnit(cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
4992 TemporalUnitGroup::DateTime
, &largestUnit
)) {
4998 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4999 &zonedRelativeTo
, &timeZone
)) {
5002 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5003 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5006 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
5011 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5016 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5017 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5022 if (smallestUnit
== TemporalUnit::Auto
) {
5024 smallestUnitPresent
= false;
5027 smallestUnit
= TemporalUnit::Nanosecond
;
5030 // Step 18. (Moved above)
5033 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5036 if (largestUnitValue
.isUndefined()) {
5038 largestUnitPresent
= false;
5041 largestUnit
= defaultLargestUnit
;
5042 } else if (largestUnit
== TemporalUnit::Auto
) {
5044 largestUnit
= defaultLargestUnit
;
5048 if (!smallestUnitPresent
&& !largestUnitPresent
) {
5049 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5050 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
5055 if (largestUnit
> smallestUnit
) {
5056 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5057 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
5062 if (smallestUnit
> TemporalUnit::Day
) {
5064 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
5067 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
5075 bool hoursToDaysConversionMayOccur
= false;
5078 if (duration
.days
!= 0 && zonedRelativeTo
) {
5079 hoursToDaysConversionMayOccur
= true;
5083 else if (std::abs(duration
.hours
) >= 24) {
5084 hoursToDaysConversionMayOccur
= true;
5088 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
5089 roundingIncrement
== Increment
{1};
5092 bool calendarUnitsPresent
=
5093 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
5096 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
5097 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
5098 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
5099 std::abs(duration
.milliseconds
) < 1000 &&
5100 std::abs(duration
.microseconds
) < 1000 &&
5101 std::abs(duration
.nanoseconds
) < 1000) {
5103 auto* obj
= CreateTemporalDuration(cx
, duration
);
5108 args
.rval().setObject(*obj
);
5113 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5116 bool plainDateTimeOrRelativeToWillBeUsed
=
5117 !roundingGranularityIsNoop
|| largestUnit
<= TemporalUnit::Day
||
5118 calendarUnitsPresent
|| duration
.days
!= 0;
5121 PlainDateTime relativeToDateTime
;
5122 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5124 const auto& instant
= zonedRelativeTo
.instant();
5127 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5130 precalculatedPlainDateTime
=
5131 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5134 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5135 zonedRelativeTo
.calendar());
5136 if (!plainRelativeTo
) {
5142 Rooted
<CalendarRecord
> calendar(cx
);
5143 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5146 CalendarMethod::DateAdd
,
5147 CalendarMethod::DateUntil
,
5154 DateDuration unbalanceResult
;
5155 if (plainRelativeTo
) {
5156 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5157 largestUnit
, plainRelativeTo
, calendar
,
5158 &unbalanceResult
)) {
5162 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5163 largestUnit
, &unbalanceResult
)) {
5166 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5171 NormalizedDuration
{unbalanceResult
, NormalizeTimeDuration(duration
)};
5172 NormalizedDuration roundResult
;
5173 if (plainRelativeTo
|| zonedRelativeTo
) {
5174 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5175 roundingMode
, plainRelativeTo
, calendar
,
5176 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5181 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5182 roundingMode
, &roundResult
)) {
5188 TimeDuration balanceResult
;
5189 if (zonedRelativeTo
) {
5191 NormalizedDuration adjustResult
;
5192 if (!AdjustRoundedDurationDays(cx
, roundResult
, roundingIncrement
,
5193 smallestUnit
, roundingMode
, zonedRelativeTo
,
5195 precalculatedPlainDateTime
, &adjustResult
)) {
5198 roundResult
= adjustResult
;
5201 if (!BalanceTimeDurationRelative(
5202 cx
, roundResult
, largestUnit
, zonedRelativeTo
, timeZone
,
5203 precalculatedPlainDateTime
, &balanceResult
)) {
5208 NormalizedTimeDuration withDays
;
5209 if (!Add24HourDaysToNormalizedTimeDuration(
5210 cx
, roundResult
.time
, roundResult
.date
.days
, &withDays
)) {
5215 balanceResult
= temporal::BalanceTimeDuration(withDays
, largestUnit
);
5219 auto balanceInput
= DateDuration
{
5220 roundResult
.date
.years
,
5221 roundResult
.date
.months
,
5222 roundResult
.date
.weeks
,
5225 DateDuration dateResult
;
5226 if (!::BalanceDateDurationRelative(cx
, balanceInput
, largestUnit
,
5227 smallestUnit
, plainRelativeTo
, calendar
,
5233 auto result
= Duration
{
5234 double(dateResult
.years
), double(dateResult
.months
),
5235 double(dateResult
.weeks
), double(dateResult
.days
),
5236 double(balanceResult
.hours
), double(balanceResult
.minutes
),
5237 double(balanceResult
.seconds
), double(balanceResult
.milliseconds
),
5238 balanceResult
.microseconds
, balanceResult
.nanoseconds
,
5240 MOZ_ASSERT(IsValidDuration(result
));
5241 auto* obj
= CreateTemporalDuration(cx
, result
);
5246 args
.rval().setObject(*obj
);
5251 * Temporal.Duration.prototype.round ( options )
5253 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5255 CallArgs args
= CallArgsFromVp(argc
, vp
);
5256 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
5260 * Temporal.Duration.prototype.total ( totalOf )
5262 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
5263 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
5264 auto duration
= ToDuration(durationObj
);
5267 Rooted
<JSObject
*> relativeTo(cx
);
5268 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5269 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5270 Rooted
<TimeZoneRecord
> timeZone(cx
);
5271 auto unit
= TemporalUnit::Auto
;
5272 if (args
.get(0).isString()) {
5273 // Step 4. (Not applicable in our implementation.)
5275 // Steps 6-10. (Implicit)
5276 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
5279 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5280 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::Unit
,
5281 TemporalUnitGroup::DateTime
, &unit
)) {
5286 Rooted
<JSObject
*> totalOf(
5287 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
5293 if (!ToRelativeTemporalObject(cx
, totalOf
, &plainRelativeTo
,
5294 &zonedRelativeTo
, &timeZone
)) {
5297 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5298 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5301 if (!GetTemporalUnit(cx
, totalOf
, TemporalUnitKey::Unit
,
5302 TemporalUnitGroup::DateTime
, &unit
)) {
5306 if (unit
== TemporalUnit::Auto
) {
5307 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5308 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
5314 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5317 bool plainDateTimeOrRelativeToWillBeUsed
=
5318 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
5321 PlainDateTime relativeToDateTime
;
5322 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5324 const auto& instant
= zonedRelativeTo
.instant();
5327 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5330 precalculatedPlainDateTime
=
5331 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5334 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5335 zonedRelativeTo
.calendar());
5336 if (!plainRelativeTo
) {
5342 Rooted
<CalendarRecord
> calendar(cx
);
5343 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5346 CalendarMethod::DateAdd
,
5347 CalendarMethod::DateUntil
,
5354 DateDuration unbalanceResult
;
5355 if (plainRelativeTo
) {
5356 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5357 plainRelativeTo
, calendar
,
5358 &unbalanceResult
)) {
5362 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5363 &unbalanceResult
)) {
5366 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5370 int64_t unbalancedDays
= unbalanceResult
.days
;
5374 NormalizedTimeDuration balanceResult
;
5375 if (zonedRelativeTo
) {
5377 Rooted
<ZonedDateTime
> intermediate(cx
);
5378 if (!MoveRelativeZonedDateTime(
5379 cx
, zonedRelativeTo
, calendar
, timeZone
,
5380 {unbalanceResult
.years
, unbalanceResult
.months
,
5381 unbalanceResult
.weeks
, 0},
5382 precalculatedPlainDateTime
, &intermediate
)) {
5387 auto timeDuration
= NormalizeTimeDuration(duration
);
5390 const auto& startNs
= intermediate
.instant();
5393 const auto& startInstant
= startNs
;
5396 mozilla::Maybe
<PlainDateTime
> startDateTime
{};
5399 Instant intermediateNs
;
5400 if (unbalancedDays
!= 0) {
5402 PlainDateTime dateTime
;
5403 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5406 startDateTime
= mozilla::Some(dateTime
);
5409 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
5411 if (!AddDaysToZonedDateTime(cx
, startInstant
, dateTime
, timeZone
,
5412 isoCalendar
, unbalancedDays
, &addResult
)) {
5417 intermediateNs
= addResult
;
5420 intermediateNs
= startNs
;
5425 if (!AddInstant(cx
, intermediateNs
, timeDuration
, &endNs
)) {
5431 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startNs
);
5435 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5436 if (TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
&&
5437 difference
!= NormalizedTimeDuration
{}) {
5439 if (!startDateTime
) {
5440 PlainDateTime dateTime
;
5441 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5444 startDateTime
= mozilla::Some(dateTime
);
5448 NormalizedTimeAndDays timeAndDays
;
5449 if (!NormalizedTimeDurationToDays(cx
, difference
, intermediate
, timeZone
,
5450 *startDateTime
, &timeAndDays
)) {
5455 balanceResult
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
5458 days
= timeAndDays
.days
;
5461 balanceResult
= difference
;
5466 auto timeDuration
= NormalizeTimeDuration(duration
);
5469 if (!Add24HourDaysToNormalizedTimeDuration(cx
, timeDuration
, unbalancedDays
,
5477 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult
));
5480 auto roundInput
= NormalizedDuration
{
5482 unbalanceResult
.years
,
5483 unbalanceResult
.months
,
5484 unbalanceResult
.weeks
,
5490 if (plainRelativeTo
|| zonedRelativeTo
) {
5491 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5492 TemporalRoundingMode::Trunc
, plainRelativeTo
, calendar
,
5493 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5498 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5499 TemporalRoundingMode::Trunc
, &total
)) {
5505 args
.rval().setNumber(total
);
5510 * Temporal.Duration.prototype.total ( totalOf )
5512 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5514 CallArgs args
= CallArgsFromVp(argc
, vp
);
5515 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
5519 * Temporal.Duration.prototype.toString ( [ options ] )
5521 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
5522 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5525 SecondsStringPrecision precision
= {Precision::Auto(),
5526 TemporalUnit::Nanosecond
, Increment
{1}};
5527 auto roundingMode
= TemporalRoundingMode::Trunc
;
5528 if (args
.hasDefined(0)) {
5530 Rooted
<JSObject
*> options(
5531 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
5537 auto digits
= Precision::Auto();
5538 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
5543 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5548 auto smallestUnit
= TemporalUnit::Auto
;
5549 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5550 TemporalUnitGroup::Time
, &smallestUnit
)) {
5555 if (smallestUnit
== TemporalUnit::Hour
||
5556 smallestUnit
== TemporalUnit::Minute
) {
5557 const char* smallestUnitStr
=
5558 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
5559 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5560 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
5561 smallestUnitStr
, "smallestUnit");
5566 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
5571 if (precision
.unit
!= TemporalUnit::Nanosecond
||
5572 precision
.increment
!= Increment
{1}) {
5574 auto timeDuration
= NormalizeTimeDuration(duration
);
5577 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
5580 auto rounded
= RoundDuration(timeDuration
, precision
.increment
,
5581 precision
.unit
, roundingMode
);
5584 auto balanced
= BalanceTimeDuration(
5585 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
5589 duration
.years
, duration
.months
,
5590 duration
.weeks
, duration
.days
+ double(balanced
.days
),
5591 double(balanced
.hours
), double(balanced
.minutes
),
5592 double(balanced
.seconds
), double(balanced
.milliseconds
),
5593 balanced
.microseconds
, balanced
.nanoseconds
,
5595 MOZ_ASSERT(IsValidDuration(duration
));
5602 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
5607 args
.rval().setString(str
);
5612 * Temporal.Duration.prototype.toString ( [ options ] )
5614 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5616 CallArgs args
= CallArgsFromVp(argc
, vp
);
5617 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
5621 * Temporal.Duration.prototype.toJSON ( )
5623 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
5624 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5627 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5632 args
.rval().setString(str
);
5637 * Temporal.Duration.prototype.toJSON ( )
5639 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5641 CallArgs args
= CallArgsFromVp(argc
, vp
);
5642 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
5646 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5648 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
5649 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5652 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5657 args
.rval().setString(str
);
5662 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5664 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5666 CallArgs args
= CallArgsFromVp(argc
, vp
);
5667 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
5671 * Temporal.Duration.prototype.valueOf ( )
5673 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5674 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
5675 "Duration", "primitive type");
5679 const JSClass
DurationObject::class_
= {
5680 "Temporal.Duration",
5681 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
5682 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
5684 &DurationObject::classSpec_
,
5687 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
5689 static const JSFunctionSpec Duration_methods
[] = {
5690 JS_FN("from", Duration_from
, 1, 0),
5691 JS_FN("compare", Duration_compare
, 2, 0),
5695 static const JSFunctionSpec Duration_prototype_methods
[] = {
5696 JS_FN("with", Duration_with
, 1, 0),
5697 JS_FN("negated", Duration_negated
, 0, 0),
5698 JS_FN("abs", Duration_abs
, 0, 0),
5699 JS_FN("add", Duration_add
, 1, 0),
5700 JS_FN("subtract", Duration_subtract
, 1, 0),
5701 JS_FN("round", Duration_round
, 1, 0),
5702 JS_FN("total", Duration_total
, 1, 0),
5703 JS_FN("toString", Duration_toString
, 0, 0),
5704 JS_FN("toJSON", Duration_toJSON
, 0, 0),
5705 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
5706 JS_FN("valueOf", Duration_valueOf
, 0, 0),
5710 static const JSPropertySpec Duration_prototype_properties
[] = {
5711 JS_PSG("years", Duration_years
, 0),
5712 JS_PSG("months", Duration_months
, 0),
5713 JS_PSG("weeks", Duration_weeks
, 0),
5714 JS_PSG("days", Duration_days
, 0),
5715 JS_PSG("hours", Duration_hours
, 0),
5716 JS_PSG("minutes", Duration_minutes
, 0),
5717 JS_PSG("seconds", Duration_seconds
, 0),
5718 JS_PSG("milliseconds", Duration_milliseconds
, 0),
5719 JS_PSG("microseconds", Duration_microseconds
, 0),
5720 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
5721 JS_PSG("sign", Duration_sign
, 0),
5722 JS_PSG("blank", Duration_blank
, 0),
5723 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
5727 const ClassSpec
DurationObject::classSpec_
= {
5728 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
5729 GenericCreatePrototype
<DurationObject
>,
5732 Duration_prototype_methods
,
5733 Duration_prototype_properties
,
5735 ClassSpec::DontDefineConstructor
,