1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/Duration.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/EnumSet.h"
13 #include "mozilla/FloatingPoint.h"
14 #include "mozilla/Maybe.h"
19 #include <initializer_list>
21 #include <type_traits>
26 #include "NamespaceImports.h"
28 #include "builtin/temporal/Calendar.h"
29 #include "builtin/temporal/Instant.h"
30 #include "builtin/temporal/Int128.h"
31 #include "builtin/temporal/Int96.h"
32 #include "builtin/temporal/PlainDate.h"
33 #include "builtin/temporal/PlainDateTime.h"
34 #include "builtin/temporal/Temporal.h"
35 #include "builtin/temporal/TemporalFields.h"
36 #include "builtin/temporal/TemporalParser.h"
37 #include "builtin/temporal/TemporalRoundingMode.h"
38 #include "builtin/temporal/TemporalTypes.h"
39 #include "builtin/temporal/TemporalUnit.h"
40 #include "builtin/temporal/TimeZone.h"
41 #include "builtin/temporal/Wrapped.h"
42 #include "builtin/temporal/ZonedDateTime.h"
43 #include "gc/AllocKind.h"
44 #include "gc/Barrier.h"
45 #include "gc/GCEnum.h"
46 #include "js/CallArgs.h"
47 #include "js/CallNonGenericMethod.h"
49 #include "js/Conversions.h"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
54 #include "js/Printer.h"
55 #include "js/PropertyDescriptor.h"
56 #include "js/PropertySpec.h"
57 #include "js/RootingAPI.h"
59 #include "util/StringBuffer.h"
60 #include "vm/BytecodeUtil.h"
61 #include "vm/GlobalObject.h"
62 #include "vm/JSAtomState.h"
63 #include "vm/JSContext.h"
64 #include "vm/JSObject.h"
65 #include "vm/ObjectOperations.h"
66 #include "vm/PlainObject.h"
67 #include "vm/StringType.h"
69 #include "vm/JSObject-inl.h"
70 #include "vm/NativeObject-inl.h"
71 #include "vm/ObjectOperations-inl.h"
74 using namespace js::temporal
;
76 static inline bool IsDuration(Handle
<Value
> v
) {
77 return v
.isObject() && v
.toObject().is
<DurationObject
>();
81 static bool IsIntegerOrInfinity(double d
) {
82 return IsInteger(d
) || std::isinf(d
);
85 static bool IsIntegerOrInfinityDuration(const Duration
& duration
) {
86 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
87 milliseconds
, microseconds
, nanoseconds
] = duration
;
89 // Integers exceeding the Number range are represented as infinity.
91 return IsIntegerOrInfinity(years
) && IsIntegerOrInfinity(months
) &&
92 IsIntegerOrInfinity(weeks
) && IsIntegerOrInfinity(days
) &&
93 IsIntegerOrInfinity(hours
) && IsIntegerOrInfinity(minutes
) &&
94 IsIntegerOrInfinity(seconds
) && IsIntegerOrInfinity(milliseconds
) &&
95 IsIntegerOrInfinity(microseconds
) && IsIntegerOrInfinity(nanoseconds
);
98 static bool IsIntegerDuration(const Duration
& duration
) {
99 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
100 milliseconds
, microseconds
, nanoseconds
] = duration
;
102 return IsInteger(years
) && IsInteger(months
) && IsInteger(weeks
) &&
103 IsInteger(days
) && IsInteger(hours
) && IsInteger(minutes
) &&
104 IsInteger(seconds
) && IsInteger(milliseconds
) &&
105 IsInteger(microseconds
) && IsInteger(nanoseconds
);
109 static constexpr bool IsSafeInteger(int64_t x
) {
110 constexpr int64_t MaxSafeInteger
= int64_t(1) << 53;
111 constexpr int64_t MinSafeInteger
= -MaxSafeInteger
;
112 return MinSafeInteger
< x
&& x
< MaxSafeInteger
;
116 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
117 * milliseconds, microseconds, nanoseconds )
119 int32_t js::temporal::DurationSign(const Duration
& duration
) {
120 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
122 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
123 milliseconds
, microseconds
, nanoseconds
] = duration
;
126 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
127 milliseconds
, microseconds
, nanoseconds
}) {
144 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
145 * milliseconds, microseconds, nanoseconds )
147 static int32_t DurationSign(const DateDuration
& duration
) {
148 const auto& [years
, months
, weeks
, days
] = duration
;
151 for (auto v
: {years
, months
, weeks
, days
}) {
168 * Normalize a nanoseconds amount into a time duration.
170 static NormalizedTimeDuration
NormalizeNanoseconds(const Int96
& nanoseconds
) {
171 // Split into seconds and nanoseconds.
172 auto [seconds
, nanos
] = nanoseconds
/ ToNanoseconds(TemporalUnit::Second
);
174 return {seconds
, nanos
};
178 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
179 * value is too large.
181 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeNanoseconds(
182 double nanoseconds
) {
183 MOZ_ASSERT(IsInteger(nanoseconds
));
185 if (auto int96
= Int96::fromInteger(nanoseconds
)) {
186 // The number of normalized seconds must not exceed `2**53 - 1`.
187 constexpr auto limit
=
188 Int96
{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second
);
190 if (int96
->abs() < limit
) {
191 return mozilla::Some(NormalizeNanoseconds(*int96
));
194 return mozilla::Nothing();
198 * Normalize a microseconds amount into a time duration.
200 static NormalizedTimeDuration
NormalizeMicroseconds(const Int96
& microseconds
) {
201 // Split into seconds and microseconds.
202 auto [seconds
, micros
] = microseconds
/ ToMicroseconds(TemporalUnit::Second
);
204 // Scale microseconds to nanoseconds.
205 int32_t nanos
= micros
* int32_t(ToNanoseconds(TemporalUnit::Microsecond
));
207 return {seconds
, nanos
};
211 * Normalize a microseconds amount into a time duration. Return Nothing if the
212 * value is too large.
214 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeMicroseconds(
215 double microseconds
) {
216 MOZ_ASSERT(IsInteger(microseconds
));
218 if (auto int96
= Int96::fromInteger(microseconds
)) {
219 // The number of normalized seconds must not exceed `2**53 - 1`.
220 constexpr auto limit
=
221 Int96
{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second
);
223 if (int96
->abs() < limit
) {
224 return mozilla::Some(NormalizeMicroseconds(*int96
));
227 return mozilla::Nothing();
231 * Normalize a duration into a time duration. Return Nothing if any duration
232 * value is too large.
234 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeSeconds(
235 const Duration
& duration
) {
237 auto nanoseconds
= NormalizeNanoseconds(duration
.nanoseconds
);
241 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds
));
243 auto microseconds
= NormalizeMicroseconds(duration
.microseconds
);
247 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds
));
249 // Overflows for millis/seconds/minutes/hours/days always result in an
250 // invalid normalized time duration.
252 int64_t milliseconds
;
253 if (!mozilla::NumberEqualsInt64(duration
.milliseconds
, &milliseconds
)) {
258 if (!mozilla::NumberEqualsInt64(duration
.seconds
, &seconds
)) {
263 if (!mozilla::NumberEqualsInt64(duration
.minutes
, &minutes
)) {
268 if (!mozilla::NumberEqualsInt64(duration
.hours
, &hours
)) {
273 if (!mozilla::NumberEqualsInt64(duration
.days
, &days
)) {
277 // Compute the overall amount of milliseconds.
278 mozilla::CheckedInt64 millis
= days
;
286 millis
+= milliseconds
;
287 if (!millis
.isValid()) {
291 auto milli
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
292 if (!IsValidNormalizedTimeDuration(milli
)) {
296 // Compute the overall time duration.
297 auto result
= milli
+ *microseconds
+ *nanoseconds
;
298 if (!IsValidNormalizedTimeDuration(result
)) {
302 return mozilla::Some(result
);
305 return mozilla::Nothing();
309 * Normalize a days amount into a time duration. Return Nothing if the value is
312 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeDays(int64_t days
) {
314 // Compute the overall amount of milliseconds.
316 mozilla::CheckedInt64(days
) * ToMilliseconds(TemporalUnit::Day
);
317 if (!millis
.isValid()) {
321 auto result
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
322 if (!IsValidNormalizedTimeDuration(result
)) {
326 return mozilla::Some(result
);
329 return mozilla::Nothing();
333 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
336 static NormalizedTimeDuration
NormalizeTimeDuration(
337 double hours
, double minutes
, double seconds
, double milliseconds
,
338 double microseconds
, double nanoseconds
) {
339 MOZ_ASSERT(IsInteger(hours
));
340 MOZ_ASSERT(IsInteger(minutes
));
341 MOZ_ASSERT(IsInteger(seconds
));
342 MOZ_ASSERT(IsInteger(milliseconds
));
343 MOZ_ASSERT(IsInteger(microseconds
));
344 MOZ_ASSERT(IsInteger(nanoseconds
));
347 mozilla::CheckedInt64 millis
= int64_t(hours
);
349 millis
+= int64_t(minutes
);
351 millis
+= int64_t(seconds
);
353 millis
+= int64_t(milliseconds
);
354 MOZ_ASSERT(millis
.isValid());
356 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
359 auto micros
= Int96::fromInteger(microseconds
);
362 normalized
+= NormalizeMicroseconds(*micros
);
365 auto nanos
= Int96::fromInteger(nanoseconds
);
368 normalized
+= NormalizeNanoseconds(*nanos
);
371 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
378 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
381 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
382 int32_t hours
, int32_t minutes
, int32_t seconds
, int32_t milliseconds
,
383 int32_t microseconds
, int32_t nanoseconds
) {
385 mozilla::CheckedInt64 millis
= int64_t(hours
);
387 millis
+= int64_t(minutes
);
389 millis
+= int64_t(seconds
);
391 millis
+= int64_t(milliseconds
);
392 MOZ_ASSERT(millis
.isValid());
394 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
397 normalized
+= NormalizeMicroseconds(Int96
{microseconds
});
400 normalized
+= NormalizeNanoseconds(Int96
{nanoseconds
});
403 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
410 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
413 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
414 const Duration
& duration
) {
415 MOZ_ASSERT(IsValidDuration(duration
));
417 return ::NormalizeTimeDuration(duration
.hours
, duration
.minutes
,
418 duration
.seconds
, duration
.milliseconds
,
419 duration
.microseconds
, duration
.nanoseconds
);
423 * AddNormalizedTimeDuration ( one, two )
425 static bool AddNormalizedTimeDuration(JSContext
* cx
,
426 const NormalizedTimeDuration
& one
,
427 const NormalizedTimeDuration
& two
,
428 NormalizedTimeDuration
* result
) {
429 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
430 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
433 auto sum
= one
+ two
;
436 if (!IsValidNormalizedTimeDuration(sum
)) {
437 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
438 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
448 * SubtractNormalizedTimeDuration ( one, two )
450 static bool SubtractNormalizedTimeDuration(JSContext
* cx
,
451 const NormalizedTimeDuration
& one
,
452 const NormalizedTimeDuration
& two
,
453 NormalizedTimeDuration
* result
) {
454 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
455 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
458 auto sum
= one
- two
;
461 if (!IsValidNormalizedTimeDuration(sum
)) {
462 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
463 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
473 * Add24HourDaysToNormalizedTimeDuration ( d, days )
475 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
476 JSContext
* cx
, const NormalizedTimeDuration
& d
, int64_t days
,
477 NormalizedTimeDuration
* result
) {
478 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
481 auto normalizedDays
= NormalizeDays(days
);
482 if (!normalizedDays
) {
483 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
484 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
489 auto sum
= d
+ *normalizedDays
;
490 if (!IsValidNormalizedTimeDuration(sum
)) {
491 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
492 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
502 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
504 bool js::temporal::CombineDateAndNormalizedTimeDuration(
505 JSContext
* cx
, const DateDuration
& date
, const NormalizedTimeDuration
& time
,
506 NormalizedDuration
* result
) {
507 MOZ_ASSERT(IsValidDuration(date
));
508 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
511 int32_t dateSign
= ::DurationSign(date
);
514 int32_t timeSign
= NormalizedTimeDurationSign(time
);
517 if ((dateSign
* timeSign
) < 0) {
518 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
519 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN
);
524 *result
= {date
, time
};
529 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
531 NormalizedTimeDuration
532 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
533 const Instant
& one
, const Instant
& two
) {
534 MOZ_ASSERT(IsValidEpochInstant(one
));
535 MOZ_ASSERT(IsValidEpochInstant(two
));
538 auto result
= one
- two
;
541 MOZ_ASSERT(IsValidInstantSpan(result
));
544 return result
.to
<NormalizedTimeDuration
>();
548 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
549 * milliseconds, microseconds, nanoseconds )
551 bool js::temporal::IsValidDuration(const Duration
& duration
) {
552 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
554 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
555 milliseconds
, microseconds
, nanoseconds
] = duration
;
558 int32_t sign
= DurationSign(duration
);
561 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
562 milliseconds
, microseconds
, nanoseconds
}) {
564 if (!std::isfinite(v
)) {
569 if (v
< 0 && sign
> 0) {
574 if (v
> 0 && sign
< 0) {
580 if (std::abs(years
) >= double(int64_t(1) << 32)) {
585 if (std::abs(months
) >= double(int64_t(1) << 32)) {
590 if (std::abs(weeks
) >= double(int64_t(1) << 32)) {
595 if (!NormalizeSeconds(duration
)) {
605 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
606 * milliseconds, microseconds, nanoseconds )
608 bool js::temporal::IsValidDuration(const DateDuration
& duration
) {
609 return IsValidDuration(duration
.toDuration());
613 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
614 * milliseconds, microseconds, nanoseconds )
616 bool js::temporal::IsValidDuration(const NormalizedDuration
& duration
) {
617 return IsValidDuration(duration
.date
) &&
618 IsValidNormalizedTimeDuration(duration
.time
) &&
619 (::DurationSign(duration
.date
) *
620 NormalizedTimeDurationSign(duration
.time
) >=
625 static bool ThrowInvalidDurationPart(JSContext
* cx
, double value
,
626 const char* name
, unsigned errorNumber
) {
628 const char* numStr
= NumberToCString(&cbuf
, value
);
630 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
, name
,
636 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
637 * milliseconds, microseconds, nanoseconds )
639 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
640 const Duration
& duration
) {
641 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
643 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
644 milliseconds
, microseconds
, nanoseconds
] = duration
;
647 int32_t sign
= DurationSign(duration
);
649 auto throwIfInvalid
= [&](double v
, const char* name
) {
651 if (!std::isfinite(v
)) {
652 return ThrowInvalidDurationPart(
653 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
657 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
658 return ThrowInvalidDurationPart(cx
, v
, name
,
659 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
665 auto throwIfTooLarge
= [&](double v
, const char* name
) {
666 if (std::abs(v
) >= double(int64_t(1) << 32)) {
667 return ThrowInvalidDurationPart(
668 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
674 if (!throwIfInvalid(years
, "years")) {
677 if (!throwIfInvalid(months
, "months")) {
680 if (!throwIfInvalid(weeks
, "weeks")) {
683 if (!throwIfInvalid(days
, "days")) {
686 if (!throwIfInvalid(hours
, "hours")) {
689 if (!throwIfInvalid(minutes
, "minutes")) {
692 if (!throwIfInvalid(seconds
, "seconds")) {
695 if (!throwIfInvalid(milliseconds
, "milliseconds")) {
698 if (!throwIfInvalid(microseconds
, "microseconds")) {
701 if (!throwIfInvalid(nanoseconds
, "nanoseconds")) {
706 if (!throwIfTooLarge(years
, "years")) {
711 if (!throwIfTooLarge(months
, "months")) {
716 if (!throwIfTooLarge(weeks
, "weeks")) {
721 if (!NormalizeSeconds(duration
)) {
722 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
723 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
727 MOZ_ASSERT(IsValidDuration(duration
));
734 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
735 * milliseconds, microseconds, nanoseconds )
737 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
738 const DateDuration
& duration
) {
739 const auto& [years
, months
, weeks
, days
] = duration
;
742 int32_t sign
= ::DurationSign(duration
);
744 auto throwIfInvalid
= [&](int64_t v
, const char* name
) {
745 // Step 2.a. (Not applicable)
748 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
749 return ThrowInvalidDurationPart(cx
, double(v
), name
,
750 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
756 auto throwIfTooLarge
= [&](int64_t v
, const char* name
) {
757 if (std::abs(v
) >= (int64_t(1) << 32)) {
758 return ThrowInvalidDurationPart(
759 cx
, double(v
), name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
765 if (!throwIfInvalid(years
, "years")) {
768 if (!throwIfInvalid(months
, "months")) {
771 if (!throwIfInvalid(weeks
, "weeks")) {
774 if (!throwIfInvalid(days
, "days")) {
779 if (!throwIfTooLarge(years
, "years")) {
784 if (!throwIfTooLarge(months
, "months")) {
789 if (!throwIfTooLarge(weeks
, "weeks")) {
794 if (std::abs(days
) > ((int64_t(1) << 53) / 86400)) {
795 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
796 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
800 MOZ_ASSERT(IsValidDuration(duration
));
807 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
808 * seconds, milliseconds, microseconds )
810 static TemporalUnit
DefaultTemporalLargestUnit(const Duration
& duration
) {
811 MOZ_ASSERT(IsIntegerDuration(duration
));
814 if (duration
.years
!= 0) {
815 return TemporalUnit::Year
;
819 if (duration
.months
!= 0) {
820 return TemporalUnit::Month
;
824 if (duration
.weeks
!= 0) {
825 return TemporalUnit::Week
;
829 if (duration
.days
!= 0) {
830 return TemporalUnit::Day
;
834 if (duration
.hours
!= 0) {
835 return TemporalUnit::Hour
;
839 if (duration
.minutes
!= 0) {
840 return TemporalUnit::Minute
;
844 if (duration
.seconds
!= 0) {
845 return TemporalUnit::Second
;
849 if (duration
.milliseconds
!= 0) {
850 return TemporalUnit::Millisecond
;
854 if (duration
.microseconds
!= 0) {
855 return TemporalUnit::Microsecond
;
859 return TemporalUnit::Nanosecond
;
863 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
864 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
866 static DurationObject
* CreateTemporalDuration(JSContext
* cx
,
867 const CallArgs
& args
,
868 const Duration
& duration
) {
869 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
870 milliseconds
, microseconds
, nanoseconds
] = duration
;
873 if (!ThrowIfInvalidDuration(cx
, duration
)) {
878 Rooted
<JSObject
*> proto(cx
);
879 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Duration
, &proto
)) {
883 auto* object
= NewObjectWithClassProto
<DurationObject
>(cx
, proto
);
889 // Add zero to convert -0 to +0.
890 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
891 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
892 NumberValue(months
+ (+0.0)));
893 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
894 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
895 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
896 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
897 NumberValue(minutes
+ (+0.0)));
898 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
899 NumberValue(seconds
+ (+0.0)));
900 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
901 NumberValue(milliseconds
+ (+0.0)));
902 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
903 NumberValue(microseconds
+ (+0.0)));
904 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
905 NumberValue(nanoseconds
+ (+0.0)));
912 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
913 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
915 DurationObject
* js::temporal::CreateTemporalDuration(JSContext
* cx
,
916 const Duration
& duration
) {
917 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
918 milliseconds
, microseconds
, nanoseconds
] = duration
;
920 MOZ_ASSERT(IsInteger(years
));
921 MOZ_ASSERT(IsInteger(months
));
922 MOZ_ASSERT(IsInteger(weeks
));
923 MOZ_ASSERT(IsInteger(days
));
924 MOZ_ASSERT(IsInteger(hours
));
925 MOZ_ASSERT(IsInteger(minutes
));
926 MOZ_ASSERT(IsInteger(seconds
));
927 MOZ_ASSERT(IsInteger(milliseconds
));
928 MOZ_ASSERT(IsInteger(microseconds
));
929 MOZ_ASSERT(IsInteger(nanoseconds
));
932 if (!ThrowIfInvalidDuration(cx
, duration
)) {
937 auto* object
= NewBuiltinClassInstance
<DurationObject
>(cx
);
943 // Add zero to convert -0 to +0.
944 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
945 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
946 NumberValue(months
+ (+0.0)));
947 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
948 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
949 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
950 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
951 NumberValue(minutes
+ (+0.0)));
952 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
953 NumberValue(seconds
+ (+0.0)));
954 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
955 NumberValue(milliseconds
+ (+0.0)));
956 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
957 NumberValue(microseconds
+ (+0.0)));
958 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
959 NumberValue(nanoseconds
+ (+0.0)));
966 * ToIntegerIfIntegral ( argument )
968 static bool ToIntegerIfIntegral(JSContext
* cx
, const char* name
,
969 Handle
<Value
> argument
, double* num
) {
972 if (!JS::ToNumber(cx
, argument
, &d
)) {
977 if (!js::IsInteger(d
)) {
979 const char* numStr
= NumberToCString(&cbuf
, d
);
981 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
982 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
993 * ToIntegerIfIntegral ( argument )
995 static bool ToIntegerIfIntegral(JSContext
* cx
, Handle
<PropertyName
*> name
,
996 Handle
<Value
> argument
, double* result
) {
999 if (!JS::ToNumber(cx
, argument
, &d
)) {
1004 if (!js::IsInteger(d
)) {
1005 if (auto nameStr
= js::QuoteString(cx
, name
)) {
1007 const char* numStr
= NumberToCString(&cbuf
, d
);
1009 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1010 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
1022 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1024 static bool ToTemporalPartialDurationRecord(
1025 JSContext
* cx
, Handle
<JSObject
*> temporalDurationLike
, Duration
* result
) {
1026 // Steps 1-3. (Not applicable in our implementation.)
1028 Rooted
<Value
> value(cx
);
1031 auto getDurationProperty
= [&](Handle
<PropertyName
*> name
, double* num
) {
1032 if (!GetProperty(cx
, temporalDurationLike
, temporalDurationLike
, name
,
1037 if (!value
.isUndefined()) {
1040 if (!ToIntegerIfIntegral(cx
, name
, value
, num
)) {
1048 if (!getDurationProperty(cx
->names().days
, &result
->days
)) {
1051 if (!getDurationProperty(cx
->names().hours
, &result
->hours
)) {
1054 if (!getDurationProperty(cx
->names().microseconds
, &result
->microseconds
)) {
1057 if (!getDurationProperty(cx
->names().milliseconds
, &result
->milliseconds
)) {
1060 if (!getDurationProperty(cx
->names().minutes
, &result
->minutes
)) {
1063 if (!getDurationProperty(cx
->names().months
, &result
->months
)) {
1066 if (!getDurationProperty(cx
->names().nanoseconds
, &result
->nanoseconds
)) {
1069 if (!getDurationProperty(cx
->names().seconds
, &result
->seconds
)) {
1072 if (!getDurationProperty(cx
->names().weeks
, &result
->weeks
)) {
1075 if (!getDurationProperty(cx
->names().years
, &result
->years
)) {
1081 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1082 JSMSG_TEMPORAL_DURATION_MISSING_UNIT
);
1091 * ToTemporalDurationRecord ( temporalDurationLike )
1093 bool js::temporal::ToTemporalDurationRecord(JSContext
* cx
,
1094 Handle
<Value
> temporalDurationLike
,
1097 if (!temporalDurationLike
.isObject()) {
1099 if (!temporalDurationLike
.isString()) {
1100 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
,
1101 temporalDurationLike
, nullptr, "not a string");
1104 Rooted
<JSString
*> string(cx
, temporalDurationLike
.toString());
1107 return ParseTemporalDurationString(cx
, string
, result
);
1110 Rooted
<JSObject
*> durationLike(cx
, &temporalDurationLike
.toObject());
1113 if (auto* duration
= durationLike
->maybeUnwrapIf
<DurationObject
>()) {
1114 *result
= ToDuration(duration
);
1119 Duration duration
= {};
1122 if (!ToTemporalPartialDurationRecord(cx
, durationLike
, &duration
)) {
1127 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1137 * ToTemporalDuration ( item )
1139 Wrapped
<DurationObject
*> js::temporal::ToTemporalDuration(JSContext
* cx
,
1140 Handle
<Value
> item
) {
1142 if (item
.isObject()) {
1143 JSObject
* itemObj
= &item
.toObject();
1144 if (itemObj
->canUnwrapAs
<DurationObject
>()) {
1151 if (!ToTemporalDurationRecord(cx
, item
, &result
)) {
1156 return CreateTemporalDuration(cx
, result
);
1160 * ToTemporalDuration ( item )
1162 bool js::temporal::ToTemporalDuration(JSContext
* cx
, Handle
<Value
> item
,
1164 auto obj
= ToTemporalDuration(cx
, item
);
1169 *result
= ToDuration(&obj
.unwrap());
1174 * DaysUntil ( earlier, later )
1176 int32_t js::temporal::DaysUntil(const PlainDate
& earlier
,
1177 const PlainDate
& later
) {
1178 MOZ_ASSERT(ISODateTimeWithinLimits(earlier
));
1179 MOZ_ASSERT(ISODateTimeWithinLimits(later
));
1182 int32_t epochDaysEarlier
= MakeDay(earlier
);
1183 MOZ_ASSERT(std::abs(epochDaysEarlier
) <= 100'000'000);
1186 int32_t epochDaysLater
= MakeDay(later
);
1187 MOZ_ASSERT(std::abs(epochDaysLater
) <= 100'000'000);
1190 return epochDaysLater
- epochDaysEarlier
;
1194 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1196 static bool MoveRelativeDate(
1197 JSContext
* cx
, Handle
<CalendarRecord
> calendar
,
1198 Handle
<Wrapped
<PlainDateObject
*>> relativeTo
, const DateDuration
& duration
,
1199 MutableHandle
<Wrapped
<PlainDateObject
*>> relativeToResult
,
1200 int32_t* daysResult
) {
1201 auto* unwrappedRelativeTo
= relativeTo
.unwrap(cx
);
1202 if (!unwrappedRelativeTo
) {
1205 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1208 auto newDate
= AddDate(cx
, calendar
, relativeTo
, duration
);
1212 auto later
= ToPlainDate(&newDate
.unwrap());
1213 relativeToResult
.set(newDate
);
1216 *daysResult
= DaysUntil(relativeToDate
, later
);
1217 MOZ_ASSERT(std::abs(*daysResult
) <= 200'000'000);
1224 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1225 * months, weeks, days, precalculatedPlainDateTime )
1227 static bool MoveRelativeZonedDateTime(
1228 JSContext
* cx
, Handle
<ZonedDateTime
> zonedDateTime
,
1229 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
1230 const DateDuration
& duration
,
1231 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1232 MutableHandle
<ZonedDateTime
> result
) {
1234 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1235 timeZone
, TimeZoneMethod::GetOffsetNanosecondsFor
));
1238 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1239 timeZone
, TimeZoneMethod::GetPossibleInstantsFor
));
1242 Instant intermediateNs
;
1243 if (precalculatedPlainDateTime
) {
1244 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1245 duration
, *precalculatedPlainDateTime
,
1250 if (!AddZonedDateTime(cx
, zonedDateTime
.instant(), timeZone
, calendar
,
1251 duration
, &intermediateNs
)) {
1255 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
1258 result
.set(ZonedDateTime
{intermediateNs
, zonedDateTime
.timeZone(),
1259 zonedDateTime
.calendar()});
1264 * Split duration into full days and remainding nanoseconds.
1266 static NormalizedTimeAndDays
NormalizedTimeDurationToDays(
1267 const NormalizedTimeDuration
& duration
) {
1268 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1270 auto [seconds
, nanoseconds
] = duration
;
1271 if (seconds
< 0 && nanoseconds
> 0) {
1273 nanoseconds
-= 1'000'000'000;
1276 int64_t days
= seconds
/ ToSeconds(TemporalUnit::Day
);
1277 seconds
= seconds
% ToSeconds(TemporalUnit::Day
);
1279 int64_t time
= seconds
* ToNanoseconds(TemporalUnit::Second
) + nanoseconds
;
1281 constexpr int64_t dayLength
= ToNanoseconds(TemporalUnit::Day
);
1282 MOZ_ASSERT(std::abs(time
) < dayLength
);
1284 return {days
, time
, dayLength
};
1288 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1289 * microseconds, nanoseconds )
1291 static TimeDuration
CreateTimeDurationRecord(int64_t days
, int64_t hours
,
1292 int64_t minutes
, int64_t seconds
,
1293 int64_t milliseconds
,
1294 double microseconds
,
1295 double nanoseconds
) {
1297 MOZ_ASSERT(IsValidDuration(
1298 {0, 0, 0, double(days
), double(hours
), double(minutes
), double(seconds
),
1299 double(milliseconds
), microseconds
, nanoseconds
}));
1301 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1302 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1303 MOZ_ASSERT(IsSafeInteger(days
));
1304 MOZ_ASSERT(IsSafeInteger(hours
));
1305 MOZ_ASSERT(IsSafeInteger(minutes
));
1306 MOZ_ASSERT(IsSafeInteger(seconds
));
1308 // |milliseconds| is explicitly casted to double by consumers, so we can also
1309 // omit the `ℝ(𝔽(x))` conversion.
1312 // NB: Adds +0.0 to correctly handle negative zero.
1319 microseconds
+ (+0.0),
1320 nanoseconds
+ (+0.0),
1325 * BalanceTimeDuration ( norm, largestUnit )
1327 TimeDuration
js::temporal::BalanceTimeDuration(
1328 const NormalizedTimeDuration
& duration
, TemporalUnit largestUnit
) {
1329 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1331 auto [seconds
, nanoseconds
] = duration
;
1333 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1334 // Convert these back to their absolute value and adjust the seconds part
1337 // For example the nanoseconds duration |-1n| is represented as the
1338 // duration {seconds: -1, nanoseconds: 999'999'999}.
1339 if (seconds
< 0 && nanoseconds
> 0) {
1341 nanoseconds
-= ToNanoseconds(TemporalUnit::Second
);
1347 int64_t minutes
= 0;
1348 int64_t milliseconds
= 0;
1349 int64_t microseconds
= 0;
1351 // Steps 2-3. (Not applicable in our implementation.)
1353 // We don't need to convert to positive numbers, because integer division
1354 // truncates and the %-operator has modulo semantics.
1357 switch (largestUnit
) {
1359 case TemporalUnit::Year
:
1360 case TemporalUnit::Month
:
1361 case TemporalUnit::Week
:
1362 case TemporalUnit::Day
: {
1364 microseconds
= nanoseconds
/ 1000;
1367 nanoseconds
= nanoseconds
% 1000;
1370 milliseconds
= microseconds
/ 1000;
1373 microseconds
= microseconds
% 1000;
1375 // Steps 4.e-f. (Not applicable)
1376 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1379 minutes
= seconds
/ 60;
1382 seconds
= seconds
% 60;
1385 hours
= minutes
/ 60;
1388 minutes
= minutes
% 60;
1400 case TemporalUnit::Hour
: {
1402 microseconds
= nanoseconds
/ 1000;
1405 nanoseconds
= nanoseconds
% 1000;
1408 milliseconds
= microseconds
/ 1000;
1411 microseconds
= microseconds
% 1000;
1413 // Steps 5.e-f. (Not applicable)
1414 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1417 minutes
= seconds
/ 60;
1420 seconds
= seconds
% 60;
1423 hours
= minutes
/ 60;
1426 minutes
= minutes
% 60;
1431 case TemporalUnit::Minute
: {
1433 microseconds
= nanoseconds
/ 1000;
1436 nanoseconds
= nanoseconds
% 1000;
1439 milliseconds
= microseconds
/ 1000;
1442 microseconds
= microseconds
% 1000;
1444 // Steps 6.e-f. (Not applicable)
1445 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1448 minutes
= seconds
/ 60;
1451 seconds
= seconds
% 60;
1457 case TemporalUnit::Second
: {
1459 microseconds
= nanoseconds
/ 1000;
1462 nanoseconds
= nanoseconds
% 1000;
1465 milliseconds
= microseconds
/ 1000;
1468 microseconds
= microseconds
% 1000;
1470 // Steps 7.e-f. (Not applicable)
1471 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1477 case TemporalUnit::Millisecond
: {
1478 static_assert((NormalizedTimeDuration::max().seconds
+ 1) *
1479 ToMilliseconds(TemporalUnit::Second
) <=
1481 "total number duration milliseconds fits into int64");
1483 int64_t millis
= seconds
* ToMilliseconds(TemporalUnit::Second
);
1485 // Set to zero per step 1.
1489 microseconds
= nanoseconds
/ 1000;
1492 nanoseconds
= nanoseconds
% 1000;
1495 milliseconds
= microseconds
/ 1000;
1498 microseconds
= microseconds
% 1000;
1500 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1501 milliseconds
+= millis
;
1507 case TemporalUnit::Microsecond
: {
1509 int64_t microseconds
= nanoseconds
/ 1000;
1512 nanoseconds
= nanoseconds
% 1000;
1514 MOZ_ASSERT(std::abs(microseconds
) <= 999'999);
1516 std::fma(double(seconds
), ToMicroseconds(TemporalUnit::Second
),
1517 double(microseconds
));
1520 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros
,
1521 double(nanoseconds
));
1525 case TemporalUnit::Nanosecond
: {
1526 MOZ_ASSERT(std::abs(nanoseconds
) <= 999'999'999);
1528 std::fma(double(seconds
), ToNanoseconds(TemporalUnit::Second
),
1529 double(nanoseconds
));
1532 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos
);
1535 case TemporalUnit::Auto
:
1536 MOZ_CRASH("Unexpected temporal unit");
1540 return CreateTimeDurationRecord(days
, hours
, minutes
, seconds
, milliseconds
,
1541 double(microseconds
), double(nanoseconds
));
1545 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1546 * timeZoneRec, precalculatedPlainDateTime )
1548 static bool BalanceTimeDurationRelative(
1549 JSContext
* cx
, const NormalizedDuration
& duration
, TemporalUnit largestUnit
,
1550 Handle
<ZonedDateTime
> relativeTo
, Handle
<TimeZoneRecord
> timeZone
,
1551 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
1552 TimeDuration
* result
) {
1553 MOZ_ASSERT(IsValidDuration(duration
));
1556 const auto& startNs
= relativeTo
.instant();
1559 const auto& startInstant
= startNs
;
1562 auto intermediateNs
= startNs
;
1565 PlainDateTime startDateTime
;
1566 if (duration
.date
.days
!= 0) {
1568 if (!precalculatedPlainDateTime
) {
1569 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1572 precalculatedPlainDateTime
=
1573 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1577 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
1578 if (!AddDaysToZonedDateTime(cx
, startInstant
, *precalculatedPlainDateTime
,
1579 timeZone
, isoCalendar
, duration
.date
.days
,
1587 if (!AddInstant(cx
, intermediateNs
, duration
.time
, &endNs
)) {
1590 MOZ_ASSERT(IsValidEpochInstant(endNs
));
1594 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startInstant
);
1597 if (normalized
== NormalizedTimeDuration
{}) {
1604 if (TemporalUnit::Year
<= largestUnit
&& largestUnit
<= TemporalUnit::Day
) {
1606 if (!precalculatedPlainDateTime
) {
1607 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &startDateTime
)) {
1610 precalculatedPlainDateTime
=
1611 mozilla::SomeRef
<const PlainDateTime
>(startDateTime
);
1615 NormalizedTimeAndDays timeAndDays
;
1616 if (!NormalizedTimeDurationToDays(cx
, normalized
, relativeTo
, timeZone
,
1617 *precalculatedPlainDateTime
,
1623 days
= timeAndDays
.days
;
1626 normalized
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
1627 MOZ_ASSERT_IF(days
> 0, normalized
>= NormalizedTimeDuration
{});
1628 MOZ_ASSERT_IF(days
< 0, normalized
<= NormalizedTimeDuration
{});
1631 largestUnit
= TemporalUnit::Hour
;
1635 auto balanceResult
= BalanceTimeDuration(normalized
, largestUnit
);
1640 balanceResult
.hours
,
1641 balanceResult
.minutes
,
1642 balanceResult
.seconds
,
1643 balanceResult
.milliseconds
,
1644 balanceResult
.microseconds
,
1645 balanceResult
.nanoseconds
,
1647 MOZ_ASSERT(IsValidDuration(result
->toDuration()));
1652 * CreateDateDurationRecord ( years, months, weeks, days )
1654 static DateDuration
CreateDateDurationRecord(int64_t years
, int64_t months
,
1655 int64_t weeks
, int64_t days
) {
1656 MOZ_ASSERT(IsValidDuration(Duration
{
1662 return {years
, months
, weeks
, days
};
1666 * CreateDateDurationRecord ( years, months, weeks, days )
1668 static bool CreateDateDurationRecord(JSContext
* cx
, int64_t years
,
1669 int64_t months
, int64_t weeks
,
1670 int64_t days
, DateDuration
* result
) {
1671 auto duration
= DateDuration
{years
, months
, weeks
, days
};
1672 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1680 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration
& duration
,
1681 TemporalUnit largestUnit
) {
1682 MOZ_ASSERT(largestUnit
!= TemporalUnit::Auto
);
1684 // Steps 2, 3.a-b, 4.a-b, 6-7.
1685 return (largestUnit
> TemporalUnit::Year
&& duration
.years
!= 0) ||
1686 (largestUnit
> TemporalUnit::Month
&& duration
.months
!= 0) ||
1687 (largestUnit
> TemporalUnit::Week
&& duration
.weeks
!= 0);
1691 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1692 * plainRelativeTo, calendarRec )
1694 static bool UnbalanceDateDurationRelative(
1695 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1696 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1697 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1698 MOZ_ASSERT(IsValidDuration(duration
));
1700 auto [years
, months
, weeks
, days
] = duration
;
1702 // Step 1. (Not applicable in our implementation.)
1704 // Steps 2, 3.a, 4.a, and 6.
1705 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1706 // Steps 2.a, 3.a, 4.a, and 6.
1712 if (largestUnit
== TemporalUnit::Month
) {
1713 // Step 3.a. (Handled above)
1714 MOZ_ASSERT(years
!= 0);
1716 // Step 3.b. (Not applicable in our implementation.)
1720 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1724 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1727 auto yearsDuration
= DateDuration
{years
};
1730 Rooted
<Wrapped
<PlainDateObject
*>> later(
1731 cx
, CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsDuration
));
1737 Duration untilResult
;
1738 if (!CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
,
1739 TemporalUnit::Month
, &untilResult
)) {
1744 int64_t yearsInMonths
= int64_t(untilResult
.months
);
1747 return CreateDateDurationRecord(cx
, 0, months
+ yearsInMonths
, weeks
, days
,
1752 if (largestUnit
== TemporalUnit::Week
) {
1753 // Step 4.a. (Handled above)
1754 MOZ_ASSERT(years
!= 0 || months
!= 0);
1756 // Step 4.b. (Not applicable in our implementation.)
1760 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1763 auto yearsMonthsDuration
= DateDuration
{years
, months
};
1767 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsDuration
);
1771 auto laterDate
= ToPlainDate(&later
.unwrap());
1773 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1774 if (!unwrappedRelativeTo
) {
1777 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1780 int32_t yearsMonthsInDays
= DaysUntil(relativeToDate
, laterDate
);
1783 return CreateDateDurationRecord(cx
, 0, 0, weeks
, days
+ yearsMonthsInDays
,
1787 // Step 5. (Not applicable in our implementation.)
1789 // Step 6. (Handled above)
1790 MOZ_ASSERT(years
!= 0 || months
!= 0 || weeks
!= 0);
1792 // FIXME: why don't we unconditionally throw an error for missing calendars?
1794 // Step 7. (Not applicable in our implementation.)
1798 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1801 auto yearsMonthsWeeksDuration
= DateDuration
{years
, months
, weeks
};
1805 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsWeeksDuration
);
1809 auto laterDate
= ToPlainDate(&later
.unwrap());
1811 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1812 if (!unwrappedRelativeTo
) {
1815 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1818 int32_t yearsMonthsWeeksInDay
= DaysUntil(relativeToDate
, laterDate
);
1821 return CreateDateDurationRecord(cx
, 0, 0, 0, days
+ yearsMonthsWeeksInDay
,
1826 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1827 * plainRelativeTo, calendarRec )
1829 static bool UnbalanceDateDurationRelative(JSContext
* cx
,
1830 const DateDuration
& duration
,
1831 TemporalUnit largestUnit
,
1832 DateDuration
* result
) {
1833 MOZ_ASSERT(IsValidDuration(duration
));
1835 // Step 1. (Not applicable.)
1837 // Steps 2, 3.a, 4.a, and 6.
1838 if (!UnbalanceDateDurationRelativeHasEffect(duration
, largestUnit
)) {
1839 // Steps 2.a, 3.a, 4.a, and 6.
1844 // Step 5. (Not applicable.)
1846 // Steps 3.b, 4.b, and 7.
1847 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1848 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
, "calendar");
1853 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1854 * smallestUnit, plainRelativeTo, calendarRec )
1856 static bool BalanceDateDurationRelative(
1857 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
1858 TemporalUnit smallestUnit
,
1859 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1860 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
1861 MOZ_ASSERT(IsValidDuration(duration
));
1862 MOZ_ASSERT(largestUnit
<= smallestUnit
);
1864 auto [years
, months
, weeks
, days
] = duration
;
1866 // FIXME: spec issue - effectful code paths should be more fine-grained
1867 // similar to UnbalanceDateDurationRelative. For example:
1868 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1869 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1870 // 3. Else if days = 0, then no-op.
1872 // Also note that |weeks| is never balanced, even when non-zero.
1874 // Step 1. (Not applicable in our implementation.)
1877 if (largestUnit
> TemporalUnit::Week
||
1878 (years
== 0 && months
== 0 && weeks
== 0 && days
== 0)) {
1885 if (!plainRelativeTo
) {
1886 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1887 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
1894 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1898 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
1900 // Steps 8-9. (Not applicable in our implementation.)
1902 auto untilAddedDate
= [&](const DateDuration
& duration
,
1903 Duration
* untilResult
) {
1904 Rooted
<Wrapped
<PlainDateObject
*>> later(
1905 cx
, AddDate(cx
, calendar
, plainRelativeTo
, duration
));
1910 return CalendarDateUntil(cx
, calendar
, plainRelativeTo
, later
, largestUnit
,
1915 if (largestUnit
== TemporalUnit::Year
) {
1917 if (smallestUnit
== TemporalUnit::Week
) {
1919 MOZ_ASSERT(days
== 0);
1922 auto yearsMonthsDuration
= DateDuration
{years
, months
};
1924 // Steps 10.a.iii-iv.
1925 Duration untilResult
;
1926 if (!untilAddedDate(yearsMonthsDuration
, &untilResult
)) {
1931 *result
= CreateDateDurationRecord(int64_t(untilResult
.years
),
1932 int64_t(untilResult
.months
), weeks
, 0);
1937 const auto& yearsMonthsWeeksDaysDuration
= duration
;
1940 Duration untilResult
;
1941 if (!untilAddedDate(yearsMonthsWeeksDaysDuration
, &untilResult
)) {
1945 // FIXME: spec bug - CreateDateDurationRecord is infallible
1946 // https://github.com/tc39/proposal-temporal/issues/2750
1949 *result
= CreateDateDurationRecord(
1950 int64_t(untilResult
.years
), int64_t(untilResult
.months
),
1951 int64_t(untilResult
.weeks
), int64_t(untilResult
.days
));
1956 if (largestUnit
== TemporalUnit::Month
) {
1958 MOZ_ASSERT(years
== 0);
1961 if (smallestUnit
== TemporalUnit::Week
) {
1963 MOZ_ASSERT(days
== 0);
1966 *result
= CreateDateDurationRecord(0, months
, weeks
, 0);
1971 const auto& monthsWeeksDaysDuration
= duration
;
1974 Duration untilResult
;
1975 if (!untilAddedDate(monthsWeeksDaysDuration
, &untilResult
)) {
1979 // FIXME: spec bug - CreateDateDurationRecord is infallible
1980 // https://github.com/tc39/proposal-temporal/issues/2750
1983 *result
= CreateDateDurationRecord(0, int64_t(untilResult
.months
),
1984 int64_t(untilResult
.weeks
),
1985 int64_t(untilResult
.days
));
1990 MOZ_ASSERT(largestUnit
== TemporalUnit::Week
);
1993 MOZ_ASSERT(years
== 0);
1996 MOZ_ASSERT(months
== 0);
1999 const auto& weeksDaysDuration
= duration
;
2002 Duration untilResult
;
2003 if (!untilAddedDate(weeksDaysDuration
, &untilResult
)) {
2007 // FIXME: spec bug - CreateDateDurationRecord is infallible
2008 // https://github.com/tc39/proposal-temporal/issues/2750
2011 *result
= CreateDateDurationRecord(0, 0, int64_t(untilResult
.weeks
),
2012 int64_t(untilResult
.days
));
2017 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2018 * smallestUnit, plainRelativeTo, calendarRec )
2020 bool js::temporal::BalanceDateDurationRelative(
2021 JSContext
* cx
, const DateDuration
& duration
, TemporalUnit largestUnit
,
2022 TemporalUnit smallestUnit
,
2023 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2024 Handle
<CalendarRecord
> calendar
, DateDuration
* result
) {
2025 MOZ_ASSERT(plainRelativeTo
);
2026 MOZ_ASSERT(calendar
.receiver());
2028 return ::BalanceDateDurationRelative(cx
, duration
, largestUnit
, smallestUnit
,
2029 plainRelativeTo
, calendar
, result
);
2033 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2034 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2035 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2037 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2039 MOZ_ASSERT(IsValidDuration(one
));
2040 MOZ_ASSERT(IsValidDuration(two
));
2042 // Steps 1-2. (Not applicable)
2045 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2048 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2051 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2054 if (largestUnit
<= TemporalUnit::Week
) {
2055 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2056 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
2062 auto normalized1
= NormalizeTimeDuration(one
);
2065 auto normalized2
= NormalizeTimeDuration(two
);
2068 NormalizedTimeDuration normalized
;
2069 if (!AddNormalizedTimeDuration(cx
, normalized1
, normalized2
, &normalized
)) {
2074 int64_t days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
2075 int64_t days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
2076 auto totalDays
= mozilla::CheckedInt64(days1
) + days2
;
2077 MOZ_ASSERT(totalDays
.isValid(), "adding two duration days can't overflow");
2079 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized
, totalDays
.value(),
2085 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2088 *result
= balanced
.toDuration();
2093 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2094 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2095 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2097 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2098 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2099 Handle
<CalendarRecord
> calendar
, Duration
* result
) {
2100 MOZ_ASSERT(IsValidDuration(one
));
2101 MOZ_ASSERT(IsValidDuration(two
));
2103 // Steps 1-2. (Not applicable)
2105 // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
2109 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2112 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2115 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2117 // Step 6. (Not applicable)
2119 // Step 7.a. (Not applicable in our implementation.)
2122 auto dateDuration1
= one
.toDateDuration();
2125 auto dateDuration2
= two
.toDateDuration();
2127 // FIXME: spec issue - calendarUnitsPresent is unused.
2130 [[maybe_unused
]] bool calendarUnitsPresent
= true;
2133 if (dateDuration1
.years
== 0 && dateDuration1
.months
== 0 &&
2134 dateDuration1
.weeks
== 0 && dateDuration2
.years
== 0 &&
2135 dateDuration2
.months
== 0 && dateDuration2
.weeks
== 0) {
2136 calendarUnitsPresent
= false;
2140 Rooted
<Wrapped
<PlainDateObject
*>> intermediate(
2141 cx
, AddDate(cx
, calendar
, plainRelativeTo
, dateDuration1
));
2142 if (!intermediate
) {
2147 Rooted
<Wrapped
<PlainDateObject
*>> end(
2148 cx
, AddDate(cx
, calendar
, intermediate
, dateDuration2
));
2154 auto dateLargestUnit
= std::min(TemporalUnit::Day
, largestUnit
);
2157 DateDuration dateDifference
;
2158 if (!DifferenceDate(cx
, calendar
, plainRelativeTo
, end
, dateLargestUnit
,
2164 auto normalized1
= NormalizeTimeDuration(one
);
2167 auto normalized2
= NormalizeTimeDuration(two
);
2170 NormalizedTimeDuration normalized1WithDays
;
2171 if (!Add24HourDaysToNormalizedTimeDuration(
2172 cx
, normalized1
, dateDifference
.days
, &normalized1WithDays
)) {
2177 NormalizedTimeDuration normalized
;
2178 if (!AddNormalizedTimeDuration(cx
, normalized1WithDays
, normalized2
,
2184 auto balanced
= temporal::BalanceTimeDuration(normalized
, largestUnit
);
2188 double(dateDifference
.years
), double(dateDifference
.months
),
2189 double(dateDifference
.weeks
), double(balanced
.days
),
2190 double(balanced
.hours
), double(balanced
.minutes
),
2191 double(balanced
.seconds
), double(balanced
.milliseconds
),
2192 balanced
.microseconds
, balanced
.nanoseconds
,
2194 MOZ_ASSERT(IsValidDuration(*result
));
2199 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2200 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2201 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2203 static bool AddDuration(
2204 JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2205 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2206 Handle
<TimeZoneRecord
> timeZone
,
2207 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2209 // Steps 1-2. (Not applicable)
2212 auto largestUnit1
= DefaultTemporalLargestUnit(one
);
2215 auto largestUnit2
= DefaultTemporalLargestUnit(two
);
2218 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
2220 // Steps 6-7. (Not applicable)
2222 // Steps 8-9. (Not applicable in our implementation.)
2224 // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
2228 // 10. If largestUnit is one of "year", "month", "week", or "day", then
2229 // a. If precalculatedPlainDateTime is undefined, then
2230 // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
2232 // i. Let startDateTime be precalculatedPlainDateTime.
2233 // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
2234 // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
2235 // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
2236 // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
2237 // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
2238 // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
2239 // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
2244 bool startDateTimeNeeded
= largestUnit
<= TemporalUnit::Day
;
2247 if (!startDateTimeNeeded
) {
2248 // Steps 11-12. (Not applicable)
2251 auto normalized1
= NormalizeTimeDuration(one
);
2254 auto normalized2
= NormalizeTimeDuration(two
);
2256 // Step 15. (Inlined AddZonedDateTime, step 6.)
2257 Instant intermediateNs
;
2258 if (!AddInstant(cx
, zonedRelativeTo
.instant(), normalized1
,
2262 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2264 // Step 16. (Inlined AddZonedDateTime, step 6.)
2266 if (!AddInstant(cx
, intermediateNs
, normalized2
, &endNs
)) {
2269 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2272 auto normalized
= NormalizedTimeDurationFromEpochNanosecondsDifference(
2273 endNs
, zonedRelativeTo
.instant());
2276 auto balanced
= BalanceTimeDuration(normalized
, largestUnit
);
2279 *result
= balanced
.toDuration();
2284 PlainDateTime startDateTime
;
2285 if (!precalculatedPlainDateTime
) {
2286 if (!GetPlainDateTimeFor(cx
, timeZone
, zonedRelativeTo
.instant(),
2291 startDateTime
= *precalculatedPlainDateTime
;
2295 auto normalized1
= CreateNormalizedDurationRecord(one
);
2298 auto normalized2
= CreateNormalizedDurationRecord(two
);
2301 Instant intermediateNs
;
2302 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2303 normalized1
, startDateTime
, &intermediateNs
)) {
2306 MOZ_ASSERT(IsValidEpochInstant(intermediateNs
));
2310 if (!AddZonedDateTime(cx
, intermediateNs
, timeZone
, calendar
, normalized2
,
2314 MOZ_ASSERT(IsValidEpochInstant(endNs
));
2316 // Step 17. (Not applicable)
2319 NormalizedDuration difference
;
2320 if (!DifferenceZonedDateTime(cx
, zonedRelativeTo
.instant(), endNs
, timeZone
,
2321 calendar
, largestUnit
, startDateTime
,
2327 auto balanced
= BalanceTimeDuration(difference
.time
, TemporalUnit::Hour
);
2331 double(difference
.date
.years
), double(difference
.date
.months
),
2332 double(difference
.date
.weeks
), double(difference
.date
.days
),
2333 double(balanced
.hours
), double(balanced
.minutes
),
2334 double(balanced
.seconds
), double(balanced
.milliseconds
),
2335 balanced
.microseconds
, balanced
.nanoseconds
,
2337 MOZ_ASSERT(IsValidDuration(*result
));
2342 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2343 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2344 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2346 static bool AddDuration(JSContext
* cx
, const Duration
& one
, const Duration
& two
,
2347 Handle
<ZonedDateTime
> zonedRelativeTo
,
2348 Handle
<CalendarRecord
> calendar
,
2349 Handle
<TimeZoneRecord
> timeZone
, Duration
* result
) {
2350 return AddDuration(cx
, one
, two
, zonedRelativeTo
, calendar
, timeZone
,
2351 mozilla::Nothing(), result
);
2355 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2356 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2357 * precalculatedPlainDateTime )
2359 static bool AdjustRoundedDurationDays(
2360 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2361 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2362 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2363 Handle
<TimeZoneRecord
> timeZone
,
2364 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
2365 NormalizedDuration
* result
) {
2366 MOZ_ASSERT(IsValidDuration(duration
));
2369 if ((TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
) ||
2370 (unit
== TemporalUnit::Nanosecond
&& increment
== Increment
{1})) {
2375 // The increment is limited for all smaller temporal units.
2376 MOZ_ASSERT(increment
< MaximumTemporalDurationRoundingIncrement(unit
));
2379 MOZ_ASSERT(precalculatedPlainDateTime
);
2382 int32_t direction
= NormalizedTimeDurationSign(duration
.time
);
2386 if (!AddZonedDateTime(cx
, zonedRelativeTo
.instant(), timeZone
, calendar
,
2387 duration
.date
, *precalculatedPlainDateTime
,
2391 MOZ_ASSERT(IsValidEpochInstant(dayStart
));
2394 PlainDateTime dayStartDateTime
;
2395 if (!GetPlainDateTimeFor(cx
, timeZone
, dayStart
, &dayStartDateTime
)) {
2401 if (!AddDaysToZonedDateTime(cx
, dayStart
, dayStartDateTime
, timeZone
,
2402 zonedRelativeTo
.calendar(), direction
, &dayEnd
)) {
2405 MOZ_ASSERT(IsValidEpochInstant(dayEnd
));
2409 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd
, dayStart
);
2410 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs
.to
<InstantSpan
>()));
2413 NormalizedTimeDuration oneDayLess
;
2414 if (!SubtractNormalizedTimeDuration(cx
, duration
.time
, dayLengthNs
,
2420 int32_t oneDayLessSign
= NormalizedTimeDurationSign(oneDayLess
);
2421 if ((direction
> 0 && oneDayLessSign
< 0) ||
2422 (direction
< 0 && oneDayLessSign
> 0)) {
2428 Duration adjustedDateDuration
;
2429 if (!AddDuration(cx
, duration
.date
.toDuration(), {0, 0, 0, double(direction
)},
2430 zonedRelativeTo
, calendar
, timeZone
,
2431 precalculatedPlainDateTime
, &adjustedDateDuration
)) {
2436 auto roundedTime
= RoundDuration(oneDayLess
, increment
, unit
, roundingMode
);
2439 return CombineDateAndNormalizedTimeDuration(
2440 cx
, adjustedDateDuration
.toDateDuration(), roundedTime
, result
);
2444 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2445 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2446 * precalculatedPlainDateTime )
2448 bool js::temporal::AdjustRoundedDurationDays(
2449 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
2450 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2451 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<CalendarRecord
> calendar
,
2452 Handle
<TimeZoneRecord
> timeZone
,
2453 const PlainDateTime
& precalculatedPlainDateTime
,
2454 NormalizedDuration
* result
) {
2455 return ::AdjustRoundedDurationDays(
2456 cx
, duration
, increment
, unit
, roundingMode
, zonedRelativeTo
, calendar
,
2457 timeZone
, mozilla::SomeRef(precalculatedPlainDateTime
), result
);
2460 static bool NumberToStringBuilder(JSContext
* cx
, double num
,
2461 JSStringBuilder
& sb
) {
2462 MOZ_ASSERT(IsInteger(num
));
2463 MOZ_ASSERT(num
>= 0);
2464 MOZ_ASSERT(num
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2468 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
2470 return sb
.append(numStr
, length
);
2473 static Duration
AbsoluteDuration(const Duration
& duration
) {
2475 std::abs(duration
.years
), std::abs(duration
.months
),
2476 std::abs(duration
.weeks
), std::abs(duration
.days
),
2477 std::abs(duration
.hours
), std::abs(duration
.minutes
),
2478 std::abs(duration
.seconds
), std::abs(duration
.milliseconds
),
2479 std::abs(duration
.microseconds
), std::abs(duration
.nanoseconds
),
2484 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2486 [[nodiscard
]] static bool FormatFractionalSeconds(JSStringBuilder
& result
,
2487 int32_t subSecondNanoseconds
,
2488 Precision precision
) {
2489 MOZ_ASSERT(0 <= subSecondNanoseconds
&& subSecondNanoseconds
< 1'000'000'000);
2490 MOZ_ASSERT(precision
!= Precision::Minute());
2493 if (precision
== Precision::Auto()) {
2495 if (subSecondNanoseconds
== 0) {
2499 // Step 3. (Reordered)
2500 if (!result
.append('.')) {
2505 int32_t k
= 100'000'000;
2507 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2510 subSecondNanoseconds
%= k
;
2512 } while (subSecondNanoseconds
);
2515 uint8_t p
= precision
.value();
2520 // Step 3. (Reordered)
2521 if (!result
.append('.')) {
2526 int32_t k
= 100'000'000;
2527 for (uint8_t i
= 0; i
< precision
.value(); i
++) {
2528 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
2531 subSecondNanoseconds
%= k
;
2540 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2541 * normSeconds, precision )
2543 static JSString
* TemporalDurationToString(JSContext
* cx
,
2544 const Duration
& duration
,
2545 Precision precision
) {
2546 MOZ_ASSERT(IsValidDuration(duration
));
2547 MOZ_ASSERT(precision
!= Precision::Minute());
2549 // Fast path for zero durations.
2550 if (duration
== Duration
{} &&
2551 (precision
== Precision::Auto() || precision
.value() == 0)) {
2552 return NewStringCopyZ
<CanGC
>(cx
, "PT0S");
2555 // Convert to absolute values up front. This is okay to do, because when the
2556 // duration is valid, all components have the same sign.
2557 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
2558 milliseconds
, microseconds
, nanoseconds
] =
2559 AbsoluteDuration(duration
);
2561 // Years to seconds parts are all safe integers for valid durations.
2562 MOZ_ASSERT(years
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2563 MOZ_ASSERT(months
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2564 MOZ_ASSERT(weeks
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2565 MOZ_ASSERT(days
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2566 MOZ_ASSERT(hours
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2567 MOZ_ASSERT(minutes
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2568 MOZ_ASSERT(seconds
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
2570 auto secondsDuration
= NormalizeTimeDuration(0.0, 0.0, seconds
, milliseconds
,
2571 microseconds
, nanoseconds
);
2574 int32_t sign
= DurationSign(duration
);
2577 JSStringBuilder
result(cx
);
2579 // Step 13. (Reordered)
2581 if (!result
.append('-')) {
2586 // Step 14. (Reordered)
2587 if (!result
.append('P')) {
2593 if (!NumberToStringBuilder(cx
, years
, result
)) {
2596 if (!result
.append('Y')) {
2603 if (!NumberToStringBuilder(cx
, months
, result
)) {
2606 if (!result
.append('M')) {
2613 if (!NumberToStringBuilder(cx
, weeks
, result
)) {
2616 if (!result
.append('W')) {
2623 if (!NumberToStringBuilder(cx
, days
, result
)) {
2626 if (!result
.append('D')) {
2631 // Step 7. (Moved above)
2633 // Steps 10-11. (Reordered)
2634 bool zeroMinutesAndHigher
= years
== 0 && months
== 0 && weeks
== 0 &&
2635 days
== 0 && hours
== 0 && minutes
== 0;
2637 // Steps 8-9, 12, and 15.
2638 bool hasSecondsPart
= (secondsDuration
!= NormalizedTimeDuration
{}) ||
2639 zeroMinutesAndHigher
|| precision
!= Precision::Auto();
2640 if (hours
!= 0 || minutes
!= 0 || hasSecondsPart
) {
2641 // Step 15. (Reordered)
2642 if (!result
.append('T')) {
2648 if (!NumberToStringBuilder(cx
, hours
, result
)) {
2651 if (!result
.append('H')) {
2658 if (!NumberToStringBuilder(cx
, minutes
, result
)) {
2661 if (!result
.append('M')) {
2667 if (hasSecondsPart
) {
2669 if (!NumberToStringBuilder(cx
, double(secondsDuration
.seconds
), result
)) {
2674 if (!FormatFractionalSeconds(result
, secondsDuration
.nanoseconds
,
2680 if (!result
.append('S')) {
2686 // Steps 13-15. (Moved above)
2689 return result
.finishString();
2693 * ToRelativeTemporalObject ( options )
2695 static bool ToRelativeTemporalObject(
2696 JSContext
* cx
, Handle
<JSObject
*> options
,
2697 MutableHandle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2698 MutableHandle
<ZonedDateTime
> zonedRelativeTo
,
2699 MutableHandle
<TimeZoneRecord
> timeZoneRecord
) {
2701 Rooted
<Value
> value(cx
);
2702 if (!GetProperty(cx
, options
, options
, cx
->names().relativeTo
, &value
)) {
2707 if (value
.isUndefined()) {
2708 // FIXME: spec issue - switch return record fields for consistency.
2709 // FIXME: spec bug - [[TimeZoneRec]] field not created
2711 plainRelativeTo
.set(nullptr);
2712 zonedRelativeTo
.set(ZonedDateTime
{});
2713 timeZoneRecord
.set(TimeZoneRecord
{});
2718 auto offsetBehaviour
= OffsetBehaviour::Option
;
2721 auto matchBehaviour
= MatchBehaviour::MatchExactly
;
2724 PlainDateTime dateTime
;
2725 Rooted
<CalendarValue
> calendar(cx
);
2726 Rooted
<TimeZoneValue
> timeZone(cx
);
2728 if (value
.isObject()) {
2729 Rooted
<JSObject
*> obj(cx
, &value
.toObject());
2732 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
2733 auto instant
= ToInstant(zonedDateTime
);
2734 Rooted
<TimeZoneValue
> timeZone(cx
, zonedDateTime
->timeZone());
2735 Rooted
<CalendarValue
> calendar(cx
, zonedDateTime
->calendar());
2737 if (!timeZone
.wrap(cx
)) {
2740 if (!calendar
.wrap(cx
)) {
2745 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2746 if (!CreateTimeZoneMethodsRecord(
2749 TimeZoneMethod::GetOffsetNanosecondsFor
,
2750 TimeZoneMethod::GetPossibleInstantsFor
,
2757 plainRelativeTo
.set(nullptr);
2758 zonedRelativeTo
.set(ZonedDateTime
{instant
, timeZone
, calendar
});
2759 timeZoneRecord
.set(timeZoneRec
);
2764 if (obj
->canUnwrapAs
<PlainDateObject
>()) {
2765 plainRelativeTo
.set(obj
);
2766 zonedRelativeTo
.set(ZonedDateTime
{});
2767 timeZoneRecord
.set(TimeZoneRecord
{});
2772 if (auto* dateTime
= obj
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
2773 auto plainDateTime
= ToPlainDate(dateTime
);
2775 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
2776 if (!calendar
.wrap(cx
)) {
2781 auto* plainDate
= CreateTemporalDate(cx
, plainDateTime
, calendar
);
2787 plainRelativeTo
.set(plainDate
);
2788 zonedRelativeTo
.set(ZonedDateTime
{});
2789 timeZoneRecord
.set(TimeZoneRecord
{});
2794 if (!GetTemporalCalendarWithISODefault(cx
, obj
, &calendar
)) {
2799 Rooted
<CalendarRecord
> calendarRec(cx
);
2800 if (!CreateCalendarMethodsRecord(cx
, calendar
,
2802 CalendarMethod::DateFromFields
,
2803 CalendarMethod::Fields
,
2810 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2811 if (!CalendarFields(cx
, calendarRec
,
2812 {CalendarField::Day
, CalendarField::Month
,
2813 CalendarField::MonthCode
, CalendarField::Year
},
2819 if (!AppendSorted(cx
, fieldNames
.get(),
2821 TemporalField::Hour
,
2822 TemporalField::Microsecond
,
2823 TemporalField::Millisecond
,
2824 TemporalField::Minute
,
2825 TemporalField::Nanosecond
,
2826 TemporalField::Offset
,
2827 TemporalField::Second
,
2828 TemporalField::TimeZone
,
2834 Rooted
<PlainObject
*> fields(cx
, PrepareTemporalFields(cx
, obj
, fieldNames
));
2840 Rooted
<PlainObject
*> dateOptions(cx
, NewPlainObjectWithProto(cx
, nullptr));
2846 Rooted
<Value
> overflow(cx
, StringValue(cx
->names().constrain
));
2847 if (!DefineDataProperty(cx
, dateOptions
, cx
->names().overflow
, overflow
)) {
2852 if (!InterpretTemporalDateTimeFields(cx
, calendarRec
, fields
, dateOptions
,
2858 Rooted
<Value
> offset(cx
);
2859 if (!GetProperty(cx
, fields
, fields
, cx
->names().offset
, &offset
)) {
2864 Rooted
<Value
> timeZoneValue(cx
);
2865 if (!GetProperty(cx
, fields
, fields
, cx
->names().timeZone
,
2871 if (!timeZoneValue
.isUndefined()) {
2872 if (!ToTemporalTimeZone(cx
, timeZoneValue
, &timeZone
)) {
2878 if (offset
.isUndefined()) {
2879 offsetBehaviour
= OffsetBehaviour::Wall
;
2884 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2885 MOZ_ASSERT(!offset
.isUndefined());
2886 MOZ_ASSERT(offset
.isString());
2889 Rooted
<JSString
*> offsetString(cx
, offset
.toString());
2890 if (!offsetString
) {
2895 if (!ParseDateTimeUTCOffset(cx
, offsetString
, &offsetNs
)) {
2905 if (!value
.isString()) {
2906 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
2907 nullptr, "not a string");
2910 Rooted
<JSString
*> string(cx
, value
.toString());
2915 int64_t timeZoneOffset
;
2916 Rooted
<ParsedTimeZone
> timeZoneName(cx
);
2917 Rooted
<JSString
*> calendarString(cx
);
2918 if (!ParseTemporalRelativeToString(cx
, string
, &dateTime
, &isUTC
,
2919 &hasOffset
, &timeZoneOffset
,
2920 &timeZoneName
, &calendarString
)) {
2924 // Step 6.c. (Not applicable in our implementation.)
2929 if (!ToTemporalTimeZone(cx
, timeZoneName
, &timeZone
)) {
2933 // Steps 6.f.ii-iii.
2935 offsetBehaviour
= OffsetBehaviour::Exact
;
2936 } else if (!hasOffset
) {
2937 offsetBehaviour
= OffsetBehaviour::Wall
;
2941 matchBehaviour
= MatchBehaviour::MatchMinutes
;
2943 MOZ_ASSERT(!timeZone
);
2947 if (calendarString
) {
2948 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
2952 calendar
.set(CalendarValue(cx
->names().iso8601
));
2957 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2958 MOZ_ASSERT(hasOffset
);
2961 offsetNs
= timeZoneOffset
;
2972 auto* plainDate
= CreateTemporalDate(cx
, dateTime
.date
, calendar
);
2977 plainRelativeTo
.set(plainDate
);
2978 zonedRelativeTo
.set(ZonedDateTime
{});
2979 timeZoneRecord
.set(TimeZoneRecord
{});
2983 // Steps 8-9. (Moved above)
2986 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2987 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2989 TimeZoneMethod::GetOffsetNanosecondsFor
,
2990 TimeZoneMethod::GetPossibleInstantsFor
,
2997 Instant epochNanoseconds
;
2998 if (!InterpretISODateTimeOffset(
2999 cx
, dateTime
, offsetBehaviour
, offsetNs
, timeZoneRec
,
3000 TemporalDisambiguation::Compatible
, TemporalOffset::Reject
,
3001 matchBehaviour
, &epochNanoseconds
)) {
3004 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds
));
3007 plainRelativeTo
.set(nullptr);
3008 zonedRelativeTo
.set(ZonedDateTime
{epochNanoseconds
, timeZone
, calendar
});
3009 timeZoneRecord
.set(timeZoneRec
);
3014 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
3017 static bool CreateCalendarMethodsRecordFromRelativeTo(
3018 JSContext
* cx
, Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3019 Handle
<ZonedDateTime
> zonedRelativeTo
,
3020 mozilla::EnumSet
<CalendarMethod
> methods
,
3021 MutableHandle
<CalendarRecord
> result
) {
3023 if (zonedRelativeTo
) {
3024 return CreateCalendarMethodsRecord(cx
, zonedRelativeTo
.calendar(), methods
,
3029 if (plainRelativeTo
) {
3030 auto* unwrapped
= plainRelativeTo
.unwrap(cx
);
3035 Rooted
<CalendarValue
> calendar(cx
, unwrapped
->calendar());
3036 if (!calendar
.wrap(cx
)) {
3040 return CreateCalendarMethodsRecord(cx
, calendar
, methods
, result
);
3047 struct RoundedDuration final
{
3048 NormalizedDuration duration
;
3052 enum class ComputeRemainder
: bool { No
, Yes
};
3055 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3057 static NormalizedTimeDuration
RoundNormalizedTimeDurationToIncrement(
3058 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
,
3059 Increment increment
, TemporalRoundingMode roundingMode
) {
3060 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3061 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3062 MOZ_ASSERT(increment
<= MaximumTemporalDurationRoundingIncrement(unit
));
3064 int64_t divisor
= ToNanoseconds(unit
) * increment
.value();
3065 MOZ_ASSERT(divisor
> 0);
3066 MOZ_ASSERT(divisor
<= ToNanoseconds(TemporalUnit::Day
));
3068 auto totalNanoseconds
= duration
.toNanoseconds();
3070 RoundNumberToIncrement(totalNanoseconds
, Int128
{divisor
}, roundingMode
);
3071 return NormalizedTimeDuration::fromNanoseconds(rounded
);
3075 * DivideNormalizedTimeDuration ( d, divisor )
3077 static double TotalNormalizedTimeDuration(
3078 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
) {
3079 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3080 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3082 auto numerator
= duration
.toNanoseconds();
3083 auto denominator
= Int128
{ToNanoseconds(unit
)};
3084 return FractionToDouble(numerator
, denominator
);
3088 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3089 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3090 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3092 NormalizedTimeDuration
js::temporal::RoundDuration(
3093 const NormalizedTimeDuration
& duration
, Increment increment
,
3094 TemporalUnit unit
, TemporalRoundingMode roundingMode
) {
3095 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
3096 MOZ_ASSERT(unit
> TemporalUnit::Day
);
3098 // Steps 1-13. (Not applicable)
3101 auto rounded
= RoundNormalizedTimeDurationToIncrement(
3102 duration
, unit
, increment
, roundingMode
);
3103 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded
));
3109 struct FractionalDays final
{
3112 int64_t dayLength
= 0;
3114 FractionalDays() = default;
3116 explicit FractionalDays(int64_t durationDays
,
3117 const NormalizedTimeAndDays
& timeAndDays
)
3118 : days(durationDays
+ timeAndDays
.days
),
3119 time(timeAndDays
.time
),
3120 dayLength(timeAndDays
.dayLength
) {
3121 MOZ_ASSERT(durationDays
<= (int64_t(1) << 53) / (24 * 60 * 60));
3122 MOZ_ASSERT(timeAndDays
.days
<= (int64_t(1) << 53) / (24 * 60 * 60));
3124 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3125 // positive and less than 2**53.
3126 MOZ_ASSERT(dayLength
> 0);
3127 MOZ_ASSERT(dayLength
< int64_t(1) << 53);
3129 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3130 // less than |timeAndDays.dayLength|.
3131 MOZ_ASSERT(std::abs(time
) < dayLength
);
3134 FractionalDays
operator+=(int32_t epochDays
) {
3135 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3140 FractionalDays
operator-=(int32_t epochDays
) {
3141 MOZ_ASSERT(std::abs(epochDays
) <= 200'000'000);
3146 int64_t truncate() const {
3147 int64_t truncatedDays
= days
;
3149 // Round toward positive infinity when the integer days are negative and
3150 // the fractional part is positive.
3151 if (truncatedDays
< 0) {
3154 } else if (time
< 0) {
3155 // Round toward negative infinity when the integer days are positive and
3156 // the fractional part is negative.
3157 if (truncatedDays
> 0) {
3161 return truncatedDays
;
3164 int32_t sign() const {
3166 return days
< 0 ? -1 : 1;
3168 return time
< 0 ? -1 : time
> 0 ? 1 : 0;
3172 struct Fraction final
{
3173 int64_t numerator
= 0;
3174 int32_t denominator
= 0;
3176 constexpr Fraction() = default;
3178 constexpr Fraction(int64_t numerator
, int32_t denominator
)
3179 : numerator(numerator
), denominator(denominator
) {
3180 MOZ_ASSERT(denominator
> 0);
3184 struct RoundedNumber final
{
3189 static RoundedNumber
RoundNumberToIncrement(
3190 const Fraction
& fraction
, const FractionalDays
& fractionalDays
,
3191 Increment increment
, TemporalRoundingMode roundingMode
,
3192 ComputeRemainder computeRemainder
) {
3194 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3195 static constexpr int64_t maxDurationDays
=
3196 (int64_t(1) << 53) / (24 * 60 * 60);
3198 // Numbers of days between nsMinInstant and nsMaxInstant.
3199 static constexpr int32_t epochDays
= 200'000'000;
3201 // Maximum number of days in |fractionalDays|.
3202 static constexpr int64_t maxFractionalDays
=
3203 2 * maxDurationDays
+ 2 * epochDays
;
3206 MOZ_ASSERT(std::abs(fraction
.numerator
) < (int64_t(1) << 32) * 2);
3207 MOZ_ASSERT(fraction
.denominator
> 0);
3208 MOZ_ASSERT(fraction
.denominator
<= epochDays
);
3209 MOZ_ASSERT(std::abs(fractionalDays
.days
) <= maxFractionalDays
);
3210 MOZ_ASSERT(fractionalDays
.dayLength
> 0);
3211 MOZ_ASSERT(fractionalDays
.dayLength
< (int64_t(1) << 53));
3212 MOZ_ASSERT(std::abs(fractionalDays
.time
) < fractionalDays
.dayLength
);
3213 MOZ_ASSERT(increment
<= Increment::max());
3217 // Change the representation of |fractionalWeeks| from a real number to a
3218 // rational number, because we don't support arbitrary precision real
3221 // |fractionalWeeks| is defined as:
3224 // = weeks + days' / abs(oneWeekDays)
3226 // where days' = days + nanoseconds / dayLength.
3228 // The fractional part |nanoseconds / dayLength| is from step 7.
3230 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3233 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3234 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3235 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3237 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3238 // to omit the multiplication by |dayLength| when the rounding conditions are
3239 // appropriately modified to account for the |nanoseconds / dayLength| part.
3240 // This allows to implement rounding using only int64 values.
3242 // This optimization is currently only implemented when |nanoseconds| is zero.
3244 // Example how to expand this optimization for non-zero |nanoseconds|:
3246 // |Round(fraction / increment) * increment| with:
3247 // fraction = numerator / denominator
3248 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3249 // denominator = dayLength * abs(oneWeekDays)
3251 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3253 // |Round(fraction / increment) * increment| with:
3254 // fraction = numerator / denominator
3255 // numerator = weeks * abs(oneWeekDays) + days
3256 // denominator = abs(oneWeekDays)
3259 // fraction / increment
3260 // = (numerator / denominator) / increment
3261 // = numerator / (denominator * increment)
3263 // And |numerator| and |denominator * increment| both fit into int64.
3265 // The "ceiling" operation has to be modified from:
3267 // CeilDiv(dividend, divisor)
3268 // quot, rem = dividend / divisor
3269 // return quot + (rem > 0)
3273 // CeilDiv(dividend, divisor, fractional)
3274 // quot, rem = dividend / divisor
3275 // return quot + ((rem > 0) || (fractional > 0))
3277 // To properly account for the fractional |nanoseconds| part. Alternatively
3278 // |dividend| can be modified before calling `CeilDiv`.
3282 if (fractionalDays
.time
== 0) {
3283 auto [numerator
, denominator
] = fraction
;
3284 int64_t totalDays
= fractionalDays
.days
+ denominator
* numerator
;
3286 if (computeRemainder
== ComputeRemainder::Yes
) {
3287 constexpr auto rounded
= Int128
{0};
3288 double total
= FractionToDouble(totalDays
, denominator
);
3289 return {rounded
, total
};
3293 RoundNumberToIncrement(totalDays
, denominator
, increment
, roundingMode
);
3294 constexpr double total
= 0;
3295 return {rounded
, total
};
3299 auto dayLength
= mozilla::CheckedInt64(fractionalDays
.dayLength
);
3301 auto denominator
= dayLength
* fraction
.denominator
;
3302 if (!denominator
.isValid()) {
3306 auto amountNanos
= denominator
* fraction
.numerator
;
3307 if (!amountNanos
.isValid()) {
3311 auto totalNanoseconds
= dayLength
* fractionalDays
.days
;
3312 totalNanoseconds
+= fractionalDays
.time
;
3313 totalNanoseconds
+= amountNanos
;
3314 if (!totalNanoseconds
.isValid()) {
3318 if (computeRemainder
== ComputeRemainder::Yes
) {
3319 constexpr auto rounded
= Int128
{0};
3321 FractionToDouble(totalNanoseconds
.value(), denominator
.value());
3322 return {rounded
, total
};
3325 auto rounded
= RoundNumberToIncrement(
3326 totalNanoseconds
.value(), denominator
.value(), increment
, roundingMode
);
3327 constexpr double total
= 0;
3328 return {rounded
, total
};
3331 // Use int128 when values are too large for int64. Additionally assert all
3332 // values fit into int128.
3334 // `dayLength` < 2**53
3335 auto dayLength
= Int128
{fractionalDays
.dayLength
};
3336 MOZ_ASSERT(dayLength
< Int128
{1} << 53);
3338 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3339 auto denominator
= dayLength
* Int128
{fraction
.denominator
};
3340 MOZ_ASSERT(denominator
< Int128
{1} << (53 + 28));
3342 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3344 // `abs(maxFractionalDays)`
3345 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3346 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3347 // ≤ 2 * 2**37 + 2**29
3349 auto totalDays
= Int128
{fractionalDays
.days
};
3350 MOZ_ASSERT(totalDays
.abs() <= Uint128
{1} << 39);
3352 // `abs(fraction.numerator)` ≤ (2**33)
3353 auto totalAmount
= Int128
{fraction
.numerator
};
3354 MOZ_ASSERT(totalAmount
.abs() <= Uint128
{1} << 33);
3356 // `denominator` < 2**(53 + 28)
3357 // `abs(totalAmount)` <= 2**33
3359 // `denominator * totalAmount`
3360 // ≤ 2**(53 + 28) * 2**33
3361 // = 2**(53 + 28 + 33)
3363 auto amountNanos
= denominator
* totalAmount
;
3364 MOZ_ASSERT(amountNanos
.abs() <= Uint128
{1} << 114);
3366 // `dayLength` < 2**53
3367 // `totalDays` ≤ 2**39
3368 // `fractionalDays.time` < `dayLength` < 2**53
3369 // `amountNanos` ≤ 2**114
3371 // `dayLength * totalDays`
3372 // ≤ 2**(53 + 39) = 2**92
3374 // `dayLength * totalDays + fractionalDays.time`
3377 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3379 auto totalNanoseconds
= dayLength
* totalDays
;
3380 totalNanoseconds
+= Int128
{fractionalDays
.time
};
3381 totalNanoseconds
+= amountNanos
;
3382 MOZ_ASSERT(totalNanoseconds
.abs() <= Uint128
{1} << 115);
3384 if (computeRemainder
== ComputeRemainder::Yes
) {
3385 constexpr auto rounded
= Int128
{0};
3386 double total
= FractionToDouble(totalNanoseconds
, denominator
);
3387 return {rounded
, total
};
3390 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, denominator
,
3391 increment
, roundingMode
);
3392 constexpr double total
= 0;
3393 return {rounded
, total
};
3396 static bool RoundDurationYear(JSContext
* cx
, const NormalizedDuration
& duration
,
3397 FractionalDays fractionalDays
,
3398 Increment increment
,
3399 TemporalRoundingMode roundingMode
,
3400 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3401 Handle
<CalendarRecord
> calendar
,
3402 ComputeRemainder computeRemainder
,
3403 RoundedDuration
* result
) {
3404 // Numbers of days between nsMinInstant and nsMaxInstant.
3405 static constexpr int32_t epochDays
= 200'000'000;
3407 auto [years
, months
, weeks
, days
] = duration
.date
;
3410 auto yearsDuration
= DateDuration
{years
};
3413 auto yearsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsDuration
);
3417 auto yearsLaterDate
= ToPlainDate(&yearsLater
.unwrap());
3419 // Step 10.f. (Reordered)
3420 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsLater
);
3423 auto yearsMonthsWeeks
= DateDuration
{years
, months
, weeks
};
3426 PlainDate yearsMonthsWeeksLater
;
3427 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3428 &yearsMonthsWeeksLater
)) {
3433 int32_t monthsWeeksInDays
= DaysUntil(yearsLaterDate
, yearsMonthsWeeksLater
);
3434 MOZ_ASSERT(std::abs(monthsWeeksInDays
) <= epochDays
);
3436 // Step 10.f. (Moved up)
3439 fractionalDays
+= monthsWeeksInDays
;
3441 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3442 // https://github.com/tc39/proposal-temporal/issues/2540
3445 PlainDate isoResult
;
3446 if (!AddISODate(cx
, yearsLaterDate
, {0, 0, 0, fractionalDays
.truncate()},
3447 TemporalOverflow::Constrain
, &isoResult
)) {
3452 Rooted
<PlainDateObject
*> wholeDaysLater(
3453 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3454 if (!wholeDaysLater
) {
3459 DateDuration timePassed
;
3460 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3461 TemporalUnit::Year
, &timePassed
)) {
3466 int64_t yearsPassed
= timePassed
.years
;
3469 years
+= yearsPassed
;
3472 auto yearsPassedDuration
= DateDuration
{yearsPassed
};
3476 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, yearsPassedDuration
,
3477 &newRelativeTo
, &daysPassed
)) {
3480 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3483 fractionalDays
-= daysPassed
;
3486 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3489 auto oneYear
= DateDuration
{sign
};
3492 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3493 int32_t oneYearDays
;
3494 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneYear
,
3495 &moveResultIgnored
, &oneYearDays
)) {
3500 if (oneYearDays
== 0) {
3501 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3502 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3507 auto fractionalYears
= Fraction
{years
, std::abs(oneYearDays
)};
3510 auto [numYears
, total
] =
3511 RoundNumberToIncrement(fractionalYears
, fractionalDays
, increment
,
3512 roundingMode
, computeRemainder
);
3515 int64_t numMonths
= 0;
3516 int64_t numWeeks
= 0;
3519 constexpr auto time
= NormalizedTimeDuration
{};
3522 if (numYears
.abs() >= (Uint128
{1} << 32)) {
3523 return ThrowInvalidDurationPart(cx
, double(numYears
), "years",
3524 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3527 auto resultDuration
= DateDuration
{int64_t(numYears
), numMonths
, numWeeks
};
3528 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3532 *result
= {{resultDuration
, time
}, total
};
3536 static bool RoundDurationMonth(JSContext
* cx
,
3537 const NormalizedDuration
& duration
,
3538 FractionalDays fractionalDays
,
3539 Increment increment
,
3540 TemporalRoundingMode roundingMode
,
3541 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3542 Handle
<CalendarRecord
> calendar
,
3543 ComputeRemainder computeRemainder
,
3544 RoundedDuration
* result
) {
3545 // Numbers of days between nsMinInstant and nsMaxInstant.
3546 static constexpr int32_t epochDays
= 200'000'000;
3548 auto [years
, months
, weeks
, days
] = duration
.date
;
3551 auto yearsMonths
= DateDuration
{years
, months
};
3554 auto yearsMonthsLater
= AddDate(cx
, calendar
, dateRelativeTo
, yearsMonths
);
3555 if (!yearsMonthsLater
) {
3558 auto yearsMonthsLaterDate
= ToPlainDate(&yearsMonthsLater
.unwrap());
3560 // Step 11.f. (Reordered)
3561 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
, yearsMonthsLater
);
3564 auto yearsMonthsWeeks
= DateDuration
{years
, months
, weeks
};
3567 PlainDate yearsMonthsWeeksLater
;
3568 if (!AddDate(cx
, calendar
, dateRelativeTo
, yearsMonthsWeeks
,
3569 &yearsMonthsWeeksLater
)) {
3574 int32_t weeksInDays
= DaysUntil(yearsMonthsLaterDate
, yearsMonthsWeeksLater
);
3575 MOZ_ASSERT(std::abs(weeksInDays
) <= epochDays
);
3577 // Step 11.f. (Moved up)
3580 fractionalDays
+= weeksInDays
;
3582 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3583 // https://github.com/tc39/proposal-temporal/issues/2540
3586 PlainDate isoResult
;
3587 if (!AddISODate(cx
, yearsMonthsLaterDate
,
3588 {0, 0, 0, fractionalDays
.truncate()},
3589 TemporalOverflow::Constrain
, &isoResult
)) {
3594 Rooted
<PlainDateObject
*> wholeDaysLater(
3595 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3596 if (!wholeDaysLater
) {
3601 DateDuration timePassed
;
3602 if (!DifferenceDate(cx
, calendar
, newRelativeTo
, wholeDaysLater
,
3603 TemporalUnit::Month
, &timePassed
)) {
3608 int64_t monthsPassed
= timePassed
.months
;
3611 months
+= monthsPassed
;
3614 auto monthsPassedDuration
= DateDuration
{0, monthsPassed
};
3618 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, monthsPassedDuration
,
3619 &newRelativeTo
, &daysPassed
)) {
3622 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3625 fractionalDays
-= daysPassed
;
3628 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3631 auto oneMonth
= DateDuration
{0, sign
};
3634 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3635 int32_t oneMonthDays
;
3636 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneMonth
,
3637 &moveResultIgnored
, &oneMonthDays
)) {
3642 if (oneMonthDays
== 0) {
3643 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3644 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3649 auto fractionalMonths
= Fraction
{months
, std::abs(oneMonthDays
)};
3652 auto [numMonths
, total
] =
3653 RoundNumberToIncrement(fractionalMonths
, fractionalDays
, increment
,
3654 roundingMode
, computeRemainder
);
3657 int64_t numWeeks
= 0;
3660 constexpr auto time
= NormalizedTimeDuration
{};
3663 if (numMonths
.abs() >= (Uint128
{1} << 32)) {
3664 return ThrowInvalidDurationPart(cx
, double(numMonths
), "months",
3665 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3668 auto resultDuration
= DateDuration
{years
, int64_t(numMonths
), numWeeks
};
3669 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3673 *result
= {{resultDuration
, time
}, total
};
3677 static bool RoundDurationWeek(JSContext
* cx
, const NormalizedDuration
& duration
,
3678 FractionalDays fractionalDays
,
3679 Increment increment
,
3680 TemporalRoundingMode roundingMode
,
3681 Handle
<Wrapped
<PlainDateObject
*>> dateRelativeTo
,
3682 Handle
<CalendarRecord
> calendar
,
3683 ComputeRemainder computeRemainder
,
3684 RoundedDuration
* result
) {
3685 // Numbers of days between nsMinInstant and nsMaxInstant.
3686 static constexpr int32_t epochDays
= 200'000'000;
3688 auto [years
, months
, weeks
, days
] = duration
.date
;
3690 auto* unwrappedRelativeTo
= dateRelativeTo
.unwrap(cx
);
3691 if (!unwrappedRelativeTo
) {
3694 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
3697 PlainDate isoResult
;
3698 if (!AddISODate(cx
, relativeToDate
, {0, 0, 0, fractionalDays
.truncate()},
3699 TemporalOverflow::Constrain
, &isoResult
)) {
3704 Rooted
<PlainDateObject
*> wholeDaysLater(
3705 cx
, CreateTemporalDate(cx
, isoResult
, calendar
.receiver()));
3706 if (!wholeDaysLater
) {
3711 DateDuration timePassed
;
3712 if (!DifferenceDate(cx
, calendar
, dateRelativeTo
, wholeDaysLater
,
3713 TemporalUnit::Week
, &timePassed
)) {
3718 int64_t weeksPassed
= timePassed
.weeks
;
3721 weeks
+= weeksPassed
;
3724 auto weeksPassedDuration
= DateDuration
{0, 0, weeksPassed
};
3727 Rooted
<Wrapped
<PlainDateObject
*>> newRelativeTo(cx
);
3729 if (!MoveRelativeDate(cx
, calendar
, dateRelativeTo
, weeksPassedDuration
,
3730 &newRelativeTo
, &daysPassed
)) {
3733 MOZ_ASSERT(std::abs(daysPassed
) <= epochDays
);
3736 fractionalDays
-= daysPassed
;
3739 int32_t sign
= fractionalDays
.sign() < 0 ? -1 : 1;
3742 auto oneWeek
= DateDuration
{0, 0, sign
};
3745 Rooted
<Wrapped
<PlainDateObject
*>> moveResultIgnored(cx
);
3746 int32_t oneWeekDays
;
3747 if (!MoveRelativeDate(cx
, calendar
, newRelativeTo
, oneWeek
,
3748 &moveResultIgnored
, &oneWeekDays
)) {
3753 if (oneWeekDays
== 0) {
3754 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3755 JSMSG_TEMPORAL_INVALID_NUMBER
, "days");
3760 auto fractionalWeeks
= Fraction
{weeks
, std::abs(oneWeekDays
)};
3763 auto [numWeeks
, total
] =
3764 RoundNumberToIncrement(fractionalWeeks
, fractionalDays
, increment
,
3765 roundingMode
, computeRemainder
);
3768 constexpr auto time
= NormalizedTimeDuration
{};
3771 if (numWeeks
.abs() >= (Uint128
{1} << 32)) {
3772 return ThrowInvalidDurationPart(cx
, double(numWeeks
), "weeks",
3773 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
3776 auto resultDuration
= DateDuration
{years
, months
, int64_t(numWeeks
)};
3777 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3781 *result
= {{resultDuration
, time
}, total
};
3785 static bool RoundDurationDay(JSContext
* cx
, const NormalizedDuration
& duration
,
3786 const FractionalDays
& fractionalDays
,
3787 Increment increment
,
3788 TemporalRoundingMode roundingMode
,
3789 ComputeRemainder computeRemainder
,
3790 RoundedDuration
* result
) {
3791 auto [years
, months
, weeks
, days
] = duration
.date
;
3793 // Pass zero fraction.
3794 constexpr auto zero
= Fraction
{0, 1};
3797 auto [numDays
, total
] = RoundNumberToIncrement(
3798 zero
, fractionalDays
, increment
, roundingMode
, computeRemainder
);
3800 MOZ_ASSERT(Int128
{INT64_MIN
} <= numDays
&& numDays
<= Int128
{INT64_MAX
},
3801 "rounded days fits in int64");
3804 constexpr auto time
= NormalizedTimeDuration
{};
3807 auto resultDuration
= DateDuration
{years
, months
, weeks
, int64_t(numDays
)};
3808 if (!ThrowIfInvalidDuration(cx
, resultDuration
)) {
3812 *result
= {{resultDuration
, time
}, total
};
3817 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3818 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3819 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3821 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
3822 Increment increment
, TemporalUnit unit
,
3823 TemporalRoundingMode roundingMode
,
3824 ComputeRemainder computeRemainder
,
3825 RoundedDuration
* result
) {
3826 // The remainder is only needed when called from |Duration_total|. And `total`
3827 // always passes |increment=1| and |roundingMode=trunc|.
3828 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3829 increment
== Increment
{1});
3830 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3831 roundingMode
== TemporalRoundingMode::Trunc
);
3833 // Steps 1-5. (Not applicable.)
3836 if (unit
<= TemporalUnit::Week
) {
3837 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3838 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3843 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3844 // because in that case this operation is a no-op. This case happens for
3845 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3848 // But maybe this can be even more efficiently handled in the callers. For
3849 // example when Temporal.PlainTime.prototype.{since,until} is called without
3850 // an options object, we can not only skip the RoundDuration call, but also
3851 // the following BalanceTimeDuration call.
3853 // Step 7. (Moved below.)
3855 // Steps 8-9. (Not applicable.)
3857 // Steps 10-12. (Not applicable.)
3860 if (unit
== TemporalUnit::Day
) {
3862 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3863 auto fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3865 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
3866 roundingMode
, computeRemainder
, result
);
3869 MOZ_ASSERT(TemporalUnit::Hour
<= unit
&& unit
<= TemporalUnit::Nanosecond
);
3872 auto time
= duration
.time
;
3874 if (computeRemainder
== ComputeRemainder::No
) {
3875 time
= RoundNormalizedTimeDurationToIncrement(time
, unit
, increment
,
3878 MOZ_ASSERT(increment
== Increment
{1});
3879 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
3881 total
= TotalNormalizedTimeDuration(duration
.time
, unit
);
3883 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
3886 MOZ_ASSERT(IsValidDuration(duration
.date
));
3887 *result
= {{duration
.date
, time
}, total
};
3892 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3893 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3894 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3896 static bool RoundDuration(
3897 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
3898 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
3899 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
3900 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
3901 Handle
<TimeZoneRecord
> timeZone
,
3902 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
3903 ComputeRemainder computeRemainder
, RoundedDuration
* result
) {
3904 // Note: |duration.days| can have a different sign than the other date
3905 // components. The date and time components can have different signs, too.
3906 MOZ_ASSERT(IsValidDuration(Duration
{double(duration
.date
.years
),
3907 double(duration
.date
.months
),
3908 double(duration
.date
.weeks
)}));
3909 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
.time
));
3911 MOZ_ASSERT(plainRelativeTo
|| zonedRelativeTo
,
3912 "Use RoundDuration without relativeTo when plainRelativeTo and "
3913 "zonedRelativeTo are both undefined");
3915 // The remainder is only needed when called from |Duration_total|. And `total`
3916 // always passes |increment=1| and |roundingMode=trunc|.
3917 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3918 increment
== Increment
{1});
3919 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
3920 roundingMode
== TemporalRoundingMode::Trunc
);
3922 // Steps 1-5. (Not applicable in our implementation.)
3924 // Step 6.a. (Not applicable in our implementation.)
3925 MOZ_ASSERT_IF(unit
<= TemporalUnit::Week
, plainRelativeTo
);
3929 unit
<= TemporalUnit::Week
,
3930 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
3934 unit
<= TemporalUnit::Week
,
3935 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateUntil
));
3938 case TemporalUnit::Year
:
3939 case TemporalUnit::Month
:
3940 case TemporalUnit::Week
:
3942 case TemporalUnit::Day
:
3943 // We can't take the faster code path when |zonedRelativeTo| is present.
3944 if (zonedRelativeTo
) {
3948 case TemporalUnit::Hour
:
3949 case TemporalUnit::Minute
:
3950 case TemporalUnit::Second
:
3951 case TemporalUnit::Millisecond
:
3952 case TemporalUnit::Microsecond
:
3953 case TemporalUnit::Nanosecond
:
3954 // Steps 7-9 and 13-21.
3955 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
3956 computeRemainder
, result
);
3957 case TemporalUnit::Auto
:
3958 MOZ_CRASH("Unexpected temporal unit");
3962 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
);
3965 FractionalDays fractionalDays
;
3966 if (zonedRelativeTo
) {
3968 Rooted
<ZonedDateTime
> intermediate(cx
);
3969 if (!MoveRelativeZonedDateTime(cx
, zonedRelativeTo
, calendar
, timeZone
,
3970 duration
.date
, precalculatedPlainDateTime
,
3976 NormalizedTimeAndDays timeAndDays
;
3977 if (!NormalizedTimeDurationToDays(cx
, duration
.time
, intermediate
, timeZone
,
3983 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3986 auto timeAndDays
= NormalizedTimeDurationToDays(duration
.time
);
3987 fractionalDays
= FractionalDays
{duration
.date
.days
, timeAndDays
};
3990 // Step 7.c. (Moved below)
3992 // Step 8. (Not applicable)
3995 // FIXME: spec issue - `total` doesn't need be initialised.
4000 case TemporalUnit::Year
:
4001 return RoundDurationYear(cx
, duration
, fractionalDays
, increment
,
4002 roundingMode
, plainRelativeTo
, calendar
,
4003 computeRemainder
, result
);
4006 case TemporalUnit::Month
:
4007 return RoundDurationMonth(cx
, duration
, fractionalDays
, increment
,
4008 roundingMode
, plainRelativeTo
, calendar
,
4009 computeRemainder
, result
);
4012 case TemporalUnit::Week
:
4013 return RoundDurationWeek(cx
, duration
, fractionalDays
, increment
,
4014 roundingMode
, plainRelativeTo
, calendar
,
4015 computeRemainder
, result
);
4018 case TemporalUnit::Day
:
4019 return RoundDurationDay(cx
, duration
, fractionalDays
, increment
,
4020 roundingMode
, computeRemainder
, result
);
4022 // Steps 14-19. (Handled elsewhere)
4023 case TemporalUnit::Auto
:
4024 case TemporalUnit::Hour
:
4025 case TemporalUnit::Minute
:
4026 case TemporalUnit::Second
:
4027 case TemporalUnit::Millisecond
:
4028 case TemporalUnit::Microsecond
:
4029 case TemporalUnit::Nanosecond
:
4033 MOZ_CRASH("Unexpected temporal unit");
4037 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4038 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4039 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4041 static bool RoundDuration(
4042 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4043 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4044 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4045 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4046 Handle
<TimeZoneRecord
> timeZone
,
4047 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4049 // Only called from |Duration_total|, which always passes |increment=1| and
4050 // |roundingMode=trunc|.
4051 MOZ_ASSERT(increment
== Increment
{1});
4052 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4054 RoundedDuration rounded
;
4055 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4056 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4057 precalculatedPlainDateTime
, ComputeRemainder::Yes
,
4062 *result
= rounded
.total
;
4067 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4068 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4069 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4071 static bool RoundDuration(
4072 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4073 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4074 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4075 Handle
<CalendarRecord
> calendar
, Handle
<ZonedDateTime
> zonedRelativeTo
,
4076 Handle
<TimeZoneRecord
> timeZone
,
4077 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
,
4078 NormalizedDuration
* result
) {
4079 RoundedDuration rounded
;
4080 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4081 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4082 precalculatedPlainDateTime
, ComputeRemainder::No
,
4087 *result
= rounded
.duration
;
4092 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4093 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4094 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4096 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4097 Increment increment
, TemporalUnit unit
,
4098 TemporalRoundingMode roundingMode
, double* result
) {
4099 MOZ_ASSERT(IsValidDuration(duration
));
4101 // Only called from |Duration_total|, which always passes |increment=1| and
4102 // |roundingMode=trunc|.
4103 MOZ_ASSERT(increment
== Increment
{1});
4104 MOZ_ASSERT(roundingMode
== TemporalRoundingMode::Trunc
);
4106 RoundedDuration rounded
;
4107 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4108 ComputeRemainder::Yes
, &rounded
)) {
4112 *result
= rounded
.total
;
4117 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4118 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4119 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4121 static bool RoundDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
4122 Increment increment
, TemporalUnit unit
,
4123 TemporalRoundingMode roundingMode
,
4124 NormalizedDuration
* result
) {
4125 MOZ_ASSERT(IsValidDuration(duration
));
4127 RoundedDuration rounded
;
4128 if (!::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4129 ComputeRemainder::No
, &rounded
)) {
4133 *result
= rounded
.duration
;
4138 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4139 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4140 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4142 bool js::temporal::RoundDuration(
4143 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4144 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4145 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
4146 Handle
<CalendarRecord
> calendar
, NormalizedDuration
* result
) {
4147 MOZ_ASSERT(IsValidDuration(duration
));
4149 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
, ZonedDateTime
{});
4150 Rooted
<TimeZoneRecord
> timeZone(cx
, TimeZoneRecord
{});
4151 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4152 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4153 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4154 precalculatedPlainDateTime
, result
);
4158 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4159 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4160 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4162 bool js::temporal::RoundDuration(
4163 JSContext
* cx
, const NormalizedDuration
& duration
, Increment increment
,
4164 TemporalUnit unit
, TemporalRoundingMode roundingMode
,
4165 Handle
<PlainDateObject
*> plainRelativeTo
, Handle
<CalendarRecord
> calendar
,
4166 Handle
<ZonedDateTime
> zonedRelativeTo
, Handle
<TimeZoneRecord
> timeZone
,
4167 const PlainDateTime
& precalculatedPlainDateTime
,
4168 NormalizedDuration
* result
) {
4169 MOZ_ASSERT(IsValidDuration(duration
));
4171 return ::RoundDuration(cx
, duration
, increment
, unit
, roundingMode
,
4172 plainRelativeTo
, calendar
, zonedRelativeTo
, timeZone
,
4173 mozilla::SomeRef(precalculatedPlainDateTime
), result
);
4176 enum class DurationOperation
{ Add
, Subtract
};
4179 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4182 static bool AddDurationToOrSubtractDurationFromDuration(
4183 JSContext
* cx
, DurationOperation operation
, const CallArgs
& args
) {
4184 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4185 auto duration
= ToDuration(durationObj
);
4187 // Step 1. (Not applicable in our implementation.)
4191 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
4195 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4196 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4197 Rooted
<TimeZoneRecord
> timeZone(cx
);
4198 if (args
.hasDefined(1)) {
4199 const char* name
= operation
== DurationOperation::Add
? "add" : "subtract";
4202 Rooted
<JSObject
*> options(cx
,
4203 RequireObjectArg(cx
, "options", name
, args
[1]));
4209 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4210 &zonedRelativeTo
, &timeZone
)) {
4213 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4214 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4218 Rooted
<CalendarRecord
> calendar(cx
);
4219 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4222 CalendarMethod::DateAdd
,
4223 CalendarMethod::DateUntil
,
4230 if (operation
== DurationOperation::Subtract
) {
4231 other
= other
.negate();
4235 if (plainRelativeTo
) {
4236 if (!AddDuration(cx
, duration
, other
, plainRelativeTo
, calendar
, &result
)) {
4239 } else if (zonedRelativeTo
) {
4240 if (!AddDuration(cx
, duration
, other
, zonedRelativeTo
, calendar
, timeZone
,
4245 if (!AddDuration(cx
, duration
, other
, &result
)) {
4251 auto* obj
= CreateTemporalDuration(cx
, result
);
4256 args
.rval().setObject(*obj
);
4261 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4262 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4265 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4266 CallArgs args
= CallArgsFromVp(argc
, vp
);
4269 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
4275 if (args
.hasDefined(0) &&
4276 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
4282 if (args
.hasDefined(1) &&
4283 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
4289 if (args
.hasDefined(2) &&
4290 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
4296 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
4302 if (args
.hasDefined(4) &&
4303 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
4309 if (args
.hasDefined(5) &&
4310 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
4316 if (args
.hasDefined(6) &&
4317 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
4322 double milliseconds
= 0;
4323 if (args
.hasDefined(7) &&
4324 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
4329 double microseconds
= 0;
4330 if (args
.hasDefined(8) &&
4331 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
4336 double nanoseconds
= 0;
4337 if (args
.hasDefined(9) &&
4338 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
4343 auto* duration
= CreateTemporalDuration(
4345 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
4346 microseconds
, nanoseconds
});
4351 args
.rval().setObject(*duration
);
4356 * Temporal.Duration.from ( item )
4358 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4359 CallArgs args
= CallArgsFromVp(argc
, vp
);
4361 Handle
<Value
> item
= args
.get(0);
4364 if (item
.isObject()) {
4365 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
4366 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
4371 args
.rval().setObject(*result
);
4377 auto result
= ToTemporalDuration(cx
, item
);
4382 args
.rval().setObject(*result
);
4387 * Temporal.Duration.compare ( one, two [ , options ] )
4389 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4390 CallArgs args
= CallArgsFromVp(argc
, vp
);
4394 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
4400 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
4405 Rooted
<JSObject
*> options(cx
);
4406 if (args
.hasDefined(2)) {
4407 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
4415 args
.rval().setInt32(0);
4420 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4421 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4422 Rooted
<TimeZoneRecord
> timeZone(cx
);
4424 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4425 &zonedRelativeTo
, &timeZone
)) {
4428 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4429 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4433 auto hasCalendarUnit
= [](const auto& d
) {
4434 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
4436 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
4439 Rooted
<CalendarRecord
> calendar(cx
);
4440 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4443 CalendarMethod::DateAdd
,
4450 if (zonedRelativeTo
&&
4451 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
4453 const auto& instant
= zonedRelativeTo
.instant();
4456 PlainDateTime dateTime
;
4457 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
4462 auto normalized1
= CreateNormalizedDurationRecord(one
);
4465 auto normalized2
= CreateNormalizedDurationRecord(two
);
4469 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
4470 dateTime
, &after1
)) {
4476 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
4477 dateTime
, &after2
)) {
4482 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
4487 int64_t days1
, days2
;
4488 if (calendarUnitsPresent
) {
4489 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4492 DateDuration unbalanceResult1
;
4493 if (plainRelativeTo
) {
4494 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
4495 TemporalUnit::Day
, plainRelativeTo
,
4496 calendar
, &unbalanceResult1
)) {
4500 if (!UnbalanceDateDurationRelative(
4501 cx
, one
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult1
)) {
4504 MOZ_ASSERT(one
.toDateDuration() == unbalanceResult1
);
4508 DateDuration unbalanceResult2
;
4509 if (plainRelativeTo
) {
4510 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
4511 TemporalUnit::Day
, plainRelativeTo
,
4512 calendar
, &unbalanceResult2
)) {
4516 if (!UnbalanceDateDurationRelative(
4517 cx
, two
.toDateDuration(), TemporalUnit::Day
, &unbalanceResult2
)) {
4520 MOZ_ASSERT(two
.toDateDuration() == unbalanceResult2
);
4524 days1
= unbalanceResult1
.days
;
4527 days2
= unbalanceResult2
.days
;
4530 days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
4533 days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
4537 auto normalized1
= NormalizeTimeDuration(one
);
4540 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
4546 auto normalized2
= NormalizeTimeDuration(two
);
4549 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
4555 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
4560 * get Temporal.Duration.prototype.years
4562 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
4564 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4565 args
.rval().setNumber(duration
->years());
4570 * get Temporal.Duration.prototype.years
4572 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4574 CallArgs args
= CallArgsFromVp(argc
, vp
);
4575 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
4579 * get Temporal.Duration.prototype.months
4581 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
4583 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4584 args
.rval().setNumber(duration
->months());
4589 * get Temporal.Duration.prototype.months
4591 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4593 CallArgs args
= CallArgsFromVp(argc
, vp
);
4594 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
4598 * get Temporal.Duration.prototype.weeks
4600 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
4602 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4603 args
.rval().setNumber(duration
->weeks());
4608 * get Temporal.Duration.prototype.weeks
4610 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4612 CallArgs args
= CallArgsFromVp(argc
, vp
);
4613 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
4617 * get Temporal.Duration.prototype.days
4619 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
4621 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4622 args
.rval().setNumber(duration
->days());
4627 * get Temporal.Duration.prototype.days
4629 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4631 CallArgs args
= CallArgsFromVp(argc
, vp
);
4632 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
4636 * get Temporal.Duration.prototype.hours
4638 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
4640 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4641 args
.rval().setNumber(duration
->hours());
4646 * get Temporal.Duration.prototype.hours
4648 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4650 CallArgs args
= CallArgsFromVp(argc
, vp
);
4651 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
4655 * get Temporal.Duration.prototype.minutes
4657 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
4659 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4660 args
.rval().setNumber(duration
->minutes());
4665 * get Temporal.Duration.prototype.minutes
4667 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4669 CallArgs args
= CallArgsFromVp(argc
, vp
);
4670 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
4674 * get Temporal.Duration.prototype.seconds
4676 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
4678 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4679 args
.rval().setNumber(duration
->seconds());
4684 * get Temporal.Duration.prototype.seconds
4686 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4688 CallArgs args
= CallArgsFromVp(argc
, vp
);
4689 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
4693 * get Temporal.Duration.prototype.milliseconds
4695 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
4697 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4698 args
.rval().setNumber(duration
->milliseconds());
4703 * get Temporal.Duration.prototype.milliseconds
4705 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4707 CallArgs args
= CallArgsFromVp(argc
, vp
);
4708 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
4712 * get Temporal.Duration.prototype.microseconds
4714 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
4716 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4717 args
.rval().setNumber(duration
->microseconds());
4722 * get Temporal.Duration.prototype.microseconds
4724 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4726 CallArgs args
= CallArgsFromVp(argc
, vp
);
4727 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
4731 * get Temporal.Duration.prototype.nanoseconds
4733 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
4735 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
4736 args
.rval().setNumber(duration
->nanoseconds());
4741 * get Temporal.Duration.prototype.nanoseconds
4743 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4745 CallArgs args
= CallArgsFromVp(argc
, vp
);
4746 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
4750 * get Temporal.Duration.prototype.sign
4752 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
4753 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4756 args
.rval().setInt32(DurationSign(duration
));
4761 * get Temporal.Duration.prototype.sign
4763 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4765 CallArgs args
= CallArgsFromVp(argc
, vp
);
4766 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
4770 * get Temporal.Duration.prototype.blank
4772 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
4773 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4776 args
.rval().setBoolean(duration
== Duration
{});
4781 * get Temporal.Duration.prototype.blank
4783 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4785 CallArgs args
= CallArgsFromVp(argc
, vp
);
4786 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
4790 * Temporal.Duration.prototype.with ( temporalDurationLike )
4792 * ToPartialDuration ( temporalDurationLike )
4794 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
4795 // Absent values default to the corresponding values of |this| object.
4796 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4799 Rooted
<JSObject
*> temporalDurationLike(
4800 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
4801 if (!temporalDurationLike
) {
4804 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
4809 auto* result
= CreateTemporalDuration(cx
, duration
);
4814 args
.rval().setObject(*result
);
4819 * Temporal.Duration.prototype.with ( temporalDurationLike )
4821 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4823 CallArgs args
= CallArgsFromVp(argc
, vp
);
4824 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
4828 * Temporal.Duration.prototype.negated ( )
4830 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
4831 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4834 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
4839 args
.rval().setObject(*result
);
4844 * Temporal.Duration.prototype.negated ( )
4846 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4848 CallArgs args
= CallArgsFromVp(argc
, vp
);
4849 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
4853 * Temporal.Duration.prototype.abs ( )
4855 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
4856 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4859 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
4864 args
.rval().setObject(*result
);
4869 * Temporal.Duration.prototype.abs ( )
4871 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4873 CallArgs args
= CallArgsFromVp(argc
, vp
);
4874 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
4878 * Temporal.Duration.prototype.add ( other [ , options ] )
4880 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
4881 return AddDurationToOrSubtractDurationFromDuration(cx
, DurationOperation::Add
,
4886 * Temporal.Duration.prototype.add ( other [ , options ] )
4888 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4890 CallArgs args
= CallArgsFromVp(argc
, vp
);
4891 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
4895 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4897 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
4898 return AddDurationToOrSubtractDurationFromDuration(
4899 cx
, DurationOperation::Subtract
, args
);
4903 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4905 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4907 CallArgs args
= CallArgsFromVp(argc
, vp
);
4908 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
4912 * Temporal.Duration.prototype.round ( roundTo )
4914 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4915 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4917 // Step 18. (Reordered)
4918 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
4921 auto smallestUnit
= TemporalUnit::Auto
;
4922 TemporalUnit largestUnit
;
4923 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
4924 auto roundingIncrement
= Increment
{1};
4925 Rooted
<JSObject
*> relativeTo(cx
);
4926 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4927 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4928 Rooted
<TimeZoneRecord
> timeZone(cx
);
4929 if (args
.get(0).isString()) {
4930 // Step 4. (Not applicable in our implementation.)
4932 // Steps 6-15. (Not applicable)
4935 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4936 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::SmallestUnit
,
4937 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
4941 // Step 17. (Not applicable)
4943 // Step 18. (Moved above)
4946 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4948 // Step 20. (Not applicable)
4950 // Step 20.a. (Not applicable)
4953 largestUnit
= defaultLargestUnit
;
4955 // Steps 21-25. (Not applicable)
4958 Rooted
<JSObject
*> options(
4959 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
4965 bool smallestUnitPresent
= true;
4968 bool largestUnitPresent
= true;
4972 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
4973 // absent "largestUnit" value.
4974 Rooted
<Value
> largestUnitValue(cx
);
4975 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
4976 &largestUnitValue
)) {
4980 if (!largestUnitValue
.isUndefined()) {
4981 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
4982 if (!largestUnitStr
) {
4986 largestUnit
= TemporalUnit::Auto
;
4987 if (!GetTemporalUnit(cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
4988 TemporalUnitGroup::DateTime
, &largestUnit
)) {
4994 if (!ToRelativeTemporalObject(cx
, options
, &plainRelativeTo
,
4995 &zonedRelativeTo
, &timeZone
)) {
4998 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4999 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5002 if (!ToTemporalRoundingIncrement(cx
, options
, &roundingIncrement
)) {
5007 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5012 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5013 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
5018 if (smallestUnit
== TemporalUnit::Auto
) {
5020 smallestUnitPresent
= false;
5023 smallestUnit
= TemporalUnit::Nanosecond
;
5026 // Step 18. (Moved above)
5029 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
5032 if (largestUnitValue
.isUndefined()) {
5034 largestUnitPresent
= false;
5037 largestUnit
= defaultLargestUnit
;
5038 } else if (largestUnit
== TemporalUnit::Auto
) {
5040 largestUnit
= defaultLargestUnit
;
5044 if (!smallestUnitPresent
&& !largestUnitPresent
) {
5045 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5046 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
5051 if (largestUnit
> smallestUnit
) {
5052 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5053 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
5058 if (smallestUnit
> TemporalUnit::Day
) {
5060 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
5063 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
5071 bool hoursToDaysConversionMayOccur
= false;
5074 if (duration
.days
!= 0 && zonedRelativeTo
) {
5075 hoursToDaysConversionMayOccur
= true;
5079 else if (std::abs(duration
.hours
) >= 24) {
5080 hoursToDaysConversionMayOccur
= true;
5084 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
5085 roundingIncrement
== Increment
{1};
5088 bool calendarUnitsPresent
=
5089 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
5092 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
5093 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
5094 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
5095 std::abs(duration
.milliseconds
) < 1000 &&
5096 std::abs(duration
.microseconds
) < 1000 &&
5097 std::abs(duration
.nanoseconds
) < 1000) {
5099 auto* obj
= CreateTemporalDuration(cx
, duration
);
5104 args
.rval().setObject(*obj
);
5109 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5112 bool plainDateTimeOrRelativeToWillBeUsed
=
5113 !roundingGranularityIsNoop
|| largestUnit
<= TemporalUnit::Day
||
5114 calendarUnitsPresent
|| duration
.days
!= 0;
5117 PlainDateTime relativeToDateTime
;
5118 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5120 const auto& instant
= zonedRelativeTo
.instant();
5123 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5126 precalculatedPlainDateTime
=
5127 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5130 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5131 zonedRelativeTo
.calendar());
5132 if (!plainRelativeTo
) {
5138 Rooted
<CalendarRecord
> calendar(cx
);
5139 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5142 CalendarMethod::DateAdd
,
5143 CalendarMethod::DateUntil
,
5150 DateDuration unbalanceResult
;
5151 if (plainRelativeTo
) {
5152 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5153 largestUnit
, plainRelativeTo
, calendar
,
5154 &unbalanceResult
)) {
5158 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(),
5159 largestUnit
, &unbalanceResult
)) {
5162 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5167 NormalizedDuration
{unbalanceResult
, NormalizeTimeDuration(duration
)};
5168 NormalizedDuration roundResult
;
5169 if (plainRelativeTo
|| zonedRelativeTo
) {
5170 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5171 roundingMode
, plainRelativeTo
, calendar
,
5172 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5177 if (!::RoundDuration(cx
, roundInput
, roundingIncrement
, smallestUnit
,
5178 roundingMode
, &roundResult
)) {
5184 TimeDuration balanceResult
;
5185 if (zonedRelativeTo
) {
5187 NormalizedDuration adjustResult
;
5188 if (!AdjustRoundedDurationDays(cx
, roundResult
, roundingIncrement
,
5189 smallestUnit
, roundingMode
, zonedRelativeTo
,
5191 precalculatedPlainDateTime
, &adjustResult
)) {
5194 roundResult
= adjustResult
;
5197 if (!BalanceTimeDurationRelative(
5198 cx
, roundResult
, largestUnit
, zonedRelativeTo
, timeZone
,
5199 precalculatedPlainDateTime
, &balanceResult
)) {
5204 NormalizedTimeDuration withDays
;
5205 if (!Add24HourDaysToNormalizedTimeDuration(
5206 cx
, roundResult
.time
, roundResult
.date
.days
, &withDays
)) {
5211 balanceResult
= temporal::BalanceTimeDuration(withDays
, largestUnit
);
5215 auto balanceInput
= DateDuration
{
5216 roundResult
.date
.years
,
5217 roundResult
.date
.months
,
5218 roundResult
.date
.weeks
,
5221 DateDuration dateResult
;
5222 if (!::BalanceDateDurationRelative(cx
, balanceInput
, largestUnit
,
5223 smallestUnit
, plainRelativeTo
, calendar
,
5229 auto result
= Duration
{
5230 double(dateResult
.years
), double(dateResult
.months
),
5231 double(dateResult
.weeks
), double(dateResult
.days
),
5232 double(balanceResult
.hours
), double(balanceResult
.minutes
),
5233 double(balanceResult
.seconds
), double(balanceResult
.milliseconds
),
5234 balanceResult
.microseconds
, balanceResult
.nanoseconds
,
5236 MOZ_ASSERT(IsValidDuration(result
));
5237 auto* obj
= CreateTemporalDuration(cx
, result
);
5242 args
.rval().setObject(*obj
);
5247 * Temporal.Duration.prototype.round ( options )
5249 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5251 CallArgs args
= CallArgsFromVp(argc
, vp
);
5252 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
5256 * Temporal.Duration.prototype.total ( totalOf )
5258 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
5259 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
5260 auto duration
= ToDuration(durationObj
);
5263 Rooted
<JSObject
*> relativeTo(cx
);
5264 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
5265 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
5266 Rooted
<TimeZoneRecord
> timeZone(cx
);
5267 auto unit
= TemporalUnit::Auto
;
5268 if (args
.get(0).isString()) {
5269 // Step 4. (Not applicable in our implementation.)
5271 // Steps 6-10. (Implicit)
5272 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
5275 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
5276 if (!GetTemporalUnit(cx
, paramString
, TemporalUnitKey::Unit
,
5277 TemporalUnitGroup::DateTime
, &unit
)) {
5282 Rooted
<JSObject
*> totalOf(
5283 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
5289 if (!ToRelativeTemporalObject(cx
, totalOf
, &plainRelativeTo
,
5290 &zonedRelativeTo
, &timeZone
)) {
5293 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
5294 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
5297 if (!GetTemporalUnit(cx
, totalOf
, TemporalUnitKey::Unit
,
5298 TemporalUnitGroup::DateTime
, &unit
)) {
5302 if (unit
== TemporalUnit::Auto
) {
5303 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5304 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
5310 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
5313 bool plainDateTimeOrRelativeToWillBeUsed
=
5314 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
5317 PlainDateTime relativeToDateTime
;
5318 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
5320 const auto& instant
= zonedRelativeTo
.instant();
5323 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
5326 precalculatedPlainDateTime
=
5327 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
5330 plainRelativeTo
= CreateTemporalDate(cx
, relativeToDateTime
.date
,
5331 zonedRelativeTo
.calendar());
5332 if (!plainRelativeTo
) {
5338 Rooted
<CalendarRecord
> calendar(cx
);
5339 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
5342 CalendarMethod::DateAdd
,
5343 CalendarMethod::DateUntil
,
5350 DateDuration unbalanceResult
;
5351 if (plainRelativeTo
) {
5352 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5353 plainRelativeTo
, calendar
,
5354 &unbalanceResult
)) {
5358 if (!UnbalanceDateDurationRelative(cx
, duration
.toDateDuration(), unit
,
5359 &unbalanceResult
)) {
5362 MOZ_ASSERT(duration
.toDateDuration() == unbalanceResult
);
5366 int64_t unbalancedDays
= unbalanceResult
.days
;
5370 NormalizedTimeDuration balanceResult
;
5371 if (zonedRelativeTo
) {
5373 Rooted
<ZonedDateTime
> intermediate(cx
);
5374 if (!MoveRelativeZonedDateTime(
5375 cx
, zonedRelativeTo
, calendar
, timeZone
,
5376 {unbalanceResult
.years
, unbalanceResult
.months
,
5377 unbalanceResult
.weeks
, 0},
5378 precalculatedPlainDateTime
, &intermediate
)) {
5383 auto timeDuration
= NormalizeTimeDuration(duration
);
5386 const auto& startNs
= intermediate
.instant();
5389 const auto& startInstant
= startNs
;
5392 mozilla::Maybe
<PlainDateTime
> startDateTime
{};
5395 Instant intermediateNs
;
5396 if (unbalancedDays
!= 0) {
5398 PlainDateTime dateTime
;
5399 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5402 startDateTime
= mozilla::Some(dateTime
);
5405 Rooted
<CalendarValue
> isoCalendar(cx
, CalendarValue(cx
->names().iso8601
));
5407 if (!AddDaysToZonedDateTime(cx
, startInstant
, dateTime
, timeZone
,
5408 isoCalendar
, unbalancedDays
, &addResult
)) {
5413 intermediateNs
= addResult
;
5416 intermediateNs
= startNs
;
5421 if (!AddInstant(cx
, intermediateNs
, timeDuration
, &endNs
)) {
5427 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs
, startNs
);
5431 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5432 if (TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Day
&&
5433 difference
!= NormalizedTimeDuration
{}) {
5435 if (!startDateTime
) {
5436 PlainDateTime dateTime
;
5437 if (!GetPlainDateTimeFor(cx
, timeZone
, startInstant
, &dateTime
)) {
5440 startDateTime
= mozilla::Some(dateTime
);
5444 NormalizedTimeAndDays timeAndDays
;
5445 if (!NormalizedTimeDurationToDays(cx
, difference
, intermediate
, timeZone
,
5446 *startDateTime
, &timeAndDays
)) {
5451 balanceResult
= NormalizedTimeDuration::fromNanoseconds(timeAndDays
.time
);
5454 days
= timeAndDays
.days
;
5457 balanceResult
= difference
;
5462 auto timeDuration
= NormalizeTimeDuration(duration
);
5465 if (!Add24HourDaysToNormalizedTimeDuration(cx
, timeDuration
, unbalancedDays
,
5473 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult
));
5476 auto roundInput
= NormalizedDuration
{
5478 unbalanceResult
.years
,
5479 unbalanceResult
.months
,
5480 unbalanceResult
.weeks
,
5486 if (plainRelativeTo
|| zonedRelativeTo
) {
5487 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5488 TemporalRoundingMode::Trunc
, plainRelativeTo
, calendar
,
5489 zonedRelativeTo
, timeZone
, precalculatedPlainDateTime
,
5494 if (!::RoundDuration(cx
, roundInput
, Increment
{1}, unit
,
5495 TemporalRoundingMode::Trunc
, &total
)) {
5501 args
.rval().setNumber(total
);
5506 * Temporal.Duration.prototype.total ( totalOf )
5508 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5510 CallArgs args
= CallArgsFromVp(argc
, vp
);
5511 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
5515 * Temporal.Duration.prototype.toString ( [ options ] )
5517 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
5518 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5521 SecondsStringPrecision precision
= {Precision::Auto(),
5522 TemporalUnit::Nanosecond
, Increment
{1}};
5523 auto roundingMode
= TemporalRoundingMode::Trunc
;
5524 if (args
.hasDefined(0)) {
5526 Rooted
<JSObject
*> options(
5527 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
5533 auto digits
= Precision::Auto();
5534 if (!ToFractionalSecondDigits(cx
, options
, &digits
)) {
5539 if (!ToTemporalRoundingMode(cx
, options
, &roundingMode
)) {
5544 auto smallestUnit
= TemporalUnit::Auto
;
5545 if (!GetTemporalUnit(cx
, options
, TemporalUnitKey::SmallestUnit
,
5546 TemporalUnitGroup::Time
, &smallestUnit
)) {
5551 if (smallestUnit
== TemporalUnit::Hour
||
5552 smallestUnit
== TemporalUnit::Minute
) {
5553 const char* smallestUnitStr
=
5554 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
5555 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
5556 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
5557 smallestUnitStr
, "smallestUnit");
5562 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
5567 if (precision
.unit
!= TemporalUnit::Nanosecond
||
5568 precision
.increment
!= Increment
{1}) {
5570 auto timeDuration
= NormalizeTimeDuration(duration
);
5573 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
5576 auto rounded
= RoundDuration(timeDuration
, precision
.increment
,
5577 precision
.unit
, roundingMode
);
5580 auto balanced
= BalanceTimeDuration(
5581 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
5585 duration
.years
, duration
.months
,
5586 duration
.weeks
, duration
.days
+ double(balanced
.days
),
5587 double(balanced
.hours
), double(balanced
.minutes
),
5588 double(balanced
.seconds
), double(balanced
.milliseconds
),
5589 balanced
.microseconds
, balanced
.nanoseconds
,
5591 MOZ_ASSERT(IsValidDuration(duration
));
5598 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
5603 args
.rval().setString(str
);
5608 * Temporal.Duration.prototype.toString ( [ options ] )
5610 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5612 CallArgs args
= CallArgsFromVp(argc
, vp
);
5613 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
5617 * Temporal.Duration.prototype.toJSON ( )
5619 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
5620 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5623 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5628 args
.rval().setString(str
);
5633 * Temporal.Duration.prototype.toJSON ( )
5635 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5637 CallArgs args
= CallArgsFromVp(argc
, vp
);
5638 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
5642 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5644 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
5645 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
5648 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
5653 args
.rval().setString(str
);
5658 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5660 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5662 CallArgs args
= CallArgsFromVp(argc
, vp
);
5663 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
5667 * Temporal.Duration.prototype.valueOf ( )
5669 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
5670 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
5671 "Duration", "primitive type");
5675 const JSClass
DurationObject::class_
= {
5676 "Temporal.Duration",
5677 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
5678 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
5680 &DurationObject::classSpec_
,
5683 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
5685 static const JSFunctionSpec Duration_methods
[] = {
5686 JS_FN("from", Duration_from
, 1, 0),
5687 JS_FN("compare", Duration_compare
, 2, 0),
5691 static const JSFunctionSpec Duration_prototype_methods
[] = {
5692 JS_FN("with", Duration_with
, 1, 0),
5693 JS_FN("negated", Duration_negated
, 0, 0),
5694 JS_FN("abs", Duration_abs
, 0, 0),
5695 JS_FN("add", Duration_add
, 1, 0),
5696 JS_FN("subtract", Duration_subtract
, 1, 0),
5697 JS_FN("round", Duration_round
, 1, 0),
5698 JS_FN("total", Duration_total
, 1, 0),
5699 JS_FN("toString", Duration_toString
, 0, 0),
5700 JS_FN("toJSON", Duration_toJSON
, 0, 0),
5701 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
5702 JS_FN("valueOf", Duration_valueOf
, 0, 0),
5706 static const JSPropertySpec Duration_prototype_properties
[] = {
5707 JS_PSG("years", Duration_years
, 0),
5708 JS_PSG("months", Duration_months
, 0),
5709 JS_PSG("weeks", Duration_weeks
, 0),
5710 JS_PSG("days", Duration_days
, 0),
5711 JS_PSG("hours", Duration_hours
, 0),
5712 JS_PSG("minutes", Duration_minutes
, 0),
5713 JS_PSG("seconds", Duration_seconds
, 0),
5714 JS_PSG("milliseconds", Duration_milliseconds
, 0),
5715 JS_PSG("microseconds", Duration_microseconds
, 0),
5716 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
5717 JS_PSG("sign", Duration_sign
, 0),
5718 JS_PSG("blank", Duration_blank
, 0),
5719 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
5723 const ClassSpec
DurationObject::classSpec_
= {
5724 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
5725 GenericCreatePrototype
<DurationObject
>,
5728 Duration_prototype_methods
,
5729 Duration_prototype_properties
,
5731 ClassSpec::DontDefineConstructor
,