Bug 1874684 - Part 29: Update spec fixme notes. r=mgaudet
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blobd2122f5888742e6012796102773d225c742ba56b
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"
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdlib>
19 #include <initializer_list>
20 #include <stdint.h>
21 #include <type_traits>
22 #include <utility>
24 #include "jsnum.h"
25 #include "jspubtd.h"
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"
48 #include "js/Class.h"
49 #include "js/Conversions.h"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
53 #include "js/Id.h"
54 #include "js/Printer.h"
55 #include "js/PropertyDescriptor.h"
56 #include "js/PropertySpec.h"
57 #include "js/RootingAPI.h"
58 #include "js/Value.h"
59 #include "util/StringBuffer.h"
60 #include "vm/BytecodeUtil.h"
61 #include "vm/GlobalObject.h"
62 #include "vm/JSAtomState.h"
63 #include "vm/JSContext.h"
64 #include "vm/JSObject.h"
65 #include "vm/ObjectOperations.h"
66 #include "vm/PlainObject.h"
67 #include "vm/StringType.h"
69 #include "vm/JSObject-inl.h"
70 #include "vm/NativeObject-inl.h"
71 #include "vm/ObjectOperations-inl.h"
73 using namespace js;
74 using namespace js::temporal;
76 static inline bool IsDuration(Handle<Value> v) {
77 return v.isObject() && v.toObject().is<DurationObject>();
80 #ifdef DEBUG
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);
107 #endif
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;
125 // Step 1.
126 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
127 milliseconds, microseconds, nanoseconds}) {
128 // Step 1.a.
129 if (v < 0) {
130 return -1;
133 // Step 1.b.
134 if (v > 0) {
135 return 1;
139 // Step 2.
140 return 0;
144 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
145 * milliseconds, microseconds, nanoseconds )
147 static int32_t DurationSign(const DateDuration& duration) {
148 const auto& [years, months, weeks, days] = duration;
150 // Step 1.
151 for (auto v : {years, months, weeks, days}) {
152 // Step 1.a.
153 if (v < 0) {
154 return -1;
157 // Step 1.b.
158 if (v > 0) {
159 return 1;
163 // Step 2.
164 return 0;
168 * Normalize a nanoseconds amount into a time duration.
170 static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
171 // Split into seconds and nanoseconds.
172 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
174 return {seconds, nanos};
178 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
179 * value is too large.
181 static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
182 double nanoseconds) {
183 MOZ_ASSERT(IsInteger(nanoseconds));
185 if (auto int96 = Int96::fromInteger(nanoseconds)) {
186 // The number of normalized seconds must not exceed `2**53 - 1`.
187 constexpr auto limit =
188 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
190 if (int96->abs() < limit) {
191 return mozilla::Some(NormalizeNanoseconds(*int96));
194 return mozilla::Nothing();
198 * Normalize a microseconds amount into a time duration.
200 static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
201 // Split into seconds and microseconds.
202 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
204 // Scale microseconds to nanoseconds.
205 int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));
207 return {seconds, nanos};
211 * Normalize a microseconds amount into a time duration. Return Nothing if the
212 * value is too large.
214 static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
215 double microseconds) {
216 MOZ_ASSERT(IsInteger(microseconds));
218 if (auto int96 = Int96::fromInteger(microseconds)) {
219 // The number of normalized seconds must not exceed `2**53 - 1`.
220 constexpr auto limit =
221 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
223 if (int96->abs() < limit) {
224 return mozilla::Some(NormalizeMicroseconds(*int96));
227 return mozilla::Nothing();
231 * Normalize a duration into a time duration. Return Nothing if any duration
232 * value is too large.
234 static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
235 const Duration& duration) {
236 do {
237 auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
238 if (!nanoseconds) {
239 break;
241 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
243 auto microseconds = NormalizeMicroseconds(duration.microseconds);
244 if (!microseconds) {
245 break;
247 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
249 // Overflows for millis/seconds/minutes/hours/days always result in an
250 // invalid normalized time duration.
252 int64_t milliseconds;
253 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
254 break;
257 int64_t seconds;
258 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
259 break;
262 int64_t minutes;
263 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
264 break;
267 int64_t hours;
268 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
269 break;
272 int64_t days;
273 if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
274 break;
277 // Compute the overall amount of milliseconds.
278 mozilla::CheckedInt64 millis = days;
279 millis *= 24;
280 millis += hours;
281 millis *= 60;
282 millis += minutes;
283 millis *= 60;
284 millis += seconds;
285 millis *= 1000;
286 millis += milliseconds;
287 if (!millis.isValid()) {
288 break;
291 auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
292 if (!IsValidNormalizedTimeDuration(milli)) {
293 break;
296 // Compute the overall time duration.
297 auto result = milli + *microseconds + *nanoseconds;
298 if (!IsValidNormalizedTimeDuration(result)) {
299 break;
302 return mozilla::Some(result);
303 } while (false);
305 return mozilla::Nothing();
309 * Normalize a days amount into a time duration. Return Nothing if the value is
310 * too large.
312 static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(int64_t days) {
313 do {
314 // Compute the overall amount of milliseconds.
315 auto millis =
316 mozilla::CheckedInt64(days) * ToMilliseconds(TemporalUnit::Day);
317 if (!millis.isValid()) {
318 break;
321 auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
322 if (!IsValidNormalizedTimeDuration(result)) {
323 break;
326 return mozilla::Some(result);
327 } while (false);
329 return mozilla::Nothing();
333 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
334 * nanoseconds )
336 static NormalizedTimeDuration NormalizeTimeDuration(
337 double hours, double minutes, double seconds, double milliseconds,
338 double microseconds, double nanoseconds) {
339 MOZ_ASSERT(IsInteger(hours));
340 MOZ_ASSERT(IsInteger(minutes));
341 MOZ_ASSERT(IsInteger(seconds));
342 MOZ_ASSERT(IsInteger(milliseconds));
343 MOZ_ASSERT(IsInteger(microseconds));
344 MOZ_ASSERT(IsInteger(nanoseconds));
346 // Steps 1-3.
347 mozilla::CheckedInt64 millis = int64_t(hours);
348 millis *= 60;
349 millis += int64_t(minutes);
350 millis *= 60;
351 millis += int64_t(seconds);
352 millis *= 1000;
353 millis += int64_t(milliseconds);
354 MOZ_ASSERT(millis.isValid());
356 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
358 // Step 4.
359 auto micros = Int96::fromInteger(microseconds);
360 MOZ_ASSERT(micros);
362 normalized += NormalizeMicroseconds(*micros);
364 // Step 5.
365 auto nanos = Int96::fromInteger(nanoseconds);
366 MOZ_ASSERT(nanos);
368 normalized += NormalizeNanoseconds(*nanos);
370 // Step 6.
371 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
373 // Step 7.
374 return normalized;
378 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
379 * nanoseconds )
381 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
382 int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
383 int32_t microseconds, int32_t nanoseconds) {
384 // Steps 1-3.
385 mozilla::CheckedInt64 millis = int64_t(hours);
386 millis *= 60;
387 millis += int64_t(minutes);
388 millis *= 60;
389 millis += int64_t(seconds);
390 millis *= 1000;
391 millis += int64_t(milliseconds);
392 MOZ_ASSERT(millis.isValid());
394 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
396 // Step 4.
397 normalized += NormalizeMicroseconds(Int96{microseconds});
399 // Step 5.
400 normalized += NormalizeNanoseconds(Int96{nanoseconds});
402 // Step 6.
403 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
405 // Step 7.
406 return normalized;
410 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
411 * nanoseconds )
413 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
414 const Duration& duration) {
415 MOZ_ASSERT(IsValidDuration(duration));
417 return ::NormalizeTimeDuration(duration.hours, duration.minutes,
418 duration.seconds, duration.milliseconds,
419 duration.microseconds, duration.nanoseconds);
423 * AddNormalizedTimeDuration ( one, two )
425 static bool AddNormalizedTimeDuration(JSContext* cx,
426 const NormalizedTimeDuration& one,
427 const NormalizedTimeDuration& two,
428 NormalizedTimeDuration* result) {
429 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
430 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
432 // Step 1.
433 auto sum = one + two;
435 // Step 2.
436 if (!IsValidNormalizedTimeDuration(sum)) {
437 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
438 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
439 return false;
442 // Step 3.
443 *result = sum;
444 return true;
448 * SubtractNormalizedTimeDuration ( one, two )
450 static bool SubtractNormalizedTimeDuration(JSContext* cx,
451 const NormalizedTimeDuration& one,
452 const NormalizedTimeDuration& two,
453 NormalizedTimeDuration* result) {
454 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
455 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
457 // Step 1.
458 auto sum = one - two;
460 // Step 2.
461 if (!IsValidNormalizedTimeDuration(sum)) {
462 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
463 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
464 return false;
467 // Step 3.
468 *result = sum;
469 return true;
473 * Add24HourDaysToNormalizedTimeDuration ( d, days )
475 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
476 JSContext* cx, const NormalizedTimeDuration& d, int64_t days,
477 NormalizedTimeDuration* result) {
478 MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
480 // Step 1.
481 auto normalizedDays = NormalizeDays(days);
482 if (!normalizedDays) {
483 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
484 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
485 return false;
488 // Step 2.
489 auto sum = d + *normalizedDays;
490 if (!IsValidNormalizedTimeDuration(sum)) {
491 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
492 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
493 return false;
496 // Step 3.
497 *result = sum;
498 return true;
502 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
504 bool js::temporal::CombineDateAndNormalizedTimeDuration(
505 JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
506 NormalizedDuration* result) {
507 MOZ_ASSERT(IsValidDuration(date));
508 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
510 // Step 1.
511 int32_t dateSign = ::DurationSign(date);
513 // Step 2.
514 int32_t timeSign = NormalizedTimeDurationSign(time);
516 // Step 3
517 if ((dateSign * timeSign) < 0) {
518 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
519 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
520 return false;
523 // Step 4.
524 *result = {date, time};
525 return true;
529 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
531 NormalizedTimeDuration
532 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
533 const Instant& one, const Instant& two) {
534 MOZ_ASSERT(IsValidEpochInstant(one));
535 MOZ_ASSERT(IsValidEpochInstant(two));
537 // Step 1.
538 auto result = one - two;
540 // Step 2.
541 MOZ_ASSERT(IsValidInstantSpan(result));
543 // Step 3.
544 return result.to<NormalizedTimeDuration>();
548 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
549 * milliseconds, microseconds, nanoseconds )
551 bool js::temporal::IsValidDuration(const Duration& duration) {
552 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
554 const auto& [years, months, weeks, days, hours, minutes, seconds,
555 milliseconds, microseconds, nanoseconds] = duration;
557 // Step 1.
558 int32_t sign = DurationSign(duration);
560 // Step 2.
561 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
562 milliseconds, microseconds, nanoseconds}) {
563 // Step 2.a.
564 if (!std::isfinite(v)) {
565 return false;
568 // Step 2.b.
569 if (v < 0 && sign > 0) {
570 return false;
573 // Step 2.c.
574 if (v > 0 && sign < 0) {
575 return false;
579 // Step 3.
580 if (std::abs(years) >= double(int64_t(1) << 32)) {
581 return false;
584 // Step 4.
585 if (std::abs(months) >= double(int64_t(1) << 32)) {
586 return false;
589 // Step 5.
590 if (std::abs(weeks) >= double(int64_t(1) << 32)) {
591 return false;
594 // Steps 6-8.
595 if (!NormalizeSeconds(duration)) {
596 return false;
599 // Step 9.
600 return true;
603 #ifdef DEBUG
605 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
606 * milliseconds, microseconds, nanoseconds )
608 bool js::temporal::IsValidDuration(const DateDuration& duration) {
609 return IsValidDuration(duration.toDuration());
613 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
614 * milliseconds, microseconds, nanoseconds )
616 bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
617 return IsValidDuration(duration.date) &&
618 IsValidNormalizedTimeDuration(duration.time) &&
619 (::DurationSign(duration.date) *
620 NormalizedTimeDurationSign(duration.time) >=
623 #endif
625 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
626 const char* name, unsigned errorNumber) {
627 ToCStringBuf cbuf;
628 const char* numStr = NumberToCString(&cbuf, value);
630 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
631 numStr);
632 return false;
636 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
637 * milliseconds, microseconds, nanoseconds )
639 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
640 const Duration& duration) {
641 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
643 const auto& [years, months, weeks, days, hours, minutes, seconds,
644 milliseconds, microseconds, nanoseconds] = duration;
646 // Step 1.
647 int32_t sign = DurationSign(duration);
649 auto throwIfInvalid = [&](double v, const char* name) {
650 // Step 2.a.
651 if (!std::isfinite(v)) {
652 return ThrowInvalidDurationPart(
653 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
656 // Steps 2.b-c.
657 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
658 return ThrowInvalidDurationPart(cx, v, name,
659 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
662 return true;
665 auto throwIfTooLarge = [&](double v, const char* name) {
666 if (std::abs(v) >= double(int64_t(1) << 32)) {
667 return ThrowInvalidDurationPart(
668 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
670 return true;
673 // Step 2.
674 if (!throwIfInvalid(years, "years")) {
675 return false;
677 if (!throwIfInvalid(months, "months")) {
678 return false;
680 if (!throwIfInvalid(weeks, "weeks")) {
681 return false;
683 if (!throwIfInvalid(days, "days")) {
684 return false;
686 if (!throwIfInvalid(hours, "hours")) {
687 return false;
689 if (!throwIfInvalid(minutes, "minutes")) {
690 return false;
692 if (!throwIfInvalid(seconds, "seconds")) {
693 return false;
695 if (!throwIfInvalid(milliseconds, "milliseconds")) {
696 return false;
698 if (!throwIfInvalid(microseconds, "microseconds")) {
699 return false;
701 if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
702 return false;
705 // Step 3.
706 if (!throwIfTooLarge(years, "years")) {
707 return false;
710 // Step 4.
711 if (!throwIfTooLarge(months, "months")) {
712 return false;
715 // Step 5.
716 if (!throwIfTooLarge(weeks, "weeks")) {
717 return false;
720 // Steps 6-8.
721 if (!NormalizeSeconds(duration)) {
722 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
723 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
724 return false;
727 MOZ_ASSERT(IsValidDuration(duration));
729 // Step 9.
730 return true;
734 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
735 * milliseconds, microseconds, nanoseconds )
737 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
738 const DateDuration& duration) {
739 const auto& [years, months, weeks, days] = duration;
741 // Step 1.
742 int32_t sign = ::DurationSign(duration);
744 auto throwIfInvalid = [&](int64_t v, const char* name) {
745 // Step 2.a. (Not applicable)
747 // Steps 2.b-c.
748 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
749 return ThrowInvalidDurationPart(cx, double(v), name,
750 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
753 return true;
756 auto throwIfTooLarge = [&](int64_t v, const char* name) {
757 if (std::abs(v) >= (int64_t(1) << 32)) {
758 return ThrowInvalidDurationPart(
759 cx, double(v), name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
761 return true;
764 // Step 2.
765 if (!throwIfInvalid(years, "years")) {
766 return false;
768 if (!throwIfInvalid(months, "months")) {
769 return false;
771 if (!throwIfInvalid(weeks, "weeks")) {
772 return false;
774 if (!throwIfInvalid(days, "days")) {
775 return false;
778 // Step 3.
779 if (!throwIfTooLarge(years, "years")) {
780 return false;
783 // Step 4.
784 if (!throwIfTooLarge(months, "months")) {
785 return false;
788 // Step 5.
789 if (!throwIfTooLarge(weeks, "weeks")) {
790 return false;
793 // Steps 6-8.
794 if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
795 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
796 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
797 return false;
800 MOZ_ASSERT(IsValidDuration(duration));
802 // Step 9.
803 return true;
807 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
808 * seconds, milliseconds, microseconds )
810 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
811 MOZ_ASSERT(IsIntegerDuration(duration));
813 // Step 1.
814 if (duration.years != 0) {
815 return TemporalUnit::Year;
818 // Step 2.
819 if (duration.months != 0) {
820 return TemporalUnit::Month;
823 // Step 3.
824 if (duration.weeks != 0) {
825 return TemporalUnit::Week;
828 // Step 4.
829 if (duration.days != 0) {
830 return TemporalUnit::Day;
833 // Step 5.
834 if (duration.hours != 0) {
835 return TemporalUnit::Hour;
838 // Step 6.
839 if (duration.minutes != 0) {
840 return TemporalUnit::Minute;
843 // Step 7.
844 if (duration.seconds != 0) {
845 return TemporalUnit::Second;
848 // Step 8.
849 if (duration.milliseconds != 0) {
850 return TemporalUnit::Millisecond;
853 // Step 9.
854 if (duration.microseconds != 0) {
855 return TemporalUnit::Microsecond;
858 // Step 10.
859 return TemporalUnit::Nanosecond;
863 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
864 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
866 static DurationObject* CreateTemporalDuration(JSContext* cx,
867 const CallArgs& args,
868 const Duration& duration) {
869 const auto& [years, months, weeks, days, hours, minutes, seconds,
870 milliseconds, microseconds, nanoseconds] = duration;
872 // Step 1.
873 if (!ThrowIfInvalidDuration(cx, duration)) {
874 return nullptr;
877 // Steps 2-3.
878 Rooted<JSObject*> proto(cx);
879 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
880 return nullptr;
883 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
884 if (!object) {
885 return nullptr;
888 // Steps 4-13.
889 // Add zero to convert -0 to +0.
890 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
891 object->setFixedSlot(DurationObject::MONTHS_SLOT,
892 NumberValue(months + (+0.0)));
893 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
894 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
895 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
896 object->setFixedSlot(DurationObject::MINUTES_SLOT,
897 NumberValue(minutes + (+0.0)));
898 object->setFixedSlot(DurationObject::SECONDS_SLOT,
899 NumberValue(seconds + (+0.0)));
900 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
901 NumberValue(milliseconds + (+0.0)));
902 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
903 NumberValue(microseconds + (+0.0)));
904 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
905 NumberValue(nanoseconds + (+0.0)));
907 // Step 14.
908 return object;
912 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
913 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
915 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
916 const Duration& duration) {
917 const auto& [years, months, weeks, days, hours, minutes, seconds,
918 milliseconds, microseconds, nanoseconds] = duration;
920 MOZ_ASSERT(IsInteger(years));
921 MOZ_ASSERT(IsInteger(months));
922 MOZ_ASSERT(IsInteger(weeks));
923 MOZ_ASSERT(IsInteger(days));
924 MOZ_ASSERT(IsInteger(hours));
925 MOZ_ASSERT(IsInteger(minutes));
926 MOZ_ASSERT(IsInteger(seconds));
927 MOZ_ASSERT(IsInteger(milliseconds));
928 MOZ_ASSERT(IsInteger(microseconds));
929 MOZ_ASSERT(IsInteger(nanoseconds));
931 // Step 1.
932 if (!ThrowIfInvalidDuration(cx, duration)) {
933 return nullptr;
936 // Steps 2-3.
937 auto* object = NewBuiltinClassInstance<DurationObject>(cx);
938 if (!object) {
939 return nullptr;
942 // Steps 4-13.
943 // Add zero to convert -0 to +0.
944 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
945 object->setFixedSlot(DurationObject::MONTHS_SLOT,
946 NumberValue(months + (+0.0)));
947 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
948 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
949 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
950 object->setFixedSlot(DurationObject::MINUTES_SLOT,
951 NumberValue(minutes + (+0.0)));
952 object->setFixedSlot(DurationObject::SECONDS_SLOT,
953 NumberValue(seconds + (+0.0)));
954 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
955 NumberValue(milliseconds + (+0.0)));
956 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
957 NumberValue(microseconds + (+0.0)));
958 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
959 NumberValue(nanoseconds + (+0.0)));
961 // Step 14.
962 return object;
966 * ToIntegerIfIntegral ( argument )
968 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
969 Handle<Value> argument, double* num) {
970 // Step 1.
971 double d;
972 if (!JS::ToNumber(cx, argument, &d)) {
973 return false;
976 // Step 2.
977 if (!js::IsInteger(d)) {
978 ToCStringBuf cbuf;
979 const char* numStr = NumberToCString(&cbuf, d);
981 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
982 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
983 name);
984 return false;
987 // Step 3.
988 *num = d;
989 return true;
993 * ToIntegerIfIntegral ( argument )
995 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
996 Handle<Value> argument, double* result) {
997 // Step 1.
998 double d;
999 if (!JS::ToNumber(cx, argument, &d)) {
1000 return false;
1003 // Step 2.
1004 if (!js::IsInteger(d)) {
1005 if (auto nameStr = js::QuoteString(cx, name)) {
1006 ToCStringBuf cbuf;
1007 const char* numStr = NumberToCString(&cbuf, d);
1009 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1010 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
1011 nameStr.get());
1013 return false;
1016 // Step 3.
1017 *result = d;
1018 return true;
1022 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1024 static bool ToTemporalPartialDurationRecord(
1025 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
1026 // Steps 1-3. (Not applicable in our implementation.)
1028 Rooted<Value> value(cx);
1029 bool any = false;
1031 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
1032 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
1033 &value)) {
1034 return false;
1037 if (!value.isUndefined()) {
1038 any = true;
1040 if (!ToIntegerIfIntegral(cx, name, value, num)) {
1041 return false;
1044 return true;
1047 // Steps 4-23.
1048 if (!getDurationProperty(cx->names().days, &result->days)) {
1049 return false;
1051 if (!getDurationProperty(cx->names().hours, &result->hours)) {
1052 return false;
1054 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
1055 return false;
1057 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
1058 return false;
1060 if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
1061 return false;
1063 if (!getDurationProperty(cx->names().months, &result->months)) {
1064 return false;
1066 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
1067 return false;
1069 if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
1070 return false;
1072 if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
1073 return false;
1075 if (!getDurationProperty(cx->names().years, &result->years)) {
1076 return false;
1079 // Step 24.
1080 if (!any) {
1081 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1082 JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
1083 return false;
1086 // Step 25.
1087 return true;
1091 * ToTemporalDurationRecord ( temporalDurationLike )
1093 bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
1094 Handle<Value> temporalDurationLike,
1095 Duration* result) {
1096 // Step 1.
1097 if (!temporalDurationLike.isObject()) {
1098 // Step 1.a.
1099 if (!temporalDurationLike.isString()) {
1100 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
1101 temporalDurationLike, nullptr, "not a string");
1102 return false;
1104 Rooted<JSString*> string(cx, temporalDurationLike.toString());
1106 // Step 1.b.
1107 return ParseTemporalDurationString(cx, string, result);
1110 Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
1112 // Step 2.
1113 if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
1114 *result = ToDuration(duration);
1115 return true;
1118 // Step 3.
1119 Duration duration = {};
1121 // Steps 4-14.
1122 if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
1123 return false;
1126 // Step 15.
1127 if (!ThrowIfInvalidDuration(cx, duration)) {
1128 return false;
1131 // Step 16.
1132 *result = duration;
1133 return true;
1137 * ToTemporalDuration ( item )
1139 Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
1140 Handle<Value> item) {
1141 // Step 1.
1142 if (item.isObject()) {
1143 JSObject* itemObj = &item.toObject();
1144 if (itemObj->canUnwrapAs<DurationObject>()) {
1145 return itemObj;
1149 // Step 2.
1150 Duration result;
1151 if (!ToTemporalDurationRecord(cx, item, &result)) {
1152 return nullptr;
1155 // Step 3.
1156 return CreateTemporalDuration(cx, result);
1160 * ToTemporalDuration ( item )
1162 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
1163 Duration* result) {
1164 auto obj = ToTemporalDuration(cx, item);
1165 if (!obj) {
1166 return false;
1169 *result = ToDuration(&obj.unwrap());
1170 return true;
1174 * DaysUntil ( earlier, later )
1176 int32_t js::temporal::DaysUntil(const PlainDate& earlier,
1177 const PlainDate& later) {
1178 MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
1179 MOZ_ASSERT(ISODateTimeWithinLimits(later));
1181 // Steps 1-2.
1182 int32_t epochDaysEarlier = MakeDay(earlier);
1183 MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
1185 // Steps 3-4.
1186 int32_t epochDaysLater = MakeDay(later);
1187 MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
1189 // Step 5.
1190 return epochDaysLater - epochDaysEarlier;
1194 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1196 static bool MoveRelativeDate(
1197 JSContext* cx, Handle<CalendarRecord> calendar,
1198 Handle<Wrapped<PlainDateObject*>> relativeTo, const DateDuration& duration,
1199 MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
1200 int32_t* daysResult) {
1201 auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
1202 if (!unwrappedRelativeTo) {
1203 return false;
1205 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1207 // Step 1.
1208 auto newDate = AddDate(cx, calendar, relativeTo, duration);
1209 if (!newDate) {
1210 return false;
1212 auto later = ToPlainDate(&newDate.unwrap());
1213 relativeToResult.set(newDate);
1215 // Step 2.
1216 *daysResult = DaysUntil(relativeToDate, later);
1217 MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
1219 // Step 3.
1220 return true;
1224 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1225 * months, weeks, days, precalculatedPlainDateTime )
1227 static bool MoveRelativeZonedDateTime(
1228 JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
1229 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
1230 const DateDuration& duration,
1231 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1232 MutableHandle<ZonedDateTime> result) {
1233 // Step 1.
1234 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1235 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1237 // Step 2.
1238 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1239 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1241 // Step 3.
1242 Instant intermediateNs;
1243 if (precalculatedPlainDateTime) {
1244 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1245 duration, *precalculatedPlainDateTime,
1246 &intermediateNs)) {
1247 return false;
1249 } else {
1250 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1251 duration, &intermediateNs)) {
1252 return false;
1255 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
1257 // Step 4.
1258 result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
1259 zonedDateTime.calendar()});
1260 return true;
1264 * Split duration into full days and remainding nanoseconds.
1266 static NormalizedTimeAndDays NormalizedTimeDurationToDays(
1267 const NormalizedTimeDuration& duration) {
1268 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1270 auto [seconds, nanoseconds] = duration;
1271 if (seconds < 0 && nanoseconds > 0) {
1272 seconds += 1;
1273 nanoseconds -= 1'000'000'000;
1276 int64_t days = seconds / ToSeconds(TemporalUnit::Day);
1277 seconds = seconds % ToSeconds(TemporalUnit::Day);
1279 int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
1281 constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
1282 MOZ_ASSERT(std::abs(time) < dayLength);
1284 return {days, time, dayLength};
1288 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1289 * microseconds, nanoseconds )
1291 static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
1292 int64_t minutes, int64_t seconds,
1293 int64_t milliseconds,
1294 double microseconds,
1295 double nanoseconds) {
1296 // Step 1.
1297 MOZ_ASSERT(IsValidDuration(
1298 {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
1299 double(milliseconds), microseconds, nanoseconds}));
1301 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1302 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1303 MOZ_ASSERT(IsSafeInteger(days));
1304 MOZ_ASSERT(IsSafeInteger(hours));
1305 MOZ_ASSERT(IsSafeInteger(minutes));
1306 MOZ_ASSERT(IsSafeInteger(seconds));
1308 // |milliseconds| is explicitly casted to double by consumers, so we can also
1309 // omit the `ℝ(𝔽(x))` conversion.
1311 // Step 2.
1312 // NB: Adds +0.0 to correctly handle negative zero.
1313 return {
1314 days,
1315 hours,
1316 minutes,
1317 seconds,
1318 milliseconds,
1319 microseconds + (+0.0),
1320 nanoseconds + (+0.0),
1325 * BalanceTimeDuration ( norm, largestUnit )
1327 TimeDuration js::temporal::BalanceTimeDuration(
1328 const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
1329 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1331 auto [seconds, nanoseconds] = duration;
1333 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1334 // Convert these back to their absolute value and adjust the seconds part
1335 // accordingly.
1337 // For example the nanoseconds duration |-1n| is represented as the
1338 // duration {seconds: -1, nanoseconds: 999'999'999}.
1339 if (seconds < 0 && nanoseconds > 0) {
1340 seconds += 1;
1341 nanoseconds -= ToNanoseconds(TemporalUnit::Second);
1344 // Step 1.
1345 int64_t days = 0;
1346 int64_t hours = 0;
1347 int64_t minutes = 0;
1348 int64_t milliseconds = 0;
1349 int64_t microseconds = 0;
1351 // Steps 2-3. (Not applicable in our implementation.)
1353 // We don't need to convert to positive numbers, because integer division
1354 // truncates and the %-operator has modulo semantics.
1356 // Steps 4-10.
1357 switch (largestUnit) {
1358 // Step 4.
1359 case TemporalUnit::Year:
1360 case TemporalUnit::Month:
1361 case TemporalUnit::Week:
1362 case TemporalUnit::Day: {
1363 // Step 4.a.
1364 microseconds = nanoseconds / 1000;
1366 // Step 4.b.
1367 nanoseconds = nanoseconds % 1000;
1369 // Step 4.c.
1370 milliseconds = microseconds / 1000;
1372 // Step 4.d.
1373 microseconds = microseconds % 1000;
1375 // Steps 4.e-f. (Not applicable)
1376 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1378 // Step 4.g.
1379 minutes = seconds / 60;
1381 // Step 4.h.
1382 seconds = seconds % 60;
1384 // Step 4.i.
1385 hours = minutes / 60;
1387 // Step 4.j.
1388 minutes = minutes % 60;
1390 // Step 4.k.
1391 days = hours / 24;
1393 // Step 4.l.
1394 hours = hours % 24;
1396 break;
1399 // Step 5.
1400 case TemporalUnit::Hour: {
1401 // Step 5.a.
1402 microseconds = nanoseconds / 1000;
1404 // Step 5.b.
1405 nanoseconds = nanoseconds % 1000;
1407 // Step 5.c.
1408 milliseconds = microseconds / 1000;
1410 // Step 5.d.
1411 microseconds = microseconds % 1000;
1413 // Steps 5.e-f. (Not applicable)
1414 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1416 // Step 5.g.
1417 minutes = seconds / 60;
1419 // Step 5.h.
1420 seconds = seconds % 60;
1422 // Step 5.i.
1423 hours = minutes / 60;
1425 // Step 5.j.
1426 minutes = minutes % 60;
1428 break;
1431 case TemporalUnit::Minute: {
1432 // Step 6.a.
1433 microseconds = nanoseconds / 1000;
1435 // Step 6.b.
1436 nanoseconds = nanoseconds % 1000;
1438 // Step 6.c.
1439 milliseconds = microseconds / 1000;
1441 // Step 6.d.
1442 microseconds = microseconds % 1000;
1444 // Steps 6.e-f. (Not applicable)
1445 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1447 // Step 6.g.
1448 minutes = seconds / 60;
1450 // Step 6.h.
1451 seconds = seconds % 60;
1453 break;
1456 // Step 7.
1457 case TemporalUnit::Second: {
1458 // Step 7.a.
1459 microseconds = nanoseconds / 1000;
1461 // Step 7.b.
1462 nanoseconds = nanoseconds % 1000;
1464 // Step 7.c.
1465 milliseconds = microseconds / 1000;
1467 // Step 7.d.
1468 microseconds = microseconds % 1000;
1470 // Steps 7.e-f. (Not applicable)
1471 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1473 break;
1476 // Step 8.
1477 case TemporalUnit::Millisecond: {
1478 static_assert((NormalizedTimeDuration::max().seconds + 1) *
1479 ToMilliseconds(TemporalUnit::Second) <=
1480 INT64_MAX,
1481 "total number duration milliseconds fits into int64");
1483 int64_t millis = seconds * ToMilliseconds(TemporalUnit::Second);
1485 // Set to zero per step 1.
1486 seconds = 0;
1488 // Step 8.a.
1489 microseconds = nanoseconds / 1000;
1491 // Step 8.b.
1492 nanoseconds = nanoseconds % 1000;
1494 // Step 8.c.
1495 milliseconds = microseconds / 1000;
1497 // Step 8.d.
1498 microseconds = microseconds % 1000;
1500 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1501 milliseconds += millis;
1503 // The number of normalized seconds must not exceed `2**53 - 1`.
1504 constexpr auto limit =
1505 (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second);
1506 constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
1508 static_assert(
1509 int64_t(double(max)) < limit && int64_t(double(max + 1)) >= limit,
1510 "max is the maximum allowed milliseconds value");
1512 auto totalMillis =
1513 (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
1514 if (totalMillis > max) {
1515 // FIXME: spec bug - handle too large duration values
1516 // https://github.com/tc39/proposal-temporal/issues/2785
1519 break;
1522 // Step 9.
1523 case TemporalUnit::Microsecond: {
1524 // Step 9.a.
1525 int64_t microseconds = nanoseconds / 1000;
1527 // Step 9.b.
1528 nanoseconds = nanoseconds % 1000;
1530 MOZ_ASSERT(std::abs(microseconds) <= 999'999);
1531 double micros =
1532 std::fma(double(seconds), ToMicroseconds(TemporalUnit::Second),
1533 double(microseconds));
1535 // The number of normalized seconds must not exceed `2**53 - 1`.
1536 constexpr auto limit = Int128{int64_t(1) << 53} *
1537 Int128{ToMicroseconds(TemporalUnit::Second)};
1538 constexpr auto max =
1539 (Int128{0x1e8} << 64) + Int128{0x47ff'ffff'fff7'ffff};
1540 static_assert(max < limit);
1542 auto totalMicros =
1543 (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
1544 Int128{microseconds};
1545 if (totalMicros > max) {
1546 // FIXME: spec bug - handle too large duration values
1547 // https://github.com/tc39/proposal-temporal/issues/2785
1550 // Step 11.
1551 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros,
1552 double(nanoseconds));
1555 // Step 10.
1556 case TemporalUnit::Nanosecond: {
1557 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
1558 double nanos =
1559 std::fma(double(seconds), ToNanoseconds(TemporalUnit::Second),
1560 double(nanoseconds));
1562 // The number of normalized seconds must not exceed `2**53 - 1`.
1563 constexpr auto limit = Int128{int64_t(1) << 53} *
1564 Int128{ToNanoseconds(TemporalUnit::Second)};
1565 constexpr auto max =
1566 (Int128{0x77359} << 64) + Int128{0x3fff'ffff'dfff'ffff};
1567 static_assert(max < limit);
1569 auto totalNanos =
1570 (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
1571 Int128{nanoseconds};
1572 if (totalNanos > max) {
1573 // FIXME: spec bug - handle too large duration values
1574 // https://github.com/tc39/proposal-temporal/issues/2785
1577 // Step 11.
1578 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos);
1581 case TemporalUnit::Auto:
1582 MOZ_CRASH("Unexpected temporal unit");
1585 // Step 11.
1586 return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
1587 double(microseconds), double(nanoseconds));
1591 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1592 * timeZoneRec, precalculatedPlainDateTime )
1594 static bool BalanceTimeDurationRelative(
1595 JSContext* cx, const NormalizedDuration& duration, TemporalUnit largestUnit,
1596 Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
1597 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1598 TimeDuration* result) {
1599 MOZ_ASSERT(IsValidDuration(duration));
1601 // Step 1.
1602 const auto& startNs = relativeTo.instant();
1604 // Step 2.
1605 const auto& startInstant = startNs;
1607 // Step 3.
1608 auto intermediateNs = startNs;
1610 // Step 4.
1611 PlainDateTime startDateTime;
1612 if (duration.date.days != 0) {
1613 // Step 4.a.
1614 if (!precalculatedPlainDateTime) {
1615 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1616 return false;
1618 precalculatedPlainDateTime =
1619 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1622 // Steps 4.b-c.
1623 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
1624 if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
1625 timeZone, isoCalendar, duration.date.days,
1626 &intermediateNs)) {
1627 return false;
1631 // Step 5.
1632 Instant endNs;
1633 if (!AddInstant(cx, intermediateNs, duration.time, &endNs)) {
1634 return false;
1636 MOZ_ASSERT(IsValidEpochInstant(endNs));
1638 // Step 6.
1639 auto normalized =
1640 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startInstant);
1642 // Step 7.
1643 if (normalized == NormalizedTimeDuration{}) {
1644 *result = {};
1645 return true;
1648 // Steps 8-9.
1649 int64_t days = 0;
1650 if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
1651 // Step 8.a.
1652 if (!precalculatedPlainDateTime) {
1653 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1654 return false;
1656 precalculatedPlainDateTime =
1657 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1660 // Step 8.b.
1661 NormalizedTimeAndDays timeAndDays;
1662 if (!NormalizedTimeDurationToDays(cx, normalized, relativeTo, timeZone,
1663 *precalculatedPlainDateTime,
1664 &timeAndDays)) {
1665 return false;
1668 // Step 8.c.
1669 days = timeAndDays.days;
1671 // Step 8.d.
1672 normalized = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
1673 MOZ_ASSERT_IF(days > 0, normalized >= NormalizedTimeDuration{});
1674 MOZ_ASSERT_IF(days < 0, normalized <= NormalizedTimeDuration{});
1676 // Step 8.e.
1677 largestUnit = TemporalUnit::Hour;
1680 // Step 10.
1681 auto balanceResult = BalanceTimeDuration(normalized, largestUnit);
1683 // Step 11.
1684 *result = {
1685 days,
1686 balanceResult.hours,
1687 balanceResult.minutes,
1688 balanceResult.seconds,
1689 balanceResult.milliseconds,
1690 balanceResult.microseconds,
1691 balanceResult.nanoseconds,
1693 MOZ_ASSERT(IsValidDuration(result->toDuration()));
1694 return true;
1698 * CreateDateDurationRecord ( years, months, weeks, days )
1700 static DateDuration CreateDateDurationRecord(int64_t years, int64_t months,
1701 int64_t weeks, int64_t days) {
1702 MOZ_ASSERT(IsValidDuration(Duration{
1703 double(years),
1704 double(months),
1705 double(weeks),
1706 double(days),
1707 }));
1708 return {years, months, weeks, days};
1712 * CreateDateDurationRecord ( years, months, weeks, days )
1714 static bool CreateDateDurationRecord(JSContext* cx, int64_t years,
1715 int64_t months, int64_t weeks,
1716 int64_t days, DateDuration* result) {
1717 auto duration = DateDuration{years, months, weeks, days};
1718 if (!ThrowIfInvalidDuration(cx, duration)) {
1719 return false;
1722 *result = duration;
1723 return true;
1726 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration& duration,
1727 TemporalUnit largestUnit) {
1728 MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
1730 // Steps 2-4.
1731 return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
1732 (largestUnit > TemporalUnit::Month && duration.months != 0) ||
1733 (largestUnit > TemporalUnit::Week && duration.weeks != 0);
1737 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1738 * plainRelativeTo, calendarRec )
1740 static bool UnbalanceDateDurationRelative(
1741 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1742 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1743 Handle<CalendarRecord> calendar, DateDuration* result) {
1744 MOZ_ASSERT(IsValidDuration(duration));
1746 auto [years, months, weeks, days] = duration;
1748 // Step 1. (Not applicable in our implementation.)
1750 // Steps 2-4.
1751 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1752 *result = duration;
1753 return true;
1756 // Step 5.
1757 MOZ_ASSERT(largestUnit != TemporalUnit::Year);
1759 // Step 6. (Not applicable in our implementation.)
1761 // Step 7.
1762 MOZ_ASSERT(
1763 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1765 // Step 8.
1766 if (largestUnit == TemporalUnit::Month) {
1767 // Step 8.a.
1768 MOZ_ASSERT(
1769 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1771 // Step 8.b.
1772 auto yearsDuration = DateDuration{years};
1774 // Step 8.c.
1775 Rooted<Wrapped<PlainDateObject*>> later(
1776 cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
1777 if (!later) {
1778 return false;
1781 // Steps 8.d-f.
1782 Duration untilResult;
1783 if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
1784 TemporalUnit::Month, &untilResult)) {
1785 return false;
1788 // Step 8.g.
1789 int64_t yearsInMonths = int64_t(untilResult.months);
1791 // Step 8.h.
1792 return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
1793 result);
1796 // Step 9.
1797 if (largestUnit == TemporalUnit::Week) {
1798 // Step 9.a.
1799 auto yearsMonthsDuration = DateDuration{years, months};
1801 // Step 9.b.
1802 auto later =
1803 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
1804 if (!later) {
1805 return false;
1807 auto laterDate = ToPlainDate(&later.unwrap());
1809 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1810 if (!unwrappedRelativeTo) {
1811 return false;
1813 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1815 // Step 9.c.
1816 int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
1818 // Step 9.d.
1819 return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
1820 result);
1823 // Step 10. (Not applicable in our implementation.)
1825 // Step 11.
1826 auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};
1828 // Step 12.
1829 auto later =
1830 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
1831 if (!later) {
1832 return false;
1834 auto laterDate = ToPlainDate(&later.unwrap());
1836 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1837 if (!unwrappedRelativeTo) {
1838 return false;
1840 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1842 // Step 13.
1843 int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
1845 // Step 14.
1846 return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
1847 result);
1851 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1852 * plainRelativeTo, calendarRec )
1854 static bool UnbalanceDateDurationRelative(JSContext* cx,
1855 const DateDuration& duration,
1856 TemporalUnit largestUnit,
1857 DateDuration* result) {
1858 MOZ_ASSERT(IsValidDuration(duration));
1860 // Step 1. (Not applicable.)
1862 // Step 2-4.
1863 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1864 *result = duration;
1865 return true;
1868 // Step 5.
1869 MOZ_ASSERT(largestUnit != TemporalUnit::Year);
1871 // Steps 6.
1872 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1873 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
1874 return false;
1878 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1879 * smallestUnit, plainRelativeTo, calendarRec )
1881 static bool BalanceDateDurationRelative(
1882 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1883 TemporalUnit smallestUnit,
1884 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1885 Handle<CalendarRecord> calendar, DateDuration* result) {
1886 MOZ_ASSERT(IsValidDuration(duration));
1887 MOZ_ASSERT(largestUnit <= smallestUnit);
1889 auto [years, months, weeks, days] = duration;
1891 // FIXME: spec issue - effectful code paths should be more fine-grained
1892 // similar to UnbalanceDateDurationRelative. For example:
1893 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1894 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1895 // 3. Else if days = 0, then no-op.
1897 // Also note that |weeks| is never balanced, even when non-zero.
1899 // Step 1. (Not applicable in our implementation.)
1901 // Steps 2-4.
1902 if (largestUnit > TemporalUnit::Week ||
1903 (years == 0 && months == 0 && weeks == 0 && days == 0)) {
1904 // Step 4.a.
1905 *result = duration;
1906 return true;
1909 // Step 5.
1910 if (!plainRelativeTo) {
1911 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1912 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
1913 "relativeTo");
1914 return false;
1917 // Step 6.
1918 MOZ_ASSERT(
1919 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1921 // Step 7.
1922 MOZ_ASSERT(
1923 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1925 // Steps 8-9. (Not applicable in our implementation.)
1927 auto untilAddedDate = [&](const DateDuration& duration,
1928 Duration* untilResult) {
1929 Rooted<Wrapped<PlainDateObject*>> later(
1930 cx, AddDate(cx, calendar, plainRelativeTo, duration));
1931 if (!later) {
1932 return false;
1935 return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
1936 untilResult);
1939 // Step 10.
1940 if (largestUnit == TemporalUnit::Year) {
1941 // Step 10.a.
1942 if (smallestUnit == TemporalUnit::Week) {
1943 // Step 10.a.i.
1944 MOZ_ASSERT(days == 0);
1946 // Step 10.a.ii.
1947 auto yearsMonthsDuration = DateDuration{years, months};
1949 // Steps 10.a.iii-iv.
1950 Duration untilResult;
1951 if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
1952 return false;
1955 // Step 10.a.v.
1956 *result = CreateDateDurationRecord(int64_t(untilResult.years),
1957 int64_t(untilResult.months), weeks, 0);
1958 return true;
1961 // Step 10.b.
1962 const auto& yearsMonthsWeeksDaysDuration = duration;
1964 // Steps 10.c-d.
1965 Duration untilResult;
1966 if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
1967 return false;
1970 // Step 10.e.
1971 *result = CreateDateDurationRecord(
1972 int64_t(untilResult.years), int64_t(untilResult.months),
1973 int64_t(untilResult.weeks), int64_t(untilResult.days));
1974 return true;
1977 // Step 11.
1978 if (largestUnit == TemporalUnit::Month) {
1979 // Step 11.a.
1980 MOZ_ASSERT(years == 0);
1982 // Step 11.b.
1983 if (smallestUnit == TemporalUnit::Week) {
1984 // Step 10.b.i.
1985 MOZ_ASSERT(days == 0);
1987 // Step 10.b.ii.
1988 *result = CreateDateDurationRecord(0, months, weeks, 0);
1989 return true;
1992 // Step 11.c.
1993 const auto& monthsWeeksDaysDuration = duration;
1995 // Steps 11.d-e.
1996 Duration untilResult;
1997 if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
1998 return false;
2001 // Step 11.f.
2002 *result = CreateDateDurationRecord(0, int64_t(untilResult.months),
2003 int64_t(untilResult.weeks),
2004 int64_t(untilResult.days));
2005 return true;
2008 // Step 12.
2009 MOZ_ASSERT(largestUnit == TemporalUnit::Week);
2011 // Step 13.
2012 MOZ_ASSERT(years == 0);
2014 // Step 14.
2015 MOZ_ASSERT(months == 0);
2017 // Step 15.
2018 const auto& weeksDaysDuration = duration;
2020 // Steps 16-17.
2021 Duration untilResult;
2022 if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
2023 return false;
2026 // Step 18.
2027 *result = CreateDateDurationRecord(0, 0, int64_t(untilResult.weeks),
2028 int64_t(untilResult.days));
2029 return true;
2033 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2034 * smallestUnit, plainRelativeTo, calendarRec )
2036 bool js::temporal::BalanceDateDurationRelative(
2037 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
2038 TemporalUnit smallestUnit,
2039 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2040 Handle<CalendarRecord> calendar, DateDuration* result) {
2041 MOZ_ASSERT(plainRelativeTo);
2042 MOZ_ASSERT(calendar.receiver());
2044 return ::BalanceDateDurationRelative(cx, duration, largestUnit, smallestUnit,
2045 plainRelativeTo, calendar, result);
2049 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2050 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2051 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2053 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2054 Duration* result) {
2055 MOZ_ASSERT(IsValidDuration(one));
2056 MOZ_ASSERT(IsValidDuration(two));
2058 // Steps 1-2. (Not applicable)
2060 // Step 3.
2061 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2063 // Step 4.
2064 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2066 // Step 5.
2067 auto largestUnit = std::min(largestUnit1, largestUnit2);
2069 // Step 6.a.
2070 if (largestUnit <= TemporalUnit::Week) {
2071 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2072 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
2073 "relativeTo");
2074 return false;
2077 // Step 6.b.
2078 auto normalized1 = NormalizeTimeDuration(one);
2080 // Step 6.c.
2081 auto normalized2 = NormalizeTimeDuration(two);
2083 // Step 6.d.
2084 NormalizedTimeDuration normalized;
2085 if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
2086 return false;
2089 // Step 6.e.
2090 int64_t days1 = mozilla::AssertedCast<int64_t>(one.days);
2091 int64_t days2 = mozilla::AssertedCast<int64_t>(two.days);
2092 auto totalDays = mozilla::CheckedInt64(days1) + days2;
2093 MOZ_ASSERT(totalDays.isValid(), "adding two duration days can't overflow");
2095 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized, totalDays.value(),
2096 &normalized)) {
2097 return false;
2100 // Step 6.f.
2101 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2103 // Steps 6.g.
2104 *result = balanced.toDuration();
2105 return true;
2109 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2110 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2111 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2113 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2114 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2115 Handle<CalendarRecord> calendar, Duration* result) {
2116 MOZ_ASSERT(IsValidDuration(one));
2117 MOZ_ASSERT(IsValidDuration(two));
2119 // Steps 1-2. (Not applicable)
2121 // Step 3.
2122 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2124 // Step 4.
2125 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2127 // Step 5.
2128 auto largestUnit = std::min(largestUnit1, largestUnit2);
2130 // Step 6. (Not applicable)
2132 // Step 7.a. (Not applicable in our implementation.)
2134 // Step 7.b.
2135 auto dateDuration1 = one.toDateDuration();
2137 // Step 7.c.
2138 auto dateDuration2 = two.toDateDuration();
2140 // Step 7.d.
2141 Rooted<Wrapped<PlainDateObject*>> intermediate(
2142 cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
2143 if (!intermediate) {
2144 return false;
2147 // Step 7.e.
2148 Rooted<Wrapped<PlainDateObject*>> end(
2149 cx, AddDate(cx, calendar, intermediate, dateDuration2));
2150 if (!end) {
2151 return false;
2154 // Step 7.f.
2155 auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
2157 // Steps 7.g-i.
2158 DateDuration dateDifference;
2159 if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
2160 &dateDifference)) {
2161 return false;
2164 // Step 7.j.
2165 auto normalized1 = NormalizeTimeDuration(one);
2167 // Step 7.k.
2168 auto normalized2 = NormalizeTimeDuration(two);
2170 // Step 7.l.
2171 NormalizedTimeDuration normalized1WithDays;
2172 if (!Add24HourDaysToNormalizedTimeDuration(
2173 cx, normalized1, dateDifference.days, &normalized1WithDays)) {
2174 return false;
2177 // Step 7.m.
2178 NormalizedTimeDuration normalized;
2179 if (!AddNormalizedTimeDuration(cx, normalized1WithDays, normalized2,
2180 &normalized)) {
2181 return false;
2184 // Step 7.n.
2185 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2187 // Steps 7.o.
2188 *result = {
2189 double(dateDifference.years), double(dateDifference.months),
2190 double(dateDifference.weeks), double(balanced.days),
2191 double(balanced.hours), double(balanced.minutes),
2192 double(balanced.seconds), double(balanced.milliseconds),
2193 balanced.microseconds, balanced.nanoseconds,
2195 MOZ_ASSERT(IsValidDuration(*result));
2196 return true;
2200 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2201 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2202 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2204 static bool AddDuration(
2205 JSContext* cx, const Duration& one, const Duration& two,
2206 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2207 Handle<TimeZoneRecord> timeZone,
2208 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2209 Duration* result) {
2210 // Steps 1-2. (Not applicable)
2212 // Step 3.
2213 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2215 // Step 4.
2216 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2218 // Step 5.
2219 auto largestUnit = std::min(largestUnit1, largestUnit2);
2221 // Steps 6-7. (Not applicable)
2223 // Steps 8-9. (Not applicable in our implementation.)
2225 // Step 10.
2226 bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
2228 // Steps 11-17.
2229 if (!startDateTimeNeeded) {
2230 // Steps 11-12. (Not applicable)
2232 // Step 13.
2233 auto normalized1 = NormalizeTimeDuration(one);
2235 // Step 14.
2236 auto normalized2 = NormalizeTimeDuration(two);
2238 // Step 15. (Inlined AddZonedDateTime, step 6.)
2239 Instant intermediateNs;
2240 if (!AddInstant(cx, zonedRelativeTo.instant(), normalized1,
2241 &intermediateNs)) {
2242 return false;
2244 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2246 // Step 16. (Inlined AddZonedDateTime, step 6.)
2247 Instant endNs;
2248 if (!AddInstant(cx, intermediateNs, normalized2, &endNs)) {
2249 return false;
2251 MOZ_ASSERT(IsValidEpochInstant(endNs));
2253 // Step 17.a.
2254 auto normalized = NormalizedTimeDurationFromEpochNanosecondsDifference(
2255 endNs, zonedRelativeTo.instant());
2257 // Step 17.b.
2258 auto balanced = BalanceTimeDuration(normalized, largestUnit);
2260 // Step 17.c.
2261 *result = balanced.toDuration();
2262 return true;
2265 // Steps 11-12.
2266 PlainDateTime startDateTime;
2267 if (!precalculatedPlainDateTime) {
2268 if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
2269 &startDateTime)) {
2270 return false;
2272 } else {
2273 startDateTime = *precalculatedPlainDateTime;
2276 // Step 13.
2277 auto normalized1 = CreateNormalizedDurationRecord(one);
2279 // Step 14.
2280 auto normalized2 = CreateNormalizedDurationRecord(two);
2282 // Step 15.
2283 Instant intermediateNs;
2284 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2285 normalized1, startDateTime, &intermediateNs)) {
2286 return false;
2288 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2290 // Step 16.
2291 Instant endNs;
2292 if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, normalized2,
2293 &endNs)) {
2294 return false;
2296 MOZ_ASSERT(IsValidEpochInstant(endNs));
2298 // Step 17. (Not applicable)
2300 // Step 18.
2301 NormalizedDuration difference;
2302 if (!DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs, timeZone,
2303 calendar, largestUnit, startDateTime,
2304 &difference)) {
2305 return false;
2308 // Step 19.
2309 auto balanced = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
2311 // Step 20.
2312 *result = {
2313 double(difference.date.years), double(difference.date.months),
2314 double(difference.date.weeks), double(difference.date.days),
2315 double(balanced.hours), double(balanced.minutes),
2316 double(balanced.seconds), double(balanced.milliseconds),
2317 balanced.microseconds, balanced.nanoseconds,
2319 MOZ_ASSERT(IsValidDuration(*result));
2320 return true;
2324 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2325 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2326 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2328 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2329 Handle<ZonedDateTime> zonedRelativeTo,
2330 Handle<CalendarRecord> calendar,
2331 Handle<TimeZoneRecord> timeZone, Duration* result) {
2332 return AddDuration(cx, one, two, zonedRelativeTo, calendar, timeZone,
2333 mozilla::Nothing(), result);
2337 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2338 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2339 * precalculatedPlainDateTime )
2341 static bool AdjustRoundedDurationDays(
2342 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2343 TemporalUnit unit, TemporalRoundingMode roundingMode,
2344 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2345 Handle<TimeZoneRecord> timeZone,
2346 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2347 NormalizedDuration* result) {
2348 MOZ_ASSERT(IsValidDuration(duration));
2350 // Step 1.
2351 if ((TemporalUnit::Year <= unit && unit <= TemporalUnit::Day) ||
2352 (unit == TemporalUnit::Nanosecond && increment == Increment{1})) {
2353 *result = duration;
2354 return true;
2357 // The increment is limited for all smaller temporal units.
2358 MOZ_ASSERT(increment < MaximumTemporalDurationRoundingIncrement(unit));
2360 // Step 2.
2361 MOZ_ASSERT(precalculatedPlainDateTime);
2363 // Step 3.
2364 int32_t direction = NormalizedTimeDurationSign(duration.time);
2366 // Steps 4-5.
2367 Instant dayStart;
2368 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2369 duration.date, *precalculatedPlainDateTime,
2370 &dayStart)) {
2371 return false;
2373 MOZ_ASSERT(IsValidEpochInstant(dayStart));
2375 // Step 6.
2376 PlainDateTime dayStartDateTime;
2377 if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
2378 return false;
2381 // Step 7.
2382 Instant dayEnd;
2383 if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
2384 zonedRelativeTo.calendar(), direction, &dayEnd)) {
2385 return false;
2387 MOZ_ASSERT(IsValidEpochInstant(dayEnd));
2389 // Step 8.
2390 auto dayLengthNs =
2391 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd, dayStart);
2392 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs.to<InstantSpan>()));
2394 // Step 9.
2395 NormalizedTimeDuration oneDayLess;
2396 if (!SubtractNormalizedTimeDuration(cx, duration.time, dayLengthNs,
2397 &oneDayLess)) {
2398 return false;
2401 // Step 10.
2402 int32_t oneDayLessSign = NormalizedTimeDurationSign(oneDayLess);
2403 if ((direction > 0 && oneDayLessSign < 0) ||
2404 (direction < 0 && oneDayLessSign > 0)) {
2405 *result = duration;
2406 return true;
2409 // Step 11.
2410 Duration adjustedDateDuration;
2411 if (!AddDuration(cx, duration.date.toDuration(), {0, 0, 0, double(direction)},
2412 zonedRelativeTo, calendar, timeZone,
2413 precalculatedPlainDateTime, &adjustedDateDuration)) {
2414 return false;
2417 // Step 12.
2418 auto roundedTime = RoundDuration(oneDayLess, increment, unit, roundingMode);
2420 // Step 13.
2421 return CombineDateAndNormalizedTimeDuration(
2422 cx, adjustedDateDuration.toDateDuration(), roundedTime, result);
2426 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2427 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2428 * precalculatedPlainDateTime )
2430 bool js::temporal::AdjustRoundedDurationDays(
2431 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2432 TemporalUnit unit, TemporalRoundingMode roundingMode,
2433 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2434 Handle<TimeZoneRecord> timeZone,
2435 const PlainDateTime& precalculatedPlainDateTime,
2436 NormalizedDuration* result) {
2437 return ::AdjustRoundedDurationDays(
2438 cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
2439 timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
2442 static bool NumberToStringBuilder(JSContext* cx, double num,
2443 JSStringBuilder& sb) {
2444 MOZ_ASSERT(IsInteger(num));
2445 MOZ_ASSERT(num >= 0);
2446 MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2448 ToCStringBuf cbuf;
2449 size_t length;
2450 const char* numStr = NumberToCString(&cbuf, num, &length);
2452 return sb.append(numStr, length);
2455 static Duration AbsoluteDuration(const Duration& duration) {
2456 return {
2457 std::abs(duration.years), std::abs(duration.months),
2458 std::abs(duration.weeks), std::abs(duration.days),
2459 std::abs(duration.hours), std::abs(duration.minutes),
2460 std::abs(duration.seconds), std::abs(duration.milliseconds),
2461 std::abs(duration.microseconds), std::abs(duration.nanoseconds),
2466 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2468 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
2469 int32_t subSecondNanoseconds,
2470 Precision precision) {
2471 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
2472 MOZ_ASSERT(precision != Precision::Minute());
2474 // Steps 1-2.
2475 if (precision == Precision::Auto()) {
2476 // Step 1.a.
2477 if (subSecondNanoseconds == 0) {
2478 return true;
2481 // Step 3. (Reordered)
2482 if (!result.append('.')) {
2483 return false;
2486 // Steps 1.b-c.
2487 int32_t k = 100'000'000;
2488 do {
2489 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2490 return false;
2492 subSecondNanoseconds %= k;
2493 k /= 10;
2494 } while (subSecondNanoseconds);
2495 } else {
2496 // Step 2.a.
2497 uint8_t p = precision.value();
2498 if (p == 0) {
2499 return true;
2502 // Step 3. (Reordered)
2503 if (!result.append('.')) {
2504 return false;
2507 // Steps 2.b-c.
2508 int32_t k = 100'000'000;
2509 for (uint8_t i = 0; i < precision.value(); i++) {
2510 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2511 return false;
2513 subSecondNanoseconds %= k;
2514 k /= 10;
2518 return true;
2522 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2523 * normSeconds, precision )
2525 static JSString* TemporalDurationToString(JSContext* cx,
2526 const Duration& duration,
2527 Precision precision) {
2528 MOZ_ASSERT(IsValidDuration(duration));
2529 MOZ_ASSERT(precision != Precision::Minute());
2531 // Fast path for zero durations.
2532 if (duration == Duration{} &&
2533 (precision == Precision::Auto() || precision.value() == 0)) {
2534 return NewStringCopyZ<CanGC>(cx, "PT0S");
2537 // Convert to absolute values up front. This is okay to do, because when the
2538 // duration is valid, all components have the same sign.
2539 const auto& [years, months, weeks, days, hours, minutes, seconds,
2540 milliseconds, microseconds, nanoseconds] =
2541 AbsoluteDuration(duration);
2543 // Years to seconds parts are all safe integers for valid durations.
2544 MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2545 MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2546 MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2547 MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2548 MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2549 MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2550 MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2552 auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
2553 microseconds, nanoseconds);
2555 // Step 1.
2556 int32_t sign = DurationSign(duration);
2558 // Steps 2 and 7.
2559 JSStringBuilder result(cx);
2561 // Step 13. (Reordered)
2562 if (sign < 0) {
2563 if (!result.append('-')) {
2564 return nullptr;
2568 // Step 14. (Reordered)
2569 if (!result.append('P')) {
2570 return nullptr;
2573 // Step 3.
2574 if (years != 0) {
2575 if (!NumberToStringBuilder(cx, years, result)) {
2576 return nullptr;
2578 if (!result.append('Y')) {
2579 return nullptr;
2583 // Step 4.
2584 if (months != 0) {
2585 if (!NumberToStringBuilder(cx, months, result)) {
2586 return nullptr;
2588 if (!result.append('M')) {
2589 return nullptr;
2593 // Step 5.
2594 if (weeks != 0) {
2595 if (!NumberToStringBuilder(cx, weeks, result)) {
2596 return nullptr;
2598 if (!result.append('W')) {
2599 return nullptr;
2603 // Step 6.
2604 if (days != 0) {
2605 if (!NumberToStringBuilder(cx, days, result)) {
2606 return nullptr;
2608 if (!result.append('D')) {
2609 return nullptr;
2613 // Step 7. (Moved above)
2615 // Steps 10-11. (Reordered)
2616 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
2617 days == 0 && hours == 0 && minutes == 0;
2619 // Steps 8-9, 12, and 15.
2620 bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
2621 zeroMinutesAndHigher || precision != Precision::Auto();
2622 if (hours != 0 || minutes != 0 || hasSecondsPart) {
2623 // Step 15. (Reordered)
2624 if (!result.append('T')) {
2625 return nullptr;
2628 // Step 8.
2629 if (hours != 0) {
2630 if (!NumberToStringBuilder(cx, hours, result)) {
2631 return nullptr;
2633 if (!result.append('H')) {
2634 return nullptr;
2638 // Step 9.
2639 if (minutes != 0) {
2640 if (!NumberToStringBuilder(cx, minutes, result)) {
2641 return nullptr;
2643 if (!result.append('M')) {
2644 return nullptr;
2648 // Step 12.
2649 if (hasSecondsPart) {
2650 // Step 12.a.
2651 if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
2652 return nullptr;
2655 // Step 12.b.
2656 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
2657 precision)) {
2658 return nullptr;
2661 // Step 12.c.
2662 if (!result.append('S')) {
2663 return nullptr;
2668 // Steps 13-15. (Moved above)
2670 // Step 16.
2671 return result.finishString();
2675 * ToRelativeTemporalObject ( options )
2677 static bool ToRelativeTemporalObject(
2678 JSContext* cx, Handle<JSObject*> options,
2679 MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
2680 MutableHandle<ZonedDateTime> zonedRelativeTo,
2681 MutableHandle<TimeZoneRecord> timeZoneRecord) {
2682 // Step 1.
2683 Rooted<Value> value(cx);
2684 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
2685 return false;
2688 // Step 2.
2689 if (value.isUndefined()) {
2690 plainRelativeTo.set(nullptr);
2691 zonedRelativeTo.set(ZonedDateTime{});
2692 timeZoneRecord.set(TimeZoneRecord{});
2693 return true;
2696 // Step 3.
2697 auto offsetBehaviour = OffsetBehaviour::Option;
2699 // Step 4.
2700 auto matchBehaviour = MatchBehaviour::MatchExactly;
2702 // Steps 5-6.
2703 PlainDateTime dateTime;
2704 Rooted<CalendarValue> calendar(cx);
2705 Rooted<TimeZoneValue> timeZone(cx);
2706 int64_t offsetNs;
2707 if (value.isObject()) {
2708 Rooted<JSObject*> obj(cx, &value.toObject());
2710 // Step 5.a.
2711 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
2712 auto instant = ToInstant(zonedDateTime);
2713 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
2714 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
2716 if (!timeZone.wrap(cx)) {
2717 return false;
2719 if (!calendar.wrap(cx)) {
2720 return false;
2723 // Step 5.a.i.
2724 Rooted<TimeZoneRecord> timeZoneRec(cx);
2725 if (!CreateTimeZoneMethodsRecord(
2726 cx, timeZone,
2728 TimeZoneMethod::GetOffsetNanosecondsFor,
2729 TimeZoneMethod::GetPossibleInstantsFor,
2731 &timeZoneRec)) {
2732 return false;
2735 // Step 5.a.ii.
2736 plainRelativeTo.set(nullptr);
2737 zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
2738 timeZoneRecord.set(timeZoneRec);
2739 return true;
2742 // Step 5.b.
2743 if (obj->canUnwrapAs<PlainDateObject>()) {
2744 plainRelativeTo.set(obj);
2745 zonedRelativeTo.set(ZonedDateTime{});
2746 timeZoneRecord.set(TimeZoneRecord{});
2747 return true;
2750 // Step 5.c.
2751 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
2752 auto plainDateTime = ToPlainDate(dateTime);
2754 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
2755 if (!calendar.wrap(cx)) {
2756 return false;
2759 // Step 5.c.i.
2760 auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
2761 if (!plainDate) {
2762 return false;
2765 // Step 5.c.ii.
2766 plainRelativeTo.set(plainDate);
2767 zonedRelativeTo.set(ZonedDateTime{});
2768 timeZoneRecord.set(TimeZoneRecord{});
2769 return true;
2772 // Step 5.d.
2773 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
2774 return false;
2777 // Step 5.e.
2778 Rooted<CalendarRecord> calendarRec(cx);
2779 if (!CreateCalendarMethodsRecord(cx, calendar,
2781 CalendarMethod::DateFromFields,
2782 CalendarMethod::Fields,
2784 &calendarRec)) {
2785 return false;
2788 // Step 5.f.
2789 JS::RootedVector<PropertyKey> fieldNames(cx);
2790 if (!CalendarFields(cx, calendarRec,
2791 {CalendarField::Day, CalendarField::Month,
2792 CalendarField::MonthCode, CalendarField::Year},
2793 &fieldNames)) {
2794 return false;
2797 // Step 5.g.
2798 if (!AppendSorted(cx, fieldNames.get(),
2800 TemporalField::Hour,
2801 TemporalField::Microsecond,
2802 TemporalField::Millisecond,
2803 TemporalField::Minute,
2804 TemporalField::Nanosecond,
2805 TemporalField::Offset,
2806 TemporalField::Second,
2807 TemporalField::TimeZone,
2808 })) {
2809 return false;
2812 // Step 5.h.
2813 Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, obj, fieldNames));
2814 if (!fields) {
2815 return false;
2818 // Step 5.i.
2819 Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
2820 if (!dateOptions) {
2821 return false;
2824 // Step 5.j.
2825 Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
2826 if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
2827 return false;
2830 // Step 5.k.
2831 if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
2832 &dateTime)) {
2833 return false;
2836 // Step 5.l.
2837 Rooted<Value> offset(cx);
2838 if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
2839 return false;
2842 // Step 5.m.
2843 Rooted<Value> timeZoneValue(cx);
2844 if (!GetProperty(cx, fields, fields, cx->names().timeZone,
2845 &timeZoneValue)) {
2846 return false;
2849 // Step 5.n.
2850 if (!timeZoneValue.isUndefined()) {
2851 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
2852 return false;
2856 // Step 5.o.
2857 if (offset.isUndefined()) {
2858 offsetBehaviour = OffsetBehaviour::Wall;
2861 // Steps 8-9.
2862 if (timeZone) {
2863 if (offsetBehaviour == OffsetBehaviour::Option) {
2864 MOZ_ASSERT(!offset.isUndefined());
2865 MOZ_ASSERT(offset.isString());
2867 // Step 8.a.
2868 Rooted<JSString*> offsetString(cx, offset.toString());
2869 if (!offsetString) {
2870 return false;
2873 // Step 8.b.
2874 if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
2875 return false;
2877 } else {
2878 // Step 9.
2879 offsetNs = 0;
2882 } else {
2883 // Step 6.a.
2884 if (!value.isString()) {
2885 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
2886 nullptr, "not a string");
2887 return false;
2889 Rooted<JSString*> string(cx, value.toString());
2891 // Step 6.b.
2892 bool isUTC;
2893 bool hasOffset;
2894 int64_t timeZoneOffset;
2895 Rooted<ParsedTimeZone> timeZoneAnnotation(cx);
2896 Rooted<JSString*> calendarString(cx);
2897 if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
2898 &hasOffset, &timeZoneOffset,
2899 &timeZoneAnnotation, &calendarString)) {
2900 return false;
2903 // Step 6.c. (Not applicable in our implementation.)
2905 // Steps 6.e-f.
2906 if (timeZoneAnnotation) {
2907 // Step 6.f.i.
2908 if (!ToTemporalTimeZone(cx, timeZoneAnnotation, &timeZone)) {
2909 return false;
2912 // Steps 6.f.ii-iii.
2913 if (isUTC) {
2914 offsetBehaviour = OffsetBehaviour::Exact;
2915 } else if (!hasOffset) {
2916 offsetBehaviour = OffsetBehaviour::Wall;
2919 // Step 6.f.iv.
2920 matchBehaviour = MatchBehaviour::MatchMinutes;
2921 } else {
2922 MOZ_ASSERT(!timeZone);
2925 // Steps 6.g-j.
2926 if (calendarString) {
2927 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
2928 return false;
2930 } else {
2931 calendar.set(CalendarValue(cx->names().iso8601));
2934 // Steps 8-9.
2935 if (timeZone) {
2936 if (offsetBehaviour == OffsetBehaviour::Option) {
2937 MOZ_ASSERT(hasOffset);
2939 // Step 8.a.
2940 offsetNs = timeZoneOffset;
2941 } else {
2942 // Step 9.
2943 offsetNs = 0;
2948 // Step 7.
2949 if (!timeZone) {
2950 // Step 7.a.
2951 auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
2952 if (!plainDate) {
2953 return false;
2956 plainRelativeTo.set(plainDate);
2957 zonedRelativeTo.set(ZonedDateTime{});
2958 timeZoneRecord.set(TimeZoneRecord{});
2959 return true;
2962 // Steps 8-9. (Moved above)
2964 // Step 10.
2965 Rooted<TimeZoneRecord> timeZoneRec(cx);
2966 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2968 TimeZoneMethod::GetOffsetNanosecondsFor,
2969 TimeZoneMethod::GetPossibleInstantsFor,
2971 &timeZoneRec)) {
2972 return false;
2975 // Step 11.
2976 Instant epochNanoseconds;
2977 if (!InterpretISODateTimeOffset(
2978 cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
2979 TemporalDisambiguation::Compatible, TemporalOffset::Reject,
2980 matchBehaviour, &epochNanoseconds)) {
2981 return false;
2983 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
2985 // Step 12.
2986 plainRelativeTo.set(nullptr);
2987 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
2988 timeZoneRecord.set(timeZoneRec);
2989 return true;
2993 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
2994 * methods )
2996 static bool CreateCalendarMethodsRecordFromRelativeTo(
2997 JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2998 Handle<ZonedDateTime> zonedRelativeTo,
2999 mozilla::EnumSet<CalendarMethod> methods,
3000 MutableHandle<CalendarRecord> result) {
3001 // Step 1.
3002 if (zonedRelativeTo) {
3003 return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
3004 result);
3007 // Step 2.
3008 if (plainRelativeTo) {
3009 auto* unwrapped = plainRelativeTo.unwrap(cx);
3010 if (!unwrapped) {
3011 return false;
3014 Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
3015 if (!calendar.wrap(cx)) {
3016 return false;
3019 return CreateCalendarMethodsRecord(cx, calendar, methods, result);
3022 // Step 3.
3023 return true;
3026 struct RoundedDuration final {
3027 NormalizedDuration duration;
3028 double total = 0;
3031 enum class ComputeRemainder : bool { No, Yes };
3034 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3036 static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
3037 const NormalizedTimeDuration& duration, const TemporalUnit unit,
3038 Increment increment, TemporalRoundingMode roundingMode) {
3039 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3040 MOZ_ASSERT(unit > TemporalUnit::Day);
3041 MOZ_ASSERT(increment <= MaximumTemporalDurationRoundingIncrement(unit));
3043 int64_t divisor = ToNanoseconds(unit) * increment.value();
3044 MOZ_ASSERT(divisor > 0);
3045 MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
3047 auto totalNanoseconds = duration.toNanoseconds();
3048 auto rounded =
3049 RoundNumberToIncrement(totalNanoseconds, Int128{divisor}, roundingMode);
3050 return NormalizedTimeDuration::fromNanoseconds(rounded);
3054 * DivideNormalizedTimeDuration ( d, divisor )
3056 static double TotalNormalizedTimeDuration(
3057 const NormalizedTimeDuration& duration, const TemporalUnit unit) {
3058 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3059 MOZ_ASSERT(unit > TemporalUnit::Day);
3061 auto numerator = duration.toNanoseconds();
3062 auto denominator = Int128{ToNanoseconds(unit)};
3063 return FractionToDouble(numerator, denominator);
3067 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3068 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3069 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3071 NormalizedTimeDuration js::temporal::RoundDuration(
3072 const NormalizedTimeDuration& duration, Increment increment,
3073 TemporalUnit unit, TemporalRoundingMode roundingMode) {
3074 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3075 MOZ_ASSERT(unit > TemporalUnit::Day);
3077 // Steps 1-13. (Not applicable)
3079 // Steps 14-19.
3080 auto rounded = RoundNormalizedTimeDurationToIncrement(
3081 duration, unit, increment, roundingMode);
3082 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded));
3084 // Step 20.
3085 return rounded;
3088 struct FractionalDays final {
3089 int64_t days = 0;
3090 int64_t time = 0;
3091 int64_t dayLength = 0;
3093 FractionalDays() = default;
3095 explicit FractionalDays(int64_t durationDays,
3096 const NormalizedTimeAndDays& timeAndDays)
3097 : days(durationDays + timeAndDays.days),
3098 time(timeAndDays.time),
3099 dayLength(timeAndDays.dayLength) {
3100 MOZ_ASSERT(durationDays <= (int64_t(1) << 53) / (24 * 60 * 60));
3101 MOZ_ASSERT(timeAndDays.days <= (int64_t(1) << 53) / (24 * 60 * 60));
3103 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3104 // positive and less than 2**53.
3105 MOZ_ASSERT(dayLength > 0);
3106 MOZ_ASSERT(dayLength < int64_t(1) << 53);
3108 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3109 // less than |timeAndDays.dayLength|.
3110 MOZ_ASSERT(std::abs(time) < dayLength);
3113 FractionalDays operator+=(int32_t epochDays) {
3114 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3115 days += epochDays;
3116 return *this;
3119 FractionalDays operator-=(int32_t epochDays) {
3120 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3121 days -= epochDays;
3122 return *this;
3125 int64_t truncate() const {
3126 int64_t truncatedDays = days;
3127 if (time > 0) {
3128 // Round toward positive infinity when the integer days are negative and
3129 // the fractional part is positive.
3130 if (truncatedDays < 0) {
3131 truncatedDays += 1;
3133 } else if (time < 0) {
3134 // Round toward negative infinity when the integer days are positive and
3135 // the fractional part is negative.
3136 if (truncatedDays > 0) {
3137 truncatedDays -= 1;
3140 return truncatedDays;
3143 int32_t sign() const {
3144 if (days != 0) {
3145 return days < 0 ? -1 : 1;
3147 return time < 0 ? -1 : time > 0 ? 1 : 0;
3151 struct Fraction final {
3152 int64_t numerator = 0;
3153 int32_t denominator = 0;
3155 constexpr Fraction() = default;
3157 constexpr Fraction(int64_t numerator, int32_t denominator)
3158 : numerator(numerator), denominator(denominator) {
3159 MOZ_ASSERT(denominator > 0);
3163 struct RoundedNumber final {
3164 Int128 rounded;
3165 double total = 0;
3168 static RoundedNumber RoundNumberToIncrement(
3169 const Fraction& fraction, const FractionalDays& fractionalDays,
3170 Increment increment, TemporalRoundingMode roundingMode,
3171 ComputeRemainder computeRemainder) {
3172 #ifdef DEBUG
3173 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3174 static constexpr int64_t maxDurationDays =
3175 (int64_t(1) << 53) / (24 * 60 * 60);
3177 // Numbers of days between nsMinInstant and nsMaxInstant.
3178 static constexpr int32_t epochDays = 200'000'000;
3180 // Maximum number of days in |fractionalDays|.
3181 static constexpr int64_t maxFractionalDays =
3182 2 * maxDurationDays + 2 * epochDays;
3183 #endif
3185 MOZ_ASSERT(std::abs(fraction.numerator) < (int64_t(1) << 32) * 2);
3186 MOZ_ASSERT(fraction.denominator > 0);
3187 MOZ_ASSERT(fraction.denominator <= epochDays);
3188 MOZ_ASSERT(std::abs(fractionalDays.days) <= maxFractionalDays);
3189 MOZ_ASSERT(fractionalDays.dayLength > 0);
3190 MOZ_ASSERT(fractionalDays.dayLength < (int64_t(1) << 53));
3191 MOZ_ASSERT(std::abs(fractionalDays.time) < fractionalDays.dayLength);
3192 MOZ_ASSERT(increment <= Increment::max());
3194 // clang-format off
3196 // Change the representation of |fractionalWeeks| from a real number to a
3197 // rational number, because we don't support arbitrary precision real
3198 // numbers.
3200 // |fractionalWeeks| is defined as:
3202 // fractionalWeeks
3203 // = weeks + days' / abs(oneWeekDays)
3205 // where days' = days + nanoseconds / dayLength.
3207 // The fractional part |nanoseconds / dayLength| is from step 7.
3209 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3211 // fractionalWeeks
3212 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3213 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3214 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3216 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3217 // to omit the multiplication by |dayLength| when the rounding conditions are
3218 // appropriately modified to account for the |nanoseconds / dayLength| part.
3219 // This allows to implement rounding using only int64 values.
3221 // This optimization is currently only implemented when |nanoseconds| is zero.
3223 // Example how to expand this optimization for non-zero |nanoseconds|:
3225 // |Round(fraction / increment) * increment| with:
3226 // fraction = numerator / denominator
3227 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3228 // denominator = dayLength * abs(oneWeekDays)
3230 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3232 // |Round(fraction / increment) * increment| with:
3233 // fraction = numerator / denominator
3234 // numerator = weeks * abs(oneWeekDays) + days
3235 // denominator = abs(oneWeekDays)
3237 // Where:
3238 // fraction / increment
3239 // = (numerator / denominator) / increment
3240 // = numerator / (denominator * increment)
3242 // And |numerator| and |denominator * increment| both fit into int64.
3244 // The "ceiling" operation has to be modified from:
3246 // CeilDiv(dividend, divisor)
3247 // quot, rem = dividend / divisor
3248 // return quot + (rem > 0)
3250 // To:
3252 // CeilDiv(dividend, divisor, fractional)
3253 // quot, rem = dividend / divisor
3254 // return quot + ((rem > 0) || (fractional > 0))
3256 // To properly account for the fractional |nanoseconds| part. Alternatively
3257 // |dividend| can be modified before calling `CeilDiv`.
3259 // clang-format on
3261 if (fractionalDays.time == 0) {
3262 auto [numerator, denominator] = fraction;
3263 int64_t totalDays = fractionalDays.days + denominator * numerator;
3265 if (computeRemainder == ComputeRemainder::Yes) {
3266 constexpr auto rounded = Int128{0};
3267 double total = FractionToDouble(totalDays, denominator);
3268 return {rounded, total};
3271 auto rounded =
3272 RoundNumberToIncrement(totalDays, denominator, increment, roundingMode);
3273 constexpr double total = 0;
3274 return {rounded, total};
3277 do {
3278 auto dayLength = mozilla::CheckedInt64(fractionalDays.dayLength);
3280 auto denominator = dayLength * fraction.denominator;
3281 if (!denominator.isValid()) {
3282 break;
3285 auto amountNanos = denominator * fraction.numerator;
3286 if (!amountNanos.isValid()) {
3287 break;
3290 auto totalNanoseconds = dayLength * fractionalDays.days;
3291 totalNanoseconds += fractionalDays.time;
3292 totalNanoseconds += amountNanos;
3293 if (!totalNanoseconds.isValid()) {
3294 break;
3297 if (computeRemainder == ComputeRemainder::Yes) {
3298 constexpr auto rounded = Int128{0};
3299 double total =
3300 FractionToDouble(totalNanoseconds.value(), denominator.value());
3301 return {rounded, total};
3304 auto rounded = RoundNumberToIncrement(
3305 totalNanoseconds.value(), denominator.value(), increment, roundingMode);
3306 constexpr double total = 0;
3307 return {rounded, total};
3308 } while (false);
3310 // Use int128 when values are too large for int64. Additionally assert all
3311 // values fit into int128.
3313 // `dayLength` < 2**53
3314 auto dayLength = Int128{fractionalDays.dayLength};
3315 MOZ_ASSERT(dayLength < Int128{1} << 53);
3317 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3318 auto denominator = dayLength * Int128{fraction.denominator};
3319 MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
3321 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3323 // `abs(maxFractionalDays)`
3324 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3325 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3326 // ≤ 2 * 2**37 + 2**29
3327 // ≤ 2**39
3328 auto totalDays = Int128{fractionalDays.days};
3329 MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
3331 // `abs(fraction.numerator)` ≤ (2**33)
3332 auto totalAmount = Int128{fraction.numerator};
3333 MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
3335 // `denominator` < 2**(53 + 28)
3336 // `abs(totalAmount)` <= 2**33
3338 // `denominator * totalAmount`
3339 // ≤ 2**(53 + 28) * 2**33
3340 // = 2**(53 + 28 + 33)
3341 // = 2**114
3342 auto amountNanos = denominator * totalAmount;
3343 MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
3345 // `dayLength` < 2**53
3346 // `totalDays` ≤ 2**39
3347 // `fractionalDays.time` < `dayLength` < 2**53
3348 // `amountNanos` ≤ 2**114
3350 // `dayLength * totalDays`
3351 // ≤ 2**(53 + 39) = 2**92
3353 // `dayLength * totalDays + fractionalDays.time`
3354 // ≤ 2**93
3356 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3357 // ≤ 2**115
3358 auto totalNanoseconds = dayLength * totalDays;
3359 totalNanoseconds += Int128{fractionalDays.time};
3360 totalNanoseconds += amountNanos;
3361 MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
3363 if (computeRemainder == ComputeRemainder::Yes) {
3364 constexpr auto rounded = Int128{0};
3365 double total = FractionToDouble(totalNanoseconds, denominator);
3366 return {rounded, total};
3369 auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
3370 increment, roundingMode);
3371 constexpr double total = 0;
3372 return {rounded, total};
3375 static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
3376 FractionalDays fractionalDays,
3377 Increment increment,
3378 TemporalRoundingMode roundingMode,
3379 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3380 Handle<CalendarRecord> calendar,
3381 ComputeRemainder computeRemainder,
3382 RoundedDuration* result) {
3383 #ifdef DEBUG
3384 // Numbers of days between nsMinInstant and nsMaxInstant.
3385 static constexpr int32_t epochDays = 200'000'000;
3386 #endif
3388 auto [years, months, weeks, days] = duration.date;
3390 // Step 10.a.
3391 auto yearsDuration = DateDuration{years};
3393 // Step 10.b.
3394 auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
3395 if (!yearsLater) {
3396 return false;
3398 auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
3400 // Step 10.f. (Reordered)
3401 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
3403 // Step 10.c.
3404 auto yearsMonthsWeeks = DateDuration{years, months, weeks};
3406 // Step 10.d.
3407 PlainDate yearsMonthsWeeksLater;
3408 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3409 &yearsMonthsWeeksLater)) {
3410 return false;
3413 // Step 10.e.
3414 int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
3415 MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
3417 // Step 10.f. (Moved up)
3419 // Step 10.g.
3420 fractionalDays += monthsWeeksInDays;
3422 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3423 // https://github.com/tc39/proposal-temporal/issues/2540
3425 // Step 10.h.
3426 PlainDate isoResult;
3427 if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, fractionalDays.truncate()},
3428 TemporalOverflow::Constrain, &isoResult)) {
3429 return false;
3432 // Step 10.i.
3433 Rooted<PlainDateObject*> wholeDaysLater(
3434 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3435 if (!wholeDaysLater) {
3436 return false;
3439 // Steps 10.j-l.
3440 DateDuration timePassed;
3441 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3442 TemporalUnit::Year, &timePassed)) {
3443 return false;
3446 // Step 10.m.
3447 int64_t yearsPassed = timePassed.years;
3449 // Step 10.n.
3450 years += yearsPassed;
3452 // Step 10.o.
3453 auto yearsPassedDuration = DateDuration{yearsPassed};
3455 // Steps 10.p-r.
3456 int32_t daysPassed;
3457 if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
3458 &newRelativeTo, &daysPassed)) {
3459 return false;
3461 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3463 // Step 10.s.
3464 fractionalDays -= daysPassed;
3466 // Steps 10.t.
3467 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3469 // Step 10.u.
3470 auto oneYear = DateDuration{sign};
3472 // Steps 10.v-w.
3473 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3474 int32_t oneYearDays;
3475 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
3476 &moveResultIgnored, &oneYearDays)) {
3477 return false;
3480 // Step 10.x.
3481 if (oneYearDays == 0) {
3482 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3483 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3484 return false;
3487 // Steps 10.y.
3488 auto fractionalYears = Fraction{years, std::abs(oneYearDays)};
3490 // Steps 10.z-aa.
3491 auto [numYears, total] =
3492 RoundNumberToIncrement(fractionalYears, fractionalDays, increment,
3493 roundingMode, computeRemainder);
3495 // Step 10.ab.
3496 int64_t numMonths = 0;
3497 int64_t numWeeks = 0;
3499 // Step 10.ac.
3500 constexpr auto time = NormalizedTimeDuration{};
3502 // Step 20.
3503 if (numYears.abs() >= (Uint128{1} << 32)) {
3504 return ThrowInvalidDurationPart(cx, double(numYears), "years",
3505 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3508 auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
3509 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3510 return false;
3513 *result = {{resultDuration, time}, total};
3514 return true;
3517 static bool RoundDurationMonth(JSContext* cx,
3518 const NormalizedDuration& duration,
3519 FractionalDays fractionalDays,
3520 Increment increment,
3521 TemporalRoundingMode roundingMode,
3522 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3523 Handle<CalendarRecord> calendar,
3524 ComputeRemainder computeRemainder,
3525 RoundedDuration* result) {
3526 #ifdef DEBUG
3527 // Numbers of days between nsMinInstant and nsMaxInstant.
3528 static constexpr int32_t epochDays = 200'000'000;
3529 #endif
3531 auto [years, months, weeks, days] = duration.date;
3533 // Step 11.a.
3534 auto yearsMonths = DateDuration{years, months};
3536 // Step 11.b.
3537 auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
3538 if (!yearsMonthsLater) {
3539 return false;
3541 auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
3543 // Step 11.f. (Reordered)
3544 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
3546 // Step 11.c.
3547 auto yearsMonthsWeeks = DateDuration{years, months, weeks};
3549 // Step 11.d.
3550 PlainDate yearsMonthsWeeksLater;
3551 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3552 &yearsMonthsWeeksLater)) {
3553 return false;
3556 // Step 11.e.
3557 int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
3558 MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
3560 // Step 11.f. (Moved up)
3562 // Step 11.g.
3563 fractionalDays += weeksInDays;
3565 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3566 // https://github.com/tc39/proposal-temporal/issues/2540
3568 // Step 11.h.
3569 PlainDate isoResult;
3570 if (!AddISODate(cx, yearsMonthsLaterDate,
3571 {0, 0, 0, fractionalDays.truncate()},
3572 TemporalOverflow::Constrain, &isoResult)) {
3573 return false;
3576 // Step 11.i.
3577 Rooted<PlainDateObject*> wholeDaysLater(
3578 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3579 if (!wholeDaysLater) {
3580 return false;
3583 // Steps 11.j-l.
3584 DateDuration timePassed;
3585 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3586 TemporalUnit::Month, &timePassed)) {
3587 return false;
3590 // Step 11.m.
3591 int64_t monthsPassed = timePassed.months;
3593 // Step 11.n.
3594 months += monthsPassed;
3596 // Step 11.o.
3597 auto monthsPassedDuration = DateDuration{0, monthsPassed};
3599 // Steps 11.p-r.
3600 int32_t daysPassed;
3601 if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
3602 &newRelativeTo, &daysPassed)) {
3603 return false;
3605 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3607 // Step 11.s.
3608 fractionalDays -= daysPassed;
3610 // Steps 11.t.
3611 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3613 // Step 11.u.
3614 auto oneMonth = DateDuration{0, sign};
3616 // Steps 11.v-w.
3617 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3618 int32_t oneMonthDays;
3619 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
3620 &moveResultIgnored, &oneMonthDays)) {
3621 return false;
3624 // Step 11.x.
3625 if (oneMonthDays == 0) {
3626 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3627 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3628 return false;
3631 // Step 11.y.
3632 auto fractionalMonths = Fraction{months, std::abs(oneMonthDays)};
3634 // Steps 11.z-aa.
3635 auto [numMonths, total] =
3636 RoundNumberToIncrement(fractionalMonths, fractionalDays, increment,
3637 roundingMode, computeRemainder);
3639 // Step 11.ab.
3640 int64_t numWeeks = 0;
3642 // Step 11.ac.
3643 constexpr auto time = NormalizedTimeDuration{};
3645 // Step 21.
3646 if (numMonths.abs() >= (Uint128{1} << 32)) {
3647 return ThrowInvalidDurationPart(cx, double(numMonths), "months",
3648 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3651 auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
3652 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3653 return false;
3656 *result = {{resultDuration, time}, total};
3657 return true;
3660 static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
3661 FractionalDays fractionalDays,
3662 Increment increment,
3663 TemporalRoundingMode roundingMode,
3664 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3665 Handle<CalendarRecord> calendar,
3666 ComputeRemainder computeRemainder,
3667 RoundedDuration* result) {
3668 #ifdef DEBUG
3669 // Numbers of days between nsMinInstant and nsMaxInstant.
3670 static constexpr int32_t epochDays = 200'000'000;
3671 #endif
3673 auto [years, months, weeks, days] = duration.date;
3675 auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
3676 if (!unwrappedRelativeTo) {
3677 return false;
3679 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
3681 // Step 12.a
3682 PlainDate isoResult;
3683 if (!AddISODate(cx, relativeToDate, {0, 0, 0, fractionalDays.truncate()},
3684 TemporalOverflow::Constrain, &isoResult)) {
3685 return false;
3688 // Step 12.b.
3689 Rooted<PlainDateObject*> wholeDaysLater(
3690 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3691 if (!wholeDaysLater) {
3692 return false;
3695 // Steps 12.c-e.
3696 DateDuration timePassed;
3697 if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
3698 TemporalUnit::Week, &timePassed)) {
3699 return false;
3702 // Step 12.f.
3703 int64_t weeksPassed = timePassed.weeks;
3705 // Step 12.g.
3706 weeks += weeksPassed;
3708 // Step 12.h.
3709 auto weeksPassedDuration = DateDuration{0, 0, weeksPassed};
3711 // Steps 12.i-k.
3712 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
3713 int32_t daysPassed;
3714 if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
3715 &newRelativeTo, &daysPassed)) {
3716 return false;
3718 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3720 // Step 12.l.
3721 fractionalDays -= daysPassed;
3723 // Steps 12.m.
3724 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3726 // Step 12.n.
3727 auto oneWeek = DateDuration{0, 0, sign};
3729 // Steps 12.o-p.
3730 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3731 int32_t oneWeekDays;
3732 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
3733 &moveResultIgnored, &oneWeekDays)) {
3734 return false;
3737 // Step 12.q.
3738 if (oneWeekDays == 0) {
3739 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3740 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3741 return false;
3744 // Step 12.r.
3745 auto fractionalWeeks = Fraction{weeks, std::abs(oneWeekDays)};
3747 // Steps 12.s-t.
3748 auto [numWeeks, total] =
3749 RoundNumberToIncrement(fractionalWeeks, fractionalDays, increment,
3750 roundingMode, computeRemainder);
3752 // Step 12.u.
3753 constexpr auto time = NormalizedTimeDuration{};
3755 // Step 20.
3756 if (numWeeks.abs() >= (Uint128{1} << 32)) {
3757 return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
3758 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3761 auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
3762 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3763 return false;
3766 *result = {{resultDuration, time}, total};
3767 return true;
3770 static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
3771 const FractionalDays& fractionalDays,
3772 Increment increment,
3773 TemporalRoundingMode roundingMode,
3774 ComputeRemainder computeRemainder,
3775 RoundedDuration* result) {
3776 auto [years, months, weeks, days] = duration.date;
3778 // Pass zero fraction.
3779 constexpr auto zero = Fraction{0, 1};
3781 // Steps 13.a-b.
3782 auto [numDays, total] = RoundNumberToIncrement(
3783 zero, fractionalDays, increment, roundingMode, computeRemainder);
3785 MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
3786 "rounded days fits in int64");
3788 // Step 13.c.
3789 constexpr auto time = NormalizedTimeDuration{};
3791 // Step 20.
3792 auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
3793 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3794 return false;
3797 *result = {{resultDuration, time}, total};
3798 return true;
3802 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3803 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3804 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3806 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
3807 Increment increment, TemporalUnit unit,
3808 TemporalRoundingMode roundingMode,
3809 ComputeRemainder computeRemainder,
3810 RoundedDuration* result) {
3811 // The remainder is only needed when called from |Duration_total|. And `total`
3812 // always passes |increment=1| and |roundingMode=trunc|.
3813 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3814 increment == Increment{1});
3815 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3816 roundingMode == TemporalRoundingMode::Trunc);
3818 // Steps 1-5. (Not applicable.)
3820 // Step 6.
3821 if (unit <= TemporalUnit::Week) {
3822 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3823 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3824 "relativeTo");
3825 return false;
3828 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3829 // because in that case this operation is a no-op. This case happens for
3830 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3831 // options object.
3833 // But maybe this can be even more efficiently handled in the callers. For
3834 // example when Temporal.PlainTime.prototype.{since,until} is called without
3835 // an options object, we can not only skip the RoundDuration call, but also
3836 // the following BalanceTimeDuration call.
3838 // Step 7. (Moved below.)
3840 // Steps 8-9. (Not applicable.)
3842 // Steps 10-12. (Not applicable.)
3844 // Step 13.
3845 if (unit == TemporalUnit::Day) {
3846 // Step 7.
3847 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3848 auto fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3850 return RoundDurationDay(cx, duration, fractionalDays, increment,
3851 roundingMode, computeRemainder, result);
3854 MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
3856 // Steps 14-19.
3857 auto time = duration.time;
3858 double total = 0;
3859 if (computeRemainder == ComputeRemainder::No) {
3860 time = RoundNormalizedTimeDurationToIncrement(time, unit, increment,
3861 roundingMode);
3862 } else {
3863 MOZ_ASSERT(increment == Increment{1});
3864 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
3866 total = TotalNormalizedTimeDuration(duration.time, unit);
3868 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
3870 // Step 20.
3871 MOZ_ASSERT(IsValidDuration(duration.date));
3872 *result = {{duration.date, time}, total};
3873 return true;
3877 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3878 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3879 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3881 static bool RoundDuration(
3882 JSContext* cx, const NormalizedDuration& duration, Increment increment,
3883 TemporalUnit unit, TemporalRoundingMode roundingMode,
3884 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3885 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
3886 Handle<TimeZoneRecord> timeZone,
3887 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
3888 ComputeRemainder computeRemainder, RoundedDuration* result) {
3889 // Note: |duration.days| can have a different sign than the other date
3890 // components. The date and time components can have different signs, too.
3891 MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
3892 double(duration.date.months),
3893 double(duration.date.weeks)}));
3894 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
3896 MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
3897 "Use RoundDuration without relativeTo when plainRelativeTo and "
3898 "zonedRelativeTo are both undefined");
3900 // The remainder is only needed when called from |Duration_total|. And `total`
3901 // always passes |increment=1| and |roundingMode=trunc|.
3902 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3903 increment == Increment{1});
3904 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3905 roundingMode == TemporalRoundingMode::Trunc);
3907 // Steps 1-5. (Not applicable in our implementation.)
3909 // Step 6.a. (Not applicable in our implementation.)
3910 MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
3912 // Step 6.b.
3913 MOZ_ASSERT_IF(
3914 unit <= TemporalUnit::Week,
3915 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3917 // Step 6.c.
3918 MOZ_ASSERT_IF(
3919 unit <= TemporalUnit::Week,
3920 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3922 switch (unit) {
3923 case TemporalUnit::Year:
3924 case TemporalUnit::Month:
3925 case TemporalUnit::Week:
3926 break;
3927 case TemporalUnit::Day:
3928 // We can't take the faster code path when |zonedRelativeTo| is present.
3929 if (zonedRelativeTo) {
3930 break;
3932 [[fallthrough]];
3933 case TemporalUnit::Hour:
3934 case TemporalUnit::Minute:
3935 case TemporalUnit::Second:
3936 case TemporalUnit::Millisecond:
3937 case TemporalUnit::Microsecond:
3938 case TemporalUnit::Nanosecond:
3939 // Steps 7-9 and 13-21.
3940 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
3941 computeRemainder, result);
3942 case TemporalUnit::Auto:
3943 MOZ_CRASH("Unexpected temporal unit");
3946 // Step 7.
3947 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
3949 // Steps 7.a-c.
3950 FractionalDays fractionalDays;
3951 if (zonedRelativeTo) {
3952 // Step 7.a.i.
3953 Rooted<ZonedDateTime> intermediate(cx);
3954 if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
3955 duration.date, precalculatedPlainDateTime,
3956 &intermediate)) {
3957 return false;
3960 // Steps 7.a.ii.
3961 NormalizedTimeAndDays timeAndDays;
3962 if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
3963 &timeAndDays)) {
3964 return false;
3967 // Step 7.a.iii.
3968 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3969 } else {
3970 // Step 7.b.
3971 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3972 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3975 // Step 7.c. (Moved below)
3977 // Step 8. (Not applicable)
3979 // Step 9.
3980 // FIXME: spec issue - `total` doesn't need be initialised.
3981 // https://github.com/tc39/proposal-temporal/issues/2784
3983 // Steps 10-20.
3984 switch (unit) {
3985 // Steps 10 and 20.
3986 case TemporalUnit::Year:
3987 return RoundDurationYear(cx, duration, fractionalDays, increment,
3988 roundingMode, plainRelativeTo, calendar,
3989 computeRemainder, result);
3991 // Steps 11 and 20.
3992 case TemporalUnit::Month:
3993 return RoundDurationMonth(cx, duration, fractionalDays, increment,
3994 roundingMode, plainRelativeTo, calendar,
3995 computeRemainder, result);
3997 // Steps 12 and 20.
3998 case TemporalUnit::Week:
3999 return RoundDurationWeek(cx, duration, fractionalDays, increment,
4000 roundingMode, plainRelativeTo, calendar,
4001 computeRemainder, result);
4003 // Steps 13 and 20.
4004 case TemporalUnit::Day:
4005 return RoundDurationDay(cx, duration, fractionalDays, increment,
4006 roundingMode, computeRemainder, result);
4008 // Steps 14-19. (Handled elsewhere)
4009 case TemporalUnit::Auto:
4010 case TemporalUnit::Hour:
4011 case TemporalUnit::Minute:
4012 case TemporalUnit::Second:
4013 case TemporalUnit::Millisecond:
4014 case TemporalUnit::Microsecond:
4015 case TemporalUnit::Nanosecond:
4016 break;
4019 MOZ_CRASH("Unexpected temporal unit");
4023 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4024 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4025 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4027 static bool RoundDuration(
4028 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4029 TemporalUnit unit, TemporalRoundingMode roundingMode,
4030 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4031 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4032 Handle<TimeZoneRecord> timeZone,
4033 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4034 double* result) {
4035 // Only called from |Duration_total|, which always passes |increment=1| and
4036 // |roundingMode=trunc|.
4037 MOZ_ASSERT(increment == Increment{1});
4038 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4040 RoundedDuration rounded;
4041 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4042 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4043 precalculatedPlainDateTime, ComputeRemainder::Yes,
4044 &rounded)) {
4045 return false;
4048 *result = rounded.total;
4049 return true;
4053 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4054 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4055 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4057 static bool RoundDuration(
4058 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4059 TemporalUnit unit, TemporalRoundingMode roundingMode,
4060 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4061 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4062 Handle<TimeZoneRecord> timeZone,
4063 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4064 NormalizedDuration* result) {
4065 RoundedDuration rounded;
4066 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4067 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4068 precalculatedPlainDateTime, ComputeRemainder::No,
4069 &rounded)) {
4070 return false;
4073 *result = rounded.duration;
4074 return true;
4078 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4079 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4080 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4082 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4083 Increment increment, TemporalUnit unit,
4084 TemporalRoundingMode roundingMode, double* result) {
4085 MOZ_ASSERT(IsValidDuration(duration));
4087 // Only called from |Duration_total|, which always passes |increment=1| and
4088 // |roundingMode=trunc|.
4089 MOZ_ASSERT(increment == Increment{1});
4090 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4092 RoundedDuration rounded;
4093 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4094 ComputeRemainder::Yes, &rounded)) {
4095 return false;
4098 *result = rounded.total;
4099 return true;
4103 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4104 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4105 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4107 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4108 Increment increment, TemporalUnit unit,
4109 TemporalRoundingMode roundingMode,
4110 NormalizedDuration* result) {
4111 MOZ_ASSERT(IsValidDuration(duration));
4113 RoundedDuration rounded;
4114 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4115 ComputeRemainder::No, &rounded)) {
4116 return false;
4119 *result = rounded.duration;
4120 return true;
4124 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4125 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4126 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4128 bool js::temporal::RoundDuration(
4129 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4130 TemporalUnit unit, TemporalRoundingMode roundingMode,
4131 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4132 Handle<CalendarRecord> calendar, NormalizedDuration* result) {
4133 MOZ_ASSERT(IsValidDuration(duration));
4135 Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
4136 Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
4137 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4138 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4139 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4140 precalculatedPlainDateTime, result);
4144 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4145 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4146 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4148 bool js::temporal::RoundDuration(
4149 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4150 TemporalUnit unit, TemporalRoundingMode roundingMode,
4151 Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
4152 Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
4153 const PlainDateTime& precalculatedPlainDateTime,
4154 NormalizedDuration* result) {
4155 MOZ_ASSERT(IsValidDuration(duration));
4157 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4158 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4159 mozilla::SomeRef(precalculatedPlainDateTime), result);
4162 enum class DurationOperation { Add, Subtract };
4165 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4166 * options )
4168 static bool AddDurationToOrSubtractDurationFromDuration(
4169 JSContext* cx, DurationOperation operation, const CallArgs& args) {
4170 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4171 auto duration = ToDuration(durationObj);
4173 // Step 1. (Not applicable in our implementation.)
4175 // Step 2.
4176 Duration other;
4177 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
4178 return false;
4181 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4182 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4183 Rooted<TimeZoneRecord> timeZone(cx);
4184 if (args.hasDefined(1)) {
4185 const char* name = operation == DurationOperation::Add ? "add" : "subtract";
4187 // Step 3.
4188 Rooted<JSObject*> options(cx,
4189 RequireObjectArg(cx, "options", name, args[1]));
4190 if (!options) {
4191 return false;
4194 // Steps 4-7.
4195 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4196 &zonedRelativeTo, &timeZone)) {
4197 return false;
4199 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4200 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4203 // Step 8.
4204 Rooted<CalendarRecord> calendar(cx);
4205 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4206 zonedRelativeTo,
4208 CalendarMethod::DateAdd,
4209 CalendarMethod::DateUntil,
4211 &calendar)) {
4212 return false;
4215 // Step 9.
4216 if (operation == DurationOperation::Subtract) {
4217 other = other.negate();
4220 Duration result;
4221 if (plainRelativeTo) {
4222 if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
4223 return false;
4225 } else if (zonedRelativeTo) {
4226 if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
4227 &result)) {
4228 return false;
4230 } else {
4231 if (!AddDuration(cx, duration, other, &result)) {
4232 return false;
4236 // Step 10.
4237 auto* obj = CreateTemporalDuration(cx, result);
4238 if (!obj) {
4239 return false;
4242 args.rval().setObject(*obj);
4243 return true;
4247 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4248 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4249 * ] ] ] ] ] ] )
4251 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
4252 CallArgs args = CallArgsFromVp(argc, vp);
4254 // Step 1.
4255 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
4256 return false;
4259 // Step 2.
4260 double years = 0;
4261 if (args.hasDefined(0) &&
4262 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
4263 return false;
4266 // Step 3.
4267 double months = 0;
4268 if (args.hasDefined(1) &&
4269 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
4270 return false;
4273 // Step 4.
4274 double weeks = 0;
4275 if (args.hasDefined(2) &&
4276 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
4277 return false;
4280 // Step 5.
4281 double days = 0;
4282 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
4283 return false;
4286 // Step 6.
4287 double hours = 0;
4288 if (args.hasDefined(4) &&
4289 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
4290 return false;
4293 // Step 7.
4294 double minutes = 0;
4295 if (args.hasDefined(5) &&
4296 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
4297 return false;
4300 // Step 8.
4301 double seconds = 0;
4302 if (args.hasDefined(6) &&
4303 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
4304 return false;
4307 // Step 9.
4308 double milliseconds = 0;
4309 if (args.hasDefined(7) &&
4310 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
4311 return false;
4314 // Step 10.
4315 double microseconds = 0;
4316 if (args.hasDefined(8) &&
4317 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
4318 return false;
4321 // Step 11.
4322 double nanoseconds = 0;
4323 if (args.hasDefined(9) &&
4324 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
4325 return false;
4328 // Step 12.
4329 auto* duration = CreateTemporalDuration(
4330 cx, args,
4331 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
4332 microseconds, nanoseconds});
4333 if (!duration) {
4334 return false;
4337 args.rval().setObject(*duration);
4338 return true;
4342 * Temporal.Duration.from ( item )
4344 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
4345 CallArgs args = CallArgsFromVp(argc, vp);
4347 Handle<Value> item = args.get(0);
4349 // Step 1.
4350 if (item.isObject()) {
4351 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
4352 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
4353 if (!result) {
4354 return false;
4357 args.rval().setObject(*result);
4358 return true;
4362 // Step 2.
4363 auto result = ToTemporalDuration(cx, item);
4364 if (!result) {
4365 return false;
4368 args.rval().setObject(*result);
4369 return true;
4373 * Temporal.Duration.compare ( one, two [ , options ] )
4375 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
4376 CallArgs args = CallArgsFromVp(argc, vp);
4378 // Step 1.
4379 Duration one;
4380 if (!ToTemporalDuration(cx, args.get(0), &one)) {
4381 return false;
4384 // Step 2.
4385 Duration two;
4386 if (!ToTemporalDuration(cx, args.get(1), &two)) {
4387 return false;
4390 // Step 3.
4391 Rooted<JSObject*> options(cx);
4392 if (args.hasDefined(2)) {
4393 options = RequireObjectArg(cx, "options", "compare", args[2]);
4394 if (!options) {
4395 return false;
4399 // Step 4.
4400 if (one == two) {
4401 args.rval().setInt32(0);
4402 return true;
4405 // Steps 5-8.
4406 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4407 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4408 Rooted<TimeZoneRecord> timeZone(cx);
4409 if (options) {
4410 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4411 &zonedRelativeTo, &timeZone)) {
4412 return false;
4414 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4415 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4418 // Steps 9-10.
4419 auto hasCalendarUnit = [](const auto& d) {
4420 return d.years != 0 || d.months != 0 || d.weeks != 0;
4422 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
4424 // Step 11.
4425 Rooted<CalendarRecord> calendar(cx);
4426 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4427 zonedRelativeTo,
4429 CalendarMethod::DateAdd,
4431 &calendar)) {
4432 return false;
4435 // Step 12.
4436 if (zonedRelativeTo &&
4437 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
4438 // Step 12.a.
4439 const auto& instant = zonedRelativeTo.instant();
4441 // Step 12.b.
4442 PlainDateTime dateTime;
4443 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
4444 return false;
4447 // Step 12.c.
4448 auto normalized1 = CreateNormalizedDurationRecord(one);
4450 // Step 12.d.
4451 auto normalized2 = CreateNormalizedDurationRecord(two);
4453 // Step 12.e.
4454 Instant after1;
4455 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
4456 dateTime, &after1)) {
4457 return false;
4460 // Step 12.f.
4461 Instant after2;
4462 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
4463 dateTime, &after2)) {
4464 return false;
4467 // Steps 12.g-i.
4468 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
4469 return true;
4472 // Steps 13-14.
4473 int64_t days1, days2;
4474 if (calendarUnitsPresent) {
4475 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4477 // Step 13.a.
4478 DateDuration unbalanceResult1;
4479 if (plainRelativeTo) {
4480 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
4481 TemporalUnit::Day, plainRelativeTo,
4482 calendar, &unbalanceResult1)) {
4483 return false;
4485 } else {
4486 if (!UnbalanceDateDurationRelative(
4487 cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
4488 return false;
4490 MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
4493 // Step 13.b.
4494 DateDuration unbalanceResult2;
4495 if (plainRelativeTo) {
4496 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
4497 TemporalUnit::Day, plainRelativeTo,
4498 calendar, &unbalanceResult2)) {
4499 return false;
4501 } else {
4502 if (!UnbalanceDateDurationRelative(
4503 cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
4504 return false;
4506 MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
4509 // Step 13.c.
4510 days1 = unbalanceResult1.days;
4512 // Step 13.d.
4513 days2 = unbalanceResult2.days;
4514 } else {
4515 // Step 14.a.
4516 days1 = mozilla::AssertedCast<int64_t>(one.days);
4518 // Step 14.b.
4519 days2 = mozilla::AssertedCast<int64_t>(two.days);
4522 // Step 15.
4523 auto normalized1 = NormalizeTimeDuration(one);
4525 // Step 16.
4526 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
4527 &normalized1)) {
4528 return false;
4531 // Step 17.
4532 auto normalized2 = NormalizeTimeDuration(two);
4534 // Step 18.
4535 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
4536 &normalized2)) {
4537 return false;
4540 // Step 19.
4541 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
4542 return true;
4546 * get Temporal.Duration.prototype.years
4548 static bool Duration_years(JSContext* cx, const CallArgs& args) {
4549 // Step 3.
4550 auto* duration = &args.thisv().toObject().as<DurationObject>();
4551 args.rval().setNumber(duration->years());
4552 return true;
4556 * get Temporal.Duration.prototype.years
4558 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
4559 // Steps 1-2.
4560 CallArgs args = CallArgsFromVp(argc, vp);
4561 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
4565 * get Temporal.Duration.prototype.months
4567 static bool Duration_months(JSContext* cx, const CallArgs& args) {
4568 // Step 3.
4569 auto* duration = &args.thisv().toObject().as<DurationObject>();
4570 args.rval().setNumber(duration->months());
4571 return true;
4575 * get Temporal.Duration.prototype.months
4577 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
4578 // Steps 1-2.
4579 CallArgs args = CallArgsFromVp(argc, vp);
4580 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
4584 * get Temporal.Duration.prototype.weeks
4586 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
4587 // Step 3.
4588 auto* duration = &args.thisv().toObject().as<DurationObject>();
4589 args.rval().setNumber(duration->weeks());
4590 return true;
4594 * get Temporal.Duration.prototype.weeks
4596 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
4597 // Steps 1-2.
4598 CallArgs args = CallArgsFromVp(argc, vp);
4599 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
4603 * get Temporal.Duration.prototype.days
4605 static bool Duration_days(JSContext* cx, const CallArgs& args) {
4606 // Step 3.
4607 auto* duration = &args.thisv().toObject().as<DurationObject>();
4608 args.rval().setNumber(duration->days());
4609 return true;
4613 * get Temporal.Duration.prototype.days
4615 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
4616 // Steps 1-2.
4617 CallArgs args = CallArgsFromVp(argc, vp);
4618 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
4622 * get Temporal.Duration.prototype.hours
4624 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
4625 // Step 3.
4626 auto* duration = &args.thisv().toObject().as<DurationObject>();
4627 args.rval().setNumber(duration->hours());
4628 return true;
4632 * get Temporal.Duration.prototype.hours
4634 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
4635 // Steps 1-2.
4636 CallArgs args = CallArgsFromVp(argc, vp);
4637 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
4641 * get Temporal.Duration.prototype.minutes
4643 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
4644 // Step 3.
4645 auto* duration = &args.thisv().toObject().as<DurationObject>();
4646 args.rval().setNumber(duration->minutes());
4647 return true;
4651 * get Temporal.Duration.prototype.minutes
4653 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
4654 // Steps 1-2.
4655 CallArgs args = CallArgsFromVp(argc, vp);
4656 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
4660 * get Temporal.Duration.prototype.seconds
4662 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
4663 // Step 3.
4664 auto* duration = &args.thisv().toObject().as<DurationObject>();
4665 args.rval().setNumber(duration->seconds());
4666 return true;
4670 * get Temporal.Duration.prototype.seconds
4672 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
4673 // Steps 1-2.
4674 CallArgs args = CallArgsFromVp(argc, vp);
4675 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
4679 * get Temporal.Duration.prototype.milliseconds
4681 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
4682 // Step 3.
4683 auto* duration = &args.thisv().toObject().as<DurationObject>();
4684 args.rval().setNumber(duration->milliseconds());
4685 return true;
4689 * get Temporal.Duration.prototype.milliseconds
4691 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
4692 // Steps 1-2.
4693 CallArgs args = CallArgsFromVp(argc, vp);
4694 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
4698 * get Temporal.Duration.prototype.microseconds
4700 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
4701 // Step 3.
4702 auto* duration = &args.thisv().toObject().as<DurationObject>();
4703 args.rval().setNumber(duration->microseconds());
4704 return true;
4708 * get Temporal.Duration.prototype.microseconds
4710 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
4711 // Steps 1-2.
4712 CallArgs args = CallArgsFromVp(argc, vp);
4713 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
4717 * get Temporal.Duration.prototype.nanoseconds
4719 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
4720 // Step 3.
4721 auto* duration = &args.thisv().toObject().as<DurationObject>();
4722 args.rval().setNumber(duration->nanoseconds());
4723 return true;
4727 * get Temporal.Duration.prototype.nanoseconds
4729 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
4730 // Steps 1-2.
4731 CallArgs args = CallArgsFromVp(argc, vp);
4732 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
4736 * get Temporal.Duration.prototype.sign
4738 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
4739 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4741 // Step 3.
4742 args.rval().setInt32(DurationSign(duration));
4743 return true;
4747 * get Temporal.Duration.prototype.sign
4749 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
4750 // Steps 1-2.
4751 CallArgs args = CallArgsFromVp(argc, vp);
4752 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
4756 * get Temporal.Duration.prototype.blank
4758 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
4759 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4761 // Steps 3-5.
4762 args.rval().setBoolean(duration == Duration{});
4763 return true;
4767 * get Temporal.Duration.prototype.blank
4769 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
4770 // Steps 1-2.
4771 CallArgs args = CallArgsFromVp(argc, vp);
4772 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
4776 * Temporal.Duration.prototype.with ( temporalDurationLike )
4778 * ToPartialDuration ( temporalDurationLike )
4780 static bool Duration_with(JSContext* cx, const CallArgs& args) {
4781 // Absent values default to the corresponding values of |this| object.
4782 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4784 // Steps 3-23.
4785 Rooted<JSObject*> temporalDurationLike(
4786 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
4787 if (!temporalDurationLike) {
4788 return false;
4790 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
4791 return false;
4794 // Step 24.
4795 auto* result = CreateTemporalDuration(cx, duration);
4796 if (!result) {
4797 return false;
4800 args.rval().setObject(*result);
4801 return true;
4805 * Temporal.Duration.prototype.with ( temporalDurationLike )
4807 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
4808 // Steps 1-2.
4809 CallArgs args = CallArgsFromVp(argc, vp);
4810 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
4814 * Temporal.Duration.prototype.negated ( )
4816 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
4817 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4819 // Step 3.
4820 auto* result = CreateTemporalDuration(cx, duration.negate());
4821 if (!result) {
4822 return false;
4825 args.rval().setObject(*result);
4826 return true;
4830 * Temporal.Duration.prototype.negated ( )
4832 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
4833 // Steps 1-2.
4834 CallArgs args = CallArgsFromVp(argc, vp);
4835 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
4839 * Temporal.Duration.prototype.abs ( )
4841 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
4842 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4844 // Step 3.
4845 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
4846 if (!result) {
4847 return false;
4850 args.rval().setObject(*result);
4851 return true;
4855 * Temporal.Duration.prototype.abs ( )
4857 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
4858 // Steps 1-2.
4859 CallArgs args = CallArgsFromVp(argc, vp);
4860 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
4864 * Temporal.Duration.prototype.add ( other [ , options ] )
4866 static bool Duration_add(JSContext* cx, const CallArgs& args) {
4867 return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
4868 args);
4872 * Temporal.Duration.prototype.add ( other [ , options ] )
4874 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
4875 // Steps 1-2.
4876 CallArgs args = CallArgsFromVp(argc, vp);
4877 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
4881 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4883 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
4884 return AddDurationToOrSubtractDurationFromDuration(
4885 cx, DurationOperation::Subtract, args);
4889 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4891 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
4892 // Steps 1-2.
4893 CallArgs args = CallArgsFromVp(argc, vp);
4894 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
4898 * Temporal.Duration.prototype.round ( roundTo )
4900 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4901 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4903 // Step 18. (Reordered)
4904 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4906 // Steps 3-25.
4907 auto smallestUnit = TemporalUnit::Auto;
4908 TemporalUnit largestUnit;
4909 auto roundingMode = TemporalRoundingMode::HalfExpand;
4910 auto roundingIncrement = Increment{1};
4911 Rooted<JSObject*> relativeTo(cx);
4912 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4913 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4914 Rooted<TimeZoneRecord> timeZone(cx);
4915 if (args.get(0).isString()) {
4916 // Step 4. (Not applicable in our implementation.)
4918 // Steps 6-15. (Not applicable)
4920 // Step 16.
4921 Rooted<JSString*> paramString(cx, args[0].toString());
4922 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
4923 TemporalUnitGroup::DateTime, &smallestUnit)) {
4924 return false;
4927 // Step 17. (Not applicable)
4929 // Step 18. (Moved above)
4931 // Step 19.
4932 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4934 // Step 20. (Not applicable)
4936 // Step 20.a. (Not applicable)
4938 // Step 20.b.
4939 largestUnit = defaultLargestUnit;
4941 // Steps 21-25. (Not applicable)
4942 } else {
4943 // Steps 3 and 5.
4944 Rooted<JSObject*> options(
4945 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
4946 if (!options) {
4947 return false;
4950 // Step 6.
4951 bool smallestUnitPresent = true;
4953 // Step 7.
4954 bool largestUnitPresent = true;
4956 // Steps 8-9.
4958 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
4959 // absent "largestUnit" value.
4960 Rooted<Value> largestUnitValue(cx);
4961 if (!GetProperty(cx, options, options, cx->names().largestUnit,
4962 &largestUnitValue)) {
4963 return false;
4966 if (!largestUnitValue.isUndefined()) {
4967 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
4968 if (!largestUnitStr) {
4969 return false;
4972 largestUnit = TemporalUnit::Auto;
4973 if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
4974 TemporalUnitGroup::DateTime, &largestUnit)) {
4975 return false;
4979 // Steps 10-13.
4980 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4981 &zonedRelativeTo, &timeZone)) {
4982 return false;
4984 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4985 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4987 // Step 14.
4988 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
4989 return false;
4992 // Step 15.
4993 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
4994 return false;
4997 // Step 16.
4998 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
4999 TemporalUnitGroup::DateTime, &smallestUnit)) {
5000 return false;
5003 // Step 17.
5004 if (smallestUnit == TemporalUnit::Auto) {
5005 // Step 17.a.
5006 smallestUnitPresent = false;
5008 // Step 17.b.
5009 smallestUnit = TemporalUnit::Nanosecond;
5012 // Step 18. (Moved above)
5014 // Step 19.
5015 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5017 // Steps 20-21.
5018 if (largestUnitValue.isUndefined()) {
5019 // Step 20.a.
5020 largestUnitPresent = false;
5022 // Step 20.b.
5023 largestUnit = defaultLargestUnit;
5024 } else if (largestUnit == TemporalUnit::Auto) {
5025 // Step 21.a
5026 largestUnit = defaultLargestUnit;
5029 // Step 22.
5030 if (!smallestUnitPresent && !largestUnitPresent) {
5031 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5032 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
5033 return false;
5036 // Step 23.
5037 if (largestUnit > smallestUnit) {
5038 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5039 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
5040 return false;
5043 // Steps 24-25.
5044 if (smallestUnit > TemporalUnit::Day) {
5045 // Step 24.
5046 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
5048 // Step 25.
5049 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
5050 false)) {
5051 return false;
5056 // Step 26.
5057 bool hoursToDaysConversionMayOccur = false;
5059 // Step 27.
5060 if (duration.days != 0 && zonedRelativeTo) {
5061 hoursToDaysConversionMayOccur = true;
5064 // Step 28.
5065 else if (std::abs(duration.hours) >= 24) {
5066 hoursToDaysConversionMayOccur = true;
5069 // Step 29.
5070 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
5071 roundingIncrement == Increment{1};
5073 // Step 30.
5074 bool calendarUnitsPresent =
5075 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
5077 // Step 31.
5078 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
5079 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
5080 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
5081 std::abs(duration.milliseconds) < 1000 &&
5082 std::abs(duration.microseconds) < 1000 &&
5083 std::abs(duration.nanoseconds) < 1000) {
5084 // Steps 31.a-b.
5085 auto* obj = CreateTemporalDuration(cx, duration);
5086 if (!obj) {
5087 return false;
5090 args.rval().setObject(*obj);
5091 return true;
5094 // Step 32.
5095 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5097 // Step 33.
5098 bool plainDateTimeOrRelativeToWillBeUsed =
5099 !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
5100 calendarUnitsPresent || duration.days != 0;
5102 // Step 34.
5103 PlainDateTime relativeToDateTime;
5104 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5105 // Steps 34.a-b.
5106 const auto& instant = zonedRelativeTo.instant();
5108 // Step 34.c.
5109 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5110 return false;
5112 precalculatedPlainDateTime =
5113 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5115 // Step 34.d.
5116 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5117 zonedRelativeTo.calendar());
5118 if (!plainRelativeTo) {
5119 return false;
5123 // Step 35.
5124 Rooted<CalendarRecord> calendar(cx);
5125 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5126 zonedRelativeTo,
5128 CalendarMethod::DateAdd,
5129 CalendarMethod::DateUntil,
5131 &calendar)) {
5132 return false;
5135 // Step 36.
5136 DateDuration unbalanceResult;
5137 if (plainRelativeTo) {
5138 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5139 largestUnit, plainRelativeTo, calendar,
5140 &unbalanceResult)) {
5141 return false;
5143 } else {
5144 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5145 largestUnit, &unbalanceResult)) {
5146 return false;
5148 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5151 // Steps 37-39.
5152 auto roundInput =
5153 NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
5154 NormalizedDuration roundResult;
5155 if (plainRelativeTo || zonedRelativeTo) {
5156 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5157 roundingMode, plainRelativeTo, calendar,
5158 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5159 &roundResult)) {
5160 return false;
5162 } else {
5163 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5164 roundingMode, &roundResult)) {
5165 return false;
5169 // Steps 40-41.
5170 TimeDuration balanceResult;
5171 if (zonedRelativeTo) {
5172 // Step 40.a.
5173 NormalizedDuration adjustResult;
5174 if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
5175 smallestUnit, roundingMode, zonedRelativeTo,
5176 calendar, timeZone,
5177 precalculatedPlainDateTime, &adjustResult)) {
5178 return false;
5180 roundResult = adjustResult;
5182 // Step 40.b.
5183 if (!BalanceTimeDurationRelative(
5184 cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
5185 precalculatedPlainDateTime, &balanceResult)) {
5186 return false;
5188 } else {
5189 // Step 41.a.
5190 NormalizedTimeDuration withDays;
5191 if (!Add24HourDaysToNormalizedTimeDuration(
5192 cx, roundResult.time, roundResult.date.days, &withDays)) {
5193 return false;
5196 // Step 41.b.
5197 balanceResult = temporal::BalanceTimeDuration(withDays, largestUnit);
5200 // Step 41.
5201 auto balanceInput = DateDuration{
5202 roundResult.date.years,
5203 roundResult.date.months,
5204 roundResult.date.weeks,
5205 balanceResult.days,
5207 DateDuration dateResult;
5208 if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
5209 smallestUnit, plainRelativeTo, calendar,
5210 &dateResult)) {
5211 return false;
5214 // Step 42.
5215 auto result = Duration{
5216 double(dateResult.years), double(dateResult.months),
5217 double(dateResult.weeks), double(dateResult.days),
5218 double(balanceResult.hours), double(balanceResult.minutes),
5219 double(balanceResult.seconds), double(balanceResult.milliseconds),
5220 balanceResult.microseconds, balanceResult.nanoseconds,
5223 auto* obj = CreateTemporalDuration(cx, result);
5224 if (!obj) {
5225 return false;
5228 args.rval().setObject(*obj);
5229 return true;
5233 * Temporal.Duration.prototype.round ( options )
5235 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
5236 // Steps 1-2.
5237 CallArgs args = CallArgsFromVp(argc, vp);
5238 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
5242 * Temporal.Duration.prototype.total ( totalOf )
5244 static bool Duration_total(JSContext* cx, const CallArgs& args) {
5245 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
5246 auto duration = ToDuration(durationObj);
5248 // Steps 3-11.
5249 Rooted<JSObject*> relativeTo(cx);
5250 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5251 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5252 Rooted<TimeZoneRecord> timeZone(cx);
5253 auto unit = TemporalUnit::Auto;
5254 if (args.get(0).isString()) {
5255 // Step 4. (Not applicable in our implementation.)
5257 // Steps 6-10. (Implicit)
5258 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
5260 // Step 11.
5261 Rooted<JSString*> paramString(cx, args[0].toString());
5262 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
5263 TemporalUnitGroup::DateTime, &unit)) {
5264 return false;
5266 } else {
5267 // Steps 3 and 5.
5268 Rooted<JSObject*> totalOf(
5269 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
5270 if (!totalOf) {
5271 return false;
5274 // Steps 6-10.
5275 if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
5276 &zonedRelativeTo, &timeZone)) {
5277 return false;
5279 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5280 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5282 // Step 11.
5283 if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
5284 TemporalUnitGroup::DateTime, &unit)) {
5285 return false;
5288 if (unit == TemporalUnit::Auto) {
5289 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5290 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
5291 return false;
5295 // Step 12.
5296 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5298 // Step 13.
5299 bool plainDateTimeOrRelativeToWillBeUsed =
5300 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
5302 // Step 14.
5303 PlainDateTime relativeToDateTime;
5304 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5305 // Steps 14.a-b.
5306 const auto& instant = zonedRelativeTo.instant();
5308 // Step 14.c.
5309 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5310 return false;
5312 precalculatedPlainDateTime =
5313 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5315 // Step 14.d
5316 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5317 zonedRelativeTo.calendar());
5318 if (!plainRelativeTo) {
5319 return false;
5323 // Step 15.
5324 Rooted<CalendarRecord> calendar(cx);
5325 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5326 zonedRelativeTo,
5328 CalendarMethod::DateAdd,
5329 CalendarMethod::DateUntil,
5331 &calendar)) {
5332 return false;
5335 // Step 16.
5336 DateDuration unbalanceResult;
5337 if (plainRelativeTo) {
5338 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5339 plainRelativeTo, calendar,
5340 &unbalanceResult)) {
5341 return false;
5343 } else {
5344 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5345 &unbalanceResult)) {
5346 return false;
5348 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5351 // Step 17.
5352 int64_t unbalancedDays = unbalanceResult.days;
5354 // Steps 18-19.
5355 int64_t days;
5356 NormalizedTimeDuration balanceResult;
5357 if (zonedRelativeTo) {
5358 // Step 18.a
5359 Rooted<ZonedDateTime> intermediate(cx);
5360 if (!MoveRelativeZonedDateTime(
5361 cx, zonedRelativeTo, calendar, timeZone,
5362 {unbalanceResult.years, unbalanceResult.months,
5363 unbalanceResult.weeks, 0},
5364 precalculatedPlainDateTime, &intermediate)) {
5365 return false;
5368 // Step 18.b.
5369 auto timeDuration = NormalizeTimeDuration(duration);
5371 // Step 18.c
5372 const auto& startNs = intermediate.instant();
5374 // Step 18.d.
5375 const auto& startInstant = startNs;
5377 // Step 18.e.
5378 mozilla::Maybe<PlainDateTime> startDateTime{};
5380 // Steps 18.f-g.
5381 Instant intermediateNs;
5382 if (unbalancedDays != 0) {
5383 // Step 18.f.i.
5384 PlainDateTime dateTime;
5385 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5386 return false;
5388 startDateTime = mozilla::Some(dateTime);
5390 // Step 18.f.ii.
5391 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
5392 Instant addResult;
5393 if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
5394 isoCalendar, unbalancedDays, &addResult)) {
5395 return false;
5398 // Step 18.f.iii.
5399 intermediateNs = addResult;
5400 } else {
5401 // Step 18.g.
5402 intermediateNs = startNs;
5405 // Step 18.h.
5406 Instant endNs;
5407 if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
5408 return false;
5411 // Step 18.i.
5412 auto difference =
5413 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
5415 // Steps 18.j-k.
5417 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5418 if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
5419 difference != NormalizedTimeDuration{}) {
5420 // Step 18.j.i.
5421 if (!startDateTime) {
5422 PlainDateTime dateTime;
5423 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5424 return false;
5426 startDateTime = mozilla::Some(dateTime);
5429 // Step 18.j.ii.
5430 NormalizedTimeAndDays timeAndDays;
5431 if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
5432 *startDateTime, &timeAndDays)) {
5433 return false;
5436 // Step 18.j.iii.
5437 balanceResult = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
5439 // Step 18.j.iv.
5440 days = timeAndDays.days;
5441 } else {
5442 // Step 18.k.i.
5443 balanceResult = difference;
5444 days = 0;
5446 } else {
5447 // Step 19.a.
5448 auto timeDuration = NormalizeTimeDuration(duration);
5450 // Step 19.b.
5451 if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
5452 &balanceResult)) {
5453 return false;
5456 // Step 19.c.
5457 days = 0;
5459 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult));
5461 // Step 20.
5462 auto roundInput = NormalizedDuration{
5464 unbalanceResult.years,
5465 unbalanceResult.months,
5466 unbalanceResult.weeks,
5467 days,
5469 balanceResult,
5471 double total;
5472 if (plainRelativeTo || zonedRelativeTo) {
5473 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5474 TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
5475 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5476 &total)) {
5477 return false;
5479 } else {
5480 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5481 TemporalRoundingMode::Trunc, &total)) {
5482 return false;
5486 // Step 21.
5487 args.rval().setNumber(total);
5488 return true;
5492 * Temporal.Duration.prototype.total ( totalOf )
5494 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
5495 // Steps 1-2.
5496 CallArgs args = CallArgsFromVp(argc, vp);
5497 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
5501 * Temporal.Duration.prototype.toString ( [ options ] )
5503 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
5504 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5506 // Steps 3-9.
5507 SecondsStringPrecision precision = {Precision::Auto(),
5508 TemporalUnit::Nanosecond, Increment{1}};
5509 auto roundingMode = TemporalRoundingMode::Trunc;
5510 if (args.hasDefined(0)) {
5511 // Step 3.
5512 Rooted<JSObject*> options(
5513 cx, RequireObjectArg(cx, "options", "toString", args[0]));
5514 if (!options) {
5515 return false;
5518 // Steps 4-5.
5519 auto digits = Precision::Auto();
5520 if (!ToFractionalSecondDigits(cx, options, &digits)) {
5521 return false;
5524 // Step 6.
5525 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5526 return false;
5529 // Step 7.
5530 auto smallestUnit = TemporalUnit::Auto;
5531 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5532 TemporalUnitGroup::Time, &smallestUnit)) {
5533 return false;
5536 // Step 8.
5537 if (smallestUnit == TemporalUnit::Hour ||
5538 smallestUnit == TemporalUnit::Minute) {
5539 const char* smallestUnitStr =
5540 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
5541 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5542 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
5543 smallestUnitStr, "smallestUnit");
5544 return false;
5547 // Step 9.
5548 precision = ToSecondsStringPrecision(smallestUnit, digits);
5551 // Steps 10-11.
5552 Duration result;
5553 if (precision.unit != TemporalUnit::Nanosecond ||
5554 precision.increment != Increment{1}) {
5555 // Step 10.a.
5556 auto timeDuration = NormalizeTimeDuration(duration);
5558 // Step 10.b.
5559 auto largestUnit = DefaultTemporalLargestUnit(duration);
5561 // Steps 10.c-d.
5562 auto rounded = RoundDuration(timeDuration, precision.increment,
5563 precision.unit, roundingMode);
5565 // Step 10.e.
5566 auto balanced = BalanceTimeDuration(
5567 rounded, std::min(largestUnit, TemporalUnit::Second));
5569 // Step 10.f.
5570 result = {
5571 duration.years, duration.months,
5572 duration.weeks, duration.days + double(balanced.days),
5573 double(balanced.hours), double(balanced.minutes),
5574 double(balanced.seconds), double(balanced.milliseconds),
5575 balanced.microseconds, balanced.nanoseconds,
5577 MOZ_ASSERT(IsValidDuration(duration));
5578 } else {
5579 // Step 11.
5580 result = duration;
5583 // Steps 12-13.
5584 JSString* str = TemporalDurationToString(cx, result, precision.precision);
5585 if (!str) {
5586 return false;
5589 args.rval().setString(str);
5590 return true;
5594 * Temporal.Duration.prototype.toString ( [ options ] )
5596 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
5597 // Steps 1-2.
5598 CallArgs args = CallArgsFromVp(argc, vp);
5599 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
5603 * Temporal.Duration.prototype.toJSON ( )
5605 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
5606 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5608 // Steps 3-4.
5609 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5610 if (!str) {
5611 return false;
5614 args.rval().setString(str);
5615 return true;
5619 * Temporal.Duration.prototype.toJSON ( )
5621 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
5622 // Steps 1-2.
5623 CallArgs args = CallArgsFromVp(argc, vp);
5624 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
5628 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5630 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
5631 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5633 // Steps 3-4.
5634 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5635 if (!str) {
5636 return false;
5639 args.rval().setString(str);
5640 return true;
5644 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5646 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
5647 // Steps 1-2.
5648 CallArgs args = CallArgsFromVp(argc, vp);
5649 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
5653 * Temporal.Duration.prototype.valueOf ( )
5655 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
5656 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
5657 "Duration", "primitive type");
5658 return false;
5661 const JSClass DurationObject::class_ = {
5662 "Temporal.Duration",
5663 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
5664 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
5665 JS_NULL_CLASS_OPS,
5666 &DurationObject::classSpec_,
5669 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
5671 static const JSFunctionSpec Duration_methods[] = {
5672 JS_FN("from", Duration_from, 1, 0),
5673 JS_FN("compare", Duration_compare, 2, 0),
5674 JS_FS_END,
5677 static const JSFunctionSpec Duration_prototype_methods[] = {
5678 JS_FN("with", Duration_with, 1, 0),
5679 JS_FN("negated", Duration_negated, 0, 0),
5680 JS_FN("abs", Duration_abs, 0, 0),
5681 JS_FN("add", Duration_add, 1, 0),
5682 JS_FN("subtract", Duration_subtract, 1, 0),
5683 JS_FN("round", Duration_round, 1, 0),
5684 JS_FN("total", Duration_total, 1, 0),
5685 JS_FN("toString", Duration_toString, 0, 0),
5686 JS_FN("toJSON", Duration_toJSON, 0, 0),
5687 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
5688 JS_FN("valueOf", Duration_valueOf, 0, 0),
5689 JS_FS_END,
5692 static const JSPropertySpec Duration_prototype_properties[] = {
5693 JS_PSG("years", Duration_years, 0),
5694 JS_PSG("months", Duration_months, 0),
5695 JS_PSG("weeks", Duration_weeks, 0),
5696 JS_PSG("days", Duration_days, 0),
5697 JS_PSG("hours", Duration_hours, 0),
5698 JS_PSG("minutes", Duration_minutes, 0),
5699 JS_PSG("seconds", Duration_seconds, 0),
5700 JS_PSG("milliseconds", Duration_milliseconds, 0),
5701 JS_PSG("microseconds", Duration_microseconds, 0),
5702 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
5703 JS_PSG("sign", Duration_sign, 0),
5704 JS_PSG("blank", Duration_blank, 0),
5705 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
5706 JS_PS_END,
5709 const ClassSpec DurationObject::classSpec_ = {
5710 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
5711 GenericCreatePrototype<DurationObject>,
5712 Duration_methods,
5713 nullptr,
5714 Duration_prototype_methods,
5715 Duration_prototype_properties,
5716 nullptr,
5717 ClassSpec::DontDefineConstructor,