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/StringBuilder.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 int32_t js::temporal::DurationSign(const DateDuration
& duration
) {
148 const auto& [years
, months
, weeks
, days
] = duration
;
151 for (auto v
: {years
, months
, weeks
, days
}) {
168 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
169 * milliseconds, microseconds, nanoseconds )
171 int32_t js::temporal::DurationSign(const NormalizedDuration
& duration
) {
172 MOZ_ASSERT(IsValidDuration(duration
));
174 if (int32_t sign
= DurationSign(duration
.date
)) {
177 return NormalizedTimeDurationSign(duration
.time
);
181 * Normalize a nanoseconds amount into a time duration.
183 static NormalizedTimeDuration
NormalizeNanoseconds(const Int96
& nanoseconds
) {
184 // Split into seconds and nanoseconds.
185 auto [seconds
, nanos
] = nanoseconds
/ ToNanoseconds(TemporalUnit::Second
);
187 return {seconds
, nanos
};
191 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
192 * value is too large.
194 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeNanoseconds(
195 double nanoseconds
) {
196 MOZ_ASSERT(IsInteger(nanoseconds
));
198 if (auto int96
= Int96::fromInteger(nanoseconds
)) {
199 // The number of normalized seconds must not exceed `2**53 - 1`.
200 constexpr auto limit
=
201 Int96
{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second
);
203 if (int96
->abs() < limit
) {
204 return mozilla::Some(NormalizeNanoseconds(*int96
));
207 return mozilla::Nothing();
211 * Normalize a microseconds amount into a time duration.
213 static NormalizedTimeDuration
NormalizeMicroseconds(const Int96
& microseconds
) {
214 // Split into seconds and microseconds.
215 auto [seconds
, micros
] = microseconds
/ ToMicroseconds(TemporalUnit::Second
);
217 // Scale microseconds to nanoseconds.
218 int32_t nanos
= micros
* int32_t(ToNanoseconds(TemporalUnit::Microsecond
));
220 return {seconds
, nanos
};
224 * Normalize a microseconds amount into a time duration. Return Nothing if the
225 * value is too large.
227 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeMicroseconds(
228 double microseconds
) {
229 MOZ_ASSERT(IsInteger(microseconds
));
231 if (auto int96
= Int96::fromInteger(microseconds
)) {
232 // The number of normalized seconds must not exceed `2**53 - 1`.
233 constexpr auto limit
=
234 Int96
{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second
);
236 if (int96
->abs() < limit
) {
237 return mozilla::Some(NormalizeMicroseconds(*int96
));
240 return mozilla::Nothing();
244 * Normalize a duration into a time duration. Return Nothing if any duration
245 * value is too large.
247 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeSeconds(
248 const Duration
& duration
) {
250 auto nanoseconds
= NormalizeNanoseconds(duration
.nanoseconds
);
254 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds
));
256 auto microseconds
= NormalizeMicroseconds(duration
.microseconds
);
260 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds
));
262 // Overflows for millis/seconds/minutes/hours/days always result in an
263 // invalid normalized time duration.
265 int64_t milliseconds
;
266 if (!mozilla::NumberEqualsInt64(duration
.milliseconds
, &milliseconds
)) {
271 if (!mozilla::NumberEqualsInt64(duration
.seconds
, &seconds
)) {
276 if (!mozilla::NumberEqualsInt64(duration
.minutes
, &minutes
)) {
281 if (!mozilla::NumberEqualsInt64(duration
.hours
, &hours
)) {
286 if (!mozilla::NumberEqualsInt64(duration
.days
, &days
)) {
290 // Compute the overall amount of milliseconds.
291 mozilla::CheckedInt64 millis
= days
;
299 millis
+= milliseconds
;
300 if (!millis
.isValid()) {
304 auto milli
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
305 if (!IsValidNormalizedTimeDuration(milli
)) {
309 // Compute the overall time duration.
310 auto result
= milli
+ *microseconds
+ *nanoseconds
;
311 if (!IsValidNormalizedTimeDuration(result
)) {
315 return mozilla::Some(result
);
318 return mozilla::Nothing();
322 * Normalize a days amount into a time duration. Return Nothing if the value is
325 static mozilla::Maybe
<NormalizedTimeDuration
> NormalizeDays(int64_t days
) {
327 // Compute the overall amount of milliseconds.
329 mozilla::CheckedInt64(days
) * ToMilliseconds(TemporalUnit::Day
);
330 if (!millis
.isValid()) {
334 auto result
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
335 if (!IsValidNormalizedTimeDuration(result
)) {
339 return mozilla::Some(result
);
342 return mozilla::Nothing();
346 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
349 static NormalizedTimeDuration
NormalizeTimeDuration(
350 double hours
, double minutes
, double seconds
, double milliseconds
,
351 double microseconds
, double nanoseconds
) {
352 MOZ_ASSERT(IsInteger(hours
));
353 MOZ_ASSERT(IsInteger(minutes
));
354 MOZ_ASSERT(IsInteger(seconds
));
355 MOZ_ASSERT(IsInteger(milliseconds
));
356 MOZ_ASSERT(IsInteger(microseconds
));
357 MOZ_ASSERT(IsInteger(nanoseconds
));
360 mozilla::CheckedInt64 millis
= int64_t(hours
);
362 millis
+= int64_t(minutes
);
364 millis
+= int64_t(seconds
);
366 millis
+= int64_t(milliseconds
);
367 MOZ_ASSERT(millis
.isValid());
369 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
372 auto micros
= Int96::fromInteger(microseconds
);
375 normalized
+= NormalizeMicroseconds(*micros
);
378 auto nanos
= Int96::fromInteger(nanoseconds
);
381 normalized
+= NormalizeNanoseconds(*nanos
);
384 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
391 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
394 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
395 int32_t hours
, int32_t minutes
, int32_t seconds
, int32_t milliseconds
,
396 int32_t microseconds
, int32_t nanoseconds
) {
398 mozilla::CheckedInt64 millis
= int64_t(hours
);
400 millis
+= int64_t(minutes
);
402 millis
+= int64_t(seconds
);
404 millis
+= int64_t(milliseconds
);
405 MOZ_ASSERT(millis
.isValid());
407 auto normalized
= NormalizedTimeDuration::fromMilliseconds(millis
.value());
410 normalized
+= NormalizeMicroseconds(Int96
{microseconds
});
413 normalized
+= NormalizeNanoseconds(Int96
{nanoseconds
});
416 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized
));
423 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
426 NormalizedTimeDuration
js::temporal::NormalizeTimeDuration(
427 const Duration
& duration
) {
428 MOZ_ASSERT(IsValidDuration(duration
));
430 return ::NormalizeTimeDuration(duration
.hours
, duration
.minutes
,
431 duration
.seconds
, duration
.milliseconds
,
432 duration
.microseconds
, duration
.nanoseconds
);
436 * AddNormalizedTimeDuration ( one, two )
438 static bool AddNormalizedTimeDuration(JSContext
* cx
,
439 const NormalizedTimeDuration
& one
,
440 const NormalizedTimeDuration
& two
,
441 NormalizedTimeDuration
* result
) {
442 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
443 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
446 auto sum
= one
+ two
;
449 if (!IsValidNormalizedTimeDuration(sum
)) {
450 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
451 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
461 * SubtractNormalizedTimeDuration ( one, two )
463 static bool SubtractNormalizedTimeDuration(JSContext
* cx
,
464 const NormalizedTimeDuration
& one
,
465 const NormalizedTimeDuration
& two
,
466 NormalizedTimeDuration
* result
) {
467 MOZ_ASSERT(IsValidNormalizedTimeDuration(one
));
468 MOZ_ASSERT(IsValidNormalizedTimeDuration(two
));
471 auto sum
= one
- two
;
474 if (!IsValidNormalizedTimeDuration(sum
)) {
475 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
476 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
486 * Add24HourDaysToNormalizedTimeDuration ( d, days )
488 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
489 JSContext
* cx
, const NormalizedTimeDuration
& d
, int64_t days
,
490 NormalizedTimeDuration
* result
) {
491 MOZ_ASSERT(IsValidNormalizedTimeDuration(d
));
494 auto normalizedDays
= NormalizeDays(days
);
495 if (!normalizedDays
) {
496 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
497 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
502 auto sum
= d
+ *normalizedDays
;
503 if (!IsValidNormalizedTimeDuration(sum
)) {
504 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
505 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
515 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
517 bool js::temporal::CombineDateAndNormalizedTimeDuration(
518 JSContext
* cx
, const DateDuration
& date
, const NormalizedTimeDuration
& time
,
519 NormalizedDuration
* result
) {
520 MOZ_ASSERT(IsValidDuration(date
));
521 MOZ_ASSERT(IsValidNormalizedTimeDuration(time
));
524 int32_t dateSign
= DurationSign(date
);
527 int32_t timeSign
= NormalizedTimeDurationSign(time
);
530 if ((dateSign
* timeSign
) < 0) {
531 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
532 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN
);
537 *result
= {date
, time
};
542 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
544 NormalizedTimeDuration
545 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
546 const Instant
& one
, const Instant
& two
) {
547 MOZ_ASSERT(IsValidEpochInstant(one
));
548 MOZ_ASSERT(IsValidEpochInstant(two
));
551 auto result
= one
- two
;
554 MOZ_ASSERT(IsValidInstantSpan(result
));
557 return result
.to
<NormalizedTimeDuration
>();
561 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
562 * milliseconds, microseconds, nanoseconds )
564 bool js::temporal::IsValidDuration(const Duration
& duration
) {
565 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
567 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
568 milliseconds
, microseconds
, nanoseconds
] = duration
;
571 int32_t sign
= DurationSign(duration
);
574 for (auto v
: {years
, months
, weeks
, days
, hours
, minutes
, seconds
,
575 milliseconds
, microseconds
, nanoseconds
}) {
577 if (!std::isfinite(v
)) {
582 if (v
< 0 && sign
> 0) {
587 if (v
> 0 && sign
< 0) {
593 if (std::abs(years
) >= double(int64_t(1) << 32)) {
598 if (std::abs(months
) >= double(int64_t(1) << 32)) {
603 if (std::abs(weeks
) >= double(int64_t(1) << 32)) {
608 if (!NormalizeSeconds(duration
)) {
618 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
619 * milliseconds, microseconds, nanoseconds )
621 bool js::temporal::IsValidDuration(const DateDuration
& duration
) {
622 return IsValidDuration(duration
.toDuration());
626 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
627 * milliseconds, microseconds, nanoseconds )
629 bool js::temporal::IsValidDuration(const NormalizedDuration
& duration
) {
630 if (!IsValidNormalizedTimeDuration(duration
.time
)) {
634 auto d
= duration
.date
.toDuration();
635 auto [seconds
, nanoseconds
] = duration
.time
.denormalize();
636 d
.seconds
= double(seconds
);
637 d
.nanoseconds
= double(nanoseconds
);
639 return IsValidDuration(d
);
643 static bool ThrowInvalidDurationPart(JSContext
* cx
, double value
,
644 const char* name
, unsigned errorNumber
) {
646 const char* numStr
= NumberToCString(&cbuf
, value
);
648 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, errorNumber
, name
,
654 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
655 * milliseconds, microseconds, nanoseconds )
657 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
658 const Duration
& duration
) {
659 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration
));
661 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
662 milliseconds
, microseconds
, nanoseconds
] = duration
;
665 int32_t sign
= DurationSign(duration
);
667 auto throwIfInvalid
= [&](double v
, const char* name
) {
669 if (!std::isfinite(v
)) {
670 return ThrowInvalidDurationPart(
671 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
675 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
676 return ThrowInvalidDurationPart(cx
, v
, name
,
677 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
683 auto throwIfTooLarge
= [&](double v
, const char* name
) {
684 if (std::abs(v
) >= double(int64_t(1) << 32)) {
685 return ThrowInvalidDurationPart(
686 cx
, v
, name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
692 if (!throwIfInvalid(years
, "years")) {
695 if (!throwIfInvalid(months
, "months")) {
698 if (!throwIfInvalid(weeks
, "weeks")) {
701 if (!throwIfInvalid(days
, "days")) {
704 if (!throwIfInvalid(hours
, "hours")) {
707 if (!throwIfInvalid(minutes
, "minutes")) {
710 if (!throwIfInvalid(seconds
, "seconds")) {
713 if (!throwIfInvalid(milliseconds
, "milliseconds")) {
716 if (!throwIfInvalid(microseconds
, "microseconds")) {
719 if (!throwIfInvalid(nanoseconds
, "nanoseconds")) {
724 if (!throwIfTooLarge(years
, "years")) {
729 if (!throwIfTooLarge(months
, "months")) {
734 if (!throwIfTooLarge(weeks
, "weeks")) {
739 if (!NormalizeSeconds(duration
)) {
740 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
741 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
745 MOZ_ASSERT(IsValidDuration(duration
));
752 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
753 * milliseconds, microseconds, nanoseconds )
755 bool js::temporal::ThrowIfInvalidDuration(JSContext
* cx
,
756 const DateDuration
& duration
) {
757 const auto& [years
, months
, weeks
, days
] = duration
;
760 int32_t sign
= DurationSign(duration
);
762 auto throwIfInvalid
= [&](int64_t v
, const char* name
) {
763 // Step 2.a. (Not applicable)
766 if ((v
< 0 && sign
> 0) || (v
> 0 && sign
< 0)) {
767 return ThrowInvalidDurationPart(cx
, double(v
), name
,
768 JSMSG_TEMPORAL_DURATION_INVALID_SIGN
);
774 auto throwIfTooLarge
= [&](int64_t v
, const char* name
) {
775 if (std::abs(v
) >= (int64_t(1) << 32)) {
776 return ThrowInvalidDurationPart(
777 cx
, double(v
), name
, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE
);
783 if (!throwIfInvalid(years
, "years")) {
786 if (!throwIfInvalid(months
, "months")) {
789 if (!throwIfInvalid(weeks
, "weeks")) {
792 if (!throwIfInvalid(days
, "days")) {
797 if (!throwIfTooLarge(years
, "years")) {
802 if (!throwIfTooLarge(months
, "months")) {
807 if (!throwIfTooLarge(weeks
, "weeks")) {
812 if (std::abs(days
) > ((int64_t(1) << 53) / 86400)) {
813 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
814 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
818 MOZ_ASSERT(IsValidDuration(duration
));
825 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
826 * seconds, milliseconds, microseconds )
828 static TemporalUnit
DefaultTemporalLargestUnit(const Duration
& duration
) {
829 MOZ_ASSERT(IsIntegerDuration(duration
));
832 if (duration
.years
!= 0) {
833 return TemporalUnit::Year
;
837 if (duration
.months
!= 0) {
838 return TemporalUnit::Month
;
842 if (duration
.weeks
!= 0) {
843 return TemporalUnit::Week
;
847 if (duration
.days
!= 0) {
848 return TemporalUnit::Day
;
852 if (duration
.hours
!= 0) {
853 return TemporalUnit::Hour
;
857 if (duration
.minutes
!= 0) {
858 return TemporalUnit::Minute
;
862 if (duration
.seconds
!= 0) {
863 return TemporalUnit::Second
;
867 if (duration
.milliseconds
!= 0) {
868 return TemporalUnit::Millisecond
;
872 if (duration
.microseconds
!= 0) {
873 return TemporalUnit::Microsecond
;
877 return TemporalUnit::Nanosecond
;
881 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
882 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
884 static DurationObject
* CreateTemporalDuration(JSContext
* cx
,
885 const CallArgs
& args
,
886 const Duration
& duration
) {
887 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
888 milliseconds
, microseconds
, nanoseconds
] = duration
;
891 if (!ThrowIfInvalidDuration(cx
, duration
)) {
896 Rooted
<JSObject
*> proto(cx
);
897 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Duration
, &proto
)) {
901 auto* object
= NewObjectWithClassProto
<DurationObject
>(cx
, proto
);
907 // Add zero to convert -0 to +0.
908 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
909 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
910 NumberValue(months
+ (+0.0)));
911 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
912 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
913 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
914 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
915 NumberValue(minutes
+ (+0.0)));
916 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
917 NumberValue(seconds
+ (+0.0)));
918 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
919 NumberValue(milliseconds
+ (+0.0)));
920 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
921 NumberValue(microseconds
+ (+0.0)));
922 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
923 NumberValue(nanoseconds
+ (+0.0)));
930 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
931 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
933 DurationObject
* js::temporal::CreateTemporalDuration(JSContext
* cx
,
934 const Duration
& duration
) {
935 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
936 milliseconds
, microseconds
, nanoseconds
] = duration
;
938 MOZ_ASSERT(IsInteger(years
));
939 MOZ_ASSERT(IsInteger(months
));
940 MOZ_ASSERT(IsInteger(weeks
));
941 MOZ_ASSERT(IsInteger(days
));
942 MOZ_ASSERT(IsInteger(hours
));
943 MOZ_ASSERT(IsInteger(minutes
));
944 MOZ_ASSERT(IsInteger(seconds
));
945 MOZ_ASSERT(IsInteger(milliseconds
));
946 MOZ_ASSERT(IsInteger(microseconds
));
947 MOZ_ASSERT(IsInteger(nanoseconds
));
950 if (!ThrowIfInvalidDuration(cx
, duration
)) {
955 auto* object
= NewBuiltinClassInstance
<DurationObject
>(cx
);
961 // Add zero to convert -0 to +0.
962 object
->setFixedSlot(DurationObject::YEARS_SLOT
, NumberValue(years
+ (+0.0)));
963 object
->setFixedSlot(DurationObject::MONTHS_SLOT
,
964 NumberValue(months
+ (+0.0)));
965 object
->setFixedSlot(DurationObject::WEEKS_SLOT
, NumberValue(weeks
+ (+0.0)));
966 object
->setFixedSlot(DurationObject::DAYS_SLOT
, NumberValue(days
+ (+0.0)));
967 object
->setFixedSlot(DurationObject::HOURS_SLOT
, NumberValue(hours
+ (+0.0)));
968 object
->setFixedSlot(DurationObject::MINUTES_SLOT
,
969 NumberValue(minutes
+ (+0.0)));
970 object
->setFixedSlot(DurationObject::SECONDS_SLOT
,
971 NumberValue(seconds
+ (+0.0)));
972 object
->setFixedSlot(DurationObject::MILLISECONDS_SLOT
,
973 NumberValue(milliseconds
+ (+0.0)));
974 object
->setFixedSlot(DurationObject::MICROSECONDS_SLOT
,
975 NumberValue(microseconds
+ (+0.0)));
976 object
->setFixedSlot(DurationObject::NANOSECONDS_SLOT
,
977 NumberValue(nanoseconds
+ (+0.0)));
984 * ToIntegerIfIntegral ( argument )
986 static bool ToIntegerIfIntegral(JSContext
* cx
, const char* name
,
987 Handle
<Value
> argument
, double* num
) {
990 if (!JS::ToNumber(cx
, argument
, &d
)) {
995 if (!js::IsInteger(d
)) {
997 const char* numStr
= NumberToCString(&cbuf
, d
);
999 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1000 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
1011 * ToIntegerIfIntegral ( argument )
1013 static bool ToIntegerIfIntegral(JSContext
* cx
, Handle
<PropertyName
*> name
,
1014 Handle
<Value
> argument
, double* result
) {
1017 if (!JS::ToNumber(cx
, argument
, &d
)) {
1022 if (!js::IsInteger(d
)) {
1023 if (auto nameStr
= js::QuoteString(cx
, name
)) {
1025 const char* numStr
= NumberToCString(&cbuf
, d
);
1027 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1028 JSMSG_TEMPORAL_DURATION_NOT_INTEGER
, numStr
,
1040 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1042 static bool ToTemporalPartialDurationRecord(
1043 JSContext
* cx
, Handle
<JSObject
*> temporalDurationLike
, Duration
* result
) {
1044 // Steps 1-3. (Not applicable in our implementation.)
1046 Rooted
<Value
> value(cx
);
1049 auto getDurationProperty
= [&](Handle
<PropertyName
*> name
, double* num
) {
1050 if (!GetProperty(cx
, temporalDurationLike
, temporalDurationLike
, name
,
1055 if (!value
.isUndefined()) {
1058 if (!ToIntegerIfIntegral(cx
, name
, value
, num
)) {
1066 if (!getDurationProperty(cx
->names().days
, &result
->days
)) {
1069 if (!getDurationProperty(cx
->names().hours
, &result
->hours
)) {
1072 if (!getDurationProperty(cx
->names().microseconds
, &result
->microseconds
)) {
1075 if (!getDurationProperty(cx
->names().milliseconds
, &result
->milliseconds
)) {
1078 if (!getDurationProperty(cx
->names().minutes
, &result
->minutes
)) {
1081 if (!getDurationProperty(cx
->names().months
, &result
->months
)) {
1084 if (!getDurationProperty(cx
->names().nanoseconds
, &result
->nanoseconds
)) {
1087 if (!getDurationProperty(cx
->names().seconds
, &result
->seconds
)) {
1090 if (!getDurationProperty(cx
->names().weeks
, &result
->weeks
)) {
1093 if (!getDurationProperty(cx
->names().years
, &result
->years
)) {
1099 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1100 JSMSG_TEMPORAL_DURATION_MISSING_UNIT
);
1109 * ToTemporalDurationRecord ( temporalDurationLike )
1111 bool js::temporal::ToTemporalDurationRecord(JSContext
* cx
,
1112 Handle
<Value
> temporalDurationLike
,
1115 if (!temporalDurationLike
.isObject()) {
1117 if (!temporalDurationLike
.isString()) {
1118 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
,
1119 temporalDurationLike
, nullptr, "not a string");
1122 Rooted
<JSString
*> string(cx
, temporalDurationLike
.toString());
1125 return ParseTemporalDurationString(cx
, string
, result
);
1128 Rooted
<JSObject
*> durationLike(cx
, &temporalDurationLike
.toObject());
1131 if (auto* duration
= durationLike
->maybeUnwrapIf
<DurationObject
>()) {
1132 *result
= ToDuration(duration
);
1137 Duration duration
= {};
1140 if (!ToTemporalPartialDurationRecord(cx
, durationLike
, &duration
)) {
1145 if (!ThrowIfInvalidDuration(cx
, duration
)) {
1155 * ToTemporalDuration ( item )
1157 Wrapped
<DurationObject
*> js::temporal::ToTemporalDuration(JSContext
* cx
,
1158 Handle
<Value
> item
) {
1160 if (item
.isObject()) {
1161 JSObject
* itemObj
= &item
.toObject();
1162 if (itemObj
->canUnwrapAs
<DurationObject
>()) {
1169 if (!ToTemporalDurationRecord(cx
, item
, &result
)) {
1174 return CreateTemporalDuration(cx
, result
);
1178 * ToTemporalDuration ( item )
1180 bool js::temporal::ToTemporalDuration(JSContext
* cx
, Handle
<Value
> item
,
1182 auto obj
= ToTemporalDuration(cx
, item
);
1187 *result
= ToDuration(&obj
.unwrap());
1192 * DaysUntil ( earlier, later )
1194 int32_t js::temporal::DaysUntil(const PlainDate
& earlier
,
1195 const PlainDate
& later
) {
1196 MOZ_ASSERT(ISODateTimeWithinLimits(earlier
));
1197 MOZ_ASSERT(ISODateTimeWithinLimits(later
));
1200 int32_t epochDaysEarlier
= MakeDay(earlier
);
1201 MOZ_ASSERT(MinEpochDay
<= epochDaysEarlier
&&
1202 epochDaysEarlier
<= MaxEpochDay
);
1205 int32_t epochDaysLater
= MakeDay(later
);
1206 MOZ_ASSERT(MinEpochDay
<= epochDaysLater
&& epochDaysLater
<= MaxEpochDay
);
1209 return epochDaysLater
- epochDaysEarlier
;
1213 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1214 * microseconds, nanoseconds )
1216 static TimeDuration
CreateTimeDurationRecord(int64_t days
, int64_t hours
,
1217 int64_t minutes
, int64_t seconds
,
1218 int64_t milliseconds
,
1219 int64_t microseconds
,
1220 int64_t nanoseconds
) {
1222 MOZ_ASSERT(IsValidDuration(
1223 {0, 0, 0, double(days
), double(hours
), double(minutes
), double(seconds
),
1224 double(milliseconds
), double(microseconds
), double(nanoseconds
)}));
1226 // All values are safe integers, so we don't need to convert to `double` and
1227 // back for the `ℝ(𝔽(x))` conversion.
1228 MOZ_ASSERT(IsSafeInteger(days
));
1229 MOZ_ASSERT(IsSafeInteger(hours
));
1230 MOZ_ASSERT(IsSafeInteger(minutes
));
1231 MOZ_ASSERT(IsSafeInteger(seconds
));
1232 MOZ_ASSERT(IsSafeInteger(milliseconds
));
1233 MOZ_ASSERT(IsSafeInteger(microseconds
));
1234 MOZ_ASSERT(IsSafeInteger(nanoseconds
));
1243 double(microseconds
),
1244 double(nanoseconds
),
1249 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1250 * microseconds, nanoseconds )
1252 static TimeDuration
CreateTimeDurationRecord(int64_t milliseconds
,
1253 const Int128
& microseconds
,
1254 const Int128
& nanoseconds
) {
1256 MOZ_ASSERT(IsValidDuration({0, 0, 0, 0, 0, 0, 0, double(milliseconds
),
1257 double(microseconds
), double(nanoseconds
)}));
1261 0, 0, 0, 0, milliseconds
, double(microseconds
), double(nanoseconds
),
1266 * BalanceTimeDuration ( norm, largestUnit )
1268 TimeDuration
js::temporal::BalanceTimeDuration(
1269 const NormalizedTimeDuration
& duration
, TemporalUnit largestUnit
) {
1270 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1271 MOZ_ASSERT(largestUnit
<= TemporalUnit::Second
,
1272 "fallible fractional seconds units");
1274 auto [seconds
, nanoseconds
] = duration
.denormalize();
1279 int64_t minutes
= 0;
1280 int64_t milliseconds
= 0;
1281 int64_t microseconds
= 0;
1283 // Steps 2-3. (Not applicable in our implementation.)
1285 // We don't need to convert to positive numbers, because integer division
1286 // truncates and the %-operator has modulo semantics.
1289 switch (largestUnit
) {
1291 case TemporalUnit::Year
:
1292 case TemporalUnit::Month
:
1293 case TemporalUnit::Week
:
1294 case TemporalUnit::Day
: {
1296 microseconds
= nanoseconds
/ 1000;
1299 nanoseconds
= nanoseconds
% 1000;
1302 milliseconds
= microseconds
/ 1000;
1305 microseconds
= microseconds
% 1000;
1307 // Steps 4.e-f. (Not applicable)
1308 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1311 minutes
= seconds
/ 60;
1314 seconds
= seconds
% 60;
1317 hours
= minutes
/ 60;
1320 minutes
= minutes
% 60;
1332 case TemporalUnit::Hour
: {
1334 microseconds
= nanoseconds
/ 1000;
1337 nanoseconds
= nanoseconds
% 1000;
1340 milliseconds
= microseconds
/ 1000;
1343 microseconds
= microseconds
% 1000;
1345 // Steps 5.e-f. (Not applicable)
1346 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1349 minutes
= seconds
/ 60;
1352 seconds
= seconds
% 60;
1355 hours
= minutes
/ 60;
1358 minutes
= minutes
% 60;
1363 case TemporalUnit::Minute
: {
1365 microseconds
= nanoseconds
/ 1000;
1368 nanoseconds
= nanoseconds
% 1000;
1371 milliseconds
= microseconds
/ 1000;
1374 microseconds
= microseconds
% 1000;
1376 // Steps 6.e-f. (Not applicable)
1377 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1380 minutes
= seconds
/ 60;
1383 seconds
= seconds
% 60;
1389 case TemporalUnit::Second
: {
1391 microseconds
= nanoseconds
/ 1000;
1394 nanoseconds
= nanoseconds
% 1000;
1397 milliseconds
= microseconds
/ 1000;
1400 microseconds
= microseconds
% 1000;
1402 // Steps 7.e-f. (Not applicable)
1403 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1408 case TemporalUnit::Millisecond
:
1409 case TemporalUnit::Microsecond
:
1410 case TemporalUnit::Nanosecond
:
1411 case TemporalUnit::Auto
:
1412 MOZ_CRASH("Unexpected temporal unit");
1416 return CreateTimeDurationRecord(days
, hours
, minutes
, seconds
, milliseconds
,
1417 microseconds
, nanoseconds
);
1421 * BalanceTimeDuration ( norm, largestUnit )
1423 bool js::temporal::BalanceTimeDuration(JSContext
* cx
,
1424 const NormalizedTimeDuration
& duration
,
1425 TemporalUnit largestUnit
,
1426 TimeDuration
* result
) {
1427 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
1429 auto [seconds
, nanoseconds
] = duration
.denormalize();
1431 // Steps 1-3. (Not applicable in our implementation.)
1433 // We don't need to convert to positive numbers, because integer division
1434 // truncates and the %-operator has modulo semantics.
1437 switch (largestUnit
) {
1439 case TemporalUnit::Year
:
1440 case TemporalUnit::Month
:
1441 case TemporalUnit::Week
:
1442 case TemporalUnit::Day
:
1443 case TemporalUnit::Hour
:
1444 case TemporalUnit::Minute
:
1445 case TemporalUnit::Second
:
1446 *result
= BalanceTimeDuration(duration
, largestUnit
);
1450 case TemporalUnit::Millisecond
: {
1451 // The number of normalized seconds must not exceed `2**53 - 1`.
1452 constexpr auto limit
=
1453 (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second
);
1455 // The largest possible milliseconds value whose double representation
1456 // doesn't exceed the normalized seconds limit.
1457 constexpr auto max
= int64_t(0x7cff'ffff'ffff'fdff);
1459 // Assert |max| is the maximum allowed milliseconds value.
1460 static_assert(double(max
) < double(limit
));
1461 static_assert(double(max
+ 1) >= double(limit
));
1463 static_assert((NormalizedTimeDuration::max().seconds
+ 1) *
1464 ToMilliseconds(TemporalUnit::Second
) <=
1466 "total number duration milliseconds fits into int64");
1469 int64_t microseconds
= nanoseconds
/ 1000;
1472 nanoseconds
= nanoseconds
% 1000;
1475 int64_t milliseconds
= microseconds
/ 1000;
1476 MOZ_ASSERT(std::abs(milliseconds
) <= 999);
1479 microseconds
= microseconds
% 1000;
1482 (seconds
* ToMilliseconds(TemporalUnit::Second
)) + milliseconds
;
1483 if (std::abs(millis
) > max
) {
1484 JS_ReportErrorNumberASCII(
1485 cx
, GetErrorMessage
, nullptr,
1486 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
1491 *result
= CreateTimeDurationRecord(millis
, Int128
{microseconds
},
1492 Int128
{nanoseconds
});
1497 case TemporalUnit::Microsecond
: {
1498 // The number of normalized seconds must not exceed `2**53 - 1`.
1499 constexpr auto limit
= Uint128
{int64_t(1) << 53} *
1500 Uint128
{ToMicroseconds(TemporalUnit::Second
)};
1502 // The largest possible microseconds value whose double representation
1503 // doesn't exceed the normalized seconds limit.
1504 constexpr auto max
=
1505 (Uint128
{0x1e8} << 64) + Uint128
{0x47ff'ffff'fff7'ffff};
1506 static_assert(max
< limit
);
1508 // Assert |max| is the maximum allowed microseconds value.
1509 MOZ_ASSERT(double(max
) < double(limit
));
1510 MOZ_ASSERT(double(max
+ Uint128
{1}) >= double(limit
));
1513 int64_t microseconds
= nanoseconds
/ 1000;
1514 MOZ_ASSERT(std::abs(microseconds
) <= 999'999);
1517 nanoseconds
= nanoseconds
% 1000;
1520 (Int128
{seconds
} * Int128
{ToMicroseconds(TemporalUnit::Second
)}) +
1521 Int128
{microseconds
};
1522 if (micros
.abs() > max
) {
1523 JS_ReportErrorNumberASCII(
1524 cx
, GetErrorMessage
, nullptr,
1525 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
1530 *result
= CreateTimeDurationRecord(0, micros
, Int128
{nanoseconds
});
1535 case TemporalUnit::Nanosecond
: {
1536 // The number of normalized seconds must not exceed `2**53 - 1`.
1537 constexpr auto limit
= Uint128
{int64_t(1) << 53} *
1538 Uint128
{ToNanoseconds(TemporalUnit::Second
)};
1540 // The largest possible nanoseconds value whose double representation
1541 // doesn't exceed the normalized seconds limit.
1542 constexpr auto max
=
1543 (Uint128
{0x77359} << 64) + Uint128
{0x3fff'ffff'dfff'ffff};
1544 static_assert(max
< limit
);
1546 // Assert |max| is the maximum allowed nanoseconds value.
1547 MOZ_ASSERT(double(max
) < double(limit
));
1548 MOZ_ASSERT(double(max
+ Uint128
{1}) >= double(limit
));
1550 MOZ_ASSERT(std::abs(nanoseconds
) <= 999'999'999);
1553 (Int128
{seconds
} * Int128
{ToNanoseconds(TemporalUnit::Second
)}) +
1554 Int128
{nanoseconds
};
1555 if (nanos
.abs() > max
) {
1556 JS_ReportErrorNumberASCII(
1557 cx
, GetErrorMessage
, nullptr,
1558 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
1563 *result
= CreateTimeDurationRecord(0, Int128
{}, nanos
);
1567 case TemporalUnit::Auto
:
1570 MOZ_CRASH("Unexpected temporal unit");
1574 * UnbalanceDateDurationRelative ( years, months, weeks, days, plainRelativeTo,
1577 static bool UnbalanceDateDurationRelative(
1578 JSContext
* cx
, const DateDuration
& duration
,
1579 Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1580 Handle
<CalendarRecord
> calendar
, int64_t* result
) {
1581 MOZ_ASSERT(IsValidDuration(duration
));
1583 auto [years
, months
, weeks
, days
] = duration
;
1586 if (years
== 0 && months
== 0 && weeks
== 0) {
1593 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1596 auto yearsMonthsWeeksDuration
= DateDuration
{years
, months
, weeks
};
1600 CalendarDateAdd(cx
, calendar
, plainRelativeTo
, yearsMonthsWeeksDuration
);
1604 auto laterDate
= ToPlainDate(&later
.unwrap());
1606 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
1607 if (!unwrappedRelativeTo
) {
1610 auto relativeToDate
= ToPlainDate(unwrappedRelativeTo
);
1613 int32_t yearsMonthsWeeksInDay
= DaysUntil(relativeToDate
, laterDate
);
1616 *result
= days
+ yearsMonthsWeeksInDay
;
1620 static bool NumberToStringBuilder(JSContext
* cx
, double num
,
1621 JSStringBuilder
& sb
) {
1622 MOZ_ASSERT(IsInteger(num
));
1623 MOZ_ASSERT(num
>= 0);
1624 MOZ_ASSERT(num
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1628 const char* numStr
= NumberToCString(&cbuf
, num
, &length
);
1630 return sb
.append(numStr
, length
);
1633 static Duration
AbsoluteDuration(const Duration
& duration
) {
1635 std::abs(duration
.years
), std::abs(duration
.months
),
1636 std::abs(duration
.weeks
), std::abs(duration
.days
),
1637 std::abs(duration
.hours
), std::abs(duration
.minutes
),
1638 std::abs(duration
.seconds
), std::abs(duration
.milliseconds
),
1639 std::abs(duration
.microseconds
), std::abs(duration
.nanoseconds
),
1644 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
1646 [[nodiscard
]] static bool FormatFractionalSeconds(JSStringBuilder
& result
,
1647 int32_t subSecondNanoseconds
,
1648 Precision precision
) {
1649 MOZ_ASSERT(0 <= subSecondNanoseconds
&& subSecondNanoseconds
< 1'000'000'000);
1650 MOZ_ASSERT(precision
!= Precision::Minute());
1653 if (precision
== Precision::Auto()) {
1655 if (subSecondNanoseconds
== 0) {
1659 // Step 3. (Reordered)
1660 if (!result
.append('.')) {
1665 int32_t k
= 100'000'000;
1667 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
1670 subSecondNanoseconds
%= k
;
1672 } while (subSecondNanoseconds
);
1675 uint8_t p
= precision
.value();
1680 // Step 3. (Reordered)
1681 if (!result
.append('.')) {
1686 int32_t k
= 100'000'000;
1687 for (uint8_t i
= 0; i
< precision
.value(); i
++) {
1688 if (!result
.append(char('0' + (subSecondNanoseconds
/ k
)))) {
1691 subSecondNanoseconds
%= k
;
1700 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
1701 * normSeconds, precision )
1703 static JSString
* TemporalDurationToString(JSContext
* cx
,
1704 const Duration
& duration
,
1705 Precision precision
) {
1706 MOZ_ASSERT(IsValidDuration(duration
));
1707 MOZ_ASSERT(precision
!= Precision::Minute());
1709 // Fast path for zero durations.
1710 if (duration
== Duration
{} &&
1711 (precision
== Precision::Auto() || precision
.value() == 0)) {
1712 return NewStringCopyZ
<CanGC
>(cx
, "PT0S");
1715 // Convert to absolute values up front. This is okay to do, because when the
1716 // duration is valid, all components have the same sign.
1717 const auto& [years
, months
, weeks
, days
, hours
, minutes
, seconds
,
1718 milliseconds
, microseconds
, nanoseconds
] =
1719 AbsoluteDuration(duration
);
1721 // Years to seconds parts are all safe integers for valid durations.
1722 MOZ_ASSERT(years
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1723 MOZ_ASSERT(months
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1724 MOZ_ASSERT(weeks
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1725 MOZ_ASSERT(days
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1726 MOZ_ASSERT(hours
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1727 MOZ_ASSERT(minutes
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1728 MOZ_ASSERT(seconds
< DOUBLE_INTEGRAL_PRECISION_LIMIT
);
1730 auto secondsDuration
= NormalizeTimeDuration(0.0, 0.0, seconds
, milliseconds
,
1731 microseconds
, nanoseconds
);
1734 int32_t sign
= DurationSign(duration
);
1737 JSStringBuilder
result(cx
);
1739 // Step 13. (Reordered)
1741 if (!result
.append('-')) {
1746 // Step 14. (Reordered)
1747 if (!result
.append('P')) {
1753 if (!NumberToStringBuilder(cx
, years
, result
)) {
1756 if (!result
.append('Y')) {
1763 if (!NumberToStringBuilder(cx
, months
, result
)) {
1766 if (!result
.append('M')) {
1773 if (!NumberToStringBuilder(cx
, weeks
, result
)) {
1776 if (!result
.append('W')) {
1783 if (!NumberToStringBuilder(cx
, days
, result
)) {
1786 if (!result
.append('D')) {
1791 // Step 7. (Moved above)
1793 // Steps 10-11. (Reordered)
1794 bool zeroMinutesAndHigher
= years
== 0 && months
== 0 && weeks
== 0 &&
1795 days
== 0 && hours
== 0 && minutes
== 0;
1797 // Steps 8-9, 12, and 15.
1798 bool hasSecondsPart
= (secondsDuration
!= NormalizedTimeDuration
{}) ||
1799 zeroMinutesAndHigher
|| precision
!= Precision::Auto();
1800 if (hours
!= 0 || minutes
!= 0 || hasSecondsPart
) {
1801 // Step 15. (Reordered)
1802 if (!result
.append('T')) {
1808 if (!NumberToStringBuilder(cx
, hours
, result
)) {
1811 if (!result
.append('H')) {
1818 if (!NumberToStringBuilder(cx
, minutes
, result
)) {
1821 if (!result
.append('M')) {
1827 if (hasSecondsPart
) {
1829 if (!NumberToStringBuilder(cx
, double(secondsDuration
.seconds
), result
)) {
1834 if (!FormatFractionalSeconds(result
, secondsDuration
.nanoseconds
,
1840 if (!result
.append('S')) {
1846 // Steps 13-15. (Moved above)
1849 return result
.finishString();
1853 * GetTemporalRelativeToOption ( options )
1855 static bool GetTemporalRelativeToOption(
1856 JSContext
* cx
, Handle
<JSObject
*> options
,
1857 MutableHandle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
1858 MutableHandle
<ZonedDateTime
> zonedRelativeTo
,
1859 MutableHandle
<TimeZoneRecord
> timeZoneRecord
) {
1861 Rooted
<Value
> value(cx
);
1862 if (!GetProperty(cx
, options
, options
, cx
->names().relativeTo
, &value
)) {
1867 if (value
.isUndefined()) {
1868 plainRelativeTo
.set(nullptr);
1869 zonedRelativeTo
.set(ZonedDateTime
{});
1870 timeZoneRecord
.set(TimeZoneRecord
{});
1875 auto offsetBehaviour
= OffsetBehaviour::Option
;
1878 auto matchBehaviour
= MatchBehaviour::MatchExactly
;
1881 PlainDateTime dateTime
;
1882 Rooted
<CalendarValue
> calendar(cx
);
1883 Rooted
<TimeZoneValue
> timeZone(cx
);
1885 if (value
.isObject()) {
1886 Rooted
<JSObject
*> obj(cx
, &value
.toObject());
1889 if (auto* zonedDateTime
= obj
->maybeUnwrapIf
<ZonedDateTimeObject
>()) {
1890 auto instant
= ToInstant(zonedDateTime
);
1891 Rooted
<TimeZoneValue
> timeZone(cx
, zonedDateTime
->timeZone());
1892 Rooted
<CalendarValue
> calendar(cx
, zonedDateTime
->calendar());
1894 if (!timeZone
.wrap(cx
)) {
1897 if (!calendar
.wrap(cx
)) {
1902 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
1903 if (!CreateTimeZoneMethodsRecord(
1906 TimeZoneMethod::GetOffsetNanosecondsFor
,
1907 TimeZoneMethod::GetPossibleInstantsFor
,
1914 plainRelativeTo
.set(nullptr);
1915 zonedRelativeTo
.set(ZonedDateTime
{instant
, timeZone
, calendar
});
1916 timeZoneRecord
.set(timeZoneRec
);
1921 if (obj
->canUnwrapAs
<PlainDateObject
>()) {
1922 plainRelativeTo
.set(obj
);
1923 zonedRelativeTo
.set(ZonedDateTime
{});
1924 timeZoneRecord
.set(TimeZoneRecord
{});
1929 if (auto* dateTime
= obj
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
1930 auto plainDateTime
= ToPlainDate(dateTime
);
1932 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
1933 if (!calendar
.wrap(cx
)) {
1938 auto* plainDate
= CreateTemporalDate(cx
, plainDateTime
, calendar
);
1944 plainRelativeTo
.set(plainDate
);
1945 zonedRelativeTo
.set(ZonedDateTime
{});
1946 timeZoneRecord
.set(TimeZoneRecord
{});
1951 if (!GetTemporalCalendarWithISODefault(cx
, obj
, &calendar
)) {
1956 Rooted
<CalendarRecord
> calendarRec(cx
);
1957 if (!CreateCalendarMethodsRecord(cx
, calendar
,
1959 CalendarMethod::DateFromFields
,
1960 CalendarMethod::Fields
,
1967 Rooted
<PlainObject
*> fields(
1968 cx
, PrepareCalendarFields(cx
, calendarRec
, obj
,
1971 CalendarField::Month
,
1972 CalendarField::MonthCode
,
1973 CalendarField::Year
,
1976 TemporalField::Hour
,
1977 TemporalField::Microsecond
,
1978 TemporalField::Millisecond
,
1979 TemporalField::Minute
,
1980 TemporalField::Nanosecond
,
1981 TemporalField::Offset
,
1982 TemporalField::Second
,
1983 TemporalField::TimeZone
,
1990 Rooted
<PlainObject
*> dateOptions(cx
, NewPlainObjectWithProto(cx
, nullptr));
1996 Rooted
<Value
> overflow(cx
, StringValue(cx
->names().constrain
));
1997 if (!DefineDataProperty(cx
, dateOptions
, cx
->names().overflow
, overflow
)) {
2002 if (!InterpretTemporalDateTimeFields(cx
, calendarRec
, fields
, dateOptions
,
2008 Rooted
<Value
> offset(cx
);
2009 if (!GetProperty(cx
, fields
, fields
, cx
->names().offset
, &offset
)) {
2014 Rooted
<Value
> timeZoneValue(cx
);
2015 if (!GetProperty(cx
, fields
, fields
, cx
->names().timeZone
,
2021 if (!timeZoneValue
.isUndefined()) {
2022 if (!ToTemporalTimeZone(cx
, timeZoneValue
, &timeZone
)) {
2028 if (offset
.isUndefined()) {
2029 offsetBehaviour
= OffsetBehaviour::Wall
;
2034 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2035 MOZ_ASSERT(!offset
.isUndefined());
2036 MOZ_ASSERT(offset
.isString());
2039 Rooted
<JSString
*> offsetString(cx
, offset
.toString());
2040 if (!offsetString
) {
2045 if (!ParseDateTimeUTCOffset(cx
, offsetString
, &offsetNs
)) {
2055 if (!value
.isString()) {
2056 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, value
,
2057 nullptr, "not a string");
2060 Rooted
<JSString
*> string(cx
, value
.toString());
2065 int64_t timeZoneOffset
;
2066 Rooted
<ParsedTimeZone
> timeZoneAnnotation(cx
);
2067 Rooted
<JSString
*> calendarString(cx
);
2068 if (!ParseTemporalRelativeToString(cx
, string
, &dateTime
, &isUTC
,
2069 &hasOffset
, &timeZoneOffset
,
2070 &timeZoneAnnotation
, &calendarString
)) {
2074 // Step 6.c. (Not applicable in our implementation.)
2077 if (timeZoneAnnotation
) {
2079 if (!ToTemporalTimeZone(cx
, timeZoneAnnotation
, &timeZone
)) {
2083 // Steps 6.f.ii-iii.
2085 offsetBehaviour
= OffsetBehaviour::Exact
;
2086 } else if (!hasOffset
) {
2087 offsetBehaviour
= OffsetBehaviour::Wall
;
2091 matchBehaviour
= MatchBehaviour::MatchMinutes
;
2093 MOZ_ASSERT(!timeZone
);
2097 if (calendarString
) {
2098 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
2102 calendar
.set(CalendarValue(CalendarId::ISO8601
));
2107 if (offsetBehaviour
== OffsetBehaviour::Option
) {
2108 MOZ_ASSERT(hasOffset
);
2111 offsetNs
= timeZoneOffset
;
2122 auto* plainDate
= CreateTemporalDate(cx
, dateTime
.date
, calendar
);
2127 plainRelativeTo
.set(plainDate
);
2128 zonedRelativeTo
.set(ZonedDateTime
{});
2129 timeZoneRecord
.set(TimeZoneRecord
{});
2133 // Steps 8-9. (Moved above)
2136 Rooted
<TimeZoneRecord
> timeZoneRec(cx
);
2137 if (!CreateTimeZoneMethodsRecord(cx
, timeZone
,
2139 TimeZoneMethod::GetOffsetNanosecondsFor
,
2140 TimeZoneMethod::GetPossibleInstantsFor
,
2147 Instant epochNanoseconds
;
2148 if (!InterpretISODateTimeOffset(
2149 cx
, dateTime
, offsetBehaviour
, offsetNs
, timeZoneRec
,
2150 TemporalDisambiguation::Compatible
, TemporalOffset::Reject
,
2151 matchBehaviour
, &epochNanoseconds
)) {
2154 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds
));
2157 plainRelativeTo
.set(nullptr);
2158 zonedRelativeTo
.set(ZonedDateTime
{epochNanoseconds
, timeZone
, calendar
});
2159 timeZoneRecord
.set(timeZoneRec
);
2164 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
2167 static bool CreateCalendarMethodsRecordFromRelativeTo(
2168 JSContext
* cx
, Handle
<Wrapped
<PlainDateObject
*>> plainRelativeTo
,
2169 Handle
<ZonedDateTime
> zonedRelativeTo
,
2170 mozilla::EnumSet
<CalendarMethod
> methods
,
2171 MutableHandle
<CalendarRecord
> result
) {
2173 if (zonedRelativeTo
) {
2174 return CreateCalendarMethodsRecord(cx
, zonedRelativeTo
.calendar(), methods
,
2179 if (plainRelativeTo
) {
2180 auto* unwrapped
= plainRelativeTo
.unwrap(cx
);
2185 Rooted
<CalendarValue
> calendar(cx
, unwrapped
->calendar());
2186 if (!calendar
.wrap(cx
)) {
2190 return CreateCalendarMethodsRecord(cx
, calendar
, methods
, result
);
2198 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
2200 static NormalizedTimeDuration
RoundNormalizedTimeDurationToIncrement(
2201 const NormalizedTimeDuration
& duration
, const TemporalUnit unit
,
2202 Increment increment
, TemporalRoundingMode roundingMode
) {
2203 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
2204 MOZ_ASSERT(unit
>= TemporalUnit::Day
);
2205 MOZ_ASSERT_IF(unit
>= TemporalUnit::Hour
,
2206 increment
<= MaximumTemporalDurationRoundingIncrement(unit
));
2208 auto divisor
= Int128
{ToNanoseconds(unit
)} * Int128
{increment
.value()};
2209 MOZ_ASSERT(divisor
> Int128
{0});
2210 MOZ_ASSERT_IF(unit
>= TemporalUnit::Hour
,
2211 divisor
<= Int128
{ToNanoseconds(TemporalUnit::Day
)});
2213 auto totalNanoseconds
= duration
.toNanoseconds();
2215 RoundNumberToIncrement(totalNanoseconds
, divisor
, roundingMode
);
2216 return NormalizedTimeDuration::fromNanoseconds(rounded
);
2220 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
2222 static bool RoundNormalizedTimeDurationToIncrement(
2223 JSContext
* cx
, const NormalizedTimeDuration
& duration
,
2224 const TemporalUnit unit
, Increment increment
,
2225 TemporalRoundingMode roundingMode
, NormalizedTimeDuration
* result
) {
2227 auto rounded
= RoundNormalizedTimeDurationToIncrement(
2228 duration
, unit
, increment
, roundingMode
);
2231 if (!IsValidNormalizedTimeDuration(rounded
)) {
2232 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2233 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
2243 * DivideNormalizedTimeDuration ( d, divisor )
2245 double js::temporal::DivideNormalizedTimeDuration(
2246 const NormalizedTimeDuration
& duration
, TemporalUnit unit
) {
2247 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
2248 MOZ_ASSERT(unit
>= TemporalUnit::Day
);
2250 auto numerator
= duration
.toNanoseconds();
2251 auto denominator
= Int128
{ToNanoseconds(unit
)};
2252 return FractionToDouble(numerator
, denominator
);
2255 enum class ComputeRemainder
: bool { No
, Yes
};
2258 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
2259 static constexpr int64_t MaxDurationDays
= (int64_t(1) << 53) / (24 * 60 * 60);
2262 struct FractionalDays final
{
2266 explicit FractionalDays(const NormalizedDuration
& duration
) {
2267 MOZ_ASSERT(IsValidDuration(duration
));
2269 auto [seconds
, nanoseconds
] = duration
.time
.denormalize();
2271 int64_t days
= seconds
/ ToSeconds(TemporalUnit::Day
);
2272 seconds
= seconds
% ToSeconds(TemporalUnit::Day
);
2274 int64_t time
= seconds
* ToNanoseconds(TemporalUnit::Second
) + nanoseconds
;
2275 MOZ_ASSERT(std::abs(time
) < ToNanoseconds(TemporalUnit::Day
));
2277 days
+= duration
.date
.days
;
2278 MOZ_ASSERT(std::abs(days
) <= MaxDurationDays
);
2285 struct RoundedDays final
{
2286 int64_t rounded
= 0;
2290 static RoundedDays
RoundNumberToIncrement(const FractionalDays
& fractionalDays
,
2291 Increment increment
,
2292 TemporalRoundingMode roundingMode
,
2293 ComputeRemainder computeRemainder
) {
2294 MOZ_ASSERT(std::abs(fractionalDays
.days
) <= MaxDurationDays
);
2295 MOZ_ASSERT(std::abs(fractionalDays
.time
) < ToNanoseconds(TemporalUnit::Day
));
2296 MOZ_ASSERT(increment
<= Increment::max());
2298 constexpr int64_t dayLength
= ToNanoseconds(TemporalUnit::Day
);
2300 // Fast-path when no time components are present. Multiplying and later
2301 // dividing by |dayLength| cancel each other out.
2302 if (fractionalDays
.time
== 0) {
2303 int64_t totalDays
= fractionalDays
.days
;
2305 if (computeRemainder
== ComputeRemainder::Yes
) {
2306 constexpr int64_t rounded
= 0;
2307 double total
= FractionToDouble(totalDays
, 1);
2308 return {rounded
, total
};
2312 RoundNumberToIncrement(totalDays
, 1, increment
, roundingMode
);
2313 MOZ_ASSERT(Int128
{INT64_MIN
} <= rounded
&& rounded
<= Int128
{INT64_MAX
},
2314 "rounded days fits in int64");
2315 constexpr double total
= 0;
2316 return {int64_t(rounded
), total
};
2319 // Fast-path when |totalNanoseconds| fits into int64.
2321 auto totalNanoseconds
=
2322 mozilla::CheckedInt64(dayLength
) * fractionalDays
.days
;
2323 totalNanoseconds
+= fractionalDays
.time
;
2324 if (!totalNanoseconds
.isValid()) {
2328 if (computeRemainder
== ComputeRemainder::Yes
) {
2329 constexpr int64_t rounded
= 0;
2330 double total
= FractionToDouble(totalNanoseconds
.value(), dayLength
);
2331 return {rounded
, total
};
2334 auto rounded
= RoundNumberToIncrement(totalNanoseconds
.value(), dayLength
,
2335 increment
, roundingMode
);
2336 MOZ_ASSERT(Int128
{INT64_MIN
} <= rounded
&& rounded
<= Int128
{INT64_MAX
},
2337 "rounded days fits in int64");
2338 constexpr double total
= 0;
2339 return {int64_t(rounded
), total
};
2342 auto totalNanoseconds
= Int128
{dayLength
} * Int128
{fractionalDays
.days
};
2343 totalNanoseconds
+= Int128
{fractionalDays
.time
};
2345 if (computeRemainder
== ComputeRemainder::Yes
) {
2346 constexpr int64_t rounded
= 0;
2347 double total
= FractionToDouble(totalNanoseconds
, Int128
{dayLength
});
2348 return {rounded
, total
};
2351 auto rounded
= RoundNumberToIncrement(totalNanoseconds
, Int128
{dayLength
},
2352 increment
, roundingMode
);
2353 MOZ_ASSERT(Int128
{INT64_MIN
} <= rounded
&& rounded
<= Int128
{INT64_MAX
},
2354 "rounded days fits in int64");
2355 constexpr double total
= 0;
2356 return {int64_t(rounded
), total
};
2359 struct RoundedDuration final
{
2360 NormalizedDuration duration
;
2365 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2367 static RoundedDuration
RoundTimeDuration(const NormalizedDuration
& duration
,
2368 Increment increment
, TemporalUnit unit
,
2369 TemporalRoundingMode roundingMode
,
2370 ComputeRemainder computeRemainder
) {
2371 MOZ_ASSERT(IsValidDuration(duration
));
2372 MOZ_ASSERT(unit
> TemporalUnit::Day
);
2374 // The remainder is only needed when called from |Duration_total|. And `total`
2375 // always passes |increment=1| and |roundingMode=trunc|.
2376 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
2377 increment
== Increment
{1});
2378 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
2379 roundingMode
== TemporalRoundingMode::Trunc
);
2382 MOZ_ASSERT(unit
> TemporalUnit::Day
);
2384 // Step 2. (Not applicable)
2387 NormalizedTimeDuration time
;
2389 if (computeRemainder
== ComputeRemainder::No
) {
2390 time
= RoundNormalizedTimeDurationToIncrement(duration
.time
, unit
,
2391 increment
, roundingMode
);
2393 total
= DivideNormalizedTimeDuration(duration
.time
, unit
);
2397 return {NormalizedDuration
{duration
.date
, time
}, total
};
2401 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2403 static bool RoundTimeDuration(JSContext
* cx
, const NormalizedDuration
& duration
,
2404 Increment increment
, TemporalUnit unit
,
2405 TemporalRoundingMode roundingMode
,
2406 ComputeRemainder computeRemainder
,
2407 RoundedDuration
* result
) {
2408 MOZ_ASSERT(IsValidDuration(duration
));
2410 // The remainder is only needed when called from |Duration_total|. And `total`
2411 // always passes |increment=1| and |roundingMode=trunc|.
2412 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
2413 increment
== Increment
{1});
2414 MOZ_ASSERT_IF(computeRemainder
== ComputeRemainder::Yes
,
2415 roundingMode
== TemporalRoundingMode::Trunc
);
2418 MOZ_ASSERT(unit
>= TemporalUnit::Day
);
2421 if (unit
== TemporalUnit::Day
) {
2423 auto fractionalDays
= FractionalDays
{duration
};
2426 auto [days
, total
] = RoundNumberToIncrement(fractionalDays
, increment
,
2427 roundingMode
, computeRemainder
);
2430 constexpr auto time
= NormalizedTimeDuration
{};
2433 auto date
= DateDuration
{0, 0, 0, days
};
2434 if (!ThrowIfInvalidDuration(cx
, date
)) {
2438 auto normalized
= NormalizedDuration
{date
, time
};
2439 MOZ_ASSERT(IsValidDuration(normalized
));
2441 *result
= {normalized
, total
};
2446 auto rounded
= RoundTimeDuration(duration
, increment
, unit
, roundingMode
,
2448 if (!IsValidNormalizedTimeDuration(rounded
.duration
.time
)) {
2449 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2450 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME
);
2453 MOZ_ASSERT(IsValidDuration(rounded
.duration
));
2461 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2463 static bool RoundTimeDuration(JSContext
* cx
,
2464 const NormalizedTimeDuration
& duration
,
2465 Increment increment
, TemporalUnit unit
,
2466 TemporalRoundingMode roundingMode
,
2467 NormalizedTimeDuration
* result
) {
2468 auto normalized
= NormalizedDuration
{{}, duration
};
2470 RoundedDuration rounded
;
2471 if (!RoundTimeDuration(cx
, normalized
, increment
, unit
, roundingMode
,
2472 ComputeRemainder::No
, &rounded
)) {
2475 *result
= rounded
.duration
.time
;
2480 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2482 NormalizedTimeDuration
js::temporal::RoundTimeDuration(
2483 const NormalizedTimeDuration
& duration
, Increment increment
,
2484 TemporalUnit unit
, TemporalRoundingMode roundingMode
) {
2485 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration
));
2486 MOZ_ASSERT(unit
> TemporalUnit::Day
);
2488 auto normalized
= NormalizedDuration
{{}, duration
};
2489 auto result
= ::RoundTimeDuration(normalized
, increment
, unit
, roundingMode
,
2490 ComputeRemainder::No
);
2491 MOZ_ASSERT(IsValidNormalizedTimeDuration(result
.duration
.time
));
2493 return result
.duration
.time
;
2496 enum class UnsignedRoundingMode
{
2505 * GetUnsignedRoundingMode ( roundingMode, sign )
2507 static UnsignedRoundingMode
GetUnsignedRoundingMode(
2508 TemporalRoundingMode roundingMode
, bool isNegative
) {
2509 switch (roundingMode
) {
2510 case TemporalRoundingMode::Ceil
:
2511 return isNegative
? UnsignedRoundingMode::Zero
2512 : UnsignedRoundingMode::Infinity
;
2513 case TemporalRoundingMode::Floor
:
2514 return isNegative
? UnsignedRoundingMode::Infinity
2515 : UnsignedRoundingMode::Zero
;
2516 case TemporalRoundingMode::Expand
:
2517 return UnsignedRoundingMode::Infinity
;
2518 case TemporalRoundingMode::Trunc
:
2519 return UnsignedRoundingMode::Zero
;
2520 case TemporalRoundingMode::HalfCeil
:
2521 return isNegative
? UnsignedRoundingMode::HalfZero
2522 : UnsignedRoundingMode::HalfInfinity
;
2523 case TemporalRoundingMode::HalfFloor
:
2524 return isNegative
? UnsignedRoundingMode::HalfInfinity
2525 : UnsignedRoundingMode::HalfZero
;
2526 case TemporalRoundingMode::HalfExpand
:
2527 return UnsignedRoundingMode::HalfInfinity
;
2528 case TemporalRoundingMode::HalfTrunc
:
2529 return UnsignedRoundingMode::HalfZero
;
2530 case TemporalRoundingMode::HalfEven
:
2531 return UnsignedRoundingMode::HalfEven
;
2533 MOZ_CRASH("invalid rounding mode");
2536 struct DurationNudge
{
2537 NormalizedDuration duration
;
2540 bool didExpandCalendarUnit
= false;
2544 * NudgeToCalendarUnit ( sign, duration, destEpochNs, dateTime, calendarRec,
2545 * timeZoneRec, increment, unit, roundingMode )
2547 static bool NudgeToCalendarUnit(
2548 JSContext
* cx
, const NormalizedDuration
& duration
,
2549 const Instant
& destEpochNs
, const PlainDateTime
& dateTime
,
2550 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
2551 Increment increment
, TemporalUnit unit
, TemporalRoundingMode roundingMode
,
2552 DurationNudge
* result
) {
2553 MOZ_ASSERT(IsValidDuration(duration
));
2554 MOZ_ASSERT(IsValidEpochInstant(destEpochNs
));
2555 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
2556 MOZ_ASSERT(unit
<= TemporalUnit::Day
);
2558 int32_t sign
= DurationSign(duration
) < 0 ? -1 : 1;
2563 DateDuration startDuration
;
2564 DateDuration endDuration
;
2565 if (unit
== TemporalUnit::Year
) {
2567 int64_t years
= RoundNumberToIncrement(duration
.date
.years
, increment
,
2568 TemporalRoundingMode::Trunc
);
2574 r2
= years
+ int64_t(increment
.value()) * sign
;
2577 startDuration
= {r1
};
2581 } else if (unit
== TemporalUnit::Month
) {
2583 int64_t months
= RoundNumberToIncrement(duration
.date
.months
, increment
,
2584 TemporalRoundingMode::Trunc
);
2590 r2
= months
+ int64_t(increment
.value()) * sign
;
2593 startDuration
= {duration
.date
.years
, r1
};
2596 endDuration
= {duration
.date
.years
, r2
};
2597 } else if (unit
== TemporalUnit::Week
) {
2598 // FIXME: spec bug - CreateTemporalDate is fallible. Also possibly incorrect
2599 // to call BalanceISODate. Just use AddDate for now.
2600 // https://github.com/tc39/proposal-temporal/issues/2881
2602 // Steps 3.a and 3.c.
2603 PlainDate weeksStart
;
2604 if (!AddDate(cx
, calendar
, dateTime
.date
,
2605 {duration
.date
.years
, duration
.date
.months
}, &weeksStart
)) {
2609 // Steps 3.b and 3.d.
2612 cx
, calendar
, dateTime
.date
,
2613 {duration
.date
.years
, duration
.date
.months
, 0, duration
.date
.days
},
2619 DateDuration untilResult
;
2620 if (!DifferenceDate(cx
, calendar
, weeksStart
, weeksEnd
, TemporalUnit::Week
,
2627 RoundNumberToIncrement(duration
.date
.weeks
+ untilResult
.weeks
,
2628 increment
, TemporalRoundingMode::Trunc
);
2634 r2
= weeks
+ int64_t(increment
.value()) * sign
;
2637 startDuration
= {duration
.date
.years
, duration
.date
.months
, r1
};
2640 endDuration
= {duration
.date
.years
, duration
.date
.months
, r2
};
2643 MOZ_ASSERT(unit
== TemporalUnit::Day
);
2646 int64_t days
= RoundNumberToIncrement(duration
.date
.days
, increment
,
2647 TemporalRoundingMode::Trunc
);
2653 r2
= days
+ int64_t(increment
.value()) * sign
;
2656 startDuration
= {duration
.date
.years
, duration
.date
.months
,
2657 duration
.date
.weeks
, r1
};
2660 endDuration
= {duration
.date
.years
, duration
.date
.months
,
2661 duration
.date
.weeks
, r2
};
2663 MOZ_ASSERT_IF(sign
> 0, r1
>= 0 && r1
< r2
);
2664 MOZ_ASSERT_IF(sign
< 0, r1
<= 0 && r1
> r2
);
2668 if (!AddDate(cx
, calendar
, dateTime
.date
, startDuration
, &start
)) {
2674 if (!AddDate(cx
, calendar
, dateTime
.date
, endDuration
, &end
)) {
2679 Instant startEpochNs
;
2681 if (!timeZone
.receiver()) {
2683 startEpochNs
= GetUTCEpochNanoseconds({start
, dateTime
.time
});
2686 endEpochNs
= GetUTCEpochNanoseconds({end
, dateTime
.time
});
2689 Rooted
<PlainDateTimeWithCalendar
> startDateTime(
2691 PlainDateTimeWithCalendar
{{start
, dateTime
.time
}, calendar
.receiver()});
2694 if (!GetInstantFor(cx
, timeZone
, startDateTime
,
2695 TemporalDisambiguation::Compatible
, &startEpochNs
)) {
2700 Rooted
<PlainDateTimeWithCalendar
> endDateTime(
2702 PlainDateTimeWithCalendar
{{end
, dateTime
.time
}, calendar
.receiver()});
2705 if (!GetInstantFor(cx
, timeZone
, endDateTime
,
2706 TemporalDisambiguation::Compatible
, &endEpochNs
)) {
2712 if (startEpochNs
> destEpochNs
|| destEpochNs
>= endEpochNs
) {
2713 JS_ReportErrorNumberASCII(
2714 cx
, GetErrorMessage
, nullptr,
2715 JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT
);
2718 MOZ_ASSERT(startEpochNs
<= destEpochNs
&& destEpochNs
< endEpochNs
);
2720 if (endEpochNs
>= destEpochNs
|| destEpochNs
> startEpochNs
) {
2721 JS_ReportErrorNumberASCII(
2722 cx
, GetErrorMessage
, nullptr,
2723 JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT
);
2726 MOZ_ASSERT(endEpochNs
< destEpochNs
&& destEpochNs
<= startEpochNs
);
2728 MOZ_ASSERT(startEpochNs
!= endEpochNs
);
2731 auto unsignedRoundingMode
= GetUnsignedRoundingMode(roundingMode
, sign
< 0);
2734 auto numerator
= (destEpochNs
- startEpochNs
).toNanoseconds();
2735 auto denominator
= (endEpochNs
- startEpochNs
).toNanoseconds();
2736 MOZ_ASSERT(denominator
!= Int128
{0});
2737 MOZ_ASSERT(numerator
.abs() < denominator
.abs());
2738 MOZ_ASSERT_IF(denominator
> Int128
{0}, numerator
>= Int128
{0});
2739 MOZ_ASSERT_IF(denominator
< Int128
{0}, numerator
<= Int128
{0});
2741 // Ensure |numerator| and |denominator| are both non-negative to simplify the
2742 // following computations.
2743 if (denominator
< Int128
{0}) {
2744 numerator
= -numerator
;
2745 denominator
= -denominator
;
2748 // |total| must only be computed when called from Duration.prototype.total,
2749 // which always passes "trunc" rounding mode with an increment of one.
2750 double total
= mozilla::UnspecifiedNaN
<double>();
2751 if (roundingMode
== TemporalRoundingMode::Trunc
&&
2752 increment
== Increment
{1}) {
2753 // total = r1 + progress × increment × sign
2754 // = r1 + (numerator / denominator) × increment × sign
2755 // = r1 + (numerator × increment × sign) / denominator
2756 // = (r1 × denominator + numerator × increment × sign) / denominator
2758 // Computing `n` can't overflow, because:
2759 // - For years, months, and weeks, `abs(r1) ≤ 2^32`.
2760 // - For days, `abs(r1) < ⌈(2^53) / (24 * 60 * 60)⌉`.
2761 // - `denominator` and `numerator` are below-or-equal `2 × 8.64 × 10^21`.
2762 // - And finally `increment ≤ 10^9`.
2763 auto n
= Int128
{r1
} * denominator
+ numerator
* Int128
{sign
};
2764 total
= FractionToDouble(n
, denominator
);
2767 // Step 15. (Inlined ApplyUnsignedRoundingMode)
2771 // ApplyUnsignedRoundingMode, steps 1-16.
2773 // `total = r1` iff `progress = 0`. And `progress = 0` iff `numerator = 0`.
2776 // = (r1 × denominator + numerator × increment × sign) / denominator - r1
2777 // = (numerator × increment × sign) / denominator
2780 // = r1 + increment - (r1 × denominator + numerator × increment × sign) / denominator
2781 // = (increment × denominator - numerator × increment × sign) / denominator
2784 // ⇔ (numerator × increment × sign) / denominator < (increment × denominator - numerator × increment × sign) / denominator
2785 // ⇔ (numerator × increment × sign) < (increment × denominator - numerator × increment × sign)
2786 // ⇔ (numerator × sign) < (denominator - numerator × sign)
2787 // ⇔ (2 × numerator × sign) < denominator
2789 // cardinality = (r1 / (r2 – r1)) modulo 2
2790 // = (r1 / (r1 + increment - r1)) modulo 2
2791 // = (r1 / increment) modulo 2
2794 bool didExpandCalendarUnit
;
2795 if (numerator
== Int128
{0}) {
2796 didExpandCalendarUnit
= false;
2797 } else if (unsignedRoundingMode
== UnsignedRoundingMode::Zero
) {
2798 didExpandCalendarUnit
= false;
2799 } else if (unsignedRoundingMode
== UnsignedRoundingMode::Infinity
) {
2800 didExpandCalendarUnit
= true;
2801 } else if (numerator
+ numerator
< denominator
) {
2802 didExpandCalendarUnit
= false;
2803 } else if (numerator
+ numerator
> denominator
) {
2804 didExpandCalendarUnit
= true;
2805 } else if (unsignedRoundingMode
== UnsignedRoundingMode::HalfZero
) {
2806 didExpandCalendarUnit
= false;
2807 } else if (unsignedRoundingMode
== UnsignedRoundingMode::HalfInfinity
) {
2808 didExpandCalendarUnit
= true;
2809 } else if ((r1
/ increment
.value()) % 2 == 0) {
2810 didExpandCalendarUnit
= false;
2812 didExpandCalendarUnit
= true;
2815 // FIXME: spec bug - zero progress case incorrect
2816 // https://github.com/tc39/proposal-temporal/issues/2893
2819 auto resultDuration
= didExpandCalendarUnit
? endDuration
: startDuration
;
2820 auto resultEpochNs
= didExpandCalendarUnit
? endEpochNs
: startEpochNs
;
2821 *result
= {{resultDuration
, {}}, resultEpochNs
, total
, didExpandCalendarUnit
};
2826 * NudgeToZonedTime ( sign, duration, dateTime, calendarRec, timeZoneRec,
2827 * increment, unit, roundingMode )
2829 static bool NudgeToZonedTime(JSContext
* cx
, const NormalizedDuration
& duration
,
2830 const PlainDateTime
& dateTime
,
2831 Handle
<CalendarRecord
> calendar
,
2832 Handle
<TimeZoneRecord
> timeZone
,
2833 Increment increment
, TemporalUnit unit
,
2834 TemporalRoundingMode roundingMode
,
2835 DurationNudge
* result
) {
2836 MOZ_ASSERT(IsValidDuration(duration
));
2837 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
2839 int32_t sign
= DurationSign(duration
) < 0 ? -1 : 1;
2842 MOZ_ASSERT(unit
>= TemporalUnit::Hour
);
2846 if (!AddDate(cx
, calendar
, dateTime
.date
, duration
.date
, &start
)) {
2851 Rooted
<PlainDateTimeWithCalendar
> startDateTime(
2853 PlainDateTimeWithCalendar
{{start
, dateTime
.time
}, calendar
.receiver()});
2854 MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime
));
2858 if (!BalanceISODate(cx
, start
, sign
, &end
)) {
2863 Rooted
<PlainDateTimeWithCalendar
> endDateTime(cx
);
2864 if (!CreateTemporalDateTime(cx
, {end
, dateTime
.time
}, calendar
.receiver(),
2870 Instant startEpochNs
;
2871 if (!GetInstantFor(cx
, timeZone
, startDateTime
,
2872 TemporalDisambiguation::Compatible
, &startEpochNs
)) {
2878 if (!GetInstantFor(cx
, timeZone
, endDateTime
,
2879 TemporalDisambiguation::Compatible
, &endEpochNs
)) {
2884 auto daySpan
= NormalizedTimeDurationFromEpochNanosecondsDifference(
2885 endEpochNs
, startEpochNs
);
2887 // FIXME: spec bug - how can this assert be valid for custom time zones?
2890 MOZ_ASSERT(NormalizedTimeDurationSign(daySpan
) == sign
);
2892 // FIXME: spec issue - Use DifferenceInstant?
2893 // FIXME: spec issue - Is this call really fallible?
2896 NormalizedTimeDuration roundedTime
;
2897 if (!RoundNormalizedTimeDurationToIncrement(
2898 cx
, duration
.time
, unit
, increment
, roundingMode
, &roundedTime
)) {
2903 NormalizedTimeDuration beyondDaySpan
;
2904 if (!SubtractNormalizedTimeDuration(cx
, roundedTime
, daySpan
,
2910 bool didRoundBeyondDay
;
2912 Instant nudgedEpochNs
;
2913 if (NormalizedTimeDurationSign(beyondDaySpan
) != -sign
) {
2915 didRoundBeyondDay
= true;
2921 if (!RoundNormalizedTimeDurationToIncrement(
2922 cx
, beyondDaySpan
, unit
, increment
, roundingMode
, &roundedTime
)) {
2926 // Step 15.d. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
2927 nudgedEpochNs
= endEpochNs
+ roundedTime
.to
<InstantSpan
>();
2930 didRoundBeyondDay
= false;
2935 // Step 16.c. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
2936 nudgedEpochNs
= startEpochNs
+ roundedTime
.to
<InstantSpan
>();
2940 NormalizedDuration resultDuration
;
2941 if (!CreateNormalizedDurationRecord(cx
,
2943 duration
.date
.years
,
2944 duration
.date
.months
,
2945 duration
.date
.weeks
,
2946 duration
.date
.days
+ dayDelta
,
2948 roundedTime
, &resultDuration
)) {
2956 mozilla::UnspecifiedNaN
<double>(),
2963 * NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment,
2964 * smallestUnit, roundingMode )
2966 static bool NudgeToDayOrTime(JSContext
* cx
, const NormalizedDuration
& duration
,
2967 const Instant
& destEpochNs
,
2968 TemporalUnit largestUnit
, Increment increment
,
2969 TemporalUnit smallestUnit
,
2970 TemporalRoundingMode roundingMode
,
2971 DurationNudge
* result
) {
2972 MOZ_ASSERT(IsValidDuration(duration
));
2973 MOZ_ASSERT(IsValidEpochInstant(destEpochNs
));
2975 // FIXME: spec bug - incorrect assertion
2976 // https://github.com/tc39/proposal-temporal/issues/2897
2979 MOZ_ASSERT(smallestUnit
>= TemporalUnit::Day
);
2982 NormalizedTimeDuration withDays
;
2983 if (!Add24HourDaysToNormalizedTimeDuration(cx
, duration
.time
,
2984 duration
.date
.days
, &withDays
)) {
2989 double total
= DivideNormalizedTimeDuration(withDays
, smallestUnit
);
2990 NormalizedTimeDuration roundedTime
;
2991 if (!RoundNormalizedTimeDurationToIncrement(
2992 cx
, withDays
, smallestUnit
, increment
, roundingMode
, &roundedTime
)) {
2997 NormalizedTimeDuration diffTime
;
2998 if (!SubtractNormalizedTimeDuration(cx
, roundedTime
, withDays
, &diffTime
)) {
3002 constexpr int64_t secPerDay
= ToSeconds(TemporalUnit::Day
);
3005 int64_t wholeDays
= withDays
.toSeconds() / secPerDay
;
3008 int64_t roundedWholeDays
= roundedTime
.toSeconds() / secPerDay
;
3011 int64_t dayDelta
= roundedWholeDays
- wholeDays
;
3014 int32_t dayDeltaSign
= dayDelta
< 0 ? -1 : dayDelta
> 0 ? 1 : 0;
3017 bool didExpandDays
= dayDeltaSign
== NormalizedTimeDurationSign(withDays
);
3019 // Step 13. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
3020 auto nudgedEpochNs
= destEpochNs
+ diffTime
.to
<InstantSpan
>();
3026 auto remainder
= roundedTime
;
3029 if (largestUnit
<= TemporalUnit::Day
) {
3031 days
= roundedWholeDays
;
3034 remainder
= roundedTime
- NormalizedTimeDuration::fromSeconds(
3035 roundedWholeDays
* secPerDay
);
3039 NormalizedDuration resultDuration
;
3040 if (!CreateNormalizedDurationRecord(cx
,
3042 duration
.date
.years
,
3043 duration
.date
.months
,
3044 duration
.date
.weeks
,
3047 remainder
, &resultDuration
)) {
3052 *result
= {resultDuration
, nudgedEpochNs
, total
, didExpandDays
};
3057 * BubbleRelativeDuration ( sign, duration, nudgedEpochNs, dateTime,
3058 * calendarRec, timeZoneRec, largestUnit, smallestUnit )
3060 static bool BubbleRelativeDuration(
3061 JSContext
* cx
, const NormalizedDuration
& duration
,
3062 const DurationNudge
& nudge
, const PlainDateTime
& dateTime
,
3063 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
3064 TemporalUnit largestUnit
, TemporalUnit smallestUnit
,
3065 NormalizedDuration
* result
) {
3066 MOZ_ASSERT(IsValidDuration(duration
));
3067 MOZ_ASSERT(IsValidDuration(nudge
.duration
));
3068 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
3069 MOZ_ASSERT(largestUnit
<= smallestUnit
);
3071 int32_t sign
= DurationSign(duration
) < 0 ? -1 : 1;
3074 MOZ_ASSERT(largestUnit
<= TemporalUnit::Day
);
3077 MOZ_ASSERT(smallestUnit
<= TemporalUnit::Day
);
3079 // FIXME: spec issue - directly return when `smallestUnit == largestUnit`.
3080 // https://github.com/tc39/proposal-temporal/issues/2890
3083 if (smallestUnit
== largestUnit
) {
3084 *result
= nudge
.duration
;
3087 MOZ_ASSERT(smallestUnit
!= TemporalUnit::Year
);
3089 // FIXME: spec bug - wrong loop condition and "day" case not reachable
3090 // https://github.com/tc39/proposal-temporal/issues/2890
3093 auto dateDuration
= nudge
.duration
.date
;
3094 auto timeDuration
= nudge
.duration
.time
;
3095 auto unit
= smallestUnit
;
3096 while (unit
> largestUnit
) {
3098 using TemporalUnitType
= std::underlying_type_t
<TemporalUnit
>;
3100 static_assert(static_cast<TemporalUnitType
>(TemporalUnit::Auto
) == 0,
3101 "TemporalUnit::Auto has value zero");
3102 MOZ_ASSERT(unit
> TemporalUnit::Auto
, "can subtract unit by one");
3104 unit
= static_cast<TemporalUnit
>(static_cast<TemporalUnitType
>(unit
) - 1);
3106 MOZ_ASSERT(TemporalUnit::Year
<= unit
&& unit
<= TemporalUnit::Week
);
3108 // Step 8.a. (Not applicable in our implementation.)
3111 if (unit
!= TemporalUnit::Week
|| largestUnit
== TemporalUnit::Week
) {
3113 DateDuration endDuration
;
3114 if (unit
== TemporalUnit::Year
) {
3116 int64_t years
= dateDuration
.years
+ sign
;
3119 endDuration
= {years
};
3120 } else if (unit
== TemporalUnit::Month
) {
3122 int64_t months
= dateDuration
.months
+ sign
;
3125 endDuration
= {dateDuration
.years
, months
};
3126 } else if (unit
== TemporalUnit::Week
) {
3128 int64_t weeks
= dateDuration
.weeks
+ sign
;
3131 endDuration
= {dateDuration
.years
, dateDuration
.months
, weeks
};
3134 MOZ_ASSERT(unit
== TemporalUnit::Day
);
3137 int64_t days
= dateDuration
.days
+ sign
;
3140 endDuration
= {dateDuration
.years
, dateDuration
.months
,
3141 dateDuration
.weeks
, days
};
3146 if (!AddDate(cx
, calendar
, dateTime
.date
, endDuration
, &end
)) {
3150 // Steps 8.b.vi-vii.
3152 if (!timeZone
.receiver()) {
3154 endEpochNs
= GetUTCEpochNanoseconds({end
, dateTime
.time
});
3157 Rooted
<PlainDateTimeWithCalendar
> endDateTime(
3158 cx
, PlainDateTimeWithCalendar
{{end
, dateTime
.time
},
3159 calendar
.receiver()});
3161 // Steps 8.b.vii.2-3.
3162 if (!GetInstantFor(cx
, timeZone
, endDateTime
,
3163 TemporalDisambiguation::Compatible
, &endEpochNs
)) {
3170 // NB: |nudge.epochNs| can be outside the valid epoch nanoseconds limits.
3171 auto beyondEnd
= nudge
.epochNs
- endEpochNs
;
3174 int32_t beyondEndSign
= beyondEnd
< InstantSpan
{} ? -1
3175 : beyondEnd
> InstantSpan
{} ? 1
3179 if (beyondEndSign
!= -sign
) {
3180 dateDuration
= endDuration
;
3187 // Step 8.c. (Moved above)
3191 *result
= {dateDuration
, timeDuration
};
3196 * RoundRelativeDuration ( duration, destEpochNs, dateTime, calendarRec,
3197 * timeZoneRec, largestUnit, increment, smallestUnit, roundingMode )
3199 bool js::temporal::RoundRelativeDuration(
3200 JSContext
* cx
, const NormalizedDuration
& duration
,
3201 const Instant
& destEpochNs
, const PlainDateTime
& dateTime
,
3202 Handle
<CalendarRecord
> calendar
, Handle
<TimeZoneRecord
> timeZone
,
3203 TemporalUnit largestUnit
, Increment increment
, TemporalUnit smallestUnit
,
3204 TemporalRoundingMode roundingMode
, RoundedRelativeDuration
* result
) {
3205 MOZ_ASSERT(IsValidDuration(duration
));
3206 MOZ_ASSERT(IsValidEpochInstant(destEpochNs
));
3207 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime
));
3208 MOZ_ASSERT(largestUnit
<= smallestUnit
);
3211 bool irregularLengthUnit
=
3212 (smallestUnit
< TemporalUnit::Day
) ||
3213 (timeZone
.receiver() && smallestUnit
== TemporalUnit::Day
);
3215 // Step 4. (Not applicable in our implementation.)
3218 DurationNudge nudge
;
3219 if (irregularLengthUnit
) {
3221 if (!NudgeToCalendarUnit(cx
, duration
, destEpochNs
, dateTime
, calendar
,
3222 timeZone
, increment
, smallestUnit
, roundingMode
,
3226 } else if (timeZone
.receiver()) {
3228 if (!NudgeToZonedTime(cx
, duration
, dateTime
, calendar
, timeZone
, increment
,
3229 smallestUnit
, roundingMode
, &nudge
)) {
3234 if (!NudgeToDayOrTime(cx
, duration
, destEpochNs
, largestUnit
, increment
,
3235 smallestUnit
, roundingMode
, &nudge
)) {
3241 auto nudgedDuration
= nudge
.duration
;
3244 if (nudge
.didExpandCalendarUnit
&& smallestUnit
!= TemporalUnit::Week
) {
3245 // Step 9.a. (Inlined LargerOfTwoTemporalUnits)
3246 auto startUnit
= std::min(smallestUnit
, TemporalUnit::Day
);
3249 if (!BubbleRelativeDuration(cx
, duration
, nudge
, dateTime
, calendar
,
3250 timeZone
, largestUnit
, startUnit
,
3257 largestUnit
= std::max(largestUnit
, TemporalUnit::Hour
);
3260 TimeDuration balanced
;
3261 if (!BalanceTimeDuration(cx
, nudgedDuration
.time
, largestUnit
, &balanced
)) {
3266 auto resultDuration
= Duration
{
3267 double(nudgedDuration
.date
.years
),
3268 double(nudgedDuration
.date
.months
),
3269 double(nudgedDuration
.date
.weeks
),
3270 double(nudgedDuration
.date
.days
),
3271 double(balanced
.hours
),
3272 double(balanced
.minutes
),
3273 double(balanced
.seconds
),
3274 double(balanced
.milliseconds
),
3275 balanced
.microseconds
,
3276 balanced
.nanoseconds
,
3278 MOZ_ASSERT(IsValidDuration(resultDuration
));
3280 *result
= {resultDuration
, nudge
.total
};
3284 enum class DurationOperation
{ Add
, Subtract
};
3287 * AddDurations ( operation, duration, other )
3289 static bool AddDurations(JSContext
* cx
, DurationOperation operation
,
3290 const CallArgs
& args
) {
3291 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
3292 auto duration
= ToDuration(durationObj
);
3294 // Step 1. (Not applicable in our implementation.)
3298 if (!ToTemporalDurationRecord(cx
, args
.get(0), &other
)) {
3302 // Steps 3-12. (Not applicable in our implementation.)
3305 if (operation
== DurationOperation::Subtract
) {
3306 other
= other
.negate();
3310 auto largestUnit1
= DefaultTemporalLargestUnit(duration
);
3313 auto largestUnit2
= DefaultTemporalLargestUnit(other
);
3316 auto largestUnit
= std::min(largestUnit1
, largestUnit2
);
3319 auto normalized1
= NormalizeTimeDuration(duration
);
3322 auto normalized2
= NormalizeTimeDuration(other
);
3325 if (largestUnit
<= TemporalUnit::Week
) {
3326 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3327 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3333 NormalizedTimeDuration normalized
;
3334 if (!AddNormalizedTimeDuration(cx
, normalized1
, normalized2
, &normalized
)) {
3339 int64_t days1
= mozilla::AssertedCast
<int64_t>(duration
.days
);
3340 int64_t days2
= mozilla::AssertedCast
<int64_t>(other
.days
);
3341 auto totalDays
= mozilla::CheckedInt64(days1
) + days2
;
3342 MOZ_ASSERT(totalDays
.isValid(), "adding two duration days can't overflow");
3344 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized
, totalDays
.value(),
3350 TimeDuration balanced
;
3351 if (!temporal::BalanceTimeDuration(cx
, normalized
, largestUnit
, &balanced
)) {
3356 auto* obj
= CreateTemporalDuration(cx
, balanced
.toDuration());
3361 args
.rval().setObject(*obj
);
3366 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
3367 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
3370 static bool DurationConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3371 CallArgs args
= CallArgsFromVp(argc
, vp
);
3374 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.Duration")) {
3380 if (args
.hasDefined(0) &&
3381 !ToIntegerIfIntegral(cx
, "years", args
[0], &years
)) {
3387 if (args
.hasDefined(1) &&
3388 !ToIntegerIfIntegral(cx
, "months", args
[1], &months
)) {
3394 if (args
.hasDefined(2) &&
3395 !ToIntegerIfIntegral(cx
, "weeks", args
[2], &weeks
)) {
3401 if (args
.hasDefined(3) && !ToIntegerIfIntegral(cx
, "days", args
[3], &days
)) {
3407 if (args
.hasDefined(4) &&
3408 !ToIntegerIfIntegral(cx
, "hours", args
[4], &hours
)) {
3414 if (args
.hasDefined(5) &&
3415 !ToIntegerIfIntegral(cx
, "minutes", args
[5], &minutes
)) {
3421 if (args
.hasDefined(6) &&
3422 !ToIntegerIfIntegral(cx
, "seconds", args
[6], &seconds
)) {
3427 double milliseconds
= 0;
3428 if (args
.hasDefined(7) &&
3429 !ToIntegerIfIntegral(cx
, "milliseconds", args
[7], &milliseconds
)) {
3434 double microseconds
= 0;
3435 if (args
.hasDefined(8) &&
3436 !ToIntegerIfIntegral(cx
, "microseconds", args
[8], µseconds
)) {
3441 double nanoseconds
= 0;
3442 if (args
.hasDefined(9) &&
3443 !ToIntegerIfIntegral(cx
, "nanoseconds", args
[9], &nanoseconds
)) {
3448 auto* duration
= CreateTemporalDuration(
3450 {years
, months
, weeks
, days
, hours
, minutes
, seconds
, milliseconds
,
3451 microseconds
, nanoseconds
});
3456 args
.rval().setObject(*duration
);
3461 * Temporal.Duration.from ( item )
3463 static bool Duration_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3464 CallArgs args
= CallArgsFromVp(argc
, vp
);
3466 Handle
<Value
> item
= args
.get(0);
3469 if (item
.isObject()) {
3470 if (auto* duration
= item
.toObject().maybeUnwrapIf
<DurationObject
>()) {
3471 auto* result
= CreateTemporalDuration(cx
, ToDuration(duration
));
3476 args
.rval().setObject(*result
);
3482 auto result
= ToTemporalDuration(cx
, item
);
3487 args
.rval().setObject(*result
);
3492 * Temporal.Duration.compare ( one, two [ , options ] )
3494 static bool Duration_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3495 CallArgs args
= CallArgsFromVp(argc
, vp
);
3499 if (!ToTemporalDuration(cx
, args
.get(0), &one
)) {
3505 if (!ToTemporalDuration(cx
, args
.get(1), &two
)) {
3510 Rooted
<JSObject
*> options(cx
);
3511 if (args
.hasDefined(2)) {
3512 options
= RequireObjectArg(cx
, "options", "compare", args
[2]);
3520 args
.rval().setInt32(0);
3525 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
3526 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
3527 Rooted
<TimeZoneRecord
> timeZone(cx
);
3529 if (!GetTemporalRelativeToOption(cx
, options
, &plainRelativeTo
,
3530 &zonedRelativeTo
, &timeZone
)) {
3533 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
3534 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
3538 auto hasCalendarUnit
= [](const auto& d
) {
3539 return d
.years
!= 0 || d
.months
!= 0 || d
.weeks
!= 0;
3541 bool calendarUnitsPresent
= hasCalendarUnit(one
) || hasCalendarUnit(two
);
3544 Rooted
<CalendarRecord
> calendar(cx
);
3545 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
3548 CalendarMethod::DateAdd
,
3555 if (zonedRelativeTo
&&
3556 (calendarUnitsPresent
|| one
.days
!= 0 || two
.days
!= 0)) {
3558 const auto& instant
= zonedRelativeTo
.instant();
3561 PlainDateTime dateTime
;
3562 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &dateTime
)) {
3567 auto normalized1
= CreateNormalizedDurationRecord(one
);
3570 auto normalized2
= CreateNormalizedDurationRecord(two
);
3574 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized1
,
3575 dateTime
, &after1
)) {
3581 if (!AddZonedDateTime(cx
, instant
, timeZone
, calendar
, normalized2
,
3582 dateTime
, &after2
)) {
3587 args
.rval().setInt32(after1
< after2
? -1 : after1
> after2
? 1 : 0);
3592 int64_t days1
, days2
;
3593 if (calendarUnitsPresent
) {
3595 if (!plainRelativeTo
) {
3596 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3597 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
3603 if (!UnbalanceDateDurationRelative(cx
, one
.toDateDuration(),
3604 plainRelativeTo
, calendar
, &days1
)) {
3609 if (!UnbalanceDateDurationRelative(cx
, two
.toDateDuration(),
3610 plainRelativeTo
, calendar
, &days2
)) {
3615 days1
= mozilla::AssertedCast
<int64_t>(one
.days
);
3618 days2
= mozilla::AssertedCast
<int64_t>(two
.days
);
3622 auto normalized1
= NormalizeTimeDuration(one
);
3625 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized1
, days1
,
3631 auto normalized2
= NormalizeTimeDuration(two
);
3634 if (!Add24HourDaysToNormalizedTimeDuration(cx
, normalized2
, days2
,
3640 args
.rval().setInt32(CompareNormalizedTimeDuration(normalized1
, normalized2
));
3645 * get Temporal.Duration.prototype.years
3647 static bool Duration_years(JSContext
* cx
, const CallArgs
& args
) {
3649 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3650 args
.rval().setNumber(duration
->years());
3655 * get Temporal.Duration.prototype.years
3657 static bool Duration_years(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3659 CallArgs args
= CallArgsFromVp(argc
, vp
);
3660 return CallNonGenericMethod
<IsDuration
, Duration_years
>(cx
, args
);
3664 * get Temporal.Duration.prototype.months
3666 static bool Duration_months(JSContext
* cx
, const CallArgs
& args
) {
3668 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3669 args
.rval().setNumber(duration
->months());
3674 * get Temporal.Duration.prototype.months
3676 static bool Duration_months(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3678 CallArgs args
= CallArgsFromVp(argc
, vp
);
3679 return CallNonGenericMethod
<IsDuration
, Duration_months
>(cx
, args
);
3683 * get Temporal.Duration.prototype.weeks
3685 static bool Duration_weeks(JSContext
* cx
, const CallArgs
& args
) {
3687 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3688 args
.rval().setNumber(duration
->weeks());
3693 * get Temporal.Duration.prototype.weeks
3695 static bool Duration_weeks(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3697 CallArgs args
= CallArgsFromVp(argc
, vp
);
3698 return CallNonGenericMethod
<IsDuration
, Duration_weeks
>(cx
, args
);
3702 * get Temporal.Duration.prototype.days
3704 static bool Duration_days(JSContext
* cx
, const CallArgs
& args
) {
3706 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3707 args
.rval().setNumber(duration
->days());
3712 * get Temporal.Duration.prototype.days
3714 static bool Duration_days(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3716 CallArgs args
= CallArgsFromVp(argc
, vp
);
3717 return CallNonGenericMethod
<IsDuration
, Duration_days
>(cx
, args
);
3721 * get Temporal.Duration.prototype.hours
3723 static bool Duration_hours(JSContext
* cx
, const CallArgs
& args
) {
3725 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3726 args
.rval().setNumber(duration
->hours());
3731 * get Temporal.Duration.prototype.hours
3733 static bool Duration_hours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3735 CallArgs args
= CallArgsFromVp(argc
, vp
);
3736 return CallNonGenericMethod
<IsDuration
, Duration_hours
>(cx
, args
);
3740 * get Temporal.Duration.prototype.minutes
3742 static bool Duration_minutes(JSContext
* cx
, const CallArgs
& args
) {
3744 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3745 args
.rval().setNumber(duration
->minutes());
3750 * get Temporal.Duration.prototype.minutes
3752 static bool Duration_minutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3754 CallArgs args
= CallArgsFromVp(argc
, vp
);
3755 return CallNonGenericMethod
<IsDuration
, Duration_minutes
>(cx
, args
);
3759 * get Temporal.Duration.prototype.seconds
3761 static bool Duration_seconds(JSContext
* cx
, const CallArgs
& args
) {
3763 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3764 args
.rval().setNumber(duration
->seconds());
3769 * get Temporal.Duration.prototype.seconds
3771 static bool Duration_seconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3773 CallArgs args
= CallArgsFromVp(argc
, vp
);
3774 return CallNonGenericMethod
<IsDuration
, Duration_seconds
>(cx
, args
);
3778 * get Temporal.Duration.prototype.milliseconds
3780 static bool Duration_milliseconds(JSContext
* cx
, const CallArgs
& args
) {
3782 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3783 args
.rval().setNumber(duration
->milliseconds());
3788 * get Temporal.Duration.prototype.milliseconds
3790 static bool Duration_milliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3792 CallArgs args
= CallArgsFromVp(argc
, vp
);
3793 return CallNonGenericMethod
<IsDuration
, Duration_milliseconds
>(cx
, args
);
3797 * get Temporal.Duration.prototype.microseconds
3799 static bool Duration_microseconds(JSContext
* cx
, const CallArgs
& args
) {
3801 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3802 args
.rval().setNumber(duration
->microseconds());
3807 * get Temporal.Duration.prototype.microseconds
3809 static bool Duration_microseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3811 CallArgs args
= CallArgsFromVp(argc
, vp
);
3812 return CallNonGenericMethod
<IsDuration
, Duration_microseconds
>(cx
, args
);
3816 * get Temporal.Duration.prototype.nanoseconds
3818 static bool Duration_nanoseconds(JSContext
* cx
, const CallArgs
& args
) {
3820 auto* duration
= &args
.thisv().toObject().as
<DurationObject
>();
3821 args
.rval().setNumber(duration
->nanoseconds());
3826 * get Temporal.Duration.prototype.nanoseconds
3828 static bool Duration_nanoseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3830 CallArgs args
= CallArgsFromVp(argc
, vp
);
3831 return CallNonGenericMethod
<IsDuration
, Duration_nanoseconds
>(cx
, args
);
3835 * get Temporal.Duration.prototype.sign
3837 static bool Duration_sign(JSContext
* cx
, const CallArgs
& args
) {
3838 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
3841 args
.rval().setInt32(DurationSign(duration
));
3846 * get Temporal.Duration.prototype.sign
3848 static bool Duration_sign(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3850 CallArgs args
= CallArgsFromVp(argc
, vp
);
3851 return CallNonGenericMethod
<IsDuration
, Duration_sign
>(cx
, args
);
3855 * get Temporal.Duration.prototype.blank
3857 static bool Duration_blank(JSContext
* cx
, const CallArgs
& args
) {
3858 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
3861 args
.rval().setBoolean(duration
== Duration
{});
3866 * get Temporal.Duration.prototype.blank
3868 static bool Duration_blank(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3870 CallArgs args
= CallArgsFromVp(argc
, vp
);
3871 return CallNonGenericMethod
<IsDuration
, Duration_blank
>(cx
, args
);
3875 * Temporal.Duration.prototype.with ( temporalDurationLike )
3877 * ToPartialDuration ( temporalDurationLike )
3879 static bool Duration_with(JSContext
* cx
, const CallArgs
& args
) {
3880 // Absent values default to the corresponding values of |this| object.
3881 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
3884 Rooted
<JSObject
*> temporalDurationLike(
3885 cx
, RequireObjectArg(cx
, "temporalDurationLike", "with", args
.get(0)));
3886 if (!temporalDurationLike
) {
3889 if (!ToTemporalPartialDurationRecord(cx
, temporalDurationLike
, &duration
)) {
3894 auto* result
= CreateTemporalDuration(cx
, duration
);
3899 args
.rval().setObject(*result
);
3904 * Temporal.Duration.prototype.with ( temporalDurationLike )
3906 static bool Duration_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3908 CallArgs args
= CallArgsFromVp(argc
, vp
);
3909 return CallNonGenericMethod
<IsDuration
, Duration_with
>(cx
, args
);
3913 * Temporal.Duration.prototype.negated ( )
3915 static bool Duration_negated(JSContext
* cx
, const CallArgs
& args
) {
3916 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
3919 auto* result
= CreateTemporalDuration(cx
, duration
.negate());
3924 args
.rval().setObject(*result
);
3929 * Temporal.Duration.prototype.negated ( )
3931 static bool Duration_negated(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3933 CallArgs args
= CallArgsFromVp(argc
, vp
);
3934 return CallNonGenericMethod
<IsDuration
, Duration_negated
>(cx
, args
);
3938 * Temporal.Duration.prototype.abs ( )
3940 static bool Duration_abs(JSContext
* cx
, const CallArgs
& args
) {
3941 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
3944 auto* result
= CreateTemporalDuration(cx
, AbsoluteDuration(duration
));
3949 args
.rval().setObject(*result
);
3954 * Temporal.Duration.prototype.abs ( )
3956 static bool Duration_abs(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3958 CallArgs args
= CallArgsFromVp(argc
, vp
);
3959 return CallNonGenericMethod
<IsDuration
, Duration_abs
>(cx
, args
);
3963 * Temporal.Duration.prototype.add ( other )
3965 static bool Duration_add(JSContext
* cx
, const CallArgs
& args
) {
3967 return AddDurations(cx
, DurationOperation::Add
, args
);
3971 * Temporal.Duration.prototype.add ( other )
3973 static bool Duration_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3975 CallArgs args
= CallArgsFromVp(argc
, vp
);
3976 return CallNonGenericMethod
<IsDuration
, Duration_add
>(cx
, args
);
3980 * Temporal.Duration.prototype.subtract ( other )
3982 static bool Duration_subtract(JSContext
* cx
, const CallArgs
& args
) {
3984 return AddDurations(cx
, DurationOperation::Subtract
, args
);
3988 * Temporal.Duration.prototype.subtract ( other )
3990 static bool Duration_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3992 CallArgs args
= CallArgsFromVp(argc
, vp
);
3993 return CallNonGenericMethod
<IsDuration
, Duration_subtract
>(cx
, args
);
3997 * Temporal.Duration.prototype.round ( roundTo )
3999 static bool Duration_round(JSContext
* cx
, const CallArgs
& args
) {
4000 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4002 // Step 18. (Reordered)
4003 auto existingLargestUnit
= DefaultTemporalLargestUnit(duration
);
4006 auto smallestUnit
= TemporalUnit::Auto
;
4007 TemporalUnit largestUnit
;
4008 auto roundingMode
= TemporalRoundingMode::HalfExpand
;
4009 auto roundingIncrement
= Increment
{1};
4010 Rooted
<JSObject
*> relativeTo(cx
);
4011 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4012 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4013 Rooted
<TimeZoneRecord
> timeZone(cx
);
4014 if (args
.get(0).isString()) {
4015 // Step 4. (Not applicable in our implementation.)
4017 // Steps 6-15. (Not applicable)
4020 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4021 if (!GetTemporalUnitValuedOption(
4022 cx
, paramString
, TemporalUnitKey::SmallestUnit
,
4023 TemporalUnitGroup::DateTime
, &smallestUnit
)) {
4027 // Step 17. (Not applicable)
4029 // Step 18. (Moved above)
4032 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4034 // Step 20. (Not applicable)
4036 // Step 20.a. (Not applicable)
4039 largestUnit
= defaultLargestUnit
;
4041 // Steps 21-25. (Not applicable)
4044 Rooted
<JSObject
*> options(
4045 cx
, RequireObjectArg(cx
, "roundTo", "round", args
.get(0)));
4051 bool smallestUnitPresent
= true;
4054 bool largestUnitPresent
= true;
4058 // Inlined GetTemporalUnitValuedOption and GetOption so we can more easily
4059 // detect an absent "largestUnit" value.
4060 Rooted
<Value
> largestUnitValue(cx
);
4061 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
4062 &largestUnitValue
)) {
4066 if (!largestUnitValue
.isUndefined()) {
4067 Rooted
<JSString
*> largestUnitStr(cx
, JS::ToString(cx
, largestUnitValue
));
4068 if (!largestUnitStr
) {
4072 largestUnit
= TemporalUnit::Auto
;
4073 if (!GetTemporalUnitValuedOption(
4074 cx
, largestUnitStr
, TemporalUnitKey::LargestUnit
,
4075 TemporalUnitGroup::DateTime
, &largestUnit
)) {
4081 if (!GetTemporalRelativeToOption(cx
, options
, &plainRelativeTo
,
4082 &zonedRelativeTo
, &timeZone
)) {
4085 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4086 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4089 if (!GetRoundingIncrementOption(cx
, options
, &roundingIncrement
)) {
4094 if (!GetRoundingModeOption(cx
, options
, &roundingMode
)) {
4099 if (!GetTemporalUnitValuedOption(cx
, options
, TemporalUnitKey::SmallestUnit
,
4100 TemporalUnitGroup::DateTime
,
4106 if (smallestUnit
== TemporalUnit::Auto
) {
4108 smallestUnitPresent
= false;
4111 smallestUnit
= TemporalUnit::Nanosecond
;
4114 // Step 18. (Moved above)
4117 auto defaultLargestUnit
= std::min(existingLargestUnit
, smallestUnit
);
4120 if (largestUnitValue
.isUndefined()) {
4122 largestUnitPresent
= false;
4125 largestUnit
= defaultLargestUnit
;
4126 } else if (largestUnit
== TemporalUnit::Auto
) {
4128 largestUnit
= defaultLargestUnit
;
4132 if (!smallestUnitPresent
&& !largestUnitPresent
) {
4133 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4134 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER
);
4139 if (largestUnit
> smallestUnit
) {
4140 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4141 JSMSG_TEMPORAL_INVALID_UNIT_RANGE
);
4146 if (smallestUnit
> TemporalUnit::Day
) {
4148 auto maximum
= MaximumTemporalDurationRoundingIncrement(smallestUnit
);
4151 if (!ValidateTemporalRoundingIncrement(cx
, roundingIncrement
, maximum
,
4159 bool hoursToDaysConversionMayOccur
= false;
4162 if (duration
.days
!= 0 && zonedRelativeTo
) {
4163 hoursToDaysConversionMayOccur
= true;
4167 else if (std::abs(duration
.hours
) >= 24) {
4168 hoursToDaysConversionMayOccur
= true;
4172 bool roundingGranularityIsNoop
= smallestUnit
== TemporalUnit::Nanosecond
&&
4173 roundingIncrement
== Increment
{1};
4176 bool calendarUnitsPresent
=
4177 duration
.years
!= 0 || duration
.months
!= 0 || duration
.weeks
!= 0;
4180 if (roundingGranularityIsNoop
&& largestUnit
== existingLargestUnit
&&
4181 !calendarUnitsPresent
&& !hoursToDaysConversionMayOccur
&&
4182 std::abs(duration
.minutes
) < 60 && std::abs(duration
.seconds
) < 60 &&
4183 std::abs(duration
.milliseconds
) < 1000 &&
4184 std::abs(duration
.microseconds
) < 1000 &&
4185 std::abs(duration
.nanoseconds
) < 1000) {
4187 auto* obj
= CreateTemporalDuration(cx
, duration
);
4192 args
.rval().setObject(*obj
);
4197 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4200 bool plainDateTimeOrRelativeToWillBeUsed
= largestUnit
<= TemporalUnit::Day
||
4201 calendarUnitsPresent
||
4205 PlainDateTime relativeToDateTime
;
4206 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
4208 const auto& instant
= zonedRelativeTo
.instant();
4211 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
4214 precalculatedPlainDateTime
=
4215 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
4217 // FIXME: spec issue - Unnecessary CreateTemporalDate call
4219 // https://github.com/tc39/proposal-temporal/issues/2873
4223 Rooted
<CalendarRecord
> calendar(cx
);
4224 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4227 CalendarMethod::DateAdd
,
4228 CalendarMethod::DateUntil
,
4235 auto normDuration
= CreateNormalizedDurationRecord(duration
);
4237 // Step 37. (Not applicable in our implementation.)
4240 Duration roundResult
;
4241 if (zonedRelativeTo
) {
4243 auto relativeEpochNs
= zonedRelativeTo
.instant();
4246 const auto& relativeInstant
= relativeEpochNs
;
4249 if (precalculatedPlainDateTime
) {
4251 Instant targetEpochNs
;
4252 if (!AddZonedDateTime(cx
, relativeInstant
, timeZone
, calendar
,
4253 normDuration
, *precalculatedPlainDateTime
,
4259 if (!DifferenceZonedDateTimeWithRounding(
4260 cx
, relativeEpochNs
, targetEpochNs
, timeZone
, calendar
,
4261 *precalculatedPlainDateTime
,
4273 Instant targetEpochNs
;
4274 if (!AddZonedDateTime(cx
, relativeInstant
, timeZone
, calendar
,
4275 normDuration
, &targetEpochNs
)) {
4280 if (!DifferenceZonedDateTimeWithRounding(cx
, relativeEpochNs
,
4292 } else if (plainRelativeTo
) {
4294 auto targetTime
= AddTime(PlainTime
{}, normDuration
.time
);
4297 auto dateDuration
= DateDuration
{
4298 normDuration
.date
.years
,
4299 normDuration
.date
.months
,
4300 normDuration
.date
.weeks
,
4301 normDuration
.date
.days
+ targetTime
.days
,
4303 MOZ_ASSERT(IsValidDuration(dateDuration
));
4306 PlainDate targetDate
;
4307 if (!AddDate(cx
, calendar
, plainRelativeTo
, dateDuration
, &targetDate
)) {
4310 auto targetDateTime
= PlainDateTime
{targetDate
, targetTime
.time
};
4312 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
4313 if (!unwrappedRelativeTo
) {
4316 auto sourceDateTime
= PlainDateTime
{ToPlainDate(unwrappedRelativeTo
), {}};
4319 if (!DifferencePlainDateTimeWithRounding(cx
, sourceDateTime
, targetDateTime
,
4332 if (calendarUnitsPresent
|| largestUnit
< TemporalUnit::Day
) {
4333 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4334 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
4340 MOZ_ASSERT(smallestUnit
>= TemporalUnit::Day
);
4342 // FIXME: spec issue - can with switch the call order, so that
4343 // Add24HourDaysToNormalizedTimeDuration is first called. That way we don't
4344 // have to add the additional `days` parameter to RoundTimeDuration.
4347 RoundedDuration rounded
;
4348 if (!::RoundTimeDuration(cx
, normDuration
, roundingIncrement
, smallestUnit
,
4349 roundingMode
, ComputeRemainder::No
, &rounded
)) {
4354 NormalizedTimeDuration withDays
;
4355 if (!Add24HourDaysToNormalizedTimeDuration(
4356 cx
, rounded
.duration
.time
, rounded
.duration
.date
.days
, &withDays
)) {
4361 TimeDuration balanceResult
;
4362 if (!temporal::BalanceTimeDuration(cx
, withDays
, largestUnit
,
4368 roundResult
= balanceResult
.toDuration();
4372 auto* obj
= CreateTemporalDuration(cx
, roundResult
);
4377 args
.rval().setObject(*obj
);
4382 * Temporal.Duration.prototype.round ( options )
4384 static bool Duration_round(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4386 CallArgs args
= CallArgsFromVp(argc
, vp
);
4387 return CallNonGenericMethod
<IsDuration
, Duration_round
>(cx
, args
);
4391 * Temporal.Duration.prototype.total ( totalOf )
4393 static bool Duration_total(JSContext
* cx
, const CallArgs
& args
) {
4394 auto* durationObj
= &args
.thisv().toObject().as
<DurationObject
>();
4395 auto duration
= ToDuration(durationObj
);
4398 Rooted
<JSObject
*> relativeTo(cx
);
4399 Rooted
<Wrapped
<PlainDateObject
*>> plainRelativeTo(cx
);
4400 Rooted
<ZonedDateTime
> zonedRelativeTo(cx
);
4401 Rooted
<TimeZoneRecord
> timeZone(cx
);
4402 auto unit
= TemporalUnit::Auto
;
4403 if (args
.get(0).isString()) {
4404 // Step 4. (Not applicable in our implementation.)
4406 // Steps 6-10. (Implicit)
4407 MOZ_ASSERT(!plainRelativeTo
&& !zonedRelativeTo
);
4410 Rooted
<JSString
*> paramString(cx
, args
[0].toString());
4411 if (!GetTemporalUnitValuedOption(cx
, paramString
, TemporalUnitKey::Unit
,
4412 TemporalUnitGroup::DateTime
, &unit
)) {
4417 Rooted
<JSObject
*> totalOf(
4418 cx
, RequireObjectArg(cx
, "totalOf", "total", args
.get(0)));
4424 if (!GetTemporalRelativeToOption(cx
, totalOf
, &plainRelativeTo
,
4425 &zonedRelativeTo
, &timeZone
)) {
4428 MOZ_ASSERT(!plainRelativeTo
|| !zonedRelativeTo
);
4429 MOZ_ASSERT_IF(zonedRelativeTo
, timeZone
.receiver());
4432 if (!GetTemporalUnitValuedOption(cx
, totalOf
, TemporalUnitKey::Unit
,
4433 TemporalUnitGroup::DateTime
, &unit
)) {
4437 if (unit
== TemporalUnit::Auto
) {
4438 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4439 JSMSG_TEMPORAL_MISSING_OPTION
, "unit");
4445 mozilla::Maybe
<const PlainDateTime
&> precalculatedPlainDateTime
{};
4448 bool plainDateTimeOrRelativeToWillBeUsed
=
4449 unit
<= TemporalUnit::Day
|| duration
.toDateDuration() != DateDuration
{};
4452 PlainDateTime relativeToDateTime
;
4453 if (zonedRelativeTo
&& plainDateTimeOrRelativeToWillBeUsed
) {
4455 const auto& instant
= zonedRelativeTo
.instant();
4458 if (!GetPlainDateTimeFor(cx
, timeZone
, instant
, &relativeToDateTime
)) {
4461 precalculatedPlainDateTime
=
4462 mozilla::SomeRef
<const PlainDateTime
>(relativeToDateTime
);
4464 // FIXME: spec issue - Unnecessary CreateTemporalDate call
4466 // https://github.com/tc39/proposal-temporal/issues/2873
4470 Rooted
<CalendarRecord
> calendar(cx
);
4471 if (!CreateCalendarMethodsRecordFromRelativeTo(cx
, plainRelativeTo
,
4474 CalendarMethod::DateAdd
,
4475 CalendarMethod::DateUntil
,
4482 auto normDuration
= CreateNormalizedDurationRecord(duration
);
4484 // Step 17. (Not applicable in our implementation.)
4488 if (zonedRelativeTo
) {
4490 auto relativeEpochNs
= zonedRelativeTo
.instant();
4493 const auto& relativeInstant
= relativeEpochNs
;
4496 Instant targetEpochNs
;
4497 if (precalculatedPlainDateTime
) {
4498 if (!AddZonedDateTime(cx
, relativeInstant
, timeZone
, calendar
,
4499 normDuration
, *precalculatedPlainDateTime
,
4504 if (!AddZonedDateTime(cx
, relativeInstant
, timeZone
, calendar
,
4505 normDuration
, &targetEpochNs
)) {
4511 if (unit
<= TemporalUnit::Day
) {
4512 if (!DifferenceZonedDateTimeWithRounding(
4513 cx
, relativeEpochNs
, targetEpochNs
, timeZone
, calendar
,
4514 *precalculatedPlainDateTime
, unit
, &total
)) {
4518 total
= DifferenceZonedDateTimeWithRounding(targetEpochNs
,
4519 relativeEpochNs
, unit
);
4521 } else if (plainRelativeTo
) {
4523 auto targetTime
= AddTime(PlainTime
{}, normDuration
.time
);
4526 auto dateDuration
= DateDuration
{
4527 normDuration
.date
.years
,
4528 normDuration
.date
.months
,
4529 normDuration
.date
.weeks
,
4530 normDuration
.date
.days
+ targetTime
.days
,
4532 MOZ_ASSERT(IsValidDuration(dateDuration
));
4535 PlainDate targetDate
;
4536 if (!AddDate(cx
, calendar
, plainRelativeTo
, dateDuration
, &targetDate
)) {
4539 auto targetDateTime
= PlainDateTime
{targetDate
, targetTime
.time
};
4541 auto* unwrappedRelativeTo
= plainRelativeTo
.unwrap(cx
);
4542 if (!unwrappedRelativeTo
) {
4545 auto sourceDateTime
= PlainDateTime
{ToPlainDate(unwrappedRelativeTo
), {}};
4548 if (!::DifferencePlainDateTimeWithRounding(
4549 cx
, sourceDateTime
, targetDateTime
, calendar
, unit
, &total
)) {
4554 if (normDuration
.date
.years
|| normDuration
.date
.months
||
4555 normDuration
.date
.weeks
|| unit
< TemporalUnit::Day
) {
4556 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4557 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE
,
4562 // FIXME: spec issue - Add24HourDaysToNormalizedTimeDuration and
4563 // RoundTimeDuration are probably both infallible
4566 NormalizedTimeDuration withDays
;
4567 if (!Add24HourDaysToNormalizedTimeDuration(
4568 cx
, normDuration
.time
, normDuration
.date
.days
, &withDays
)) {
4573 auto roundInput
= NormalizedDuration
{{}, withDays
};
4574 RoundedDuration rounded
;
4575 if (!::RoundTimeDuration(cx
, roundInput
, Increment
{1}, unit
,
4576 TemporalRoundingMode::Trunc
, ComputeRemainder::Yes
,
4580 total
= rounded
.total
;
4584 MOZ_ASSERT(!std::isnan(total
));
4587 args
.rval().setNumber(total
);
4592 * Temporal.Duration.prototype.total ( totalOf )
4594 static bool Duration_total(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4596 CallArgs args
= CallArgsFromVp(argc
, vp
);
4597 return CallNonGenericMethod
<IsDuration
, Duration_total
>(cx
, args
);
4601 * Temporal.Duration.prototype.toString ( [ options ] )
4603 static bool Duration_toString(JSContext
* cx
, const CallArgs
& args
) {
4604 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4607 SecondsStringPrecision precision
= {Precision::Auto(),
4608 TemporalUnit::Nanosecond
, Increment
{1}};
4609 auto roundingMode
= TemporalRoundingMode::Trunc
;
4610 if (args
.hasDefined(0)) {
4612 Rooted
<JSObject
*> options(
4613 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
4619 auto digits
= Precision::Auto();
4620 if (!GetTemporalFractionalSecondDigitsOption(cx
, options
, &digits
)) {
4625 if (!GetRoundingModeOption(cx
, options
, &roundingMode
)) {
4630 auto smallestUnit
= TemporalUnit::Auto
;
4631 if (!GetTemporalUnitValuedOption(cx
, options
, TemporalUnitKey::SmallestUnit
,
4632 TemporalUnitGroup::Time
, &smallestUnit
)) {
4637 if (smallestUnit
== TemporalUnit::Hour
||
4638 smallestUnit
== TemporalUnit::Minute
) {
4639 const char* smallestUnitStr
=
4640 smallestUnit
== TemporalUnit::Hour
? "hour" : "minute";
4641 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
4642 JSMSG_TEMPORAL_INVALID_UNIT_OPTION
,
4643 smallestUnitStr
, "smallestUnit");
4648 precision
= ToSecondsStringPrecision(smallestUnit
, digits
);
4653 if (precision
.unit
!= TemporalUnit::Nanosecond
||
4654 precision
.increment
!= Increment
{1}) {
4656 auto timeDuration
= NormalizeTimeDuration(duration
);
4659 auto largestUnit
= DefaultTemporalLargestUnit(duration
);
4662 NormalizedTimeDuration rounded
;
4663 if (!RoundTimeDuration(cx
, timeDuration
, precision
.increment
,
4664 precision
.unit
, roundingMode
, &rounded
)) {
4669 auto balanced
= BalanceTimeDuration(
4670 rounded
, std::min(largestUnit
, TemporalUnit::Second
));
4674 duration
.years
, duration
.months
,
4675 duration
.weeks
, duration
.days
+ double(balanced
.days
),
4676 double(balanced
.hours
), double(balanced
.minutes
),
4677 double(balanced
.seconds
), double(balanced
.milliseconds
),
4678 balanced
.microseconds
, balanced
.nanoseconds
,
4680 MOZ_ASSERT(IsValidDuration(duration
));
4687 JSString
* str
= TemporalDurationToString(cx
, result
, precision
.precision
);
4692 args
.rval().setString(str
);
4697 * Temporal.Duration.prototype.toString ( [ options ] )
4699 static bool Duration_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4701 CallArgs args
= CallArgsFromVp(argc
, vp
);
4702 return CallNonGenericMethod
<IsDuration
, Duration_toString
>(cx
, args
);
4706 * Temporal.Duration.prototype.toJSON ( )
4708 static bool Duration_toJSON(JSContext
* cx
, const CallArgs
& args
) {
4709 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4712 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
4717 args
.rval().setString(str
);
4722 * Temporal.Duration.prototype.toJSON ( )
4724 static bool Duration_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4726 CallArgs args
= CallArgsFromVp(argc
, vp
);
4727 return CallNonGenericMethod
<IsDuration
, Duration_toJSON
>(cx
, args
);
4731 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
4733 static bool Duration_toLocaleString(JSContext
* cx
, const CallArgs
& args
) {
4734 auto duration
= ToDuration(&args
.thisv().toObject().as
<DurationObject
>());
4737 JSString
* str
= TemporalDurationToString(cx
, duration
, Precision::Auto());
4742 args
.rval().setString(str
);
4747 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
4749 static bool Duration_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4751 CallArgs args
= CallArgsFromVp(argc
, vp
);
4752 return CallNonGenericMethod
<IsDuration
, Duration_toLocaleString
>(cx
, args
);
4756 * Temporal.Duration.prototype.valueOf ( )
4758 static bool Duration_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
4759 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_CANT_CONVERT_TO
,
4760 "Duration", "primitive type");
4764 const JSClass
DurationObject::class_
= {
4765 "Temporal.Duration",
4766 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT
) |
4767 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration
),
4769 &DurationObject::classSpec_
,
4772 const JSClass
& DurationObject::protoClass_
= PlainObject::class_
;
4774 static const JSFunctionSpec Duration_methods
[] = {
4775 JS_FN("from", Duration_from
, 1, 0),
4776 JS_FN("compare", Duration_compare
, 2, 0),
4780 static const JSFunctionSpec Duration_prototype_methods
[] = {
4781 JS_FN("with", Duration_with
, 1, 0),
4782 JS_FN("negated", Duration_negated
, 0, 0),
4783 JS_FN("abs", Duration_abs
, 0, 0),
4784 JS_FN("add", Duration_add
, 1, 0),
4785 JS_FN("subtract", Duration_subtract
, 1, 0),
4786 JS_FN("round", Duration_round
, 1, 0),
4787 JS_FN("total", Duration_total
, 1, 0),
4788 JS_FN("toString", Duration_toString
, 0, 0),
4789 JS_FN("toJSON", Duration_toJSON
, 0, 0),
4790 JS_FN("toLocaleString", Duration_toLocaleString
, 0, 0),
4791 JS_FN("valueOf", Duration_valueOf
, 0, 0),
4795 static const JSPropertySpec Duration_prototype_properties
[] = {
4796 JS_PSG("years", Duration_years
, 0),
4797 JS_PSG("months", Duration_months
, 0),
4798 JS_PSG("weeks", Duration_weeks
, 0),
4799 JS_PSG("days", Duration_days
, 0),
4800 JS_PSG("hours", Duration_hours
, 0),
4801 JS_PSG("minutes", Duration_minutes
, 0),
4802 JS_PSG("seconds", Duration_seconds
, 0),
4803 JS_PSG("milliseconds", Duration_milliseconds
, 0),
4804 JS_PSG("microseconds", Duration_microseconds
, 0),
4805 JS_PSG("nanoseconds", Duration_nanoseconds
, 0),
4806 JS_PSG("sign", Duration_sign
, 0),
4807 JS_PSG("blank", Duration_blank
, 0),
4808 JS_STRING_SYM_PS(toStringTag
, "Temporal.Duration", JSPROP_READONLY
),
4812 const ClassSpec
DurationObject::classSpec_
= {
4813 GenericCreateConstructor
<DurationConstructor
, 0, gc::AllocKind::FUNCTION
>,
4814 GenericCreatePrototype
<DurationObject
>,
4817 Duration_prototype_methods
,
4818 Duration_prototype_properties
,
4820 ClassSpec::DontDefineConstructor
,