Bug 1874684 - Part 11: Remove no longer needed BigInt code path in TemporalDurationTo...
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blob09e0b9c3a93f5f9915b44c64025cda679594ed6e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/Duration.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/EnumSet.h"
12 #include "mozilla/FloatingPoint.h"
13 #include "mozilla/Maybe.h"
15 #include <algorithm>
16 #include <cmath>
17 #include <cstdlib>
18 #include <initializer_list>
19 #include <stdint.h>
20 #include <type_traits>
21 #include <utility>
23 #include "jsnum.h"
24 #include "jspubtd.h"
25 #include "NamespaceImports.h"
27 #include "builtin/temporal/Calendar.h"
28 #include "builtin/temporal/Instant.h"
29 #include "builtin/temporal/Int128.h"
30 #include "builtin/temporal/Int96.h"
31 #include "builtin/temporal/PlainDate.h"
32 #include "builtin/temporal/PlainDateTime.h"
33 #include "builtin/temporal/Temporal.h"
34 #include "builtin/temporal/TemporalFields.h"
35 #include "builtin/temporal/TemporalParser.h"
36 #include "builtin/temporal/TemporalRoundingMode.h"
37 #include "builtin/temporal/TemporalTypes.h"
38 #include "builtin/temporal/TemporalUnit.h"
39 #include "builtin/temporal/TimeZone.h"
40 #include "builtin/temporal/Wrapped.h"
41 #include "builtin/temporal/ZonedDateTime.h"
42 #include "gc/AllocKind.h"
43 #include "gc/Barrier.h"
44 #include "gc/GCEnum.h"
45 #include "js/CallArgs.h"
46 #include "js/CallNonGenericMethod.h"
47 #include "js/Class.h"
48 #include "js/Conversions.h"
49 #include "js/ErrorReport.h"
50 #include "js/friend/ErrorMessages.h"
51 #include "js/GCVector.h"
52 #include "js/Id.h"
53 #include "js/Printer.h"
54 #include "js/PropertyDescriptor.h"
55 #include "js/PropertySpec.h"
56 #include "js/RootingAPI.h"
57 #include "js/Value.h"
58 #include "util/StringBuffer.h"
59 #include "vm/BytecodeUtil.h"
60 #include "vm/GlobalObject.h"
61 #include "vm/JSAtomState.h"
62 #include "vm/JSContext.h"
63 #include "vm/JSObject.h"
64 #include "vm/ObjectOperations.h"
65 #include "vm/PlainObject.h"
66 #include "vm/StringType.h"
68 #include "vm/JSObject-inl.h"
69 #include "vm/NativeObject-inl.h"
70 #include "vm/ObjectOperations-inl.h"
72 using namespace js;
73 using namespace js::temporal;
75 static inline bool IsDuration(Handle<Value> v) {
76 return v.isObject() && v.toObject().is<DurationObject>();
79 #ifdef DEBUG
80 static bool IsIntegerOrInfinity(double d) {
81 return IsInteger(d) || std::isinf(d);
84 static bool IsIntegerOrInfinityDuration(const Duration& duration) {
85 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
86 microseconds, nanoseconds] = duration;
88 // Integers exceeding the Number range are represented as infinity.
90 return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
91 IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
92 IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
93 IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
94 IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
97 static bool IsIntegerDuration(const Duration& duration) {
98 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
99 microseconds, nanoseconds] = duration;
101 return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
102 IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
103 IsInteger(seconds) && IsInteger(milliseconds) &&
104 IsInteger(microseconds) && IsInteger(nanoseconds);
106 #endif
108 static constexpr bool IsSafeInteger(int64_t x) {
109 constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
110 constexpr int64_t MinSafeInteger = -MaxSafeInteger;
111 return MinSafeInteger < x && x < MaxSafeInteger;
114 static constexpr bool IsSafeInteger(const Int128& x) {
115 constexpr Int128 MaxSafeInteger = Int128{int64_t(1) << 53};
116 constexpr Int128 MinSafeInteger = -MaxSafeInteger;
117 return MinSafeInteger < x && x < MaxSafeInteger;
121 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
122 * milliseconds, microseconds, nanoseconds )
124 int32_t js::temporal::DurationSign(const Duration& duration) {
125 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
127 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
128 microseconds, nanoseconds] = duration;
130 // Step 1.
131 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
132 milliseconds, microseconds, nanoseconds}) {
133 // Step 1.a.
134 if (v < 0) {
135 return -1;
138 // Step 1.b.
139 if (v > 0) {
140 return 1;
144 // Step 2.
145 return 0;
149 * Normalize a nanoseconds amount into a time duration.
151 static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
152 // Split into seconds and nanoseconds.
153 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
155 return {seconds, nanos};
159 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
160 * value is too large.
162 static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
163 double nanoseconds) {
164 MOZ_ASSERT(IsInteger(nanoseconds));
166 if (auto int96 = Int96::fromInteger(nanoseconds)) {
167 // The number of normalized seconds must not exceed `2**53 - 1`.
168 constexpr auto limit =
169 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
171 if (int96->abs() < limit) {
172 return mozilla::Some(NormalizeNanoseconds(*int96));
175 return mozilla::Nothing();
179 * Normalize a microseconds amount into a time duration.
181 static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
182 // Split into seconds and microseconds.
183 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
185 // Scale microseconds to nanoseconds.
186 int32_t nanos = micros * ToNanoseconds(TemporalUnit::Microsecond);
188 return {seconds, nanos};
192 * Normalize a microseconds amount into a time duration. Return Nothing if the
193 * value is too large.
195 static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
196 double microseconds) {
197 MOZ_ASSERT(IsInteger(microseconds));
199 if (auto int96 = Int96::fromInteger(microseconds)) {
200 // The number of normalized seconds must not exceed `2**53 - 1`.
201 constexpr auto limit =
202 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
204 if (int96->abs() < limit) {
205 return mozilla::Some(NormalizeMicroseconds(*int96));
208 return mozilla::Nothing();
212 * Normalize a duration into a time duration. Return Nothing if any duration
213 * value is too large.
215 static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
216 const Duration& duration) {
217 do {
218 auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
219 if (!nanoseconds) {
220 break;
222 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
224 auto microseconds = NormalizeMicroseconds(duration.microseconds);
225 if (!microseconds) {
226 break;
228 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
230 // Overflows for millis/seconds/minutes/hours/days always result in an
231 // invalid normalized time duration.
233 int64_t milliseconds;
234 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
235 break;
238 int64_t seconds;
239 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
240 break;
243 int64_t minutes;
244 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
245 break;
248 int64_t hours;
249 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
250 break;
253 int64_t days;
254 if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
255 break;
258 // Compute the overall amount of milliseconds.
259 mozilla::CheckedInt64 millis = days;
260 millis *= 24;
261 millis += hours;
262 millis *= 60;
263 millis += minutes;
264 millis *= 60;
265 millis += seconds;
266 millis *= 1000;
267 millis += milliseconds;
268 if (!millis.isValid()) {
269 break;
272 auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
273 if (!IsValidNormalizedTimeDuration(milli)) {
274 break;
277 // Compute the overall time duration.
278 auto result = milli + *microseconds + *nanoseconds;
279 if (!IsValidNormalizedTimeDuration(result)) {
280 break;
283 return mozilla::Some(result);
284 } while (false);
286 return mozilla::Nothing();
290 * Normalize a days amount into a time duration. Return Nothing if the value is
291 * too large.
293 static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(double days) {
294 MOZ_ASSERT(IsInteger(days));
296 do {
297 int64_t intDays;
298 if (!mozilla::NumberEqualsInt64(days, &intDays)) {
299 break;
302 // Compute the overall amount of milliseconds.
303 auto millis =
304 mozilla::CheckedInt64(intDays) * ToMilliseconds(TemporalUnit::Day);
305 if (!millis.isValid()) {
306 break;
309 auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
310 if (!IsValidNormalizedTimeDuration(result)) {
311 break;
314 return mozilla::Some(result);
315 } while (false);
317 return mozilla::Nothing();
321 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
322 * nanoseconds )
324 static NormalizedTimeDuration NormalizeTimeDuration(
325 double hours, double minutes, double seconds, double milliseconds,
326 double microseconds, double nanoseconds) {
327 MOZ_ASSERT(IsInteger(hours));
328 MOZ_ASSERT(IsInteger(minutes));
329 MOZ_ASSERT(IsInteger(seconds));
330 MOZ_ASSERT(IsInteger(milliseconds));
331 MOZ_ASSERT(IsInteger(microseconds));
332 MOZ_ASSERT(IsInteger(nanoseconds));
334 // Steps 1-3.
335 mozilla::CheckedInt64 millis = int64_t(hours);
336 millis *= 60;
337 millis += int64_t(minutes);
338 millis *= 60;
339 millis += int64_t(seconds);
340 millis *= 1000;
341 millis += int64_t(milliseconds);
342 MOZ_ASSERT(millis.isValid());
344 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
346 // Step 4.
347 auto micros = Int96::fromInteger(microseconds);
348 MOZ_ASSERT(micros);
350 normalized += NormalizeMicroseconds(*micros);
352 // Step 5.
353 auto nanos = Int96::fromInteger(nanoseconds);
354 MOZ_ASSERT(nanos);
356 normalized += NormalizeNanoseconds(*nanos);
358 // Step 6.
359 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
361 // Step 7.
362 return normalized;
366 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
367 * nanoseconds )
369 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
370 int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
371 int32_t microseconds, int32_t nanoseconds) {
372 // Steps 1-3.
373 mozilla::CheckedInt64 millis = int64_t(hours);
374 millis *= 60;
375 millis += int64_t(minutes);
376 millis *= 60;
377 millis += int64_t(seconds);
378 millis *= 1000;
379 millis += int64_t(milliseconds);
380 MOZ_ASSERT(millis.isValid());
382 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
384 // Step 4.
385 normalized += NormalizeMicroseconds(Int96{microseconds});
387 // Step 5.
388 normalized += NormalizeNanoseconds(Int96{nanoseconds});
390 // Step 6.
391 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
393 // Step 7.
394 return normalized;
398 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
399 * nanoseconds )
401 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
402 const Duration& duration) {
403 MOZ_ASSERT(IsValidDuration(duration));
405 return ::NormalizeTimeDuration(duration.hours, duration.minutes,
406 duration.seconds, duration.milliseconds,
407 duration.microseconds, duration.nanoseconds);
411 * AddNormalizedTimeDuration ( one, two )
413 static bool AddNormalizedTimeDuration(JSContext* cx,
414 const NormalizedTimeDuration& one,
415 const NormalizedTimeDuration& two,
416 NormalizedTimeDuration* result) {
417 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
418 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
420 // Step 1.
421 auto sum = one + two;
423 // Step 2.
424 if (!IsValidNormalizedTimeDuration(sum)) {
425 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
426 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
427 return false;
430 // Step 3.
431 *result = sum;
432 return true;
436 * SubtractNormalizedTimeDuration ( one, two )
438 static bool SubtractNormalizedTimeDuration(JSContext* cx,
439 const NormalizedTimeDuration& one,
440 const NormalizedTimeDuration& two,
441 NormalizedTimeDuration* result) {
442 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
443 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
445 // Step 1.
446 auto sum = one - two;
448 // Step 2.
449 if (!IsValidNormalizedTimeDuration(sum)) {
450 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
451 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
452 return false;
455 // Step 3.
456 *result = sum;
457 return true;
461 * Add24HourDaysToNormalizedTimeDuration ( d, days )
463 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
464 JSContext* cx, const NormalizedTimeDuration& d, double days,
465 NormalizedTimeDuration* result) {
466 MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
467 MOZ_ASSERT(IsInteger(days));
469 // Step 1.
470 auto normalizedDays = NormalizeDays(days);
471 if (!normalizedDays) {
472 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
473 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
474 return false;
477 // Step 2.
478 auto sum = d + *normalizedDays;
479 if (!IsValidNormalizedTimeDuration(sum)) {
480 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
481 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
482 return false;
485 // Step 3.
486 *result = sum;
487 return true;
491 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
493 bool js::temporal::CombineDateAndNormalizedTimeDuration(
494 JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
495 NormalizedDuration* result) {
496 MOZ_ASSERT(IsValidDuration(date.toDuration()));
497 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
499 // Step 1.
500 int32_t dateSign = ::DurationSign(date.toDuration());
502 // Step 2.
503 int32_t timeSign = NormalizedTimeDurationSign(time);
505 // Step 3
506 if ((dateSign * timeSign) < 0) {
507 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
508 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
509 return false;
512 // Step 4.
513 *result = {date, time};
514 return true;
518 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
520 NormalizedTimeDuration
521 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
522 const Instant& one, const Instant& two) {
523 MOZ_ASSERT(IsValidEpochInstant(one));
524 MOZ_ASSERT(IsValidEpochInstant(two));
526 // Step 1.
527 auto result = one - two;
529 // Step 2.
530 MOZ_ASSERT(IsValidInstantSpan(result));
532 // Step 3.
533 return result.to<NormalizedTimeDuration>();
537 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
538 * milliseconds, microseconds, nanoseconds )
540 bool js::temporal::IsValidDuration(const Duration& duration) {
541 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
543 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
544 microseconds, nanoseconds] = duration;
546 // Step 1.
547 int32_t sign = DurationSign(duration);
549 // Step 2.
550 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
551 milliseconds, microseconds, nanoseconds}) {
552 // Step 2.a.
553 if (!std::isfinite(v)) {
554 return false;
557 // Step 2.b.
558 if (v < 0 && sign > 0) {
559 return false;
562 // Step 2.c.
563 if (v > 0 && sign < 0) {
564 return false;
568 // Step 3.
569 if (std::abs(years) >= double(int64_t(1) << 32)) {
570 return false;
573 // Step 4.
574 if (std::abs(months) >= double(int64_t(1) << 32)) {
575 return false;
578 // Step 5.
579 if (std::abs(weeks) >= double(int64_t(1) << 32)) {
580 return false;
583 // Steps 6-8.
584 if (!NormalizeSeconds(duration)) {
585 return false;
588 // Step 9.
589 return true;
592 #ifdef DEBUG
594 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
595 * milliseconds, microseconds, nanoseconds )
597 bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
598 auto date = duration.date.toDuration();
599 return IsValidDuration(date) &&
600 IsValidNormalizedTimeDuration(duration.time) &&
601 (DurationSign(date) * NormalizedTimeDurationSign(duration.time) >= 0);
603 #endif
605 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
606 const char* name, unsigned errorNumber) {
607 ToCStringBuf cbuf;
608 const char* numStr = NumberToCString(&cbuf, value);
610 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
611 numStr);
612 return false;
616 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
617 * milliseconds, microseconds, nanoseconds )
619 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
620 const Duration& duration) {
621 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
623 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
624 microseconds, nanoseconds] = duration;
626 // Step 1.
627 int32_t sign = DurationSign(duration);
629 auto throwIfInvalid = [&](double v, const char* name) {
630 // Step 2.a.
631 if (!std::isfinite(v)) {
632 return ThrowInvalidDurationPart(
633 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
636 // Steps 2.b-c.
637 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
638 return ThrowInvalidDurationPart(cx, v, name,
639 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
642 return true;
645 auto throwIfTooLarge = [&](double v, const char* name) {
646 if (std::abs(v) >= double(int64_t(1) << 32)) {
647 return ThrowInvalidDurationPart(
648 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
650 return true;
653 // Step 2.
654 if (!throwIfInvalid(years, "years")) {
655 return false;
657 if (!throwIfInvalid(months, "months")) {
658 return false;
660 if (!throwIfInvalid(weeks, "weeks")) {
661 return false;
663 if (!throwIfInvalid(days, "days")) {
664 return false;
666 if (!throwIfInvalid(hours, "hours")) {
667 return false;
669 if (!throwIfInvalid(minutes, "minutes")) {
670 return false;
672 if (!throwIfInvalid(seconds, "seconds")) {
673 return false;
675 if (!throwIfInvalid(milliseconds, "milliseconds")) {
676 return false;
678 if (!throwIfInvalid(microseconds, "microseconds")) {
679 return false;
681 if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
682 return false;
685 // Step 3.
686 if (!throwIfTooLarge(years, "years")) {
687 return false;
690 // Step 4.
691 if (!throwIfTooLarge(months, "months")) {
692 return false;
695 // Step 5.
696 if (!throwIfTooLarge(weeks, "weeks")) {
697 return false;
700 // Steps 6-8.
701 if (!NormalizeSeconds(duration)) {
702 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
703 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
704 return false;
707 MOZ_ASSERT(IsValidDuration(duration));
709 // Step 9.
710 return true;
714 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
715 * milliseconds, microseconds, nanoseconds )
717 static bool ThrowIfInvalidDuration(JSContext* cx,
718 const DateDuration& duration) {
719 auto& [years, months, weeks, days] = duration;
721 // Step 1.
722 int32_t sign = DurationSign(duration.toDuration());
724 auto throwIfInvalid = [&](int64_t v, const char* name) {
725 // Step 2.a. (Not applicable)
727 // Steps 2.b-c.
728 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
729 return ThrowInvalidDurationPart(cx, v, name,
730 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
733 return true;
736 auto throwIfTooLarge = [&](int64_t v, const char* name) {
737 if (std::abs(v) >= (int64_t(1) << 32)) {
738 return ThrowInvalidDurationPart(
739 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
741 return true;
744 // Step 2.
745 if (!throwIfInvalid(years, "years")) {
746 return false;
748 if (!throwIfInvalid(months, "months")) {
749 return false;
751 if (!throwIfInvalid(weeks, "weeks")) {
752 return false;
754 if (!throwIfInvalid(days, "days")) {
755 return false;
758 // Step 3.
759 if (!throwIfTooLarge(years, "years")) {
760 return false;
763 // Step 4.
764 if (!throwIfTooLarge(months, "months")) {
765 return false;
768 // Step 5.
769 if (!throwIfTooLarge(weeks, "weeks")) {
770 return false;
773 // Steps 6-8.
774 if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
775 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
776 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
777 return false;
780 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
782 // Step 9.
783 return true;
787 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
788 * seconds, milliseconds, microseconds )
790 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
791 MOZ_ASSERT(IsIntegerDuration(duration));
793 // Step 1.
794 if (duration.years != 0) {
795 return TemporalUnit::Year;
798 // Step 2.
799 if (duration.months != 0) {
800 return TemporalUnit::Month;
803 // Step 3.
804 if (duration.weeks != 0) {
805 return TemporalUnit::Week;
808 // Step 4.
809 if (duration.days != 0) {
810 return TemporalUnit::Day;
813 // Step 5.
814 if (duration.hours != 0) {
815 return TemporalUnit::Hour;
818 // Step 6.
819 if (duration.minutes != 0) {
820 return TemporalUnit::Minute;
823 // Step 7.
824 if (duration.seconds != 0) {
825 return TemporalUnit::Second;
828 // Step 8.
829 if (duration.milliseconds != 0) {
830 return TemporalUnit::Millisecond;
833 // Step 9.
834 if (duration.microseconds != 0) {
835 return TemporalUnit::Microsecond;
838 // Step 10.
839 return TemporalUnit::Nanosecond;
843 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
844 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
846 static DurationObject* CreateTemporalDuration(JSContext* cx,
847 const CallArgs& args,
848 const Duration& duration) {
849 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
850 microseconds, nanoseconds] = duration;
852 // Step 1.
853 if (!ThrowIfInvalidDuration(cx, duration)) {
854 return nullptr;
857 // Steps 2-3.
858 Rooted<JSObject*> proto(cx);
859 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
860 return nullptr;
863 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
864 if (!object) {
865 return nullptr;
868 // Steps 4-13.
869 // Add zero to convert -0 to +0.
870 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
871 object->setFixedSlot(DurationObject::MONTHS_SLOT,
872 NumberValue(months + (+0.0)));
873 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
874 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
875 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
876 object->setFixedSlot(DurationObject::MINUTES_SLOT,
877 NumberValue(minutes + (+0.0)));
878 object->setFixedSlot(DurationObject::SECONDS_SLOT,
879 NumberValue(seconds + (+0.0)));
880 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
881 NumberValue(milliseconds + (+0.0)));
882 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
883 NumberValue(microseconds + (+0.0)));
884 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
885 NumberValue(nanoseconds + (+0.0)));
887 // Step 14.
888 return object;
892 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
893 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
895 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
896 const Duration& duration) {
897 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
898 microseconds, nanoseconds] = duration;
900 MOZ_ASSERT(IsInteger(years));
901 MOZ_ASSERT(IsInteger(months));
902 MOZ_ASSERT(IsInteger(weeks));
903 MOZ_ASSERT(IsInteger(days));
904 MOZ_ASSERT(IsInteger(hours));
905 MOZ_ASSERT(IsInteger(minutes));
906 MOZ_ASSERT(IsInteger(seconds));
907 MOZ_ASSERT(IsInteger(milliseconds));
908 MOZ_ASSERT(IsInteger(microseconds));
909 MOZ_ASSERT(IsInteger(nanoseconds));
911 // Step 1.
912 if (!ThrowIfInvalidDuration(cx, duration)) {
913 return nullptr;
916 // Steps 2-3.
917 auto* object = NewBuiltinClassInstance<DurationObject>(cx);
918 if (!object) {
919 return nullptr;
922 // Steps 4-13.
923 // Add zero to convert -0 to +0.
924 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
925 object->setFixedSlot(DurationObject::MONTHS_SLOT,
926 NumberValue(months + (+0.0)));
927 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
928 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
929 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
930 object->setFixedSlot(DurationObject::MINUTES_SLOT,
931 NumberValue(minutes + (+0.0)));
932 object->setFixedSlot(DurationObject::SECONDS_SLOT,
933 NumberValue(seconds + (+0.0)));
934 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
935 NumberValue(milliseconds + (+0.0)));
936 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
937 NumberValue(microseconds + (+0.0)));
938 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
939 NumberValue(nanoseconds + (+0.0)));
941 // Step 14.
942 return object;
946 * ToIntegerIfIntegral ( argument )
948 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
949 Handle<Value> argument, double* num) {
950 // Step 1.
951 double d;
952 if (!JS::ToNumber(cx, argument, &d)) {
953 return false;
956 // Step 2.
957 if (!js::IsInteger(d)) {
958 ToCStringBuf cbuf;
959 const char* numStr = NumberToCString(&cbuf, d);
961 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
962 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
963 name);
964 return false;
967 // Step 3.
968 *num = d;
969 return true;
973 * ToIntegerIfIntegral ( argument )
975 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
976 Handle<Value> argument, double* result) {
977 // Step 1.
978 double d;
979 if (!JS::ToNumber(cx, argument, &d)) {
980 return false;
983 // Step 2.
984 if (!js::IsInteger(d)) {
985 if (auto nameStr = js::QuoteString(cx, name)) {
986 ToCStringBuf cbuf;
987 const char* numStr = NumberToCString(&cbuf, d);
989 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
990 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
991 nameStr.get());
993 return false;
996 // Step 3.
997 *result = d;
998 return true;
1002 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1004 static bool ToTemporalPartialDurationRecord(
1005 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
1006 // Steps 1-3. (Not applicable in our implementation.)
1008 Rooted<Value> value(cx);
1009 bool any = false;
1011 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
1012 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
1013 &value)) {
1014 return false;
1017 if (!value.isUndefined()) {
1018 any = true;
1020 if (!ToIntegerIfIntegral(cx, name, value, num)) {
1021 return false;
1024 return true;
1027 // Steps 4-23.
1028 if (!getDurationProperty(cx->names().days, &result->days)) {
1029 return false;
1031 if (!getDurationProperty(cx->names().hours, &result->hours)) {
1032 return false;
1034 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
1035 return false;
1037 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
1038 return false;
1040 if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
1041 return false;
1043 if (!getDurationProperty(cx->names().months, &result->months)) {
1044 return false;
1046 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
1047 return false;
1049 if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
1050 return false;
1052 if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
1053 return false;
1055 if (!getDurationProperty(cx->names().years, &result->years)) {
1056 return false;
1059 // Step 24.
1060 if (!any) {
1061 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1062 JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
1063 return false;
1066 // Step 25.
1067 return true;
1071 * ToTemporalDurationRecord ( temporalDurationLike )
1073 bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
1074 Handle<Value> temporalDurationLike,
1075 Duration* result) {
1076 // Step 1.
1077 if (!temporalDurationLike.isObject()) {
1078 // Step 1.a.
1079 if (!temporalDurationLike.isString()) {
1080 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
1081 temporalDurationLike, nullptr, "not a string");
1082 return false;
1084 Rooted<JSString*> string(cx, temporalDurationLike.toString());
1086 // Step 1.b.
1087 return ParseTemporalDurationString(cx, string, result);
1090 Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
1092 // Step 2.
1093 if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
1094 *result = ToDuration(duration);
1095 return true;
1098 // Step 3.
1099 Duration duration = {};
1101 // Steps 4-14.
1102 if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
1103 return false;
1106 // Step 15.
1107 if (!ThrowIfInvalidDuration(cx, duration)) {
1108 return false;
1111 // Step 16.
1112 *result = duration;
1113 return true;
1117 * ToTemporalDuration ( item )
1119 Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
1120 Handle<Value> item) {
1121 // Step 1.
1122 if (item.isObject()) {
1123 JSObject* itemObj = &item.toObject();
1124 if (itemObj->canUnwrapAs<DurationObject>()) {
1125 return itemObj;
1129 // Step 2.
1130 Duration result;
1131 if (!ToTemporalDurationRecord(cx, item, &result)) {
1132 return nullptr;
1135 // Step 3.
1136 return CreateTemporalDuration(cx, result);
1140 * ToTemporalDuration ( item )
1142 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
1143 Duration* result) {
1144 auto obj = ToTemporalDuration(cx, item);
1145 if (!obj) {
1146 return false;
1149 *result = ToDuration(&obj.unwrap());
1150 return true;
1154 * DaysUntil ( earlier, later )
1156 int32_t js::temporal::DaysUntil(const PlainDate& earlier,
1157 const PlainDate& later) {
1158 MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
1159 MOZ_ASSERT(ISODateTimeWithinLimits(later));
1161 // Steps 1-2.
1162 int32_t epochDaysEarlier = MakeDay(earlier);
1163 MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
1165 // Steps 3-4.
1166 int32_t epochDaysLater = MakeDay(later);
1167 MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
1169 // Step 5.
1170 return epochDaysLater - epochDaysEarlier;
1174 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1176 static bool MoveRelativeDate(
1177 JSContext* cx, Handle<CalendarRecord> calendar,
1178 Handle<Wrapped<PlainDateObject*>> relativeTo, const Duration& duration,
1179 MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
1180 int32_t* daysResult) {
1181 auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
1182 if (!unwrappedRelativeTo) {
1183 return false;
1185 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1187 // Step 1.
1188 auto newDate = AddDate(cx, calendar, relativeTo, duration);
1189 if (!newDate) {
1190 return false;
1192 auto later = ToPlainDate(&newDate.unwrap());
1193 relativeToResult.set(newDate);
1195 // Step 2.
1196 *daysResult = DaysUntil(relativeToDate, later);
1197 MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
1199 // Step 3.
1200 return true;
1204 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1205 * months, weeks, days, precalculatedPlainDateTime )
1207 static bool MoveRelativeZonedDateTime(
1208 JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
1209 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
1210 const DateDuration& duration,
1211 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1212 MutableHandle<ZonedDateTime> result) {
1213 // Step 1.
1214 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1215 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1217 // Step 2.
1218 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1219 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1221 // Step 3.
1222 Instant intermediateNs;
1223 if (precalculatedPlainDateTime) {
1224 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1225 duration, *precalculatedPlainDateTime,
1226 &intermediateNs)) {
1227 return false;
1229 } else {
1230 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1231 duration, &intermediateNs)) {
1232 return false;
1235 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
1237 // Step 4.
1238 result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
1239 zonedDateTime.calendar()});
1240 return true;
1244 * Split duration into full days and remainding nanoseconds.
1246 static NormalizedTimeAndDays NormalizedTimeDurationToDays(
1247 const NormalizedTimeDuration& duration) {
1248 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1250 auto [seconds, nanoseconds] = duration;
1251 if (seconds < 0 && nanoseconds > 0) {
1252 seconds += 1;
1253 nanoseconds -= 1'000'000'000;
1256 int64_t days = seconds / ToSeconds(TemporalUnit::Day);
1257 seconds = seconds % ToSeconds(TemporalUnit::Day);
1259 int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
1261 constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
1262 MOZ_ASSERT(std::abs(time) < dayLength);
1264 return {days, time, dayLength};
1268 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1269 * microseconds, nanoseconds )
1271 static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
1272 int64_t minutes, int64_t seconds,
1273 int64_t milliseconds,
1274 double microseconds,
1275 double nanoseconds) {
1276 // Step 1.
1277 MOZ_ASSERT(IsValidDuration(
1278 {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
1279 double(milliseconds), microseconds, nanoseconds}));
1281 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1282 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1283 MOZ_ASSERT(IsSafeInteger(days));
1284 MOZ_ASSERT(IsSafeInteger(hours));
1285 MOZ_ASSERT(IsSafeInteger(minutes));
1286 MOZ_ASSERT(IsSafeInteger(seconds));
1288 // |milliseconds| is explicitly casted to double by consumers, so we can also
1289 // omit the `ℝ(𝔽(x))` conversion.
1291 // Step 2.
1292 // NB: Adds +0.0 to correctly handle negative zero.
1293 return {
1294 days,
1295 hours,
1296 minutes,
1297 seconds,
1298 milliseconds,
1299 microseconds + (+0.0),
1300 nanoseconds + (+0.0),
1305 * BalanceTimeDuration ( norm, largestUnit )
1307 TimeDuration js::temporal::BalanceTimeDuration(
1308 const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
1309 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1311 auto [seconds, nanoseconds] = duration;
1313 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1314 // Convert these back to their absolute value and adjust the seconds part
1315 // accordingly.
1317 // For example the nanoseconds duration |-1n| is represented as the
1318 // duration {seconds: -1, nanoseconds: 999'999'999}.
1319 if (seconds < 0 && nanoseconds > 0) {
1320 seconds += 1;
1321 nanoseconds -= ToNanoseconds(TemporalUnit::Second);
1324 // Step 1.
1325 int64_t days = 0;
1326 int64_t hours = 0;
1327 int64_t minutes = 0;
1328 int64_t milliseconds = 0;
1329 int64_t microseconds = 0;
1331 // Steps 2-3. (Not applicable in our implementation.)
1333 // We don't need to convert to positive numbers, because integer division
1334 // truncates and the %-operator has modulo semantics.
1336 // Steps 4-10.
1337 switch (largestUnit) {
1338 // Step 4.
1339 case TemporalUnit::Year:
1340 case TemporalUnit::Month:
1341 case TemporalUnit::Week:
1342 case TemporalUnit::Day: {
1343 // Step 4.a.
1344 microseconds = nanoseconds / 1000;
1346 // Step 4.b.
1347 nanoseconds = nanoseconds % 1000;
1349 // Step 4.c.
1350 milliseconds = microseconds / 1000;
1352 // Step 4.d.
1353 microseconds = microseconds % 1000;
1355 // Steps 4.e-f. (Not applicable)
1356 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1358 // Step 4.g.
1359 minutes = seconds / 60;
1361 // Step 4.h.
1362 seconds = seconds % 60;
1364 // Step 4.i.
1365 hours = minutes / 60;
1367 // Step 4.j.
1368 minutes = minutes % 60;
1370 // Step 4.k.
1371 days = hours / 24;
1373 // Step 4.l.
1374 hours = hours % 24;
1376 break;
1379 // Step 5.
1380 case TemporalUnit::Hour: {
1381 // Step 5.a.
1382 microseconds = nanoseconds / 1000;
1384 // Step 5.b.
1385 nanoseconds = nanoseconds % 1000;
1387 // Step 5.c.
1388 milliseconds = microseconds / 1000;
1390 // Step 5.d.
1391 microseconds = microseconds % 1000;
1393 // Steps 5.e-f. (Not applicable)
1394 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1396 // Step 5.g.
1397 minutes = seconds / 60;
1399 // Step 5.h.
1400 seconds = seconds % 60;
1402 // Step 5.i.
1403 hours = minutes / 60;
1405 // Step 5.j.
1406 minutes = minutes % 60;
1408 break;
1411 case TemporalUnit::Minute: {
1412 // Step 6.a.
1413 microseconds = nanoseconds / 1000;
1415 // Step 6.b.
1416 nanoseconds = nanoseconds % 1000;
1418 // Step 6.c.
1419 milliseconds = microseconds / 1000;
1421 // Step 6.d.
1422 microseconds = microseconds % 1000;
1424 // Steps 6.e-f. (Not applicable)
1425 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1427 // Step 6.g.
1428 minutes = seconds / 60;
1430 // Step 6.h.
1431 seconds = seconds % 60;
1433 break;
1436 // Step 7.
1437 case TemporalUnit::Second: {
1438 // Step 7.a.
1439 microseconds = nanoseconds / 1000;
1441 // Step 7.b.
1442 nanoseconds = nanoseconds % 1000;
1444 // Step 7.c.
1445 milliseconds = microseconds / 1000;
1447 // Step 7.d.
1448 microseconds = microseconds % 1000;
1450 // Steps 7.e-f. (Not applicable)
1451 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1453 break;
1456 // Step 8.
1457 case TemporalUnit::Millisecond: {
1458 static_assert((NormalizedTimeDuration::max().seconds + 1) *
1459 ToMilliseconds(TemporalUnit::Second) <=
1460 INT64_MAX,
1461 "total number duration milliseconds fits into int64");
1463 int64_t millis = seconds * ToMilliseconds(TemporalUnit::Second);
1465 // Set to zero per step 1.
1466 seconds = 0;
1468 // Step 8.a.
1469 microseconds = nanoseconds / 1000;
1471 // Step 8.b.
1472 nanoseconds = nanoseconds % 1000;
1474 // Step 8.c.
1475 milliseconds = microseconds / 1000;
1477 // Step 8.d.
1478 microseconds = microseconds % 1000;
1480 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1481 milliseconds += millis;
1483 break;
1486 // Step 9.
1487 case TemporalUnit::Microsecond: {
1488 // Step 9.a.
1489 int64_t microseconds = nanoseconds / 1000;
1491 // Step 9.b.
1492 nanoseconds = nanoseconds % 1000;
1494 MOZ_ASSERT(std::abs(microseconds) <= 999'999);
1495 double micros =
1496 std::fma(double(seconds), ToMicroseconds(TemporalUnit::Second),
1497 double(microseconds));
1499 // Step 11.
1500 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros,
1501 double(nanoseconds));
1504 // Step 10.
1505 case TemporalUnit::Nanosecond: {
1506 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
1507 double nanos =
1508 std::fma(double(seconds), ToNanoseconds(TemporalUnit::Second),
1509 double(nanoseconds));
1511 // Step 11.
1512 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos);
1515 case TemporalUnit::Auto:
1516 MOZ_CRASH("Unexpected temporal unit");
1519 // Step 11.
1520 return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
1521 double(microseconds), double(nanoseconds));
1525 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1526 * timeZoneRec, precalculatedPlainDateTime )
1528 static bool BalanceTimeDurationRelative(
1529 JSContext* cx, const NormalizedDuration& duration, TemporalUnit largestUnit,
1530 Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
1531 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1532 TimeDuration* result) {
1533 MOZ_ASSERT(IsValidDuration(duration));
1535 // Step 1.
1536 const auto& startNs = relativeTo.instant();
1538 // Step 2.
1539 const auto& startInstant = startNs;
1541 // Step 3.
1542 auto intermediateNs = startNs;
1544 // Step 4.
1545 PlainDateTime startDateTime;
1546 if (duration.date.days != 0) {
1547 // Step 4.a.
1548 if (!precalculatedPlainDateTime) {
1549 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1550 return false;
1552 precalculatedPlainDateTime =
1553 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1556 // Steps 4.b-c.
1557 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
1558 if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
1559 timeZone, isoCalendar, duration.date.days,
1560 &intermediateNs)) {
1561 return false;
1565 // Step 5.
1566 Instant endNs;
1567 if (!AddInstant(cx, intermediateNs, duration.time, &endNs)) {
1568 return false;
1570 MOZ_ASSERT(IsValidEpochInstant(endNs));
1572 // Step 6.
1573 auto normalized =
1574 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startInstant);
1576 // Step 7.
1577 if (normalized == NormalizedTimeDuration{}) {
1578 *result = {};
1579 return true;
1582 // Steps 8-9.
1583 int64_t days = 0;
1584 if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
1585 // Step 8.a.
1586 if (!precalculatedPlainDateTime) {
1587 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1588 return false;
1590 precalculatedPlainDateTime =
1591 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1594 // Step 8.b.
1595 NormalizedTimeAndDays timeAndDays;
1596 if (!NormalizedTimeDurationToDays(cx, normalized, relativeTo, timeZone,
1597 *precalculatedPlainDateTime,
1598 &timeAndDays)) {
1599 return false;
1602 // Step 8.c.
1603 days = timeAndDays.days;
1605 // Step 8.d.
1606 normalized = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
1607 MOZ_ASSERT_IF(days > 0, normalized >= NormalizedTimeDuration{});
1608 MOZ_ASSERT_IF(days < 0, normalized <= NormalizedTimeDuration{});
1610 // Step 8.e.
1611 largestUnit = TemporalUnit::Hour;
1614 // Step 10.
1615 auto balanceResult = BalanceTimeDuration(normalized, largestUnit);
1617 // Step 11.
1618 *result = {
1619 days,
1620 balanceResult.hours,
1621 balanceResult.minutes,
1622 balanceResult.seconds,
1623 balanceResult.milliseconds,
1624 balanceResult.microseconds,
1625 balanceResult.nanoseconds,
1627 MOZ_ASSERT(IsValidDuration(result->toDuration()));
1628 return true;
1632 * CreateDateDurationRecord ( years, months, weeks, days )
1634 static DateDuration CreateDateDurationRecord(int64_t years, int64_t months,
1635 int64_t weeks, int64_t days) {
1636 MOZ_ASSERT(IsValidDuration(Duration{
1637 double(years),
1638 double(months),
1639 double(weeks),
1640 double(days),
1641 }));
1642 return {years, months, weeks, days};
1646 * CreateDateDurationRecord ( years, months, weeks, days )
1648 static bool CreateDateDurationRecord(JSContext* cx, int64_t years,
1649 int64_t months, int64_t weeks,
1650 int64_t days, DateDuration* result) {
1651 auto duration = DateDuration{years, months, weeks, days};
1652 if (!ThrowIfInvalidDuration(cx, duration)) {
1653 return false;
1656 *result = duration;
1657 return true;
1660 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration& duration,
1661 TemporalUnit largestUnit) {
1662 MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
1664 // Steps 2, 3.a-b, 4.a-b, 6-7.
1665 return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
1666 (largestUnit > TemporalUnit::Month && duration.months != 0) ||
1667 (largestUnit > TemporalUnit::Week && duration.weeks != 0);
1671 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1672 * plainRelativeTo, calendarRec )
1674 static bool UnbalanceDateDurationRelative(
1675 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1676 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1677 Handle<CalendarRecord> calendar, DateDuration* result) {
1678 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
1680 auto [years, months, weeks, days] = duration;
1682 // Step 1. (Not applicable in our implementation.)
1684 // Steps 2, 3.a, 4.a, and 6.
1685 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1686 // Steps 2.a, 3.a, 4.a, and 6.
1687 *result = duration;
1688 return true;
1691 // Step 3.
1692 if (largestUnit == TemporalUnit::Month) {
1693 // Step 3.a. (Handled above)
1694 MOZ_ASSERT(years != 0);
1696 // Step 3.b. (Not applicable in our implementation.)
1698 // Step 3.c.
1699 MOZ_ASSERT(
1700 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1702 // Step 3.d.
1703 MOZ_ASSERT(
1704 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1706 // Step 3.e.
1707 auto yearsDuration = Duration{double(years)};
1709 // Step 3.f.
1710 Rooted<Wrapped<PlainDateObject*>> later(
1711 cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
1712 if (!later) {
1713 return false;
1716 // Steps 3.g-i.
1717 Duration untilResult;
1718 if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
1719 TemporalUnit::Month, &untilResult)) {
1720 return false;
1723 // Step 3.j.
1724 int64_t yearsInMonths = int64_t(untilResult.months);
1726 // Step 3.k.
1727 return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
1728 result);
1731 // Step 4.
1732 if (largestUnit == TemporalUnit::Week) {
1733 // Step 4.a. (Handled above)
1734 MOZ_ASSERT(years != 0 || months != 0);
1736 // Step 4.b. (Not applicable in our implementation.)
1738 // Step 4.c.
1739 MOZ_ASSERT(
1740 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1742 // Step 4.d.
1743 auto yearsMonthsDuration = Duration{double(years), double(months)};
1745 // Step 4.e.
1746 auto later =
1747 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
1748 if (!later) {
1749 return false;
1751 auto laterDate = ToPlainDate(&later.unwrap());
1753 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1754 if (!unwrappedRelativeTo) {
1755 return false;
1757 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1759 // Step 4.f.
1760 int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
1762 // Step 4.g.
1763 return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
1764 result);
1767 // Step 5. (Not applicable in our implementation.)
1769 // Step 6. (Handled above)
1770 MOZ_ASSERT(years != 0 || months != 0 || weeks != 0);
1772 // FIXME: why don't we unconditionally throw an error for missing calendars?
1774 // Step 7. (Not applicable in our implementation.)
1776 // Step 8.
1777 MOZ_ASSERT(
1778 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1780 // Step 9.
1781 auto yearsMonthsWeeksDuration =
1782 Duration{double(years), double(months), double(weeks)};
1784 // Step 10.
1785 auto later =
1786 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
1787 if (!later) {
1788 return false;
1790 auto laterDate = ToPlainDate(&later.unwrap());
1792 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1793 if (!unwrappedRelativeTo) {
1794 return false;
1796 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1798 // Step 11.
1799 int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
1801 // Step 12.
1802 return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
1803 result);
1807 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1808 * plainRelativeTo, calendarRec )
1810 static bool UnbalanceDateDurationRelative(JSContext* cx,
1811 const DateDuration& duration,
1812 TemporalUnit largestUnit,
1813 DateDuration* result) {
1814 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
1816 // Step 1. (Not applicable.)
1818 // Steps 2, 3.a, 4.a, and 6.
1819 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1820 // Steps 2.a, 3.a, 4.a, and 6.
1821 *result = duration;
1822 return true;
1825 // Step 5. (Not applicable.)
1827 // Steps 3.b, 4.b, and 7.
1828 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1829 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
1830 return false;
1834 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1835 * smallestUnit, plainRelativeTo, calendarRec )
1837 static bool BalanceDateDurationRelative(
1838 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1839 TemporalUnit smallestUnit,
1840 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1841 Handle<CalendarRecord> calendar, DateDuration* result) {
1842 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
1843 MOZ_ASSERT(largestUnit <= smallestUnit);
1845 auto [years, months, weeks, days] = duration;
1847 // FIXME: spec issue - effectful code paths should be more fine-grained
1848 // similar to UnbalanceDateDurationRelative. For example:
1849 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1850 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1851 // 3. Else if days = 0, then no-op.
1853 // Also note that |weeks| is never balanced, even when non-zero.
1855 // Step 1. (Not applicable in our implementation.)
1857 // Steps 2-4.
1858 if (largestUnit > TemporalUnit::Week ||
1859 (years == 0 && months == 0 && weeks == 0 && days == 0)) {
1860 // Step 4.a.
1861 *result = duration;
1862 return true;
1865 // Step 5.
1866 if (!plainRelativeTo) {
1867 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1868 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
1869 "relativeTo");
1870 return false;
1873 // Step 6.
1874 MOZ_ASSERT(
1875 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1877 // Step 7.
1878 MOZ_ASSERT(
1879 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1881 // Steps 8-9. (Not applicable in our implementation.)
1883 auto untilAddedDate = [&](const Duration& duration, Duration* untilResult) {
1884 Rooted<Wrapped<PlainDateObject*>> later(
1885 cx, AddDate(cx, calendar, plainRelativeTo, duration));
1886 if (!later) {
1887 return false;
1890 return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
1891 untilResult);
1894 // Step 10.
1895 if (largestUnit == TemporalUnit::Year) {
1896 // Step 10.a.
1897 if (smallestUnit == TemporalUnit::Week) {
1898 // Step 10.a.i.
1899 MOZ_ASSERT(days == 0);
1901 // Step 10.a.ii.
1902 auto yearsMonthsDuration = Duration{double(years), double(months)};
1904 // Steps 10.a.iii-iv.
1905 Duration untilResult;
1906 if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
1907 return false;
1910 // Step 10.a.v.
1911 *result = CreateDateDurationRecord(int64_t(untilResult.years),
1912 int64_t(untilResult.months), weeks, 0);
1913 return true;
1916 // Step 10.b.
1917 auto yearsMonthsWeeksDaysDuration =
1918 Duration{double(years), double(months), double(weeks), double(days)};
1920 // Steps 10.c-d.
1921 Duration untilResult;
1922 if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
1923 return false;
1926 // FIXME: spec bug - CreateDateDurationRecord is infallible
1927 // https://github.com/tc39/proposal-temporal/issues/2750
1929 // Step 10.e.
1930 *result = CreateDateDurationRecord(
1931 int64_t(untilResult.years), int64_t(untilResult.months),
1932 int64_t(untilResult.weeks), int64_t(untilResult.days));
1933 return true;
1936 // Step 11.
1937 if (largestUnit == TemporalUnit::Month) {
1938 // Step 11.a.
1939 MOZ_ASSERT(years == 0);
1941 // Step 11.b.
1942 if (smallestUnit == TemporalUnit::Week) {
1943 // Step 10.b.i.
1944 MOZ_ASSERT(days == 0);
1946 // Step 10.b.ii.
1947 *result = CreateDateDurationRecord(0, months, weeks, 0);
1948 return true;
1951 // Step 11.c.
1952 auto monthsWeeksDaysDuration =
1953 Duration{0, double(months), double(weeks), double(days)};
1955 // Steps 11.d-e.
1956 Duration untilResult;
1957 if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
1958 return false;
1961 // FIXME: spec bug - CreateDateDurationRecord is infallible
1962 // https://github.com/tc39/proposal-temporal/issues/2750
1964 // Step 11.f.
1965 *result = CreateDateDurationRecord(0, int64_t(untilResult.months),
1966 int64_t(untilResult.weeks),
1967 int64_t(untilResult.days));
1968 return true;
1971 // Step 12.
1972 MOZ_ASSERT(largestUnit == TemporalUnit::Week);
1974 // Step 13.
1975 MOZ_ASSERT(years == 0);
1977 // Step 14.
1978 MOZ_ASSERT(months == 0);
1980 // Step 15.
1981 auto weeksDaysDuration = Duration{0, 0, double(weeks), double(days)};
1983 // Steps 16-17.
1984 Duration untilResult;
1985 if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
1986 return false;
1989 // FIXME: spec bug - CreateDateDurationRecord is infallible
1990 // https://github.com/tc39/proposal-temporal/issues/2750
1992 // Step 18.
1993 *result = CreateDateDurationRecord(0, 0, int64_t(untilResult.weeks),
1994 int64_t(untilResult.days));
1995 return true;
1999 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2000 * smallestUnit, plainRelativeTo, calendarRec )
2002 bool js::temporal::BalanceDateDurationRelative(
2003 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
2004 TemporalUnit smallestUnit,
2005 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2006 Handle<CalendarRecord> calendar, DateDuration* result) {
2007 MOZ_ASSERT(plainRelativeTo);
2008 MOZ_ASSERT(calendar.receiver());
2010 return ::BalanceDateDurationRelative(cx, duration, largestUnit, smallestUnit,
2011 plainRelativeTo, calendar, result);
2015 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2016 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2017 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2019 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2020 Duration* result) {
2021 MOZ_ASSERT(IsValidDuration(one));
2022 MOZ_ASSERT(IsValidDuration(two));
2024 // Steps 1-2. (Not applicable)
2026 // Step 3.
2027 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2029 // Step 4.
2030 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2032 // Step 5.
2033 auto largestUnit = std::min(largestUnit1, largestUnit2);
2035 // Step 6.a.
2036 if (largestUnit <= TemporalUnit::Week) {
2037 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2038 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
2039 "relativeTo");
2040 return false;
2043 // Step 6.b.
2044 auto normalized1 = NormalizeTimeDuration(one);
2046 // Step 6.c.
2047 auto normalized2 = NormalizeTimeDuration(two);
2049 // Step 6.d.
2050 NormalizedTimeDuration normalized;
2051 if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
2052 return false;
2055 // Step 6.e.
2056 if (!Add24HourDaysToNormalizedTimeDuration(
2057 cx, normalized, one.days + two.days, &normalized)) {
2058 return false;
2061 // Step 6.f.
2062 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2064 // Steps 6.g.
2065 *result = balanced.toDuration();
2066 return true;
2070 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2071 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2072 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2074 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2075 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2076 Handle<CalendarRecord> calendar, Duration* result) {
2077 MOZ_ASSERT(IsValidDuration(one));
2078 MOZ_ASSERT(IsValidDuration(two));
2080 // Steps 1-2. (Not applicable)
2082 // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
2083 // not undefined.
2085 // Step 3.
2086 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2088 // Step 4.
2089 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2091 // Step 5.
2092 auto largestUnit = std::min(largestUnit1, largestUnit2);
2094 // Step 6. (Not applicable)
2096 // Step 7.a. (Not applicable in our implementation.)
2098 // Step 7.b.
2099 auto dateDuration1 = Duration{one.years, one.months, one.weeks, one.days};
2101 // Step 7.c.
2102 auto dateDuration2 = Duration{two.years, two.months, two.weeks, two.days};
2104 // FIXME: spec issue - calendarUnitsPresent is unused.
2106 // Step 7.d.
2107 [[maybe_unused]] bool calendarUnitsPresent = true;
2109 // Step 7.e.
2110 if (dateDuration1.years == 0 && dateDuration1.months == 0 &&
2111 dateDuration1.weeks == 0 && dateDuration2.years == 0 &&
2112 dateDuration2.months == 0 && dateDuration2.weeks == 0) {
2113 calendarUnitsPresent = false;
2116 // Step 7.f.
2117 Rooted<Wrapped<PlainDateObject*>> intermediate(
2118 cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
2119 if (!intermediate) {
2120 return false;
2123 // Step 7.g.
2124 Rooted<Wrapped<PlainDateObject*>> end(
2125 cx, AddDate(cx, calendar, intermediate, dateDuration2));
2126 if (!end) {
2127 return false;
2130 // Step 7.h.
2131 auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
2133 // Steps 7.i-k.
2134 Duration dateDifference;
2135 if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
2136 &dateDifference)) {
2137 return false;
2140 // Step 7.l.
2141 auto normalized1 = NormalizeTimeDuration(one);
2143 // Step 7.m.
2144 auto normalized2 = NormalizeTimeDuration(two);
2146 // Step 7.n.
2147 NormalizedTimeDuration normalized1WithDays;
2148 if (!Add24HourDaysToNormalizedTimeDuration(
2149 cx, normalized1, dateDifference.days, &normalized1WithDays)) {
2150 return false;
2153 // Step 7.o.
2154 NormalizedTimeDuration normalized;
2155 if (!AddNormalizedTimeDuration(cx, normalized1WithDays, normalized2,
2156 &normalized)) {
2157 return false;
2160 // Step 7.p.
2161 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2163 // Steps 7.q.
2164 *result = {
2165 dateDifference.years, dateDifference.months,
2166 dateDifference.weeks, double(balanced.days),
2167 double(balanced.hours), double(balanced.minutes),
2168 double(balanced.seconds), double(balanced.milliseconds),
2169 balanced.microseconds, balanced.nanoseconds,
2171 MOZ_ASSERT(IsValidDuration(*result));
2172 return true;
2176 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2177 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2178 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2180 static bool AddDuration(
2181 JSContext* cx, const Duration& one, const Duration& two,
2182 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2183 Handle<TimeZoneRecord> timeZone,
2184 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2185 Duration* result) {
2186 // Steps 1-2. (Not applicable)
2188 // Step 3.
2189 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2191 // Step 4.
2192 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2194 // Step 5.
2195 auto largestUnit = std::min(largestUnit1, largestUnit2);
2197 // Steps 6-7. (Not applicable)
2199 // Steps 8-9. (Not applicable in our implementation.)
2201 // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
2203 // clang-format off
2205 // 10. If largestUnit is one of "year", "month", "week", or "day", then
2206 // a. If precalculatedPlainDateTime is undefined, then
2207 // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
2208 // b. Else,
2209 // i. Let startDateTime be precalculatedPlainDateTime.
2210 // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
2211 // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
2212 // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
2213 // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
2214 // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
2215 // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
2216 // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
2218 // clang-format on
2220 // Step 10.
2221 bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
2223 // Steps 11-17.
2224 if (!startDateTimeNeeded) {
2225 // Steps 11-12. (Not applicable)
2227 // Step 13.
2228 auto normalized1 = NormalizeTimeDuration(one);
2230 // Step 14.
2231 auto normalized2 = NormalizeTimeDuration(two);
2233 // Step 15. (Inlined AddZonedDateTime, step 6.)
2234 Instant intermediateNs;
2235 if (!AddInstant(cx, zonedRelativeTo.instant(), normalized1,
2236 &intermediateNs)) {
2237 return false;
2239 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2241 // Step 16. (Inlined AddZonedDateTime, step 6.)
2242 Instant endNs;
2243 if (!AddInstant(cx, intermediateNs, normalized2, &endNs)) {
2244 return false;
2246 MOZ_ASSERT(IsValidEpochInstant(endNs));
2248 // Step 17.a.
2249 auto normalized = NormalizedTimeDurationFromEpochNanosecondsDifference(
2250 endNs, zonedRelativeTo.instant());
2252 // Step 17.b.
2253 auto balanced = BalanceTimeDuration(normalized, largestUnit);
2255 // Step 17.c.
2256 *result = balanced.toDuration();
2257 return true;
2260 // Steps 11-12.
2261 PlainDateTime startDateTime;
2262 if (!precalculatedPlainDateTime) {
2263 if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
2264 &startDateTime)) {
2265 return false;
2267 } else {
2268 startDateTime = *precalculatedPlainDateTime;
2271 // Step 13.
2272 auto normalized1 = CreateNormalizedDurationRecord(one);
2274 // Step 14.
2275 auto normalized2 = CreateNormalizedDurationRecord(two);
2277 // Step 15.
2278 Instant intermediateNs;
2279 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2280 normalized1, startDateTime, &intermediateNs)) {
2281 return false;
2283 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2285 // Step 16.
2286 Instant endNs;
2287 if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, normalized2,
2288 &endNs)) {
2289 return false;
2291 MOZ_ASSERT(IsValidEpochInstant(endNs));
2293 // Step 17. (Not applicable)
2295 // Step 18.
2296 NormalizedDuration difference;
2297 if (!DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs, timeZone,
2298 calendar, largestUnit, startDateTime,
2299 &difference)) {
2300 return false;
2303 // Step 19.
2304 auto balanced = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
2306 // Step 20.
2307 *result = {
2308 double(difference.date.years), double(difference.date.months),
2309 double(difference.date.weeks), double(difference.date.days),
2310 double(balanced.hours), double(balanced.minutes),
2311 double(balanced.seconds), double(balanced.milliseconds),
2312 balanced.microseconds, balanced.nanoseconds,
2314 MOZ_ASSERT(IsValidDuration(*result));
2315 return true;
2319 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2320 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2321 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2323 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2324 Handle<ZonedDateTime> zonedRelativeTo,
2325 Handle<CalendarRecord> calendar,
2326 Handle<TimeZoneRecord> timeZone, Duration* result) {
2327 return AddDuration(cx, one, two, zonedRelativeTo, calendar, timeZone,
2328 mozilla::Nothing(), result);
2332 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2333 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2334 * precalculatedPlainDateTime )
2336 static bool AdjustRoundedDurationDays(
2337 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2338 TemporalUnit unit, TemporalRoundingMode roundingMode,
2339 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2340 Handle<TimeZoneRecord> timeZone,
2341 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2342 NormalizedDuration* result) {
2343 MOZ_ASSERT(IsValidDuration(duration));
2345 // Step 1.
2346 if ((TemporalUnit::Year <= unit && unit <= TemporalUnit::Day) ||
2347 (unit == TemporalUnit::Nanosecond && increment == Increment{1})) {
2348 *result = duration;
2349 return true;
2352 // The increment is limited for all smaller temporal units.
2353 MOZ_ASSERT(increment < MaximumTemporalDurationRoundingIncrement(unit));
2355 // Step 2.
2356 MOZ_ASSERT(precalculatedPlainDateTime);
2358 // Step 3.
2359 int32_t direction = NormalizedTimeDurationSign(duration.time);
2361 // Steps 4-5.
2362 Instant dayStart;
2363 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2364 duration.date, *precalculatedPlainDateTime,
2365 &dayStart)) {
2366 return false;
2368 MOZ_ASSERT(IsValidEpochInstant(dayStart));
2370 // Step 6.
2371 PlainDateTime dayStartDateTime;
2372 if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
2373 return false;
2376 // Step 7.
2377 Instant dayEnd;
2378 if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
2379 zonedRelativeTo.calendar(), direction, &dayEnd)) {
2380 return false;
2382 MOZ_ASSERT(IsValidEpochInstant(dayEnd));
2384 // Step 8.
2385 auto dayLengthNs =
2386 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd, dayStart);
2387 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs.to<InstantSpan>()));
2389 // Step 9.
2390 NormalizedTimeDuration oneDayLess;
2391 if (!SubtractNormalizedTimeDuration(cx, duration.time, dayLengthNs,
2392 &oneDayLess)) {
2393 return false;
2396 // Step 10.
2397 int32_t oneDayLessSign = NormalizedTimeDurationSign(oneDayLess);
2398 if ((direction > 0 && oneDayLessSign < 0) ||
2399 (direction < 0 && oneDayLessSign > 0)) {
2400 *result = duration;
2401 return true;
2404 // Step 11.
2405 Duration adjustedDateDuration;
2406 if (!AddDuration(cx, duration.date.toDuration(), {0, 0, 0, double(direction)},
2407 zonedRelativeTo, calendar, timeZone,
2408 precalculatedPlainDateTime, &adjustedDateDuration)) {
2409 return false;
2412 // Step 12.
2413 auto roundedTime = RoundDuration(oneDayLess, increment, unit, roundingMode);
2415 // Step 13.
2416 return CombineDateAndNormalizedTimeDuration(
2417 cx, adjustedDateDuration.toDateDuration(), roundedTime, result);
2421 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2422 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2423 * precalculatedPlainDateTime )
2425 bool js::temporal::AdjustRoundedDurationDays(
2426 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2427 TemporalUnit unit, TemporalRoundingMode roundingMode,
2428 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2429 Handle<TimeZoneRecord> timeZone,
2430 const PlainDateTime& precalculatedPlainDateTime,
2431 NormalizedDuration* result) {
2432 return ::AdjustRoundedDurationDays(
2433 cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
2434 timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
2437 static bool NumberToStringBuilder(JSContext* cx, double num,
2438 JSStringBuilder& sb) {
2439 MOZ_ASSERT(IsInteger(num));
2440 MOZ_ASSERT(num >= 0);
2441 MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2443 ToCStringBuf cbuf;
2444 size_t length;
2445 const char* numStr = NumberToCString(&cbuf, num, &length);
2447 return sb.append(numStr, length);
2450 static Duration AbsoluteDuration(const Duration& duration) {
2451 return {
2452 std::abs(duration.years), std::abs(duration.months),
2453 std::abs(duration.weeks), std::abs(duration.days),
2454 std::abs(duration.hours), std::abs(duration.minutes),
2455 std::abs(duration.seconds), std::abs(duration.milliseconds),
2456 std::abs(duration.microseconds), std::abs(duration.nanoseconds),
2461 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2463 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
2464 int32_t subSecondNanoseconds,
2465 Precision precision) {
2466 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
2467 MOZ_ASSERT(precision != Precision::Minute());
2469 // Steps 1-2.
2470 if (precision == Precision::Auto()) {
2471 // Step 1.a.
2472 if (subSecondNanoseconds == 0) {
2473 return true;
2476 // Step 3. (Reordered)
2477 if (!result.append('.')) {
2478 return false;
2481 // Steps 1.b-c.
2482 uint32_t k = 100'000'000;
2483 do {
2484 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2485 return false;
2487 subSecondNanoseconds %= k;
2488 k /= 10;
2489 } while (subSecondNanoseconds);
2490 } else {
2491 // Step 2.a.
2492 uint8_t p = precision.value();
2493 if (p == 0) {
2494 return true;
2497 // Step 3. (Reordered)
2498 if (!result.append('.')) {
2499 return false;
2502 // Steps 2.b-c.
2503 uint32_t k = 100'000'000;
2504 for (uint8_t i = 0; i < precision.value(); i++) {
2505 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2506 return false;
2508 subSecondNanoseconds %= k;
2509 k /= 10;
2513 return true;
2517 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2518 * normSeconds, precision )
2520 static JSString* TemporalDurationToString(JSContext* cx,
2521 const Duration& duration,
2522 Precision precision) {
2523 MOZ_ASSERT(IsValidDuration(duration));
2524 MOZ_ASSERT(precision != Precision::Minute());
2526 // Fast path for zero durations.
2527 if (duration == Duration{} &&
2528 (precision == Precision::Auto() || precision.value() == 0)) {
2529 return NewStringCopyZ<CanGC>(cx, "PT0S");
2532 // Convert to absolute values up front. This is okay to do, because when the
2533 // duration is valid, all components have the same sign.
2534 const auto& [years, months, weeks, days, hours, minutes, seconds,
2535 milliseconds, microseconds, nanoseconds] =
2536 AbsoluteDuration(duration);
2538 // Years to seconds parts are all safe integers for valid durations.
2539 MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2540 MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2541 MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2542 MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2543 MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2544 MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2545 MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2547 auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
2548 microseconds, nanoseconds);
2550 // Step 1.
2551 int32_t sign = DurationSign(duration);
2553 // Steps 2 and 7.
2554 JSStringBuilder result(cx);
2556 // Step 13. (Reordered)
2557 if (sign < 0) {
2558 if (!result.append('-')) {
2559 return nullptr;
2563 // Step 14. (Reordered)
2564 if (!result.append('P')) {
2565 return nullptr;
2568 // Step 3.
2569 if (years != 0) {
2570 if (!NumberToStringBuilder(cx, years, result)) {
2571 return nullptr;
2573 if (!result.append('Y')) {
2574 return nullptr;
2578 // Step 4.
2579 if (months != 0) {
2580 if (!NumberToStringBuilder(cx, months, result)) {
2581 return nullptr;
2583 if (!result.append('M')) {
2584 return nullptr;
2588 // Step 5.
2589 if (weeks != 0) {
2590 if (!NumberToStringBuilder(cx, weeks, result)) {
2591 return nullptr;
2593 if (!result.append('W')) {
2594 return nullptr;
2598 // Step 6.
2599 if (days != 0) {
2600 if (!NumberToStringBuilder(cx, days, result)) {
2601 return nullptr;
2603 if (!result.append('D')) {
2604 return nullptr;
2608 // Step 7. (Moved above)
2610 // Steps 10-11. (Reordered)
2611 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
2612 days == 0 && hours == 0 && minutes == 0;
2614 // Steps 8-9, 12, and 15.
2615 bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
2616 zeroMinutesAndHigher || precision != Precision::Auto();
2617 if (hours != 0 || minutes != 0 || hasSecondsPart) {
2618 // Step 15. (Reordered)
2619 if (!result.append('T')) {
2620 return nullptr;
2623 // Step 8.
2624 if (hours != 0) {
2625 if (!NumberToStringBuilder(cx, hours, result)) {
2626 return nullptr;
2628 if (!result.append('H')) {
2629 return nullptr;
2633 // Step 9.
2634 if (minutes != 0) {
2635 if (!NumberToStringBuilder(cx, minutes, result)) {
2636 return nullptr;
2638 if (!result.append('M')) {
2639 return nullptr;
2643 // Step 12.
2644 if (hasSecondsPart) {
2645 // Step 12.a.
2646 if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
2647 return nullptr;
2650 // Step 12.b.
2651 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
2652 precision)) {
2653 return nullptr;
2656 // Step 12.c.
2657 if (!result.append('S')) {
2658 return nullptr;
2663 // Steps 13-15. (Moved above)
2665 // Step 16.
2666 return result.finishString();
2670 * ToRelativeTemporalObject ( options )
2672 static bool ToRelativeTemporalObject(
2673 JSContext* cx, Handle<JSObject*> options,
2674 MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
2675 MutableHandle<ZonedDateTime> zonedRelativeTo,
2676 MutableHandle<TimeZoneRecord> timeZoneRecord) {
2677 // Step 1.
2678 Rooted<Value> value(cx);
2679 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
2680 return false;
2683 // Step 2.
2684 if (value.isUndefined()) {
2685 // FIXME: spec issue - switch return record fields for consistency.
2686 // FIXME: spec bug - [[TimeZoneRec]] field not created
2688 plainRelativeTo.set(nullptr);
2689 zonedRelativeTo.set(ZonedDateTime{});
2690 timeZoneRecord.set(TimeZoneRecord{});
2691 return true;
2694 // Step 3.
2695 auto offsetBehaviour = OffsetBehaviour::Option;
2697 // Step 4.
2698 auto matchBehaviour = MatchBehaviour::MatchExactly;
2700 // Steps 5-6.
2701 PlainDateTime dateTime;
2702 Rooted<CalendarValue> calendar(cx);
2703 Rooted<TimeZoneValue> timeZone(cx);
2704 int64_t offsetNs;
2705 if (value.isObject()) {
2706 Rooted<JSObject*> obj(cx, &value.toObject());
2708 // Step 5.a.
2709 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
2710 auto instant = ToInstant(zonedDateTime);
2711 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
2712 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
2714 if (!timeZone.wrap(cx)) {
2715 return false;
2717 if (!calendar.wrap(cx)) {
2718 return false;
2721 // Step 5.a.i.
2722 Rooted<TimeZoneRecord> timeZoneRec(cx);
2723 if (!CreateTimeZoneMethodsRecord(
2724 cx, timeZone,
2726 TimeZoneMethod::GetOffsetNanosecondsFor,
2727 TimeZoneMethod::GetPossibleInstantsFor,
2729 &timeZoneRec)) {
2730 return false;
2733 // Step 5.a.ii.
2734 plainRelativeTo.set(nullptr);
2735 zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
2736 timeZoneRecord.set(timeZoneRec);
2737 return true;
2740 // Step 5.b.
2741 if (obj->canUnwrapAs<PlainDateObject>()) {
2742 plainRelativeTo.set(obj);
2743 zonedRelativeTo.set(ZonedDateTime{});
2744 timeZoneRecord.set(TimeZoneRecord{});
2745 return true;
2748 // Step 5.c.
2749 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
2750 auto plainDateTime = ToPlainDate(dateTime);
2752 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
2753 if (!calendar.wrap(cx)) {
2754 return false;
2757 // Step 5.c.i.
2758 auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
2759 if (!plainDate) {
2760 return false;
2763 // Step 5.c.ii.
2764 plainRelativeTo.set(plainDate);
2765 zonedRelativeTo.set(ZonedDateTime{});
2766 timeZoneRecord.set(TimeZoneRecord{});
2767 return true;
2770 // Step 5.d.
2771 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
2772 return false;
2775 // Step 5.e.
2776 Rooted<CalendarRecord> calendarRec(cx);
2777 if (!CreateCalendarMethodsRecord(cx, calendar,
2779 CalendarMethod::DateFromFields,
2780 CalendarMethod::Fields,
2782 &calendarRec)) {
2783 return false;
2786 // Step 5.f.
2787 JS::RootedVector<PropertyKey> fieldNames(cx);
2788 if (!CalendarFields(cx, calendarRec,
2789 {CalendarField::Day, CalendarField::Month,
2790 CalendarField::MonthCode, CalendarField::Year},
2791 &fieldNames)) {
2792 return false;
2795 // Step 5.g.
2796 if (!AppendSorted(cx, fieldNames.get(),
2798 TemporalField::Hour,
2799 TemporalField::Microsecond,
2800 TemporalField::Millisecond,
2801 TemporalField::Minute,
2802 TemporalField::Nanosecond,
2803 TemporalField::Offset,
2804 TemporalField::Second,
2805 TemporalField::TimeZone,
2806 })) {
2807 return false;
2810 // Step 5.h.
2811 Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, obj, fieldNames));
2812 if (!fields) {
2813 return false;
2816 // Step 5.i.
2817 Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
2818 if (!dateOptions) {
2819 return false;
2822 // Step 5.j.
2823 Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
2824 if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
2825 return false;
2828 // Step 5.k.
2829 if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
2830 &dateTime)) {
2831 return false;
2834 // Step 5.l.
2835 Rooted<Value> offset(cx);
2836 if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
2837 return false;
2840 // Step 5.m.
2841 Rooted<Value> timeZoneValue(cx);
2842 if (!GetProperty(cx, fields, fields, cx->names().timeZone,
2843 &timeZoneValue)) {
2844 return false;
2847 // Step 5.n.
2848 if (!timeZoneValue.isUndefined()) {
2849 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
2850 return false;
2854 // Step 5.o.
2855 if (offset.isUndefined()) {
2856 offsetBehaviour = OffsetBehaviour::Wall;
2859 // Steps 8-9.
2860 if (timeZone) {
2861 if (offsetBehaviour == OffsetBehaviour::Option) {
2862 MOZ_ASSERT(!offset.isUndefined());
2863 MOZ_ASSERT(offset.isString());
2865 // Step 8.a.
2866 Rooted<JSString*> offsetString(cx, offset.toString());
2867 if (!offsetString) {
2868 return false;
2871 // Step 8.b.
2872 if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
2873 return false;
2875 } else {
2876 // Step 9.
2877 offsetNs = 0;
2880 } else {
2881 // Step 6.a.
2882 if (!value.isString()) {
2883 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
2884 nullptr, "not a string");
2885 return false;
2887 Rooted<JSString*> string(cx, value.toString());
2889 // Step 6.b.
2890 bool isUTC;
2891 bool hasOffset;
2892 int64_t timeZoneOffset;
2893 Rooted<ParsedTimeZone> timeZoneName(cx);
2894 Rooted<JSString*> calendarString(cx);
2895 if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
2896 &hasOffset, &timeZoneOffset,
2897 &timeZoneName, &calendarString)) {
2898 return false;
2901 // Step 6.c. (Not applicable in our implementation.)
2903 // Steps 6.e-f.
2904 if (timeZoneName) {
2905 // Step 6.f.i.
2906 if (!ToTemporalTimeZone(cx, timeZoneName, &timeZone)) {
2907 return false;
2910 // Steps 6.f.ii-iii.
2911 if (isUTC) {
2912 offsetBehaviour = OffsetBehaviour::Exact;
2913 } else if (!hasOffset) {
2914 offsetBehaviour = OffsetBehaviour::Wall;
2917 // Step 6.f.iv.
2918 matchBehaviour = MatchBehaviour::MatchMinutes;
2919 } else {
2920 MOZ_ASSERT(!timeZone);
2923 // Steps 6.g-j.
2924 if (calendarString) {
2925 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
2926 return false;
2928 } else {
2929 calendar.set(CalendarValue(cx->names().iso8601));
2932 // Steps 8-9.
2933 if (timeZone) {
2934 if (offsetBehaviour == OffsetBehaviour::Option) {
2935 MOZ_ASSERT(hasOffset);
2937 // Step 8.a.
2938 offsetNs = timeZoneOffset;
2939 } else {
2940 // Step 9.
2941 offsetNs = 0;
2946 // Step 7.
2947 if (!timeZone) {
2948 // Step 7.a.
2949 auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
2950 if (!plainDate) {
2951 return false;
2954 plainRelativeTo.set(plainDate);
2955 zonedRelativeTo.set(ZonedDateTime{});
2956 timeZoneRecord.set(TimeZoneRecord{});
2957 return true;
2960 // Steps 8-9. (Moved above)
2962 // Step 10.
2963 Rooted<TimeZoneRecord> timeZoneRec(cx);
2964 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2966 TimeZoneMethod::GetOffsetNanosecondsFor,
2967 TimeZoneMethod::GetPossibleInstantsFor,
2969 &timeZoneRec)) {
2970 return false;
2973 // Step 11.
2974 Instant epochNanoseconds;
2975 if (!InterpretISODateTimeOffset(
2976 cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
2977 TemporalDisambiguation::Compatible, TemporalOffset::Reject,
2978 matchBehaviour, &epochNanoseconds)) {
2979 return false;
2981 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
2983 // Step 12.
2984 plainRelativeTo.set(nullptr);
2985 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
2986 timeZoneRecord.set(timeZoneRec);
2987 return true;
2991 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
2992 * methods )
2994 static bool CreateCalendarMethodsRecordFromRelativeTo(
2995 JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2996 Handle<ZonedDateTime> zonedRelativeTo,
2997 mozilla::EnumSet<CalendarMethod> methods,
2998 MutableHandle<CalendarRecord> result) {
2999 // Step 1.
3000 if (zonedRelativeTo) {
3001 return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
3002 result);
3005 // Step 2.
3006 if (plainRelativeTo) {
3007 auto* unwrapped = plainRelativeTo.unwrap(cx);
3008 if (!unwrapped) {
3009 return false;
3012 Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
3013 if (!calendar.wrap(cx)) {
3014 return false;
3017 return CreateCalendarMethodsRecord(cx, calendar, methods, result);
3020 // Step 3.
3021 return true;
3024 struct RoundedNumber final {
3025 Int128 rounded;
3026 double total;
3030 * RoundNumberToIncrement ( x, increment, roundingMode )
3032 static RoundedNumber TruncateNumber(int64_t numerator, int64_t denominator) {
3033 // Computes the quotient and real number value of the rational number
3034 // |numerator / denominator|.
3036 // Int64 division truncates.
3037 int64_t quot = numerator / denominator;
3038 int64_t rem = numerator % denominator;
3040 // The total value is stored as a mathematical number in the draft proposal,
3041 // so we can't convert it to a double without loss of precision. We use two
3042 // different approaches to compute the total value based on the input range.
3044 // For example:
3046 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3047 // is |16.66668333...| and the best possible approximation is
3048 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3049 // numerator and denominator to doubles and then performing a double division.
3051 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3052 // can't use double division, because |14400000000000001| can't be represented
3053 // as an exact double value. The exact result is |4000.0000000000002777...|.
3055 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3056 // be computed through |q + r / denominator|.
3057 double total;
3058 if (::IsSafeInteger(numerator) && ::IsSafeInteger(denominator)) {
3059 total = double(numerator) / double(denominator);
3060 } else {
3061 total = double(quot) + double(rem) / double(denominator);
3063 return {Int128{quot}, total};
3067 * RoundNumberToIncrement ( x, increment, roundingMode )
3069 static RoundedNumber TruncateNumber(const Int128& numerator,
3070 const Int128& denominator) {
3071 MOZ_ASSERT(denominator > Int128{});
3072 MOZ_ASSERT(numerator > Int128{INT64_MAX} || denominator > Int128{INT64_MAX},
3073 "small values use the int64 overload");
3075 // Int128 division truncates.
3076 auto [quot, rem] = numerator.divrem(denominator);
3078 double total = double(quot) + double(rem) / double(denominator);
3079 return {quot, total};
3082 struct RoundedDuration final {
3083 NormalizedDuration duration;
3084 double total = 0;
3087 enum class ComputeRemainder : bool { No, Yes };
3090 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3092 static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
3093 const NormalizedTimeDuration& duration, const TemporalUnit unit,
3094 Increment increment, TemporalRoundingMode roundingMode) {
3095 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3096 MOZ_ASSERT(unit > TemporalUnit::Day);
3097 MOZ_ASSERT(increment <= MaximumTemporalDurationRoundingIncrement(unit));
3099 int64_t divisor = ToNanoseconds(unit) * increment.value();
3100 MOZ_ASSERT(divisor > 0);
3101 MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
3103 auto totalNanoseconds = duration.toTotalNanoseconds();
3104 auto rounded =
3105 RoundNumberToIncrement(totalNanoseconds, Int128{divisor}, roundingMode);
3106 return NormalizedTimeDuration::fromNanoseconds(rounded);
3110 * DivideNormalizedTimeDuration ( d, divisor )
3112 static double TotalNormalizedTimeDuration(
3113 const NormalizedTimeDuration& duration, const TemporalUnit unit) {
3114 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3115 MOZ_ASSERT(unit > TemporalUnit::Day);
3117 // Compute real number value of the rational number |numerator / denominator|.
3119 auto numerator = duration.toTotalNanoseconds();
3120 auto denominator = ToNanoseconds(unit);
3121 MOZ_ASSERT(::IsSafeInteger(denominator));
3123 // The total value is stored as a mathematical number in the draft proposal,
3124 // so we can't convert it to a double without loss of precision. We use two
3125 // different approaches to compute the total value based on the input range.
3127 // For example:
3129 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3130 // is |16.66668333...| and the best possible approximation is
3131 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3132 // numerator and denominator to doubles and then performing a double division.
3134 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3135 // can't use double division, because |14400000000000001| can't be represented
3136 // as an exact double value. The exact result is |4000.0000000000002777...|.
3138 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3139 // be computed through |q + r / denominator|.
3140 if (::IsSafeInteger(numerator)) {
3141 return double(numerator) / double(denominator);
3144 auto [q, r] = numerator.divrem(Int128{denominator});
3145 return double(q) + double(r) / double(denominator);
3149 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3150 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3151 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3153 NormalizedTimeDuration js::temporal::RoundDuration(
3154 const NormalizedTimeDuration& duration, Increment increment,
3155 TemporalUnit unit, TemporalRoundingMode roundingMode) {
3156 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3157 MOZ_ASSERT(unit > TemporalUnit::Day);
3159 // Steps 1-13. (Not applicable)
3161 // Steps 14-19.
3162 auto rounded = RoundNormalizedTimeDurationToIncrement(
3163 duration, unit, increment, roundingMode);
3164 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded));
3166 // Step 20.
3167 return rounded;
3170 static int64_t TruncateDays(const NormalizedTimeAndDays& timeAndDays,
3171 int64_t days, int32_t daysToAdd) {
3172 #ifdef DEBUG
3173 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3174 static constexpr int64_t durationDays = (int64_t(1) << 53) / (24 * 60 * 60);
3176 // Numbers of days between nsMinInstant and nsMaxInstant.
3177 static constexpr int32_t epochDays = 200'000'000;
3178 #endif
3180 MOZ_ASSERT(std::abs(days) <= durationDays);
3181 MOZ_ASSERT(std::abs(timeAndDays.days) <= durationDays);
3182 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays);
3184 static_assert(durationDays + durationDays + epochDays <= INT64_MAX,
3185 "addition can't overflow");
3187 int64_t totalDays = days + timeAndDays.days + daysToAdd;
3189 int64_t truncatedDays = totalDays;
3190 if (timeAndDays.time > 0) {
3191 // Round toward positive infinity when the integer days are negative and
3192 // the fractional part is positive.
3193 if (truncatedDays < 0) {
3194 truncatedDays += 1;
3196 } else if (timeAndDays.time < 0) {
3197 // Round toward negative infinity when the integer days are positive and
3198 // the fractional part is negative.
3199 if (truncatedDays > 0) {
3200 truncatedDays -= 1;
3204 return truncatedDays;
3207 static bool DaysIsNegative(int64_t days,
3208 const NormalizedTimeAndDays& timeAndDays,
3209 int32_t daysToAdd) {
3210 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3211 static constexpr int64_t durationDays = (int64_t(1) << 53) / (24 * 60 * 60);
3213 // Numbers of days between nsMinInstant and nsMaxInstant.
3214 static constexpr int32_t epochDays = 200'000'000;
3216 MOZ_ASSERT(std::abs(days) <= durationDays);
3217 MOZ_ASSERT(std::abs(timeAndDays.days) <= durationDays);
3218 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3220 static_assert(durationDays + durationDays + epochDays * 2 <= INT64_MAX,
3221 "addition can't overflow");
3223 int64_t totalDays = days + timeAndDays.days + daysToAdd;
3224 return totalDays < 0 || (totalDays == 0 && timeAndDays.time < 0);
3227 static RoundedNumber RoundNumberToIncrement(
3228 int64_t durationAmount, int64_t amountPassed, int64_t durationDays,
3229 int32_t daysToAdd, const NormalizedTimeAndDays& timeAndDays,
3230 int32_t oneUnitDays, Increment increment, TemporalRoundingMode roundingMode,
3231 ComputeRemainder computeRemainder) {
3232 #ifdef DEBUG
3233 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3234 static constexpr int64_t maxDurationDays =
3235 (int64_t(1) << 53) / (24 * 60 * 60);
3237 // Numbers of days between nsMinInstant and nsMaxInstant.
3238 static constexpr int32_t epochDays = 200'000'000;
3239 #endif
3241 MOZ_ASSERT(std::abs(durationAmount) < (int64_t(1) << 32));
3242 MOZ_ASSERT(std::abs(amountPassed) < (int64_t(1) << 32));
3243 MOZ_ASSERT(std::abs(durationDays) <= maxDurationDays);
3244 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3245 MOZ_ASSERT(timeAndDays.dayLength > 0);
3246 MOZ_ASSERT(timeAndDays.dayLength < (int64_t(1) << 53));
3247 MOZ_ASSERT(std::abs(timeAndDays.time) < timeAndDays.dayLength);
3248 MOZ_ASSERT(std::abs(timeAndDays.days) <= maxDurationDays);
3249 MOZ_ASSERT(oneUnitDays != 0);
3250 MOZ_ASSERT(std::abs(oneUnitDays) <= epochDays);
3251 MOZ_ASSERT(increment <= Increment::max());
3253 // clang-format off
3255 // Change the representation of |fractionalWeeks| from a real number to a
3256 // rational number, because we don't support arbitrary precision real
3257 // numbers.
3259 // |fractionalWeeks| is defined as:
3261 // fractionalWeeks
3262 // = weeks + days' / abs(oneWeekDays)
3264 // where days' = days + nanoseconds / dayLength.
3266 // The fractional part |nanoseconds / dayLength| is from step 4.
3268 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3270 // fractionalWeeks
3271 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3272 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3273 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3275 // clang-format on
3277 do {
3278 auto dayLength = mozilla::CheckedInt64(timeAndDays.dayLength);
3280 auto denominator = dayLength * std::abs(oneUnitDays);
3281 if (!denominator.isValid()) {
3282 break;
3285 auto totalDays = mozilla::CheckedInt64(durationDays);
3286 totalDays += timeAndDays.days;
3287 totalDays += daysToAdd;
3288 MOZ_ASSERT(totalDays.isValid());
3290 auto totalAmount = mozilla::CheckedInt64(durationAmount) + amountPassed;
3291 MOZ_ASSERT(totalAmount.isValid());
3293 auto amountNanos = denominator * totalAmount;
3294 if (!amountNanos.isValid()) {
3295 break;
3298 auto totalNanoseconds = dayLength * totalDays;
3299 totalNanoseconds += timeAndDays.time;
3300 totalNanoseconds += amountNanos;
3301 if (!totalNanoseconds.isValid()) {
3302 break;
3305 if (computeRemainder == ComputeRemainder::Yes) {
3306 return TruncateNumber(totalNanoseconds.value(), denominator.value());
3309 auto rounded = RoundNumberToIncrement(
3310 totalNanoseconds.value(), denominator.value(), increment, roundingMode);
3311 constexpr double total = 0;
3312 return {rounded, total};
3313 } while (false);
3315 // Use int128 when values are too large for int64. Additionally assert all
3316 // values fit into int128.
3318 // `dayLength` < 2**53
3319 auto dayLength = Int128{timeAndDays.dayLength};
3320 MOZ_ASSERT(dayLength < Int128{1} << 53);
3322 // `abs(oneUnitDays)` < 200'000'000, log2(200'000'000) = ~27.57.
3323 auto denominator = dayLength * Int128{std::abs(oneUnitDays)};
3324 MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
3326 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3328 // `abs(maxDurationDays)` ≤ 2**(53 - 16).
3329 // `abs(timeAndDays.days)` ≤ 2**(53 - 16).
3330 // `abs(daysToAdd)` ≤ 2**29.
3332 // 2**(53 - 16) + 2**(53 - 16) + 2**29
3333 // = 2**37 + 2**37 + 2**29
3334 // = 2**38 + 2**29
3335 // ≤ 2**39
3336 auto totalDays = Int128{durationDays};
3337 totalDays += Int128{timeAndDays.days};
3338 totalDays += Int128{daysToAdd};
3339 MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
3341 // `abs(durationAmount)` ≤ 2**32
3342 // `abs(amountPassed)` ≤ 2**32
3343 auto totalAmount = Int128{durationAmount} + Int128{amountPassed};
3344 MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
3346 // `denominator` < 2**(53 + 28)
3347 // `abs(totalAmount)` <= 2**33
3349 // `denominator * totalAmount`
3350 // ≤ 2**(53 + 28) * 2**33
3351 // = 2**(53 + 28 + 33)
3352 // = 2**114
3353 auto amountNanos = denominator * totalAmount;
3354 MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
3356 // `dayLength` < 2**53
3357 // `totalDays` ≤ 2**39
3358 // `timeAndDays.time` < `dayLength` < 2**53
3359 // `amountNanos` ≤ 2**114
3361 // `dayLength * totalDays`
3362 // ≤ 2**(53 + 39) = 2**92
3364 // `dayLength * totalDays + timeAndDays.time`
3365 // ≤ 2**93
3367 // `dayLength * totalDays + timeAndDays.time + amountNanos`
3368 // ≤ 2**115
3369 auto totalNanoseconds = dayLength * totalDays;
3370 totalNanoseconds += Int128{timeAndDays.time};
3371 totalNanoseconds += amountNanos;
3372 MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
3374 if (computeRemainder == ComputeRemainder::Yes) {
3375 return TruncateNumber(totalNanoseconds, denominator);
3378 auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
3379 increment, roundingMode);
3380 constexpr double total = 0;
3381 return {rounded, total};
3384 static RoundedNumber RoundNumberToIncrement(
3385 double durationDays, const NormalizedTimeAndDays& timeAndDays,
3386 Increment increment, TemporalRoundingMode roundingMode,
3387 ComputeRemainder computeRemainder) {
3388 constexpr int64_t daysAmount = 0;
3389 constexpr int64_t daysPassed = 0;
3390 constexpr int32_t oneDayDays = 1;
3391 constexpr int32_t daysToAdd = 0;
3393 return RoundNumberToIncrement(daysAmount, daysPassed, durationDays, daysToAdd,
3394 timeAndDays, oneDayDays, increment,
3395 roundingMode, computeRemainder);
3398 static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
3399 const NormalizedTimeAndDays& timeAndDays,
3400 Increment increment,
3401 TemporalRoundingMode roundingMode,
3402 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3403 Handle<CalendarRecord> calendar,
3404 ComputeRemainder computeRemainder,
3405 RoundedDuration* result) {
3406 // Numbers of days between nsMinInstant and nsMaxInstant.
3407 static constexpr int32_t epochDays = 200'000'000;
3409 auto [years, months, weeks, days] = duration.date;
3411 // Step 10.a.
3412 Duration yearsDuration = {double(years)};
3414 // Step 10.b.
3415 auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
3416 if (!yearsLater) {
3417 return false;
3419 auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
3421 // Step 10.f. (Reordered)
3422 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
3424 // Step 10.c.
3425 Duration yearsMonthsWeeks = {double(years), double(months), double(weeks)};
3427 // Step 10.d.
3428 PlainDate yearsMonthsWeeksLater;
3429 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3430 &yearsMonthsWeeksLater)) {
3431 return false;
3434 // Step 10.e.
3435 int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
3436 MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
3438 // Step 10.f. (Moved up)
3440 // Step 10.g.
3441 // Our implementation keeps |days| and |monthsWeeksInDays| separate.
3443 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3444 // https://github.com/tc39/proposal-temporal/issues/2540
3446 // Step 10.h.
3447 int64_t truncatedDays = TruncateDays(timeAndDays, days, monthsWeeksInDays);
3449 PlainDate isoResult;
3450 if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, truncatedDays},
3451 TemporalOverflow::Constrain, &isoResult)) {
3452 return false;
3455 // Step 10.i.
3456 Rooted<PlainDateObject*> wholeDaysLater(
3457 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3458 if (!wholeDaysLater) {
3459 return false;
3462 // Steps 10.j-l.
3463 Duration timePassed;
3464 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3465 TemporalUnit::Year, &timePassed)) {
3466 return false;
3469 // Step 10.m.
3470 int64_t yearsPassed = int64_t(timePassed.years);
3472 // Step 10.n.
3473 // Our implementation keeps |years| and |yearsPassed| separate.
3475 // Step 10.o.
3476 Duration yearsPassedDuration = {double(yearsPassed)};
3478 // Steps 10.p-r.
3479 int32_t daysPassed;
3480 if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
3481 &newRelativeTo, &daysPassed)) {
3482 return false;
3484 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3486 // Step 10.s.
3488 // Our implementation keeps |days| and |daysPassed| separate.
3489 int32_t daysToAdd = monthsWeeksInDays - daysPassed;
3490 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3492 // Steps 10.t.
3493 double sign = DaysIsNegative(days, timeAndDays, daysToAdd) ? -1 : 1;
3495 // Step 10.u.
3496 Duration oneYear = {sign};
3498 // Steps 10.v-w.
3499 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3500 int32_t oneYearDays;
3501 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
3502 &moveResultIgnored, &oneYearDays)) {
3503 return false;
3506 // Step 10.x.
3507 if (oneYearDays == 0) {
3508 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3509 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3510 return false;
3513 // Steps 10.y-aa.
3514 auto [numYears, total] = RoundNumberToIncrement(
3515 years, yearsPassed, days, daysToAdd, timeAndDays, oneYearDays, increment,
3516 roundingMode, computeRemainder);
3518 // Step 10.ab.
3519 int64_t numMonths = 0;
3520 int64_t numWeeks = 0;
3522 // Step 10.ac.
3523 constexpr auto time = NormalizedTimeDuration{};
3525 // Step 20.
3526 if (numYears.abs() >= (Uint128{1} << 32)) {
3527 return ThrowInvalidDurationPart(cx, double(numYears), "years",
3528 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3531 auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
3532 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3533 return false;
3536 *result = {{resultDuration, time}, total};
3537 return true;
3540 static bool RoundDurationMonth(JSContext* cx,
3541 const NormalizedDuration& duration,
3542 const NormalizedTimeAndDays& timeAndDays,
3543 Increment increment,
3544 TemporalRoundingMode roundingMode,
3545 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3546 Handle<CalendarRecord> calendar,
3547 ComputeRemainder computeRemainder,
3548 RoundedDuration* result) {
3549 // Numbers of days between nsMinInstant and nsMaxInstant.
3550 static constexpr int32_t epochDays = 200'000'000;
3552 auto [years, months, weeks, days] = duration.date;
3554 // Step 11.a.
3555 Duration yearsMonths = {double(years), double(months)};
3557 // Step 11.b.
3558 auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
3559 if (!yearsMonthsLater) {
3560 return false;
3562 auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
3564 // Step 11.f. (Reordered)
3565 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
3567 // Step 11.c.
3568 Duration yearsMonthsWeeks = {double(years), double(months), double(weeks)};
3570 // Step 11.d.
3571 PlainDate yearsMonthsWeeksLater;
3572 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3573 &yearsMonthsWeeksLater)) {
3574 return false;
3577 // Step 11.e.
3578 int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
3579 MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
3581 // Step 11.f. (Moved up)
3583 // Step 11.g.
3584 // Our implementation keeps |days| and |weeksInDays| separate.
3586 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3587 // https://github.com/tc39/proposal-temporal/issues/2540
3589 // Step 11.h.
3590 int64_t truncatedDays = TruncateDays(timeAndDays, days, weeksInDays);
3592 PlainDate isoResult;
3593 if (!AddISODate(cx, yearsMonthsLaterDate, {0, 0, 0, truncatedDays},
3594 TemporalOverflow::Constrain, &isoResult)) {
3595 return false;
3598 // Step 11.i.
3599 Rooted<PlainDateObject*> wholeDaysLater(
3600 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3601 if (!wholeDaysLater) {
3602 return false;
3605 // Steps 11.j-l.
3606 Duration timePassed;
3607 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3608 TemporalUnit::Month, &timePassed)) {
3609 return false;
3612 // Step 11.m.
3613 int64_t monthsPassed = int64_t(timePassed.months);
3615 // Step 11.n.
3616 // Our implementation keeps |months| and |monthsPassed| separate.
3618 // Step 11.o.
3619 Duration monthsPassedDuration = {0, double(monthsPassed)};
3621 // Steps 11.p-r.
3622 int32_t daysPassed;
3623 if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
3624 &newRelativeTo, &daysPassed)) {
3625 return false;
3627 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3629 // Step 11.s.
3631 // Our implementation keeps |days| and |daysPassed| separate.
3632 int32_t daysToAdd = weeksInDays - daysPassed;
3633 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3635 // Steps 11.t.
3636 double sign = DaysIsNegative(days, timeAndDays, daysToAdd) ? -1 : 1;
3638 // Step 11.u.
3639 Duration oneMonth = {0, sign};
3641 // Steps 11.v-w.
3642 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3643 int32_t oneMonthDays;
3644 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
3645 &moveResultIgnored, &oneMonthDays)) {
3646 return false;
3649 // Step 11.x.
3650 if (oneMonthDays == 0) {
3651 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3652 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3653 return false;
3656 // Steps 11.y-aa.
3657 auto [numMonths, total] = RoundNumberToIncrement(
3658 months, monthsPassed, days, daysToAdd, timeAndDays, oneMonthDays,
3659 increment, roundingMode, computeRemainder);
3661 // Step 11.ab.
3662 int64_t numWeeks = 0;
3664 // Step 11.ac.
3665 constexpr auto time = NormalizedTimeDuration{};
3667 // Step 21.
3668 if (numMonths.abs() >= (Uint128{1} << 32)) {
3669 return ThrowInvalidDurationPart(cx, double(numMonths), "months",
3670 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3673 auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
3674 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3675 return false;
3678 *result = {{resultDuration, time}, total};
3679 return true;
3682 static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
3683 const NormalizedTimeAndDays& timeAndDays,
3684 Increment increment,
3685 TemporalRoundingMode roundingMode,
3686 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3687 Handle<CalendarRecord> calendar,
3688 ComputeRemainder computeRemainder,
3689 RoundedDuration* result) {
3690 // Numbers of days between nsMinInstant and nsMaxInstant.
3691 static constexpr int32_t epochDays = 200'000'000;
3693 auto [years, months, weeks, days] = duration.date;
3695 auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
3696 if (!unwrappedRelativeTo) {
3697 return false;
3699 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
3701 // Step 12.a
3702 int64_t truncatedDays = TruncateDays(timeAndDays, days, 0);
3704 PlainDate isoResult;
3705 if (!AddISODate(cx, relativeToDate, {0, 0, 0, truncatedDays},
3706 TemporalOverflow::Constrain, &isoResult)) {
3707 return false;
3710 // Step 12.b.
3711 Rooted<PlainDateObject*> wholeDaysLater(
3712 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3713 if (!wholeDaysLater) {
3714 return false;
3717 // Steps 12.c-e.
3718 Duration timePassed;
3719 if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
3720 TemporalUnit::Week, &timePassed)) {
3721 return false;
3724 // Step 12.f.
3725 int64_t weeksPassed = int64_t(timePassed.weeks);
3727 // Step 12.g.
3728 // Our implementation keeps |weeks| and |weeksPassed| separate.
3730 // Step 12.h.
3731 Duration weeksPassedDuration = {0, 0, double(weeksPassed)};
3733 // Steps 12.i-k.
3734 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
3735 int32_t daysPassed;
3736 if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
3737 &newRelativeTo, &daysPassed)) {
3738 return false;
3740 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3742 // Step 12.l.
3744 // Our implementation keeps |days| and |daysPassed| separate.
3745 int32_t daysToAdd = -daysPassed;
3746 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays);
3748 // Steps 12.m.
3749 double sign = DaysIsNegative(days, timeAndDays, daysToAdd) ? -1 : 1;
3751 // Step 12.n.
3752 Duration oneWeek = {0, 0, sign};
3754 // Steps 12.o-p.
3755 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3756 int32_t oneWeekDays;
3757 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
3758 &moveResultIgnored, &oneWeekDays)) {
3759 return false;
3762 // Step 12.q.
3763 if (oneWeekDays == 0) {
3764 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3765 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3766 return false;
3769 // Steps 12.r-t.
3770 auto [numWeeks, total] = RoundNumberToIncrement(
3771 weeks, weeksPassed, days, daysToAdd, timeAndDays, oneWeekDays, increment,
3772 roundingMode, computeRemainder);
3774 // Step 12.u.
3775 constexpr auto time = NormalizedTimeDuration{};
3777 // Step 20.
3778 if (numWeeks.abs() >= (Uint128{1} << 32)) {
3779 return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
3780 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3783 auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
3784 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3785 return false;
3788 *result = {{resultDuration, time}, total};
3789 return true;
3792 static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
3793 const NormalizedTimeAndDays& timeAndDays,
3794 Increment increment,
3795 TemporalRoundingMode roundingMode,
3796 ComputeRemainder computeRemainder,
3797 RoundedDuration* result) {
3798 auto [years, months, weeks, days] = duration.date;
3800 // Steps 13.a-b.
3801 auto [numDays, total] = RoundNumberToIncrement(
3802 days, timeAndDays, increment, roundingMode, computeRemainder);
3804 MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
3805 "rounded days fits in int64");
3807 // Step 13.c.
3808 constexpr auto time = NormalizedTimeDuration{};
3810 // Step 20.
3811 auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
3812 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3813 return false;
3816 *result = {{resultDuration, time}, total};
3817 return true;
3821 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3822 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3823 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3825 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
3826 Increment increment, TemporalUnit unit,
3827 TemporalRoundingMode roundingMode,
3828 ComputeRemainder computeRemainder,
3829 RoundedDuration* result) {
3830 // The remainder is only needed when called from |Duration_total|. And `total`
3831 // always passes |increment=1| and |roundingMode=trunc|.
3832 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3833 increment == Increment{1});
3834 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3835 roundingMode == TemporalRoundingMode::Trunc);
3837 // Steps 1-5. (Not applicable.)
3839 // Step 6.
3840 if (unit <= TemporalUnit::Week) {
3841 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3842 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3843 "relativeTo");
3844 return false;
3847 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3848 // because in that case this operation is a no-op. This case happens for
3849 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3850 // options object.
3852 // But maybe this can be even more efficiently handled in the callers. For
3853 // example when Temporal.PlainTime.prototype.{since,until} is called without
3854 // an options object, we can not only skip the RoundDuration call, but also
3855 // the following BalanceTimeDuration call.
3857 // Step 7. (Moved below.)
3859 // Steps 8-9. (Not applicable.)
3861 // Steps 10-12. (Not applicable.)
3863 // Step 13.
3864 if (unit == TemporalUnit::Day) {
3865 // Step 7.
3866 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3868 return RoundDurationDay(cx, duration, timeAndDays, increment, roundingMode,
3869 computeRemainder, result);
3872 MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
3874 // Steps 14-19.
3875 auto time = duration.time;
3876 double total = 0;
3877 if (computeRemainder == ComputeRemainder::No) {
3878 time = RoundNormalizedTimeDurationToIncrement(time, unit, increment,
3879 roundingMode);
3880 } else {
3881 MOZ_ASSERT(increment == Increment{1});
3882 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
3884 total = TotalNormalizedTimeDuration(duration.time, unit);
3886 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
3888 // Step 20.
3889 MOZ_ASSERT(IsValidDuration(duration.date.toDuration()));
3890 *result = {{duration.date, time}, total};
3891 return true;
3895 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3896 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3897 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3899 static bool RoundDuration(
3900 JSContext* cx, const NormalizedDuration& duration, Increment increment,
3901 TemporalUnit unit, TemporalRoundingMode roundingMode,
3902 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3903 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
3904 Handle<TimeZoneRecord> timeZone,
3905 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
3906 ComputeRemainder computeRemainder, RoundedDuration* result) {
3907 // Note: |duration.days| can have a different sign than the other date
3908 // components. The date and time components can have different signs, too.
3909 MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
3910 double(duration.date.months),
3911 double(duration.date.weeks)}));
3912 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
3914 MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
3915 "Use RoundDuration without relativeTo when plainRelativeTo and "
3916 "zonedRelativeTo are both undefined");
3918 // The remainder is only needed when called from |Duration_total|. And `total`
3919 // always passes |increment=1| and |roundingMode=trunc|.
3920 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3921 increment == Increment{1});
3922 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3923 roundingMode == TemporalRoundingMode::Trunc);
3925 // Steps 1-5. (Not applicable in our implementation.)
3927 // Step 6.a. (Not applicable in our implementation.)
3928 MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
3930 // Step 6.b.
3931 MOZ_ASSERT_IF(
3932 unit <= TemporalUnit::Week,
3933 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3935 // Step 6.c.
3936 MOZ_ASSERT_IF(
3937 unit <= TemporalUnit::Week,
3938 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3940 switch (unit) {
3941 case TemporalUnit::Year:
3942 case TemporalUnit::Month:
3943 case TemporalUnit::Week:
3944 break;
3945 case TemporalUnit::Day:
3946 // We can't take the faster code path when |zonedRelativeTo| is present.
3947 if (zonedRelativeTo) {
3948 break;
3950 [[fallthrough]];
3951 case TemporalUnit::Hour:
3952 case TemporalUnit::Minute:
3953 case TemporalUnit::Second:
3954 case TemporalUnit::Millisecond:
3955 case TemporalUnit::Microsecond:
3956 case TemporalUnit::Nanosecond:
3957 // Steps 7-9 and 13-21.
3958 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
3959 computeRemainder, result);
3960 case TemporalUnit::Auto:
3961 MOZ_CRASH("Unexpected temporal unit");
3964 // Step 7.
3965 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
3967 // Steps 7.a-c.
3968 NormalizedTimeAndDays timeAndDays;
3969 if (zonedRelativeTo) {
3970 // Step 7.a.i.
3971 Rooted<ZonedDateTime> intermediate(cx);
3972 if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
3973 duration.date, precalculatedPlainDateTime,
3974 &intermediate)) {
3975 return false;
3978 // Steps 7.a.ii.
3979 if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
3980 &timeAndDays)) {
3981 return false;
3984 // Step 7.a.iii. (Not applicable in our implementation.)
3985 } else {
3986 // Step 7.b. (Partial)
3987 timeAndDays = ::NormalizedTimeDurationToDays(duration.time);
3990 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3991 // less than |timeAndDays.dayLength|.
3992 MOZ_ASSERT(std::abs(timeAndDays.time) < timeAndDays.dayLength);
3994 // Step 7.c. (Moved below)
3996 // Step 8. (Not applicable)
3998 // Step 9.
3999 // FIXME: spec issue - `total` doesn't need be initialised.
4001 // Steps 10-20.
4002 switch (unit) {
4003 // Steps 10 and 20.
4004 case TemporalUnit::Year:
4005 return RoundDurationYear(cx, duration, timeAndDays, increment,
4006 roundingMode, plainRelativeTo, calendar,
4007 computeRemainder, result);
4009 // Steps 11 and 20.
4010 case TemporalUnit::Month:
4011 return RoundDurationMonth(cx, duration, timeAndDays, increment,
4012 roundingMode, plainRelativeTo, calendar,
4013 computeRemainder, result);
4015 // Steps 12 and 20.
4016 case TemporalUnit::Week:
4017 return RoundDurationWeek(cx, duration, timeAndDays, increment,
4018 roundingMode, plainRelativeTo, calendar,
4019 computeRemainder, result);
4021 // Steps 13 and 20.
4022 case TemporalUnit::Day:
4023 return RoundDurationDay(cx, duration, timeAndDays, increment,
4024 roundingMode, computeRemainder, result);
4026 // Steps 14-19. (Handled elsewhere)
4027 case TemporalUnit::Auto:
4028 case TemporalUnit::Hour:
4029 case TemporalUnit::Minute:
4030 case TemporalUnit::Second:
4031 case TemporalUnit::Millisecond:
4032 case TemporalUnit::Microsecond:
4033 case TemporalUnit::Nanosecond:
4034 break;
4037 MOZ_CRASH("Unexpected temporal unit");
4041 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4042 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4043 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4045 static bool RoundDuration(
4046 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4047 TemporalUnit unit, TemporalRoundingMode roundingMode,
4048 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4049 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4050 Handle<TimeZoneRecord> timeZone,
4051 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4052 double* result) {
4053 // Only called from |Duration_total|, which always passes |increment=1| and
4054 // |roundingMode=trunc|.
4055 MOZ_ASSERT(increment == Increment{1});
4056 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4058 RoundedDuration rounded;
4059 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4060 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4061 precalculatedPlainDateTime, ComputeRemainder::Yes,
4062 &rounded)) {
4063 return false;
4066 *result = rounded.total;
4067 return true;
4071 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4072 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4073 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4075 static bool RoundDuration(
4076 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4077 TemporalUnit unit, TemporalRoundingMode roundingMode,
4078 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4079 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4080 Handle<TimeZoneRecord> timeZone,
4081 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4082 NormalizedDuration* result) {
4083 RoundedDuration rounded;
4084 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4085 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4086 precalculatedPlainDateTime, ComputeRemainder::No,
4087 &rounded)) {
4088 return false;
4091 *result = rounded.duration;
4092 return true;
4096 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4097 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4098 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4100 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4101 Increment increment, TemporalUnit unit,
4102 TemporalRoundingMode roundingMode, double* result) {
4103 MOZ_ASSERT(IsValidDuration(duration));
4105 // Only called from |Duration_total|, which always passes |increment=1| and
4106 // |roundingMode=trunc|.
4107 MOZ_ASSERT(increment == Increment{1});
4108 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4110 RoundedDuration rounded;
4111 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4112 ComputeRemainder::Yes, &rounded)) {
4113 return false;
4116 *result = rounded.total;
4117 return true;
4121 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4122 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4123 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4125 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4126 Increment increment, TemporalUnit unit,
4127 TemporalRoundingMode roundingMode,
4128 NormalizedDuration* result) {
4129 MOZ_ASSERT(IsValidDuration(duration));
4131 RoundedDuration rounded;
4132 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4133 ComputeRemainder::No, &rounded)) {
4134 return false;
4137 *result = rounded.duration;
4138 return true;
4142 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4143 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4144 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4146 bool js::temporal::RoundDuration(
4147 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4148 TemporalUnit unit, TemporalRoundingMode roundingMode,
4149 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4150 Handle<CalendarRecord> calendar, NormalizedDuration* result) {
4151 MOZ_ASSERT(IsValidDuration(duration));
4153 Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
4154 Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
4155 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4156 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4157 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4158 precalculatedPlainDateTime, result);
4162 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4163 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4164 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4166 bool js::temporal::RoundDuration(
4167 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4168 TemporalUnit unit, TemporalRoundingMode roundingMode,
4169 Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
4170 Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
4171 const PlainDateTime& precalculatedPlainDateTime,
4172 NormalizedDuration* result) {
4173 MOZ_ASSERT(IsValidDuration(duration));
4175 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4176 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4177 mozilla::SomeRef(precalculatedPlainDateTime), result);
4180 enum class DurationOperation { Add, Subtract };
4183 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4184 * options )
4186 static bool AddDurationToOrSubtractDurationFromDuration(
4187 JSContext* cx, DurationOperation operation, const CallArgs& args) {
4188 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4189 auto duration = ToDuration(durationObj);
4191 // Step 1. (Not applicable in our implementation.)
4193 // Step 2.
4194 Duration other;
4195 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
4196 return false;
4199 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4200 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4201 Rooted<TimeZoneRecord> timeZone(cx);
4202 if (args.hasDefined(1)) {
4203 const char* name = operation == DurationOperation::Add ? "add" : "subtract";
4205 // Step 3.
4206 Rooted<JSObject*> options(cx,
4207 RequireObjectArg(cx, "options", name, args[1]));
4208 if (!options) {
4209 return false;
4212 // Steps 4-7.
4213 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4214 &zonedRelativeTo, &timeZone)) {
4215 return false;
4217 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4218 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4221 // Step 8.
4222 Rooted<CalendarRecord> calendar(cx);
4223 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4224 zonedRelativeTo,
4226 CalendarMethod::DateAdd,
4227 CalendarMethod::DateUntil,
4229 &calendar)) {
4230 return false;
4233 // Step 9.
4234 if (operation == DurationOperation::Subtract) {
4235 other = other.negate();
4238 Duration result;
4239 if (plainRelativeTo) {
4240 if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
4241 return false;
4243 } else if (zonedRelativeTo) {
4244 if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
4245 &result)) {
4246 return false;
4248 } else {
4249 if (!AddDuration(cx, duration, other, &result)) {
4250 return false;
4254 // Step 10.
4255 auto* obj = CreateTemporalDuration(cx, result);
4256 if (!obj) {
4257 return false;
4260 args.rval().setObject(*obj);
4261 return true;
4265 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4266 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4267 * ] ] ] ] ] ] )
4269 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
4270 CallArgs args = CallArgsFromVp(argc, vp);
4272 // Step 1.
4273 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
4274 return false;
4277 // Step 2.
4278 double years = 0;
4279 if (args.hasDefined(0) &&
4280 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
4281 return false;
4284 // Step 3.
4285 double months = 0;
4286 if (args.hasDefined(1) &&
4287 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
4288 return false;
4291 // Step 4.
4292 double weeks = 0;
4293 if (args.hasDefined(2) &&
4294 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
4295 return false;
4298 // Step 5.
4299 double days = 0;
4300 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
4301 return false;
4304 // Step 6.
4305 double hours = 0;
4306 if (args.hasDefined(4) &&
4307 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
4308 return false;
4311 // Step 7.
4312 double minutes = 0;
4313 if (args.hasDefined(5) &&
4314 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
4315 return false;
4318 // Step 8.
4319 double seconds = 0;
4320 if (args.hasDefined(6) &&
4321 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
4322 return false;
4325 // Step 9.
4326 double milliseconds = 0;
4327 if (args.hasDefined(7) &&
4328 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
4329 return false;
4332 // Step 10.
4333 double microseconds = 0;
4334 if (args.hasDefined(8) &&
4335 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
4336 return false;
4339 // Step 11.
4340 double nanoseconds = 0;
4341 if (args.hasDefined(9) &&
4342 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
4343 return false;
4346 // Step 12.
4347 auto* duration = CreateTemporalDuration(
4348 cx, args,
4349 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
4350 microseconds, nanoseconds});
4351 if (!duration) {
4352 return false;
4355 args.rval().setObject(*duration);
4356 return true;
4360 * Temporal.Duration.from ( item )
4362 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
4363 CallArgs args = CallArgsFromVp(argc, vp);
4365 Handle<Value> item = args.get(0);
4367 // Step 1.
4368 if (item.isObject()) {
4369 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
4370 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
4371 if (!result) {
4372 return false;
4375 args.rval().setObject(*result);
4376 return true;
4380 // Step 2.
4381 auto result = ToTemporalDuration(cx, item);
4382 if (!result) {
4383 return false;
4386 args.rval().setObject(*result);
4387 return true;
4391 * Temporal.Duration.compare ( one, two [ , options ] )
4393 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
4394 CallArgs args = CallArgsFromVp(argc, vp);
4396 // Step 1.
4397 Duration one;
4398 if (!ToTemporalDuration(cx, args.get(0), &one)) {
4399 return false;
4402 // Step 2.
4403 Duration two;
4404 if (!ToTemporalDuration(cx, args.get(1), &two)) {
4405 return false;
4408 // Step 3.
4409 Rooted<JSObject*> options(cx);
4410 if (args.hasDefined(2)) {
4411 options = RequireObjectArg(cx, "options", "compare", args[2]);
4412 if (!options) {
4413 return false;
4417 // Step 4.
4418 if (one == two) {
4419 args.rval().setInt32(0);
4420 return true;
4423 // Steps 5-8.
4424 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4425 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4426 Rooted<TimeZoneRecord> timeZone(cx);
4427 if (options) {
4428 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4429 &zonedRelativeTo, &timeZone)) {
4430 return false;
4432 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4433 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4436 // Steps 9-10.
4437 auto hasCalendarUnit = [](const auto& d) {
4438 return d.years != 0 || d.months != 0 || d.weeks != 0;
4440 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
4442 // Step 11.
4443 Rooted<CalendarRecord> calendar(cx);
4444 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4445 zonedRelativeTo,
4447 CalendarMethod::DateAdd,
4449 &calendar)) {
4450 return false;
4453 // Step 12.
4454 if (zonedRelativeTo &&
4455 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
4456 // Step 12.a.
4457 const auto& instant = zonedRelativeTo.instant();
4459 // Step 12.b.
4460 PlainDateTime dateTime;
4461 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
4462 return false;
4465 // Step 12.c.
4466 auto normalized1 = CreateNormalizedDurationRecord(one);
4468 // Step 12.d.
4469 auto normalized2 = CreateNormalizedDurationRecord(two);
4471 // Step 12.e.
4472 Instant after1;
4473 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
4474 dateTime, &after1)) {
4475 return false;
4478 // Step 12.f.
4479 Instant after2;
4480 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
4481 dateTime, &after2)) {
4482 return false;
4485 // Steps 12.g-i.
4486 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
4487 return true;
4490 // Steps 13-14.
4491 double days1, days2;
4492 if (calendarUnitsPresent) {
4493 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4495 // Step 13.a.
4496 DateDuration unbalanceResult1;
4497 if (plainRelativeTo) {
4498 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
4499 TemporalUnit::Day, plainRelativeTo,
4500 calendar, &unbalanceResult1)) {
4501 return false;
4503 } else {
4504 if (!UnbalanceDateDurationRelative(
4505 cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
4506 return false;
4508 MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
4511 // Step 13.b.
4512 DateDuration unbalanceResult2;
4513 if (plainRelativeTo) {
4514 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
4515 TemporalUnit::Day, plainRelativeTo,
4516 calendar, &unbalanceResult2)) {
4517 return false;
4519 } else {
4520 if (!UnbalanceDateDurationRelative(
4521 cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
4522 return false;
4524 MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
4527 // Step 13.c.
4528 days1 = unbalanceResult1.days;
4530 // Step 13.d.
4531 days2 = unbalanceResult2.days;
4532 } else {
4533 // Step 14.a.
4534 days1 = one.days;
4536 // Step 14.b.
4537 days2 = two.days;
4540 // Step 15.
4541 auto normalized1 = NormalizeTimeDuration(one);
4543 // Step 16.
4544 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
4545 &normalized1)) {
4546 return false;
4549 // Step 17.
4550 auto normalized2 = NormalizeTimeDuration(two);
4552 // Step 18.
4553 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
4554 &normalized2)) {
4555 return false;
4558 // Step 19.
4559 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
4560 return true;
4564 * get Temporal.Duration.prototype.years
4566 static bool Duration_years(JSContext* cx, const CallArgs& args) {
4567 // Step 3.
4568 auto* duration = &args.thisv().toObject().as<DurationObject>();
4569 args.rval().setNumber(duration->years());
4570 return true;
4574 * get Temporal.Duration.prototype.years
4576 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
4577 // Steps 1-2.
4578 CallArgs args = CallArgsFromVp(argc, vp);
4579 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
4583 * get Temporal.Duration.prototype.months
4585 static bool Duration_months(JSContext* cx, const CallArgs& args) {
4586 // Step 3.
4587 auto* duration = &args.thisv().toObject().as<DurationObject>();
4588 args.rval().setNumber(duration->months());
4589 return true;
4593 * get Temporal.Duration.prototype.months
4595 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
4596 // Steps 1-2.
4597 CallArgs args = CallArgsFromVp(argc, vp);
4598 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
4602 * get Temporal.Duration.prototype.weeks
4604 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
4605 // Step 3.
4606 auto* duration = &args.thisv().toObject().as<DurationObject>();
4607 args.rval().setNumber(duration->weeks());
4608 return true;
4612 * get Temporal.Duration.prototype.weeks
4614 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
4615 // Steps 1-2.
4616 CallArgs args = CallArgsFromVp(argc, vp);
4617 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
4621 * get Temporal.Duration.prototype.days
4623 static bool Duration_days(JSContext* cx, const CallArgs& args) {
4624 // Step 3.
4625 auto* duration = &args.thisv().toObject().as<DurationObject>();
4626 args.rval().setNumber(duration->days());
4627 return true;
4631 * get Temporal.Duration.prototype.days
4633 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
4634 // Steps 1-2.
4635 CallArgs args = CallArgsFromVp(argc, vp);
4636 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
4640 * get Temporal.Duration.prototype.hours
4642 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
4643 // Step 3.
4644 auto* duration = &args.thisv().toObject().as<DurationObject>();
4645 args.rval().setNumber(duration->hours());
4646 return true;
4650 * get Temporal.Duration.prototype.hours
4652 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
4653 // Steps 1-2.
4654 CallArgs args = CallArgsFromVp(argc, vp);
4655 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
4659 * get Temporal.Duration.prototype.minutes
4661 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
4662 // Step 3.
4663 auto* duration = &args.thisv().toObject().as<DurationObject>();
4664 args.rval().setNumber(duration->minutes());
4665 return true;
4669 * get Temporal.Duration.prototype.minutes
4671 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
4672 // Steps 1-2.
4673 CallArgs args = CallArgsFromVp(argc, vp);
4674 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
4678 * get Temporal.Duration.prototype.seconds
4680 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
4681 // Step 3.
4682 auto* duration = &args.thisv().toObject().as<DurationObject>();
4683 args.rval().setNumber(duration->seconds());
4684 return true;
4688 * get Temporal.Duration.prototype.seconds
4690 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
4691 // Steps 1-2.
4692 CallArgs args = CallArgsFromVp(argc, vp);
4693 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
4697 * get Temporal.Duration.prototype.milliseconds
4699 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
4700 // Step 3.
4701 auto* duration = &args.thisv().toObject().as<DurationObject>();
4702 args.rval().setNumber(duration->milliseconds());
4703 return true;
4707 * get Temporal.Duration.prototype.milliseconds
4709 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
4710 // Steps 1-2.
4711 CallArgs args = CallArgsFromVp(argc, vp);
4712 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
4716 * get Temporal.Duration.prototype.microseconds
4718 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
4719 // Step 3.
4720 auto* duration = &args.thisv().toObject().as<DurationObject>();
4721 args.rval().setNumber(duration->microseconds());
4722 return true;
4726 * get Temporal.Duration.prototype.microseconds
4728 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
4729 // Steps 1-2.
4730 CallArgs args = CallArgsFromVp(argc, vp);
4731 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
4735 * get Temporal.Duration.prototype.nanoseconds
4737 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
4738 // Step 3.
4739 auto* duration = &args.thisv().toObject().as<DurationObject>();
4740 args.rval().setNumber(duration->nanoseconds());
4741 return true;
4745 * get Temporal.Duration.prototype.nanoseconds
4747 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
4748 // Steps 1-2.
4749 CallArgs args = CallArgsFromVp(argc, vp);
4750 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
4754 * get Temporal.Duration.prototype.sign
4756 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
4757 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4759 // Step 3.
4760 args.rval().setInt32(DurationSign(duration));
4761 return true;
4765 * get Temporal.Duration.prototype.sign
4767 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
4768 // Steps 1-2.
4769 CallArgs args = CallArgsFromVp(argc, vp);
4770 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
4774 * get Temporal.Duration.prototype.blank
4776 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
4777 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4779 // Steps 3-5.
4780 args.rval().setBoolean(duration == Duration{});
4781 return true;
4785 * get Temporal.Duration.prototype.blank
4787 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
4788 // Steps 1-2.
4789 CallArgs args = CallArgsFromVp(argc, vp);
4790 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
4794 * Temporal.Duration.prototype.with ( temporalDurationLike )
4796 * ToPartialDuration ( temporalDurationLike )
4798 static bool Duration_with(JSContext* cx, const CallArgs& args) {
4799 // Absent values default to the corresponding values of |this| object.
4800 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4802 // Steps 3-23.
4803 Rooted<JSObject*> temporalDurationLike(
4804 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
4805 if (!temporalDurationLike) {
4806 return false;
4808 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
4809 return false;
4812 // Step 24.
4813 auto* result = CreateTemporalDuration(cx, duration);
4814 if (!result) {
4815 return false;
4818 args.rval().setObject(*result);
4819 return true;
4823 * Temporal.Duration.prototype.with ( temporalDurationLike )
4825 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
4826 // Steps 1-2.
4827 CallArgs args = CallArgsFromVp(argc, vp);
4828 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
4832 * Temporal.Duration.prototype.negated ( )
4834 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
4835 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4837 // Step 3.
4838 auto* result = CreateTemporalDuration(cx, duration.negate());
4839 if (!result) {
4840 return false;
4843 args.rval().setObject(*result);
4844 return true;
4848 * Temporal.Duration.prototype.negated ( )
4850 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
4851 // Steps 1-2.
4852 CallArgs args = CallArgsFromVp(argc, vp);
4853 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
4857 * Temporal.Duration.prototype.abs ( )
4859 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
4860 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4862 // Step 3.
4863 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
4864 if (!result) {
4865 return false;
4868 args.rval().setObject(*result);
4869 return true;
4873 * Temporal.Duration.prototype.abs ( )
4875 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
4876 // Steps 1-2.
4877 CallArgs args = CallArgsFromVp(argc, vp);
4878 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
4882 * Temporal.Duration.prototype.add ( other [ , options ] )
4884 static bool Duration_add(JSContext* cx, const CallArgs& args) {
4885 return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
4886 args);
4890 * Temporal.Duration.prototype.add ( other [ , options ] )
4892 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
4893 // Steps 1-2.
4894 CallArgs args = CallArgsFromVp(argc, vp);
4895 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
4899 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4901 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
4902 return AddDurationToOrSubtractDurationFromDuration(
4903 cx, DurationOperation::Subtract, args);
4907 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4909 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
4910 // Steps 1-2.
4911 CallArgs args = CallArgsFromVp(argc, vp);
4912 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
4916 * Temporal.Duration.prototype.round ( roundTo )
4918 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4919 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4921 // Step 18. (Reordered)
4922 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4924 // Steps 3-25.
4925 auto smallestUnit = TemporalUnit::Auto;
4926 TemporalUnit largestUnit;
4927 auto roundingMode = TemporalRoundingMode::HalfExpand;
4928 auto roundingIncrement = Increment{1};
4929 Rooted<JSObject*> relativeTo(cx);
4930 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4931 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4932 Rooted<TimeZoneRecord> timeZone(cx);
4933 if (args.get(0).isString()) {
4934 // Step 4. (Not applicable in our implementation.)
4936 // Steps 6-15. (Not applicable)
4938 // Step 16.
4939 Rooted<JSString*> paramString(cx, args[0].toString());
4940 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
4941 TemporalUnitGroup::DateTime, &smallestUnit)) {
4942 return false;
4945 // Step 17. (Not applicable)
4947 // Step 18. (Moved above)
4949 // Step 19.
4950 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4952 // Step 20. (Not applicable)
4954 // Step 20.a. (Not applicable)
4956 // Step 20.b.
4957 largestUnit = defaultLargestUnit;
4959 // Steps 21-25. (Not applicable)
4960 } else {
4961 // Steps 3 and 5.
4962 Rooted<JSObject*> options(
4963 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
4964 if (!options) {
4965 return false;
4968 // Step 6.
4969 bool smallestUnitPresent = true;
4971 // Step 7.
4972 bool largestUnitPresent = true;
4974 // Steps 8-9.
4976 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
4977 // absent "largestUnit" value.
4978 Rooted<Value> largestUnitValue(cx);
4979 if (!GetProperty(cx, options, options, cx->names().largestUnit,
4980 &largestUnitValue)) {
4981 return false;
4984 if (!largestUnitValue.isUndefined()) {
4985 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
4986 if (!largestUnitStr) {
4987 return false;
4990 largestUnit = TemporalUnit::Auto;
4991 if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
4992 TemporalUnitGroup::DateTime, &largestUnit)) {
4993 return false;
4997 // Steps 10-13.
4998 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4999 &zonedRelativeTo, &timeZone)) {
5000 return false;
5002 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5003 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5005 // Step 14.
5006 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
5007 return false;
5010 // Step 15.
5011 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5012 return false;
5015 // Step 16.
5016 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5017 TemporalUnitGroup::DateTime, &smallestUnit)) {
5018 return false;
5021 // Step 17.
5022 if (smallestUnit == TemporalUnit::Auto) {
5023 // Step 17.a.
5024 smallestUnitPresent = false;
5026 // Step 17.b.
5027 smallestUnit = TemporalUnit::Nanosecond;
5030 // Step 18. (Moved above)
5032 // Step 19.
5033 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5035 // Steps 20-21.
5036 if (largestUnitValue.isUndefined()) {
5037 // Step 20.a.
5038 largestUnitPresent = false;
5040 // Step 20.b.
5041 largestUnit = defaultLargestUnit;
5042 } else if (largestUnit == TemporalUnit::Auto) {
5043 // Step 21.a
5044 largestUnit = defaultLargestUnit;
5047 // Step 22.
5048 if (!smallestUnitPresent && !largestUnitPresent) {
5049 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5050 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
5051 return false;
5054 // Step 23.
5055 if (largestUnit > smallestUnit) {
5056 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5057 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
5058 return false;
5061 // Steps 24-25.
5062 if (smallestUnit > TemporalUnit::Day) {
5063 // Step 24.
5064 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
5066 // Step 25.
5067 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
5068 false)) {
5069 return false;
5074 // Step 26.
5075 bool hoursToDaysConversionMayOccur = false;
5077 // Step 27.
5078 if (duration.days != 0 && zonedRelativeTo) {
5079 hoursToDaysConversionMayOccur = true;
5082 // Step 28.
5083 else if (std::abs(duration.hours) >= 24) {
5084 hoursToDaysConversionMayOccur = true;
5087 // Step 29.
5088 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
5089 roundingIncrement == Increment{1};
5091 // Step 30.
5092 bool calendarUnitsPresent =
5093 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
5095 // Step 31.
5096 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
5097 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
5098 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
5099 std::abs(duration.milliseconds) < 1000 &&
5100 std::abs(duration.microseconds) < 1000 &&
5101 std::abs(duration.nanoseconds) < 1000) {
5102 // Steps 31.a-b.
5103 auto* obj = CreateTemporalDuration(cx, duration);
5104 if (!obj) {
5105 return false;
5108 args.rval().setObject(*obj);
5109 return true;
5112 // Step 32.
5113 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5115 // Step 33.
5116 bool plainDateTimeOrRelativeToWillBeUsed =
5117 !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
5118 calendarUnitsPresent || duration.days != 0;
5120 // Step 34.
5121 PlainDateTime relativeToDateTime;
5122 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5123 // Steps 34.a-b.
5124 const auto& instant = zonedRelativeTo.instant();
5126 // Step 34.c.
5127 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5128 return false;
5130 precalculatedPlainDateTime =
5131 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5133 // Step 34.d.
5134 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5135 zonedRelativeTo.calendar());
5136 if (!plainRelativeTo) {
5137 return false;
5141 // Step 35.
5142 Rooted<CalendarRecord> calendar(cx);
5143 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5144 zonedRelativeTo,
5146 CalendarMethod::DateAdd,
5147 CalendarMethod::DateUntil,
5149 &calendar)) {
5150 return false;
5153 // Step 36.
5154 DateDuration unbalanceResult;
5155 if (plainRelativeTo) {
5156 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5157 largestUnit, plainRelativeTo, calendar,
5158 &unbalanceResult)) {
5159 return false;
5161 } else {
5162 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5163 largestUnit, &unbalanceResult)) {
5164 return false;
5166 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5169 // Steps 37-39.
5170 auto roundInput =
5171 NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
5172 NormalizedDuration roundResult;
5173 if (plainRelativeTo || zonedRelativeTo) {
5174 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5175 roundingMode, plainRelativeTo, calendar,
5176 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5177 &roundResult)) {
5178 return false;
5180 } else {
5181 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5182 roundingMode, &roundResult)) {
5183 return false;
5187 // Steps 40-41.
5188 TimeDuration balanceResult;
5189 if (zonedRelativeTo) {
5190 // Step 40.a.
5191 NormalizedDuration adjustResult;
5192 if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
5193 smallestUnit, roundingMode, zonedRelativeTo,
5194 calendar, timeZone,
5195 precalculatedPlainDateTime, &adjustResult)) {
5196 return false;
5198 roundResult = adjustResult;
5200 // Step 40.b.
5201 if (!BalanceTimeDurationRelative(
5202 cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
5203 precalculatedPlainDateTime, &balanceResult)) {
5204 return false;
5206 } else {
5207 // Step 41.a.
5208 NormalizedTimeDuration withDays;
5209 if (!Add24HourDaysToNormalizedTimeDuration(
5210 cx, roundResult.time, roundResult.date.days, &withDays)) {
5211 return false;
5214 // Step 41.b.
5215 balanceResult = temporal::BalanceTimeDuration(withDays, largestUnit);
5218 // Step 41.
5219 auto balanceInput = DateDuration{
5220 roundResult.date.years,
5221 roundResult.date.months,
5222 roundResult.date.weeks,
5223 balanceResult.days,
5225 DateDuration dateResult;
5226 if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
5227 smallestUnit, plainRelativeTo, calendar,
5228 &dateResult)) {
5229 return false;
5232 // Step 42.
5233 auto result = Duration{
5234 double(dateResult.years), double(dateResult.months),
5235 double(dateResult.weeks), double(dateResult.days),
5236 double(balanceResult.hours), double(balanceResult.minutes),
5237 double(balanceResult.seconds), double(balanceResult.milliseconds),
5238 balanceResult.microseconds, balanceResult.nanoseconds,
5240 MOZ_ASSERT(IsValidDuration(result));
5241 auto* obj = CreateTemporalDuration(cx, result);
5242 if (!obj) {
5243 return false;
5246 args.rval().setObject(*obj);
5247 return true;
5251 * Temporal.Duration.prototype.round ( options )
5253 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
5254 // Steps 1-2.
5255 CallArgs args = CallArgsFromVp(argc, vp);
5256 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
5260 * Temporal.Duration.prototype.total ( totalOf )
5262 static bool Duration_total(JSContext* cx, const CallArgs& args) {
5263 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
5264 auto duration = ToDuration(durationObj);
5266 // Steps 3-11.
5267 Rooted<JSObject*> relativeTo(cx);
5268 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5269 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5270 Rooted<TimeZoneRecord> timeZone(cx);
5271 auto unit = TemporalUnit::Auto;
5272 if (args.get(0).isString()) {
5273 // Step 4. (Not applicable in our implementation.)
5275 // Steps 6-10. (Implicit)
5276 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
5278 // Step 11.
5279 Rooted<JSString*> paramString(cx, args[0].toString());
5280 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
5281 TemporalUnitGroup::DateTime, &unit)) {
5282 return false;
5284 } else {
5285 // Steps 3 and 5.
5286 Rooted<JSObject*> totalOf(
5287 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
5288 if (!totalOf) {
5289 return false;
5292 // Steps 6-10.
5293 if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
5294 &zonedRelativeTo, &timeZone)) {
5295 return false;
5297 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5298 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5300 // Step 11.
5301 if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
5302 TemporalUnitGroup::DateTime, &unit)) {
5303 return false;
5306 if (unit == TemporalUnit::Auto) {
5307 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5308 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
5309 return false;
5313 // Step 12.
5314 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5316 // Step 13.
5317 bool plainDateTimeOrRelativeToWillBeUsed =
5318 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
5320 // Step 14.
5321 PlainDateTime relativeToDateTime;
5322 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5323 // Steps 14.a-b.
5324 const auto& instant = zonedRelativeTo.instant();
5326 // Step 14.c.
5327 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5328 return false;
5330 precalculatedPlainDateTime =
5331 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5333 // Step 14.d
5334 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5335 zonedRelativeTo.calendar());
5336 if (!plainRelativeTo) {
5337 return false;
5341 // Step 15.
5342 Rooted<CalendarRecord> calendar(cx);
5343 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5344 zonedRelativeTo,
5346 CalendarMethod::DateAdd,
5347 CalendarMethod::DateUntil,
5349 &calendar)) {
5350 return false;
5353 // Step 16.
5354 DateDuration unbalanceResult;
5355 if (plainRelativeTo) {
5356 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5357 plainRelativeTo, calendar,
5358 &unbalanceResult)) {
5359 return false;
5361 } else {
5362 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5363 &unbalanceResult)) {
5364 return false;
5366 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5369 // Step 17.
5370 int64_t unbalancedDays = unbalanceResult.days;
5372 // Steps 18-19.
5373 int64_t days;
5374 NormalizedTimeDuration balanceResult;
5375 if (zonedRelativeTo) {
5376 // Step 18.a
5377 Rooted<ZonedDateTime> intermediate(cx);
5378 if (!MoveRelativeZonedDateTime(
5379 cx, zonedRelativeTo, calendar, timeZone,
5380 {unbalanceResult.years, unbalanceResult.months,
5381 unbalanceResult.weeks, 0},
5382 precalculatedPlainDateTime, &intermediate)) {
5383 return false;
5386 // Step 18.b.
5387 auto timeDuration = NormalizeTimeDuration(duration);
5389 // Step 18.c
5390 const auto& startNs = intermediate.instant();
5392 // Step 18.d.
5393 const auto& startInstant = startNs;
5395 // Step 18.e.
5396 mozilla::Maybe<PlainDateTime> startDateTime{};
5398 // Steps 18.f-g.
5399 Instant intermediateNs;
5400 if (unbalancedDays != 0) {
5401 // Step 18.f.i.
5402 PlainDateTime dateTime;
5403 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5404 return false;
5406 startDateTime = mozilla::Some(dateTime);
5408 // Step 18.f.ii.
5409 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
5410 Instant addResult;
5411 if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
5412 isoCalendar, unbalancedDays, &addResult)) {
5413 return false;
5416 // Step 18.f.iii.
5417 intermediateNs = addResult;
5418 } else {
5419 // Step 18.g.
5420 intermediateNs = startNs;
5423 // Step 18.h.
5424 Instant endNs;
5425 if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
5426 return false;
5429 // Step 18.i.
5430 auto difference =
5431 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
5433 // Steps 18.j-k.
5435 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5436 if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
5437 difference != NormalizedTimeDuration{}) {
5438 // Step 18.j.i.
5439 if (!startDateTime) {
5440 PlainDateTime dateTime;
5441 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5442 return false;
5444 startDateTime = mozilla::Some(dateTime);
5447 // Step 18.j.ii.
5448 NormalizedTimeAndDays timeAndDays;
5449 if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
5450 *startDateTime, &timeAndDays)) {
5451 return false;
5454 // Step 18.j.iii.
5455 balanceResult = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
5457 // Step 18.j.iv.
5458 days = timeAndDays.days;
5459 } else {
5460 // Step 18.k.i.
5461 balanceResult = difference;
5462 days = 0;
5464 } else {
5465 // Step 19.a.
5466 auto timeDuration = NormalizeTimeDuration(duration);
5468 // Step 19.b.
5469 if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
5470 &balanceResult)) {
5471 return false;
5474 // Step 19.c.
5475 days = 0;
5477 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult));
5479 // Step 20.
5480 auto roundInput = NormalizedDuration{
5482 unbalanceResult.years,
5483 unbalanceResult.months,
5484 unbalanceResult.weeks,
5485 days,
5487 balanceResult,
5489 double total;
5490 if (plainRelativeTo || zonedRelativeTo) {
5491 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5492 TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
5493 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5494 &total)) {
5495 return false;
5497 } else {
5498 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5499 TemporalRoundingMode::Trunc, &total)) {
5500 return false;
5504 // Step 21.
5505 args.rval().setNumber(total);
5506 return true;
5510 * Temporal.Duration.prototype.total ( totalOf )
5512 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
5513 // Steps 1-2.
5514 CallArgs args = CallArgsFromVp(argc, vp);
5515 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
5519 * Temporal.Duration.prototype.toString ( [ options ] )
5521 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
5522 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5524 // Steps 3-9.
5525 SecondsStringPrecision precision = {Precision::Auto(),
5526 TemporalUnit::Nanosecond, Increment{1}};
5527 auto roundingMode = TemporalRoundingMode::Trunc;
5528 if (args.hasDefined(0)) {
5529 // Step 3.
5530 Rooted<JSObject*> options(
5531 cx, RequireObjectArg(cx, "options", "toString", args[0]));
5532 if (!options) {
5533 return false;
5536 // Steps 4-5.
5537 auto digits = Precision::Auto();
5538 if (!ToFractionalSecondDigits(cx, options, &digits)) {
5539 return false;
5542 // Step 6.
5543 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5544 return false;
5547 // Step 7.
5548 auto smallestUnit = TemporalUnit::Auto;
5549 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5550 TemporalUnitGroup::Time, &smallestUnit)) {
5551 return false;
5554 // Step 8.
5555 if (smallestUnit == TemporalUnit::Hour ||
5556 smallestUnit == TemporalUnit::Minute) {
5557 const char* smallestUnitStr =
5558 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
5559 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5560 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
5561 smallestUnitStr, "smallestUnit");
5562 return false;
5565 // Step 9.
5566 precision = ToSecondsStringPrecision(smallestUnit, digits);
5569 // Steps 10-11.
5570 Duration result;
5571 if (precision.unit != TemporalUnit::Nanosecond ||
5572 precision.increment != Increment{1}) {
5573 // Step 10.a.
5574 auto timeDuration = NormalizeTimeDuration(duration);
5576 // Step 10.b.
5577 auto largestUnit = DefaultTemporalLargestUnit(duration);
5579 // Steps 10.c-d.
5580 auto rounded = RoundDuration(timeDuration, precision.increment,
5581 precision.unit, roundingMode);
5583 // Step 10.e.
5584 auto balanced = BalanceTimeDuration(
5585 rounded, std::min(largestUnit, TemporalUnit::Second));
5587 // Step 10.f.
5588 result = {
5589 duration.years, duration.months,
5590 duration.weeks, duration.days + double(balanced.days),
5591 double(balanced.hours), double(balanced.minutes),
5592 double(balanced.seconds), double(balanced.milliseconds),
5593 balanced.microseconds, balanced.nanoseconds,
5595 MOZ_ASSERT(IsValidDuration(duration));
5596 } else {
5597 // Step 11.
5598 result = duration;
5601 // Steps 12-13.
5602 JSString* str = TemporalDurationToString(cx, result, precision.precision);
5603 if (!str) {
5604 return false;
5607 args.rval().setString(str);
5608 return true;
5612 * Temporal.Duration.prototype.toString ( [ options ] )
5614 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
5615 // Steps 1-2.
5616 CallArgs args = CallArgsFromVp(argc, vp);
5617 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
5621 * Temporal.Duration.prototype.toJSON ( )
5623 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
5624 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5626 // Steps 3-4.
5627 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5628 if (!str) {
5629 return false;
5632 args.rval().setString(str);
5633 return true;
5637 * Temporal.Duration.prototype.toJSON ( )
5639 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
5640 // Steps 1-2.
5641 CallArgs args = CallArgsFromVp(argc, vp);
5642 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
5646 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5648 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
5649 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5651 // Steps 3-4.
5652 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5653 if (!str) {
5654 return false;
5657 args.rval().setString(str);
5658 return true;
5662 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5664 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
5665 // Steps 1-2.
5666 CallArgs args = CallArgsFromVp(argc, vp);
5667 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
5671 * Temporal.Duration.prototype.valueOf ( )
5673 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
5674 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
5675 "Duration", "primitive type");
5676 return false;
5679 const JSClass DurationObject::class_ = {
5680 "Temporal.Duration",
5681 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
5682 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
5683 JS_NULL_CLASS_OPS,
5684 &DurationObject::classSpec_,
5687 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
5689 static const JSFunctionSpec Duration_methods[] = {
5690 JS_FN("from", Duration_from, 1, 0),
5691 JS_FN("compare", Duration_compare, 2, 0),
5692 JS_FS_END,
5695 static const JSFunctionSpec Duration_prototype_methods[] = {
5696 JS_FN("with", Duration_with, 1, 0),
5697 JS_FN("negated", Duration_negated, 0, 0),
5698 JS_FN("abs", Duration_abs, 0, 0),
5699 JS_FN("add", Duration_add, 1, 0),
5700 JS_FN("subtract", Duration_subtract, 1, 0),
5701 JS_FN("round", Duration_round, 1, 0),
5702 JS_FN("total", Duration_total, 1, 0),
5703 JS_FN("toString", Duration_toString, 0, 0),
5704 JS_FN("toJSON", Duration_toJSON, 0, 0),
5705 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
5706 JS_FN("valueOf", Duration_valueOf, 0, 0),
5707 JS_FS_END,
5710 static const JSPropertySpec Duration_prototype_properties[] = {
5711 JS_PSG("years", Duration_years, 0),
5712 JS_PSG("months", Duration_months, 0),
5713 JS_PSG("weeks", Duration_weeks, 0),
5714 JS_PSG("days", Duration_days, 0),
5715 JS_PSG("hours", Duration_hours, 0),
5716 JS_PSG("minutes", Duration_minutes, 0),
5717 JS_PSG("seconds", Duration_seconds, 0),
5718 JS_PSG("milliseconds", Duration_milliseconds, 0),
5719 JS_PSG("microseconds", Duration_microseconds, 0),
5720 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
5721 JS_PSG("sign", Duration_sign, 0),
5722 JS_PSG("blank", Duration_blank, 0),
5723 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
5724 JS_PS_END,
5727 const ClassSpec DurationObject::classSpec_ = {
5728 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
5729 GenericCreatePrototype<DurationObject>,
5730 Duration_methods,
5731 nullptr,
5732 Duration_prototype_methods,
5733 Duration_prototype_properties,
5734 nullptr,
5735 ClassSpec::DontDefineConstructor,