Bug 1874684 - Part 10: Replace BigInt with Int128 in RoundNumberToIncrement. r=mgaudet
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blob272c8031e94b57638bf707327ee56789422fba61
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/BigIntType.h"
60 #include "vm/BytecodeUtil.h"
61 #include "vm/GlobalObject.h"
62 #include "vm/JSAtomState.h"
63 #include "vm/JSContext.h"
64 #include "vm/JSObject.h"
65 #include "vm/ObjectOperations.h"
66 #include "vm/PlainObject.h"
67 #include "vm/StringType.h"
69 #include "vm/JSObject-inl.h"
70 #include "vm/NativeObject-inl.h"
71 #include "vm/ObjectOperations-inl.h"
73 using namespace js;
74 using namespace js::temporal;
76 static inline bool IsDuration(Handle<Value> v) {
77 return v.isObject() && v.toObject().is<DurationObject>();
80 #ifdef DEBUG
81 static bool IsIntegerOrInfinity(double d) {
82 return IsInteger(d) || std::isinf(d);
85 static bool IsIntegerOrInfinityDuration(const Duration& duration) {
86 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
87 microseconds, nanoseconds] = duration;
89 // Integers exceeding the Number range are represented as infinity.
91 return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
92 IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
93 IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
94 IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
95 IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
98 static bool IsIntegerDuration(const Duration& duration) {
99 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
100 microseconds, nanoseconds] = duration;
102 return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
103 IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
104 IsInteger(seconds) && IsInteger(milliseconds) &&
105 IsInteger(microseconds) && IsInteger(nanoseconds);
107 #endif
109 static constexpr bool IsSafeInteger(int64_t x) {
110 constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
111 constexpr int64_t MinSafeInteger = -MaxSafeInteger;
112 return MinSafeInteger < x && x < MaxSafeInteger;
115 static constexpr bool IsSafeInteger(const Int128& x) {
116 constexpr Int128 MaxSafeInteger = Int128{int64_t(1) << 53};
117 constexpr Int128 MinSafeInteger = -MaxSafeInteger;
118 return MinSafeInteger < x && x < MaxSafeInteger;
122 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
123 * milliseconds, microseconds, nanoseconds )
125 int32_t js::temporal::DurationSign(const Duration& duration) {
126 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
128 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
129 microseconds, nanoseconds] = duration;
131 // Step 1.
132 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
133 milliseconds, microseconds, nanoseconds}) {
134 // Step 1.a.
135 if (v < 0) {
136 return -1;
139 // Step 1.b.
140 if (v > 0) {
141 return 1;
145 // Step 2.
146 return 0;
150 * Normalize a nanoseconds amount into a time duration.
152 static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
153 // Split into seconds and nanoseconds.
154 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
156 return {seconds, nanos};
160 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
161 * value is too large.
163 static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
164 double nanoseconds) {
165 MOZ_ASSERT(IsInteger(nanoseconds));
167 if (auto int96 = Int96::fromInteger(nanoseconds)) {
168 // The number of normalized seconds must not exceed `2**53 - 1`.
169 constexpr auto limit =
170 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
172 if (int96->abs() < limit) {
173 return mozilla::Some(NormalizeNanoseconds(*int96));
176 return mozilla::Nothing();
180 * Normalize a microseconds amount into a time duration.
182 static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
183 // Split into seconds and microseconds.
184 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
186 // Scale microseconds to nanoseconds.
187 int32_t nanos = micros * ToNanoseconds(TemporalUnit::Microsecond);
189 return {seconds, nanos};
193 * Normalize a microseconds amount into a time duration. Return Nothing if the
194 * value is too large.
196 static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
197 double microseconds) {
198 MOZ_ASSERT(IsInteger(microseconds));
200 if (auto int96 = Int96::fromInteger(microseconds)) {
201 // The number of normalized seconds must not exceed `2**53 - 1`.
202 constexpr auto limit =
203 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
205 if (int96->abs() < limit) {
206 return mozilla::Some(NormalizeMicroseconds(*int96));
209 return mozilla::Nothing();
213 * Normalize a duration into a time duration. Return Nothing if any duration
214 * value is too large.
216 static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
217 const Duration& duration) {
218 do {
219 auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
220 if (!nanoseconds) {
221 break;
223 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
225 auto microseconds = NormalizeMicroseconds(duration.microseconds);
226 if (!microseconds) {
227 break;
229 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
231 // Overflows for millis/seconds/minutes/hours/days always result in an
232 // invalid normalized time duration.
234 int64_t milliseconds;
235 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
236 break;
239 int64_t seconds;
240 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
241 break;
244 int64_t minutes;
245 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
246 break;
249 int64_t hours;
250 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
251 break;
254 int64_t days;
255 if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
256 break;
259 // Compute the overall amount of milliseconds.
260 mozilla::CheckedInt64 millis = days;
261 millis *= 24;
262 millis += hours;
263 millis *= 60;
264 millis += minutes;
265 millis *= 60;
266 millis += seconds;
267 millis *= 1000;
268 millis += milliseconds;
269 if (!millis.isValid()) {
270 break;
273 auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
274 if (!IsValidNormalizedTimeDuration(milli)) {
275 break;
278 // Compute the overall time duration.
279 auto result = milli + *microseconds + *nanoseconds;
280 if (!IsValidNormalizedTimeDuration(result)) {
281 break;
284 return mozilla::Some(result);
285 } while (false);
287 return mozilla::Nothing();
291 * Normalize a days amount into a time duration. Return Nothing if the value is
292 * too large.
294 static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(double days) {
295 MOZ_ASSERT(IsInteger(days));
297 do {
298 int64_t intDays;
299 if (!mozilla::NumberEqualsInt64(days, &intDays)) {
300 break;
303 // Compute the overall amount of milliseconds.
304 auto millis =
305 mozilla::CheckedInt64(intDays) * ToMilliseconds(TemporalUnit::Day);
306 if (!millis.isValid()) {
307 break;
310 auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
311 if (!IsValidNormalizedTimeDuration(result)) {
312 break;
315 return mozilla::Some(result);
316 } while (false);
318 return mozilla::Nothing();
322 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
323 * nanoseconds )
325 static NormalizedTimeDuration NormalizeTimeDuration(
326 double hours, double minutes, double seconds, double milliseconds,
327 double microseconds, double nanoseconds) {
328 MOZ_ASSERT(IsInteger(hours));
329 MOZ_ASSERT(IsInteger(minutes));
330 MOZ_ASSERT(IsInteger(seconds));
331 MOZ_ASSERT(IsInteger(milliseconds));
332 MOZ_ASSERT(IsInteger(microseconds));
333 MOZ_ASSERT(IsInteger(nanoseconds));
335 // Steps 1-3.
336 mozilla::CheckedInt64 millis = int64_t(hours);
337 millis *= 60;
338 millis += int64_t(minutes);
339 millis *= 60;
340 millis += int64_t(seconds);
341 millis *= 1000;
342 millis += int64_t(milliseconds);
343 MOZ_ASSERT(millis.isValid());
345 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
347 // Step 4.
348 auto micros = Int96::fromInteger(microseconds);
349 MOZ_ASSERT(micros);
351 normalized += NormalizeMicroseconds(*micros);
353 // Step 5.
354 auto nanos = Int96::fromInteger(nanoseconds);
355 MOZ_ASSERT(nanos);
357 normalized += NormalizeNanoseconds(*nanos);
359 // Step 6.
360 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
362 // Step 7.
363 return normalized;
367 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
368 * nanoseconds )
370 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
371 int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
372 int32_t microseconds, int32_t nanoseconds) {
373 // Steps 1-3.
374 mozilla::CheckedInt64 millis = int64_t(hours);
375 millis *= 60;
376 millis += int64_t(minutes);
377 millis *= 60;
378 millis += int64_t(seconds);
379 millis *= 1000;
380 millis += int64_t(milliseconds);
381 MOZ_ASSERT(millis.isValid());
383 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
385 // Step 4.
386 normalized += NormalizeMicroseconds(Int96{microseconds});
388 // Step 5.
389 normalized += NormalizeNanoseconds(Int96{nanoseconds});
391 // Step 6.
392 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
394 // Step 7.
395 return normalized;
399 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
400 * nanoseconds )
402 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
403 const Duration& duration) {
404 MOZ_ASSERT(IsValidDuration(duration));
406 return ::NormalizeTimeDuration(duration.hours, duration.minutes,
407 duration.seconds, duration.milliseconds,
408 duration.microseconds, duration.nanoseconds);
412 * AddNormalizedTimeDuration ( one, two )
414 static bool AddNormalizedTimeDuration(JSContext* cx,
415 const NormalizedTimeDuration& one,
416 const NormalizedTimeDuration& two,
417 NormalizedTimeDuration* result) {
418 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
419 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
421 // Step 1.
422 auto sum = one + two;
424 // Step 2.
425 if (!IsValidNormalizedTimeDuration(sum)) {
426 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
427 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
428 return false;
431 // Step 3.
432 *result = sum;
433 return true;
437 * SubtractNormalizedTimeDuration ( one, two )
439 static bool SubtractNormalizedTimeDuration(JSContext* cx,
440 const NormalizedTimeDuration& one,
441 const NormalizedTimeDuration& two,
442 NormalizedTimeDuration* result) {
443 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
444 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
446 // Step 1.
447 auto sum = one - two;
449 // Step 2.
450 if (!IsValidNormalizedTimeDuration(sum)) {
451 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
452 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
453 return false;
456 // Step 3.
457 *result = sum;
458 return true;
462 * Add24HourDaysToNormalizedTimeDuration ( d, days )
464 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
465 JSContext* cx, const NormalizedTimeDuration& d, double days,
466 NormalizedTimeDuration* result) {
467 MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
468 MOZ_ASSERT(IsInteger(days));
470 // Step 1.
471 auto normalizedDays = NormalizeDays(days);
472 if (!normalizedDays) {
473 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
474 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
475 return false;
478 // Step 2.
479 auto sum = d + *normalizedDays;
480 if (!IsValidNormalizedTimeDuration(sum)) {
481 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
482 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
483 return false;
486 // Step 3.
487 *result = sum;
488 return true;
492 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
494 bool js::temporal::CombineDateAndNormalizedTimeDuration(
495 JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
496 NormalizedDuration* result) {
497 MOZ_ASSERT(IsValidDuration(date.toDuration()));
498 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
500 // Step 1.
501 int32_t dateSign = ::DurationSign(date.toDuration());
503 // Step 2.
504 int32_t timeSign = NormalizedTimeDurationSign(time);
506 // Step 3
507 if ((dateSign * timeSign) < 0) {
508 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
509 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
510 return false;
513 // Step 4.
514 *result = {date, time};
515 return true;
519 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
521 NormalizedTimeDuration
522 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
523 const Instant& one, const Instant& two) {
524 MOZ_ASSERT(IsValidEpochInstant(one));
525 MOZ_ASSERT(IsValidEpochInstant(two));
527 // Step 1.
528 auto result = one - two;
530 // Step 2.
531 MOZ_ASSERT(IsValidInstantSpan(result));
533 // Step 3.
534 return result.to<NormalizedTimeDuration>();
538 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
539 * milliseconds, microseconds, nanoseconds )
541 bool js::temporal::IsValidDuration(const Duration& duration) {
542 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
544 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
545 microseconds, nanoseconds] = duration;
547 // Step 1.
548 int32_t sign = DurationSign(duration);
550 // Step 2.
551 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
552 milliseconds, microseconds, nanoseconds}) {
553 // Step 2.a.
554 if (!std::isfinite(v)) {
555 return false;
558 // Step 2.b.
559 if (v < 0 && sign > 0) {
560 return false;
563 // Step 2.c.
564 if (v > 0 && sign < 0) {
565 return false;
569 // Step 3.
570 if (std::abs(years) >= double(int64_t(1) << 32)) {
571 return false;
574 // Step 4.
575 if (std::abs(months) >= double(int64_t(1) << 32)) {
576 return false;
579 // Step 5.
580 if (std::abs(weeks) >= double(int64_t(1) << 32)) {
581 return false;
584 // Steps 6-8.
585 if (!NormalizeSeconds(duration)) {
586 return false;
589 // Step 9.
590 return true;
593 #ifdef DEBUG
595 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
596 * milliseconds, microseconds, nanoseconds )
598 bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
599 auto date = duration.date.toDuration();
600 return IsValidDuration(date) &&
601 IsValidNormalizedTimeDuration(duration.time) &&
602 (DurationSign(date) * NormalizedTimeDurationSign(duration.time) >= 0);
604 #endif
606 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
607 const char* name, unsigned errorNumber) {
608 ToCStringBuf cbuf;
609 const char* numStr = NumberToCString(&cbuf, value);
611 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
612 numStr);
613 return false;
617 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
618 * milliseconds, microseconds, nanoseconds )
620 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
621 const Duration& duration) {
622 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
624 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
625 microseconds, nanoseconds] = duration;
627 // Step 1.
628 int32_t sign = DurationSign(duration);
630 auto throwIfInvalid = [&](double v, const char* name) {
631 // Step 2.a.
632 if (!std::isfinite(v)) {
633 return ThrowInvalidDurationPart(
634 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
637 // Steps 2.b-c.
638 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
639 return ThrowInvalidDurationPart(cx, v, name,
640 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
643 return true;
646 auto throwIfTooLarge = [&](double v, const char* name) {
647 if (std::abs(v) >= double(int64_t(1) << 32)) {
648 return ThrowInvalidDurationPart(
649 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
651 return true;
654 // Step 2.
655 if (!throwIfInvalid(years, "years")) {
656 return false;
658 if (!throwIfInvalid(months, "months")) {
659 return false;
661 if (!throwIfInvalid(weeks, "weeks")) {
662 return false;
664 if (!throwIfInvalid(days, "days")) {
665 return false;
667 if (!throwIfInvalid(hours, "hours")) {
668 return false;
670 if (!throwIfInvalid(minutes, "minutes")) {
671 return false;
673 if (!throwIfInvalid(seconds, "seconds")) {
674 return false;
676 if (!throwIfInvalid(milliseconds, "milliseconds")) {
677 return false;
679 if (!throwIfInvalid(microseconds, "microseconds")) {
680 return false;
682 if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
683 return false;
686 // Step 3.
687 if (!throwIfTooLarge(years, "years")) {
688 return false;
691 // Step 4.
692 if (!throwIfTooLarge(months, "months")) {
693 return false;
696 // Step 5.
697 if (!throwIfTooLarge(weeks, "weeks")) {
698 return false;
701 // Steps 6-8.
702 if (!NormalizeSeconds(duration)) {
703 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
704 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
705 return false;
708 MOZ_ASSERT(IsValidDuration(duration));
710 // Step 9.
711 return true;
715 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
716 * milliseconds, microseconds, nanoseconds )
718 static bool ThrowIfInvalidDuration(JSContext* cx,
719 const DateDuration& duration) {
720 auto& [years, months, weeks, days] = duration;
722 // Step 1.
723 int32_t sign = DurationSign(duration.toDuration());
725 auto throwIfInvalid = [&](int64_t v, const char* name) {
726 // Step 2.a. (Not applicable)
728 // Steps 2.b-c.
729 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
730 return ThrowInvalidDurationPart(cx, v, name,
731 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
734 return true;
737 auto throwIfTooLarge = [&](int64_t v, const char* name) {
738 if (std::abs(v) >= (int64_t(1) << 32)) {
739 return ThrowInvalidDurationPart(
740 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
742 return true;
745 // Step 2.
746 if (!throwIfInvalid(years, "years")) {
747 return false;
749 if (!throwIfInvalid(months, "months")) {
750 return false;
752 if (!throwIfInvalid(weeks, "weeks")) {
753 return false;
755 if (!throwIfInvalid(days, "days")) {
756 return false;
759 // Step 3.
760 if (!throwIfTooLarge(years, "years")) {
761 return false;
764 // Step 4.
765 if (!throwIfTooLarge(months, "months")) {
766 return false;
769 // Step 5.
770 if (!throwIfTooLarge(weeks, "weeks")) {
771 return false;
774 // Steps 6-8.
775 if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
776 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
777 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
778 return false;
781 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
783 // Step 9.
784 return true;
788 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
789 * seconds, milliseconds, microseconds )
791 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
792 MOZ_ASSERT(IsIntegerDuration(duration));
794 // Step 1.
795 if (duration.years != 0) {
796 return TemporalUnit::Year;
799 // Step 2.
800 if (duration.months != 0) {
801 return TemporalUnit::Month;
804 // Step 3.
805 if (duration.weeks != 0) {
806 return TemporalUnit::Week;
809 // Step 4.
810 if (duration.days != 0) {
811 return TemporalUnit::Day;
814 // Step 5.
815 if (duration.hours != 0) {
816 return TemporalUnit::Hour;
819 // Step 6.
820 if (duration.minutes != 0) {
821 return TemporalUnit::Minute;
824 // Step 7.
825 if (duration.seconds != 0) {
826 return TemporalUnit::Second;
829 // Step 8.
830 if (duration.milliseconds != 0) {
831 return TemporalUnit::Millisecond;
834 // Step 9.
835 if (duration.microseconds != 0) {
836 return TemporalUnit::Microsecond;
839 // Step 10.
840 return TemporalUnit::Nanosecond;
844 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
845 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
847 static DurationObject* CreateTemporalDuration(JSContext* cx,
848 const CallArgs& args,
849 const Duration& duration) {
850 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
851 microseconds, nanoseconds] = duration;
853 // Step 1.
854 if (!ThrowIfInvalidDuration(cx, duration)) {
855 return nullptr;
858 // Steps 2-3.
859 Rooted<JSObject*> proto(cx);
860 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
861 return nullptr;
864 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
865 if (!object) {
866 return nullptr;
869 // Steps 4-13.
870 // Add zero to convert -0 to +0.
871 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
872 object->setFixedSlot(DurationObject::MONTHS_SLOT,
873 NumberValue(months + (+0.0)));
874 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
875 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
876 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
877 object->setFixedSlot(DurationObject::MINUTES_SLOT,
878 NumberValue(minutes + (+0.0)));
879 object->setFixedSlot(DurationObject::SECONDS_SLOT,
880 NumberValue(seconds + (+0.0)));
881 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
882 NumberValue(milliseconds + (+0.0)));
883 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
884 NumberValue(microseconds + (+0.0)));
885 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
886 NumberValue(nanoseconds + (+0.0)));
888 // Step 14.
889 return object;
893 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
894 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
896 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
897 const Duration& duration) {
898 auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
899 microseconds, nanoseconds] = duration;
901 MOZ_ASSERT(IsInteger(years));
902 MOZ_ASSERT(IsInteger(months));
903 MOZ_ASSERT(IsInteger(weeks));
904 MOZ_ASSERT(IsInteger(days));
905 MOZ_ASSERT(IsInteger(hours));
906 MOZ_ASSERT(IsInteger(minutes));
907 MOZ_ASSERT(IsInteger(seconds));
908 MOZ_ASSERT(IsInteger(milliseconds));
909 MOZ_ASSERT(IsInteger(microseconds));
910 MOZ_ASSERT(IsInteger(nanoseconds));
912 // Step 1.
913 if (!ThrowIfInvalidDuration(cx, duration)) {
914 return nullptr;
917 // Steps 2-3.
918 auto* object = NewBuiltinClassInstance<DurationObject>(cx);
919 if (!object) {
920 return nullptr;
923 // Steps 4-13.
924 // Add zero to convert -0 to +0.
925 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
926 object->setFixedSlot(DurationObject::MONTHS_SLOT,
927 NumberValue(months + (+0.0)));
928 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
929 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
930 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
931 object->setFixedSlot(DurationObject::MINUTES_SLOT,
932 NumberValue(minutes + (+0.0)));
933 object->setFixedSlot(DurationObject::SECONDS_SLOT,
934 NumberValue(seconds + (+0.0)));
935 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
936 NumberValue(milliseconds + (+0.0)));
937 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
938 NumberValue(microseconds + (+0.0)));
939 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
940 NumberValue(nanoseconds + (+0.0)));
942 // Step 14.
943 return object;
947 * ToIntegerIfIntegral ( argument )
949 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
950 Handle<Value> argument, double* num) {
951 // Step 1.
952 double d;
953 if (!JS::ToNumber(cx, argument, &d)) {
954 return false;
957 // Step 2.
958 if (!js::IsInteger(d)) {
959 ToCStringBuf cbuf;
960 const char* numStr = NumberToCString(&cbuf, d);
962 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
963 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
964 name);
965 return false;
968 // Step 3.
969 *num = d;
970 return true;
974 * ToIntegerIfIntegral ( argument )
976 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
977 Handle<Value> argument, double* result) {
978 // Step 1.
979 double d;
980 if (!JS::ToNumber(cx, argument, &d)) {
981 return false;
984 // Step 2.
985 if (!js::IsInteger(d)) {
986 if (auto nameStr = js::QuoteString(cx, name)) {
987 ToCStringBuf cbuf;
988 const char* numStr = NumberToCString(&cbuf, d);
990 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
991 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
992 nameStr.get());
994 return false;
997 // Step 3.
998 *result = d;
999 return true;
1003 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1005 static bool ToTemporalPartialDurationRecord(
1006 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
1007 // Steps 1-3. (Not applicable in our implementation.)
1009 Rooted<Value> value(cx);
1010 bool any = false;
1012 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
1013 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
1014 &value)) {
1015 return false;
1018 if (!value.isUndefined()) {
1019 any = true;
1021 if (!ToIntegerIfIntegral(cx, name, value, num)) {
1022 return false;
1025 return true;
1028 // Steps 4-23.
1029 if (!getDurationProperty(cx->names().days, &result->days)) {
1030 return false;
1032 if (!getDurationProperty(cx->names().hours, &result->hours)) {
1033 return false;
1035 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
1036 return false;
1038 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
1039 return false;
1041 if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
1042 return false;
1044 if (!getDurationProperty(cx->names().months, &result->months)) {
1045 return false;
1047 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
1048 return false;
1050 if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
1051 return false;
1053 if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
1054 return false;
1056 if (!getDurationProperty(cx->names().years, &result->years)) {
1057 return false;
1060 // Step 24.
1061 if (!any) {
1062 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1063 JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
1064 return false;
1067 // Step 25.
1068 return true;
1072 * ToTemporalDurationRecord ( temporalDurationLike )
1074 bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
1075 Handle<Value> temporalDurationLike,
1076 Duration* result) {
1077 // Step 1.
1078 if (!temporalDurationLike.isObject()) {
1079 // Step 1.a.
1080 if (!temporalDurationLike.isString()) {
1081 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
1082 temporalDurationLike, nullptr, "not a string");
1083 return false;
1085 Rooted<JSString*> string(cx, temporalDurationLike.toString());
1087 // Step 1.b.
1088 return ParseTemporalDurationString(cx, string, result);
1091 Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
1093 // Step 2.
1094 if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
1095 *result = ToDuration(duration);
1096 return true;
1099 // Step 3.
1100 Duration duration = {};
1102 // Steps 4-14.
1103 if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
1104 return false;
1107 // Step 15.
1108 if (!ThrowIfInvalidDuration(cx, duration)) {
1109 return false;
1112 // Step 16.
1113 *result = duration;
1114 return true;
1118 * ToTemporalDuration ( item )
1120 Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
1121 Handle<Value> item) {
1122 // Step 1.
1123 if (item.isObject()) {
1124 JSObject* itemObj = &item.toObject();
1125 if (itemObj->canUnwrapAs<DurationObject>()) {
1126 return itemObj;
1130 // Step 2.
1131 Duration result;
1132 if (!ToTemporalDurationRecord(cx, item, &result)) {
1133 return nullptr;
1136 // Step 3.
1137 return CreateTemporalDuration(cx, result);
1141 * ToTemporalDuration ( item )
1143 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
1144 Duration* result) {
1145 auto obj = ToTemporalDuration(cx, item);
1146 if (!obj) {
1147 return false;
1150 *result = ToDuration(&obj.unwrap());
1151 return true;
1155 * DaysUntil ( earlier, later )
1157 int32_t js::temporal::DaysUntil(const PlainDate& earlier,
1158 const PlainDate& later) {
1159 MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
1160 MOZ_ASSERT(ISODateTimeWithinLimits(later));
1162 // Steps 1-2.
1163 int32_t epochDaysEarlier = MakeDay(earlier);
1164 MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
1166 // Steps 3-4.
1167 int32_t epochDaysLater = MakeDay(later);
1168 MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
1170 // Step 5.
1171 return epochDaysLater - epochDaysEarlier;
1175 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1177 static bool MoveRelativeDate(
1178 JSContext* cx, Handle<CalendarRecord> calendar,
1179 Handle<Wrapped<PlainDateObject*>> relativeTo, const Duration& duration,
1180 MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
1181 int32_t* daysResult) {
1182 auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
1183 if (!unwrappedRelativeTo) {
1184 return false;
1186 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1188 // Step 1.
1189 auto newDate = AddDate(cx, calendar, relativeTo, duration);
1190 if (!newDate) {
1191 return false;
1193 auto later = ToPlainDate(&newDate.unwrap());
1194 relativeToResult.set(newDate);
1196 // Step 2.
1197 *daysResult = DaysUntil(relativeToDate, later);
1198 MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
1200 // Step 3.
1201 return true;
1205 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1206 * months, weeks, days, precalculatedPlainDateTime )
1208 static bool MoveRelativeZonedDateTime(
1209 JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
1210 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
1211 const DateDuration& duration,
1212 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1213 MutableHandle<ZonedDateTime> result) {
1214 // Step 1.
1215 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1216 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1218 // Step 2.
1219 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1220 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1222 // Step 3.
1223 Instant intermediateNs;
1224 if (precalculatedPlainDateTime) {
1225 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1226 duration, *precalculatedPlainDateTime,
1227 &intermediateNs)) {
1228 return false;
1230 } else {
1231 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1232 duration, &intermediateNs)) {
1233 return false;
1236 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
1238 // Step 4.
1239 result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
1240 zonedDateTime.calendar()});
1241 return true;
1245 * Split duration into full days and remainding nanoseconds.
1247 static NormalizedTimeAndDays NormalizedTimeDurationToDays(
1248 const NormalizedTimeDuration& duration) {
1249 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1251 auto [seconds, nanoseconds] = duration;
1252 if (seconds < 0 && nanoseconds > 0) {
1253 seconds += 1;
1254 nanoseconds -= 1'000'000'000;
1257 int64_t days = seconds / ToSeconds(TemporalUnit::Day);
1258 seconds = seconds % ToSeconds(TemporalUnit::Day);
1260 int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
1262 constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
1263 MOZ_ASSERT(std::abs(time) < dayLength);
1265 return {days, time, dayLength};
1269 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1270 * microseconds, nanoseconds )
1272 static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
1273 int64_t minutes, int64_t seconds,
1274 int64_t milliseconds,
1275 double microseconds,
1276 double nanoseconds) {
1277 // Step 1.
1278 MOZ_ASSERT(IsValidDuration(
1279 {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
1280 double(milliseconds), microseconds, nanoseconds}));
1282 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1283 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1284 MOZ_ASSERT(IsSafeInteger(days));
1285 MOZ_ASSERT(IsSafeInteger(hours));
1286 MOZ_ASSERT(IsSafeInteger(minutes));
1287 MOZ_ASSERT(IsSafeInteger(seconds));
1289 // |milliseconds| is explicitly casted to double by consumers, so we can also
1290 // omit the `ℝ(𝔽(x))` conversion.
1292 // Step 2.
1293 // NB: Adds +0.0 to correctly handle negative zero.
1294 return {
1295 days,
1296 hours,
1297 minutes,
1298 seconds,
1299 milliseconds,
1300 microseconds + (+0.0),
1301 nanoseconds + (+0.0),
1306 * BalanceTimeDuration ( norm, largestUnit )
1308 TimeDuration js::temporal::BalanceTimeDuration(
1309 const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
1310 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1312 auto [seconds, nanoseconds] = duration;
1314 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1315 // Convert these back to their absolute value and adjust the seconds part
1316 // accordingly.
1318 // For example the nanoseconds duration |-1n| is represented as the
1319 // duration {seconds: -1, nanoseconds: 999'999'999}.
1320 if (seconds < 0 && nanoseconds > 0) {
1321 seconds += 1;
1322 nanoseconds -= ToNanoseconds(TemporalUnit::Second);
1325 // Step 1.
1326 int64_t days = 0;
1327 int64_t hours = 0;
1328 int64_t minutes = 0;
1329 int64_t milliseconds = 0;
1330 int64_t microseconds = 0;
1332 // Steps 2-3. (Not applicable in our implementation.)
1334 // We don't need to convert to positive numbers, because integer division
1335 // truncates and the %-operator has modulo semantics.
1337 // Steps 4-10.
1338 switch (largestUnit) {
1339 // Step 4.
1340 case TemporalUnit::Year:
1341 case TemporalUnit::Month:
1342 case TemporalUnit::Week:
1343 case TemporalUnit::Day: {
1344 // Step 4.a.
1345 microseconds = nanoseconds / 1000;
1347 // Step 4.b.
1348 nanoseconds = nanoseconds % 1000;
1350 // Step 4.c.
1351 milliseconds = microseconds / 1000;
1353 // Step 4.d.
1354 microseconds = microseconds % 1000;
1356 // Steps 4.e-f. (Not applicable)
1357 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1359 // Step 4.g.
1360 minutes = seconds / 60;
1362 // Step 4.h.
1363 seconds = seconds % 60;
1365 // Step 4.i.
1366 hours = minutes / 60;
1368 // Step 4.j.
1369 minutes = minutes % 60;
1371 // Step 4.k.
1372 days = hours / 24;
1374 // Step 4.l.
1375 hours = hours % 24;
1377 break;
1380 // Step 5.
1381 case TemporalUnit::Hour: {
1382 // Step 5.a.
1383 microseconds = nanoseconds / 1000;
1385 // Step 5.b.
1386 nanoseconds = nanoseconds % 1000;
1388 // Step 5.c.
1389 milliseconds = microseconds / 1000;
1391 // Step 5.d.
1392 microseconds = microseconds % 1000;
1394 // Steps 5.e-f. (Not applicable)
1395 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1397 // Step 5.g.
1398 minutes = seconds / 60;
1400 // Step 5.h.
1401 seconds = seconds % 60;
1403 // Step 5.i.
1404 hours = minutes / 60;
1406 // Step 5.j.
1407 minutes = minutes % 60;
1409 break;
1412 case TemporalUnit::Minute: {
1413 // Step 6.a.
1414 microseconds = nanoseconds / 1000;
1416 // Step 6.b.
1417 nanoseconds = nanoseconds % 1000;
1419 // Step 6.c.
1420 milliseconds = microseconds / 1000;
1422 // Step 6.d.
1423 microseconds = microseconds % 1000;
1425 // Steps 6.e-f. (Not applicable)
1426 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1428 // Step 6.g.
1429 minutes = seconds / 60;
1431 // Step 6.h.
1432 seconds = seconds % 60;
1434 break;
1437 // Step 7.
1438 case TemporalUnit::Second: {
1439 // Step 7.a.
1440 microseconds = nanoseconds / 1000;
1442 // Step 7.b.
1443 nanoseconds = nanoseconds % 1000;
1445 // Step 7.c.
1446 milliseconds = microseconds / 1000;
1448 // Step 7.d.
1449 microseconds = microseconds % 1000;
1451 // Steps 7.e-f. (Not applicable)
1452 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1454 break;
1457 // Step 8.
1458 case TemporalUnit::Millisecond: {
1459 static_assert((NormalizedTimeDuration::max().seconds + 1) *
1460 ToMilliseconds(TemporalUnit::Second) <=
1461 INT64_MAX,
1462 "total number duration milliseconds fits into int64");
1464 int64_t millis = seconds * ToMilliseconds(TemporalUnit::Second);
1466 // Set to zero per step 1.
1467 seconds = 0;
1469 // Step 8.a.
1470 microseconds = nanoseconds / 1000;
1472 // Step 8.b.
1473 nanoseconds = nanoseconds % 1000;
1475 // Step 8.c.
1476 milliseconds = microseconds / 1000;
1478 // Step 8.d.
1479 microseconds = microseconds % 1000;
1481 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1482 milliseconds += millis;
1484 break;
1487 // Step 9.
1488 case TemporalUnit::Microsecond: {
1489 // Step 9.a.
1490 int64_t microseconds = nanoseconds / 1000;
1492 // Step 9.b.
1493 nanoseconds = nanoseconds % 1000;
1495 MOZ_ASSERT(std::abs(microseconds) <= 999'999);
1496 double micros =
1497 std::fma(double(seconds), ToMicroseconds(TemporalUnit::Second),
1498 double(microseconds));
1500 // Step 11.
1501 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros,
1502 double(nanoseconds));
1505 // Step 10.
1506 case TemporalUnit::Nanosecond: {
1507 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
1508 double nanos =
1509 std::fma(double(seconds), ToNanoseconds(TemporalUnit::Second),
1510 double(nanoseconds));
1512 // Step 11.
1513 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos);
1516 case TemporalUnit::Auto:
1517 MOZ_CRASH("Unexpected temporal unit");
1520 // Step 11.
1521 return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
1522 double(microseconds), double(nanoseconds));
1526 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1527 * timeZoneRec, precalculatedPlainDateTime )
1529 static bool BalanceTimeDurationRelative(
1530 JSContext* cx, const NormalizedDuration& duration, TemporalUnit largestUnit,
1531 Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
1532 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1533 TimeDuration* result) {
1534 MOZ_ASSERT(IsValidDuration(duration));
1536 // Step 1.
1537 const auto& startNs = relativeTo.instant();
1539 // Step 2.
1540 const auto& startInstant = startNs;
1542 // Step 3.
1543 auto intermediateNs = startNs;
1545 // Step 4.
1546 PlainDateTime startDateTime;
1547 if (duration.date.days != 0) {
1548 // Step 4.a.
1549 if (!precalculatedPlainDateTime) {
1550 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1551 return false;
1553 precalculatedPlainDateTime =
1554 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1557 // Steps 4.b-c.
1558 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
1559 if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
1560 timeZone, isoCalendar, duration.date.days,
1561 &intermediateNs)) {
1562 return false;
1566 // Step 5.
1567 Instant endNs;
1568 if (!AddInstant(cx, intermediateNs, duration.time, &endNs)) {
1569 return false;
1571 MOZ_ASSERT(IsValidEpochInstant(endNs));
1573 // Step 6.
1574 auto normalized =
1575 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startInstant);
1577 // Step 7.
1578 if (normalized == NormalizedTimeDuration{}) {
1579 *result = {};
1580 return true;
1583 // Steps 8-9.
1584 int64_t days = 0;
1585 if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
1586 // Step 8.a.
1587 if (!precalculatedPlainDateTime) {
1588 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1589 return false;
1591 precalculatedPlainDateTime =
1592 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1595 // Step 8.b.
1596 NormalizedTimeAndDays timeAndDays;
1597 if (!NormalizedTimeDurationToDays(cx, normalized, relativeTo, timeZone,
1598 *precalculatedPlainDateTime,
1599 &timeAndDays)) {
1600 return false;
1603 // Step 8.c.
1604 days = timeAndDays.days;
1606 // Step 8.d.
1607 normalized = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
1608 MOZ_ASSERT_IF(days > 0, normalized >= NormalizedTimeDuration{});
1609 MOZ_ASSERT_IF(days < 0, normalized <= NormalizedTimeDuration{});
1611 // Step 8.e.
1612 largestUnit = TemporalUnit::Hour;
1615 // Step 10.
1616 auto balanceResult = BalanceTimeDuration(normalized, largestUnit);
1618 // Step 11.
1619 *result = {
1620 days,
1621 balanceResult.hours,
1622 balanceResult.minutes,
1623 balanceResult.seconds,
1624 balanceResult.milliseconds,
1625 balanceResult.microseconds,
1626 balanceResult.nanoseconds,
1628 MOZ_ASSERT(IsValidDuration(result->toDuration()));
1629 return true;
1633 * CreateDateDurationRecord ( years, months, weeks, days )
1635 static DateDuration CreateDateDurationRecord(int64_t years, int64_t months,
1636 int64_t weeks, int64_t days) {
1637 MOZ_ASSERT(IsValidDuration(Duration{
1638 double(years),
1639 double(months),
1640 double(weeks),
1641 double(days),
1642 }));
1643 return {years, months, weeks, days};
1647 * CreateDateDurationRecord ( years, months, weeks, days )
1649 static bool CreateDateDurationRecord(JSContext* cx, int64_t years,
1650 int64_t months, int64_t weeks,
1651 int64_t days, DateDuration* result) {
1652 auto duration = DateDuration{years, months, weeks, days};
1653 if (!ThrowIfInvalidDuration(cx, duration)) {
1654 return false;
1657 *result = duration;
1658 return true;
1661 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration& duration,
1662 TemporalUnit largestUnit) {
1663 MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
1665 // Steps 2, 3.a-b, 4.a-b, 6-7.
1666 return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
1667 (largestUnit > TemporalUnit::Month && duration.months != 0) ||
1668 (largestUnit > TemporalUnit::Week && duration.weeks != 0);
1672 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1673 * plainRelativeTo, calendarRec )
1675 static bool UnbalanceDateDurationRelative(
1676 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1677 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1678 Handle<CalendarRecord> calendar, DateDuration* result) {
1679 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
1681 auto [years, months, weeks, days] = duration;
1683 // Step 1. (Not applicable in our implementation.)
1685 // Steps 2, 3.a, 4.a, and 6.
1686 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1687 // Steps 2.a, 3.a, 4.a, and 6.
1688 *result = duration;
1689 return true;
1692 // Step 3.
1693 if (largestUnit == TemporalUnit::Month) {
1694 // Step 3.a. (Handled above)
1695 MOZ_ASSERT(years != 0);
1697 // Step 3.b. (Not applicable in our implementation.)
1699 // Step 3.c.
1700 MOZ_ASSERT(
1701 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1703 // Step 3.d.
1704 MOZ_ASSERT(
1705 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1707 // Step 3.e.
1708 auto yearsDuration = Duration{double(years)};
1710 // Step 3.f.
1711 Rooted<Wrapped<PlainDateObject*>> later(
1712 cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
1713 if (!later) {
1714 return false;
1717 // Steps 3.g-i.
1718 Duration untilResult;
1719 if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
1720 TemporalUnit::Month, &untilResult)) {
1721 return false;
1724 // Step 3.j.
1725 int64_t yearsInMonths = int64_t(untilResult.months);
1727 // Step 3.k.
1728 return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
1729 result);
1732 // Step 4.
1733 if (largestUnit == TemporalUnit::Week) {
1734 // Step 4.a. (Handled above)
1735 MOZ_ASSERT(years != 0 || months != 0);
1737 // Step 4.b. (Not applicable in our implementation.)
1739 // Step 4.c.
1740 MOZ_ASSERT(
1741 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1743 // Step 4.d.
1744 auto yearsMonthsDuration = Duration{double(years), double(months)};
1746 // Step 4.e.
1747 auto later =
1748 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
1749 if (!later) {
1750 return false;
1752 auto laterDate = ToPlainDate(&later.unwrap());
1754 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1755 if (!unwrappedRelativeTo) {
1756 return false;
1758 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1760 // Step 4.f.
1761 int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
1763 // Step 4.g.
1764 return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
1765 result);
1768 // Step 5. (Not applicable in our implementation.)
1770 // Step 6. (Handled above)
1771 MOZ_ASSERT(years != 0 || months != 0 || weeks != 0);
1773 // FIXME: why don't we unconditionally throw an error for missing calendars?
1775 // Step 7. (Not applicable in our implementation.)
1777 // Step 8.
1778 MOZ_ASSERT(
1779 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1781 // Step 9.
1782 auto yearsMonthsWeeksDuration =
1783 Duration{double(years), double(months), double(weeks)};
1785 // Step 10.
1786 auto later =
1787 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
1788 if (!later) {
1789 return false;
1791 auto laterDate = ToPlainDate(&later.unwrap());
1793 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1794 if (!unwrappedRelativeTo) {
1795 return false;
1797 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1799 // Step 11.
1800 int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
1802 // Step 12.
1803 return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
1804 result);
1808 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1809 * plainRelativeTo, calendarRec )
1811 static bool UnbalanceDateDurationRelative(JSContext* cx,
1812 const DateDuration& duration,
1813 TemporalUnit largestUnit,
1814 DateDuration* result) {
1815 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
1817 // Step 1. (Not applicable.)
1819 // Steps 2, 3.a, 4.a, and 6.
1820 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1821 // Steps 2.a, 3.a, 4.a, and 6.
1822 *result = duration;
1823 return true;
1826 // Step 5. (Not applicable.)
1828 // Steps 3.b, 4.b, and 7.
1829 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1830 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
1831 return false;
1835 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1836 * smallestUnit, plainRelativeTo, calendarRec )
1838 static bool BalanceDateDurationRelative(
1839 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1840 TemporalUnit smallestUnit,
1841 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1842 Handle<CalendarRecord> calendar, DateDuration* result) {
1843 MOZ_ASSERT(IsValidDuration(duration.toDuration()));
1844 MOZ_ASSERT(largestUnit <= smallestUnit);
1846 auto [years, months, weeks, days] = duration;
1848 // FIXME: spec issue - effectful code paths should be more fine-grained
1849 // similar to UnbalanceDateDurationRelative. For example:
1850 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1851 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1852 // 3. Else if days = 0, then no-op.
1854 // Also note that |weeks| is never balanced, even when non-zero.
1856 // Step 1. (Not applicable in our implementation.)
1858 // Steps 2-4.
1859 if (largestUnit > TemporalUnit::Week ||
1860 (years == 0 && months == 0 && weeks == 0 && days == 0)) {
1861 // Step 4.a.
1862 *result = duration;
1863 return true;
1866 // Step 5.
1867 if (!plainRelativeTo) {
1868 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1869 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
1870 "relativeTo");
1871 return false;
1874 // Step 6.
1875 MOZ_ASSERT(
1876 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1878 // Step 7.
1879 MOZ_ASSERT(
1880 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1882 // Steps 8-9. (Not applicable in our implementation.)
1884 auto untilAddedDate = [&](const Duration& duration, Duration* untilResult) {
1885 Rooted<Wrapped<PlainDateObject*>> later(
1886 cx, AddDate(cx, calendar, plainRelativeTo, duration));
1887 if (!later) {
1888 return false;
1891 return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
1892 untilResult);
1895 // Step 10.
1896 if (largestUnit == TemporalUnit::Year) {
1897 // Step 10.a.
1898 if (smallestUnit == TemporalUnit::Week) {
1899 // Step 10.a.i.
1900 MOZ_ASSERT(days == 0);
1902 // Step 10.a.ii.
1903 auto yearsMonthsDuration = Duration{double(years), double(months)};
1905 // Steps 10.a.iii-iv.
1906 Duration untilResult;
1907 if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
1908 return false;
1911 // Step 10.a.v.
1912 *result = CreateDateDurationRecord(int64_t(untilResult.years),
1913 int64_t(untilResult.months), weeks, 0);
1914 return true;
1917 // Step 10.b.
1918 auto yearsMonthsWeeksDaysDuration =
1919 Duration{double(years), double(months), double(weeks), double(days)};
1921 // Steps 10.c-d.
1922 Duration untilResult;
1923 if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
1924 return false;
1927 // FIXME: spec bug - CreateDateDurationRecord is infallible
1928 // https://github.com/tc39/proposal-temporal/issues/2750
1930 // Step 10.e.
1931 *result = CreateDateDurationRecord(
1932 int64_t(untilResult.years), int64_t(untilResult.months),
1933 int64_t(untilResult.weeks), int64_t(untilResult.days));
1934 return true;
1937 // Step 11.
1938 if (largestUnit == TemporalUnit::Month) {
1939 // Step 11.a.
1940 MOZ_ASSERT(years == 0);
1942 // Step 11.b.
1943 if (smallestUnit == TemporalUnit::Week) {
1944 // Step 10.b.i.
1945 MOZ_ASSERT(days == 0);
1947 // Step 10.b.ii.
1948 *result = CreateDateDurationRecord(0, months, weeks, 0);
1949 return true;
1952 // Step 11.c.
1953 auto monthsWeeksDaysDuration =
1954 Duration{0, double(months), double(weeks), double(days)};
1956 // Steps 11.d-e.
1957 Duration untilResult;
1958 if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
1959 return false;
1962 // FIXME: spec bug - CreateDateDurationRecord is infallible
1963 // https://github.com/tc39/proposal-temporal/issues/2750
1965 // Step 11.f.
1966 *result = CreateDateDurationRecord(0, int64_t(untilResult.months),
1967 int64_t(untilResult.weeks),
1968 int64_t(untilResult.days));
1969 return true;
1972 // Step 12.
1973 MOZ_ASSERT(largestUnit == TemporalUnit::Week);
1975 // Step 13.
1976 MOZ_ASSERT(years == 0);
1978 // Step 14.
1979 MOZ_ASSERT(months == 0);
1981 // Step 15.
1982 auto weeksDaysDuration = Duration{0, 0, double(weeks), double(days)};
1984 // Steps 16-17.
1985 Duration untilResult;
1986 if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
1987 return false;
1990 // FIXME: spec bug - CreateDateDurationRecord is infallible
1991 // https://github.com/tc39/proposal-temporal/issues/2750
1993 // Step 18.
1994 *result = CreateDateDurationRecord(0, 0, int64_t(untilResult.weeks),
1995 int64_t(untilResult.days));
1996 return true;
2000 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2001 * smallestUnit, plainRelativeTo, calendarRec )
2003 bool js::temporal::BalanceDateDurationRelative(
2004 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
2005 TemporalUnit smallestUnit,
2006 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2007 Handle<CalendarRecord> calendar, DateDuration* result) {
2008 MOZ_ASSERT(plainRelativeTo);
2009 MOZ_ASSERT(calendar.receiver());
2011 return ::BalanceDateDurationRelative(cx, duration, largestUnit, smallestUnit,
2012 plainRelativeTo, calendar, result);
2016 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2017 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2018 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2020 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2021 Duration* result) {
2022 MOZ_ASSERT(IsValidDuration(one));
2023 MOZ_ASSERT(IsValidDuration(two));
2025 // Steps 1-2. (Not applicable)
2027 // Step 3.
2028 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2030 // Step 4.
2031 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2033 // Step 5.
2034 auto largestUnit = std::min(largestUnit1, largestUnit2);
2036 // Step 6.a.
2037 if (largestUnit <= TemporalUnit::Week) {
2038 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2039 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
2040 "relativeTo");
2041 return false;
2044 // Step 6.b.
2045 auto normalized1 = NormalizeTimeDuration(one);
2047 // Step 6.c.
2048 auto normalized2 = NormalizeTimeDuration(two);
2050 // Step 6.d.
2051 NormalizedTimeDuration normalized;
2052 if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
2053 return false;
2056 // Step 6.e.
2057 if (!Add24HourDaysToNormalizedTimeDuration(
2058 cx, normalized, one.days + two.days, &normalized)) {
2059 return false;
2062 // Step 6.f.
2063 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2065 // Steps 6.g.
2066 *result = balanced.toDuration();
2067 return true;
2071 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2072 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2073 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2075 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2076 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2077 Handle<CalendarRecord> calendar, Duration* result) {
2078 MOZ_ASSERT(IsValidDuration(one));
2079 MOZ_ASSERT(IsValidDuration(two));
2081 // Steps 1-2. (Not applicable)
2083 // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
2084 // not undefined.
2086 // Step 3.
2087 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2089 // Step 4.
2090 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2092 // Step 5.
2093 auto largestUnit = std::min(largestUnit1, largestUnit2);
2095 // Step 6. (Not applicable)
2097 // Step 7.a. (Not applicable in our implementation.)
2099 // Step 7.b.
2100 auto dateDuration1 = Duration{one.years, one.months, one.weeks, one.days};
2102 // Step 7.c.
2103 auto dateDuration2 = Duration{two.years, two.months, two.weeks, two.days};
2105 // FIXME: spec issue - calendarUnitsPresent is unused.
2107 // Step 7.d.
2108 [[maybe_unused]] bool calendarUnitsPresent = true;
2110 // Step 7.e.
2111 if (dateDuration1.years == 0 && dateDuration1.months == 0 &&
2112 dateDuration1.weeks == 0 && dateDuration2.years == 0 &&
2113 dateDuration2.months == 0 && dateDuration2.weeks == 0) {
2114 calendarUnitsPresent = false;
2117 // Step 7.f.
2118 Rooted<Wrapped<PlainDateObject*>> intermediate(
2119 cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
2120 if (!intermediate) {
2121 return false;
2124 // Step 7.g.
2125 Rooted<Wrapped<PlainDateObject*>> end(
2126 cx, AddDate(cx, calendar, intermediate, dateDuration2));
2127 if (!end) {
2128 return false;
2131 // Step 7.h.
2132 auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
2134 // Steps 7.i-k.
2135 Duration dateDifference;
2136 if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
2137 &dateDifference)) {
2138 return false;
2141 // Step 7.l.
2142 auto normalized1 = NormalizeTimeDuration(one);
2144 // Step 7.m.
2145 auto normalized2 = NormalizeTimeDuration(two);
2147 // Step 7.n.
2148 NormalizedTimeDuration normalized1WithDays;
2149 if (!Add24HourDaysToNormalizedTimeDuration(
2150 cx, normalized1, dateDifference.days, &normalized1WithDays)) {
2151 return false;
2154 // Step 7.o.
2155 NormalizedTimeDuration normalized;
2156 if (!AddNormalizedTimeDuration(cx, normalized1WithDays, normalized2,
2157 &normalized)) {
2158 return false;
2161 // Step 7.p.
2162 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2164 // Steps 7.q.
2165 *result = {
2166 dateDifference.years, dateDifference.months,
2167 dateDifference.weeks, double(balanced.days),
2168 double(balanced.hours), double(balanced.minutes),
2169 double(balanced.seconds), double(balanced.milliseconds),
2170 balanced.microseconds, balanced.nanoseconds,
2172 MOZ_ASSERT(IsValidDuration(*result));
2173 return true;
2177 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2178 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2179 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2181 static bool AddDuration(
2182 JSContext* cx, const Duration& one, const Duration& two,
2183 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2184 Handle<TimeZoneRecord> timeZone,
2185 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2186 Duration* result) {
2187 // Steps 1-2. (Not applicable)
2189 // Step 3.
2190 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2192 // Step 4.
2193 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2195 // Step 5.
2196 auto largestUnit = std::min(largestUnit1, largestUnit2);
2198 // Steps 6-7. (Not applicable)
2200 // Steps 8-9. (Not applicable in our implementation.)
2202 // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
2204 // clang-format off
2206 // 10. If largestUnit is one of "year", "month", "week", or "day", then
2207 // a. If precalculatedPlainDateTime is undefined, then
2208 // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
2209 // b. Else,
2210 // i. Let startDateTime be precalculatedPlainDateTime.
2211 // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
2212 // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
2213 // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
2214 // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
2215 // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
2216 // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
2217 // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
2219 // clang-format on
2221 // Step 10.
2222 bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
2224 // Steps 11-17.
2225 if (!startDateTimeNeeded) {
2226 // Steps 11-12. (Not applicable)
2228 // Step 13.
2229 auto normalized1 = NormalizeTimeDuration(one);
2231 // Step 14.
2232 auto normalized2 = NormalizeTimeDuration(two);
2234 // Step 15. (Inlined AddZonedDateTime, step 6.)
2235 Instant intermediateNs;
2236 if (!AddInstant(cx, zonedRelativeTo.instant(), normalized1,
2237 &intermediateNs)) {
2238 return false;
2240 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2242 // Step 16. (Inlined AddZonedDateTime, step 6.)
2243 Instant endNs;
2244 if (!AddInstant(cx, intermediateNs, normalized2, &endNs)) {
2245 return false;
2247 MOZ_ASSERT(IsValidEpochInstant(endNs));
2249 // Step 17.a.
2250 auto normalized = NormalizedTimeDurationFromEpochNanosecondsDifference(
2251 endNs, zonedRelativeTo.instant());
2253 // Step 17.b.
2254 auto balanced = BalanceTimeDuration(normalized, largestUnit);
2256 // Step 17.c.
2257 *result = balanced.toDuration();
2258 return true;
2261 // Steps 11-12.
2262 PlainDateTime startDateTime;
2263 if (!precalculatedPlainDateTime) {
2264 if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
2265 &startDateTime)) {
2266 return false;
2268 } else {
2269 startDateTime = *precalculatedPlainDateTime;
2272 // Step 13.
2273 auto normalized1 = CreateNormalizedDurationRecord(one);
2275 // Step 14.
2276 auto normalized2 = CreateNormalizedDurationRecord(two);
2278 // Step 15.
2279 Instant intermediateNs;
2280 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2281 normalized1, startDateTime, &intermediateNs)) {
2282 return false;
2284 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2286 // Step 16.
2287 Instant endNs;
2288 if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, normalized2,
2289 &endNs)) {
2290 return false;
2292 MOZ_ASSERT(IsValidEpochInstant(endNs));
2294 // Step 17. (Not applicable)
2296 // Step 18.
2297 NormalizedDuration difference;
2298 if (!DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs, timeZone,
2299 calendar, largestUnit, startDateTime,
2300 &difference)) {
2301 return false;
2304 // Step 19.
2305 auto balanced = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
2307 // Step 20.
2308 *result = {
2309 double(difference.date.years), double(difference.date.months),
2310 double(difference.date.weeks), double(difference.date.days),
2311 double(balanced.hours), double(balanced.minutes),
2312 double(balanced.seconds), double(balanced.milliseconds),
2313 balanced.microseconds, balanced.nanoseconds,
2315 MOZ_ASSERT(IsValidDuration(*result));
2316 return true;
2320 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2321 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2322 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2324 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2325 Handle<ZonedDateTime> zonedRelativeTo,
2326 Handle<CalendarRecord> calendar,
2327 Handle<TimeZoneRecord> timeZone, Duration* result) {
2328 return AddDuration(cx, one, two, zonedRelativeTo, calendar, timeZone,
2329 mozilla::Nothing(), result);
2333 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2334 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2335 * precalculatedPlainDateTime )
2337 static bool AdjustRoundedDurationDays(
2338 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2339 TemporalUnit unit, TemporalRoundingMode roundingMode,
2340 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2341 Handle<TimeZoneRecord> timeZone,
2342 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2343 NormalizedDuration* result) {
2344 MOZ_ASSERT(IsValidDuration(duration));
2346 // Step 1.
2347 if ((TemporalUnit::Year <= unit && unit <= TemporalUnit::Day) ||
2348 (unit == TemporalUnit::Nanosecond && increment == Increment{1})) {
2349 *result = duration;
2350 return true;
2353 // The increment is limited for all smaller temporal units.
2354 MOZ_ASSERT(increment < MaximumTemporalDurationRoundingIncrement(unit));
2356 // Step 2.
2357 MOZ_ASSERT(precalculatedPlainDateTime);
2359 // Step 3.
2360 int32_t direction = NormalizedTimeDurationSign(duration.time);
2362 // Steps 4-5.
2363 Instant dayStart;
2364 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2365 duration.date, *precalculatedPlainDateTime,
2366 &dayStart)) {
2367 return false;
2369 MOZ_ASSERT(IsValidEpochInstant(dayStart));
2371 // Step 6.
2372 PlainDateTime dayStartDateTime;
2373 if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
2374 return false;
2377 // Step 7.
2378 Instant dayEnd;
2379 if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
2380 zonedRelativeTo.calendar(), direction, &dayEnd)) {
2381 return false;
2383 MOZ_ASSERT(IsValidEpochInstant(dayEnd));
2385 // Step 8.
2386 auto dayLengthNs =
2387 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd, dayStart);
2388 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs.to<InstantSpan>()));
2390 // Step 9.
2391 NormalizedTimeDuration oneDayLess;
2392 if (!SubtractNormalizedTimeDuration(cx, duration.time, dayLengthNs,
2393 &oneDayLess)) {
2394 return false;
2397 // Step 10.
2398 int32_t oneDayLessSign = NormalizedTimeDurationSign(oneDayLess);
2399 if ((direction > 0 && oneDayLessSign < 0) ||
2400 (direction < 0 && oneDayLessSign > 0)) {
2401 *result = duration;
2402 return true;
2405 // Step 11.
2406 Duration adjustedDateDuration;
2407 if (!AddDuration(cx, duration.date.toDuration(), {0, 0, 0, double(direction)},
2408 zonedRelativeTo, calendar, timeZone,
2409 precalculatedPlainDateTime, &adjustedDateDuration)) {
2410 return false;
2413 // Step 12.
2414 auto roundedTime = RoundDuration(oneDayLess, increment, unit, roundingMode);
2416 // Step 13.
2417 return CombineDateAndNormalizedTimeDuration(
2418 cx, adjustedDateDuration.toDateDuration(), roundedTime, result);
2422 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2423 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2424 * precalculatedPlainDateTime )
2426 bool js::temporal::AdjustRoundedDurationDays(
2427 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2428 TemporalUnit unit, TemporalRoundingMode roundingMode,
2429 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2430 Handle<TimeZoneRecord> timeZone,
2431 const PlainDateTime& precalculatedPlainDateTime,
2432 NormalizedDuration* result) {
2433 return ::AdjustRoundedDurationDays(
2434 cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
2435 timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
2438 static bool BigIntToStringBuilder(JSContext* cx, Handle<BigInt*> num,
2439 JSStringBuilder& sb) {
2440 MOZ_ASSERT(!num->isNegative());
2442 JSLinearString* str = BigInt::toString<CanGC>(cx, num, 10);
2443 if (!str) {
2444 return false;
2446 return sb.append(str);
2449 static bool NumberToStringBuilder(JSContext* cx, int64_t num,
2450 JSStringBuilder& sb) {
2451 MOZ_ASSERT(num >= 0);
2452 MOZ_ASSERT(num < int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
2454 ToCStringBuf cbuf;
2455 size_t length;
2456 const char* numStr = NumberToCString(&cbuf, num, &length);
2458 return sb.append(numStr, length);
2461 static bool NumberToStringBuilder(JSContext* cx, double num,
2462 JSStringBuilder& sb) {
2463 MOZ_ASSERT(IsInteger(num));
2464 MOZ_ASSERT(num >= 0);
2466 if (num < DOUBLE_INTEGRAL_PRECISION_LIMIT) {
2467 ToCStringBuf cbuf;
2468 size_t length;
2469 const char* numStr = NumberToCString(&cbuf, num, &length);
2471 return sb.append(numStr, length);
2474 Rooted<BigInt*> bi(cx, BigInt::createFromDouble(cx, num));
2475 if (!bi) {
2476 return false;
2478 return BigIntToStringBuilder(cx, bi, sb);
2481 static Duration AbsoluteDuration(const Duration& duration) {
2482 return {
2483 std::abs(duration.years), std::abs(duration.months),
2484 std::abs(duration.weeks), std::abs(duration.days),
2485 std::abs(duration.hours), std::abs(duration.minutes),
2486 std::abs(duration.seconds), std::abs(duration.milliseconds),
2487 std::abs(duration.microseconds), std::abs(duration.nanoseconds),
2492 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2494 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
2495 int32_t subSecondNanoseconds,
2496 Precision precision) {
2497 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
2498 MOZ_ASSERT(precision != Precision::Minute());
2500 // Steps 1-2.
2501 if (precision == Precision::Auto()) {
2502 // Step 1.a.
2503 if (subSecondNanoseconds == 0) {
2504 return true;
2507 // Step 3. (Reordered)
2508 if (!result.append('.')) {
2509 return false;
2512 // Steps 1.b-c.
2513 uint32_t k = 100'000'000;
2514 do {
2515 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2516 return false;
2518 subSecondNanoseconds %= k;
2519 k /= 10;
2520 } while (subSecondNanoseconds);
2521 } else {
2522 // Step 2.a.
2523 uint8_t p = precision.value();
2524 if (p == 0) {
2525 return true;
2528 // Step 3. (Reordered)
2529 if (!result.append('.')) {
2530 return false;
2533 // Steps 2.b-c.
2534 uint32_t k = 100'000'000;
2535 for (uint8_t i = 0; i < precision.value(); i++) {
2536 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2537 return false;
2539 subSecondNanoseconds %= k;
2540 k /= 10;
2544 return true;
2548 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2549 * normSeconds, precision )
2551 static JSString* TemporalDurationToString(JSContext* cx,
2552 const Duration& duration,
2553 Precision precision) {
2554 MOZ_ASSERT(IsValidDuration(duration));
2555 MOZ_ASSERT(precision != Precision::Minute());
2557 // Convert to absolute values up front. This is okay to do, because when the
2558 // duration is valid, all components have the same sign.
2559 const auto& [years, months, weeks, days, hours, minutes, seconds,
2560 milliseconds, microseconds, nanoseconds] =
2561 AbsoluteDuration(duration);
2563 // Fast path for zero durations.
2564 if (years == 0 && months == 0 && weeks == 0 && days == 0 && hours == 0 &&
2565 minutes == 0 && seconds == 0 && milliseconds == 0 && microseconds == 0 &&
2566 nanoseconds == 0 &&
2567 (precision == Precision::Auto() || precision.value() == 0)) {
2568 return NewStringCopyZ<CanGC>(cx, "PT0S");
2571 auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
2572 microseconds, nanoseconds);
2574 // Step 1.
2575 int32_t sign = DurationSign(duration);
2577 // Steps 2 and 7.
2578 JSStringBuilder result(cx);
2580 // Step 13. (Reordered)
2581 if (sign < 0) {
2582 if (!result.append('-')) {
2583 return nullptr;
2587 // Step 14. (Reordered)
2588 if (!result.append('P')) {
2589 return nullptr;
2592 // Step 3.
2593 if (years != 0) {
2594 if (!NumberToStringBuilder(cx, years, result)) {
2595 return nullptr;
2597 if (!result.append('Y')) {
2598 return nullptr;
2602 // Step 4.
2603 if (months != 0) {
2604 if (!NumberToStringBuilder(cx, months, result)) {
2605 return nullptr;
2607 if (!result.append('M')) {
2608 return nullptr;
2612 // Step 5.
2613 if (weeks != 0) {
2614 if (!NumberToStringBuilder(cx, weeks, result)) {
2615 return nullptr;
2617 if (!result.append('W')) {
2618 return nullptr;
2622 // Step 6.
2623 if (days != 0) {
2624 if (!NumberToStringBuilder(cx, days, result)) {
2625 return nullptr;
2627 if (!result.append('D')) {
2628 return nullptr;
2632 // Step 7. (Moved above)
2634 // Steps 10-11. (Reordered)
2635 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
2636 days == 0 && hours == 0 && minutes == 0;
2638 // Steps 8-9, 12, and 15.
2639 bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
2640 zeroMinutesAndHigher || precision != Precision::Auto();
2641 if (hours != 0 || minutes != 0 || hasSecondsPart) {
2642 // Step 15. (Reordered)
2643 if (!result.append('T')) {
2644 return nullptr;
2647 // Step 8.
2648 if (hours != 0) {
2649 if (!NumberToStringBuilder(cx, hours, result)) {
2650 return nullptr;
2652 if (!result.append('H')) {
2653 return nullptr;
2657 // Step 9.
2658 if (minutes != 0) {
2659 if (!NumberToStringBuilder(cx, minutes, result)) {
2660 return nullptr;
2662 if (!result.append('M')) {
2663 return nullptr;
2667 // Step 12.
2668 if (hasSecondsPart) {
2669 // Step 12.a.
2670 if (!NumberToStringBuilder(cx, secondsDuration.seconds, result)) {
2671 return nullptr;
2674 // Step 12.b.
2675 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
2676 precision)) {
2677 return nullptr;
2680 // Step 12.c.
2681 if (!result.append('S')) {
2682 return nullptr;
2687 // Steps 13-15. (Moved above)
2689 // Step 16.
2690 return result.finishString();
2694 * ToRelativeTemporalObject ( options )
2696 static bool ToRelativeTemporalObject(
2697 JSContext* cx, Handle<JSObject*> options,
2698 MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
2699 MutableHandle<ZonedDateTime> zonedRelativeTo,
2700 MutableHandle<TimeZoneRecord> timeZoneRecord) {
2701 // Step 1.
2702 Rooted<Value> value(cx);
2703 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
2704 return false;
2707 // Step 2.
2708 if (value.isUndefined()) {
2709 // FIXME: spec issue - switch return record fields for consistency.
2710 // FIXME: spec bug - [[TimeZoneRec]] field not created
2712 plainRelativeTo.set(nullptr);
2713 zonedRelativeTo.set(ZonedDateTime{});
2714 timeZoneRecord.set(TimeZoneRecord{});
2715 return true;
2718 // Step 3.
2719 auto offsetBehaviour = OffsetBehaviour::Option;
2721 // Step 4.
2722 auto matchBehaviour = MatchBehaviour::MatchExactly;
2724 // Steps 5-6.
2725 PlainDateTime dateTime;
2726 Rooted<CalendarValue> calendar(cx);
2727 Rooted<TimeZoneValue> timeZone(cx);
2728 int64_t offsetNs;
2729 if (value.isObject()) {
2730 Rooted<JSObject*> obj(cx, &value.toObject());
2732 // Step 5.a.
2733 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
2734 auto instant = ToInstant(zonedDateTime);
2735 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
2736 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
2738 if (!timeZone.wrap(cx)) {
2739 return false;
2741 if (!calendar.wrap(cx)) {
2742 return false;
2745 // Step 5.a.i.
2746 Rooted<TimeZoneRecord> timeZoneRec(cx);
2747 if (!CreateTimeZoneMethodsRecord(
2748 cx, timeZone,
2750 TimeZoneMethod::GetOffsetNanosecondsFor,
2751 TimeZoneMethod::GetPossibleInstantsFor,
2753 &timeZoneRec)) {
2754 return false;
2757 // Step 5.a.ii.
2758 plainRelativeTo.set(nullptr);
2759 zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
2760 timeZoneRecord.set(timeZoneRec);
2761 return true;
2764 // Step 5.b.
2765 if (obj->canUnwrapAs<PlainDateObject>()) {
2766 plainRelativeTo.set(obj);
2767 zonedRelativeTo.set(ZonedDateTime{});
2768 timeZoneRecord.set(TimeZoneRecord{});
2769 return true;
2772 // Step 5.c.
2773 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
2774 auto plainDateTime = ToPlainDate(dateTime);
2776 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
2777 if (!calendar.wrap(cx)) {
2778 return false;
2781 // Step 5.c.i.
2782 auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
2783 if (!plainDate) {
2784 return false;
2787 // Step 5.c.ii.
2788 plainRelativeTo.set(plainDate);
2789 zonedRelativeTo.set(ZonedDateTime{});
2790 timeZoneRecord.set(TimeZoneRecord{});
2791 return true;
2794 // Step 5.d.
2795 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
2796 return false;
2799 // Step 5.e.
2800 Rooted<CalendarRecord> calendarRec(cx);
2801 if (!CreateCalendarMethodsRecord(cx, calendar,
2803 CalendarMethod::DateFromFields,
2804 CalendarMethod::Fields,
2806 &calendarRec)) {
2807 return false;
2810 // Step 5.f.
2811 JS::RootedVector<PropertyKey> fieldNames(cx);
2812 if (!CalendarFields(cx, calendarRec,
2813 {CalendarField::Day, CalendarField::Month,
2814 CalendarField::MonthCode, CalendarField::Year},
2815 &fieldNames)) {
2816 return false;
2819 // Step 5.g.
2820 if (!AppendSorted(cx, fieldNames.get(),
2822 TemporalField::Hour,
2823 TemporalField::Microsecond,
2824 TemporalField::Millisecond,
2825 TemporalField::Minute,
2826 TemporalField::Nanosecond,
2827 TemporalField::Offset,
2828 TemporalField::Second,
2829 TemporalField::TimeZone,
2830 })) {
2831 return false;
2834 // Step 5.h.
2835 Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, obj, fieldNames));
2836 if (!fields) {
2837 return false;
2840 // Step 5.i.
2841 Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
2842 if (!dateOptions) {
2843 return false;
2846 // Step 5.j.
2847 Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
2848 if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
2849 return false;
2852 // Step 5.k.
2853 if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
2854 &dateTime)) {
2855 return false;
2858 // Step 5.l.
2859 Rooted<Value> offset(cx);
2860 if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
2861 return false;
2864 // Step 5.m.
2865 Rooted<Value> timeZoneValue(cx);
2866 if (!GetProperty(cx, fields, fields, cx->names().timeZone,
2867 &timeZoneValue)) {
2868 return false;
2871 // Step 5.n.
2872 if (!timeZoneValue.isUndefined()) {
2873 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
2874 return false;
2878 // Step 5.o.
2879 if (offset.isUndefined()) {
2880 offsetBehaviour = OffsetBehaviour::Wall;
2883 // Steps 8-9.
2884 if (timeZone) {
2885 if (offsetBehaviour == OffsetBehaviour::Option) {
2886 MOZ_ASSERT(!offset.isUndefined());
2887 MOZ_ASSERT(offset.isString());
2889 // Step 8.a.
2890 Rooted<JSString*> offsetString(cx, offset.toString());
2891 if (!offsetString) {
2892 return false;
2895 // Step 8.b.
2896 if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
2897 return false;
2899 } else {
2900 // Step 9.
2901 offsetNs = 0;
2904 } else {
2905 // Step 6.a.
2906 if (!value.isString()) {
2907 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
2908 nullptr, "not a string");
2909 return false;
2911 Rooted<JSString*> string(cx, value.toString());
2913 // Step 6.b.
2914 bool isUTC;
2915 bool hasOffset;
2916 int64_t timeZoneOffset;
2917 Rooted<ParsedTimeZone> timeZoneName(cx);
2918 Rooted<JSString*> calendarString(cx);
2919 if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
2920 &hasOffset, &timeZoneOffset,
2921 &timeZoneName, &calendarString)) {
2922 return false;
2925 // Step 6.c. (Not applicable in our implementation.)
2927 // Steps 6.e-f.
2928 if (timeZoneName) {
2929 // Step 6.f.i.
2930 if (!ToTemporalTimeZone(cx, timeZoneName, &timeZone)) {
2931 return false;
2934 // Steps 6.f.ii-iii.
2935 if (isUTC) {
2936 offsetBehaviour = OffsetBehaviour::Exact;
2937 } else if (!hasOffset) {
2938 offsetBehaviour = OffsetBehaviour::Wall;
2941 // Step 6.f.iv.
2942 matchBehaviour = MatchBehaviour::MatchMinutes;
2943 } else {
2944 MOZ_ASSERT(!timeZone);
2947 // Steps 6.g-j.
2948 if (calendarString) {
2949 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
2950 return false;
2952 } else {
2953 calendar.set(CalendarValue(cx->names().iso8601));
2956 // Steps 8-9.
2957 if (timeZone) {
2958 if (offsetBehaviour == OffsetBehaviour::Option) {
2959 MOZ_ASSERT(hasOffset);
2961 // Step 8.a.
2962 offsetNs = timeZoneOffset;
2963 } else {
2964 // Step 9.
2965 offsetNs = 0;
2970 // Step 7.
2971 if (!timeZone) {
2972 // Step 7.a.
2973 auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
2974 if (!plainDate) {
2975 return false;
2978 plainRelativeTo.set(plainDate);
2979 zonedRelativeTo.set(ZonedDateTime{});
2980 timeZoneRecord.set(TimeZoneRecord{});
2981 return true;
2984 // Steps 8-9. (Moved above)
2986 // Step 10.
2987 Rooted<TimeZoneRecord> timeZoneRec(cx);
2988 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2990 TimeZoneMethod::GetOffsetNanosecondsFor,
2991 TimeZoneMethod::GetPossibleInstantsFor,
2993 &timeZoneRec)) {
2994 return false;
2997 // Step 11.
2998 Instant epochNanoseconds;
2999 if (!InterpretISODateTimeOffset(
3000 cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
3001 TemporalDisambiguation::Compatible, TemporalOffset::Reject,
3002 matchBehaviour, &epochNanoseconds)) {
3003 return false;
3005 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
3007 // Step 12.
3008 plainRelativeTo.set(nullptr);
3009 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
3010 timeZoneRecord.set(timeZoneRec);
3011 return true;
3015 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
3016 * methods )
3018 static bool CreateCalendarMethodsRecordFromRelativeTo(
3019 JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3020 Handle<ZonedDateTime> zonedRelativeTo,
3021 mozilla::EnumSet<CalendarMethod> methods,
3022 MutableHandle<CalendarRecord> result) {
3023 // Step 1.
3024 if (zonedRelativeTo) {
3025 return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
3026 result);
3029 // Step 2.
3030 if (plainRelativeTo) {
3031 auto* unwrapped = plainRelativeTo.unwrap(cx);
3032 if (!unwrapped) {
3033 return false;
3036 Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
3037 if (!calendar.wrap(cx)) {
3038 return false;
3041 return CreateCalendarMethodsRecord(cx, calendar, methods, result);
3044 // Step 3.
3045 return true;
3048 struct RoundedNumber final {
3049 Int128 rounded;
3050 double total;
3054 * RoundNumberToIncrement ( x, increment, roundingMode )
3056 static RoundedNumber TruncateNumber(int64_t numerator, int64_t denominator) {
3057 // Computes the quotient and real number value of the rational number
3058 // |numerator / denominator|.
3060 // Int64 division truncates.
3061 int64_t quot = numerator / denominator;
3062 int64_t rem = numerator % denominator;
3064 // The total value is stored as a mathematical number in the draft proposal,
3065 // so we can't convert it to a double without loss of precision. We use two
3066 // different approaches to compute the total value based on the input range.
3068 // For example:
3070 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3071 // is |16.66668333...| and the best possible approximation is
3072 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3073 // numerator and denominator to doubles and then performing a double division.
3075 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3076 // can't use double division, because |14400000000000001| can't be represented
3077 // as an exact double value. The exact result is |4000.0000000000002777...|.
3079 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3080 // be computed through |q + r / denominator|.
3081 double total;
3082 if (::IsSafeInteger(numerator) && ::IsSafeInteger(denominator)) {
3083 total = double(numerator) / double(denominator);
3084 } else {
3085 total = double(quot) + double(rem) / double(denominator);
3087 return {Int128{quot}, total};
3091 * RoundNumberToIncrement ( x, increment, roundingMode )
3093 static RoundedNumber TruncateNumber(const Int128& numerator,
3094 const Int128& denominator) {
3095 MOZ_ASSERT(denominator > Int128{});
3096 MOZ_ASSERT(numerator > Int128{INT64_MAX} || denominator > Int128{INT64_MAX},
3097 "small values use the int64 overload");
3099 // Int128 division truncates.
3100 auto [quot, rem] = numerator.divrem(denominator);
3102 double total = double(quot) + double(rem) / double(denominator);
3103 return {quot, total};
3106 struct RoundedDuration final {
3107 NormalizedDuration duration;
3108 double total = 0;
3111 enum class ComputeRemainder : bool { No, Yes };
3114 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3116 static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
3117 const NormalizedTimeDuration& duration, const TemporalUnit unit,
3118 Increment increment, TemporalRoundingMode roundingMode) {
3119 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3120 MOZ_ASSERT(unit > TemporalUnit::Day);
3121 MOZ_ASSERT(increment <= MaximumTemporalDurationRoundingIncrement(unit));
3123 int64_t divisor = ToNanoseconds(unit) * increment.value();
3124 MOZ_ASSERT(divisor > 0);
3125 MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
3127 auto totalNanoseconds = duration.toTotalNanoseconds();
3128 auto rounded =
3129 RoundNumberToIncrement(totalNanoseconds, Int128{divisor}, roundingMode);
3130 return NormalizedTimeDuration::fromNanoseconds(rounded);
3134 * DivideNormalizedTimeDuration ( d, divisor )
3136 static double TotalNormalizedTimeDuration(
3137 const NormalizedTimeDuration& duration, const TemporalUnit unit) {
3138 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3139 MOZ_ASSERT(unit > TemporalUnit::Day);
3141 // Compute real number value of the rational number |numerator / denominator|.
3143 auto numerator = duration.toTotalNanoseconds();
3144 auto denominator = ToNanoseconds(unit);
3145 MOZ_ASSERT(::IsSafeInteger(denominator));
3147 // The total value is stored as a mathematical number in the draft proposal,
3148 // so we can't convert it to a double without loss of precision. We use two
3149 // different approaches to compute the total value based on the input range.
3151 // For example:
3153 // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
3154 // is |16.66668333...| and the best possible approximation is
3155 // |16.666683333333335070...𝔽|. We can this approximation when casting both
3156 // numerator and denominator to doubles and then performing a double division.
3158 // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
3159 // can't use double division, because |14400000000000001| can't be represented
3160 // as an exact double value. The exact result is |4000.0000000000002777...|.
3162 // The best possible approximation is |4000.0000000000004547...𝔽|, which can
3163 // be computed through |q + r / denominator|.
3164 if (::IsSafeInteger(numerator)) {
3165 return double(numerator) / double(denominator);
3168 auto [q, r] = numerator.divrem(Int128{denominator});
3169 return double(q) + double(r) / double(denominator);
3173 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3174 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3175 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3177 NormalizedTimeDuration js::temporal::RoundDuration(
3178 const NormalizedTimeDuration& duration, Increment increment,
3179 TemporalUnit unit, TemporalRoundingMode roundingMode) {
3180 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3181 MOZ_ASSERT(unit > TemporalUnit::Day);
3183 // Steps 1-13. (Not applicable)
3185 // Steps 14-19.
3186 auto rounded = RoundNormalizedTimeDurationToIncrement(
3187 duration, unit, increment, roundingMode);
3188 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded));
3190 // Step 20.
3191 return rounded;
3194 static int64_t TruncateDays(const NormalizedTimeAndDays& timeAndDays,
3195 int64_t days, int32_t daysToAdd) {
3196 #ifdef DEBUG
3197 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3198 static constexpr int64_t durationDays = (int64_t(1) << 53) / (24 * 60 * 60);
3200 // Numbers of days between nsMinInstant and nsMaxInstant.
3201 static constexpr int32_t epochDays = 200'000'000;
3202 #endif
3204 MOZ_ASSERT(std::abs(days) <= durationDays);
3205 MOZ_ASSERT(std::abs(timeAndDays.days) <= durationDays);
3206 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays);
3208 static_assert(durationDays + durationDays + epochDays <= INT64_MAX,
3209 "addition can't overflow");
3211 int64_t totalDays = days + timeAndDays.days + daysToAdd;
3213 int64_t truncatedDays = totalDays;
3214 if (timeAndDays.time > 0) {
3215 // Round toward positive infinity when the integer days are negative and
3216 // the fractional part is positive.
3217 if (truncatedDays < 0) {
3218 truncatedDays += 1;
3220 } else if (timeAndDays.time < 0) {
3221 // Round toward negative infinity when the integer days are positive and
3222 // the fractional part is negative.
3223 if (truncatedDays > 0) {
3224 truncatedDays -= 1;
3228 return truncatedDays;
3231 static bool DaysIsNegative(int64_t days,
3232 const NormalizedTimeAndDays& timeAndDays,
3233 int32_t daysToAdd) {
3234 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3235 static constexpr int64_t durationDays = (int64_t(1) << 53) / (24 * 60 * 60);
3237 // Numbers of days between nsMinInstant and nsMaxInstant.
3238 static constexpr int32_t epochDays = 200'000'000;
3240 MOZ_ASSERT(std::abs(days) <= durationDays);
3241 MOZ_ASSERT(std::abs(timeAndDays.days) <= durationDays);
3242 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3244 static_assert(durationDays + durationDays + epochDays * 2 <= INT64_MAX,
3245 "addition can't overflow");
3247 int64_t totalDays = days + timeAndDays.days + daysToAdd;
3248 return totalDays < 0 || (totalDays == 0 && timeAndDays.time < 0);
3251 static RoundedNumber RoundNumberToIncrement(
3252 int64_t durationAmount, int64_t amountPassed, int64_t durationDays,
3253 int32_t daysToAdd, const NormalizedTimeAndDays& timeAndDays,
3254 int32_t oneUnitDays, Increment increment, TemporalRoundingMode roundingMode,
3255 ComputeRemainder computeRemainder) {
3256 #ifdef DEBUG
3257 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3258 static constexpr int64_t maxDurationDays =
3259 (int64_t(1) << 53) / (24 * 60 * 60);
3261 // Numbers of days between nsMinInstant and nsMaxInstant.
3262 static constexpr int32_t epochDays = 200'000'000;
3263 #endif
3265 MOZ_ASSERT(std::abs(durationAmount) < (int64_t(1) << 32));
3266 MOZ_ASSERT(std::abs(amountPassed) < (int64_t(1) << 32));
3267 MOZ_ASSERT(std::abs(durationDays) <= maxDurationDays);
3268 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3269 MOZ_ASSERT(timeAndDays.dayLength > 0);
3270 MOZ_ASSERT(timeAndDays.dayLength < (int64_t(1) << 53));
3271 MOZ_ASSERT(std::abs(timeAndDays.time) < timeAndDays.dayLength);
3272 MOZ_ASSERT(std::abs(timeAndDays.days) <= maxDurationDays);
3273 MOZ_ASSERT(oneUnitDays != 0);
3274 MOZ_ASSERT(std::abs(oneUnitDays) <= epochDays);
3275 MOZ_ASSERT(increment <= Increment::max());
3277 // clang-format off
3279 // Change the representation of |fractionalWeeks| from a real number to a
3280 // rational number, because we don't support arbitrary precision real
3281 // numbers.
3283 // |fractionalWeeks| is defined as:
3285 // fractionalWeeks
3286 // = weeks + days' / abs(oneWeekDays)
3288 // where days' = days + nanoseconds / dayLength.
3290 // The fractional part |nanoseconds / dayLength| is from step 4.
3292 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3294 // fractionalWeeks
3295 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3296 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3297 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3299 // clang-format on
3301 do {
3302 auto dayLength = mozilla::CheckedInt64(timeAndDays.dayLength);
3304 auto denominator = dayLength * std::abs(oneUnitDays);
3305 if (!denominator.isValid()) {
3306 break;
3309 auto totalDays = mozilla::CheckedInt64(durationDays);
3310 totalDays += timeAndDays.days;
3311 totalDays += daysToAdd;
3312 MOZ_ASSERT(totalDays.isValid());
3314 auto totalAmount = mozilla::CheckedInt64(durationAmount) + amountPassed;
3315 MOZ_ASSERT(totalAmount.isValid());
3317 auto amountNanos = denominator * totalAmount;
3318 if (!amountNanos.isValid()) {
3319 break;
3322 auto totalNanoseconds = dayLength * totalDays;
3323 totalNanoseconds += timeAndDays.time;
3324 totalNanoseconds += amountNanos;
3325 if (!totalNanoseconds.isValid()) {
3326 break;
3329 if (computeRemainder == ComputeRemainder::Yes) {
3330 return TruncateNumber(totalNanoseconds.value(), denominator.value());
3333 auto rounded = RoundNumberToIncrement(
3334 totalNanoseconds.value(), denominator.value(), increment, roundingMode);
3335 constexpr double total = 0;
3336 return {rounded, total};
3337 } while (false);
3339 // Use int128 when values are too large for int64. Additionally assert all
3340 // values fit into int128.
3342 // `dayLength` < 2**53
3343 auto dayLength = Int128{timeAndDays.dayLength};
3344 MOZ_ASSERT(dayLength < Int128{1} << 53);
3346 // `abs(oneUnitDays)` < 200'000'000, log2(200'000'000) = ~27.57.
3347 auto denominator = dayLength * Int128{std::abs(oneUnitDays)};
3348 MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
3350 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3352 // `abs(maxDurationDays)` ≤ 2**(53 - 16).
3353 // `abs(timeAndDays.days)` ≤ 2**(53 - 16).
3354 // `abs(daysToAdd)` ≤ 2**29.
3356 // 2**(53 - 16) + 2**(53 - 16) + 2**29
3357 // = 2**37 + 2**37 + 2**29
3358 // = 2**38 + 2**29
3359 // ≤ 2**39
3360 auto totalDays = Int128{durationDays};
3361 totalDays += Int128{timeAndDays.days};
3362 totalDays += Int128{daysToAdd};
3363 MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
3365 // `abs(durationAmount)` ≤ 2**32
3366 // `abs(amountPassed)` ≤ 2**32
3367 auto totalAmount = Int128{durationAmount} + Int128{amountPassed};
3368 MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
3370 // `denominator` < 2**(53 + 28)
3371 // `abs(totalAmount)` <= 2**33
3373 // `denominator * totalAmount`
3374 // ≤ 2**(53 + 28) * 2**33
3375 // = 2**(53 + 28 + 33)
3376 // = 2**114
3377 auto amountNanos = denominator * totalAmount;
3378 MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
3380 // `dayLength` < 2**53
3381 // `totalDays` ≤ 2**39
3382 // `timeAndDays.time` < `dayLength` < 2**53
3383 // `amountNanos` ≤ 2**114
3385 // `dayLength * totalDays`
3386 // ≤ 2**(53 + 39) = 2**92
3388 // `dayLength * totalDays + timeAndDays.time`
3389 // ≤ 2**93
3391 // `dayLength * totalDays + timeAndDays.time + amountNanos`
3392 // ≤ 2**115
3393 auto totalNanoseconds = dayLength * totalDays;
3394 totalNanoseconds += Int128{timeAndDays.time};
3395 totalNanoseconds += amountNanos;
3396 MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
3398 if (computeRemainder == ComputeRemainder::Yes) {
3399 return TruncateNumber(totalNanoseconds, denominator);
3402 auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
3403 increment, roundingMode);
3404 constexpr double total = 0;
3405 return {rounded, total};
3408 static RoundedNumber RoundNumberToIncrement(
3409 double durationDays, const NormalizedTimeAndDays& timeAndDays,
3410 Increment increment, TemporalRoundingMode roundingMode,
3411 ComputeRemainder computeRemainder) {
3412 constexpr int64_t daysAmount = 0;
3413 constexpr int64_t daysPassed = 0;
3414 constexpr int32_t oneDayDays = 1;
3415 constexpr int32_t daysToAdd = 0;
3417 return RoundNumberToIncrement(daysAmount, daysPassed, durationDays, daysToAdd,
3418 timeAndDays, oneDayDays, increment,
3419 roundingMode, computeRemainder);
3422 static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
3423 const NormalizedTimeAndDays& timeAndDays,
3424 Increment increment,
3425 TemporalRoundingMode roundingMode,
3426 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3427 Handle<CalendarRecord> calendar,
3428 ComputeRemainder computeRemainder,
3429 RoundedDuration* result) {
3430 // Numbers of days between nsMinInstant and nsMaxInstant.
3431 static constexpr int32_t epochDays = 200'000'000;
3433 auto [years, months, weeks, days] = duration.date;
3435 // Step 10.a.
3436 Duration yearsDuration = {double(years)};
3438 // Step 10.b.
3439 auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
3440 if (!yearsLater) {
3441 return false;
3443 auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
3445 // Step 10.f. (Reordered)
3446 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
3448 // Step 10.c.
3449 Duration yearsMonthsWeeks = {double(years), double(months), double(weeks)};
3451 // Step 10.d.
3452 PlainDate yearsMonthsWeeksLater;
3453 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3454 &yearsMonthsWeeksLater)) {
3455 return false;
3458 // Step 10.e.
3459 int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
3460 MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
3462 // Step 10.f. (Moved up)
3464 // Step 10.g.
3465 // Our implementation keeps |days| and |monthsWeeksInDays| separate.
3467 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3468 // https://github.com/tc39/proposal-temporal/issues/2540
3470 // Step 10.h.
3471 int64_t truncatedDays = TruncateDays(timeAndDays, days, monthsWeeksInDays);
3473 PlainDate isoResult;
3474 if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, truncatedDays},
3475 TemporalOverflow::Constrain, &isoResult)) {
3476 return false;
3479 // Step 10.i.
3480 Rooted<PlainDateObject*> wholeDaysLater(
3481 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3482 if (!wholeDaysLater) {
3483 return false;
3486 // Steps 10.j-l.
3487 Duration timePassed;
3488 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3489 TemporalUnit::Year, &timePassed)) {
3490 return false;
3493 // Step 10.m.
3494 int64_t yearsPassed = int64_t(timePassed.years);
3496 // Step 10.n.
3497 // Our implementation keeps |years| and |yearsPassed| separate.
3499 // Step 10.o.
3500 Duration yearsPassedDuration = {double(yearsPassed)};
3502 // Steps 10.p-r.
3503 int32_t daysPassed;
3504 if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
3505 &newRelativeTo, &daysPassed)) {
3506 return false;
3508 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3510 // Step 10.s.
3512 // Our implementation keeps |days| and |daysPassed| separate.
3513 int32_t daysToAdd = monthsWeeksInDays - daysPassed;
3514 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3516 // Steps 10.t.
3517 double sign = DaysIsNegative(days, timeAndDays, daysToAdd) ? -1 : 1;
3519 // Step 10.u.
3520 Duration oneYear = {sign};
3522 // Steps 10.v-w.
3523 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3524 int32_t oneYearDays;
3525 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
3526 &moveResultIgnored, &oneYearDays)) {
3527 return false;
3530 // Step 10.x.
3531 if (oneYearDays == 0) {
3532 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3533 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3534 return false;
3537 // Steps 10.y-aa.
3538 auto [numYears, total] = RoundNumberToIncrement(
3539 years, yearsPassed, days, daysToAdd, timeAndDays, oneYearDays, increment,
3540 roundingMode, computeRemainder);
3542 // Step 10.ab.
3543 int64_t numMonths = 0;
3544 int64_t numWeeks = 0;
3546 // Step 10.ac.
3547 constexpr auto time = NormalizedTimeDuration{};
3549 // Step 20.
3550 if (numYears.abs() >= (Uint128{1} << 32)) {
3551 return ThrowInvalidDurationPart(cx, double(numYears), "years",
3552 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3555 auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
3556 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3557 return false;
3560 *result = {{resultDuration, time}, total};
3561 return true;
3564 static bool RoundDurationMonth(JSContext* cx,
3565 const NormalizedDuration& duration,
3566 const NormalizedTimeAndDays& timeAndDays,
3567 Increment increment,
3568 TemporalRoundingMode roundingMode,
3569 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3570 Handle<CalendarRecord> calendar,
3571 ComputeRemainder computeRemainder,
3572 RoundedDuration* result) {
3573 // Numbers of days between nsMinInstant and nsMaxInstant.
3574 static constexpr int32_t epochDays = 200'000'000;
3576 auto [years, months, weeks, days] = duration.date;
3578 // Step 11.a.
3579 Duration yearsMonths = {double(years), double(months)};
3581 // Step 11.b.
3582 auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
3583 if (!yearsMonthsLater) {
3584 return false;
3586 auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
3588 // Step 11.f. (Reordered)
3589 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
3591 // Step 11.c.
3592 Duration yearsMonthsWeeks = {double(years), double(months), double(weeks)};
3594 // Step 11.d.
3595 PlainDate yearsMonthsWeeksLater;
3596 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3597 &yearsMonthsWeeksLater)) {
3598 return false;
3601 // Step 11.e.
3602 int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
3603 MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
3605 // Step 11.f. (Moved up)
3607 // Step 11.g.
3608 // Our implementation keeps |days| and |weeksInDays| separate.
3610 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3611 // https://github.com/tc39/proposal-temporal/issues/2540
3613 // Step 11.h.
3614 int64_t truncatedDays = TruncateDays(timeAndDays, days, weeksInDays);
3616 PlainDate isoResult;
3617 if (!AddISODate(cx, yearsMonthsLaterDate, {0, 0, 0, truncatedDays},
3618 TemporalOverflow::Constrain, &isoResult)) {
3619 return false;
3622 // Step 11.i.
3623 Rooted<PlainDateObject*> wholeDaysLater(
3624 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3625 if (!wholeDaysLater) {
3626 return false;
3629 // Steps 11.j-l.
3630 Duration timePassed;
3631 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3632 TemporalUnit::Month, &timePassed)) {
3633 return false;
3636 // Step 11.m.
3637 int64_t monthsPassed = int64_t(timePassed.months);
3639 // Step 11.n.
3640 // Our implementation keeps |months| and |monthsPassed| separate.
3642 // Step 11.o.
3643 Duration monthsPassedDuration = {0, double(monthsPassed)};
3645 // Steps 11.p-r.
3646 int32_t daysPassed;
3647 if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
3648 &newRelativeTo, &daysPassed)) {
3649 return false;
3651 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3653 // Step 11.s.
3655 // Our implementation keeps |days| and |daysPassed| separate.
3656 int32_t daysToAdd = weeksInDays - daysPassed;
3657 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
3659 // Steps 11.t.
3660 double sign = DaysIsNegative(days, timeAndDays, daysToAdd) ? -1 : 1;
3662 // Step 11.u.
3663 Duration oneMonth = {0, sign};
3665 // Steps 11.v-w.
3666 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3667 int32_t oneMonthDays;
3668 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
3669 &moveResultIgnored, &oneMonthDays)) {
3670 return false;
3673 // Step 11.x.
3674 if (oneMonthDays == 0) {
3675 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3676 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3677 return false;
3680 // Steps 11.y-aa.
3681 auto [numMonths, total] = RoundNumberToIncrement(
3682 months, monthsPassed, days, daysToAdd, timeAndDays, oneMonthDays,
3683 increment, roundingMode, computeRemainder);
3685 // Step 11.ab.
3686 int64_t numWeeks = 0;
3688 // Step 11.ac.
3689 constexpr auto time = NormalizedTimeDuration{};
3691 // Step 21.
3692 if (numMonths.abs() >= (Uint128{1} << 32)) {
3693 return ThrowInvalidDurationPart(cx, double(numMonths), "months",
3694 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3697 auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
3698 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3699 return false;
3702 *result = {{resultDuration, time}, total};
3703 return true;
3706 static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
3707 const NormalizedTimeAndDays& timeAndDays,
3708 Increment increment,
3709 TemporalRoundingMode roundingMode,
3710 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3711 Handle<CalendarRecord> calendar,
3712 ComputeRemainder computeRemainder,
3713 RoundedDuration* result) {
3714 // Numbers of days between nsMinInstant and nsMaxInstant.
3715 static constexpr int32_t epochDays = 200'000'000;
3717 auto [years, months, weeks, days] = duration.date;
3719 auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
3720 if (!unwrappedRelativeTo) {
3721 return false;
3723 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
3725 // Step 12.a
3726 int64_t truncatedDays = TruncateDays(timeAndDays, days, 0);
3728 PlainDate isoResult;
3729 if (!AddISODate(cx, relativeToDate, {0, 0, 0, truncatedDays},
3730 TemporalOverflow::Constrain, &isoResult)) {
3731 return false;
3734 // Step 12.b.
3735 Rooted<PlainDateObject*> wholeDaysLater(
3736 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3737 if (!wholeDaysLater) {
3738 return false;
3741 // Steps 12.c-e.
3742 Duration timePassed;
3743 if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
3744 TemporalUnit::Week, &timePassed)) {
3745 return false;
3748 // Step 12.f.
3749 int64_t weeksPassed = int64_t(timePassed.weeks);
3751 // Step 12.g.
3752 // Our implementation keeps |weeks| and |weeksPassed| separate.
3754 // Step 12.h.
3755 Duration weeksPassedDuration = {0, 0, double(weeksPassed)};
3757 // Steps 12.i-k.
3758 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
3759 int32_t daysPassed;
3760 if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
3761 &newRelativeTo, &daysPassed)) {
3762 return false;
3764 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3766 // Step 12.l.
3768 // Our implementation keeps |days| and |daysPassed| separate.
3769 int32_t daysToAdd = -daysPassed;
3770 MOZ_ASSERT(std::abs(daysToAdd) <= epochDays);
3772 // Steps 12.m.
3773 double sign = DaysIsNegative(days, timeAndDays, daysToAdd) ? -1 : 1;
3775 // Step 12.n.
3776 Duration oneWeek = {0, 0, sign};
3778 // Steps 12.o-p.
3779 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3780 int32_t oneWeekDays;
3781 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
3782 &moveResultIgnored, &oneWeekDays)) {
3783 return false;
3786 // Step 12.q.
3787 if (oneWeekDays == 0) {
3788 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3789 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3790 return false;
3793 // Steps 12.r-t.
3794 auto [numWeeks, total] = RoundNumberToIncrement(
3795 weeks, weeksPassed, days, daysToAdd, timeAndDays, oneWeekDays, increment,
3796 roundingMode, computeRemainder);
3798 // Step 12.u.
3799 constexpr auto time = NormalizedTimeDuration{};
3801 // Step 20.
3802 if (numWeeks.abs() >= (Uint128{1} << 32)) {
3803 return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
3804 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3807 auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
3808 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3809 return false;
3812 *result = {{resultDuration, time}, total};
3813 return true;
3816 static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
3817 const NormalizedTimeAndDays& timeAndDays,
3818 Increment increment,
3819 TemporalRoundingMode roundingMode,
3820 ComputeRemainder computeRemainder,
3821 RoundedDuration* result) {
3822 auto [years, months, weeks, days] = duration.date;
3824 // Steps 13.a-b.
3825 auto [numDays, total] = RoundNumberToIncrement(
3826 days, timeAndDays, increment, roundingMode, computeRemainder);
3828 MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
3829 "rounded days fits in int64");
3831 // Step 13.c.
3832 constexpr auto time = NormalizedTimeDuration{};
3834 // Step 20.
3835 auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
3836 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3837 return false;
3840 *result = {{resultDuration, time}, total};
3841 return true;
3845 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3846 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3847 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3849 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
3850 Increment increment, TemporalUnit unit,
3851 TemporalRoundingMode roundingMode,
3852 ComputeRemainder computeRemainder,
3853 RoundedDuration* result) {
3854 // The remainder is only needed when called from |Duration_total|. And `total`
3855 // always passes |increment=1| and |roundingMode=trunc|.
3856 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3857 increment == Increment{1});
3858 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3859 roundingMode == TemporalRoundingMode::Trunc);
3861 // Steps 1-5. (Not applicable.)
3863 // Step 6.
3864 if (unit <= TemporalUnit::Week) {
3865 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3866 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3867 "relativeTo");
3868 return false;
3871 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3872 // because in that case this operation is a no-op. This case happens for
3873 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3874 // options object.
3876 // But maybe this can be even more efficiently handled in the callers. For
3877 // example when Temporal.PlainTime.prototype.{since,until} is called without
3878 // an options object, we can not only skip the RoundDuration call, but also
3879 // the following BalanceTimeDuration call.
3881 // Step 7. (Moved below.)
3883 // Steps 8-9. (Not applicable.)
3885 // Steps 10-12. (Not applicable.)
3887 // Step 13.
3888 if (unit == TemporalUnit::Day) {
3889 // Step 7.
3890 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3892 return RoundDurationDay(cx, duration, timeAndDays, increment, roundingMode,
3893 computeRemainder, result);
3896 MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
3898 // Steps 14-19.
3899 auto time = duration.time;
3900 double total = 0;
3901 if (computeRemainder == ComputeRemainder::No) {
3902 time = RoundNormalizedTimeDurationToIncrement(time, unit, increment,
3903 roundingMode);
3904 } else {
3905 MOZ_ASSERT(increment == Increment{1});
3906 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
3908 total = TotalNormalizedTimeDuration(duration.time, unit);
3910 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
3912 // Step 20.
3913 MOZ_ASSERT(IsValidDuration(duration.date.toDuration()));
3914 *result = {{duration.date, time}, total};
3915 return true;
3919 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3920 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3921 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3923 static bool RoundDuration(
3924 JSContext* cx, const NormalizedDuration& duration, Increment increment,
3925 TemporalUnit unit, TemporalRoundingMode roundingMode,
3926 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3927 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
3928 Handle<TimeZoneRecord> timeZone,
3929 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
3930 ComputeRemainder computeRemainder, RoundedDuration* result) {
3931 // Note: |duration.days| can have a different sign than the other date
3932 // components. The date and time components can have different signs, too.
3933 MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
3934 double(duration.date.months),
3935 double(duration.date.weeks)}));
3936 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
3938 MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
3939 "Use RoundDuration without relativeTo when plainRelativeTo and "
3940 "zonedRelativeTo are both undefined");
3942 // The remainder is only needed when called from |Duration_total|. And `total`
3943 // always passes |increment=1| and |roundingMode=trunc|.
3944 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3945 increment == Increment{1});
3946 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3947 roundingMode == TemporalRoundingMode::Trunc);
3949 // Steps 1-5. (Not applicable in our implementation.)
3951 // Step 6.a. (Not applicable in our implementation.)
3952 MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
3954 // Step 6.b.
3955 MOZ_ASSERT_IF(
3956 unit <= TemporalUnit::Week,
3957 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3959 // Step 6.c.
3960 MOZ_ASSERT_IF(
3961 unit <= TemporalUnit::Week,
3962 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3964 switch (unit) {
3965 case TemporalUnit::Year:
3966 case TemporalUnit::Month:
3967 case TemporalUnit::Week:
3968 break;
3969 case TemporalUnit::Day:
3970 // We can't take the faster code path when |zonedRelativeTo| is present.
3971 if (zonedRelativeTo) {
3972 break;
3974 [[fallthrough]];
3975 case TemporalUnit::Hour:
3976 case TemporalUnit::Minute:
3977 case TemporalUnit::Second:
3978 case TemporalUnit::Millisecond:
3979 case TemporalUnit::Microsecond:
3980 case TemporalUnit::Nanosecond:
3981 // Steps 7-9 and 13-21.
3982 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
3983 computeRemainder, result);
3984 case TemporalUnit::Auto:
3985 MOZ_CRASH("Unexpected temporal unit");
3988 // Step 7.
3989 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
3991 // Steps 7.a-c.
3992 NormalizedTimeAndDays timeAndDays;
3993 if (zonedRelativeTo) {
3994 // Step 7.a.i.
3995 Rooted<ZonedDateTime> intermediate(cx);
3996 if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
3997 duration.date, precalculatedPlainDateTime,
3998 &intermediate)) {
3999 return false;
4002 // Steps 7.a.ii.
4003 if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
4004 &timeAndDays)) {
4005 return false;
4008 // Step 7.a.iii. (Not applicable in our implementation.)
4009 } else {
4010 // Step 7.b. (Partial)
4011 timeAndDays = ::NormalizedTimeDurationToDays(duration.time);
4014 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
4015 // less than |timeAndDays.dayLength|.
4016 MOZ_ASSERT(std::abs(timeAndDays.time) < timeAndDays.dayLength);
4018 // Step 7.c. (Moved below)
4020 // Step 8. (Not applicable)
4022 // Step 9.
4023 // FIXME: spec issue - `total` doesn't need be initialised.
4025 // Steps 10-20.
4026 switch (unit) {
4027 // Steps 10 and 20.
4028 case TemporalUnit::Year:
4029 return RoundDurationYear(cx, duration, timeAndDays, increment,
4030 roundingMode, plainRelativeTo, calendar,
4031 computeRemainder, result);
4033 // Steps 11 and 20.
4034 case TemporalUnit::Month:
4035 return RoundDurationMonth(cx, duration, timeAndDays, increment,
4036 roundingMode, plainRelativeTo, calendar,
4037 computeRemainder, result);
4039 // Steps 12 and 20.
4040 case TemporalUnit::Week:
4041 return RoundDurationWeek(cx, duration, timeAndDays, increment,
4042 roundingMode, plainRelativeTo, calendar,
4043 computeRemainder, result);
4045 // Steps 13 and 20.
4046 case TemporalUnit::Day:
4047 return RoundDurationDay(cx, duration, timeAndDays, increment,
4048 roundingMode, computeRemainder, result);
4050 // Steps 14-19. (Handled elsewhere)
4051 case TemporalUnit::Auto:
4052 case TemporalUnit::Hour:
4053 case TemporalUnit::Minute:
4054 case TemporalUnit::Second:
4055 case TemporalUnit::Millisecond:
4056 case TemporalUnit::Microsecond:
4057 case TemporalUnit::Nanosecond:
4058 break;
4061 MOZ_CRASH("Unexpected temporal unit");
4065 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4066 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4067 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4069 static bool RoundDuration(
4070 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4071 TemporalUnit unit, TemporalRoundingMode roundingMode,
4072 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4073 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4074 Handle<TimeZoneRecord> timeZone,
4075 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4076 double* result) {
4077 // Only called from |Duration_total|, which always passes |increment=1| and
4078 // |roundingMode=trunc|.
4079 MOZ_ASSERT(increment == Increment{1});
4080 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4082 RoundedDuration rounded;
4083 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4084 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4085 precalculatedPlainDateTime, ComputeRemainder::Yes,
4086 &rounded)) {
4087 return false;
4090 *result = rounded.total;
4091 return true;
4095 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4096 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4097 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4099 static bool RoundDuration(
4100 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4101 TemporalUnit unit, TemporalRoundingMode roundingMode,
4102 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4103 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4104 Handle<TimeZoneRecord> timeZone,
4105 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4106 NormalizedDuration* result) {
4107 RoundedDuration rounded;
4108 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4109 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4110 precalculatedPlainDateTime, ComputeRemainder::No,
4111 &rounded)) {
4112 return false;
4115 *result = rounded.duration;
4116 return true;
4120 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4121 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4122 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4124 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4125 Increment increment, TemporalUnit unit,
4126 TemporalRoundingMode roundingMode, double* result) {
4127 MOZ_ASSERT(IsValidDuration(duration));
4129 // Only called from |Duration_total|, which always passes |increment=1| and
4130 // |roundingMode=trunc|.
4131 MOZ_ASSERT(increment == Increment{1});
4132 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4134 RoundedDuration rounded;
4135 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4136 ComputeRemainder::Yes, &rounded)) {
4137 return false;
4140 *result = rounded.total;
4141 return true;
4145 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4146 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4147 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4149 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4150 Increment increment, TemporalUnit unit,
4151 TemporalRoundingMode roundingMode,
4152 NormalizedDuration* result) {
4153 MOZ_ASSERT(IsValidDuration(duration));
4155 RoundedDuration rounded;
4156 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4157 ComputeRemainder::No, &rounded)) {
4158 return false;
4161 *result = rounded.duration;
4162 return true;
4166 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4167 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4168 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4170 bool js::temporal::RoundDuration(
4171 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4172 TemporalUnit unit, TemporalRoundingMode roundingMode,
4173 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4174 Handle<CalendarRecord> calendar, NormalizedDuration* result) {
4175 MOZ_ASSERT(IsValidDuration(duration));
4177 Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
4178 Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
4179 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4180 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4181 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4182 precalculatedPlainDateTime, result);
4186 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4187 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4188 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4190 bool js::temporal::RoundDuration(
4191 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4192 TemporalUnit unit, TemporalRoundingMode roundingMode,
4193 Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
4194 Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
4195 const PlainDateTime& precalculatedPlainDateTime,
4196 NormalizedDuration* result) {
4197 MOZ_ASSERT(IsValidDuration(duration));
4199 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4200 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4201 mozilla::SomeRef(precalculatedPlainDateTime), result);
4204 enum class DurationOperation { Add, Subtract };
4207 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4208 * options )
4210 static bool AddDurationToOrSubtractDurationFromDuration(
4211 JSContext* cx, DurationOperation operation, const CallArgs& args) {
4212 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4213 auto duration = ToDuration(durationObj);
4215 // Step 1. (Not applicable in our implementation.)
4217 // Step 2.
4218 Duration other;
4219 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
4220 return false;
4223 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4224 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4225 Rooted<TimeZoneRecord> timeZone(cx);
4226 if (args.hasDefined(1)) {
4227 const char* name = operation == DurationOperation::Add ? "add" : "subtract";
4229 // Step 3.
4230 Rooted<JSObject*> options(cx,
4231 RequireObjectArg(cx, "options", name, args[1]));
4232 if (!options) {
4233 return false;
4236 // Steps 4-7.
4237 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4238 &zonedRelativeTo, &timeZone)) {
4239 return false;
4241 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4242 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4245 // Step 8.
4246 Rooted<CalendarRecord> calendar(cx);
4247 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4248 zonedRelativeTo,
4250 CalendarMethod::DateAdd,
4251 CalendarMethod::DateUntil,
4253 &calendar)) {
4254 return false;
4257 // Step 9.
4258 if (operation == DurationOperation::Subtract) {
4259 other = other.negate();
4262 Duration result;
4263 if (plainRelativeTo) {
4264 if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
4265 return false;
4267 } else if (zonedRelativeTo) {
4268 if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
4269 &result)) {
4270 return false;
4272 } else {
4273 if (!AddDuration(cx, duration, other, &result)) {
4274 return false;
4278 // Step 10.
4279 auto* obj = CreateTemporalDuration(cx, result);
4280 if (!obj) {
4281 return false;
4284 args.rval().setObject(*obj);
4285 return true;
4289 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4290 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4291 * ] ] ] ] ] ] )
4293 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
4294 CallArgs args = CallArgsFromVp(argc, vp);
4296 // Step 1.
4297 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
4298 return false;
4301 // Step 2.
4302 double years = 0;
4303 if (args.hasDefined(0) &&
4304 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
4305 return false;
4308 // Step 3.
4309 double months = 0;
4310 if (args.hasDefined(1) &&
4311 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
4312 return false;
4315 // Step 4.
4316 double weeks = 0;
4317 if (args.hasDefined(2) &&
4318 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
4319 return false;
4322 // Step 5.
4323 double days = 0;
4324 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
4325 return false;
4328 // Step 6.
4329 double hours = 0;
4330 if (args.hasDefined(4) &&
4331 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
4332 return false;
4335 // Step 7.
4336 double minutes = 0;
4337 if (args.hasDefined(5) &&
4338 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
4339 return false;
4342 // Step 8.
4343 double seconds = 0;
4344 if (args.hasDefined(6) &&
4345 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
4346 return false;
4349 // Step 9.
4350 double milliseconds = 0;
4351 if (args.hasDefined(7) &&
4352 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
4353 return false;
4356 // Step 10.
4357 double microseconds = 0;
4358 if (args.hasDefined(8) &&
4359 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
4360 return false;
4363 // Step 11.
4364 double nanoseconds = 0;
4365 if (args.hasDefined(9) &&
4366 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
4367 return false;
4370 // Step 12.
4371 auto* duration = CreateTemporalDuration(
4372 cx, args,
4373 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
4374 microseconds, nanoseconds});
4375 if (!duration) {
4376 return false;
4379 args.rval().setObject(*duration);
4380 return true;
4384 * Temporal.Duration.from ( item )
4386 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
4387 CallArgs args = CallArgsFromVp(argc, vp);
4389 Handle<Value> item = args.get(0);
4391 // Step 1.
4392 if (item.isObject()) {
4393 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
4394 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
4395 if (!result) {
4396 return false;
4399 args.rval().setObject(*result);
4400 return true;
4404 // Step 2.
4405 auto result = ToTemporalDuration(cx, item);
4406 if (!result) {
4407 return false;
4410 args.rval().setObject(*result);
4411 return true;
4415 * Temporal.Duration.compare ( one, two [ , options ] )
4417 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
4418 CallArgs args = CallArgsFromVp(argc, vp);
4420 // Step 1.
4421 Duration one;
4422 if (!ToTemporalDuration(cx, args.get(0), &one)) {
4423 return false;
4426 // Step 2.
4427 Duration two;
4428 if (!ToTemporalDuration(cx, args.get(1), &two)) {
4429 return false;
4432 // Step 3.
4433 Rooted<JSObject*> options(cx);
4434 if (args.hasDefined(2)) {
4435 options = RequireObjectArg(cx, "options", "compare", args[2]);
4436 if (!options) {
4437 return false;
4441 // Step 4.
4442 if (one == two) {
4443 args.rval().setInt32(0);
4444 return true;
4447 // Steps 5-8.
4448 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4449 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4450 Rooted<TimeZoneRecord> timeZone(cx);
4451 if (options) {
4452 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4453 &zonedRelativeTo, &timeZone)) {
4454 return false;
4456 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4457 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4460 // Steps 9-10.
4461 auto hasCalendarUnit = [](const auto& d) {
4462 return d.years != 0 || d.months != 0 || d.weeks != 0;
4464 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
4466 // Step 11.
4467 Rooted<CalendarRecord> calendar(cx);
4468 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4469 zonedRelativeTo,
4471 CalendarMethod::DateAdd,
4473 &calendar)) {
4474 return false;
4477 // Step 12.
4478 if (zonedRelativeTo &&
4479 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
4480 // Step 12.a.
4481 const auto& instant = zonedRelativeTo.instant();
4483 // Step 12.b.
4484 PlainDateTime dateTime;
4485 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
4486 return false;
4489 // Step 12.c.
4490 auto normalized1 = CreateNormalizedDurationRecord(one);
4492 // Step 12.d.
4493 auto normalized2 = CreateNormalizedDurationRecord(two);
4495 // Step 12.e.
4496 Instant after1;
4497 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
4498 dateTime, &after1)) {
4499 return false;
4502 // Step 12.f.
4503 Instant after2;
4504 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
4505 dateTime, &after2)) {
4506 return false;
4509 // Steps 12.g-i.
4510 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
4511 return true;
4514 // Steps 13-14.
4515 double days1, days2;
4516 if (calendarUnitsPresent) {
4517 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4519 // Step 13.a.
4520 DateDuration unbalanceResult1;
4521 if (plainRelativeTo) {
4522 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
4523 TemporalUnit::Day, plainRelativeTo,
4524 calendar, &unbalanceResult1)) {
4525 return false;
4527 } else {
4528 if (!UnbalanceDateDurationRelative(
4529 cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
4530 return false;
4532 MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
4535 // Step 13.b.
4536 DateDuration unbalanceResult2;
4537 if (plainRelativeTo) {
4538 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
4539 TemporalUnit::Day, plainRelativeTo,
4540 calendar, &unbalanceResult2)) {
4541 return false;
4543 } else {
4544 if (!UnbalanceDateDurationRelative(
4545 cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
4546 return false;
4548 MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
4551 // Step 13.c.
4552 days1 = unbalanceResult1.days;
4554 // Step 13.d.
4555 days2 = unbalanceResult2.days;
4556 } else {
4557 // Step 14.a.
4558 days1 = one.days;
4560 // Step 14.b.
4561 days2 = two.days;
4564 // Step 15.
4565 auto normalized1 = NormalizeTimeDuration(one);
4567 // Step 16.
4568 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
4569 &normalized1)) {
4570 return false;
4573 // Step 17.
4574 auto normalized2 = NormalizeTimeDuration(two);
4576 // Step 18.
4577 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
4578 &normalized2)) {
4579 return false;
4582 // Step 19.
4583 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
4584 return true;
4588 * get Temporal.Duration.prototype.years
4590 static bool Duration_years(JSContext* cx, const CallArgs& args) {
4591 // Step 3.
4592 auto* duration = &args.thisv().toObject().as<DurationObject>();
4593 args.rval().setNumber(duration->years());
4594 return true;
4598 * get Temporal.Duration.prototype.years
4600 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
4601 // Steps 1-2.
4602 CallArgs args = CallArgsFromVp(argc, vp);
4603 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
4607 * get Temporal.Duration.prototype.months
4609 static bool Duration_months(JSContext* cx, const CallArgs& args) {
4610 // Step 3.
4611 auto* duration = &args.thisv().toObject().as<DurationObject>();
4612 args.rval().setNumber(duration->months());
4613 return true;
4617 * get Temporal.Duration.prototype.months
4619 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
4620 // Steps 1-2.
4621 CallArgs args = CallArgsFromVp(argc, vp);
4622 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
4626 * get Temporal.Duration.prototype.weeks
4628 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
4629 // Step 3.
4630 auto* duration = &args.thisv().toObject().as<DurationObject>();
4631 args.rval().setNumber(duration->weeks());
4632 return true;
4636 * get Temporal.Duration.prototype.weeks
4638 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
4639 // Steps 1-2.
4640 CallArgs args = CallArgsFromVp(argc, vp);
4641 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
4645 * get Temporal.Duration.prototype.days
4647 static bool Duration_days(JSContext* cx, const CallArgs& args) {
4648 // Step 3.
4649 auto* duration = &args.thisv().toObject().as<DurationObject>();
4650 args.rval().setNumber(duration->days());
4651 return true;
4655 * get Temporal.Duration.prototype.days
4657 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
4658 // Steps 1-2.
4659 CallArgs args = CallArgsFromVp(argc, vp);
4660 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
4664 * get Temporal.Duration.prototype.hours
4666 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
4667 // Step 3.
4668 auto* duration = &args.thisv().toObject().as<DurationObject>();
4669 args.rval().setNumber(duration->hours());
4670 return true;
4674 * get Temporal.Duration.prototype.hours
4676 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
4677 // Steps 1-2.
4678 CallArgs args = CallArgsFromVp(argc, vp);
4679 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
4683 * get Temporal.Duration.prototype.minutes
4685 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
4686 // Step 3.
4687 auto* duration = &args.thisv().toObject().as<DurationObject>();
4688 args.rval().setNumber(duration->minutes());
4689 return true;
4693 * get Temporal.Duration.prototype.minutes
4695 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
4696 // Steps 1-2.
4697 CallArgs args = CallArgsFromVp(argc, vp);
4698 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
4702 * get Temporal.Duration.prototype.seconds
4704 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
4705 // Step 3.
4706 auto* duration = &args.thisv().toObject().as<DurationObject>();
4707 args.rval().setNumber(duration->seconds());
4708 return true;
4712 * get Temporal.Duration.prototype.seconds
4714 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
4715 // Steps 1-2.
4716 CallArgs args = CallArgsFromVp(argc, vp);
4717 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
4721 * get Temporal.Duration.prototype.milliseconds
4723 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
4724 // Step 3.
4725 auto* duration = &args.thisv().toObject().as<DurationObject>();
4726 args.rval().setNumber(duration->milliseconds());
4727 return true;
4731 * get Temporal.Duration.prototype.milliseconds
4733 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
4734 // Steps 1-2.
4735 CallArgs args = CallArgsFromVp(argc, vp);
4736 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
4740 * get Temporal.Duration.prototype.microseconds
4742 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
4743 // Step 3.
4744 auto* duration = &args.thisv().toObject().as<DurationObject>();
4745 args.rval().setNumber(duration->microseconds());
4746 return true;
4750 * get Temporal.Duration.prototype.microseconds
4752 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
4753 // Steps 1-2.
4754 CallArgs args = CallArgsFromVp(argc, vp);
4755 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
4759 * get Temporal.Duration.prototype.nanoseconds
4761 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
4762 // Step 3.
4763 auto* duration = &args.thisv().toObject().as<DurationObject>();
4764 args.rval().setNumber(duration->nanoseconds());
4765 return true;
4769 * get Temporal.Duration.prototype.nanoseconds
4771 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
4772 // Steps 1-2.
4773 CallArgs args = CallArgsFromVp(argc, vp);
4774 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
4778 * get Temporal.Duration.prototype.sign
4780 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
4781 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4783 // Step 3.
4784 args.rval().setInt32(DurationSign(duration));
4785 return true;
4789 * get Temporal.Duration.prototype.sign
4791 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
4792 // Steps 1-2.
4793 CallArgs args = CallArgsFromVp(argc, vp);
4794 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
4798 * get Temporal.Duration.prototype.blank
4800 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
4801 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4803 // Steps 3-5.
4804 args.rval().setBoolean(duration == Duration{});
4805 return true;
4809 * get Temporal.Duration.prototype.blank
4811 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
4812 // Steps 1-2.
4813 CallArgs args = CallArgsFromVp(argc, vp);
4814 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
4818 * Temporal.Duration.prototype.with ( temporalDurationLike )
4820 * ToPartialDuration ( temporalDurationLike )
4822 static bool Duration_with(JSContext* cx, const CallArgs& args) {
4823 // Absent values default to the corresponding values of |this| object.
4824 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4826 // Steps 3-23.
4827 Rooted<JSObject*> temporalDurationLike(
4828 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
4829 if (!temporalDurationLike) {
4830 return false;
4832 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
4833 return false;
4836 // Step 24.
4837 auto* result = CreateTemporalDuration(cx, duration);
4838 if (!result) {
4839 return false;
4842 args.rval().setObject(*result);
4843 return true;
4847 * Temporal.Duration.prototype.with ( temporalDurationLike )
4849 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
4850 // Steps 1-2.
4851 CallArgs args = CallArgsFromVp(argc, vp);
4852 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
4856 * Temporal.Duration.prototype.negated ( )
4858 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
4859 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4861 // Step 3.
4862 auto* result = CreateTemporalDuration(cx, duration.negate());
4863 if (!result) {
4864 return false;
4867 args.rval().setObject(*result);
4868 return true;
4872 * Temporal.Duration.prototype.negated ( )
4874 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
4875 // Steps 1-2.
4876 CallArgs args = CallArgsFromVp(argc, vp);
4877 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
4881 * Temporal.Duration.prototype.abs ( )
4883 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
4884 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4886 // Step 3.
4887 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
4888 if (!result) {
4889 return false;
4892 args.rval().setObject(*result);
4893 return true;
4897 * Temporal.Duration.prototype.abs ( )
4899 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
4900 // Steps 1-2.
4901 CallArgs args = CallArgsFromVp(argc, vp);
4902 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
4906 * Temporal.Duration.prototype.add ( other [ , options ] )
4908 static bool Duration_add(JSContext* cx, const CallArgs& args) {
4909 return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
4910 args);
4914 * Temporal.Duration.prototype.add ( other [ , options ] )
4916 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
4917 // Steps 1-2.
4918 CallArgs args = CallArgsFromVp(argc, vp);
4919 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
4923 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4925 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
4926 return AddDurationToOrSubtractDurationFromDuration(
4927 cx, DurationOperation::Subtract, args);
4931 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4933 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
4934 // Steps 1-2.
4935 CallArgs args = CallArgsFromVp(argc, vp);
4936 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
4940 * Temporal.Duration.prototype.round ( roundTo )
4942 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4943 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4945 // Step 18. (Reordered)
4946 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4948 // Steps 3-25.
4949 auto smallestUnit = TemporalUnit::Auto;
4950 TemporalUnit largestUnit;
4951 auto roundingMode = TemporalRoundingMode::HalfExpand;
4952 auto roundingIncrement = Increment{1};
4953 Rooted<JSObject*> relativeTo(cx);
4954 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4955 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4956 Rooted<TimeZoneRecord> timeZone(cx);
4957 if (args.get(0).isString()) {
4958 // Step 4. (Not applicable in our implementation.)
4960 // Steps 6-15. (Not applicable)
4962 // Step 16.
4963 Rooted<JSString*> paramString(cx, args[0].toString());
4964 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
4965 TemporalUnitGroup::DateTime, &smallestUnit)) {
4966 return false;
4969 // Step 17. (Not applicable)
4971 // Step 18. (Moved above)
4973 // Step 19.
4974 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4976 // Step 20. (Not applicable)
4978 // Step 20.a. (Not applicable)
4980 // Step 20.b.
4981 largestUnit = defaultLargestUnit;
4983 // Steps 21-25. (Not applicable)
4984 } else {
4985 // Steps 3 and 5.
4986 Rooted<JSObject*> options(
4987 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
4988 if (!options) {
4989 return false;
4992 // Step 6.
4993 bool smallestUnitPresent = true;
4995 // Step 7.
4996 bool largestUnitPresent = true;
4998 // Steps 8-9.
5000 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
5001 // absent "largestUnit" value.
5002 Rooted<Value> largestUnitValue(cx);
5003 if (!GetProperty(cx, options, options, cx->names().largestUnit,
5004 &largestUnitValue)) {
5005 return false;
5008 if (!largestUnitValue.isUndefined()) {
5009 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
5010 if (!largestUnitStr) {
5011 return false;
5014 largestUnit = TemporalUnit::Auto;
5015 if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
5016 TemporalUnitGroup::DateTime, &largestUnit)) {
5017 return false;
5021 // Steps 10-13.
5022 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
5023 &zonedRelativeTo, &timeZone)) {
5024 return false;
5026 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5027 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5029 // Step 14.
5030 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
5031 return false;
5034 // Step 15.
5035 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5036 return false;
5039 // Step 16.
5040 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5041 TemporalUnitGroup::DateTime, &smallestUnit)) {
5042 return false;
5045 // Step 17.
5046 if (smallestUnit == TemporalUnit::Auto) {
5047 // Step 17.a.
5048 smallestUnitPresent = false;
5050 // Step 17.b.
5051 smallestUnit = TemporalUnit::Nanosecond;
5054 // Step 18. (Moved above)
5056 // Step 19.
5057 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5059 // Steps 20-21.
5060 if (largestUnitValue.isUndefined()) {
5061 // Step 20.a.
5062 largestUnitPresent = false;
5064 // Step 20.b.
5065 largestUnit = defaultLargestUnit;
5066 } else if (largestUnit == TemporalUnit::Auto) {
5067 // Step 21.a
5068 largestUnit = defaultLargestUnit;
5071 // Step 22.
5072 if (!smallestUnitPresent && !largestUnitPresent) {
5073 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5074 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
5075 return false;
5078 // Step 23.
5079 if (largestUnit > smallestUnit) {
5080 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5081 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
5082 return false;
5085 // Steps 24-25.
5086 if (smallestUnit > TemporalUnit::Day) {
5087 // Step 24.
5088 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
5090 // Step 25.
5091 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
5092 false)) {
5093 return false;
5098 // Step 26.
5099 bool hoursToDaysConversionMayOccur = false;
5101 // Step 27.
5102 if (duration.days != 0 && zonedRelativeTo) {
5103 hoursToDaysConversionMayOccur = true;
5106 // Step 28.
5107 else if (std::abs(duration.hours) >= 24) {
5108 hoursToDaysConversionMayOccur = true;
5111 // Step 29.
5112 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
5113 roundingIncrement == Increment{1};
5115 // Step 30.
5116 bool calendarUnitsPresent =
5117 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
5119 // Step 31.
5120 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
5121 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
5122 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
5123 std::abs(duration.milliseconds) < 1000 &&
5124 std::abs(duration.microseconds) < 1000 &&
5125 std::abs(duration.nanoseconds) < 1000) {
5126 // Steps 31.a-b.
5127 auto* obj = CreateTemporalDuration(cx, duration);
5128 if (!obj) {
5129 return false;
5132 args.rval().setObject(*obj);
5133 return true;
5136 // Step 32.
5137 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5139 // Step 33.
5140 bool plainDateTimeOrRelativeToWillBeUsed =
5141 !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
5142 calendarUnitsPresent || duration.days != 0;
5144 // Step 34.
5145 PlainDateTime relativeToDateTime;
5146 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5147 // Steps 34.a-b.
5148 const auto& instant = zonedRelativeTo.instant();
5150 // Step 34.c.
5151 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5152 return false;
5154 precalculatedPlainDateTime =
5155 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5157 // Step 34.d.
5158 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5159 zonedRelativeTo.calendar());
5160 if (!plainRelativeTo) {
5161 return false;
5165 // Step 35.
5166 Rooted<CalendarRecord> calendar(cx);
5167 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5168 zonedRelativeTo,
5170 CalendarMethod::DateAdd,
5171 CalendarMethod::DateUntil,
5173 &calendar)) {
5174 return false;
5177 // Step 36.
5178 DateDuration unbalanceResult;
5179 if (plainRelativeTo) {
5180 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5181 largestUnit, plainRelativeTo, calendar,
5182 &unbalanceResult)) {
5183 return false;
5185 } else {
5186 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5187 largestUnit, &unbalanceResult)) {
5188 return false;
5190 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5193 // Steps 37-39.
5194 auto roundInput =
5195 NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
5196 NormalizedDuration roundResult;
5197 if (plainRelativeTo || zonedRelativeTo) {
5198 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5199 roundingMode, plainRelativeTo, calendar,
5200 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5201 &roundResult)) {
5202 return false;
5204 } else {
5205 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5206 roundingMode, &roundResult)) {
5207 return false;
5211 // Steps 40-41.
5212 TimeDuration balanceResult;
5213 if (zonedRelativeTo) {
5214 // Step 40.a.
5215 NormalizedDuration adjustResult;
5216 if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
5217 smallestUnit, roundingMode, zonedRelativeTo,
5218 calendar, timeZone,
5219 precalculatedPlainDateTime, &adjustResult)) {
5220 return false;
5222 roundResult = adjustResult;
5224 // Step 40.b.
5225 if (!BalanceTimeDurationRelative(
5226 cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
5227 precalculatedPlainDateTime, &balanceResult)) {
5228 return false;
5230 } else {
5231 // Step 41.a.
5232 NormalizedTimeDuration withDays;
5233 if (!Add24HourDaysToNormalizedTimeDuration(
5234 cx, roundResult.time, roundResult.date.days, &withDays)) {
5235 return false;
5238 // Step 41.b.
5239 balanceResult = temporal::BalanceTimeDuration(withDays, largestUnit);
5242 // Step 41.
5243 auto balanceInput = DateDuration{
5244 roundResult.date.years,
5245 roundResult.date.months,
5246 roundResult.date.weeks,
5247 balanceResult.days,
5249 DateDuration dateResult;
5250 if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
5251 smallestUnit, plainRelativeTo, calendar,
5252 &dateResult)) {
5253 return false;
5256 // Step 42.
5257 auto result = Duration{
5258 double(dateResult.years), double(dateResult.months),
5259 double(dateResult.weeks), double(dateResult.days),
5260 double(balanceResult.hours), double(balanceResult.minutes),
5261 double(balanceResult.seconds), double(balanceResult.milliseconds),
5262 balanceResult.microseconds, balanceResult.nanoseconds,
5264 MOZ_ASSERT(IsValidDuration(result));
5265 auto* obj = CreateTemporalDuration(cx, result);
5266 if (!obj) {
5267 return false;
5270 args.rval().setObject(*obj);
5271 return true;
5275 * Temporal.Duration.prototype.round ( options )
5277 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
5278 // Steps 1-2.
5279 CallArgs args = CallArgsFromVp(argc, vp);
5280 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
5284 * Temporal.Duration.prototype.total ( totalOf )
5286 static bool Duration_total(JSContext* cx, const CallArgs& args) {
5287 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
5288 auto duration = ToDuration(durationObj);
5290 // Steps 3-11.
5291 Rooted<JSObject*> relativeTo(cx);
5292 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5293 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5294 Rooted<TimeZoneRecord> timeZone(cx);
5295 auto unit = TemporalUnit::Auto;
5296 if (args.get(0).isString()) {
5297 // Step 4. (Not applicable in our implementation.)
5299 // Steps 6-10. (Implicit)
5300 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
5302 // Step 11.
5303 Rooted<JSString*> paramString(cx, args[0].toString());
5304 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
5305 TemporalUnitGroup::DateTime, &unit)) {
5306 return false;
5308 } else {
5309 // Steps 3 and 5.
5310 Rooted<JSObject*> totalOf(
5311 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
5312 if (!totalOf) {
5313 return false;
5316 // Steps 6-10.
5317 if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
5318 &zonedRelativeTo, &timeZone)) {
5319 return false;
5321 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5322 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5324 // Step 11.
5325 if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
5326 TemporalUnitGroup::DateTime, &unit)) {
5327 return false;
5330 if (unit == TemporalUnit::Auto) {
5331 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5332 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
5333 return false;
5337 // Step 12.
5338 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5340 // Step 13.
5341 bool plainDateTimeOrRelativeToWillBeUsed =
5342 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
5344 // Step 14.
5345 PlainDateTime relativeToDateTime;
5346 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5347 // Steps 14.a-b.
5348 const auto& instant = zonedRelativeTo.instant();
5350 // Step 14.c.
5351 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5352 return false;
5354 precalculatedPlainDateTime =
5355 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5357 // Step 14.d
5358 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5359 zonedRelativeTo.calendar());
5360 if (!plainRelativeTo) {
5361 return false;
5365 // Step 15.
5366 Rooted<CalendarRecord> calendar(cx);
5367 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5368 zonedRelativeTo,
5370 CalendarMethod::DateAdd,
5371 CalendarMethod::DateUntil,
5373 &calendar)) {
5374 return false;
5377 // Step 16.
5378 DateDuration unbalanceResult;
5379 if (plainRelativeTo) {
5380 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5381 plainRelativeTo, calendar,
5382 &unbalanceResult)) {
5383 return false;
5385 } else {
5386 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5387 &unbalanceResult)) {
5388 return false;
5390 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5393 // Step 17.
5394 int64_t unbalancedDays = unbalanceResult.days;
5396 // Steps 18-19.
5397 int64_t days;
5398 NormalizedTimeDuration balanceResult;
5399 if (zonedRelativeTo) {
5400 // Step 18.a
5401 Rooted<ZonedDateTime> intermediate(cx);
5402 if (!MoveRelativeZonedDateTime(
5403 cx, zonedRelativeTo, calendar, timeZone,
5404 {unbalanceResult.years, unbalanceResult.months,
5405 unbalanceResult.weeks, 0},
5406 precalculatedPlainDateTime, &intermediate)) {
5407 return false;
5410 // Step 18.b.
5411 auto timeDuration = NormalizeTimeDuration(duration);
5413 // Step 18.c
5414 const auto& startNs = intermediate.instant();
5416 // Step 18.d.
5417 const auto& startInstant = startNs;
5419 // Step 18.e.
5420 mozilla::Maybe<PlainDateTime> startDateTime{};
5422 // Steps 18.f-g.
5423 Instant intermediateNs;
5424 if (unbalancedDays != 0) {
5425 // Step 18.f.i.
5426 PlainDateTime dateTime;
5427 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5428 return false;
5430 startDateTime = mozilla::Some(dateTime);
5432 // Step 18.f.ii.
5433 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
5434 Instant addResult;
5435 if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
5436 isoCalendar, unbalancedDays, &addResult)) {
5437 return false;
5440 // Step 18.f.iii.
5441 intermediateNs = addResult;
5442 } else {
5443 // Step 18.g.
5444 intermediateNs = startNs;
5447 // Step 18.h.
5448 Instant endNs;
5449 if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
5450 return false;
5453 // Step 18.i.
5454 auto difference =
5455 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
5457 // Steps 18.j-k.
5459 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5460 if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
5461 difference != NormalizedTimeDuration{}) {
5462 // Step 18.j.i.
5463 if (!startDateTime) {
5464 PlainDateTime dateTime;
5465 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5466 return false;
5468 startDateTime = mozilla::Some(dateTime);
5471 // Step 18.j.ii.
5472 NormalizedTimeAndDays timeAndDays;
5473 if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
5474 *startDateTime, &timeAndDays)) {
5475 return false;
5478 // Step 18.j.iii.
5479 balanceResult = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
5481 // Step 18.j.iv.
5482 days = timeAndDays.days;
5483 } else {
5484 // Step 18.k.i.
5485 balanceResult = difference;
5486 days = 0;
5488 } else {
5489 // Step 19.a.
5490 auto timeDuration = NormalizeTimeDuration(duration);
5492 // Step 19.b.
5493 if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
5494 &balanceResult)) {
5495 return false;
5498 // Step 19.c.
5499 days = 0;
5501 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult));
5503 // Step 20.
5504 auto roundInput = NormalizedDuration{
5506 unbalanceResult.years,
5507 unbalanceResult.months,
5508 unbalanceResult.weeks,
5509 days,
5511 balanceResult,
5513 double total;
5514 if (plainRelativeTo || zonedRelativeTo) {
5515 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5516 TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
5517 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5518 &total)) {
5519 return false;
5521 } else {
5522 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5523 TemporalRoundingMode::Trunc, &total)) {
5524 return false;
5528 // Step 21.
5529 args.rval().setNumber(total);
5530 return true;
5534 * Temporal.Duration.prototype.total ( totalOf )
5536 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
5537 // Steps 1-2.
5538 CallArgs args = CallArgsFromVp(argc, vp);
5539 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
5543 * Temporal.Duration.prototype.toString ( [ options ] )
5545 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
5546 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5548 // Steps 3-9.
5549 SecondsStringPrecision precision = {Precision::Auto(),
5550 TemporalUnit::Nanosecond, Increment{1}};
5551 auto roundingMode = TemporalRoundingMode::Trunc;
5552 if (args.hasDefined(0)) {
5553 // Step 3.
5554 Rooted<JSObject*> options(
5555 cx, RequireObjectArg(cx, "options", "toString", args[0]));
5556 if (!options) {
5557 return false;
5560 // Steps 4-5.
5561 auto digits = Precision::Auto();
5562 if (!ToFractionalSecondDigits(cx, options, &digits)) {
5563 return false;
5566 // Step 6.
5567 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5568 return false;
5571 // Step 7.
5572 auto smallestUnit = TemporalUnit::Auto;
5573 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5574 TemporalUnitGroup::Time, &smallestUnit)) {
5575 return false;
5578 // Step 8.
5579 if (smallestUnit == TemporalUnit::Hour ||
5580 smallestUnit == TemporalUnit::Minute) {
5581 const char* smallestUnitStr =
5582 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
5583 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5584 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
5585 smallestUnitStr, "smallestUnit");
5586 return false;
5589 // Step 9.
5590 precision = ToSecondsStringPrecision(smallestUnit, digits);
5593 // Steps 10-11.
5594 Duration result;
5595 if (precision.unit != TemporalUnit::Nanosecond ||
5596 precision.increment != Increment{1}) {
5597 // Step 10.a.
5598 auto timeDuration = NormalizeTimeDuration(duration);
5600 // Step 10.b.
5601 auto largestUnit = DefaultTemporalLargestUnit(duration);
5603 // Steps 10.c-d.
5604 auto rounded = RoundDuration(timeDuration, precision.increment,
5605 precision.unit, roundingMode);
5607 // Step 10.e.
5608 auto balanced = BalanceTimeDuration(
5609 rounded, std::min(largestUnit, TemporalUnit::Second));
5611 // Step 10.f.
5612 result = {
5613 duration.years, duration.months,
5614 duration.weeks, duration.days + double(balanced.days),
5615 double(balanced.hours), double(balanced.minutes),
5616 double(balanced.seconds), double(balanced.milliseconds),
5617 balanced.microseconds, balanced.nanoseconds,
5619 MOZ_ASSERT(IsValidDuration(duration));
5620 } else {
5621 // Step 11.
5622 result = duration;
5625 // Steps 12-13.
5626 JSString* str = TemporalDurationToString(cx, result, precision.precision);
5627 if (!str) {
5628 return false;
5631 args.rval().setString(str);
5632 return true;
5636 * Temporal.Duration.prototype.toString ( [ options ] )
5638 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
5639 // Steps 1-2.
5640 CallArgs args = CallArgsFromVp(argc, vp);
5641 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
5645 * Temporal.Duration.prototype.toJSON ( )
5647 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
5648 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5650 // Steps 3-4.
5651 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5652 if (!str) {
5653 return false;
5656 args.rval().setString(str);
5657 return true;
5661 * Temporal.Duration.prototype.toJSON ( )
5663 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
5664 // Steps 1-2.
5665 CallArgs args = CallArgsFromVp(argc, vp);
5666 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
5670 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5672 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
5673 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5675 // Steps 3-4.
5676 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5677 if (!str) {
5678 return false;
5681 args.rval().setString(str);
5682 return true;
5686 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5688 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
5689 // Steps 1-2.
5690 CallArgs args = CallArgsFromVp(argc, vp);
5691 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
5695 * Temporal.Duration.prototype.valueOf ( )
5697 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
5698 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
5699 "Duration", "primitive type");
5700 return false;
5703 const JSClass DurationObject::class_ = {
5704 "Temporal.Duration",
5705 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
5706 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
5707 JS_NULL_CLASS_OPS,
5708 &DurationObject::classSpec_,
5711 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
5713 static const JSFunctionSpec Duration_methods[] = {
5714 JS_FN("from", Duration_from, 1, 0),
5715 JS_FN("compare", Duration_compare, 2, 0),
5716 JS_FS_END,
5719 static const JSFunctionSpec Duration_prototype_methods[] = {
5720 JS_FN("with", Duration_with, 1, 0),
5721 JS_FN("negated", Duration_negated, 0, 0),
5722 JS_FN("abs", Duration_abs, 0, 0),
5723 JS_FN("add", Duration_add, 1, 0),
5724 JS_FN("subtract", Duration_subtract, 1, 0),
5725 JS_FN("round", Duration_round, 1, 0),
5726 JS_FN("total", Duration_total, 1, 0),
5727 JS_FN("toString", Duration_toString, 0, 0),
5728 JS_FN("toJSON", Duration_toJSON, 0, 0),
5729 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
5730 JS_FN("valueOf", Duration_valueOf, 0, 0),
5731 JS_FS_END,
5734 static const JSPropertySpec Duration_prototype_properties[] = {
5735 JS_PSG("years", Duration_years, 0),
5736 JS_PSG("months", Duration_months, 0),
5737 JS_PSG("weeks", Duration_weeks, 0),
5738 JS_PSG("days", Duration_days, 0),
5739 JS_PSG("hours", Duration_hours, 0),
5740 JS_PSG("minutes", Duration_minutes, 0),
5741 JS_PSG("seconds", Duration_seconds, 0),
5742 JS_PSG("milliseconds", Duration_milliseconds, 0),
5743 JS_PSG("microseconds", Duration_microseconds, 0),
5744 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
5745 JS_PSG("sign", Duration_sign, 0),
5746 JS_PSG("blank", Duration_blank, 0),
5747 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
5748 JS_PS_END,
5751 const ClassSpec DurationObject::classSpec_ = {
5752 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
5753 GenericCreatePrototype<DurationObject>,
5754 Duration_methods,
5755 nullptr,
5756 Duration_prototype_methods,
5757 Duration_prototype_properties,
5758 nullptr,
5759 ClassSpec::DontDefineConstructor,