Bug 1874684 - Part 12: Add separate structs for fractional duration units. r=sfink
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blob63ec86c1a3e9778150b53b4f87b458807e6e8335
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 struct FractionalDays final {
3171 int64_t days = 0;
3172 int64_t time = 0;
3173 int64_t dayLength = 0;
3175 FractionalDays() = default;
3177 explicit FractionalDays(int64_t durationDays,
3178 const NormalizedTimeAndDays& timeAndDays)
3179 : days(durationDays + timeAndDays.days),
3180 time(timeAndDays.time),
3181 dayLength(timeAndDays.dayLength) {
3182 MOZ_ASSERT(durationDays <= (int64_t(1) << 53) / (24 * 60 * 60));
3183 MOZ_ASSERT(timeAndDays.days <= (int64_t(1) << 53) / (24 * 60 * 60));
3185 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3186 // positive and less than 2**53.
3187 MOZ_ASSERT(dayLength > 0);
3188 MOZ_ASSERT(dayLength < int64_t(1) << 53);
3190 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3191 // less than |timeAndDays.dayLength|.
3192 MOZ_ASSERT(std::abs(time) < dayLength);
3195 FractionalDays operator+=(int32_t epochDays) {
3196 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3197 days += epochDays;
3198 return *this;
3201 FractionalDays operator-=(int32_t epochDays) {
3202 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3203 days -= epochDays;
3204 return *this;
3207 int64_t truncate() const {
3208 int64_t truncatedDays = days;
3209 if (time > 0) {
3210 // Round toward positive infinity when the integer days are negative and
3211 // the fractional part is positive.
3212 if (truncatedDays < 0) {
3213 truncatedDays += 1;
3215 } else if (time < 0) {
3216 // Round toward negative infinity when the integer days are positive and
3217 // the fractional part is negative.
3218 if (truncatedDays > 0) {
3219 truncatedDays -= 1;
3222 return truncatedDays;
3225 int32_t sign() const {
3226 if (days != 0) {
3227 return days < 0 ? -1 : 1;
3229 return time < 0 ? -1 : time > 0 ? 1 : 0;
3233 struct Fraction final {
3234 int64_t numerator = 0;
3235 int32_t denominator = 0;
3237 constexpr Fraction() = default;
3239 constexpr Fraction(int64_t numerator, int32_t denominator)
3240 : numerator(numerator), denominator(denominator) {
3241 MOZ_ASSERT(denominator > 0);
3245 static RoundedNumber RoundNumberToIncrement(
3246 const Fraction& fraction, const FractionalDays& fractionalDays,
3247 Increment increment, TemporalRoundingMode roundingMode,
3248 ComputeRemainder computeRemainder) {
3249 #ifdef DEBUG
3250 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3251 static constexpr int64_t maxDurationDays =
3252 (int64_t(1) << 53) / (24 * 60 * 60);
3254 // Numbers of days between nsMinInstant and nsMaxInstant.
3255 static constexpr int32_t epochDays = 200'000'000;
3257 // Maximum number of days in |fractionalDays|.
3258 static constexpr int64_t maxFractionalDays =
3259 2 * maxDurationDays + 2 * epochDays;
3260 #endif
3262 MOZ_ASSERT(std::abs(fraction.numerator) < (int64_t(1) << 32) * 2);
3263 MOZ_ASSERT(fraction.denominator > 0);
3264 MOZ_ASSERT(fraction.denominator <= epochDays);
3265 MOZ_ASSERT(std::abs(fractionalDays.days) <= maxFractionalDays);
3266 MOZ_ASSERT(fractionalDays.dayLength > 0);
3267 MOZ_ASSERT(fractionalDays.dayLength < (int64_t(1) << 53));
3268 MOZ_ASSERT(std::abs(fractionalDays.time) < fractionalDays.dayLength);
3269 MOZ_ASSERT(increment <= Increment::max());
3271 // clang-format off
3273 // Change the representation of |fractionalWeeks| from a real number to a
3274 // rational number, because we don't support arbitrary precision real
3275 // numbers.
3277 // |fractionalWeeks| is defined as:
3279 // fractionalWeeks
3280 // = weeks + days' / abs(oneWeekDays)
3282 // where days' = days + nanoseconds / dayLength.
3284 // The fractional part |nanoseconds / dayLength| is from step 7.
3286 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3288 // fractionalWeeks
3289 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3290 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3291 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3293 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3294 // to omit the multiplication by |dayLength| when the rounding conditions are
3295 // appropriately modified to account for the |nanoseconds / dayLength| part.
3296 // This allows to implement rounding using only int64 values.
3298 // This optimization is currently only implemented when |nanoseconds| is zero.
3300 // Example how to expand this optimization for non-zero |nanoseconds|:
3302 // |Round(fraction / increment) * increment| with:
3303 // fraction = numerator / denominator
3304 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3305 // denominator = dayLength * abs(oneWeekDays)
3307 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3309 // |Round(fraction / increment) * increment| with:
3310 // fraction = numerator / denominator
3311 // numerator = weeks * abs(oneWeekDays) + days
3312 // denominator = abs(oneWeekDays)
3314 // Where:
3315 // fraction / increment
3316 // = (numerator / denominator) / increment
3317 // = numerator / (denominator * increment)
3319 // And |numerator| and |denominator * increment| both fit into int64.
3321 // The "ceiling" operation has to be modified from:
3323 // CeilDiv(dividend, divisor)
3324 // quot, rem = dividend / divisor
3325 // return quot + (rem > 0)
3327 // To:
3329 // CeilDiv(dividend, divisor, fractional)
3330 // quot, rem = dividend / divisor
3331 // return quot + ((rem > 0) || (fractional > 0))
3333 // To properly account for the fractional |nanoseconds| part. Alternatively
3334 // |dividend| can be modified before calling `CeilDiv`.
3336 // clang-format on
3338 if (fractionalDays.time == 0) {
3339 auto [numerator, denominator] = fraction;
3340 int64_t totalDays = fractionalDays.days + denominator * numerator;
3342 if (computeRemainder == ComputeRemainder::Yes) {
3343 return TruncateNumber(totalDays, denominator);
3346 auto rounded =
3347 RoundNumberToIncrement(totalDays, denominator, increment, roundingMode);
3348 constexpr double total = 0;
3349 return {rounded, total};
3352 do {
3353 auto dayLength = mozilla::CheckedInt64(fractionalDays.dayLength);
3355 auto denominator = dayLength * fraction.denominator;
3356 if (!denominator.isValid()) {
3357 break;
3360 auto amountNanos = denominator * fraction.numerator;
3361 if (!amountNanos.isValid()) {
3362 break;
3365 auto totalNanoseconds = dayLength * fractionalDays.days;
3366 totalNanoseconds += fractionalDays.time;
3367 totalNanoseconds += amountNanos;
3368 if (!totalNanoseconds.isValid()) {
3369 break;
3372 if (computeRemainder == ComputeRemainder::Yes) {
3373 return TruncateNumber(totalNanoseconds.value(), denominator.value());
3376 auto rounded = RoundNumberToIncrement(
3377 totalNanoseconds.value(), denominator.value(), increment, roundingMode);
3378 constexpr double total = 0;
3379 return {rounded, total};
3380 } while (false);
3382 // Use int128 when values are too large for int64. Additionally assert all
3383 // values fit into int128.
3385 // `dayLength` < 2**53
3386 auto dayLength = Int128{fractionalDays.dayLength};
3387 MOZ_ASSERT(dayLength < Int128{1} << 53);
3389 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3390 auto denominator = dayLength * Int128{fraction.denominator};
3391 MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
3393 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3395 // `abs(maxFractionalDays)`
3396 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3397 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3398 // ≤ 2 * 2**37 + 2**29
3399 // ≤ 2**39
3400 auto totalDays = Int128{fractionalDays.days};
3401 MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
3403 // `abs(fraction.numerator)` ≤ (2**33)
3404 auto totalAmount = Int128{fraction.numerator};
3405 MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
3407 // `denominator` < 2**(53 + 28)
3408 // `abs(totalAmount)` <= 2**33
3410 // `denominator * totalAmount`
3411 // ≤ 2**(53 + 28) * 2**33
3412 // = 2**(53 + 28 + 33)
3413 // = 2**114
3414 auto amountNanos = denominator * totalAmount;
3415 MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
3417 // `dayLength` < 2**53
3418 // `totalDays` ≤ 2**39
3419 // `fractionalDays.time` < `dayLength` < 2**53
3420 // `amountNanos` ≤ 2**114
3422 // `dayLength * totalDays`
3423 // ≤ 2**(53 + 39) = 2**92
3425 // `dayLength * totalDays + fractionalDays.time`
3426 // ≤ 2**93
3428 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3429 // ≤ 2**115
3430 auto totalNanoseconds = dayLength * totalDays;
3431 totalNanoseconds += Int128{fractionalDays.time};
3432 totalNanoseconds += amountNanos;
3433 MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
3435 if (computeRemainder == ComputeRemainder::Yes) {
3436 return TruncateNumber(totalNanoseconds, denominator);
3439 auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
3440 increment, roundingMode);
3441 constexpr double total = 0;
3442 return {rounded, total};
3445 static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
3446 FractionalDays fractionalDays,
3447 Increment increment,
3448 TemporalRoundingMode roundingMode,
3449 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3450 Handle<CalendarRecord> calendar,
3451 ComputeRemainder computeRemainder,
3452 RoundedDuration* result) {
3453 // Numbers of days between nsMinInstant and nsMaxInstant.
3454 static constexpr int32_t epochDays = 200'000'000;
3456 auto [years, months, weeks, days] = duration.date;
3458 // Step 10.a.
3459 Duration yearsDuration = {double(years)};
3461 // Step 10.b.
3462 auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
3463 if (!yearsLater) {
3464 return false;
3466 auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
3468 // Step 10.f. (Reordered)
3469 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
3471 // Step 10.c.
3472 Duration yearsMonthsWeeks = {double(years), double(months), double(weeks)};
3474 // Step 10.d.
3475 PlainDate yearsMonthsWeeksLater;
3476 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3477 &yearsMonthsWeeksLater)) {
3478 return false;
3481 // Step 10.e.
3482 int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
3483 MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
3485 // Step 10.f. (Moved up)
3487 // Step 10.g.
3488 fractionalDays += monthsWeeksInDays;
3490 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3491 // https://github.com/tc39/proposal-temporal/issues/2540
3493 // Step 10.h.
3494 PlainDate isoResult;
3495 if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, fractionalDays.truncate()},
3496 TemporalOverflow::Constrain, &isoResult)) {
3497 return false;
3500 // Step 10.i.
3501 Rooted<PlainDateObject*> wholeDaysLater(
3502 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3503 if (!wholeDaysLater) {
3504 return false;
3507 // Steps 10.j-l.
3508 Duration timePassed;
3509 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3510 TemporalUnit::Year, &timePassed)) {
3511 return false;
3514 // Step 10.m.
3515 int64_t yearsPassed = int64_t(timePassed.years);
3517 // Step 10.n.
3518 years += yearsPassed;
3520 // Step 10.o.
3521 Duration yearsPassedDuration = {double(yearsPassed)};
3523 // Steps 10.p-r.
3524 int32_t daysPassed;
3525 if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
3526 &newRelativeTo, &daysPassed)) {
3527 return false;
3529 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3531 // Step 10.s.
3532 fractionalDays -= daysPassed;
3534 // Steps 10.t.
3535 double sign = fractionalDays.sign() < 0 ? -1 : 1;
3537 // Step 10.u.
3538 Duration oneYear = {sign};
3540 // Steps 10.v-w.
3541 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3542 int32_t oneYearDays;
3543 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
3544 &moveResultIgnored, &oneYearDays)) {
3545 return false;
3548 // Step 10.x.
3549 if (oneYearDays == 0) {
3550 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3551 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3552 return false;
3555 // Steps 10.y.
3556 auto fractionalYears = Fraction{years, std::abs(oneYearDays)};
3558 // Steps 10.z-aa.
3559 auto [numYears, total] =
3560 RoundNumberToIncrement(fractionalYears, fractionalDays, increment,
3561 roundingMode, computeRemainder);
3563 // Step 10.ab.
3564 int64_t numMonths = 0;
3565 int64_t numWeeks = 0;
3567 // Step 10.ac.
3568 constexpr auto time = NormalizedTimeDuration{};
3570 // Step 20.
3571 if (numYears.abs() >= (Uint128{1} << 32)) {
3572 return ThrowInvalidDurationPart(cx, double(numYears), "years",
3573 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3576 auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
3577 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3578 return false;
3581 *result = {{resultDuration, time}, total};
3582 return true;
3585 static bool RoundDurationMonth(JSContext* cx,
3586 const NormalizedDuration& duration,
3587 FractionalDays fractionalDays,
3588 Increment increment,
3589 TemporalRoundingMode roundingMode,
3590 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3591 Handle<CalendarRecord> calendar,
3592 ComputeRemainder computeRemainder,
3593 RoundedDuration* result) {
3594 // Numbers of days between nsMinInstant and nsMaxInstant.
3595 static constexpr int32_t epochDays = 200'000'000;
3597 auto [years, months, weeks, days] = duration.date;
3599 // Step 11.a.
3600 Duration yearsMonths = {double(years), double(months)};
3602 // Step 11.b.
3603 auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
3604 if (!yearsMonthsLater) {
3605 return false;
3607 auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
3609 // Step 11.f. (Reordered)
3610 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
3612 // Step 11.c.
3613 Duration yearsMonthsWeeks = {double(years), double(months), double(weeks)};
3615 // Step 11.d.
3616 PlainDate yearsMonthsWeeksLater;
3617 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3618 &yearsMonthsWeeksLater)) {
3619 return false;
3622 // Step 11.e.
3623 int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
3624 MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
3626 // Step 11.f. (Moved up)
3628 // Step 11.g.
3629 fractionalDays += weeksInDays;
3631 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3632 // https://github.com/tc39/proposal-temporal/issues/2540
3634 // Step 11.h.
3635 PlainDate isoResult;
3636 if (!AddISODate(cx, yearsMonthsLaterDate,
3637 {0, 0, 0, fractionalDays.truncate()},
3638 TemporalOverflow::Constrain, &isoResult)) {
3639 return false;
3642 // Step 11.i.
3643 Rooted<PlainDateObject*> wholeDaysLater(
3644 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3645 if (!wholeDaysLater) {
3646 return false;
3649 // Steps 11.j-l.
3650 Duration timePassed;
3651 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3652 TemporalUnit::Month, &timePassed)) {
3653 return false;
3656 // Step 11.m.
3657 int64_t monthsPassed = int64_t(timePassed.months);
3659 // Step 11.n.
3660 months += monthsPassed;
3662 // Step 11.o.
3663 Duration monthsPassedDuration = {0, double(monthsPassed)};
3665 // Steps 11.p-r.
3666 int32_t daysPassed;
3667 if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
3668 &newRelativeTo, &daysPassed)) {
3669 return false;
3671 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3673 // Step 11.s.
3674 fractionalDays -= daysPassed;
3676 // Steps 11.t.
3677 double sign = fractionalDays.sign() < 0 ? -1 : 1;
3679 // Step 11.u.
3680 Duration oneMonth = {0, sign};
3682 // Steps 11.v-w.
3683 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3684 int32_t oneMonthDays;
3685 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
3686 &moveResultIgnored, &oneMonthDays)) {
3687 return false;
3690 // Step 11.x.
3691 if (oneMonthDays == 0) {
3692 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3693 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3694 return false;
3697 // Step 11.y.
3698 auto fractionalMonths = Fraction{months, std::abs(oneMonthDays)};
3700 // Steps 11.z-aa.
3701 auto [numMonths, total] =
3702 RoundNumberToIncrement(fractionalMonths, fractionalDays, increment,
3703 roundingMode, computeRemainder);
3705 // Step 11.ab.
3706 int64_t numWeeks = 0;
3708 // Step 11.ac.
3709 constexpr auto time = NormalizedTimeDuration{};
3711 // Step 21.
3712 if (numMonths.abs() >= (Uint128{1} << 32)) {
3713 return ThrowInvalidDurationPart(cx, double(numMonths), "months",
3714 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3717 auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
3718 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3719 return false;
3722 *result = {{resultDuration, time}, total};
3723 return true;
3726 static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
3727 FractionalDays fractionalDays,
3728 Increment increment,
3729 TemporalRoundingMode roundingMode,
3730 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3731 Handle<CalendarRecord> calendar,
3732 ComputeRemainder computeRemainder,
3733 RoundedDuration* result) {
3734 // Numbers of days between nsMinInstant and nsMaxInstant.
3735 static constexpr int32_t epochDays = 200'000'000;
3737 auto [years, months, weeks, days] = duration.date;
3739 auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
3740 if (!unwrappedRelativeTo) {
3741 return false;
3743 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
3745 // Step 12.a
3746 PlainDate isoResult;
3747 if (!AddISODate(cx, relativeToDate, {0, 0, 0, fractionalDays.truncate()},
3748 TemporalOverflow::Constrain, &isoResult)) {
3749 return false;
3752 // Step 12.b.
3753 Rooted<PlainDateObject*> wholeDaysLater(
3754 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3755 if (!wholeDaysLater) {
3756 return false;
3759 // Steps 12.c-e.
3760 Duration timePassed;
3761 if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
3762 TemporalUnit::Week, &timePassed)) {
3763 return false;
3766 // Step 12.f.
3767 int64_t weeksPassed = int64_t(timePassed.weeks);
3769 // Step 12.g.
3770 weeks += weeksPassed;
3772 // Step 12.h.
3773 Duration weeksPassedDuration = {0, 0, double(weeksPassed)};
3775 // Steps 12.i-k.
3776 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
3777 int32_t daysPassed;
3778 if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
3779 &newRelativeTo, &daysPassed)) {
3780 return false;
3782 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3784 // Step 12.l.
3785 fractionalDays -= daysPassed;
3787 // Steps 12.m.
3788 double sign = fractionalDays.sign() < 0 ? -1 : 1;
3790 // Step 12.n.
3791 Duration oneWeek = {0, 0, sign};
3793 // Steps 12.o-p.
3794 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3795 int32_t oneWeekDays;
3796 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
3797 &moveResultIgnored, &oneWeekDays)) {
3798 return false;
3801 // Step 12.q.
3802 if (oneWeekDays == 0) {
3803 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3804 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3805 return false;
3808 // Step 12.r.
3809 auto fractionalWeeks = Fraction{weeks, std::abs(oneWeekDays)};
3811 // Steps 12.s-t.
3812 auto [numWeeks, total] =
3813 RoundNumberToIncrement(fractionalWeeks, fractionalDays, increment,
3814 roundingMode, computeRemainder);
3816 // Step 12.u.
3817 constexpr auto time = NormalizedTimeDuration{};
3819 // Step 20.
3820 if (numWeeks.abs() >= (Uint128{1} << 32)) {
3821 return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
3822 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3825 auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
3826 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3827 return false;
3830 *result = {{resultDuration, time}, total};
3831 return true;
3834 static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
3835 const FractionalDays& fractionalDays,
3836 Increment increment,
3837 TemporalRoundingMode roundingMode,
3838 ComputeRemainder computeRemainder,
3839 RoundedDuration* result) {
3840 auto [years, months, weeks, days] = duration.date;
3842 // Pass zero fraction.
3843 constexpr auto zero = Fraction{0, 1};
3845 // Steps 13.a-b.
3846 auto [numDays, total] = RoundNumberToIncrement(
3847 zero, fractionalDays, increment, roundingMode, computeRemainder);
3849 MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
3850 "rounded days fits in int64");
3852 // Step 13.c.
3853 constexpr auto time = NormalizedTimeDuration{};
3855 // Step 20.
3856 auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
3857 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3858 return false;
3861 *result = {{resultDuration, time}, total};
3862 return true;
3866 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3867 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3868 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3870 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
3871 Increment increment, TemporalUnit unit,
3872 TemporalRoundingMode roundingMode,
3873 ComputeRemainder computeRemainder,
3874 RoundedDuration* result) {
3875 // The remainder is only needed when called from |Duration_total|. And `total`
3876 // always passes |increment=1| and |roundingMode=trunc|.
3877 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3878 increment == Increment{1});
3879 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3880 roundingMode == TemporalRoundingMode::Trunc);
3882 // Steps 1-5. (Not applicable.)
3884 // Step 6.
3885 if (unit <= TemporalUnit::Week) {
3886 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3887 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3888 "relativeTo");
3889 return false;
3892 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3893 // because in that case this operation is a no-op. This case happens for
3894 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3895 // options object.
3897 // But maybe this can be even more efficiently handled in the callers. For
3898 // example when Temporal.PlainTime.prototype.{since,until} is called without
3899 // an options object, we can not only skip the RoundDuration call, but also
3900 // the following BalanceTimeDuration call.
3902 // Step 7. (Moved below.)
3904 // Steps 8-9. (Not applicable.)
3906 // Steps 10-12. (Not applicable.)
3908 // Step 13.
3909 if (unit == TemporalUnit::Day) {
3910 // Step 7.
3911 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3912 auto fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3914 return RoundDurationDay(cx, duration, fractionalDays, increment,
3915 roundingMode, computeRemainder, result);
3918 MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
3920 // Steps 14-19.
3921 auto time = duration.time;
3922 double total = 0;
3923 if (computeRemainder == ComputeRemainder::No) {
3924 time = RoundNormalizedTimeDurationToIncrement(time, unit, increment,
3925 roundingMode);
3926 } else {
3927 MOZ_ASSERT(increment == Increment{1});
3928 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
3930 total = TotalNormalizedTimeDuration(duration.time, unit);
3932 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
3934 // Step 20.
3935 MOZ_ASSERT(IsValidDuration(duration.date.toDuration()));
3936 *result = {{duration.date, time}, total};
3937 return true;
3941 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3942 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3943 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3945 static bool RoundDuration(
3946 JSContext* cx, const NormalizedDuration& duration, Increment increment,
3947 TemporalUnit unit, TemporalRoundingMode roundingMode,
3948 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3949 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
3950 Handle<TimeZoneRecord> timeZone,
3951 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
3952 ComputeRemainder computeRemainder, RoundedDuration* result) {
3953 // Note: |duration.days| can have a different sign than the other date
3954 // components. The date and time components can have different signs, too.
3955 MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
3956 double(duration.date.months),
3957 double(duration.date.weeks)}));
3958 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
3960 MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
3961 "Use RoundDuration without relativeTo when plainRelativeTo and "
3962 "zonedRelativeTo are both undefined");
3964 // The remainder is only needed when called from |Duration_total|. And `total`
3965 // always passes |increment=1| and |roundingMode=trunc|.
3966 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3967 increment == Increment{1});
3968 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3969 roundingMode == TemporalRoundingMode::Trunc);
3971 // Steps 1-5. (Not applicable in our implementation.)
3973 // Step 6.a. (Not applicable in our implementation.)
3974 MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
3976 // Step 6.b.
3977 MOZ_ASSERT_IF(
3978 unit <= TemporalUnit::Week,
3979 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3981 // Step 6.c.
3982 MOZ_ASSERT_IF(
3983 unit <= TemporalUnit::Week,
3984 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3986 switch (unit) {
3987 case TemporalUnit::Year:
3988 case TemporalUnit::Month:
3989 case TemporalUnit::Week:
3990 break;
3991 case TemporalUnit::Day:
3992 // We can't take the faster code path when |zonedRelativeTo| is present.
3993 if (zonedRelativeTo) {
3994 break;
3996 [[fallthrough]];
3997 case TemporalUnit::Hour:
3998 case TemporalUnit::Minute:
3999 case TemporalUnit::Second:
4000 case TemporalUnit::Millisecond:
4001 case TemporalUnit::Microsecond:
4002 case TemporalUnit::Nanosecond:
4003 // Steps 7-9 and 13-21.
4004 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4005 computeRemainder, result);
4006 case TemporalUnit::Auto:
4007 MOZ_CRASH("Unexpected temporal unit");
4010 // Step 7.
4011 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
4013 // Steps 7.a-c.
4014 FractionalDays fractionalDays;
4015 if (zonedRelativeTo) {
4016 // Step 7.a.i.
4017 Rooted<ZonedDateTime> intermediate(cx);
4018 if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
4019 duration.date, precalculatedPlainDateTime,
4020 &intermediate)) {
4021 return false;
4024 // Steps 7.a.ii.
4025 NormalizedTimeAndDays timeAndDays;
4026 if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
4027 &timeAndDays)) {
4028 return false;
4031 // Step 7.a.iii.
4032 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
4033 } else {
4034 // Step 7.b.
4035 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
4036 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
4039 // Step 7.c. (Moved below)
4041 // Step 8. (Not applicable)
4043 // Step 9.
4044 // FIXME: spec issue - `total` doesn't need be initialised.
4046 // Steps 10-20.
4047 switch (unit) {
4048 // Steps 10 and 20.
4049 case TemporalUnit::Year:
4050 return RoundDurationYear(cx, duration, fractionalDays, increment,
4051 roundingMode, plainRelativeTo, calendar,
4052 computeRemainder, result);
4054 // Steps 11 and 20.
4055 case TemporalUnit::Month:
4056 return RoundDurationMonth(cx, duration, fractionalDays, increment,
4057 roundingMode, plainRelativeTo, calendar,
4058 computeRemainder, result);
4060 // Steps 12 and 20.
4061 case TemporalUnit::Week:
4062 return RoundDurationWeek(cx, duration, fractionalDays, increment,
4063 roundingMode, plainRelativeTo, calendar,
4064 computeRemainder, result);
4066 // Steps 13 and 20.
4067 case TemporalUnit::Day:
4068 return RoundDurationDay(cx, duration, fractionalDays, increment,
4069 roundingMode, computeRemainder, result);
4071 // Steps 14-19. (Handled elsewhere)
4072 case TemporalUnit::Auto:
4073 case TemporalUnit::Hour:
4074 case TemporalUnit::Minute:
4075 case TemporalUnit::Second:
4076 case TemporalUnit::Millisecond:
4077 case TemporalUnit::Microsecond:
4078 case TemporalUnit::Nanosecond:
4079 break;
4082 MOZ_CRASH("Unexpected temporal unit");
4086 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4087 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4088 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4090 static bool RoundDuration(
4091 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4092 TemporalUnit unit, TemporalRoundingMode roundingMode,
4093 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4094 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4095 Handle<TimeZoneRecord> timeZone,
4096 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4097 double* result) {
4098 // Only called from |Duration_total|, which always passes |increment=1| and
4099 // |roundingMode=trunc|.
4100 MOZ_ASSERT(increment == Increment{1});
4101 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4103 RoundedDuration rounded;
4104 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4105 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4106 precalculatedPlainDateTime, ComputeRemainder::Yes,
4107 &rounded)) {
4108 return false;
4111 *result = rounded.total;
4112 return true;
4116 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4117 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4118 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4120 static bool RoundDuration(
4121 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4122 TemporalUnit unit, TemporalRoundingMode roundingMode,
4123 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4124 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4125 Handle<TimeZoneRecord> timeZone,
4126 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4127 NormalizedDuration* result) {
4128 RoundedDuration rounded;
4129 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4130 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4131 precalculatedPlainDateTime, ComputeRemainder::No,
4132 &rounded)) {
4133 return false;
4136 *result = rounded.duration;
4137 return true;
4141 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4142 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4143 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4145 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4146 Increment increment, TemporalUnit unit,
4147 TemporalRoundingMode roundingMode, double* result) {
4148 MOZ_ASSERT(IsValidDuration(duration));
4150 // Only called from |Duration_total|, which always passes |increment=1| and
4151 // |roundingMode=trunc|.
4152 MOZ_ASSERT(increment == Increment{1});
4153 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4155 RoundedDuration rounded;
4156 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4157 ComputeRemainder::Yes, &rounded)) {
4158 return false;
4161 *result = rounded.total;
4162 return true;
4166 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4167 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4168 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4170 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4171 Increment increment, TemporalUnit unit,
4172 TemporalRoundingMode roundingMode,
4173 NormalizedDuration* result) {
4174 MOZ_ASSERT(IsValidDuration(duration));
4176 RoundedDuration rounded;
4177 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4178 ComputeRemainder::No, &rounded)) {
4179 return false;
4182 *result = rounded.duration;
4183 return true;
4187 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4188 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4189 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4191 bool js::temporal::RoundDuration(
4192 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4193 TemporalUnit unit, TemporalRoundingMode roundingMode,
4194 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4195 Handle<CalendarRecord> calendar, NormalizedDuration* result) {
4196 MOZ_ASSERT(IsValidDuration(duration));
4198 Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
4199 Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
4200 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4201 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4202 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4203 precalculatedPlainDateTime, result);
4207 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4208 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4209 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4211 bool js::temporal::RoundDuration(
4212 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4213 TemporalUnit unit, TemporalRoundingMode roundingMode,
4214 Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
4215 Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
4216 const PlainDateTime& precalculatedPlainDateTime,
4217 NormalizedDuration* result) {
4218 MOZ_ASSERT(IsValidDuration(duration));
4220 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4221 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4222 mozilla::SomeRef(precalculatedPlainDateTime), result);
4225 enum class DurationOperation { Add, Subtract };
4228 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4229 * options )
4231 static bool AddDurationToOrSubtractDurationFromDuration(
4232 JSContext* cx, DurationOperation operation, const CallArgs& args) {
4233 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4234 auto duration = ToDuration(durationObj);
4236 // Step 1. (Not applicable in our implementation.)
4238 // Step 2.
4239 Duration other;
4240 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
4241 return false;
4244 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4245 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4246 Rooted<TimeZoneRecord> timeZone(cx);
4247 if (args.hasDefined(1)) {
4248 const char* name = operation == DurationOperation::Add ? "add" : "subtract";
4250 // Step 3.
4251 Rooted<JSObject*> options(cx,
4252 RequireObjectArg(cx, "options", name, args[1]));
4253 if (!options) {
4254 return false;
4257 // Steps 4-7.
4258 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4259 &zonedRelativeTo, &timeZone)) {
4260 return false;
4262 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4263 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4266 // Step 8.
4267 Rooted<CalendarRecord> calendar(cx);
4268 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4269 zonedRelativeTo,
4271 CalendarMethod::DateAdd,
4272 CalendarMethod::DateUntil,
4274 &calendar)) {
4275 return false;
4278 // Step 9.
4279 if (operation == DurationOperation::Subtract) {
4280 other = other.negate();
4283 Duration result;
4284 if (plainRelativeTo) {
4285 if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
4286 return false;
4288 } else if (zonedRelativeTo) {
4289 if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
4290 &result)) {
4291 return false;
4293 } else {
4294 if (!AddDuration(cx, duration, other, &result)) {
4295 return false;
4299 // Step 10.
4300 auto* obj = CreateTemporalDuration(cx, result);
4301 if (!obj) {
4302 return false;
4305 args.rval().setObject(*obj);
4306 return true;
4310 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4311 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4312 * ] ] ] ] ] ] )
4314 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
4315 CallArgs args = CallArgsFromVp(argc, vp);
4317 // Step 1.
4318 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
4319 return false;
4322 // Step 2.
4323 double years = 0;
4324 if (args.hasDefined(0) &&
4325 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
4326 return false;
4329 // Step 3.
4330 double months = 0;
4331 if (args.hasDefined(1) &&
4332 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
4333 return false;
4336 // Step 4.
4337 double weeks = 0;
4338 if (args.hasDefined(2) &&
4339 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
4340 return false;
4343 // Step 5.
4344 double days = 0;
4345 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
4346 return false;
4349 // Step 6.
4350 double hours = 0;
4351 if (args.hasDefined(4) &&
4352 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
4353 return false;
4356 // Step 7.
4357 double minutes = 0;
4358 if (args.hasDefined(5) &&
4359 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
4360 return false;
4363 // Step 8.
4364 double seconds = 0;
4365 if (args.hasDefined(6) &&
4366 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
4367 return false;
4370 // Step 9.
4371 double milliseconds = 0;
4372 if (args.hasDefined(7) &&
4373 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
4374 return false;
4377 // Step 10.
4378 double microseconds = 0;
4379 if (args.hasDefined(8) &&
4380 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
4381 return false;
4384 // Step 11.
4385 double nanoseconds = 0;
4386 if (args.hasDefined(9) &&
4387 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
4388 return false;
4391 // Step 12.
4392 auto* duration = CreateTemporalDuration(
4393 cx, args,
4394 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
4395 microseconds, nanoseconds});
4396 if (!duration) {
4397 return false;
4400 args.rval().setObject(*duration);
4401 return true;
4405 * Temporal.Duration.from ( item )
4407 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
4408 CallArgs args = CallArgsFromVp(argc, vp);
4410 Handle<Value> item = args.get(0);
4412 // Step 1.
4413 if (item.isObject()) {
4414 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
4415 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
4416 if (!result) {
4417 return false;
4420 args.rval().setObject(*result);
4421 return true;
4425 // Step 2.
4426 auto result = ToTemporalDuration(cx, item);
4427 if (!result) {
4428 return false;
4431 args.rval().setObject(*result);
4432 return true;
4436 * Temporal.Duration.compare ( one, two [ , options ] )
4438 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
4439 CallArgs args = CallArgsFromVp(argc, vp);
4441 // Step 1.
4442 Duration one;
4443 if (!ToTemporalDuration(cx, args.get(0), &one)) {
4444 return false;
4447 // Step 2.
4448 Duration two;
4449 if (!ToTemporalDuration(cx, args.get(1), &two)) {
4450 return false;
4453 // Step 3.
4454 Rooted<JSObject*> options(cx);
4455 if (args.hasDefined(2)) {
4456 options = RequireObjectArg(cx, "options", "compare", args[2]);
4457 if (!options) {
4458 return false;
4462 // Step 4.
4463 if (one == two) {
4464 args.rval().setInt32(0);
4465 return true;
4468 // Steps 5-8.
4469 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4470 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4471 Rooted<TimeZoneRecord> timeZone(cx);
4472 if (options) {
4473 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4474 &zonedRelativeTo, &timeZone)) {
4475 return false;
4477 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4478 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4481 // Steps 9-10.
4482 auto hasCalendarUnit = [](const auto& d) {
4483 return d.years != 0 || d.months != 0 || d.weeks != 0;
4485 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
4487 // Step 11.
4488 Rooted<CalendarRecord> calendar(cx);
4489 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4490 zonedRelativeTo,
4492 CalendarMethod::DateAdd,
4494 &calendar)) {
4495 return false;
4498 // Step 12.
4499 if (zonedRelativeTo &&
4500 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
4501 // Step 12.a.
4502 const auto& instant = zonedRelativeTo.instant();
4504 // Step 12.b.
4505 PlainDateTime dateTime;
4506 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
4507 return false;
4510 // Step 12.c.
4511 auto normalized1 = CreateNormalizedDurationRecord(one);
4513 // Step 12.d.
4514 auto normalized2 = CreateNormalizedDurationRecord(two);
4516 // Step 12.e.
4517 Instant after1;
4518 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
4519 dateTime, &after1)) {
4520 return false;
4523 // Step 12.f.
4524 Instant after2;
4525 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
4526 dateTime, &after2)) {
4527 return false;
4530 // Steps 12.g-i.
4531 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
4532 return true;
4535 // Steps 13-14.
4536 double days1, days2;
4537 if (calendarUnitsPresent) {
4538 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4540 // Step 13.a.
4541 DateDuration unbalanceResult1;
4542 if (plainRelativeTo) {
4543 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
4544 TemporalUnit::Day, plainRelativeTo,
4545 calendar, &unbalanceResult1)) {
4546 return false;
4548 } else {
4549 if (!UnbalanceDateDurationRelative(
4550 cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
4551 return false;
4553 MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
4556 // Step 13.b.
4557 DateDuration unbalanceResult2;
4558 if (plainRelativeTo) {
4559 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
4560 TemporalUnit::Day, plainRelativeTo,
4561 calendar, &unbalanceResult2)) {
4562 return false;
4564 } else {
4565 if (!UnbalanceDateDurationRelative(
4566 cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
4567 return false;
4569 MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
4572 // Step 13.c.
4573 days1 = unbalanceResult1.days;
4575 // Step 13.d.
4576 days2 = unbalanceResult2.days;
4577 } else {
4578 // Step 14.a.
4579 days1 = one.days;
4581 // Step 14.b.
4582 days2 = two.days;
4585 // Step 15.
4586 auto normalized1 = NormalizeTimeDuration(one);
4588 // Step 16.
4589 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
4590 &normalized1)) {
4591 return false;
4594 // Step 17.
4595 auto normalized2 = NormalizeTimeDuration(two);
4597 // Step 18.
4598 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
4599 &normalized2)) {
4600 return false;
4603 // Step 19.
4604 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
4605 return true;
4609 * get Temporal.Duration.prototype.years
4611 static bool Duration_years(JSContext* cx, const CallArgs& args) {
4612 // Step 3.
4613 auto* duration = &args.thisv().toObject().as<DurationObject>();
4614 args.rval().setNumber(duration->years());
4615 return true;
4619 * get Temporal.Duration.prototype.years
4621 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
4622 // Steps 1-2.
4623 CallArgs args = CallArgsFromVp(argc, vp);
4624 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
4628 * get Temporal.Duration.prototype.months
4630 static bool Duration_months(JSContext* cx, const CallArgs& args) {
4631 // Step 3.
4632 auto* duration = &args.thisv().toObject().as<DurationObject>();
4633 args.rval().setNumber(duration->months());
4634 return true;
4638 * get Temporal.Duration.prototype.months
4640 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
4641 // Steps 1-2.
4642 CallArgs args = CallArgsFromVp(argc, vp);
4643 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
4647 * get Temporal.Duration.prototype.weeks
4649 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
4650 // Step 3.
4651 auto* duration = &args.thisv().toObject().as<DurationObject>();
4652 args.rval().setNumber(duration->weeks());
4653 return true;
4657 * get Temporal.Duration.prototype.weeks
4659 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
4660 // Steps 1-2.
4661 CallArgs args = CallArgsFromVp(argc, vp);
4662 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
4666 * get Temporal.Duration.prototype.days
4668 static bool Duration_days(JSContext* cx, const CallArgs& args) {
4669 // Step 3.
4670 auto* duration = &args.thisv().toObject().as<DurationObject>();
4671 args.rval().setNumber(duration->days());
4672 return true;
4676 * get Temporal.Duration.prototype.days
4678 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
4679 // Steps 1-2.
4680 CallArgs args = CallArgsFromVp(argc, vp);
4681 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
4685 * get Temporal.Duration.prototype.hours
4687 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
4688 // Step 3.
4689 auto* duration = &args.thisv().toObject().as<DurationObject>();
4690 args.rval().setNumber(duration->hours());
4691 return true;
4695 * get Temporal.Duration.prototype.hours
4697 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
4698 // Steps 1-2.
4699 CallArgs args = CallArgsFromVp(argc, vp);
4700 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
4704 * get Temporal.Duration.prototype.minutes
4706 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
4707 // Step 3.
4708 auto* duration = &args.thisv().toObject().as<DurationObject>();
4709 args.rval().setNumber(duration->minutes());
4710 return true;
4714 * get Temporal.Duration.prototype.minutes
4716 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
4717 // Steps 1-2.
4718 CallArgs args = CallArgsFromVp(argc, vp);
4719 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
4723 * get Temporal.Duration.prototype.seconds
4725 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
4726 // Step 3.
4727 auto* duration = &args.thisv().toObject().as<DurationObject>();
4728 args.rval().setNumber(duration->seconds());
4729 return true;
4733 * get Temporal.Duration.prototype.seconds
4735 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
4736 // Steps 1-2.
4737 CallArgs args = CallArgsFromVp(argc, vp);
4738 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
4742 * get Temporal.Duration.prototype.milliseconds
4744 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
4745 // Step 3.
4746 auto* duration = &args.thisv().toObject().as<DurationObject>();
4747 args.rval().setNumber(duration->milliseconds());
4748 return true;
4752 * get Temporal.Duration.prototype.milliseconds
4754 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
4755 // Steps 1-2.
4756 CallArgs args = CallArgsFromVp(argc, vp);
4757 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
4761 * get Temporal.Duration.prototype.microseconds
4763 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
4764 // Step 3.
4765 auto* duration = &args.thisv().toObject().as<DurationObject>();
4766 args.rval().setNumber(duration->microseconds());
4767 return true;
4771 * get Temporal.Duration.prototype.microseconds
4773 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
4774 // Steps 1-2.
4775 CallArgs args = CallArgsFromVp(argc, vp);
4776 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
4780 * get Temporal.Duration.prototype.nanoseconds
4782 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
4783 // Step 3.
4784 auto* duration = &args.thisv().toObject().as<DurationObject>();
4785 args.rval().setNumber(duration->nanoseconds());
4786 return true;
4790 * get Temporal.Duration.prototype.nanoseconds
4792 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
4793 // Steps 1-2.
4794 CallArgs args = CallArgsFromVp(argc, vp);
4795 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
4799 * get Temporal.Duration.prototype.sign
4801 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
4802 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4804 // Step 3.
4805 args.rval().setInt32(DurationSign(duration));
4806 return true;
4810 * get Temporal.Duration.prototype.sign
4812 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
4813 // Steps 1-2.
4814 CallArgs args = CallArgsFromVp(argc, vp);
4815 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
4819 * get Temporal.Duration.prototype.blank
4821 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
4822 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4824 // Steps 3-5.
4825 args.rval().setBoolean(duration == Duration{});
4826 return true;
4830 * get Temporal.Duration.prototype.blank
4832 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
4833 // Steps 1-2.
4834 CallArgs args = CallArgsFromVp(argc, vp);
4835 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
4839 * Temporal.Duration.prototype.with ( temporalDurationLike )
4841 * ToPartialDuration ( temporalDurationLike )
4843 static bool Duration_with(JSContext* cx, const CallArgs& args) {
4844 // Absent values default to the corresponding values of |this| object.
4845 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4847 // Steps 3-23.
4848 Rooted<JSObject*> temporalDurationLike(
4849 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
4850 if (!temporalDurationLike) {
4851 return false;
4853 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
4854 return false;
4857 // Step 24.
4858 auto* result = CreateTemporalDuration(cx, duration);
4859 if (!result) {
4860 return false;
4863 args.rval().setObject(*result);
4864 return true;
4868 * Temporal.Duration.prototype.with ( temporalDurationLike )
4870 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
4871 // Steps 1-2.
4872 CallArgs args = CallArgsFromVp(argc, vp);
4873 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
4877 * Temporal.Duration.prototype.negated ( )
4879 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
4880 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4882 // Step 3.
4883 auto* result = CreateTemporalDuration(cx, duration.negate());
4884 if (!result) {
4885 return false;
4888 args.rval().setObject(*result);
4889 return true;
4893 * Temporal.Duration.prototype.negated ( )
4895 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
4896 // Steps 1-2.
4897 CallArgs args = CallArgsFromVp(argc, vp);
4898 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
4902 * Temporal.Duration.prototype.abs ( )
4904 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
4905 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4907 // Step 3.
4908 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
4909 if (!result) {
4910 return false;
4913 args.rval().setObject(*result);
4914 return true;
4918 * Temporal.Duration.prototype.abs ( )
4920 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
4921 // Steps 1-2.
4922 CallArgs args = CallArgsFromVp(argc, vp);
4923 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
4927 * Temporal.Duration.prototype.add ( other [ , options ] )
4929 static bool Duration_add(JSContext* cx, const CallArgs& args) {
4930 return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
4931 args);
4935 * Temporal.Duration.prototype.add ( other [ , options ] )
4937 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
4938 // Steps 1-2.
4939 CallArgs args = CallArgsFromVp(argc, vp);
4940 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
4944 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4946 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
4947 return AddDurationToOrSubtractDurationFromDuration(
4948 cx, DurationOperation::Subtract, args);
4952 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4954 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
4955 // Steps 1-2.
4956 CallArgs args = CallArgsFromVp(argc, vp);
4957 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
4961 * Temporal.Duration.prototype.round ( roundTo )
4963 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4964 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4966 // Step 18. (Reordered)
4967 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4969 // Steps 3-25.
4970 auto smallestUnit = TemporalUnit::Auto;
4971 TemporalUnit largestUnit;
4972 auto roundingMode = TemporalRoundingMode::HalfExpand;
4973 auto roundingIncrement = Increment{1};
4974 Rooted<JSObject*> relativeTo(cx);
4975 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4976 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4977 Rooted<TimeZoneRecord> timeZone(cx);
4978 if (args.get(0).isString()) {
4979 // Step 4. (Not applicable in our implementation.)
4981 // Steps 6-15. (Not applicable)
4983 // Step 16.
4984 Rooted<JSString*> paramString(cx, args[0].toString());
4985 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
4986 TemporalUnitGroup::DateTime, &smallestUnit)) {
4987 return false;
4990 // Step 17. (Not applicable)
4992 // Step 18. (Moved above)
4994 // Step 19.
4995 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4997 // Step 20. (Not applicable)
4999 // Step 20.a. (Not applicable)
5001 // Step 20.b.
5002 largestUnit = defaultLargestUnit;
5004 // Steps 21-25. (Not applicable)
5005 } else {
5006 // Steps 3 and 5.
5007 Rooted<JSObject*> options(
5008 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
5009 if (!options) {
5010 return false;
5013 // Step 6.
5014 bool smallestUnitPresent = true;
5016 // Step 7.
5017 bool largestUnitPresent = true;
5019 // Steps 8-9.
5021 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
5022 // absent "largestUnit" value.
5023 Rooted<Value> largestUnitValue(cx);
5024 if (!GetProperty(cx, options, options, cx->names().largestUnit,
5025 &largestUnitValue)) {
5026 return false;
5029 if (!largestUnitValue.isUndefined()) {
5030 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
5031 if (!largestUnitStr) {
5032 return false;
5035 largestUnit = TemporalUnit::Auto;
5036 if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
5037 TemporalUnitGroup::DateTime, &largestUnit)) {
5038 return false;
5042 // Steps 10-13.
5043 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
5044 &zonedRelativeTo, &timeZone)) {
5045 return false;
5047 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5048 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5050 // Step 14.
5051 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
5052 return false;
5055 // Step 15.
5056 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5057 return false;
5060 // Step 16.
5061 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5062 TemporalUnitGroup::DateTime, &smallestUnit)) {
5063 return false;
5066 // Step 17.
5067 if (smallestUnit == TemporalUnit::Auto) {
5068 // Step 17.a.
5069 smallestUnitPresent = false;
5071 // Step 17.b.
5072 smallestUnit = TemporalUnit::Nanosecond;
5075 // Step 18. (Moved above)
5077 // Step 19.
5078 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5080 // Steps 20-21.
5081 if (largestUnitValue.isUndefined()) {
5082 // Step 20.a.
5083 largestUnitPresent = false;
5085 // Step 20.b.
5086 largestUnit = defaultLargestUnit;
5087 } else if (largestUnit == TemporalUnit::Auto) {
5088 // Step 21.a
5089 largestUnit = defaultLargestUnit;
5092 // Step 22.
5093 if (!smallestUnitPresent && !largestUnitPresent) {
5094 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5095 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
5096 return false;
5099 // Step 23.
5100 if (largestUnit > smallestUnit) {
5101 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5102 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
5103 return false;
5106 // Steps 24-25.
5107 if (smallestUnit > TemporalUnit::Day) {
5108 // Step 24.
5109 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
5111 // Step 25.
5112 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
5113 false)) {
5114 return false;
5119 // Step 26.
5120 bool hoursToDaysConversionMayOccur = false;
5122 // Step 27.
5123 if (duration.days != 0 && zonedRelativeTo) {
5124 hoursToDaysConversionMayOccur = true;
5127 // Step 28.
5128 else if (std::abs(duration.hours) >= 24) {
5129 hoursToDaysConversionMayOccur = true;
5132 // Step 29.
5133 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
5134 roundingIncrement == Increment{1};
5136 // Step 30.
5137 bool calendarUnitsPresent =
5138 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
5140 // Step 31.
5141 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
5142 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
5143 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
5144 std::abs(duration.milliseconds) < 1000 &&
5145 std::abs(duration.microseconds) < 1000 &&
5146 std::abs(duration.nanoseconds) < 1000) {
5147 // Steps 31.a-b.
5148 auto* obj = CreateTemporalDuration(cx, duration);
5149 if (!obj) {
5150 return false;
5153 args.rval().setObject(*obj);
5154 return true;
5157 // Step 32.
5158 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5160 // Step 33.
5161 bool plainDateTimeOrRelativeToWillBeUsed =
5162 !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
5163 calendarUnitsPresent || duration.days != 0;
5165 // Step 34.
5166 PlainDateTime relativeToDateTime;
5167 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5168 // Steps 34.a-b.
5169 const auto& instant = zonedRelativeTo.instant();
5171 // Step 34.c.
5172 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5173 return false;
5175 precalculatedPlainDateTime =
5176 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5178 // Step 34.d.
5179 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5180 zonedRelativeTo.calendar());
5181 if (!plainRelativeTo) {
5182 return false;
5186 // Step 35.
5187 Rooted<CalendarRecord> calendar(cx);
5188 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5189 zonedRelativeTo,
5191 CalendarMethod::DateAdd,
5192 CalendarMethod::DateUntil,
5194 &calendar)) {
5195 return false;
5198 // Step 36.
5199 DateDuration unbalanceResult;
5200 if (plainRelativeTo) {
5201 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5202 largestUnit, plainRelativeTo, calendar,
5203 &unbalanceResult)) {
5204 return false;
5206 } else {
5207 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5208 largestUnit, &unbalanceResult)) {
5209 return false;
5211 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5214 // Steps 37-39.
5215 auto roundInput =
5216 NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
5217 NormalizedDuration roundResult;
5218 if (plainRelativeTo || zonedRelativeTo) {
5219 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5220 roundingMode, plainRelativeTo, calendar,
5221 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5222 &roundResult)) {
5223 return false;
5225 } else {
5226 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5227 roundingMode, &roundResult)) {
5228 return false;
5232 // Steps 40-41.
5233 TimeDuration balanceResult;
5234 if (zonedRelativeTo) {
5235 // Step 40.a.
5236 NormalizedDuration adjustResult;
5237 if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
5238 smallestUnit, roundingMode, zonedRelativeTo,
5239 calendar, timeZone,
5240 precalculatedPlainDateTime, &adjustResult)) {
5241 return false;
5243 roundResult = adjustResult;
5245 // Step 40.b.
5246 if (!BalanceTimeDurationRelative(
5247 cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
5248 precalculatedPlainDateTime, &balanceResult)) {
5249 return false;
5251 } else {
5252 // Step 41.a.
5253 NormalizedTimeDuration withDays;
5254 if (!Add24HourDaysToNormalizedTimeDuration(
5255 cx, roundResult.time, roundResult.date.days, &withDays)) {
5256 return false;
5259 // Step 41.b.
5260 balanceResult = temporal::BalanceTimeDuration(withDays, largestUnit);
5263 // Step 41.
5264 auto balanceInput = DateDuration{
5265 roundResult.date.years,
5266 roundResult.date.months,
5267 roundResult.date.weeks,
5268 balanceResult.days,
5270 DateDuration dateResult;
5271 if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
5272 smallestUnit, plainRelativeTo, calendar,
5273 &dateResult)) {
5274 return false;
5277 // Step 42.
5278 auto result = Duration{
5279 double(dateResult.years), double(dateResult.months),
5280 double(dateResult.weeks), double(dateResult.days),
5281 double(balanceResult.hours), double(balanceResult.minutes),
5282 double(balanceResult.seconds), double(balanceResult.milliseconds),
5283 balanceResult.microseconds, balanceResult.nanoseconds,
5285 MOZ_ASSERT(IsValidDuration(result));
5286 auto* obj = CreateTemporalDuration(cx, result);
5287 if (!obj) {
5288 return false;
5291 args.rval().setObject(*obj);
5292 return true;
5296 * Temporal.Duration.prototype.round ( options )
5298 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
5299 // Steps 1-2.
5300 CallArgs args = CallArgsFromVp(argc, vp);
5301 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
5305 * Temporal.Duration.prototype.total ( totalOf )
5307 static bool Duration_total(JSContext* cx, const CallArgs& args) {
5308 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
5309 auto duration = ToDuration(durationObj);
5311 // Steps 3-11.
5312 Rooted<JSObject*> relativeTo(cx);
5313 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5314 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5315 Rooted<TimeZoneRecord> timeZone(cx);
5316 auto unit = TemporalUnit::Auto;
5317 if (args.get(0).isString()) {
5318 // Step 4. (Not applicable in our implementation.)
5320 // Steps 6-10. (Implicit)
5321 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
5323 // Step 11.
5324 Rooted<JSString*> paramString(cx, args[0].toString());
5325 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
5326 TemporalUnitGroup::DateTime, &unit)) {
5327 return false;
5329 } else {
5330 // Steps 3 and 5.
5331 Rooted<JSObject*> totalOf(
5332 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
5333 if (!totalOf) {
5334 return false;
5337 // Steps 6-10.
5338 if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
5339 &zonedRelativeTo, &timeZone)) {
5340 return false;
5342 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5343 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5345 // Step 11.
5346 if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
5347 TemporalUnitGroup::DateTime, &unit)) {
5348 return false;
5351 if (unit == TemporalUnit::Auto) {
5352 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5353 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
5354 return false;
5358 // Step 12.
5359 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5361 // Step 13.
5362 bool plainDateTimeOrRelativeToWillBeUsed =
5363 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
5365 // Step 14.
5366 PlainDateTime relativeToDateTime;
5367 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5368 // Steps 14.a-b.
5369 const auto& instant = zonedRelativeTo.instant();
5371 // Step 14.c.
5372 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5373 return false;
5375 precalculatedPlainDateTime =
5376 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5378 // Step 14.d
5379 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5380 zonedRelativeTo.calendar());
5381 if (!plainRelativeTo) {
5382 return false;
5386 // Step 15.
5387 Rooted<CalendarRecord> calendar(cx);
5388 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5389 zonedRelativeTo,
5391 CalendarMethod::DateAdd,
5392 CalendarMethod::DateUntil,
5394 &calendar)) {
5395 return false;
5398 // Step 16.
5399 DateDuration unbalanceResult;
5400 if (plainRelativeTo) {
5401 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5402 plainRelativeTo, calendar,
5403 &unbalanceResult)) {
5404 return false;
5406 } else {
5407 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5408 &unbalanceResult)) {
5409 return false;
5411 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5414 // Step 17.
5415 int64_t unbalancedDays = unbalanceResult.days;
5417 // Steps 18-19.
5418 int64_t days;
5419 NormalizedTimeDuration balanceResult;
5420 if (zonedRelativeTo) {
5421 // Step 18.a
5422 Rooted<ZonedDateTime> intermediate(cx);
5423 if (!MoveRelativeZonedDateTime(
5424 cx, zonedRelativeTo, calendar, timeZone,
5425 {unbalanceResult.years, unbalanceResult.months,
5426 unbalanceResult.weeks, 0},
5427 precalculatedPlainDateTime, &intermediate)) {
5428 return false;
5431 // Step 18.b.
5432 auto timeDuration = NormalizeTimeDuration(duration);
5434 // Step 18.c
5435 const auto& startNs = intermediate.instant();
5437 // Step 18.d.
5438 const auto& startInstant = startNs;
5440 // Step 18.e.
5441 mozilla::Maybe<PlainDateTime> startDateTime{};
5443 // Steps 18.f-g.
5444 Instant intermediateNs;
5445 if (unbalancedDays != 0) {
5446 // Step 18.f.i.
5447 PlainDateTime dateTime;
5448 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5449 return false;
5451 startDateTime = mozilla::Some(dateTime);
5453 // Step 18.f.ii.
5454 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
5455 Instant addResult;
5456 if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
5457 isoCalendar, unbalancedDays, &addResult)) {
5458 return false;
5461 // Step 18.f.iii.
5462 intermediateNs = addResult;
5463 } else {
5464 // Step 18.g.
5465 intermediateNs = startNs;
5468 // Step 18.h.
5469 Instant endNs;
5470 if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
5471 return false;
5474 // Step 18.i.
5475 auto difference =
5476 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
5478 // Steps 18.j-k.
5480 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5481 if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
5482 difference != NormalizedTimeDuration{}) {
5483 // Step 18.j.i.
5484 if (!startDateTime) {
5485 PlainDateTime dateTime;
5486 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5487 return false;
5489 startDateTime = mozilla::Some(dateTime);
5492 // Step 18.j.ii.
5493 NormalizedTimeAndDays timeAndDays;
5494 if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
5495 *startDateTime, &timeAndDays)) {
5496 return false;
5499 // Step 18.j.iii.
5500 balanceResult = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
5502 // Step 18.j.iv.
5503 days = timeAndDays.days;
5504 } else {
5505 // Step 18.k.i.
5506 balanceResult = difference;
5507 days = 0;
5509 } else {
5510 // Step 19.a.
5511 auto timeDuration = NormalizeTimeDuration(duration);
5513 // Step 19.b.
5514 if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
5515 &balanceResult)) {
5516 return false;
5519 // Step 19.c.
5520 days = 0;
5522 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult));
5524 // Step 20.
5525 auto roundInput = NormalizedDuration{
5527 unbalanceResult.years,
5528 unbalanceResult.months,
5529 unbalanceResult.weeks,
5530 days,
5532 balanceResult,
5534 double total;
5535 if (plainRelativeTo || zonedRelativeTo) {
5536 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5537 TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
5538 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5539 &total)) {
5540 return false;
5542 } else {
5543 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5544 TemporalRoundingMode::Trunc, &total)) {
5545 return false;
5549 // Step 21.
5550 args.rval().setNumber(total);
5551 return true;
5555 * Temporal.Duration.prototype.total ( totalOf )
5557 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
5558 // Steps 1-2.
5559 CallArgs args = CallArgsFromVp(argc, vp);
5560 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
5564 * Temporal.Duration.prototype.toString ( [ options ] )
5566 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
5567 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5569 // Steps 3-9.
5570 SecondsStringPrecision precision = {Precision::Auto(),
5571 TemporalUnit::Nanosecond, Increment{1}};
5572 auto roundingMode = TemporalRoundingMode::Trunc;
5573 if (args.hasDefined(0)) {
5574 // Step 3.
5575 Rooted<JSObject*> options(
5576 cx, RequireObjectArg(cx, "options", "toString", args[0]));
5577 if (!options) {
5578 return false;
5581 // Steps 4-5.
5582 auto digits = Precision::Auto();
5583 if (!ToFractionalSecondDigits(cx, options, &digits)) {
5584 return false;
5587 // Step 6.
5588 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5589 return false;
5592 // Step 7.
5593 auto smallestUnit = TemporalUnit::Auto;
5594 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5595 TemporalUnitGroup::Time, &smallestUnit)) {
5596 return false;
5599 // Step 8.
5600 if (smallestUnit == TemporalUnit::Hour ||
5601 smallestUnit == TemporalUnit::Minute) {
5602 const char* smallestUnitStr =
5603 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
5604 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5605 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
5606 smallestUnitStr, "smallestUnit");
5607 return false;
5610 // Step 9.
5611 precision = ToSecondsStringPrecision(smallestUnit, digits);
5614 // Steps 10-11.
5615 Duration result;
5616 if (precision.unit != TemporalUnit::Nanosecond ||
5617 precision.increment != Increment{1}) {
5618 // Step 10.a.
5619 auto timeDuration = NormalizeTimeDuration(duration);
5621 // Step 10.b.
5622 auto largestUnit = DefaultTemporalLargestUnit(duration);
5624 // Steps 10.c-d.
5625 auto rounded = RoundDuration(timeDuration, precision.increment,
5626 precision.unit, roundingMode);
5628 // Step 10.e.
5629 auto balanced = BalanceTimeDuration(
5630 rounded, std::min(largestUnit, TemporalUnit::Second));
5632 // Step 10.f.
5633 result = {
5634 duration.years, duration.months,
5635 duration.weeks, duration.days + double(balanced.days),
5636 double(balanced.hours), double(balanced.minutes),
5637 double(balanced.seconds), double(balanced.milliseconds),
5638 balanced.microseconds, balanced.nanoseconds,
5640 MOZ_ASSERT(IsValidDuration(duration));
5641 } else {
5642 // Step 11.
5643 result = duration;
5646 // Steps 12-13.
5647 JSString* str = TemporalDurationToString(cx, result, precision.precision);
5648 if (!str) {
5649 return false;
5652 args.rval().setString(str);
5653 return true;
5657 * Temporal.Duration.prototype.toString ( [ options ] )
5659 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
5660 // Steps 1-2.
5661 CallArgs args = CallArgsFromVp(argc, vp);
5662 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
5666 * Temporal.Duration.prototype.toJSON ( )
5668 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
5669 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5671 // Steps 3-4.
5672 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5673 if (!str) {
5674 return false;
5677 args.rval().setString(str);
5678 return true;
5682 * Temporal.Duration.prototype.toJSON ( )
5684 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
5685 // Steps 1-2.
5686 CallArgs args = CallArgsFromVp(argc, vp);
5687 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
5691 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5693 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
5694 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5696 // Steps 3-4.
5697 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5698 if (!str) {
5699 return false;
5702 args.rval().setString(str);
5703 return true;
5707 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5709 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
5710 // Steps 1-2.
5711 CallArgs args = CallArgsFromVp(argc, vp);
5712 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
5716 * Temporal.Duration.prototype.valueOf ( )
5718 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
5719 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
5720 "Duration", "primitive type");
5721 return false;
5724 const JSClass DurationObject::class_ = {
5725 "Temporal.Duration",
5726 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
5727 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
5728 JS_NULL_CLASS_OPS,
5729 &DurationObject::classSpec_,
5732 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
5734 static const JSFunctionSpec Duration_methods[] = {
5735 JS_FN("from", Duration_from, 1, 0),
5736 JS_FN("compare", Duration_compare, 2, 0),
5737 JS_FS_END,
5740 static const JSFunctionSpec Duration_prototype_methods[] = {
5741 JS_FN("with", Duration_with, 1, 0),
5742 JS_FN("negated", Duration_negated, 0, 0),
5743 JS_FN("abs", Duration_abs, 0, 0),
5744 JS_FN("add", Duration_add, 1, 0),
5745 JS_FN("subtract", Duration_subtract, 1, 0),
5746 JS_FN("round", Duration_round, 1, 0),
5747 JS_FN("total", Duration_total, 1, 0),
5748 JS_FN("toString", Duration_toString, 0, 0),
5749 JS_FN("toJSON", Duration_toJSON, 0, 0),
5750 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
5751 JS_FN("valueOf", Duration_valueOf, 0, 0),
5752 JS_FS_END,
5755 static const JSPropertySpec Duration_prototype_properties[] = {
5756 JS_PSG("years", Duration_years, 0),
5757 JS_PSG("months", Duration_months, 0),
5758 JS_PSG("weeks", Duration_weeks, 0),
5759 JS_PSG("days", Duration_days, 0),
5760 JS_PSG("hours", Duration_hours, 0),
5761 JS_PSG("minutes", Duration_minutes, 0),
5762 JS_PSG("seconds", Duration_seconds, 0),
5763 JS_PSG("milliseconds", Duration_milliseconds, 0),
5764 JS_PSG("microseconds", Duration_microseconds, 0),
5765 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
5766 JS_PSG("sign", Duration_sign, 0),
5767 JS_PSG("blank", Duration_blank, 0),
5768 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
5769 JS_PS_END,
5772 const ClassSpec DurationObject::classSpec_ = {
5773 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
5774 GenericCreatePrototype<DurationObject>,
5775 Duration_methods,
5776 nullptr,
5777 Duration_prototype_methods,
5778 Duration_prototype_properties,
5779 nullptr,
5780 ClassSpec::DontDefineConstructor,