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 struct FractionalDays final
{
3173 int64_t dayLength
= 0;
3175 FractionalDays() = default;
3177 explicit FractionalDays(int64_t durationDays
,
3178 const NormalizedTimeAndDays
& timeAndDays
)
3179 : days(durationDays
+ timeAndDays
.days
),
3180 time(timeAndDays
.time
),
3181 dayLength(timeAndDays
.dayLength
) {
3182 MOZ_ASSERT(durationDays
<= (int64_t(1) << 53) / (24 * 60 * 60));
3183 MOZ_ASSERT(timeAndDays
.days
<= (int64_t(1) << 53) / (24 * 60 * 60));
3185 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3186 // positive and less than 2**53.
3187 MOZ_ASSERT(dayLength
> 0);
3188 MOZ_ASSERT(dayLength
< int64_t(1) << 53);
3190 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3191 // less than |timeAndDays.dayLength|.
3192 MOZ_ASSERT(std::abs(time
) < dayLength
);
3195 FractionalDays
operator+=(int32_t epochDays
) {
3196 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3201 FractionalDays
operator-=(int32_t epochDays
) {
3202 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3207 int64_t truncate() const {
3208 int64_t truncatedDays
= days
;
3210 // Round toward positive infinity when the integer days are negative and
3211 // the fractional part is positive.
3212 if (truncatedDays
< 0) {
3215 } else if (time
< 0) {
3216 // Round toward negative infinity when the integer days are positive and
3217 // the fractional part is negative.
3218 if (truncatedDays
> 0) {
3222 return truncatedDays
;
3225 int32_t sign() const {
3227 return days
< 0 ? -1 : 1;
3229 return time
< 0 ? -1 : time
> 0 ? 1 : 0;
3233 struct Fraction final
{
3234 int64_t numerator
= 0;
3235 int32_t denominator
= 0;
3237 constexpr Fraction() = default;
3239 constexpr Fraction(int64_t numerator
, int32_t denominator
)
3240 : numerator(numerator
), denominator(denominator
) {
3241 MOZ_ASSERT(denominator
> 0);
3245 static RoundedNumber
RoundNumberToIncrement(
3246 const Fraction
& fraction
, const FractionalDays
& fractionalDays
,
3247 Increment increment
, TemporalRoundingMode roundingMode
,
3248 ComputeRemainder computeRemainder
) {
3250 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3251 static constexpr int64_t maxDurationDays
=
3252 (int64_t(1) << 53) / (24 * 60 * 60);
3254 // Numbers of days between nsMinInstant and nsMaxInstant.
3255 static constexpr int32_t epochDays
= 200'000'000;
3257 // Maximum number of days in |fractionalDays|.
3258 static constexpr int64_t maxFractionalDays
=
3259 2 * maxDurationDays
+ 2 * epochDays
;
3262 MOZ_ASSERT(std::abs(fraction
.numerator
) < (int64_t(1) << 32) * 2);
3263 MOZ_ASSERT(fraction
.denominator
> 0);
3264 MOZ_ASSERT(fraction
.denominator
<= epochDays
);
3265 MOZ_ASSERT(std::abs(fractionalDays
.days
) <= maxFractionalDays
);
3266 MOZ_ASSERT(fractionalDays
.dayLength
> 0);
3267 MOZ_ASSERT(fractionalDays
.dayLength
< (int64_t(1) << 53));
3268 MOZ_ASSERT(std::abs(fractionalDays
.time
) < fractionalDays
.dayLength
);
3269 MOZ_ASSERT(increment
<= Increment::max());
3273 // Change the representation of |fractionalWeeks| from a real number to a
3274 // rational number, because we don't support arbitrary precision real
3277 // |fractionalWeeks| is defined as:
3280 // = weeks + days' / abs(oneWeekDays)
3282 // where days' = days + nanoseconds / dayLength.
3284 // The fractional part |nanoseconds / dayLength| is from step 7.
3286 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3289 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3290 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3291 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3293 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3294 // to omit the multiplication by |dayLength| when the rounding conditions are
3295 // appropriately modified to account for the |nanoseconds / dayLength| part.
3296 // This allows to implement rounding using only int64 values.
3298 // This optimization is currently only implemented when |nanoseconds| is zero.
3300 // Example how to expand this optimization for non-zero |nanoseconds|:
3302 // |Round(fraction / increment) * increment| with:
3303 // fraction = numerator / denominator
3304 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3305 // denominator = dayLength * abs(oneWeekDays)
3307 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3309 // |Round(fraction / increment) * increment| with:
3310 // fraction = numerator / denominator
3311 // numerator = weeks * abs(oneWeekDays) + days
3312 // denominator = abs(oneWeekDays)
3315 // fraction / increment
3316 // = (numerator / denominator) / increment
3317 // = numerator / (denominator * increment)
3319 // And |numerator| and |denominator * increment| both fit into int64.
3321 // The "ceiling" operation has to be modified from:
3323 // CeilDiv(dividend, divisor)
3324 // quot, rem = dividend / divisor
3325 // return quot + (rem > 0)
3329 // CeilDiv(dividend, divisor, fractional)
3330 // quot, rem = dividend / divisor
3331 // return quot + ((rem > 0) || (fractional > 0))
3333 // To properly account for the fractional |nanoseconds| part. Alternatively
3334 // |dividend| can be modified before calling `CeilDiv`.
3338 if (fractionalDays
.time
== 0) {
3339 auto [numerator
, denominator
] = fraction
;
3340 int64_t totalDays
= fractionalDays
.days
+ denominator
* numerator
;
3342 if (computeRemainder
== ComputeRemainder::Yes
) {
3343 return TruncateNumber(totalDays
, denominator
);
3347 RoundNumberToIncrement(totalDays
, denominator
, increment
, roundingMode
);
3348 constexpr double total
= 0;
3349 return {rounded
, total
};
3353 auto dayLength
= mozilla::CheckedInt64(fractionalDays
.dayLength
);
3355 auto denominator
= dayLength
* fraction
.denominator
;
3356 if (!denominator
.isValid()) {
3360 auto amountNanos
= denominator
* fraction
.numerator
;
3361 if (!amountNanos
.isValid()) {
3365 auto totalNanoseconds
= dayLength
* fractionalDays
.days
;
3366 totalNanoseconds
+= fractionalDays
.time
;
3367 totalNanoseconds
+= amountNanos
;
3368 if (!totalNanoseconds
.isValid()) {
3372 if (computeRemainder
== ComputeRemainder::Yes
) {
3373 return TruncateNumber(totalNanoseconds
.value(), denominator
.value());
3376 auto rounded
= RoundNumberToIncrement(
3377 totalNanoseconds
.value(), denominator
.value(), increment
, roundingMode
);
3378 constexpr double total
= 0;
3379 return {rounded
, total
};
3382 // Use int128 when values are too large for int64. Additionally assert all
3383 // values fit into int128.
3385 // `dayLength` < 2**53
3386 auto dayLength
= Int128
{fractionalDays
.dayLength
};
3387 MOZ_ASSERT(dayLength
< Int128
{1} << 53);
3389 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3390 auto denominator
= dayLength
* Int128
{fraction
.denominator
};
3391 MOZ_ASSERT(denominator
< Int128
{1} << (53 + 28));
3393 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3395 // `abs(maxFractionalDays)`
3396 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3397 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3398 // ≤ 2 * 2**37 + 2**29
3400 auto totalDays
= Int128
{fractionalDays
.days
};
3401 MOZ_ASSERT(totalDays
.abs() <= Uint128
{1} << 39);
3403 // `abs(fraction.numerator)` ≤ (2**33)
3404 auto totalAmount
= Int128
{fraction
.numerator
};
3405 MOZ_ASSERT(totalAmount
.abs() <= Uint128
{1} << 33);
3407 // `denominator` < 2**(53 + 28)
3408 // `abs(totalAmount)` <= 2**33
3410 // `denominator * totalAmount`
3411 // ≤ 2**(53 + 28) * 2**33
3412 // = 2**(53 + 28 + 33)
3414 auto amountNanos
= denominator
* totalAmount
;
3415 MOZ_ASSERT(amountNanos
.abs() <= Uint128
{1} << 114);
3417 // `dayLength` < 2**53
3418 // `totalDays` ≤ 2**39
3419 // `fractionalDays.time` < `dayLength` < 2**53
3420 // `amountNanos` ≤ 2**114
3422 // `dayLength * totalDays`
3423 // ≤ 2**(53 + 39) = 2**92
3425 // `dayLength * totalDays + fractionalDays.time`
3428 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3430 auto totalNanoseconds
= dayLength
* totalDays
;
3431 totalNanoseconds
+= Int128
{fractionalDays
.time
};
3432 totalNanoseconds
+= amountNanos
;
3433 MOZ_ASSERT(totalNanoseconds
.abs() <= Uint128
{1} << 115);
3435 if (computeRemainder
== ComputeRemainder::Yes
) {
3436 return TruncateNumber(totalNanoseconds
, denominator
);
3439 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, denominator
,
3440 increment
, roundingMode
);
3441 constexpr double total
= 0;
3442 return {rounded
, total
};
3445 static bool RoundDurationYear(JSContext
* cx
, const NormalizedDuration
& duration
,
3446 FractionalDays fractionalDays
,
3447 Increment increment
,
3448 TemporalRoundingMode roundingMode
,
3449 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3450 Handle
<CalendarRecord
> calendar
,
3451 ComputeRemainder computeRemainder
,
3452 RoundedDuration
* result
) {
3453 // Numbers of days between nsMinInstant and nsMaxInstant.
3454 static constexpr int32_t epochDays
= 200'000'000;
3456 auto [years
, months
, weeks
, days
] = duration
.date
;
3459 Duration yearsDuration
= {double(years
)};
3462 auto yearsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsDuration
);
3466 auto yearsLaterDate
= ToPlainDate(&yearsLater
.unwrap());
3468 // Step 10.f. (Reordered)
3469 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsLater
);
3472 Duration yearsMonthsWeeks
= {double(years
), double(months
), double(weeks
)};
3475 PlainDate yearsMonthsWeeksLater
;
3476 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3477 &yearsMonthsWeeksLater
)) {
3482 int32_t monthsWeeksInDays
= DaysUntil(yearsLaterDate
, yearsMonthsWeeksLater
);
3483 MOZ_ASSERT(std::abs(monthsWeeksInDays
) <= epochDays
);
3485 // Step 10.f. (Moved up)
3488 fractionalDays
+= monthsWeeksInDays
;
3490 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3491 // https://github.com/tc39/proposal-temporal/issues/2540
3494 PlainDate isoResult
;
3495 if (!AddISODate(cx
, yearsLaterDate
, {0, 0, 0, fractionalDays
.truncate()},
3496 TemporalOverflow::Constrain
, &isoResult
)) {
3501 Rooted
<PlainDateObject
*> wholeDaysLater(
3502 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3503 if (!wholeDaysLater
) {
3508 Duration timePassed
;
3509 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3510 TemporalUnit::Year
, &timePassed
)) {
3515 int64_t yearsPassed
= int64_t(timePassed
.years
);
3518 years
+= yearsPassed
;
3521 Duration yearsPassedDuration
= {double(yearsPassed
)};
3525 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, yearsPassedDuration
,
3526 &newRelativeTo
, &daysPassed
)) {
3529 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3532 fractionalDays
-= daysPassed
;
3535 double sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3538 Duration oneYear
= {sign
};
3541 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3542 int32_t oneYearDays
;
3543 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneYear
,
3544 &moveResultIgnored
, &oneYearDays
)) {
3549 if (oneYearDays
== 0) {
3550 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3551 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3556 auto fractionalYears
= Fraction
{years
, std::abs(oneYearDays
)};
3559 auto [numYears
, total
] =
3560 RoundNumberToIncrement(fractionalYears
, fractionalDays
, increment
,
3561 roundingMode
, computeRemainder
);
3564 int64_t numMonths
= 0;
3565 int64_t numWeeks
= 0;
3568 constexpr auto time
= NormalizedTimeDuration
{};
3571 if (numYears
.abs() >= (Uint128
{1} << 32)) {
3572 return ThrowInvalidDurationPart(cx
, double(numYears
), "years",
3573 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3576 auto resultDuration
= DateDuration
{int64_t(numYears
), numMonths
, numWeeks
};
3577 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3581 *result
= {{resultDuration
, time
}, total
};
3585 static bool RoundDurationMonth(JSContext
* cx
,
3586 const NormalizedDuration
& duration
,
3587 FractionalDays fractionalDays
,
3588 Increment increment
,
3589 TemporalRoundingMode roundingMode
,
3590 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3591 Handle
<CalendarRecord
> calendar
,
3592 ComputeRemainder computeRemainder
,
3593 RoundedDuration
* result
) {
3594 // Numbers of days between nsMinInstant and nsMaxInstant.
3595 static constexpr int32_t epochDays
= 200'000'000;
3597 auto [years
, months
, weeks
, days
] = duration
.date
;
3600 Duration yearsMonths
= {double(years
), double(months
)};
3603 auto yearsMonthsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsMonths
);
3604 if (!yearsMonthsLater
) {
3607 auto yearsMonthsLaterDate
= ToPlainDate(&yearsMonthsLater
.unwrap());
3609 // Step 11.f. (Reordered)
3610 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsMonthsLater
);
3613 Duration yearsMonthsWeeks
= {double(years
), double(months
), double(weeks
)};
3616 PlainDate yearsMonthsWeeksLater
;
3617 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3618 &yearsMonthsWeeksLater
)) {
3623 int32_t weeksInDays
= DaysUntil(yearsMonthsLaterDate
, yearsMonthsWeeksLater
);
3624 MOZ_ASSERT(std::abs(weeksInDays
) <= epochDays
);
3626 // Step 11.f. (Moved up)
3629 fractionalDays
+= weeksInDays
;
3631 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3632 // https://github.com/tc39/proposal-temporal/issues/2540
3635 PlainDate isoResult
;
3636 if (!AddISODate(cx
, yearsMonthsLaterDate
,
3637 {0, 0, 0, fractionalDays
.truncate()},
3638 TemporalOverflow::Constrain
, &isoResult
)) {
3643 Rooted
<PlainDateObject
*> wholeDaysLater(
3644 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3645 if (!wholeDaysLater
) {
3650 Duration timePassed
;
3651 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3652 TemporalUnit::Month
, &timePassed
)) {
3657 int64_t monthsPassed
= int64_t(timePassed
.months
);
3660 months
+= monthsPassed
;
3663 Duration monthsPassedDuration
= {0, double(monthsPassed
)};
3667 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, monthsPassedDuration
,
3668 &newRelativeTo
, &daysPassed
)) {
3671 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3674 fractionalDays
-= daysPassed
;
3677 double sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3680 Duration oneMonth
= {0, sign
};
3683 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3684 int32_t oneMonthDays
;
3685 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneMonth
,
3686 &moveResultIgnored
, &oneMonthDays
)) {
3691 if (oneMonthDays
== 0) {
3692 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3693 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3698 auto fractionalMonths
= Fraction
{months
, std::abs(oneMonthDays
)};
3701 auto [numMonths
, total
] =
3702 RoundNumberToIncrement(fractionalMonths
, fractionalDays
, increment
,
3703 roundingMode
, computeRemainder
);
3706 int64_t numWeeks
= 0;
3709 constexpr auto time
= NormalizedTimeDuration
{};
3712 if (numMonths
.abs() >= (Uint128
{1} << 32)) {
3713 return ThrowInvalidDurationPart(cx
, double(numMonths
), "months",
3714 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3717 auto resultDuration
= DateDuration
{years
, int64_t(numMonths
), numWeeks
};
3718 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3722 *result
= {{resultDuration
, time
}, total
};
3726 static bool RoundDurationWeek(JSContext
* cx
, const NormalizedDuration
& duration
,
3727 FractionalDays fractionalDays
,
3728 Increment increment
,
3729 TemporalRoundingMode roundingMode
,
3730 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3731 Handle
<CalendarRecord
> calendar
,
3732 ComputeRemainder computeRemainder
,
3733 RoundedDuration
* result
) {
3734 // Numbers of days between nsMinInstant and nsMaxInstant.
3735 static constexpr int32_t epochDays
= 200'000'000;
3737 auto [years
, months
, weeks
, days
] = duration
.date
;
3739 auto* unwrappedRelativeTo
= dateRelativeTo
.unwrap(cx
);
3740 if (!unwrappedRelativeTo
) {
3743 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
3746 PlainDate isoResult
;
3747 if (!AddISODate(cx
, relativeToDate
, {0, 0, 0, fractionalDays
.truncate()},
3748 TemporalOverflow::Constrain
, &isoResult
)) {
3753 Rooted
<PlainDateObject
*> wholeDaysLater(
3754 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3755 if (!wholeDaysLater
) {
3760 Duration timePassed
;
3761 if (!DifferenceDate(cx
, calendar
, dateRelativeTo
, wholeDaysLater
,
3762 TemporalUnit::Week
, &timePassed
)) {
3767 int64_t weeksPassed
= int64_t(timePassed
.weeks
);
3770 weeks
+= weeksPassed
;
3773 Duration weeksPassedDuration
= {0, 0, double(weeksPassed
)};
3776 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
);
3778 if (!MoveRelativeDate(cx
, calendar
, dateRelativeTo
, weeksPassedDuration
,
3779 &newRelativeTo
, &daysPassed
)) {
3782 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3785 fractionalDays
-= daysPassed
;
3788 double sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3791 Duration oneWeek
= {0, 0, sign
};
3794 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3795 int32_t oneWeekDays
;
3796 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneWeek
,
3797 &moveResultIgnored
, &oneWeekDays
)) {
3802 if (oneWeekDays
== 0) {
3803 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3804 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3809 auto fractionalWeeks
= Fraction
{weeks
, std::abs(oneWeekDays
)};
3812 auto [numWeeks
, total
] =
3813 RoundNumberToIncrement(fractionalWeeks
, fractionalDays
, increment
,
3814 roundingMode
, computeRemainder
);
3817 constexpr auto time
= NormalizedTimeDuration
{};
3820 if (numWeeks
.abs() >= (Uint128
{1} << 32)) {
3821 return ThrowInvalidDurationPart(cx
, double(numWeeks
), "weeks",
3822 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3825 auto resultDuration
= DateDuration
{years
, months
, int64_t(numWeeks
)};
3826 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3830 *result
= {{resultDuration
, time
}, total
};
3834 static bool RoundDurationDay(JSContext
* cx
, const NormalizedDuration
& duration
,
3835 const FractionalDays
& fractionalDays
,
3836 Increment increment
,
3837 TemporalRoundingMode roundingMode
,
3838 ComputeRemainder computeRemainder
,
3839 RoundedDuration
* result
) {
3840 auto [years
, months
, weeks
, days
] = duration
.date
;
3842 // Pass zero fraction.
3843 constexpr auto zero
= Fraction
{0, 1};
3846 auto [numDays
, total
] = RoundNumberToIncrement(
3847 zero
, fractionalDays
, increment
, roundingMode
, computeRemainder
);
3849 MOZ_ASSERT(Int128
{INT64_MIN
} <= numDays
&& numDays
<= Int128
{INT64_MAX
},
3850 "rounded days fits in int64");
3853 constexpr auto time
= NormalizedTimeDuration
{};
3856 auto resultDuration
= DateDuration
{years
, months
, weeks
, int64_t(numDays
)};
3857 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3861 *result
= {{resultDuration
, time
}, total
};
3866 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3867 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3868 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3870 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
3871 Increment increment
, TemporalUnit unit
,
3872 TemporalRoundingMode roundingMode
,
3873 ComputeRemainder computeRemainder
,
3874 RoundedDuration
* result
) {
3875 // The remainder is only needed when called from |Duration_total|. And `total`
3876 // always passes |increment=1| and |roundingMode=trunc|.
3877 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3878 increment
== Increment
{1});
3879 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3880 roundingMode
== TemporalRoundingMode::Trunc
);
3882 // Steps 1-5. (Not applicable.)
3885 if (unit
<= TemporalUnit::Week
) {
3886 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3887 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3892 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3893 // because in that case this operation is a no-op. This case happens for
3894 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3897 // But maybe this can be even more efficiently handled in the callers. For
3898 // example when Temporal.PlainTime.prototype.{since,until} is called without
3899 // an options object, we can not only skip the RoundDuration call, but also
3900 // the following BalanceTimeDuration call.
3902 // Step 7. (Moved below.)
3904 // Steps 8-9. (Not applicable.)
3906 // Steps 10-12. (Not applicable.)
3909 if (unit
== TemporalUnit::Day
) {
3911 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3912 auto fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3914 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
3915 roundingMode
, computeRemainder
, result
);
3918 MOZ_ASSERT(TemporalUnit::Hour
<= unit
&& unit
<= TemporalUnit::Nanosecond
);
3921 auto time
= duration
.time
;
3923 if (computeRemainder
== ComputeRemainder::No
) {
3924 time
= RoundNormalizedTimeDurationToIncrement(time
, unit
, increment
,
3927 MOZ_ASSERT(increment
== Increment
{1});
3928 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
3930 total
= TotalNormalizedTimeDuration(duration
.time
, unit
);
3932 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
3935 MOZ_ASSERT(IsValidDuration(duration
.date
.toDuration()));
3936 *result
= {{duration
.date
, time
}, total
};
3941 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3942 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3943 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3945 static bool RoundDuration(
3946 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
3947 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
3948 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3949 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
3950 Handle
<TimeZoneRecord
> timeZone
,
3951 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
3952 ComputeRemainder computeRemainder
, RoundedDuration
* result
) {
3953 // Note: |duration.days| can have a different sign than the other date
3954 // components. The date and time components can have different signs, too.
3955 MOZ_ASSERT(IsValidDuration(Duration
{double(duration
.date
.years
),
3956 double(duration
.date
.months
),
3957 double(duration
.date
.weeks
)}));
3958 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
3960 MOZ_ASSERT(plainRelativeTo
|| zonedRelativeTo
,
3961 "Use RoundDuration without relativeTo when plainRelativeTo and "
3962 "zonedRelativeTo are both undefined");
3964 // The remainder is only needed when called from |Duration_total|. And `total`
3965 // always passes |increment=1| and |roundingMode=trunc|.
3966 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3967 increment
== Increment
{1});
3968 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3969 roundingMode
== TemporalRoundingMode::Trunc
);
3971 // Steps 1-5. (Not applicable in our implementation.)
3973 // Step 6.a. (Not applicable in our implementation.)
3974 MOZ_ASSERT_IF(unit
<= TemporalUnit::Week
, plainRelativeTo
);
3978 unit
<= TemporalUnit::Week
,
3979 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
3983 unit
<= TemporalUnit::Week
,
3984 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
3987 case TemporalUnit::Year
:
3988 case TemporalUnit::Month
:
3989 case TemporalUnit::Week
:
3991 case TemporalUnit::Day
:
3992 // We can't take the faster code path when |zonedRelativeTo| is present.
3993 if (zonedRelativeTo
) {
3997 case TemporalUnit::Hour
:
3998 case TemporalUnit::Minute
:
3999 case TemporalUnit::Second
:
4000 case TemporalUnit::Millisecond
:
4001 case TemporalUnit::Microsecond
:
4002 case TemporalUnit::Nanosecond
:
4003 // Steps 7-9 and 13-21.
4004 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4005 computeRemainder
, result
);
4006 case TemporalUnit::Auto
:
4007 MOZ_CRASH("Unexpected temporal unit");
4011 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
);
4014 FractionalDays fractionalDays
;
4015 if (zonedRelativeTo
) {
4017 Rooted
<ZonedDateTime
> intermediate(cx
);
4018 if (!MoveRelativeZonedDateTime(cx
, zonedRelativeTo
, calendar
, timeZone
,
4019 duration
.date
, precalculatedPlainDateTime
,
4025 NormalizedTimeAndDays timeAndDays
;
4026 if (!NormalizedTimeDurationToDays(cx
, duration
.time
, intermediate
, timeZone
,
4032 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
4035 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
4036 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
4039 // Step 7.c. (Moved below)
4041 // Step 8. (Not applicable)
4044 // FIXME: spec issue - `total` doesn't need be initialised.
4049 case TemporalUnit::Year
:
4050 return RoundDurationYear(cx
, duration
, fractionalDays
, increment
,
4051 roundingMode
, plainRelativeTo
, calendar
,
4052 computeRemainder
, result
);
4055 case TemporalUnit::Month
:
4056 return RoundDurationMonth(cx
, duration
, fractionalDays
, increment
,
4057 roundingMode
, plainRelativeTo
, calendar
,
4058 computeRemainder
, result
);
4061 case TemporalUnit::Week
:
4062 return RoundDurationWeek(cx
, duration
, fractionalDays
, increment
,
4063 roundingMode
, plainRelativeTo
, calendar
,
4064 computeRemainder
, result
);
4067 case TemporalUnit::Day
:
4068 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
4069 roundingMode
, computeRemainder
, result
);
4071 // Steps 14-19. (Handled elsewhere)
4072 case TemporalUnit::Auto
:
4073 case TemporalUnit::Hour
:
4074 case TemporalUnit::Minute
:
4075 case TemporalUnit::Second
:
4076 case TemporalUnit::Millisecond
:
4077 case TemporalUnit::Microsecond
:
4078 case TemporalUnit::Nanosecond
:
4082 MOZ_CRASH("Unexpected temporal unit");
4086 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4087 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4088 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4090 static bool RoundDuration(
4091 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4092 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4093 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4094 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4095 Handle
<TimeZoneRecord
> timeZone
,
4096 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4098 // Only called from |Duration_total|, which always passes |increment=1| and
4099 // |roundingMode=trunc|.
4100 MOZ_ASSERT(increment
== Increment
{1});
4101 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4103 RoundedDuration rounded
;
4104 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4105 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4106 precalculatedPlainDateTime
, ComputeRemainder::Yes
,
4111 *result
= rounded
.total
;
4116 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4117 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4118 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4120 static bool RoundDuration(
4121 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4122 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4123 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4124 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4125 Handle
<TimeZoneRecord
> timeZone
,
4126 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4127 NormalizedDuration
* result
) {
4128 RoundedDuration rounded
;
4129 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4130 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4131 precalculatedPlainDateTime
, ComputeRemainder::No
,
4136 *result
= rounded
.duration
;
4141 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4142 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4143 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4145 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4146 Increment increment
, TemporalUnit unit
,
4147 TemporalRoundingMode roundingMode
, double* result
) {
4148 MOZ_ASSERT(IsValidDuration(duration
));
4150 // Only called from |Duration_total|, which always passes |increment=1| and
4151 // |roundingMode=trunc|.
4152 MOZ_ASSERT(increment
== Increment
{1});
4153 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4155 RoundedDuration rounded
;
4156 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4157 ComputeRemainder::Yes
, &rounded
)) {
4161 *result
= rounded
.total
;
4166 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4167 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4168 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4170 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4171 Increment increment
, TemporalUnit unit
,
4172 TemporalRoundingMode roundingMode
,
4173 NormalizedDuration
* result
) {
4174 MOZ_ASSERT(IsValidDuration(duration
));
4176 RoundedDuration rounded
;
4177 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4178 ComputeRemainder::No
, &rounded
)) {
4182 *result
= rounded
.duration
;
4187 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4188 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4189 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4191 bool js::temporal::RoundDuration(
4192 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4193 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4194 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4195 Handle
<CalendarRecord
> calendar
, NormalizedDuration
* result
) {
4196 MOZ_ASSERT(IsValidDuration(duration
));
4198 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
, ZonedDateTime
{});
4199 Rooted
<TimeZoneRecord
> timeZone(cx
, TimeZoneRecord
{});
4200 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4201 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4202 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4203 precalculatedPlainDateTime
, result
);
4207 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4208 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4209 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4211 bool js::temporal::RoundDuration(
4212 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4213 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4214 Handle
<PlainDateObject
*> plainRelativeTo
, Handle
<CalendarRecord
> calendar
,
4215 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<TimeZoneRecord
> timeZone
,
4216 const PlainDateTime
& precalculatedPlainDateTime
,
4217 NormalizedDuration
* result
) {
4218 MOZ_ASSERT(IsValidDuration(duration
));
4220 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4221 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4222 mozilla::SomeRef(precalculatedPlainDateTime
), result
);
4225 enum class DurationOperation
{ Add
, Subtract
};
4228 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4231 static bool AddDurationToOrSubtractDurationFromDuration(
4232 JSContext
* cx
, DurationOperation operation
, const CallArgs
& args
) {
4233 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4234 auto duration
= ToDuration(durationObj
);
4236 // Step 1. (Not applicable in our implementation.)
4240 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
4244 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4245 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4246 Rooted
<TimeZoneRecord
> timeZone(cx
);
4247 if (args
.hasDefined(1)) {
4248 const char* name
= operation
== DurationOperation::Add
? "add" : "subtract";
4251 Rooted
<JSObject
*> options(cx
,
4252 RequireObjectArg(cx
, "options", name
, args
[1]));
4258 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4259 &zonedRelativeTo
, &timeZone
)) {
4262 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4263 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4267 Rooted
<CalendarRecord
> calendar(cx
);
4268 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4271 CalendarMethod::DateAdd
,
4272 CalendarMethod::DateUntil
,
4279 if (operation
== DurationOperation::Subtract
) {
4280 other
= other
.negate();
4284 if (plainRelativeTo
) {
4285 if (!AddDuration(cx
, duration
, other
, plainRelativeTo
, calendar
, &result
)) {
4288 } else if (zonedRelativeTo
) {
4289 if (!AddDuration(cx
, duration
, other
, zonedRelativeTo
, calendar
, timeZone
,
4294 if (!AddDuration(cx
, duration
, other
, &result
)) {
4300 auto* obj
= CreateTemporalDuration(cx
, result
);
4305 args
.rval().setObject(*obj
);
4310 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4311 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4314 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4315 CallArgs args
= CallArgsFromVp(argc
, vp
);
4318 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
4324 if (args
.hasDefined(0) &&
4325 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
4331 if (args
.hasDefined(1) &&
4332 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
4338 if (args
.hasDefined(2) &&
4339 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
4345 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
4351 if (args
.hasDefined(4) &&
4352 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
4358 if (args
.hasDefined(5) &&
4359 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
4365 if (args
.hasDefined(6) &&
4366 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
4371 double milliseconds
= 0;
4372 if (args
.hasDefined(7) &&
4373 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
4378 double microseconds
= 0;
4379 if (args
.hasDefined(8) &&
4380 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
4385 double nanoseconds
= 0;
4386 if (args
.hasDefined(9) &&
4387 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
4392 auto* duration
= CreateTemporalDuration(
4394 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
4395 microseconds
, nanoseconds
});
4400 args
.rval().setObject(*duration
);
4405 * Temporal.Duration.from ( item )
4407 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4408 CallArgs args
= CallArgsFromVp(argc
, vp
);
4410 Handle
<Value
> item
= args
.get(0);
4413 if (item
.isObject()) {
4414 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
4415 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
4420 args
.rval().setObject(*result
);
4426 auto result
= ToTemporalDuration(cx
, item
);
4431 args
.rval().setObject(*result
);
4436 * Temporal.Duration.compare ( one, two [ , options ] )
4438 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4439 CallArgs args
= CallArgsFromVp(argc
, vp
);
4443 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
4449 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
4454 Rooted
<JSObject
*> options(cx
);
4455 if (args
.hasDefined(2)) {
4456 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
4464 args
.rval().setInt32(0);
4469 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4470 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4471 Rooted
<TimeZoneRecord
> timeZone(cx
);
4473 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4474 &zonedRelativeTo
, &timeZone
)) {
4477 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4478 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4482 auto hasCalendarUnit
= [](const auto& d
) {
4483 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
4485 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
4488 Rooted
<CalendarRecord
> calendar(cx
);
4489 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4492 CalendarMethod::DateAdd
,
4499 if (zonedRelativeTo
&&
4500 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
4502 const auto& instant
= zonedRelativeTo
.instant();
4505 PlainDateTime dateTime
;
4506 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
4511 auto normalized1
= CreateNormalizedDurationRecord(one
);
4514 auto normalized2
= CreateNormalizedDurationRecord(two
);
4518 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
4519 dateTime
, &after1
)) {
4525 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
4526 dateTime
, &after2
)) {
4531 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
4536 double days1
, days2
;
4537 if (calendarUnitsPresent
) {
4538 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4541 DateDuration unbalanceResult1
;
4542 if (plainRelativeTo
) {
4543 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
4544 TemporalUnit::Day
, plainRelativeTo
,
4545 calendar
, &unbalanceResult1
)) {
4549 if (!UnbalanceDateDurationRelative(
4550 cx
, one
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult1
)) {
4553 MOZ_ASSERT(one
.toDateDuration() == unbalanceResult1
);
4557 DateDuration unbalanceResult2
;
4558 if (plainRelativeTo
) {
4559 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
4560 TemporalUnit::Day
, plainRelativeTo
,
4561 calendar
, &unbalanceResult2
)) {
4565 if (!UnbalanceDateDurationRelative(
4566 cx
, two
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult2
)) {
4569 MOZ_ASSERT(two
.toDateDuration() == unbalanceResult2
);
4573 days1
= unbalanceResult1
.days
;
4576 days2
= unbalanceResult2
.days
;
4586 auto normalized1
= NormalizeTimeDuration(one
);
4589 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
4595 auto normalized2
= NormalizeTimeDuration(two
);
4598 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
4604 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
4609 * get Temporal.Duration.prototype.years
4611 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
4613 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4614 args
.rval().setNumber(duration
->years());
4619 * get Temporal.Duration.prototype.years
4621 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4623 CallArgs args
= CallArgsFromVp(argc
, vp
);
4624 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
4628 * get Temporal.Duration.prototype.months
4630 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
4632 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4633 args
.rval().setNumber(duration
->months());
4638 * get Temporal.Duration.prototype.months
4640 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4642 CallArgs args
= CallArgsFromVp(argc
, vp
);
4643 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
4647 * get Temporal.Duration.prototype.weeks
4649 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
4651 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4652 args
.rval().setNumber(duration
->weeks());
4657 * get Temporal.Duration.prototype.weeks
4659 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4661 CallArgs args
= CallArgsFromVp(argc
, vp
);
4662 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
4666 * get Temporal.Duration.prototype.days
4668 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
4670 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4671 args
.rval().setNumber(duration
->days());
4676 * get Temporal.Duration.prototype.days
4678 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4680 CallArgs args
= CallArgsFromVp(argc
, vp
);
4681 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
4685 * get Temporal.Duration.prototype.hours
4687 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
4689 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4690 args
.rval().setNumber(duration
->hours());
4695 * get Temporal.Duration.prototype.hours
4697 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4699 CallArgs args
= CallArgsFromVp(argc
, vp
);
4700 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
4704 * get Temporal.Duration.prototype.minutes
4706 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
4708 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4709 args
.rval().setNumber(duration
->minutes());
4714 * get Temporal.Duration.prototype.minutes
4716 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4718 CallArgs args
= CallArgsFromVp(argc
, vp
);
4719 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
4723 * get Temporal.Duration.prototype.seconds
4725 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
4727 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4728 args
.rval().setNumber(duration
->seconds());
4733 * get Temporal.Duration.prototype.seconds
4735 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4737 CallArgs args
= CallArgsFromVp(argc
, vp
);
4738 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
4742 * get Temporal.Duration.prototype.milliseconds
4744 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
4746 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4747 args
.rval().setNumber(duration
->milliseconds());
4752 * get Temporal.Duration.prototype.milliseconds
4754 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4756 CallArgs args
= CallArgsFromVp(argc
, vp
);
4757 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
4761 * get Temporal.Duration.prototype.microseconds
4763 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
4765 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4766 args
.rval().setNumber(duration
->microseconds());
4771 * get Temporal.Duration.prototype.microseconds
4773 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4775 CallArgs args
= CallArgsFromVp(argc
, vp
);
4776 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
4780 * get Temporal.Duration.prototype.nanoseconds
4782 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
4784 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4785 args
.rval().setNumber(duration
->nanoseconds());
4790 * get Temporal.Duration.prototype.nanoseconds
4792 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4794 CallArgs args
= CallArgsFromVp(argc
, vp
);
4795 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
4799 * get Temporal.Duration.prototype.sign
4801 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
4802 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4805 args
.rval().setInt32(DurationSign(duration
));
4810 * get Temporal.Duration.prototype.sign
4812 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4814 CallArgs args
= CallArgsFromVp(argc
, vp
);
4815 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
4819 * get Temporal.Duration.prototype.blank
4821 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
4822 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4825 args
.rval().setBoolean(duration
== Duration
{});
4830 * get Temporal.Duration.prototype.blank
4832 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4834 CallArgs args
= CallArgsFromVp(argc
, vp
);
4835 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
4839 * Temporal.Duration.prototype.with ( temporalDurationLike )
4841 * ToPartialDuration ( temporalDurationLike )
4843 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
4844 // Absent values default to the corresponding values of |this| object.
4845 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4848 Rooted
<JSObject
*> temporalDurationLike(
4849 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
4850 if (!temporalDurationLike
) {
4853 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
4858 auto* result
= CreateTemporalDuration(cx
, duration
);
4863 args
.rval().setObject(*result
);
4868 * Temporal.Duration.prototype.with ( temporalDurationLike )
4870 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4872 CallArgs args
= CallArgsFromVp(argc
, vp
);
4873 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
4877 * Temporal.Duration.prototype.negated ( )
4879 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
4880 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4883 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
4888 args
.rval().setObject(*result
);
4893 * Temporal.Duration.prototype.negated ( )
4895 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4897 CallArgs args
= CallArgsFromVp(argc
, vp
);
4898 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
4902 * Temporal.Duration.prototype.abs ( )
4904 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
4905 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4908 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
4913 args
.rval().setObject(*result
);
4918 * Temporal.Duration.prototype.abs ( )
4920 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4922 CallArgs args
= CallArgsFromVp(argc
, vp
);
4923 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
4927 * Temporal.Duration.prototype.add ( other [ , options ] )
4929 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
4930 return AddDurationToOrSubtractDurationFromDuration(cx
, DurationOperation::Add
,
4935 * Temporal.Duration.prototype.add ( other [ , options ] )
4937 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4939 CallArgs args
= CallArgsFromVp(argc
, vp
);
4940 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
4944 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4946 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
4947 return AddDurationToOrSubtractDurationFromDuration(
4948 cx
, DurationOperation::Subtract
, args
);
4952 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4954 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4956 CallArgs args
= CallArgsFromVp(argc
, vp
);
4957 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
4961 * Temporal.Duration.prototype.round ( roundTo )
4963 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4964 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4966 // Step 18. (Reordered)
4967 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
4970 auto smallestUnit
= TemporalUnit::Auto
;
4971 TemporalUnit largestUnit
;
4972 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
4973 auto roundingIncrement
= Increment
{1};
4974 Rooted
<JSObject
*> relativeTo(cx
);
4975 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4976 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4977 Rooted
<TimeZoneRecord
> timeZone(cx
);
4978 if (args
.get(0).isString()) {
4979 // Step 4. (Not applicable in our implementation.)
4981 // Steps 6-15. (Not applicable)
4984 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4985 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
4986 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
4990 // Step 17. (Not applicable)
4992 // Step 18. (Moved above)
4995 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4997 // Step 20. (Not applicable)
4999 // Step 20.a. (Not applicable)
5002 largestUnit
= defaultLargestUnit
;
5004 // Steps 21-25. (Not applicable)
5007 Rooted
<JSObject
*> options(
5008 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
5014 bool smallestUnitPresent
= true;
5017 bool largestUnitPresent
= true;
5021 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
5022 // absent "largestUnit" value.
5023 Rooted
<Value
> largestUnitValue(cx
);
5024 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
5025 &largestUnitValue
)) {
5029 if (!largestUnitValue
.isUndefined()) {
5030 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
5031 if (!largestUnitStr
) {
5035 largestUnit
= TemporalUnit::Auto
;
5036 if (!GetTemporalUnit(cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
5037 TemporalUnitGroup::DateTime
, &largestUnit
)) {
5043 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
5044 &zonedRelativeTo
, &timeZone
)) {
5047 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5048 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5051 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
5056 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5061 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5062 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5067 if (smallestUnit
== TemporalUnit::Auto
) {
5069 smallestUnitPresent
= false;
5072 smallestUnit
= TemporalUnit::Nanosecond
;
5075 // Step 18. (Moved above)
5078 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5081 if (largestUnitValue
.isUndefined()) {
5083 largestUnitPresent
= false;
5086 largestUnit
= defaultLargestUnit
;
5087 } else if (largestUnit
== TemporalUnit::Auto
) {
5089 largestUnit
= defaultLargestUnit
;
5093 if (!smallestUnitPresent
&& !largestUnitPresent
) {
5094 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5095 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
5100 if (largestUnit
> smallestUnit
) {
5101 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5102 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
5107 if (smallestUnit
> TemporalUnit::Day
) {
5109 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
5112 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
5120 bool hoursToDaysConversionMayOccur
= false;
5123 if (duration
.days
!= 0 && zonedRelativeTo
) {
5124 hoursToDaysConversionMayOccur
= true;
5128 else if (std::abs(duration
.hours
) >= 24) {
5129 hoursToDaysConversionMayOccur
= true;
5133 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
5134 roundingIncrement
== Increment
{1};
5137 bool calendarUnitsPresent
=
5138 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
5141 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
5142 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
5143 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
5144 std::abs(duration
.milliseconds
) < 1000 &&
5145 std::abs(duration
.microseconds
) < 1000 &&
5146 std::abs(duration
.nanoseconds
) < 1000) {
5148 auto* obj
= CreateTemporalDuration(cx
, duration
);
5153 args
.rval().setObject(*obj
);
5158 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5161 bool plainDateTimeOrRelativeToWillBeUsed
=
5162 !roundingGranularityIsNoop
|| largestUnit
<= TemporalUnit::Day
||
5163 calendarUnitsPresent
|| duration
.days
!= 0;
5166 PlainDateTime relativeToDateTime
;
5167 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5169 const auto& instant
= zonedRelativeTo
.instant();
5172 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5175 precalculatedPlainDateTime
=
5176 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5179 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5180 zonedRelativeTo
.calendar());
5181 if (!plainRelativeTo
) {
5187 Rooted
<CalendarRecord
> calendar(cx
);
5188 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5191 CalendarMethod::DateAdd
,
5192 CalendarMethod::DateUntil
,
5199 DateDuration unbalanceResult
;
5200 if (plainRelativeTo
) {
5201 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5202 largestUnit
, plainRelativeTo
, calendar
,
5203 &unbalanceResult
)) {
5207 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5208 largestUnit
, &unbalanceResult
)) {
5211 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5216 NormalizedDuration
{unbalanceResult
, NormalizeTimeDuration(duration
)};
5217 NormalizedDuration roundResult
;
5218 if (plainRelativeTo
|| zonedRelativeTo
) {
5219 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5220 roundingMode
, plainRelativeTo
, calendar
,
5221 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5226 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5227 roundingMode
, &roundResult
)) {
5233 TimeDuration balanceResult
;
5234 if (zonedRelativeTo
) {
5236 NormalizedDuration adjustResult
;
5237 if (!AdjustRoundedDurationDays(cx
, roundResult
, roundingIncrement
,
5238 smallestUnit
, roundingMode
, zonedRelativeTo
,
5240 precalculatedPlainDateTime
, &adjustResult
)) {
5243 roundResult
= adjustResult
;
5246 if (!BalanceTimeDurationRelative(
5247 cx
, roundResult
, largestUnit
, zonedRelativeTo
, timeZone
,
5248 precalculatedPlainDateTime
, &balanceResult
)) {
5253 NormalizedTimeDuration withDays
;
5254 if (!Add24HourDaysToNormalizedTimeDuration(
5255 cx
, roundResult
.time
, roundResult
.date
.days
, &withDays
)) {
5260 balanceResult
= temporal::BalanceTimeDuration(withDays
, largestUnit
);
5264 auto balanceInput
= DateDuration
{
5265 roundResult
.date
.years
,
5266 roundResult
.date
.months
,
5267 roundResult
.date
.weeks
,
5270 DateDuration dateResult
;
5271 if (!::BalanceDateDurationRelative(cx
, balanceInput
, largestUnit
,
5272 smallestUnit
, plainRelativeTo
, calendar
,
5278 auto result
= Duration
{
5279 double(dateResult
.years
), double(dateResult
.months
),
5280 double(dateResult
.weeks
), double(dateResult
.days
),
5281 double(balanceResult
.hours
), double(balanceResult
.minutes
),
5282 double(balanceResult
.seconds
), double(balanceResult
.milliseconds
),
5283 balanceResult
.microseconds
, balanceResult
.nanoseconds
,
5285 MOZ_ASSERT(IsValidDuration(result
));
5286 auto* obj
= CreateTemporalDuration(cx
, result
);
5291 args
.rval().setObject(*obj
);
5296 * Temporal.Duration.prototype.round ( options )
5298 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5300 CallArgs args
= CallArgsFromVp(argc
, vp
);
5301 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
5305 * Temporal.Duration.prototype.total ( totalOf )
5307 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
5308 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
5309 auto duration
= ToDuration(durationObj
);
5312 Rooted
<JSObject
*> relativeTo(cx
);
5313 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5314 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5315 Rooted
<TimeZoneRecord
> timeZone(cx
);
5316 auto unit
= TemporalUnit::Auto
;
5317 if (args
.get(0).isString()) {
5318 // Step 4. (Not applicable in our implementation.)
5320 // Steps 6-10. (Implicit)
5321 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
5324 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5325 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::Unit
,
5326 TemporalUnitGroup::DateTime
, &unit
)) {
5331 Rooted
<JSObject
*> totalOf(
5332 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
5338 if (!ToRelativeTemporalObject(cx
, totalOf
, &plainRelativeTo
,
5339 &zonedRelativeTo
, &timeZone
)) {
5342 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5343 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5346 if (!GetTemporalUnit(cx
, totalOf
, TemporalUnitKey::Unit
,
5347 TemporalUnitGroup::DateTime
, &unit
)) {
5351 if (unit
== TemporalUnit::Auto
) {
5352 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5353 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
5359 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5362 bool plainDateTimeOrRelativeToWillBeUsed
=
5363 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
5366 PlainDateTime relativeToDateTime
;
5367 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5369 const auto& instant
= zonedRelativeTo
.instant();
5372 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5375 precalculatedPlainDateTime
=
5376 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5379 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5380 zonedRelativeTo
.calendar());
5381 if (!plainRelativeTo
) {
5387 Rooted
<CalendarRecord
> calendar(cx
);
5388 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5391 CalendarMethod::DateAdd
,
5392 CalendarMethod::DateUntil
,
5399 DateDuration unbalanceResult
;
5400 if (plainRelativeTo
) {
5401 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5402 plainRelativeTo
, calendar
,
5403 &unbalanceResult
)) {
5407 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5408 &unbalanceResult
)) {
5411 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5415 int64_t unbalancedDays
= unbalanceResult
.days
;
5419 NormalizedTimeDuration balanceResult
;
5420 if (zonedRelativeTo
) {
5422 Rooted
<ZonedDateTime
> intermediate(cx
);
5423 if (!MoveRelativeZonedDateTime(
5424 cx
, zonedRelativeTo
, calendar
, timeZone
,
5425 {unbalanceResult
.years
, unbalanceResult
.months
,
5426 unbalanceResult
.weeks
, 0},
5427 precalculatedPlainDateTime
, &intermediate
)) {
5432 auto timeDuration
= NormalizeTimeDuration(duration
);
5435 const auto& startNs
= intermediate
.instant();
5438 const auto& startInstant
= startNs
;
5441 mozilla::Maybe
<PlainDateTime
> startDateTime
{};
5444 Instant intermediateNs
;
5445 if (unbalancedDays
!= 0) {
5447 PlainDateTime dateTime
;
5448 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5451 startDateTime
= mozilla::Some(dateTime
);
5454 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
5456 if (!AddDaysToZonedDateTime(cx
, startInstant
, dateTime
, timeZone
,
5457 isoCalendar
, unbalancedDays
, &addResult
)) {
5462 intermediateNs
= addResult
;
5465 intermediateNs
= startNs
;
5470 if (!AddInstant(cx
, intermediateNs
, timeDuration
, &endNs
)) {
5476 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startNs
);
5480 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5481 if (TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
&&
5482 difference
!= NormalizedTimeDuration
{}) {
5484 if (!startDateTime
) {
5485 PlainDateTime dateTime
;
5486 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5489 startDateTime
= mozilla::Some(dateTime
);
5493 NormalizedTimeAndDays timeAndDays
;
5494 if (!NormalizedTimeDurationToDays(cx
, difference
, intermediate
, timeZone
,
5495 *startDateTime
, &timeAndDays
)) {
5500 balanceResult
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
5503 days
= timeAndDays
.days
;
5506 balanceResult
= difference
;
5511 auto timeDuration
= NormalizeTimeDuration(duration
);
5514 if (!Add24HourDaysToNormalizedTimeDuration(cx
, timeDuration
, unbalancedDays
,
5522 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult
));
5525 auto roundInput
= NormalizedDuration
{
5527 unbalanceResult
.years
,
5528 unbalanceResult
.months
,
5529 unbalanceResult
.weeks
,
5535 if (plainRelativeTo
|| zonedRelativeTo
) {
5536 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5537 TemporalRoundingMode::Trunc
, plainRelativeTo
, calendar
,
5538 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5543 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5544 TemporalRoundingMode::Trunc
, &total
)) {
5550 args
.rval().setNumber(total
);
5555 * Temporal.Duration.prototype.total ( totalOf )
5557 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5559 CallArgs args
= CallArgsFromVp(argc
, vp
);
5560 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
5564 * Temporal.Duration.prototype.toString ( [ options ] )
5566 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
5567 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5570 SecondsStringPrecision precision
= {Precision::Auto(),
5571 TemporalUnit::Nanosecond
, Increment
{1}};
5572 auto roundingMode
= TemporalRoundingMode::Trunc
;
5573 if (args
.hasDefined(0)) {
5575 Rooted
<JSObject
*> options(
5576 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
5582 auto digits
= Precision::Auto();
5583 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
5588 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5593 auto smallestUnit
= TemporalUnit::Auto
;
5594 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5595 TemporalUnitGroup::Time
, &smallestUnit
)) {
5600 if (smallestUnit
== TemporalUnit::Hour
||
5601 smallestUnit
== TemporalUnit::Minute
) {
5602 const char* smallestUnitStr
=
5603 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
5604 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5605 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
5606 smallestUnitStr
, "smallestUnit");
5611 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
5616 if (precision
.unit
!= TemporalUnit::Nanosecond
||
5617 precision
.increment
!= Increment
{1}) {
5619 auto timeDuration
= NormalizeTimeDuration(duration
);
5622 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
5625 auto rounded
= RoundDuration(timeDuration
, precision
.increment
,
5626 precision
.unit
, roundingMode
);
5629 auto balanced
= BalanceTimeDuration(
5630 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
5634 duration
.years
, duration
.months
,
5635 duration
.weeks
, duration
.days
+ double(balanced
.days
),
5636 double(balanced
.hours
), double(balanced
.minutes
),
5637 double(balanced
.seconds
), double(balanced
.milliseconds
),
5638 balanced
.microseconds
, balanced
.nanoseconds
,
5640 MOZ_ASSERT(IsValidDuration(duration
));
5647 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
5652 args
.rval().setString(str
);
5657 * Temporal.Duration.prototype.toString ( [ options ] )
5659 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5661 CallArgs args
= CallArgsFromVp(argc
, vp
);
5662 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
5666 * Temporal.Duration.prototype.toJSON ( )
5668 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
5669 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5672 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5677 args
.rval().setString(str
);
5682 * Temporal.Duration.prototype.toJSON ( )
5684 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5686 CallArgs args
= CallArgsFromVp(argc
, vp
);
5687 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
5691 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5693 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
5694 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5697 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5702 args
.rval().setString(str
);
5707 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5709 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5711 CallArgs args
= CallArgsFromVp(argc
, vp
);
5712 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
5716 * Temporal.Duration.prototype.valueOf ( )
5718 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5719 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
5720 "Duration", "primitive type");
5724 const JSClass
DurationObject::class_
= {
5725 "Temporal.Duration",
5726 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
5727 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
5729 &DurationObject::classSpec_
,
5732 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
5734 static const JSFunctionSpec Duration_methods
[] = {
5735 JS_FN("from", Duration_from
, 1, 0),
5736 JS_FN("compare", Duration_compare
, 2, 0),
5740 static const JSFunctionSpec Duration_prototype_methods
[] = {
5741 JS_FN("with", Duration_with
, 1, 0),
5742 JS_FN("negated", Duration_negated
, 0, 0),
5743 JS_FN("abs", Duration_abs
, 0, 0),
5744 JS_FN("add", Duration_add
, 1, 0),
5745 JS_FN("subtract", Duration_subtract
, 1, 0),
5746 JS_FN("round", Duration_round
, 1, 0),
5747 JS_FN("total", Duration_total
, 1, 0),
5748 JS_FN("toString", Duration_toString
, 0, 0),
5749 JS_FN("toJSON", Duration_toJSON
, 0, 0),
5750 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
5751 JS_FN("valueOf", Duration_valueOf
, 0, 0),
5755 static const JSPropertySpec Duration_prototype_properties
[] = {
5756 JS_PSG("years", Duration_years
, 0),
5757 JS_PSG("months", Duration_months
, 0),
5758 JS_PSG("weeks", Duration_weeks
, 0),
5759 JS_PSG("days", Duration_days
, 0),
5760 JS_PSG("hours", Duration_hours
, 0),
5761 JS_PSG("minutes", Duration_minutes
, 0),
5762 JS_PSG("seconds", Duration_seconds
, 0),
5763 JS_PSG("milliseconds", Duration_milliseconds
, 0),
5764 JS_PSG("microseconds", Duration_microseconds
, 0),
5765 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
5766 JS_PSG("sign", Duration_sign
, 0),
5767 JS_PSG("blank", Duration_blank
, 0),
5768 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
5772 const ClassSpec
DurationObject::classSpec_
= {
5773 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
5774 GenericCreatePrototype
<DurationObject
>,
5777 Duration_prototype_methods
,
5778 Duration_prototype_properties
,
5780 ClassSpec::DontDefineConstructor
,