Bug 1874684 - Part 31: Correctly reject invalid durations in some RoundDuration calls...
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blob474b5c4e8c1736abe276c29a5f634ae34f5364bc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/Duration.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/EnumSet.h"
13 #include "mozilla/FloatingPoint.h"
14 #include "mozilla/Maybe.h"
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdlib>
19 #include <initializer_list>
20 #include <stdint.h>
21 #include <type_traits>
22 #include <utility>
24 #include "jsnum.h"
25 #include "jspubtd.h"
26 #include "NamespaceImports.h"
28 #include "builtin/temporal/Calendar.h"
29 #include "builtin/temporal/Instant.h"
30 #include "builtin/temporal/Int128.h"
31 #include "builtin/temporal/Int96.h"
32 #include "builtin/temporal/PlainDate.h"
33 #include "builtin/temporal/PlainDateTime.h"
34 #include "builtin/temporal/Temporal.h"
35 #include "builtin/temporal/TemporalFields.h"
36 #include "builtin/temporal/TemporalParser.h"
37 #include "builtin/temporal/TemporalRoundingMode.h"
38 #include "builtin/temporal/TemporalTypes.h"
39 #include "builtin/temporal/TemporalUnit.h"
40 #include "builtin/temporal/TimeZone.h"
41 #include "builtin/temporal/Wrapped.h"
42 #include "builtin/temporal/ZonedDateTime.h"
43 #include "gc/AllocKind.h"
44 #include "gc/Barrier.h"
45 #include "gc/GCEnum.h"
46 #include "js/CallArgs.h"
47 #include "js/CallNonGenericMethod.h"
48 #include "js/Class.h"
49 #include "js/Conversions.h"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
53 #include "js/Id.h"
54 #include "js/Printer.h"
55 #include "js/PropertyDescriptor.h"
56 #include "js/PropertySpec.h"
57 #include "js/RootingAPI.h"
58 #include "js/Value.h"
59 #include "util/StringBuffer.h"
60 #include "vm/BytecodeUtil.h"
61 #include "vm/GlobalObject.h"
62 #include "vm/JSAtomState.h"
63 #include "vm/JSContext.h"
64 #include "vm/JSObject.h"
65 #include "vm/ObjectOperations.h"
66 #include "vm/PlainObject.h"
67 #include "vm/StringType.h"
69 #include "vm/JSObject-inl.h"
70 #include "vm/NativeObject-inl.h"
71 #include "vm/ObjectOperations-inl.h"
73 using namespace js;
74 using namespace js::temporal;
76 static inline bool IsDuration(Handle<Value> v) {
77 return v.isObject() && v.toObject().is<DurationObject>();
80 #ifdef DEBUG
81 static bool IsIntegerOrInfinity(double d) {
82 return IsInteger(d) || std::isinf(d);
85 static bool IsIntegerOrInfinityDuration(const Duration& duration) {
86 const auto& [years, months, weeks, days, hours, minutes, seconds,
87 milliseconds, microseconds, nanoseconds] = duration;
89 // Integers exceeding the Number range are represented as infinity.
91 return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
92 IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
93 IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
94 IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
95 IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
98 static bool IsIntegerDuration(const Duration& duration) {
99 const auto& [years, months, weeks, days, hours, minutes, seconds,
100 milliseconds, microseconds, nanoseconds] = duration;
102 return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
103 IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
104 IsInteger(seconds) && IsInteger(milliseconds) &&
105 IsInteger(microseconds) && IsInteger(nanoseconds);
107 #endif
109 static constexpr bool IsSafeInteger(int64_t x) {
110 constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
111 constexpr int64_t MinSafeInteger = -MaxSafeInteger;
112 return MinSafeInteger < x && x < MaxSafeInteger;
116 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
117 * milliseconds, microseconds, nanoseconds )
119 int32_t js::temporal::DurationSign(const Duration& duration) {
120 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
122 const auto& [years, months, weeks, days, hours, minutes, seconds,
123 milliseconds, microseconds, nanoseconds] = duration;
125 // Step 1.
126 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
127 milliseconds, microseconds, nanoseconds}) {
128 // Step 1.a.
129 if (v < 0) {
130 return -1;
133 // Step 1.b.
134 if (v > 0) {
135 return 1;
139 // Step 2.
140 return 0;
144 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
145 * milliseconds, microseconds, nanoseconds )
147 static int32_t DurationSign(const DateDuration& duration) {
148 const auto& [years, months, weeks, days] = duration;
150 // Step 1.
151 for (auto v : {years, months, weeks, days}) {
152 // Step 1.a.
153 if (v < 0) {
154 return -1;
157 // Step 1.b.
158 if (v > 0) {
159 return 1;
163 // Step 2.
164 return 0;
168 * Normalize a nanoseconds amount into a time duration.
170 static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
171 // Split into seconds and nanoseconds.
172 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
174 return {seconds, nanos};
178 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
179 * value is too large.
181 static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
182 double nanoseconds) {
183 MOZ_ASSERT(IsInteger(nanoseconds));
185 if (auto int96 = Int96::fromInteger(nanoseconds)) {
186 // The number of normalized seconds must not exceed `2**53 - 1`.
187 constexpr auto limit =
188 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
190 if (int96->abs() < limit) {
191 return mozilla::Some(NormalizeNanoseconds(*int96));
194 return mozilla::Nothing();
198 * Normalize a microseconds amount into a time duration.
200 static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
201 // Split into seconds and microseconds.
202 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
204 // Scale microseconds to nanoseconds.
205 int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));
207 return {seconds, nanos};
211 * Normalize a microseconds amount into a time duration. Return Nothing if the
212 * value is too large.
214 static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
215 double microseconds) {
216 MOZ_ASSERT(IsInteger(microseconds));
218 if (auto int96 = Int96::fromInteger(microseconds)) {
219 // The number of normalized seconds must not exceed `2**53 - 1`.
220 constexpr auto limit =
221 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
223 if (int96->abs() < limit) {
224 return mozilla::Some(NormalizeMicroseconds(*int96));
227 return mozilla::Nothing();
231 * Normalize a duration into a time duration. Return Nothing if any duration
232 * value is too large.
234 static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
235 const Duration& duration) {
236 do {
237 auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
238 if (!nanoseconds) {
239 break;
241 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
243 auto microseconds = NormalizeMicroseconds(duration.microseconds);
244 if (!microseconds) {
245 break;
247 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
249 // Overflows for millis/seconds/minutes/hours/days always result in an
250 // invalid normalized time duration.
252 int64_t milliseconds;
253 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
254 break;
257 int64_t seconds;
258 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
259 break;
262 int64_t minutes;
263 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
264 break;
267 int64_t hours;
268 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
269 break;
272 int64_t days;
273 if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
274 break;
277 // Compute the overall amount of milliseconds.
278 mozilla::CheckedInt64 millis = days;
279 millis *= 24;
280 millis += hours;
281 millis *= 60;
282 millis += minutes;
283 millis *= 60;
284 millis += seconds;
285 millis *= 1000;
286 millis += milliseconds;
287 if (!millis.isValid()) {
288 break;
291 auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
292 if (!IsValidNormalizedTimeDuration(milli)) {
293 break;
296 // Compute the overall time duration.
297 auto result = milli + *microseconds + *nanoseconds;
298 if (!IsValidNormalizedTimeDuration(result)) {
299 break;
302 return mozilla::Some(result);
303 } while (false);
305 return mozilla::Nothing();
309 * Normalize a days amount into a time duration. Return Nothing if the value is
310 * too large.
312 static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(int64_t days) {
313 do {
314 // Compute the overall amount of milliseconds.
315 auto millis =
316 mozilla::CheckedInt64(days) * ToMilliseconds(TemporalUnit::Day);
317 if (!millis.isValid()) {
318 break;
321 auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
322 if (!IsValidNormalizedTimeDuration(result)) {
323 break;
326 return mozilla::Some(result);
327 } while (false);
329 return mozilla::Nothing();
333 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
334 * nanoseconds )
336 static NormalizedTimeDuration NormalizeTimeDuration(
337 double hours, double minutes, double seconds, double milliseconds,
338 double microseconds, double nanoseconds) {
339 MOZ_ASSERT(IsInteger(hours));
340 MOZ_ASSERT(IsInteger(minutes));
341 MOZ_ASSERT(IsInteger(seconds));
342 MOZ_ASSERT(IsInteger(milliseconds));
343 MOZ_ASSERT(IsInteger(microseconds));
344 MOZ_ASSERT(IsInteger(nanoseconds));
346 // Steps 1-3.
347 mozilla::CheckedInt64 millis = int64_t(hours);
348 millis *= 60;
349 millis += int64_t(minutes);
350 millis *= 60;
351 millis += int64_t(seconds);
352 millis *= 1000;
353 millis += int64_t(milliseconds);
354 MOZ_ASSERT(millis.isValid());
356 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
358 // Step 4.
359 auto micros = Int96::fromInteger(microseconds);
360 MOZ_ASSERT(micros);
362 normalized += NormalizeMicroseconds(*micros);
364 // Step 5.
365 auto nanos = Int96::fromInteger(nanoseconds);
366 MOZ_ASSERT(nanos);
368 normalized += NormalizeNanoseconds(*nanos);
370 // Step 6.
371 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
373 // Step 7.
374 return normalized;
378 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
379 * nanoseconds )
381 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
382 int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
383 int32_t microseconds, int32_t nanoseconds) {
384 // Steps 1-3.
385 mozilla::CheckedInt64 millis = int64_t(hours);
386 millis *= 60;
387 millis += int64_t(minutes);
388 millis *= 60;
389 millis += int64_t(seconds);
390 millis *= 1000;
391 millis += int64_t(milliseconds);
392 MOZ_ASSERT(millis.isValid());
394 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
396 // Step 4.
397 normalized += NormalizeMicroseconds(Int96{microseconds});
399 // Step 5.
400 normalized += NormalizeNanoseconds(Int96{nanoseconds});
402 // Step 6.
403 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
405 // Step 7.
406 return normalized;
410 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
411 * nanoseconds )
413 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
414 const Duration& duration) {
415 MOZ_ASSERT(IsValidDuration(duration));
417 return ::NormalizeTimeDuration(duration.hours, duration.minutes,
418 duration.seconds, duration.milliseconds,
419 duration.microseconds, duration.nanoseconds);
423 * AddNormalizedTimeDuration ( one, two )
425 static bool AddNormalizedTimeDuration(JSContext* cx,
426 const NormalizedTimeDuration& one,
427 const NormalizedTimeDuration& two,
428 NormalizedTimeDuration* result) {
429 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
430 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
432 // Step 1.
433 auto sum = one + two;
435 // Step 2.
436 if (!IsValidNormalizedTimeDuration(sum)) {
437 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
438 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
439 return false;
442 // Step 3.
443 *result = sum;
444 return true;
448 * SubtractNormalizedTimeDuration ( one, two )
450 static bool SubtractNormalizedTimeDuration(JSContext* cx,
451 const NormalizedTimeDuration& one,
452 const NormalizedTimeDuration& two,
453 NormalizedTimeDuration* result) {
454 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
455 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
457 // Step 1.
458 auto sum = one - two;
460 // Step 2.
461 if (!IsValidNormalizedTimeDuration(sum)) {
462 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
463 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
464 return false;
467 // Step 3.
468 *result = sum;
469 return true;
473 * Add24HourDaysToNormalizedTimeDuration ( d, days )
475 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
476 JSContext* cx, const NormalizedTimeDuration& d, int64_t days,
477 NormalizedTimeDuration* result) {
478 MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
480 // Step 1.
481 auto normalizedDays = NormalizeDays(days);
482 if (!normalizedDays) {
483 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
484 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
485 return false;
488 // Step 2.
489 auto sum = d + *normalizedDays;
490 if (!IsValidNormalizedTimeDuration(sum)) {
491 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
492 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
493 return false;
496 // Step 3.
497 *result = sum;
498 return true;
502 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
504 bool js::temporal::CombineDateAndNormalizedTimeDuration(
505 JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
506 NormalizedDuration* result) {
507 MOZ_ASSERT(IsValidDuration(date));
508 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
510 // Step 1.
511 int32_t dateSign = ::DurationSign(date);
513 // Step 2.
514 int32_t timeSign = NormalizedTimeDurationSign(time);
516 // Step 3
517 if ((dateSign * timeSign) < 0) {
518 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
519 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
520 return false;
523 // Step 4.
524 *result = {date, time};
525 return true;
529 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
531 NormalizedTimeDuration
532 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
533 const Instant& one, const Instant& two) {
534 MOZ_ASSERT(IsValidEpochInstant(one));
535 MOZ_ASSERT(IsValidEpochInstant(two));
537 // Step 1.
538 auto result = one - two;
540 // Step 2.
541 MOZ_ASSERT(IsValidInstantSpan(result));
543 // Step 3.
544 return result.to<NormalizedTimeDuration>();
548 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
549 * milliseconds, microseconds, nanoseconds )
551 bool js::temporal::IsValidDuration(const Duration& duration) {
552 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
554 const auto& [years, months, weeks, days, hours, minutes, seconds,
555 milliseconds, microseconds, nanoseconds] = duration;
557 // Step 1.
558 int32_t sign = DurationSign(duration);
560 // Step 2.
561 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
562 milliseconds, microseconds, nanoseconds}) {
563 // Step 2.a.
564 if (!std::isfinite(v)) {
565 return false;
568 // Step 2.b.
569 if (v < 0 && sign > 0) {
570 return false;
573 // Step 2.c.
574 if (v > 0 && sign < 0) {
575 return false;
579 // Step 3.
580 if (std::abs(years) >= double(int64_t(1) << 32)) {
581 return false;
584 // Step 4.
585 if (std::abs(months) >= double(int64_t(1) << 32)) {
586 return false;
589 // Step 5.
590 if (std::abs(weeks) >= double(int64_t(1) << 32)) {
591 return false;
594 // Steps 6-8.
595 if (!NormalizeSeconds(duration)) {
596 return false;
599 // Step 9.
600 return true;
603 #ifdef DEBUG
605 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
606 * milliseconds, microseconds, nanoseconds )
608 bool js::temporal::IsValidDuration(const DateDuration& duration) {
609 return IsValidDuration(duration.toDuration());
613 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
614 * milliseconds, microseconds, nanoseconds )
616 bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
617 return IsValidDuration(duration.date) &&
618 IsValidNormalizedTimeDuration(duration.time) &&
619 (::DurationSign(duration.date) *
620 NormalizedTimeDurationSign(duration.time) >=
623 #endif
625 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
626 const char* name, unsigned errorNumber) {
627 ToCStringBuf cbuf;
628 const char* numStr = NumberToCString(&cbuf, value);
630 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
631 numStr);
632 return false;
636 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
637 * milliseconds, microseconds, nanoseconds )
639 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
640 const Duration& duration) {
641 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
643 const auto& [years, months, weeks, days, hours, minutes, seconds,
644 milliseconds, microseconds, nanoseconds] = duration;
646 // Step 1.
647 int32_t sign = DurationSign(duration);
649 auto throwIfInvalid = [&](double v, const char* name) {
650 // Step 2.a.
651 if (!std::isfinite(v)) {
652 return ThrowInvalidDurationPart(
653 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
656 // Steps 2.b-c.
657 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
658 return ThrowInvalidDurationPart(cx, v, name,
659 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
662 return true;
665 auto throwIfTooLarge = [&](double v, const char* name) {
666 if (std::abs(v) >= double(int64_t(1) << 32)) {
667 return ThrowInvalidDurationPart(
668 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
670 return true;
673 // Step 2.
674 if (!throwIfInvalid(years, "years")) {
675 return false;
677 if (!throwIfInvalid(months, "months")) {
678 return false;
680 if (!throwIfInvalid(weeks, "weeks")) {
681 return false;
683 if (!throwIfInvalid(days, "days")) {
684 return false;
686 if (!throwIfInvalid(hours, "hours")) {
687 return false;
689 if (!throwIfInvalid(minutes, "minutes")) {
690 return false;
692 if (!throwIfInvalid(seconds, "seconds")) {
693 return false;
695 if (!throwIfInvalid(milliseconds, "milliseconds")) {
696 return false;
698 if (!throwIfInvalid(microseconds, "microseconds")) {
699 return false;
701 if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
702 return false;
705 // Step 3.
706 if (!throwIfTooLarge(years, "years")) {
707 return false;
710 // Step 4.
711 if (!throwIfTooLarge(months, "months")) {
712 return false;
715 // Step 5.
716 if (!throwIfTooLarge(weeks, "weeks")) {
717 return false;
720 // Steps 6-8.
721 if (!NormalizeSeconds(duration)) {
722 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
723 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
724 return false;
727 MOZ_ASSERT(IsValidDuration(duration));
729 // Step 9.
730 return true;
734 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
735 * milliseconds, microseconds, nanoseconds )
737 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
738 const DateDuration& duration) {
739 const auto& [years, months, weeks, days] = duration;
741 // Step 1.
742 int32_t sign = ::DurationSign(duration);
744 auto throwIfInvalid = [&](int64_t v, const char* name) {
745 // Step 2.a. (Not applicable)
747 // Steps 2.b-c.
748 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
749 return ThrowInvalidDurationPart(cx, double(v), name,
750 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
753 return true;
756 auto throwIfTooLarge = [&](int64_t v, const char* name) {
757 if (std::abs(v) >= (int64_t(1) << 32)) {
758 return ThrowInvalidDurationPart(
759 cx, double(v), name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
761 return true;
764 // Step 2.
765 if (!throwIfInvalid(years, "years")) {
766 return false;
768 if (!throwIfInvalid(months, "months")) {
769 return false;
771 if (!throwIfInvalid(weeks, "weeks")) {
772 return false;
774 if (!throwIfInvalid(days, "days")) {
775 return false;
778 // Step 3.
779 if (!throwIfTooLarge(years, "years")) {
780 return false;
783 // Step 4.
784 if (!throwIfTooLarge(months, "months")) {
785 return false;
788 // Step 5.
789 if (!throwIfTooLarge(weeks, "weeks")) {
790 return false;
793 // Steps 6-8.
794 if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
795 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
796 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
797 return false;
800 MOZ_ASSERT(IsValidDuration(duration));
802 // Step 9.
803 return true;
807 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
808 * seconds, milliseconds, microseconds )
810 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
811 MOZ_ASSERT(IsIntegerDuration(duration));
813 // Step 1.
814 if (duration.years != 0) {
815 return TemporalUnit::Year;
818 // Step 2.
819 if (duration.months != 0) {
820 return TemporalUnit::Month;
823 // Step 3.
824 if (duration.weeks != 0) {
825 return TemporalUnit::Week;
828 // Step 4.
829 if (duration.days != 0) {
830 return TemporalUnit::Day;
833 // Step 5.
834 if (duration.hours != 0) {
835 return TemporalUnit::Hour;
838 // Step 6.
839 if (duration.minutes != 0) {
840 return TemporalUnit::Minute;
843 // Step 7.
844 if (duration.seconds != 0) {
845 return TemporalUnit::Second;
848 // Step 8.
849 if (duration.milliseconds != 0) {
850 return TemporalUnit::Millisecond;
853 // Step 9.
854 if (duration.microseconds != 0) {
855 return TemporalUnit::Microsecond;
858 // Step 10.
859 return TemporalUnit::Nanosecond;
863 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
864 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
866 static DurationObject* CreateTemporalDuration(JSContext* cx,
867 const CallArgs& args,
868 const Duration& duration) {
869 const auto& [years, months, weeks, days, hours, minutes, seconds,
870 milliseconds, microseconds, nanoseconds] = duration;
872 // Step 1.
873 if (!ThrowIfInvalidDuration(cx, duration)) {
874 return nullptr;
877 // Steps 2-3.
878 Rooted<JSObject*> proto(cx);
879 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
880 return nullptr;
883 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
884 if (!object) {
885 return nullptr;
888 // Steps 4-13.
889 // Add zero to convert -0 to +0.
890 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
891 object->setFixedSlot(DurationObject::MONTHS_SLOT,
892 NumberValue(months + (+0.0)));
893 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
894 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
895 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
896 object->setFixedSlot(DurationObject::MINUTES_SLOT,
897 NumberValue(minutes + (+0.0)));
898 object->setFixedSlot(DurationObject::SECONDS_SLOT,
899 NumberValue(seconds + (+0.0)));
900 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
901 NumberValue(milliseconds + (+0.0)));
902 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
903 NumberValue(microseconds + (+0.0)));
904 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
905 NumberValue(nanoseconds + (+0.0)));
907 // Step 14.
908 return object;
912 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
913 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
915 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
916 const Duration& duration) {
917 const auto& [years, months, weeks, days, hours, minutes, seconds,
918 milliseconds, microseconds, nanoseconds] = duration;
920 MOZ_ASSERT(IsInteger(years));
921 MOZ_ASSERT(IsInteger(months));
922 MOZ_ASSERT(IsInteger(weeks));
923 MOZ_ASSERT(IsInteger(days));
924 MOZ_ASSERT(IsInteger(hours));
925 MOZ_ASSERT(IsInteger(minutes));
926 MOZ_ASSERT(IsInteger(seconds));
927 MOZ_ASSERT(IsInteger(milliseconds));
928 MOZ_ASSERT(IsInteger(microseconds));
929 MOZ_ASSERT(IsInteger(nanoseconds));
931 // Step 1.
932 if (!ThrowIfInvalidDuration(cx, duration)) {
933 return nullptr;
936 // Steps 2-3.
937 auto* object = NewBuiltinClassInstance<DurationObject>(cx);
938 if (!object) {
939 return nullptr;
942 // Steps 4-13.
943 // Add zero to convert -0 to +0.
944 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
945 object->setFixedSlot(DurationObject::MONTHS_SLOT,
946 NumberValue(months + (+0.0)));
947 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
948 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
949 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
950 object->setFixedSlot(DurationObject::MINUTES_SLOT,
951 NumberValue(minutes + (+0.0)));
952 object->setFixedSlot(DurationObject::SECONDS_SLOT,
953 NumberValue(seconds + (+0.0)));
954 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
955 NumberValue(milliseconds + (+0.0)));
956 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
957 NumberValue(microseconds + (+0.0)));
958 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
959 NumberValue(nanoseconds + (+0.0)));
961 // Step 14.
962 return object;
966 * ToIntegerIfIntegral ( argument )
968 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
969 Handle<Value> argument, double* num) {
970 // Step 1.
971 double d;
972 if (!JS::ToNumber(cx, argument, &d)) {
973 return false;
976 // Step 2.
977 if (!js::IsInteger(d)) {
978 ToCStringBuf cbuf;
979 const char* numStr = NumberToCString(&cbuf, d);
981 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
982 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
983 name);
984 return false;
987 // Step 3.
988 *num = d;
989 return true;
993 * ToIntegerIfIntegral ( argument )
995 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
996 Handle<Value> argument, double* result) {
997 // Step 1.
998 double d;
999 if (!JS::ToNumber(cx, argument, &d)) {
1000 return false;
1003 // Step 2.
1004 if (!js::IsInteger(d)) {
1005 if (auto nameStr = js::QuoteString(cx, name)) {
1006 ToCStringBuf cbuf;
1007 const char* numStr = NumberToCString(&cbuf, d);
1009 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1010 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
1011 nameStr.get());
1013 return false;
1016 // Step 3.
1017 *result = d;
1018 return true;
1022 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1024 static bool ToTemporalPartialDurationRecord(
1025 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
1026 // Steps 1-3. (Not applicable in our implementation.)
1028 Rooted<Value> value(cx);
1029 bool any = false;
1031 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
1032 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
1033 &value)) {
1034 return false;
1037 if (!value.isUndefined()) {
1038 any = true;
1040 if (!ToIntegerIfIntegral(cx, name, value, num)) {
1041 return false;
1044 return true;
1047 // Steps 4-23.
1048 if (!getDurationProperty(cx->names().days, &result->days)) {
1049 return false;
1051 if (!getDurationProperty(cx->names().hours, &result->hours)) {
1052 return false;
1054 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
1055 return false;
1057 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
1058 return false;
1060 if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
1061 return false;
1063 if (!getDurationProperty(cx->names().months, &result->months)) {
1064 return false;
1066 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
1067 return false;
1069 if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
1070 return false;
1072 if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
1073 return false;
1075 if (!getDurationProperty(cx->names().years, &result->years)) {
1076 return false;
1079 // Step 24.
1080 if (!any) {
1081 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1082 JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
1083 return false;
1086 // Step 25.
1087 return true;
1091 * ToTemporalDurationRecord ( temporalDurationLike )
1093 bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
1094 Handle<Value> temporalDurationLike,
1095 Duration* result) {
1096 // Step 1.
1097 if (!temporalDurationLike.isObject()) {
1098 // Step 1.a.
1099 if (!temporalDurationLike.isString()) {
1100 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
1101 temporalDurationLike, nullptr, "not a string");
1102 return false;
1104 Rooted<JSString*> string(cx, temporalDurationLike.toString());
1106 // Step 1.b.
1107 return ParseTemporalDurationString(cx, string, result);
1110 Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
1112 // Step 2.
1113 if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
1114 *result = ToDuration(duration);
1115 return true;
1118 // Step 3.
1119 Duration duration = {};
1121 // Steps 4-14.
1122 if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
1123 return false;
1126 // Step 15.
1127 if (!ThrowIfInvalidDuration(cx, duration)) {
1128 return false;
1131 // Step 16.
1132 *result = duration;
1133 return true;
1137 * ToTemporalDuration ( item )
1139 Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
1140 Handle<Value> item) {
1141 // Step 1.
1142 if (item.isObject()) {
1143 JSObject* itemObj = &item.toObject();
1144 if (itemObj->canUnwrapAs<DurationObject>()) {
1145 return itemObj;
1149 // Step 2.
1150 Duration result;
1151 if (!ToTemporalDurationRecord(cx, item, &result)) {
1152 return nullptr;
1155 // Step 3.
1156 return CreateTemporalDuration(cx, result);
1160 * ToTemporalDuration ( item )
1162 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
1163 Duration* result) {
1164 auto obj = ToTemporalDuration(cx, item);
1165 if (!obj) {
1166 return false;
1169 *result = ToDuration(&obj.unwrap());
1170 return true;
1174 * DaysUntil ( earlier, later )
1176 int32_t js::temporal::DaysUntil(const PlainDate& earlier,
1177 const PlainDate& later) {
1178 MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
1179 MOZ_ASSERT(ISODateTimeWithinLimits(later));
1181 // Steps 1-2.
1182 int32_t epochDaysEarlier = MakeDay(earlier);
1183 MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
1185 // Steps 3-4.
1186 int32_t epochDaysLater = MakeDay(later);
1187 MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
1189 // Step 5.
1190 return epochDaysLater - epochDaysEarlier;
1194 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1196 static bool MoveRelativeDate(
1197 JSContext* cx, Handle<CalendarRecord> calendar,
1198 Handle<Wrapped<PlainDateObject*>> relativeTo, const DateDuration& duration,
1199 MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
1200 int32_t* daysResult) {
1201 auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
1202 if (!unwrappedRelativeTo) {
1203 return false;
1205 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1207 // Step 1.
1208 auto newDate = AddDate(cx, calendar, relativeTo, duration);
1209 if (!newDate) {
1210 return false;
1212 auto later = ToPlainDate(&newDate.unwrap());
1213 relativeToResult.set(newDate);
1215 // Step 2.
1216 *daysResult = DaysUntil(relativeToDate, later);
1217 MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
1219 // Step 3.
1220 return true;
1224 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1225 * months, weeks, days, precalculatedPlainDateTime )
1227 static bool MoveRelativeZonedDateTime(
1228 JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
1229 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
1230 const DateDuration& duration,
1231 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1232 MutableHandle<ZonedDateTime> result) {
1233 // Step 1.
1234 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1235 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1237 // Step 2.
1238 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1239 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1241 // Step 3.
1242 Instant intermediateNs;
1243 if (precalculatedPlainDateTime) {
1244 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1245 duration, *precalculatedPlainDateTime,
1246 &intermediateNs)) {
1247 return false;
1249 } else {
1250 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1251 duration, &intermediateNs)) {
1252 return false;
1255 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
1257 // Step 4.
1258 result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
1259 zonedDateTime.calendar()});
1260 return true;
1264 * Split duration into full days and remainding nanoseconds.
1266 static NormalizedTimeAndDays NormalizedTimeDurationToDays(
1267 const NormalizedTimeDuration& duration) {
1268 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1270 auto [seconds, nanoseconds] = duration;
1271 if (seconds < 0 && nanoseconds > 0) {
1272 seconds += 1;
1273 nanoseconds -= 1'000'000'000;
1276 int64_t days = seconds / ToSeconds(TemporalUnit::Day);
1277 seconds = seconds % ToSeconds(TemporalUnit::Day);
1279 int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
1281 constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
1282 MOZ_ASSERT(std::abs(time) < dayLength);
1284 return {days, time, dayLength};
1288 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1289 * microseconds, nanoseconds )
1291 static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
1292 int64_t minutes, int64_t seconds,
1293 int64_t milliseconds,
1294 int64_t microseconds,
1295 int64_t nanoseconds) {
1296 // Step 1.
1297 MOZ_ASSERT(IsValidDuration(
1298 {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
1299 double(milliseconds), double(microseconds), double(nanoseconds)}));
1301 // All values are safe integers, so we don't need to convert to `double` and
1302 // back for the `ℝ(𝔽(x))` conversion.
1303 MOZ_ASSERT(IsSafeInteger(days));
1304 MOZ_ASSERT(IsSafeInteger(hours));
1305 MOZ_ASSERT(IsSafeInteger(minutes));
1306 MOZ_ASSERT(IsSafeInteger(seconds));
1307 MOZ_ASSERT(IsSafeInteger(milliseconds));
1308 MOZ_ASSERT(IsSafeInteger(microseconds));
1309 MOZ_ASSERT(IsSafeInteger(nanoseconds));
1311 // Step 2.
1312 return {
1313 days,
1314 hours,
1315 minutes,
1316 seconds,
1317 milliseconds,
1318 double(microseconds),
1319 double(nanoseconds),
1324 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1325 * microseconds, nanoseconds )
1327 static TimeDuration CreateTimeDurationRecord(int64_t milliseconds,
1328 const Int128& microseconds,
1329 const Int128& nanoseconds) {
1330 // Step 1.
1331 MOZ_ASSERT(IsValidDuration({0, 0, 0, 0, 0, 0, 0, double(milliseconds),
1332 double(microseconds), double(nanoseconds)}));
1334 // Step 2.
1335 return {
1336 0, 0, 0, 0, milliseconds, double(microseconds), double(nanoseconds),
1341 * BalanceTimeDuration ( norm, largestUnit )
1343 TimeDuration js::temporal::BalanceTimeDuration(
1344 const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
1345 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1346 MOZ_ASSERT(largestUnit <= TemporalUnit::Second,
1347 "fallible fractional seconds units");
1349 auto [seconds, nanoseconds] = duration;
1351 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1352 // Convert these back to their absolute value and adjust the seconds part
1353 // accordingly.
1355 // For example the nanoseconds duration |-1n| is represented as the
1356 // duration {seconds: -1, nanoseconds: 999'999'999}.
1357 if (seconds < 0 && nanoseconds > 0) {
1358 seconds += 1;
1359 nanoseconds -= ToNanoseconds(TemporalUnit::Second);
1362 // Step 1.
1363 int64_t days = 0;
1364 int64_t hours = 0;
1365 int64_t minutes = 0;
1366 int64_t milliseconds = 0;
1367 int64_t microseconds = 0;
1369 // Steps 2-3. (Not applicable in our implementation.)
1371 // We don't need to convert to positive numbers, because integer division
1372 // truncates and the %-operator has modulo semantics.
1374 // Steps 4-10.
1375 switch (largestUnit) {
1376 // Step 4.
1377 case TemporalUnit::Year:
1378 case TemporalUnit::Month:
1379 case TemporalUnit::Week:
1380 case TemporalUnit::Day: {
1381 // Step 4.a.
1382 microseconds = nanoseconds / 1000;
1384 // Step 4.b.
1385 nanoseconds = nanoseconds % 1000;
1387 // Step 4.c.
1388 milliseconds = microseconds / 1000;
1390 // Step 4.d.
1391 microseconds = microseconds % 1000;
1393 // Steps 4.e-f. (Not applicable)
1394 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1396 // Step 4.g.
1397 minutes = seconds / 60;
1399 // Step 4.h.
1400 seconds = seconds % 60;
1402 // Step 4.i.
1403 hours = minutes / 60;
1405 // Step 4.j.
1406 minutes = minutes % 60;
1408 // Step 4.k.
1409 days = hours / 24;
1411 // Step 4.l.
1412 hours = hours % 24;
1414 break;
1417 // Step 5.
1418 case TemporalUnit::Hour: {
1419 // Step 5.a.
1420 microseconds = nanoseconds / 1000;
1422 // Step 5.b.
1423 nanoseconds = nanoseconds % 1000;
1425 // Step 5.c.
1426 milliseconds = microseconds / 1000;
1428 // Step 5.d.
1429 microseconds = microseconds % 1000;
1431 // Steps 5.e-f. (Not applicable)
1432 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1434 // Step 5.g.
1435 minutes = seconds / 60;
1437 // Step 5.h.
1438 seconds = seconds % 60;
1440 // Step 5.i.
1441 hours = minutes / 60;
1443 // Step 5.j.
1444 minutes = minutes % 60;
1446 break;
1449 case TemporalUnit::Minute: {
1450 // Step 6.a.
1451 microseconds = nanoseconds / 1000;
1453 // Step 6.b.
1454 nanoseconds = nanoseconds % 1000;
1456 // Step 6.c.
1457 milliseconds = microseconds / 1000;
1459 // Step 6.d.
1460 microseconds = microseconds % 1000;
1462 // Steps 6.e-f. (Not applicable)
1463 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1465 // Step 6.g.
1466 minutes = seconds / 60;
1468 // Step 6.h.
1469 seconds = seconds % 60;
1471 break;
1474 // Step 7.
1475 case TemporalUnit::Second: {
1476 // Step 7.a.
1477 microseconds = nanoseconds / 1000;
1479 // Step 7.b.
1480 nanoseconds = nanoseconds % 1000;
1482 // Step 7.c.
1483 milliseconds = microseconds / 1000;
1485 // Step 7.d.
1486 microseconds = microseconds % 1000;
1488 // Steps 7.e-f. (Not applicable)
1489 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1491 break;
1494 case TemporalUnit::Millisecond:
1495 case TemporalUnit::Microsecond:
1496 case TemporalUnit::Nanosecond:
1497 case TemporalUnit::Auto:
1498 MOZ_CRASH("Unexpected temporal unit");
1501 // Step 11.
1502 return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
1503 microseconds, nanoseconds);
1507 * BalanceTimeDuration ( norm, largestUnit )
1509 bool js::temporal::BalanceTimeDuration(JSContext* cx,
1510 const NormalizedTimeDuration& duration,
1511 TemporalUnit largestUnit,
1512 TimeDuration* result) {
1513 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1515 auto [seconds, nanoseconds] = duration;
1517 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1518 // Convert these back to their absolute value and adjust the seconds part
1519 // accordingly.
1521 // For example the nanoseconds duration |-1n| is represented as the
1522 // duration {seconds: -1, nanoseconds: 999'999'999}.
1523 if (seconds < 0 && nanoseconds > 0) {
1524 seconds += 1;
1525 nanoseconds -= ToNanoseconds(TemporalUnit::Second);
1528 // Steps 1-3. (Not applicable in our implementation.)
1530 // We don't need to convert to positive numbers, because integer division
1531 // truncates and the %-operator has modulo semantics.
1533 // Steps 4-10.
1534 switch (largestUnit) {
1535 // Steps 4-7.
1536 case TemporalUnit::Year:
1537 case TemporalUnit::Month:
1538 case TemporalUnit::Week:
1539 case TemporalUnit::Day:
1540 case TemporalUnit::Hour:
1541 case TemporalUnit::Minute:
1542 case TemporalUnit::Second:
1543 *result = BalanceTimeDuration(duration, largestUnit);
1544 return true;
1546 // Step 8.
1547 case TemporalUnit::Millisecond: {
1548 // The number of normalized seconds must not exceed `2**53 - 1`.
1549 constexpr auto limit =
1550 (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second);
1552 // The largest possible milliseconds value whose double representation
1553 // doesn't exceed the normalized seconds limit.
1554 constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
1556 // Assert |max| is the maximum allowed milliseconds value.
1557 static_assert(double(max) < double(limit));
1558 static_assert(double(max + 1) >= double(limit));
1560 static_assert((NormalizedTimeDuration::max().seconds + 1) *
1561 ToMilliseconds(TemporalUnit::Second) <=
1562 INT64_MAX,
1563 "total number duration milliseconds fits into int64");
1565 // Step 8.a.
1566 int64_t microseconds = nanoseconds / 1000;
1568 // Step 8.b.
1569 nanoseconds = nanoseconds % 1000;
1571 // Step 8.c.
1572 int64_t milliseconds = microseconds / 1000;
1573 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1575 // Step 8.d.
1576 microseconds = microseconds % 1000;
1578 auto millis =
1579 (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
1580 if (std::abs(millis) > max) {
1581 JS_ReportErrorNumberASCII(
1582 cx, GetErrorMessage, nullptr,
1583 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
1584 return false;
1587 // Step 11.
1588 *result = CreateTimeDurationRecord(millis, Int128{microseconds},
1589 Int128{nanoseconds});
1590 return true;
1593 // Step 9.
1594 case TemporalUnit::Microsecond: {
1595 // The number of normalized seconds must not exceed `2**53 - 1`.
1596 constexpr auto limit = Uint128{int64_t(1) << 53} *
1597 Uint128{ToMicroseconds(TemporalUnit::Second)};
1599 // The largest possible microseconds value whose double representation
1600 // doesn't exceed the normalized seconds limit.
1601 constexpr auto max =
1602 (Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff};
1603 static_assert(max < limit);
1605 // Assert |max| is the maximum allowed microseconds value.
1606 MOZ_ASSERT(double(max) < double(limit));
1607 MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
1609 // Step 9.a.
1610 int64_t microseconds = nanoseconds / 1000;
1611 MOZ_ASSERT(std::abs(microseconds) <= 999'999);
1613 // Step 9.b.
1614 nanoseconds = nanoseconds % 1000;
1616 auto micros =
1617 (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
1618 Int128{microseconds};
1619 if (micros.abs() > max) {
1620 JS_ReportErrorNumberASCII(
1621 cx, GetErrorMessage, nullptr,
1622 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
1623 return false;
1626 // Step 11.
1627 *result = CreateTimeDurationRecord(0, micros, Int128{nanoseconds});
1628 return true;
1631 // Step 10.
1632 case TemporalUnit::Nanosecond: {
1633 // The number of normalized seconds must not exceed `2**53 - 1`.
1634 constexpr auto limit = Uint128{int64_t(1) << 53} *
1635 Uint128{ToNanoseconds(TemporalUnit::Second)};
1637 // The largest possible nanoseconds value whose double representation
1638 // doesn't exceed the normalized seconds limit.
1639 constexpr auto max =
1640 (Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff};
1641 static_assert(max < limit);
1643 // Assert |max| is the maximum allowed nanoseconds value.
1644 MOZ_ASSERT(double(max) < double(limit));
1645 MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
1647 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
1649 auto nanos =
1650 (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
1651 Int128{nanoseconds};
1652 if (nanos.abs() > max) {
1653 JS_ReportErrorNumberASCII(
1654 cx, GetErrorMessage, nullptr,
1655 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
1656 return false;
1659 // Step 11.
1660 *result = CreateTimeDurationRecord(0, Int128{}, nanos);
1661 return true;
1664 case TemporalUnit::Auto:
1665 break;
1667 MOZ_CRASH("Unexpected temporal unit");
1671 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1672 * timeZoneRec, precalculatedPlainDateTime )
1674 static bool BalanceTimeDurationRelative(
1675 JSContext* cx, const NormalizedDuration& duration, TemporalUnit largestUnit,
1676 Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
1677 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1678 TimeDuration* result) {
1679 MOZ_ASSERT(IsValidDuration(duration));
1681 // Step 1.
1682 const auto& startNs = relativeTo.instant();
1684 // Step 2.
1685 const auto& startInstant = startNs;
1687 // Step 3.
1688 auto intermediateNs = startNs;
1690 // Step 4.
1691 PlainDateTime startDateTime;
1692 if (duration.date.days != 0) {
1693 // Step 4.a.
1694 if (!precalculatedPlainDateTime) {
1695 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1696 return false;
1698 precalculatedPlainDateTime =
1699 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1702 // Steps 4.b-c.
1703 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
1704 if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
1705 timeZone, isoCalendar, duration.date.days,
1706 &intermediateNs)) {
1707 return false;
1711 // Step 5.
1712 Instant endNs;
1713 if (!AddInstant(cx, intermediateNs, duration.time, &endNs)) {
1714 return false;
1716 MOZ_ASSERT(IsValidEpochInstant(endNs));
1718 // Step 6.
1719 auto normalized =
1720 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startInstant);
1722 // Step 7.
1723 if (normalized == NormalizedTimeDuration{}) {
1724 *result = {};
1725 return true;
1728 // Steps 8-9.
1729 int64_t days = 0;
1730 if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
1731 // Step 8.a.
1732 if (!precalculatedPlainDateTime) {
1733 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1734 return false;
1736 precalculatedPlainDateTime =
1737 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1740 // Step 8.b.
1741 NormalizedTimeAndDays timeAndDays;
1742 if (!NormalizedTimeDurationToDays(cx, normalized, relativeTo, timeZone,
1743 *precalculatedPlainDateTime,
1744 &timeAndDays)) {
1745 return false;
1748 // Step 8.c.
1749 days = timeAndDays.days;
1751 // Step 8.d.
1752 normalized = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
1753 MOZ_ASSERT_IF(days > 0, normalized >= NormalizedTimeDuration{});
1754 MOZ_ASSERT_IF(days < 0, normalized <= NormalizedTimeDuration{});
1756 // Step 8.e.
1757 largestUnit = TemporalUnit::Hour;
1760 // Step 10.
1761 TimeDuration balanceResult;
1762 if (!BalanceTimeDuration(cx, normalized, largestUnit, &balanceResult)) {
1763 return false;
1766 // Step 11.
1767 *result = {
1768 days,
1769 balanceResult.hours,
1770 balanceResult.minutes,
1771 balanceResult.seconds,
1772 balanceResult.milliseconds,
1773 balanceResult.microseconds,
1774 balanceResult.nanoseconds,
1776 MOZ_ASSERT(IsValidDuration(result->toDuration()));
1777 return true;
1781 * CreateDateDurationRecord ( years, months, weeks, days )
1783 static DateDuration CreateDateDurationRecord(int64_t years, int64_t months,
1784 int64_t weeks, int64_t days) {
1785 MOZ_ASSERT(IsValidDuration(Duration{
1786 double(years),
1787 double(months),
1788 double(weeks),
1789 double(days),
1790 }));
1791 return {years, months, weeks, days};
1795 * CreateDateDurationRecord ( years, months, weeks, days )
1797 static bool CreateDateDurationRecord(JSContext* cx, int64_t years,
1798 int64_t months, int64_t weeks,
1799 int64_t days, DateDuration* result) {
1800 auto duration = DateDuration{years, months, weeks, days};
1801 if (!ThrowIfInvalidDuration(cx, duration)) {
1802 return false;
1805 *result = duration;
1806 return true;
1809 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration& duration,
1810 TemporalUnit largestUnit) {
1811 MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
1813 // Steps 2-4.
1814 return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
1815 (largestUnit > TemporalUnit::Month && duration.months != 0) ||
1816 (largestUnit > TemporalUnit::Week && duration.weeks != 0);
1820 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1821 * plainRelativeTo, calendarRec )
1823 static bool UnbalanceDateDurationRelative(
1824 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1825 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1826 Handle<CalendarRecord> calendar, DateDuration* result) {
1827 MOZ_ASSERT(IsValidDuration(duration));
1829 auto [years, months, weeks, days] = duration;
1831 // Step 1. (Not applicable in our implementation.)
1833 // Steps 2-4.
1834 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1835 *result = duration;
1836 return true;
1839 // Step 5.
1840 MOZ_ASSERT(largestUnit != TemporalUnit::Year);
1842 // Step 6. (Not applicable in our implementation.)
1844 // Step 7.
1845 MOZ_ASSERT(
1846 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1848 // Step 8.
1849 if (largestUnit == TemporalUnit::Month) {
1850 // Step 8.a.
1851 MOZ_ASSERT(
1852 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1854 // Step 8.b.
1855 auto yearsDuration = DateDuration{years};
1857 // Step 8.c.
1858 Rooted<Wrapped<PlainDateObject*>> later(
1859 cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
1860 if (!later) {
1861 return false;
1864 // Steps 8.d-f.
1865 Duration untilResult;
1866 if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
1867 TemporalUnit::Month, &untilResult)) {
1868 return false;
1871 // Step 8.g.
1872 int64_t yearsInMonths = int64_t(untilResult.months);
1874 // Step 8.h.
1875 return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
1876 result);
1879 // Step 9.
1880 if (largestUnit == TemporalUnit::Week) {
1881 // Step 9.a.
1882 auto yearsMonthsDuration = DateDuration{years, months};
1884 // Step 9.b.
1885 auto later =
1886 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
1887 if (!later) {
1888 return false;
1890 auto laterDate = ToPlainDate(&later.unwrap());
1892 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1893 if (!unwrappedRelativeTo) {
1894 return false;
1896 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1898 // Step 9.c.
1899 int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
1901 // Step 9.d.
1902 return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
1903 result);
1906 // Step 10. (Not applicable in our implementation.)
1908 // Step 11.
1909 auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};
1911 // Step 12.
1912 auto later =
1913 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
1914 if (!later) {
1915 return false;
1917 auto laterDate = ToPlainDate(&later.unwrap());
1919 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1920 if (!unwrappedRelativeTo) {
1921 return false;
1923 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1925 // Step 13.
1926 int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
1928 // Step 14.
1929 return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
1930 result);
1934 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1935 * plainRelativeTo, calendarRec )
1937 static bool UnbalanceDateDurationRelative(JSContext* cx,
1938 const DateDuration& duration,
1939 TemporalUnit largestUnit,
1940 DateDuration* result) {
1941 MOZ_ASSERT(IsValidDuration(duration));
1943 // Step 1. (Not applicable.)
1945 // Step 2-4.
1946 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1947 *result = duration;
1948 return true;
1951 // Step 5.
1952 MOZ_ASSERT(largestUnit != TemporalUnit::Year);
1954 // Steps 6.
1955 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1956 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
1957 return false;
1961 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1962 * smallestUnit, plainRelativeTo, calendarRec )
1964 static bool BalanceDateDurationRelative(
1965 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1966 TemporalUnit smallestUnit,
1967 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1968 Handle<CalendarRecord> calendar, DateDuration* result) {
1969 MOZ_ASSERT(IsValidDuration(duration));
1970 MOZ_ASSERT(largestUnit <= smallestUnit);
1972 auto [years, months, weeks, days] = duration;
1974 // FIXME: spec issue - effectful code paths should be more fine-grained
1975 // similar to UnbalanceDateDurationRelative. For example:
1976 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1977 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1978 // 3. Else if days = 0, then no-op.
1980 // Also note that |weeks| is never balanced, even when non-zero.
1982 // Step 1. (Not applicable in our implementation.)
1984 // Steps 2-4.
1985 if (largestUnit > TemporalUnit::Week ||
1986 (years == 0 && months == 0 && weeks == 0 && days == 0)) {
1987 // Step 4.a.
1988 *result = duration;
1989 return true;
1992 // Step 5.
1993 if (!plainRelativeTo) {
1994 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1995 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
1996 "relativeTo");
1997 return false;
2000 // Step 6.
2001 MOZ_ASSERT(
2002 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
2004 // Step 7.
2005 MOZ_ASSERT(
2006 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
2008 // Steps 8-9. (Not applicable in our implementation.)
2010 auto untilAddedDate = [&](const DateDuration& duration,
2011 Duration* untilResult) {
2012 Rooted<Wrapped<PlainDateObject*>> later(
2013 cx, AddDate(cx, calendar, plainRelativeTo, duration));
2014 if (!later) {
2015 return false;
2018 return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
2019 untilResult);
2022 // Step 10.
2023 if (largestUnit == TemporalUnit::Year) {
2024 // Step 10.a.
2025 if (smallestUnit == TemporalUnit::Week) {
2026 // Step 10.a.i.
2027 MOZ_ASSERT(days == 0);
2029 // Step 10.a.ii.
2030 auto yearsMonthsDuration = DateDuration{years, months};
2032 // Steps 10.a.iii-iv.
2033 Duration untilResult;
2034 if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
2035 return false;
2038 // Step 10.a.v.
2039 *result = CreateDateDurationRecord(int64_t(untilResult.years),
2040 int64_t(untilResult.months), weeks, 0);
2041 return true;
2044 // Step 10.b.
2045 const auto& yearsMonthsWeeksDaysDuration = duration;
2047 // Steps 10.c-d.
2048 Duration untilResult;
2049 if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
2050 return false;
2053 // Step 10.e.
2054 *result = CreateDateDurationRecord(
2055 int64_t(untilResult.years), int64_t(untilResult.months),
2056 int64_t(untilResult.weeks), int64_t(untilResult.days));
2057 return true;
2060 // Step 11.
2061 if (largestUnit == TemporalUnit::Month) {
2062 // Step 11.a.
2063 MOZ_ASSERT(years == 0);
2065 // Step 11.b.
2066 if (smallestUnit == TemporalUnit::Week) {
2067 // Step 10.b.i.
2068 MOZ_ASSERT(days == 0);
2070 // Step 10.b.ii.
2071 *result = CreateDateDurationRecord(0, months, weeks, 0);
2072 return true;
2075 // Step 11.c.
2076 const auto& monthsWeeksDaysDuration = duration;
2078 // Steps 11.d-e.
2079 Duration untilResult;
2080 if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
2081 return false;
2084 // Step 11.f.
2085 *result = CreateDateDurationRecord(0, int64_t(untilResult.months),
2086 int64_t(untilResult.weeks),
2087 int64_t(untilResult.days));
2088 return true;
2091 // Step 12.
2092 MOZ_ASSERT(largestUnit == TemporalUnit::Week);
2094 // Step 13.
2095 MOZ_ASSERT(years == 0);
2097 // Step 14.
2098 MOZ_ASSERT(months == 0);
2100 // Step 15.
2101 const auto& weeksDaysDuration = duration;
2103 // Steps 16-17.
2104 Duration untilResult;
2105 if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
2106 return false;
2109 // Step 18.
2110 *result = CreateDateDurationRecord(0, 0, int64_t(untilResult.weeks),
2111 int64_t(untilResult.days));
2112 return true;
2116 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2117 * smallestUnit, plainRelativeTo, calendarRec )
2119 bool js::temporal::BalanceDateDurationRelative(
2120 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
2121 TemporalUnit smallestUnit,
2122 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2123 Handle<CalendarRecord> calendar, DateDuration* result) {
2124 MOZ_ASSERT(plainRelativeTo);
2125 MOZ_ASSERT(calendar.receiver());
2127 return ::BalanceDateDurationRelative(cx, duration, largestUnit, smallestUnit,
2128 plainRelativeTo, calendar, result);
2132 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2133 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2134 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2136 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2137 Duration* result) {
2138 MOZ_ASSERT(IsValidDuration(one));
2139 MOZ_ASSERT(IsValidDuration(two));
2141 // Steps 1-2. (Not applicable)
2143 // Step 3.
2144 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2146 // Step 4.
2147 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2149 // Step 5.
2150 auto largestUnit = std::min(largestUnit1, largestUnit2);
2152 // Step 6.a.
2153 if (largestUnit <= TemporalUnit::Week) {
2154 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2155 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
2156 "relativeTo");
2157 return false;
2160 // Step 6.b.
2161 auto normalized1 = NormalizeTimeDuration(one);
2163 // Step 6.c.
2164 auto normalized2 = NormalizeTimeDuration(two);
2166 // Step 6.d.
2167 NormalizedTimeDuration normalized;
2168 if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
2169 return false;
2172 // Step 6.e.
2173 int64_t days1 = mozilla::AssertedCast<int64_t>(one.days);
2174 int64_t days2 = mozilla::AssertedCast<int64_t>(two.days);
2175 auto totalDays = mozilla::CheckedInt64(days1) + days2;
2176 MOZ_ASSERT(totalDays.isValid(), "adding two duration days can't overflow");
2178 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized, totalDays.value(),
2179 &normalized)) {
2180 return false;
2183 // Step 6.f.
2184 TimeDuration balanced;
2185 if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
2186 return false;
2189 // Steps 6.g.
2190 *result = balanced.toDuration();
2191 return true;
2195 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2196 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2197 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2199 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2200 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2201 Handle<CalendarRecord> calendar, Duration* result) {
2202 MOZ_ASSERT(IsValidDuration(one));
2203 MOZ_ASSERT(IsValidDuration(two));
2205 // Steps 1-2. (Not applicable)
2207 // Step 3.
2208 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2210 // Step 4.
2211 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2213 // Step 5.
2214 auto largestUnit = std::min(largestUnit1, largestUnit2);
2216 // Step 6. (Not applicable)
2218 // Step 7.a. (Not applicable in our implementation.)
2220 // Step 7.b.
2221 auto dateDuration1 = one.toDateDuration();
2223 // Step 7.c.
2224 auto dateDuration2 = two.toDateDuration();
2226 // Step 7.d.
2227 Rooted<Wrapped<PlainDateObject*>> intermediate(
2228 cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
2229 if (!intermediate) {
2230 return false;
2233 // Step 7.e.
2234 Rooted<Wrapped<PlainDateObject*>> end(
2235 cx, AddDate(cx, calendar, intermediate, dateDuration2));
2236 if (!end) {
2237 return false;
2240 // Step 7.f.
2241 auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
2243 // Steps 7.g-i.
2244 DateDuration dateDifference;
2245 if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
2246 &dateDifference)) {
2247 return false;
2250 // Step 7.j.
2251 auto normalized1 = NormalizeTimeDuration(one);
2253 // Step 7.k.
2254 auto normalized2 = NormalizeTimeDuration(two);
2256 // Step 7.l.
2257 NormalizedTimeDuration normalized1WithDays;
2258 if (!Add24HourDaysToNormalizedTimeDuration(
2259 cx, normalized1, dateDifference.days, &normalized1WithDays)) {
2260 return false;
2263 // Step 7.m.
2264 NormalizedTimeDuration normalized;
2265 if (!AddNormalizedTimeDuration(cx, normalized1WithDays, normalized2,
2266 &normalized)) {
2267 return false;
2270 // Step 7.n.
2271 TimeDuration balanced;
2272 if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
2273 return false;
2276 // Steps 7.o.
2277 *result = {
2278 double(dateDifference.years), double(dateDifference.months),
2279 double(dateDifference.weeks), double(balanced.days),
2280 double(balanced.hours), double(balanced.minutes),
2281 double(balanced.seconds), double(balanced.milliseconds),
2282 balanced.microseconds, balanced.nanoseconds,
2284 MOZ_ASSERT(IsValidDuration(*result));
2285 return true;
2289 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2290 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2291 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2293 static bool AddDuration(
2294 JSContext* cx, const Duration& one, const Duration& two,
2295 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2296 Handle<TimeZoneRecord> timeZone,
2297 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2298 Duration* result) {
2299 // Steps 1-2. (Not applicable)
2301 // Step 3.
2302 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2304 // Step 4.
2305 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2307 // Step 5.
2308 auto largestUnit = std::min(largestUnit1, largestUnit2);
2310 // Steps 6-7. (Not applicable)
2312 // Steps 8-9. (Not applicable in our implementation.)
2314 // Step 10.
2315 bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
2317 // Steps 11-17.
2318 if (!startDateTimeNeeded) {
2319 // Steps 11-12. (Not applicable)
2321 // Step 13.
2322 auto normalized1 = NormalizeTimeDuration(one);
2324 // Step 14.
2325 auto normalized2 = NormalizeTimeDuration(two);
2327 // Step 15. (Inlined AddZonedDateTime, step 6.)
2328 Instant intermediateNs;
2329 if (!AddInstant(cx, zonedRelativeTo.instant(), normalized1,
2330 &intermediateNs)) {
2331 return false;
2333 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2335 // Step 16. (Inlined AddZonedDateTime, step 6.)
2336 Instant endNs;
2337 if (!AddInstant(cx, intermediateNs, normalized2, &endNs)) {
2338 return false;
2340 MOZ_ASSERT(IsValidEpochInstant(endNs));
2342 // Step 17.a.
2343 auto normalized = NormalizedTimeDurationFromEpochNanosecondsDifference(
2344 endNs, zonedRelativeTo.instant());
2346 // Step 17.b.
2347 TimeDuration balanced;
2348 if (!BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
2349 return false;
2352 // Step 17.c.
2353 *result = balanced.toDuration();
2354 return true;
2357 // Steps 11-12.
2358 PlainDateTime startDateTime;
2359 if (!precalculatedPlainDateTime) {
2360 if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
2361 &startDateTime)) {
2362 return false;
2364 } else {
2365 startDateTime = *precalculatedPlainDateTime;
2368 // Step 13.
2369 auto normalized1 = CreateNormalizedDurationRecord(one);
2371 // Step 14.
2372 auto normalized2 = CreateNormalizedDurationRecord(two);
2374 // Step 15.
2375 Instant intermediateNs;
2376 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2377 normalized1, startDateTime, &intermediateNs)) {
2378 return false;
2380 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2382 // Step 16.
2383 Instant endNs;
2384 if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, normalized2,
2385 &endNs)) {
2386 return false;
2388 MOZ_ASSERT(IsValidEpochInstant(endNs));
2390 // Step 17. (Not applicable)
2392 // Step 18.
2393 NormalizedDuration difference;
2394 if (!DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs, timeZone,
2395 calendar, largestUnit, startDateTime,
2396 &difference)) {
2397 return false;
2400 // Step 19.
2401 auto balanced = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
2403 // Step 20.
2404 *result = {
2405 double(difference.date.years), double(difference.date.months),
2406 double(difference.date.weeks), double(difference.date.days),
2407 double(balanced.hours), double(balanced.minutes),
2408 double(balanced.seconds), double(balanced.milliseconds),
2409 balanced.microseconds, balanced.nanoseconds,
2411 MOZ_ASSERT(IsValidDuration(*result));
2412 return true;
2416 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2417 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2418 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2420 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2421 Handle<ZonedDateTime> zonedRelativeTo,
2422 Handle<CalendarRecord> calendar,
2423 Handle<TimeZoneRecord> timeZone, Duration* result) {
2424 return AddDuration(cx, one, two, zonedRelativeTo, calendar, timeZone,
2425 mozilla::Nothing(), result);
2429 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2430 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2431 * precalculatedPlainDateTime )
2433 static bool AdjustRoundedDurationDays(
2434 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2435 TemporalUnit unit, TemporalRoundingMode roundingMode,
2436 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2437 Handle<TimeZoneRecord> timeZone,
2438 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2439 NormalizedDuration* result) {
2440 MOZ_ASSERT(IsValidDuration(duration));
2442 // Step 1.
2443 if ((TemporalUnit::Year <= unit && unit <= TemporalUnit::Day) ||
2444 (unit == TemporalUnit::Nanosecond && increment == Increment{1})) {
2445 *result = duration;
2446 return true;
2449 // The increment is limited for all smaller temporal units.
2450 MOZ_ASSERT(increment < MaximumTemporalDurationRoundingIncrement(unit));
2452 // Step 2.
2453 MOZ_ASSERT(precalculatedPlainDateTime);
2455 // Step 3.
2456 int32_t direction = NormalizedTimeDurationSign(duration.time);
2458 // Steps 4-5.
2459 Instant dayStart;
2460 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2461 duration.date, *precalculatedPlainDateTime,
2462 &dayStart)) {
2463 return false;
2465 MOZ_ASSERT(IsValidEpochInstant(dayStart));
2467 // Step 6.
2468 PlainDateTime dayStartDateTime;
2469 if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
2470 return false;
2473 // Step 7.
2474 Instant dayEnd;
2475 if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
2476 zonedRelativeTo.calendar(), direction, &dayEnd)) {
2477 return false;
2479 MOZ_ASSERT(IsValidEpochInstant(dayEnd));
2481 // Step 8.
2482 auto dayLengthNs =
2483 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd, dayStart);
2484 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs.to<InstantSpan>()));
2486 // Step 9.
2487 NormalizedTimeDuration oneDayLess;
2488 if (!SubtractNormalizedTimeDuration(cx, duration.time, dayLengthNs,
2489 &oneDayLess)) {
2490 return false;
2493 // Step 10.
2494 int32_t oneDayLessSign = NormalizedTimeDurationSign(oneDayLess);
2495 if ((direction > 0 && oneDayLessSign < 0) ||
2496 (direction < 0 && oneDayLessSign > 0)) {
2497 *result = duration;
2498 return true;
2501 // Step 11.
2502 Duration adjustedDateDuration;
2503 if (!AddDuration(cx, duration.date.toDuration(), {0, 0, 0, double(direction)},
2504 zonedRelativeTo, calendar, timeZone,
2505 precalculatedPlainDateTime, &adjustedDateDuration)) {
2506 return false;
2509 // FIXME: spec bug - RoundDuration is fallible
2510 // https://github.com/tc39/proposal-temporal/issues/2801
2512 // clang-format off
2514 // let oneDaySeconds = Temporal.Duration.from({days: 1}).total("seconds");
2516 // let d = Temporal.Duration.from({
2517 // days: 1,
2518 // seconds: (2**53 - 1) - oneDaySeconds,
2519 // nanoseconds: 999_999_999,
2520 // });
2522 // let timeZone = new class extends Temporal.TimeZone {
2523 // #getPossibleInstantsFor = 0
2525 // getPossibleInstantsFor(dateTime) {
2526 // if (++this.#getPossibleInstantsFor === 2) {
2527 // return super.getPossibleInstantsFor(Temporal.PlainDateTime.from("1970-01-01T00:00:00.000000001"));
2528 // }
2529 // return super.getPossibleInstantsFor(dateTime);
2530 // }
2531 // }("UTC");
2533 // let relativeTo = new Temporal.ZonedDateTime(0n, timeZone);
2535 // let r = d.round({
2536 // largestUnit: "nanoseconds",
2537 // smallestUnit: "nanoseconds",
2538 // roundingIncrement: 2,
2539 // relativeTo,
2540 // });
2542 // clang-format on
2544 // Step 12.
2545 NormalizedTimeDuration roundedTime;
2546 if (!RoundDuration(cx, oneDayLess, increment, unit, roundingMode,
2547 &roundedTime)) {
2548 return false;
2551 // Step 13.
2552 return CombineDateAndNormalizedTimeDuration(
2553 cx, adjustedDateDuration.toDateDuration(), roundedTime, result);
2557 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2558 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2559 * precalculatedPlainDateTime )
2561 bool js::temporal::AdjustRoundedDurationDays(
2562 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2563 TemporalUnit unit, TemporalRoundingMode roundingMode,
2564 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2565 Handle<TimeZoneRecord> timeZone,
2566 const PlainDateTime& precalculatedPlainDateTime,
2567 NormalizedDuration* result) {
2568 return ::AdjustRoundedDurationDays(
2569 cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
2570 timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
2573 static bool NumberToStringBuilder(JSContext* cx, double num,
2574 JSStringBuilder& sb) {
2575 MOZ_ASSERT(IsInteger(num));
2576 MOZ_ASSERT(num >= 0);
2577 MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2579 ToCStringBuf cbuf;
2580 size_t length;
2581 const char* numStr = NumberToCString(&cbuf, num, &length);
2583 return sb.append(numStr, length);
2586 static Duration AbsoluteDuration(const Duration& duration) {
2587 return {
2588 std::abs(duration.years), std::abs(duration.months),
2589 std::abs(duration.weeks), std::abs(duration.days),
2590 std::abs(duration.hours), std::abs(duration.minutes),
2591 std::abs(duration.seconds), std::abs(duration.milliseconds),
2592 std::abs(duration.microseconds), std::abs(duration.nanoseconds),
2597 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2599 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
2600 int32_t subSecondNanoseconds,
2601 Precision precision) {
2602 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
2603 MOZ_ASSERT(precision != Precision::Minute());
2605 // Steps 1-2.
2606 if (precision == Precision::Auto()) {
2607 // Step 1.a.
2608 if (subSecondNanoseconds == 0) {
2609 return true;
2612 // Step 3. (Reordered)
2613 if (!result.append('.')) {
2614 return false;
2617 // Steps 1.b-c.
2618 int32_t k = 100'000'000;
2619 do {
2620 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2621 return false;
2623 subSecondNanoseconds %= k;
2624 k /= 10;
2625 } while (subSecondNanoseconds);
2626 } else {
2627 // Step 2.a.
2628 uint8_t p = precision.value();
2629 if (p == 0) {
2630 return true;
2633 // Step 3. (Reordered)
2634 if (!result.append('.')) {
2635 return false;
2638 // Steps 2.b-c.
2639 int32_t k = 100'000'000;
2640 for (uint8_t i = 0; i < precision.value(); i++) {
2641 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2642 return false;
2644 subSecondNanoseconds %= k;
2645 k /= 10;
2649 return true;
2653 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2654 * normSeconds, precision )
2656 static JSString* TemporalDurationToString(JSContext* cx,
2657 const Duration& duration,
2658 Precision precision) {
2659 MOZ_ASSERT(IsValidDuration(duration));
2660 MOZ_ASSERT(precision != Precision::Minute());
2662 // Fast path for zero durations.
2663 if (duration == Duration{} &&
2664 (precision == Precision::Auto() || precision.value() == 0)) {
2665 return NewStringCopyZ<CanGC>(cx, "PT0S");
2668 // Convert to absolute values up front. This is okay to do, because when the
2669 // duration is valid, all components have the same sign.
2670 const auto& [years, months, weeks, days, hours, minutes, seconds,
2671 milliseconds, microseconds, nanoseconds] =
2672 AbsoluteDuration(duration);
2674 // Years to seconds parts are all safe integers for valid durations.
2675 MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2676 MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2677 MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2678 MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2679 MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2680 MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2681 MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2683 auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
2684 microseconds, nanoseconds);
2686 // Step 1.
2687 int32_t sign = DurationSign(duration);
2689 // Steps 2 and 7.
2690 JSStringBuilder result(cx);
2692 // Step 13. (Reordered)
2693 if (sign < 0) {
2694 if (!result.append('-')) {
2695 return nullptr;
2699 // Step 14. (Reordered)
2700 if (!result.append('P')) {
2701 return nullptr;
2704 // Step 3.
2705 if (years != 0) {
2706 if (!NumberToStringBuilder(cx, years, result)) {
2707 return nullptr;
2709 if (!result.append('Y')) {
2710 return nullptr;
2714 // Step 4.
2715 if (months != 0) {
2716 if (!NumberToStringBuilder(cx, months, result)) {
2717 return nullptr;
2719 if (!result.append('M')) {
2720 return nullptr;
2724 // Step 5.
2725 if (weeks != 0) {
2726 if (!NumberToStringBuilder(cx, weeks, result)) {
2727 return nullptr;
2729 if (!result.append('W')) {
2730 return nullptr;
2734 // Step 6.
2735 if (days != 0) {
2736 if (!NumberToStringBuilder(cx, days, result)) {
2737 return nullptr;
2739 if (!result.append('D')) {
2740 return nullptr;
2744 // Step 7. (Moved above)
2746 // Steps 10-11. (Reordered)
2747 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
2748 days == 0 && hours == 0 && minutes == 0;
2750 // Steps 8-9, 12, and 15.
2751 bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
2752 zeroMinutesAndHigher || precision != Precision::Auto();
2753 if (hours != 0 || minutes != 0 || hasSecondsPart) {
2754 // Step 15. (Reordered)
2755 if (!result.append('T')) {
2756 return nullptr;
2759 // Step 8.
2760 if (hours != 0) {
2761 if (!NumberToStringBuilder(cx, hours, result)) {
2762 return nullptr;
2764 if (!result.append('H')) {
2765 return nullptr;
2769 // Step 9.
2770 if (minutes != 0) {
2771 if (!NumberToStringBuilder(cx, minutes, result)) {
2772 return nullptr;
2774 if (!result.append('M')) {
2775 return nullptr;
2779 // Step 12.
2780 if (hasSecondsPart) {
2781 // Step 12.a.
2782 if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
2783 return nullptr;
2786 // Step 12.b.
2787 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
2788 precision)) {
2789 return nullptr;
2792 // Step 12.c.
2793 if (!result.append('S')) {
2794 return nullptr;
2799 // Steps 13-15. (Moved above)
2801 // Step 16.
2802 return result.finishString();
2806 * ToRelativeTemporalObject ( options )
2808 static bool ToRelativeTemporalObject(
2809 JSContext* cx, Handle<JSObject*> options,
2810 MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
2811 MutableHandle<ZonedDateTime> zonedRelativeTo,
2812 MutableHandle<TimeZoneRecord> timeZoneRecord) {
2813 // Step 1.
2814 Rooted<Value> value(cx);
2815 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
2816 return false;
2819 // Step 2.
2820 if (value.isUndefined()) {
2821 plainRelativeTo.set(nullptr);
2822 zonedRelativeTo.set(ZonedDateTime{});
2823 timeZoneRecord.set(TimeZoneRecord{});
2824 return true;
2827 // Step 3.
2828 auto offsetBehaviour = OffsetBehaviour::Option;
2830 // Step 4.
2831 auto matchBehaviour = MatchBehaviour::MatchExactly;
2833 // Steps 5-6.
2834 PlainDateTime dateTime;
2835 Rooted<CalendarValue> calendar(cx);
2836 Rooted<TimeZoneValue> timeZone(cx);
2837 int64_t offsetNs;
2838 if (value.isObject()) {
2839 Rooted<JSObject*> obj(cx, &value.toObject());
2841 // Step 5.a.
2842 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
2843 auto instant = ToInstant(zonedDateTime);
2844 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
2845 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
2847 if (!timeZone.wrap(cx)) {
2848 return false;
2850 if (!calendar.wrap(cx)) {
2851 return false;
2854 // Step 5.a.i.
2855 Rooted<TimeZoneRecord> timeZoneRec(cx);
2856 if (!CreateTimeZoneMethodsRecord(
2857 cx, timeZone,
2859 TimeZoneMethod::GetOffsetNanosecondsFor,
2860 TimeZoneMethod::GetPossibleInstantsFor,
2862 &timeZoneRec)) {
2863 return false;
2866 // Step 5.a.ii.
2867 plainRelativeTo.set(nullptr);
2868 zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
2869 timeZoneRecord.set(timeZoneRec);
2870 return true;
2873 // Step 5.b.
2874 if (obj->canUnwrapAs<PlainDateObject>()) {
2875 plainRelativeTo.set(obj);
2876 zonedRelativeTo.set(ZonedDateTime{});
2877 timeZoneRecord.set(TimeZoneRecord{});
2878 return true;
2881 // Step 5.c.
2882 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
2883 auto plainDateTime = ToPlainDate(dateTime);
2885 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
2886 if (!calendar.wrap(cx)) {
2887 return false;
2890 // Step 5.c.i.
2891 auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
2892 if (!plainDate) {
2893 return false;
2896 // Step 5.c.ii.
2897 plainRelativeTo.set(plainDate);
2898 zonedRelativeTo.set(ZonedDateTime{});
2899 timeZoneRecord.set(TimeZoneRecord{});
2900 return true;
2903 // Step 5.d.
2904 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
2905 return false;
2908 // Step 5.e.
2909 Rooted<CalendarRecord> calendarRec(cx);
2910 if (!CreateCalendarMethodsRecord(cx, calendar,
2912 CalendarMethod::DateFromFields,
2913 CalendarMethod::Fields,
2915 &calendarRec)) {
2916 return false;
2919 // Step 5.f.
2920 JS::RootedVector<PropertyKey> fieldNames(cx);
2921 if (!CalendarFields(cx, calendarRec,
2922 {CalendarField::Day, CalendarField::Month,
2923 CalendarField::MonthCode, CalendarField::Year},
2924 &fieldNames)) {
2925 return false;
2928 // Step 5.g.
2929 if (!AppendSorted(cx, fieldNames.get(),
2931 TemporalField::Hour,
2932 TemporalField::Microsecond,
2933 TemporalField::Millisecond,
2934 TemporalField::Minute,
2935 TemporalField::Nanosecond,
2936 TemporalField::Offset,
2937 TemporalField::Second,
2938 TemporalField::TimeZone,
2939 })) {
2940 return false;
2943 // Step 5.h.
2944 Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, obj, fieldNames));
2945 if (!fields) {
2946 return false;
2949 // Step 5.i.
2950 Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
2951 if (!dateOptions) {
2952 return false;
2955 // Step 5.j.
2956 Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
2957 if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
2958 return false;
2961 // Step 5.k.
2962 if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
2963 &dateTime)) {
2964 return false;
2967 // Step 5.l.
2968 Rooted<Value> offset(cx);
2969 if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
2970 return false;
2973 // Step 5.m.
2974 Rooted<Value> timeZoneValue(cx);
2975 if (!GetProperty(cx, fields, fields, cx->names().timeZone,
2976 &timeZoneValue)) {
2977 return false;
2980 // Step 5.n.
2981 if (!timeZoneValue.isUndefined()) {
2982 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
2983 return false;
2987 // Step 5.o.
2988 if (offset.isUndefined()) {
2989 offsetBehaviour = OffsetBehaviour::Wall;
2992 // Steps 8-9.
2993 if (timeZone) {
2994 if (offsetBehaviour == OffsetBehaviour::Option) {
2995 MOZ_ASSERT(!offset.isUndefined());
2996 MOZ_ASSERT(offset.isString());
2998 // Step 8.a.
2999 Rooted<JSString*> offsetString(cx, offset.toString());
3000 if (!offsetString) {
3001 return false;
3004 // Step 8.b.
3005 if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
3006 return false;
3008 } else {
3009 // Step 9.
3010 offsetNs = 0;
3013 } else {
3014 // Step 6.a.
3015 if (!value.isString()) {
3016 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
3017 nullptr, "not a string");
3018 return false;
3020 Rooted<JSString*> string(cx, value.toString());
3022 // Step 6.b.
3023 bool isUTC;
3024 bool hasOffset;
3025 int64_t timeZoneOffset;
3026 Rooted<ParsedTimeZone> timeZoneAnnotation(cx);
3027 Rooted<JSString*> calendarString(cx);
3028 if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
3029 &hasOffset, &timeZoneOffset,
3030 &timeZoneAnnotation, &calendarString)) {
3031 return false;
3034 // Step 6.c. (Not applicable in our implementation.)
3036 // Steps 6.e-f.
3037 if (timeZoneAnnotation) {
3038 // Step 6.f.i.
3039 if (!ToTemporalTimeZone(cx, timeZoneAnnotation, &timeZone)) {
3040 return false;
3043 // Steps 6.f.ii-iii.
3044 if (isUTC) {
3045 offsetBehaviour = OffsetBehaviour::Exact;
3046 } else if (!hasOffset) {
3047 offsetBehaviour = OffsetBehaviour::Wall;
3050 // Step 6.f.iv.
3051 matchBehaviour = MatchBehaviour::MatchMinutes;
3052 } else {
3053 MOZ_ASSERT(!timeZone);
3056 // Steps 6.g-j.
3057 if (calendarString) {
3058 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
3059 return false;
3061 } else {
3062 calendar.set(CalendarValue(cx->names().iso8601));
3065 // Steps 8-9.
3066 if (timeZone) {
3067 if (offsetBehaviour == OffsetBehaviour::Option) {
3068 MOZ_ASSERT(hasOffset);
3070 // Step 8.a.
3071 offsetNs = timeZoneOffset;
3072 } else {
3073 // Step 9.
3074 offsetNs = 0;
3079 // Step 7.
3080 if (!timeZone) {
3081 // Step 7.a.
3082 auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
3083 if (!plainDate) {
3084 return false;
3087 plainRelativeTo.set(plainDate);
3088 zonedRelativeTo.set(ZonedDateTime{});
3089 timeZoneRecord.set(TimeZoneRecord{});
3090 return true;
3093 // Steps 8-9. (Moved above)
3095 // Step 10.
3096 Rooted<TimeZoneRecord> timeZoneRec(cx);
3097 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
3099 TimeZoneMethod::GetOffsetNanosecondsFor,
3100 TimeZoneMethod::GetPossibleInstantsFor,
3102 &timeZoneRec)) {
3103 return false;
3106 // Step 11.
3107 Instant epochNanoseconds;
3108 if (!InterpretISODateTimeOffset(
3109 cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
3110 TemporalDisambiguation::Compatible, TemporalOffset::Reject,
3111 matchBehaviour, &epochNanoseconds)) {
3112 return false;
3114 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
3116 // Step 12.
3117 plainRelativeTo.set(nullptr);
3118 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
3119 timeZoneRecord.set(timeZoneRec);
3120 return true;
3124 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
3125 * methods )
3127 static bool CreateCalendarMethodsRecordFromRelativeTo(
3128 JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3129 Handle<ZonedDateTime> zonedRelativeTo,
3130 mozilla::EnumSet<CalendarMethod> methods,
3131 MutableHandle<CalendarRecord> result) {
3132 // Step 1.
3133 if (zonedRelativeTo) {
3134 return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
3135 result);
3138 // Step 2.
3139 if (plainRelativeTo) {
3140 auto* unwrapped = plainRelativeTo.unwrap(cx);
3141 if (!unwrapped) {
3142 return false;
3145 Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
3146 if (!calendar.wrap(cx)) {
3147 return false;
3150 return CreateCalendarMethodsRecord(cx, calendar, methods, result);
3153 // Step 3.
3154 return true;
3157 struct RoundedDuration final {
3158 NormalizedDuration duration;
3159 double total = 0;
3162 enum class ComputeRemainder : bool { No, Yes };
3165 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3167 static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
3168 const NormalizedTimeDuration& duration, const TemporalUnit unit,
3169 Increment increment, TemporalRoundingMode roundingMode) {
3170 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3171 MOZ_ASSERT(unit > TemporalUnit::Day);
3172 MOZ_ASSERT(increment <= MaximumTemporalDurationRoundingIncrement(unit));
3174 int64_t divisor = ToNanoseconds(unit) * increment.value();
3175 MOZ_ASSERT(divisor > 0);
3176 MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
3178 auto totalNanoseconds = duration.toNanoseconds();
3179 auto rounded =
3180 RoundNumberToIncrement(totalNanoseconds, Int128{divisor}, roundingMode);
3181 return NormalizedTimeDuration::fromNanoseconds(rounded);
3185 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3187 static bool RoundNormalizedTimeDurationToIncrement(
3188 JSContext* cx, const NormalizedTimeDuration& duration,
3189 const TemporalUnit unit, Increment increment,
3190 TemporalRoundingMode roundingMode, NormalizedTimeDuration* result) {
3191 // Step 1.
3192 auto rounded = RoundNormalizedTimeDurationToIncrement(
3193 duration, unit, increment, roundingMode);
3195 // Step 2.
3196 if (!IsValidNormalizedTimeDuration(rounded)) {
3197 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3198 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
3199 return false;
3202 // Step 3.
3203 *result = rounded;
3204 return true;
3208 * DivideNormalizedTimeDuration ( d, divisor )
3210 static double TotalNormalizedTimeDuration(
3211 const NormalizedTimeDuration& duration, const TemporalUnit unit) {
3212 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3213 MOZ_ASSERT(unit > TemporalUnit::Day);
3215 auto numerator = duration.toNanoseconds();
3216 auto denominator = Int128{ToNanoseconds(unit)};
3217 return FractionToDouble(numerator, denominator);
3221 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3222 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3223 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3225 NormalizedTimeDuration js::temporal::RoundDuration(
3226 const NormalizedTimeDuration& duration, Increment increment,
3227 TemporalUnit unit, TemporalRoundingMode roundingMode) {
3228 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3229 MOZ_ASSERT(unit > TemporalUnit::Day);
3231 // Steps 1-13. (Not applicable)
3233 // Steps 14-19.
3234 auto rounded = RoundNormalizedTimeDurationToIncrement(
3235 duration, unit, increment, roundingMode);
3236 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded));
3238 // Step 20.
3239 return rounded;
3243 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3244 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3245 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3247 bool js::temporal::RoundDuration(JSContext* cx,
3248 const NormalizedTimeDuration& duration,
3249 Increment increment, TemporalUnit unit,
3250 TemporalRoundingMode roundingMode,
3251 NormalizedTimeDuration* result) {
3252 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3253 MOZ_ASSERT(unit > TemporalUnit::Day);
3255 // Steps 1-13. (Not applicable)
3257 // Steps 14-20.
3258 return RoundNormalizedTimeDurationToIncrement(cx, duration, unit, increment,
3259 roundingMode, result);
3262 struct FractionalDays final {
3263 int64_t days = 0;
3264 int64_t time = 0;
3265 int64_t dayLength = 0;
3267 FractionalDays() = default;
3269 explicit FractionalDays(int64_t durationDays,
3270 const NormalizedTimeAndDays& timeAndDays)
3271 : days(durationDays + timeAndDays.days),
3272 time(timeAndDays.time),
3273 dayLength(timeAndDays.dayLength) {
3274 MOZ_ASSERT(durationDays <= (int64_t(1) << 53) / (24 * 60 * 60));
3275 MOZ_ASSERT(timeAndDays.days <= (int64_t(1) << 53) / (24 * 60 * 60));
3277 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3278 // positive and less than 2**53.
3279 MOZ_ASSERT(dayLength > 0);
3280 MOZ_ASSERT(dayLength < int64_t(1) << 53);
3282 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3283 // less than |timeAndDays.dayLength|.
3284 MOZ_ASSERT(std::abs(time) < dayLength);
3287 FractionalDays operator+=(int32_t epochDays) {
3288 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3289 days += epochDays;
3290 return *this;
3293 FractionalDays operator-=(int32_t epochDays) {
3294 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3295 days -= epochDays;
3296 return *this;
3299 int64_t truncate() const {
3300 int64_t truncatedDays = days;
3301 if (time > 0) {
3302 // Round toward positive infinity when the integer days are negative and
3303 // the fractional part is positive.
3304 if (truncatedDays < 0) {
3305 truncatedDays += 1;
3307 } else if (time < 0) {
3308 // Round toward negative infinity when the integer days are positive and
3309 // the fractional part is negative.
3310 if (truncatedDays > 0) {
3311 truncatedDays -= 1;
3314 return truncatedDays;
3317 int32_t sign() const {
3318 if (days != 0) {
3319 return days < 0 ? -1 : 1;
3321 return time < 0 ? -1 : time > 0 ? 1 : 0;
3325 struct Fraction final {
3326 int64_t numerator = 0;
3327 int32_t denominator = 0;
3329 constexpr Fraction() = default;
3331 constexpr Fraction(int64_t numerator, int32_t denominator)
3332 : numerator(numerator), denominator(denominator) {
3333 MOZ_ASSERT(denominator > 0);
3337 struct RoundedNumber final {
3338 Int128 rounded;
3339 double total = 0;
3342 static RoundedNumber RoundNumberToIncrement(
3343 const Fraction& fraction, const FractionalDays& fractionalDays,
3344 Increment increment, TemporalRoundingMode roundingMode,
3345 ComputeRemainder computeRemainder) {
3346 #ifdef DEBUG
3347 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3348 static constexpr int64_t maxDurationDays =
3349 (int64_t(1) << 53) / (24 * 60 * 60);
3351 // Numbers of days between nsMinInstant and nsMaxInstant.
3352 static constexpr int32_t epochDays = 200'000'000;
3354 // Maximum number of days in |fractionalDays|.
3355 static constexpr int64_t maxFractionalDays =
3356 2 * maxDurationDays + 2 * epochDays;
3357 #endif
3359 MOZ_ASSERT(std::abs(fraction.numerator) < (int64_t(1) << 32) * 2);
3360 MOZ_ASSERT(fraction.denominator > 0);
3361 MOZ_ASSERT(fraction.denominator <= epochDays);
3362 MOZ_ASSERT(std::abs(fractionalDays.days) <= maxFractionalDays);
3363 MOZ_ASSERT(fractionalDays.dayLength > 0);
3364 MOZ_ASSERT(fractionalDays.dayLength < (int64_t(1) << 53));
3365 MOZ_ASSERT(std::abs(fractionalDays.time) < fractionalDays.dayLength);
3366 MOZ_ASSERT(increment <= Increment::max());
3368 // clang-format off
3370 // Change the representation of |fractionalWeeks| from a real number to a
3371 // rational number, because we don't support arbitrary precision real
3372 // numbers.
3374 // |fractionalWeeks| is defined as:
3376 // fractionalWeeks
3377 // = weeks + days' / abs(oneWeekDays)
3379 // where days' = days + nanoseconds / dayLength.
3381 // The fractional part |nanoseconds / dayLength| is from step 7.
3383 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3385 // fractionalWeeks
3386 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3387 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3388 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3390 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3391 // to omit the multiplication by |dayLength| when the rounding conditions are
3392 // appropriately modified to account for the |nanoseconds / dayLength| part.
3393 // This allows to implement rounding using only int64 values.
3395 // This optimization is currently only implemented when |nanoseconds| is zero.
3397 // Example how to expand this optimization for non-zero |nanoseconds|:
3399 // |Round(fraction / increment) * increment| with:
3400 // fraction = numerator / denominator
3401 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3402 // denominator = dayLength * abs(oneWeekDays)
3404 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3406 // |Round(fraction / increment) * increment| with:
3407 // fraction = numerator / denominator
3408 // numerator = weeks * abs(oneWeekDays) + days
3409 // denominator = abs(oneWeekDays)
3411 // Where:
3412 // fraction / increment
3413 // = (numerator / denominator) / increment
3414 // = numerator / (denominator * increment)
3416 // And |numerator| and |denominator * increment| both fit into int64.
3418 // The "ceiling" operation has to be modified from:
3420 // CeilDiv(dividend, divisor)
3421 // quot, rem = dividend / divisor
3422 // return quot + (rem > 0)
3424 // To:
3426 // CeilDiv(dividend, divisor, fractional)
3427 // quot, rem = dividend / divisor
3428 // return quot + ((rem > 0) || (fractional > 0))
3430 // To properly account for the fractional |nanoseconds| part. Alternatively
3431 // |dividend| can be modified before calling `CeilDiv`.
3433 // clang-format on
3435 if (fractionalDays.time == 0) {
3436 auto [numerator, denominator] = fraction;
3437 int64_t totalDays = fractionalDays.days + denominator * numerator;
3439 if (computeRemainder == ComputeRemainder::Yes) {
3440 constexpr auto rounded = Int128{0};
3441 double total = FractionToDouble(totalDays, denominator);
3442 return {rounded, total};
3445 auto rounded =
3446 RoundNumberToIncrement(totalDays, denominator, increment, roundingMode);
3447 constexpr double total = 0;
3448 return {rounded, total};
3451 do {
3452 auto dayLength = mozilla::CheckedInt64(fractionalDays.dayLength);
3454 auto denominator = dayLength * fraction.denominator;
3455 if (!denominator.isValid()) {
3456 break;
3459 auto amountNanos = denominator * fraction.numerator;
3460 if (!amountNanos.isValid()) {
3461 break;
3464 auto totalNanoseconds = dayLength * fractionalDays.days;
3465 totalNanoseconds += fractionalDays.time;
3466 totalNanoseconds += amountNanos;
3467 if (!totalNanoseconds.isValid()) {
3468 break;
3471 if (computeRemainder == ComputeRemainder::Yes) {
3472 constexpr auto rounded = Int128{0};
3473 double total =
3474 FractionToDouble(totalNanoseconds.value(), denominator.value());
3475 return {rounded, total};
3478 auto rounded = RoundNumberToIncrement(
3479 totalNanoseconds.value(), denominator.value(), increment, roundingMode);
3480 constexpr double total = 0;
3481 return {rounded, total};
3482 } while (false);
3484 // Use int128 when values are too large for int64. Additionally assert all
3485 // values fit into int128.
3487 // `dayLength` < 2**53
3488 auto dayLength = Int128{fractionalDays.dayLength};
3489 MOZ_ASSERT(dayLength < Int128{1} << 53);
3491 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3492 auto denominator = dayLength * Int128{fraction.denominator};
3493 MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
3495 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3497 // `abs(maxFractionalDays)`
3498 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3499 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3500 // ≤ 2 * 2**37 + 2**29
3501 // ≤ 2**39
3502 auto totalDays = Int128{fractionalDays.days};
3503 MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
3505 // `abs(fraction.numerator)` ≤ (2**33)
3506 auto totalAmount = Int128{fraction.numerator};
3507 MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
3509 // `denominator` < 2**(53 + 28)
3510 // `abs(totalAmount)` <= 2**33
3512 // `denominator * totalAmount`
3513 // ≤ 2**(53 + 28) * 2**33
3514 // = 2**(53 + 28 + 33)
3515 // = 2**114
3516 auto amountNanos = denominator * totalAmount;
3517 MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
3519 // `dayLength` < 2**53
3520 // `totalDays` ≤ 2**39
3521 // `fractionalDays.time` < `dayLength` < 2**53
3522 // `amountNanos` ≤ 2**114
3524 // `dayLength * totalDays`
3525 // ≤ 2**(53 + 39) = 2**92
3527 // `dayLength * totalDays + fractionalDays.time`
3528 // ≤ 2**93
3530 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3531 // ≤ 2**115
3532 auto totalNanoseconds = dayLength * totalDays;
3533 totalNanoseconds += Int128{fractionalDays.time};
3534 totalNanoseconds += amountNanos;
3535 MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
3537 if (computeRemainder == ComputeRemainder::Yes) {
3538 constexpr auto rounded = Int128{0};
3539 double total = FractionToDouble(totalNanoseconds, denominator);
3540 return {rounded, total};
3543 auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
3544 increment, roundingMode);
3545 constexpr double total = 0;
3546 return {rounded, total};
3549 static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
3550 FractionalDays fractionalDays,
3551 Increment increment,
3552 TemporalRoundingMode roundingMode,
3553 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3554 Handle<CalendarRecord> calendar,
3555 ComputeRemainder computeRemainder,
3556 RoundedDuration* result) {
3557 #ifdef DEBUG
3558 // Numbers of days between nsMinInstant and nsMaxInstant.
3559 static constexpr int32_t epochDays = 200'000'000;
3560 #endif
3562 auto [years, months, weeks, days] = duration.date;
3564 // Step 10.a.
3565 auto yearsDuration = DateDuration{years};
3567 // Step 10.b.
3568 auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
3569 if (!yearsLater) {
3570 return false;
3572 auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
3574 // Step 10.f. (Reordered)
3575 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
3577 // Step 10.c.
3578 auto yearsMonthsWeeks = DateDuration{years, months, weeks};
3580 // Step 10.d.
3581 PlainDate yearsMonthsWeeksLater;
3582 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3583 &yearsMonthsWeeksLater)) {
3584 return false;
3587 // Step 10.e.
3588 int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
3589 MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
3591 // Step 10.f. (Moved up)
3593 // Step 10.g.
3594 fractionalDays += monthsWeeksInDays;
3596 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3597 // https://github.com/tc39/proposal-temporal/issues/2540
3599 // Step 10.h.
3600 PlainDate isoResult;
3601 if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, fractionalDays.truncate()},
3602 TemporalOverflow::Constrain, &isoResult)) {
3603 return false;
3606 // Step 10.i.
3607 Rooted<PlainDateObject*> wholeDaysLater(
3608 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3609 if (!wholeDaysLater) {
3610 return false;
3613 // Steps 10.j-l.
3614 DateDuration timePassed;
3615 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3616 TemporalUnit::Year, &timePassed)) {
3617 return false;
3620 // Step 10.m.
3621 int64_t yearsPassed = timePassed.years;
3623 // Step 10.n.
3624 years += yearsPassed;
3626 // Step 10.o.
3627 auto yearsPassedDuration = DateDuration{yearsPassed};
3629 // Steps 10.p-r.
3630 int32_t daysPassed;
3631 if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
3632 &newRelativeTo, &daysPassed)) {
3633 return false;
3635 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3637 // Step 10.s.
3638 fractionalDays -= daysPassed;
3640 // Steps 10.t.
3641 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3643 // Step 10.u.
3644 auto oneYear = DateDuration{sign};
3646 // Steps 10.v-w.
3647 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3648 int32_t oneYearDays;
3649 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
3650 &moveResultIgnored, &oneYearDays)) {
3651 return false;
3654 // Step 10.x.
3655 if (oneYearDays == 0) {
3656 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3657 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3658 return false;
3661 // Steps 10.y.
3662 auto fractionalYears = Fraction{years, std::abs(oneYearDays)};
3664 // Steps 10.z-aa.
3665 auto [numYears, total] =
3666 RoundNumberToIncrement(fractionalYears, fractionalDays, increment,
3667 roundingMode, computeRemainder);
3669 // Step 10.ab.
3670 int64_t numMonths = 0;
3671 int64_t numWeeks = 0;
3673 // Step 10.ac.
3674 constexpr auto time = NormalizedTimeDuration{};
3676 // Step 20.
3677 if (numYears.abs() >= (Uint128{1} << 32)) {
3678 return ThrowInvalidDurationPart(cx, double(numYears), "years",
3679 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3682 auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
3683 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3684 return false;
3687 *result = {{resultDuration, time}, total};
3688 return true;
3691 static bool RoundDurationMonth(JSContext* cx,
3692 const NormalizedDuration& duration,
3693 FractionalDays fractionalDays,
3694 Increment increment,
3695 TemporalRoundingMode roundingMode,
3696 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3697 Handle<CalendarRecord> calendar,
3698 ComputeRemainder computeRemainder,
3699 RoundedDuration* result) {
3700 #ifdef DEBUG
3701 // Numbers of days between nsMinInstant and nsMaxInstant.
3702 static constexpr int32_t epochDays = 200'000'000;
3703 #endif
3705 auto [years, months, weeks, days] = duration.date;
3707 // Step 11.a.
3708 auto yearsMonths = DateDuration{years, months};
3710 // Step 11.b.
3711 auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
3712 if (!yearsMonthsLater) {
3713 return false;
3715 auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
3717 // Step 11.f. (Reordered)
3718 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
3720 // Step 11.c.
3721 auto yearsMonthsWeeks = DateDuration{years, months, weeks};
3723 // Step 11.d.
3724 PlainDate yearsMonthsWeeksLater;
3725 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3726 &yearsMonthsWeeksLater)) {
3727 return false;
3730 // Step 11.e.
3731 int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
3732 MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
3734 // Step 11.f. (Moved up)
3736 // Step 11.g.
3737 fractionalDays += weeksInDays;
3739 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3740 // https://github.com/tc39/proposal-temporal/issues/2540
3742 // Step 11.h.
3743 PlainDate isoResult;
3744 if (!AddISODate(cx, yearsMonthsLaterDate,
3745 {0, 0, 0, fractionalDays.truncate()},
3746 TemporalOverflow::Constrain, &isoResult)) {
3747 return false;
3750 // Step 11.i.
3751 Rooted<PlainDateObject*> wholeDaysLater(
3752 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3753 if (!wholeDaysLater) {
3754 return false;
3757 // Steps 11.j-l.
3758 DateDuration timePassed;
3759 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3760 TemporalUnit::Month, &timePassed)) {
3761 return false;
3764 // Step 11.m.
3765 int64_t monthsPassed = timePassed.months;
3767 // Step 11.n.
3768 months += monthsPassed;
3770 // Step 11.o.
3771 auto monthsPassedDuration = DateDuration{0, monthsPassed};
3773 // Steps 11.p-r.
3774 int32_t daysPassed;
3775 if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
3776 &newRelativeTo, &daysPassed)) {
3777 return false;
3779 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3781 // Step 11.s.
3782 fractionalDays -= daysPassed;
3784 // Steps 11.t.
3785 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3787 // Step 11.u.
3788 auto oneMonth = DateDuration{0, sign};
3790 // Steps 11.v-w.
3791 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3792 int32_t oneMonthDays;
3793 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
3794 &moveResultIgnored, &oneMonthDays)) {
3795 return false;
3798 // Step 11.x.
3799 if (oneMonthDays == 0) {
3800 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3801 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3802 return false;
3805 // Step 11.y.
3806 auto fractionalMonths = Fraction{months, std::abs(oneMonthDays)};
3808 // Steps 11.z-aa.
3809 auto [numMonths, total] =
3810 RoundNumberToIncrement(fractionalMonths, fractionalDays, increment,
3811 roundingMode, computeRemainder);
3813 // Step 11.ab.
3814 int64_t numWeeks = 0;
3816 // Step 11.ac.
3817 constexpr auto time = NormalizedTimeDuration{};
3819 // Step 21.
3820 if (numMonths.abs() >= (Uint128{1} << 32)) {
3821 return ThrowInvalidDurationPart(cx, double(numMonths), "months",
3822 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3825 auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
3826 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3827 return false;
3830 *result = {{resultDuration, time}, total};
3831 return true;
3834 static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
3835 FractionalDays fractionalDays,
3836 Increment increment,
3837 TemporalRoundingMode roundingMode,
3838 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3839 Handle<CalendarRecord> calendar,
3840 ComputeRemainder computeRemainder,
3841 RoundedDuration* result) {
3842 #ifdef DEBUG
3843 // Numbers of days between nsMinInstant and nsMaxInstant.
3844 static constexpr int32_t epochDays = 200'000'000;
3845 #endif
3847 auto [years, months, weeks, days] = duration.date;
3849 auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
3850 if (!unwrappedRelativeTo) {
3851 return false;
3853 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
3855 // Step 12.a
3856 PlainDate isoResult;
3857 if (!AddISODate(cx, relativeToDate, {0, 0, 0, fractionalDays.truncate()},
3858 TemporalOverflow::Constrain, &isoResult)) {
3859 return false;
3862 // Step 12.b.
3863 Rooted<PlainDateObject*> wholeDaysLater(
3864 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3865 if (!wholeDaysLater) {
3866 return false;
3869 // Steps 12.c-e.
3870 DateDuration timePassed;
3871 if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
3872 TemporalUnit::Week, &timePassed)) {
3873 return false;
3876 // Step 12.f.
3877 int64_t weeksPassed = timePassed.weeks;
3879 // Step 12.g.
3880 weeks += weeksPassed;
3882 // Step 12.h.
3883 auto weeksPassedDuration = DateDuration{0, 0, weeksPassed};
3885 // Steps 12.i-k.
3886 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
3887 int32_t daysPassed;
3888 if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
3889 &newRelativeTo, &daysPassed)) {
3890 return false;
3892 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3894 // Step 12.l.
3895 fractionalDays -= daysPassed;
3897 // Steps 12.m.
3898 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3900 // Step 12.n.
3901 auto oneWeek = DateDuration{0, 0, sign};
3903 // Steps 12.o-p.
3904 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3905 int32_t oneWeekDays;
3906 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
3907 &moveResultIgnored, &oneWeekDays)) {
3908 return false;
3911 // Step 12.q.
3912 if (oneWeekDays == 0) {
3913 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3914 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3915 return false;
3918 // Step 12.r.
3919 auto fractionalWeeks = Fraction{weeks, std::abs(oneWeekDays)};
3921 // Steps 12.s-t.
3922 auto [numWeeks, total] =
3923 RoundNumberToIncrement(fractionalWeeks, fractionalDays, increment,
3924 roundingMode, computeRemainder);
3926 // Step 12.u.
3927 constexpr auto time = NormalizedTimeDuration{};
3929 // Step 20.
3930 if (numWeeks.abs() >= (Uint128{1} << 32)) {
3931 return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
3932 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3935 auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
3936 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3937 return false;
3940 *result = {{resultDuration, time}, total};
3941 return true;
3944 static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
3945 const FractionalDays& fractionalDays,
3946 Increment increment,
3947 TemporalRoundingMode roundingMode,
3948 ComputeRemainder computeRemainder,
3949 RoundedDuration* result) {
3950 auto [years, months, weeks, days] = duration.date;
3952 // Pass zero fraction.
3953 constexpr auto zero = Fraction{0, 1};
3955 // Steps 13.a-b.
3956 auto [numDays, total] = RoundNumberToIncrement(
3957 zero, fractionalDays, increment, roundingMode, computeRemainder);
3959 MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
3960 "rounded days fits in int64");
3962 // Step 13.c.
3963 constexpr auto time = NormalizedTimeDuration{};
3965 // Step 20.
3966 auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
3967 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3968 return false;
3971 *result = {{resultDuration, time}, total};
3972 return true;
3976 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3977 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3978 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3980 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
3981 Increment increment, TemporalUnit unit,
3982 TemporalRoundingMode roundingMode,
3983 ComputeRemainder computeRemainder,
3984 RoundedDuration* result) {
3985 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
3986 MOZ_ASSERT_IF(unit > TemporalUnit::Day, IsValidDuration(duration.date));
3988 // The remainder is only needed when called from |Duration_total|. And `total`
3989 // always passes |increment=1| and |roundingMode=trunc|.
3990 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3991 increment == Increment{1});
3992 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3993 roundingMode == TemporalRoundingMode::Trunc);
3995 // Steps 1-5. (Not applicable.)
3997 // Step 6.
3998 if (unit <= TemporalUnit::Week) {
3999 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4000 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
4001 "relativeTo");
4002 return false;
4005 // TODO: We could directly return here if unit=nanoseconds and increment=1,
4006 // because in that case this operation is a no-op. This case happens for
4007 // example when calling Temporal.PlainTime.prototype.{since,until} without an
4008 // options object.
4010 // But maybe this can be even more efficiently handled in the callers. For
4011 // example when Temporal.PlainTime.prototype.{since,until} is called without
4012 // an options object, we can not only skip the RoundDuration call, but also
4013 // the following BalanceTimeDuration call.
4015 // Step 7. (Moved below.)
4017 // Steps 8-9. (Not applicable.)
4019 // Steps 10-12. (Not applicable.)
4021 // Step 13.
4022 if (unit == TemporalUnit::Day) {
4023 // Step 7.
4024 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
4025 auto fractionalDays = FractionalDays{duration.date.days, timeAndDays};
4027 return RoundDurationDay(cx, duration, fractionalDays, increment,
4028 roundingMode, computeRemainder, result);
4031 MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
4033 // Steps 14-19.
4034 auto time = duration.time;
4035 double total = 0;
4036 if (computeRemainder == ComputeRemainder::No) {
4037 if (!RoundNormalizedTimeDurationToIncrement(cx, time, unit, increment,
4038 roundingMode, &time)) {
4039 return false;
4041 } else {
4042 MOZ_ASSERT(increment == Increment{1});
4043 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4045 total = TotalNormalizedTimeDuration(duration.time, unit);
4047 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
4049 // Step 20.
4050 MOZ_ASSERT(IsValidDuration(duration.date));
4051 *result = {{duration.date, time}, total};
4052 return true;
4056 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4057 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4058 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4060 static bool RoundDuration(
4061 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4062 TemporalUnit unit, TemporalRoundingMode roundingMode,
4063 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4064 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4065 Handle<TimeZoneRecord> timeZone,
4066 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4067 ComputeRemainder computeRemainder, RoundedDuration* result) {
4068 // Note: |duration.days| can have a different sign than the other date
4069 // components. The date and time components can have different signs, too.
4070 MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
4071 double(duration.date.months),
4072 double(duration.date.weeks)}));
4073 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
4074 MOZ_ASSERT_IF(unit > TemporalUnit::Day, IsValidDuration(duration.date));
4076 MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
4077 "Use RoundDuration without relativeTo when plainRelativeTo and "
4078 "zonedRelativeTo are both undefined");
4080 // The remainder is only needed when called from |Duration_total|. And `total`
4081 // always passes |increment=1| and |roundingMode=trunc|.
4082 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
4083 increment == Increment{1});
4084 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
4085 roundingMode == TemporalRoundingMode::Trunc);
4087 // Steps 1-5. (Not applicable in our implementation.)
4089 // Step 6.a. (Not applicable in our implementation.)
4090 MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
4092 // Step 6.b.
4093 MOZ_ASSERT_IF(
4094 unit <= TemporalUnit::Week,
4095 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
4097 // Step 6.c.
4098 MOZ_ASSERT_IF(
4099 unit <= TemporalUnit::Week,
4100 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
4102 switch (unit) {
4103 case TemporalUnit::Year:
4104 case TemporalUnit::Month:
4105 case TemporalUnit::Week:
4106 break;
4107 case TemporalUnit::Day:
4108 // We can't take the faster code path when |zonedRelativeTo| is present.
4109 if (zonedRelativeTo) {
4110 break;
4112 [[fallthrough]];
4113 case TemporalUnit::Hour:
4114 case TemporalUnit::Minute:
4115 case TemporalUnit::Second:
4116 case TemporalUnit::Millisecond:
4117 case TemporalUnit::Microsecond:
4118 case TemporalUnit::Nanosecond:
4119 // Steps 7-9 and 13-21.
4120 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4121 computeRemainder, result);
4122 case TemporalUnit::Auto:
4123 MOZ_CRASH("Unexpected temporal unit");
4126 // Step 7.
4127 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
4129 // Steps 7.a-c.
4130 FractionalDays fractionalDays;
4131 if (zonedRelativeTo) {
4132 // Step 7.a.i.
4133 Rooted<ZonedDateTime> intermediate(cx);
4134 if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
4135 duration.date, precalculatedPlainDateTime,
4136 &intermediate)) {
4137 return false;
4140 // Steps 7.a.ii.
4141 NormalizedTimeAndDays timeAndDays;
4142 if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
4143 &timeAndDays)) {
4144 return false;
4147 // Step 7.a.iii.
4148 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
4149 } else {
4150 // Step 7.b.
4151 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
4152 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
4155 // Step 7.c. (Moved below)
4157 // Step 8. (Not applicable)
4159 // Step 9.
4160 // FIXME: spec issue - `total` doesn't need be initialised.
4161 // https://github.com/tc39/proposal-temporal/issues/2784
4163 // Steps 10-20.
4164 switch (unit) {
4165 // Steps 10 and 20.
4166 case TemporalUnit::Year:
4167 return RoundDurationYear(cx, duration, fractionalDays, increment,
4168 roundingMode, plainRelativeTo, calendar,
4169 computeRemainder, result);
4171 // Steps 11 and 20.
4172 case TemporalUnit::Month:
4173 return RoundDurationMonth(cx, duration, fractionalDays, increment,
4174 roundingMode, plainRelativeTo, calendar,
4175 computeRemainder, result);
4177 // Steps 12 and 20.
4178 case TemporalUnit::Week:
4179 return RoundDurationWeek(cx, duration, fractionalDays, increment,
4180 roundingMode, plainRelativeTo, calendar,
4181 computeRemainder, result);
4183 // Steps 13 and 20.
4184 case TemporalUnit::Day:
4185 return RoundDurationDay(cx, duration, fractionalDays, increment,
4186 roundingMode, computeRemainder, result);
4188 // Steps 14-19. (Handled elsewhere)
4189 case TemporalUnit::Auto:
4190 case TemporalUnit::Hour:
4191 case TemporalUnit::Minute:
4192 case TemporalUnit::Second:
4193 case TemporalUnit::Millisecond:
4194 case TemporalUnit::Microsecond:
4195 case TemporalUnit::Nanosecond:
4196 break;
4199 MOZ_CRASH("Unexpected temporal unit");
4203 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4204 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4205 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4207 bool js::temporal::RoundDuration(
4208 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4209 TemporalUnit unit, TemporalRoundingMode roundingMode,
4210 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4211 Handle<CalendarRecord> calendar, NormalizedDuration* result) {
4212 MOZ_ASSERT(IsValidDuration(duration));
4214 Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
4215 Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
4216 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4217 RoundedDuration rounded;
4218 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4219 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4220 precalculatedPlainDateTime, ComputeRemainder::No,
4221 &rounded)) {
4222 return false;
4225 *result = rounded.duration;
4226 return true;
4230 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4231 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4232 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4234 bool js::temporal::RoundDuration(
4235 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4236 TemporalUnit unit, TemporalRoundingMode roundingMode,
4237 Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
4238 Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
4239 const PlainDateTime& precalculatedPlainDateTime,
4240 NormalizedDuration* result) {
4241 MOZ_ASSERT(IsValidDuration(duration));
4243 RoundedDuration rounded;
4244 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4245 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4246 mozilla::SomeRef(precalculatedPlainDateTime),
4247 ComputeRemainder::No, &rounded)) {
4248 return false;
4251 *result = rounded.duration;
4252 return true;
4255 enum class DurationOperation { Add, Subtract };
4258 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4259 * options )
4261 static bool AddDurationToOrSubtractDurationFromDuration(
4262 JSContext* cx, DurationOperation operation, const CallArgs& args) {
4263 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4264 auto duration = ToDuration(durationObj);
4266 // Step 1. (Not applicable in our implementation.)
4268 // Step 2.
4269 Duration other;
4270 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
4271 return false;
4274 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4275 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4276 Rooted<TimeZoneRecord> timeZone(cx);
4277 if (args.hasDefined(1)) {
4278 const char* name = operation == DurationOperation::Add ? "add" : "subtract";
4280 // Step 3.
4281 Rooted<JSObject*> options(cx,
4282 RequireObjectArg(cx, "options", name, args[1]));
4283 if (!options) {
4284 return false;
4287 // Steps 4-7.
4288 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4289 &zonedRelativeTo, &timeZone)) {
4290 return false;
4292 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4293 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4296 // Step 8.
4297 Rooted<CalendarRecord> calendar(cx);
4298 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4299 zonedRelativeTo,
4301 CalendarMethod::DateAdd,
4302 CalendarMethod::DateUntil,
4304 &calendar)) {
4305 return false;
4308 // Step 9.
4309 if (operation == DurationOperation::Subtract) {
4310 other = other.negate();
4313 Duration result;
4314 if (plainRelativeTo) {
4315 if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
4316 return false;
4318 } else if (zonedRelativeTo) {
4319 if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
4320 &result)) {
4321 return false;
4323 } else {
4324 if (!AddDuration(cx, duration, other, &result)) {
4325 return false;
4329 // Step 10.
4330 auto* obj = CreateTemporalDuration(cx, result);
4331 if (!obj) {
4332 return false;
4335 args.rval().setObject(*obj);
4336 return true;
4340 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4341 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4342 * ] ] ] ] ] ] )
4344 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
4345 CallArgs args = CallArgsFromVp(argc, vp);
4347 // Step 1.
4348 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
4349 return false;
4352 // Step 2.
4353 double years = 0;
4354 if (args.hasDefined(0) &&
4355 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
4356 return false;
4359 // Step 3.
4360 double months = 0;
4361 if (args.hasDefined(1) &&
4362 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
4363 return false;
4366 // Step 4.
4367 double weeks = 0;
4368 if (args.hasDefined(2) &&
4369 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
4370 return false;
4373 // Step 5.
4374 double days = 0;
4375 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
4376 return false;
4379 // Step 6.
4380 double hours = 0;
4381 if (args.hasDefined(4) &&
4382 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
4383 return false;
4386 // Step 7.
4387 double minutes = 0;
4388 if (args.hasDefined(5) &&
4389 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
4390 return false;
4393 // Step 8.
4394 double seconds = 0;
4395 if (args.hasDefined(6) &&
4396 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
4397 return false;
4400 // Step 9.
4401 double milliseconds = 0;
4402 if (args.hasDefined(7) &&
4403 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
4404 return false;
4407 // Step 10.
4408 double microseconds = 0;
4409 if (args.hasDefined(8) &&
4410 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
4411 return false;
4414 // Step 11.
4415 double nanoseconds = 0;
4416 if (args.hasDefined(9) &&
4417 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
4418 return false;
4421 // Step 12.
4422 auto* duration = CreateTemporalDuration(
4423 cx, args,
4424 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
4425 microseconds, nanoseconds});
4426 if (!duration) {
4427 return false;
4430 args.rval().setObject(*duration);
4431 return true;
4435 * Temporal.Duration.from ( item )
4437 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
4438 CallArgs args = CallArgsFromVp(argc, vp);
4440 Handle<Value> item = args.get(0);
4442 // Step 1.
4443 if (item.isObject()) {
4444 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
4445 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
4446 if (!result) {
4447 return false;
4450 args.rval().setObject(*result);
4451 return true;
4455 // Step 2.
4456 auto result = ToTemporalDuration(cx, item);
4457 if (!result) {
4458 return false;
4461 args.rval().setObject(*result);
4462 return true;
4466 * Temporal.Duration.compare ( one, two [ , options ] )
4468 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
4469 CallArgs args = CallArgsFromVp(argc, vp);
4471 // Step 1.
4472 Duration one;
4473 if (!ToTemporalDuration(cx, args.get(0), &one)) {
4474 return false;
4477 // Step 2.
4478 Duration two;
4479 if (!ToTemporalDuration(cx, args.get(1), &two)) {
4480 return false;
4483 // Step 3.
4484 Rooted<JSObject*> options(cx);
4485 if (args.hasDefined(2)) {
4486 options = RequireObjectArg(cx, "options", "compare", args[2]);
4487 if (!options) {
4488 return false;
4492 // Step 4.
4493 if (one == two) {
4494 args.rval().setInt32(0);
4495 return true;
4498 // Steps 5-8.
4499 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4500 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4501 Rooted<TimeZoneRecord> timeZone(cx);
4502 if (options) {
4503 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4504 &zonedRelativeTo, &timeZone)) {
4505 return false;
4507 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4508 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4511 // Steps 9-10.
4512 auto hasCalendarUnit = [](const auto& d) {
4513 return d.years != 0 || d.months != 0 || d.weeks != 0;
4515 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
4517 // Step 11.
4518 Rooted<CalendarRecord> calendar(cx);
4519 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4520 zonedRelativeTo,
4522 CalendarMethod::DateAdd,
4524 &calendar)) {
4525 return false;
4528 // Step 12.
4529 if (zonedRelativeTo &&
4530 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
4531 // Step 12.a.
4532 const auto& instant = zonedRelativeTo.instant();
4534 // Step 12.b.
4535 PlainDateTime dateTime;
4536 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
4537 return false;
4540 // Step 12.c.
4541 auto normalized1 = CreateNormalizedDurationRecord(one);
4543 // Step 12.d.
4544 auto normalized2 = CreateNormalizedDurationRecord(two);
4546 // Step 12.e.
4547 Instant after1;
4548 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
4549 dateTime, &after1)) {
4550 return false;
4553 // Step 12.f.
4554 Instant after2;
4555 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
4556 dateTime, &after2)) {
4557 return false;
4560 // Steps 12.g-i.
4561 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
4562 return true;
4565 // Steps 13-14.
4566 int64_t days1, days2;
4567 if (calendarUnitsPresent) {
4568 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4570 // Step 13.a.
4571 DateDuration unbalanceResult1;
4572 if (plainRelativeTo) {
4573 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
4574 TemporalUnit::Day, plainRelativeTo,
4575 calendar, &unbalanceResult1)) {
4576 return false;
4578 } else {
4579 if (!UnbalanceDateDurationRelative(
4580 cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
4581 return false;
4583 MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
4586 // Step 13.b.
4587 DateDuration unbalanceResult2;
4588 if (plainRelativeTo) {
4589 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
4590 TemporalUnit::Day, plainRelativeTo,
4591 calendar, &unbalanceResult2)) {
4592 return false;
4594 } else {
4595 if (!UnbalanceDateDurationRelative(
4596 cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
4597 return false;
4599 MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
4602 // Step 13.c.
4603 days1 = unbalanceResult1.days;
4605 // Step 13.d.
4606 days2 = unbalanceResult2.days;
4607 } else {
4608 // Step 14.a.
4609 days1 = mozilla::AssertedCast<int64_t>(one.days);
4611 // Step 14.b.
4612 days2 = mozilla::AssertedCast<int64_t>(two.days);
4615 // Step 15.
4616 auto normalized1 = NormalizeTimeDuration(one);
4618 // Step 16.
4619 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
4620 &normalized1)) {
4621 return false;
4624 // Step 17.
4625 auto normalized2 = NormalizeTimeDuration(two);
4627 // Step 18.
4628 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
4629 &normalized2)) {
4630 return false;
4633 // Step 19.
4634 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
4635 return true;
4639 * get Temporal.Duration.prototype.years
4641 static bool Duration_years(JSContext* cx, const CallArgs& args) {
4642 // Step 3.
4643 auto* duration = &args.thisv().toObject().as<DurationObject>();
4644 args.rval().setNumber(duration->years());
4645 return true;
4649 * get Temporal.Duration.prototype.years
4651 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
4652 // Steps 1-2.
4653 CallArgs args = CallArgsFromVp(argc, vp);
4654 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
4658 * get Temporal.Duration.prototype.months
4660 static bool Duration_months(JSContext* cx, const CallArgs& args) {
4661 // Step 3.
4662 auto* duration = &args.thisv().toObject().as<DurationObject>();
4663 args.rval().setNumber(duration->months());
4664 return true;
4668 * get Temporal.Duration.prototype.months
4670 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
4671 // Steps 1-2.
4672 CallArgs args = CallArgsFromVp(argc, vp);
4673 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
4677 * get Temporal.Duration.prototype.weeks
4679 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
4680 // Step 3.
4681 auto* duration = &args.thisv().toObject().as<DurationObject>();
4682 args.rval().setNumber(duration->weeks());
4683 return true;
4687 * get Temporal.Duration.prototype.weeks
4689 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
4690 // Steps 1-2.
4691 CallArgs args = CallArgsFromVp(argc, vp);
4692 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
4696 * get Temporal.Duration.prototype.days
4698 static bool Duration_days(JSContext* cx, const CallArgs& args) {
4699 // Step 3.
4700 auto* duration = &args.thisv().toObject().as<DurationObject>();
4701 args.rval().setNumber(duration->days());
4702 return true;
4706 * get Temporal.Duration.prototype.days
4708 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
4709 // Steps 1-2.
4710 CallArgs args = CallArgsFromVp(argc, vp);
4711 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
4715 * get Temporal.Duration.prototype.hours
4717 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
4718 // Step 3.
4719 auto* duration = &args.thisv().toObject().as<DurationObject>();
4720 args.rval().setNumber(duration->hours());
4721 return true;
4725 * get Temporal.Duration.prototype.hours
4727 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
4728 // Steps 1-2.
4729 CallArgs args = CallArgsFromVp(argc, vp);
4730 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
4734 * get Temporal.Duration.prototype.minutes
4736 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
4737 // Step 3.
4738 auto* duration = &args.thisv().toObject().as<DurationObject>();
4739 args.rval().setNumber(duration->minutes());
4740 return true;
4744 * get Temporal.Duration.prototype.minutes
4746 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
4747 // Steps 1-2.
4748 CallArgs args = CallArgsFromVp(argc, vp);
4749 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
4753 * get Temporal.Duration.prototype.seconds
4755 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
4756 // Step 3.
4757 auto* duration = &args.thisv().toObject().as<DurationObject>();
4758 args.rval().setNumber(duration->seconds());
4759 return true;
4763 * get Temporal.Duration.prototype.seconds
4765 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
4766 // Steps 1-2.
4767 CallArgs args = CallArgsFromVp(argc, vp);
4768 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
4772 * get Temporal.Duration.prototype.milliseconds
4774 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
4775 // Step 3.
4776 auto* duration = &args.thisv().toObject().as<DurationObject>();
4777 args.rval().setNumber(duration->milliseconds());
4778 return true;
4782 * get Temporal.Duration.prototype.milliseconds
4784 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
4785 // Steps 1-2.
4786 CallArgs args = CallArgsFromVp(argc, vp);
4787 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
4791 * get Temporal.Duration.prototype.microseconds
4793 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
4794 // Step 3.
4795 auto* duration = &args.thisv().toObject().as<DurationObject>();
4796 args.rval().setNumber(duration->microseconds());
4797 return true;
4801 * get Temporal.Duration.prototype.microseconds
4803 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
4804 // Steps 1-2.
4805 CallArgs args = CallArgsFromVp(argc, vp);
4806 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
4810 * get Temporal.Duration.prototype.nanoseconds
4812 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
4813 // Step 3.
4814 auto* duration = &args.thisv().toObject().as<DurationObject>();
4815 args.rval().setNumber(duration->nanoseconds());
4816 return true;
4820 * get Temporal.Duration.prototype.nanoseconds
4822 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
4823 // Steps 1-2.
4824 CallArgs args = CallArgsFromVp(argc, vp);
4825 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
4829 * get Temporal.Duration.prototype.sign
4831 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
4832 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4834 // Step 3.
4835 args.rval().setInt32(DurationSign(duration));
4836 return true;
4840 * get Temporal.Duration.prototype.sign
4842 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
4843 // Steps 1-2.
4844 CallArgs args = CallArgsFromVp(argc, vp);
4845 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
4849 * get Temporal.Duration.prototype.blank
4851 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
4852 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4854 // Steps 3-5.
4855 args.rval().setBoolean(duration == Duration{});
4856 return true;
4860 * get Temporal.Duration.prototype.blank
4862 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
4863 // Steps 1-2.
4864 CallArgs args = CallArgsFromVp(argc, vp);
4865 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
4869 * Temporal.Duration.prototype.with ( temporalDurationLike )
4871 * ToPartialDuration ( temporalDurationLike )
4873 static bool Duration_with(JSContext* cx, const CallArgs& args) {
4874 // Absent values default to the corresponding values of |this| object.
4875 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4877 // Steps 3-23.
4878 Rooted<JSObject*> temporalDurationLike(
4879 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
4880 if (!temporalDurationLike) {
4881 return false;
4883 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
4884 return false;
4887 // Step 24.
4888 auto* result = CreateTemporalDuration(cx, duration);
4889 if (!result) {
4890 return false;
4893 args.rval().setObject(*result);
4894 return true;
4898 * Temporal.Duration.prototype.with ( temporalDurationLike )
4900 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
4901 // Steps 1-2.
4902 CallArgs args = CallArgsFromVp(argc, vp);
4903 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
4907 * Temporal.Duration.prototype.negated ( )
4909 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
4910 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4912 // Step 3.
4913 auto* result = CreateTemporalDuration(cx, duration.negate());
4914 if (!result) {
4915 return false;
4918 args.rval().setObject(*result);
4919 return true;
4923 * Temporal.Duration.prototype.negated ( )
4925 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
4926 // Steps 1-2.
4927 CallArgs args = CallArgsFromVp(argc, vp);
4928 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
4932 * Temporal.Duration.prototype.abs ( )
4934 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
4935 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4937 // Step 3.
4938 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
4939 if (!result) {
4940 return false;
4943 args.rval().setObject(*result);
4944 return true;
4948 * Temporal.Duration.prototype.abs ( )
4950 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
4951 // Steps 1-2.
4952 CallArgs args = CallArgsFromVp(argc, vp);
4953 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
4957 * Temporal.Duration.prototype.add ( other [ , options ] )
4959 static bool Duration_add(JSContext* cx, const CallArgs& args) {
4960 return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
4961 args);
4965 * Temporal.Duration.prototype.add ( other [ , options ] )
4967 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
4968 // Steps 1-2.
4969 CallArgs args = CallArgsFromVp(argc, vp);
4970 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
4974 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4976 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
4977 return AddDurationToOrSubtractDurationFromDuration(
4978 cx, DurationOperation::Subtract, args);
4982 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4984 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
4985 // Steps 1-2.
4986 CallArgs args = CallArgsFromVp(argc, vp);
4987 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
4991 * Temporal.Duration.prototype.round ( roundTo )
4993 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4994 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4996 // Step 18. (Reordered)
4997 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4999 // Steps 3-25.
5000 auto smallestUnit = TemporalUnit::Auto;
5001 TemporalUnit largestUnit;
5002 auto roundingMode = TemporalRoundingMode::HalfExpand;
5003 auto roundingIncrement = Increment{1};
5004 Rooted<JSObject*> relativeTo(cx);
5005 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5006 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5007 Rooted<TimeZoneRecord> timeZone(cx);
5008 if (args.get(0).isString()) {
5009 // Step 4. (Not applicable in our implementation.)
5011 // Steps 6-15. (Not applicable)
5013 // Step 16.
5014 Rooted<JSString*> paramString(cx, args[0].toString());
5015 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
5016 TemporalUnitGroup::DateTime, &smallestUnit)) {
5017 return false;
5020 // Step 17. (Not applicable)
5022 // Step 18. (Moved above)
5024 // Step 19.
5025 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5027 // Step 20. (Not applicable)
5029 // Step 20.a. (Not applicable)
5031 // Step 20.b.
5032 largestUnit = defaultLargestUnit;
5034 // Steps 21-25. (Not applicable)
5035 } else {
5036 // Steps 3 and 5.
5037 Rooted<JSObject*> options(
5038 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
5039 if (!options) {
5040 return false;
5043 // Step 6.
5044 bool smallestUnitPresent = true;
5046 // Step 7.
5047 bool largestUnitPresent = true;
5049 // Steps 8-9.
5051 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
5052 // absent "largestUnit" value.
5053 Rooted<Value> largestUnitValue(cx);
5054 if (!GetProperty(cx, options, options, cx->names().largestUnit,
5055 &largestUnitValue)) {
5056 return false;
5059 if (!largestUnitValue.isUndefined()) {
5060 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
5061 if (!largestUnitStr) {
5062 return false;
5065 largestUnit = TemporalUnit::Auto;
5066 if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
5067 TemporalUnitGroup::DateTime, &largestUnit)) {
5068 return false;
5072 // Steps 10-13.
5073 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
5074 &zonedRelativeTo, &timeZone)) {
5075 return false;
5077 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5078 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5080 // Step 14.
5081 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
5082 return false;
5085 // Step 15.
5086 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5087 return false;
5090 // Step 16.
5091 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5092 TemporalUnitGroup::DateTime, &smallestUnit)) {
5093 return false;
5096 // Step 17.
5097 if (smallestUnit == TemporalUnit::Auto) {
5098 // Step 17.a.
5099 smallestUnitPresent = false;
5101 // Step 17.b.
5102 smallestUnit = TemporalUnit::Nanosecond;
5105 // Step 18. (Moved above)
5107 // Step 19.
5108 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5110 // Steps 20-21.
5111 if (largestUnitValue.isUndefined()) {
5112 // Step 20.a.
5113 largestUnitPresent = false;
5115 // Step 20.b.
5116 largestUnit = defaultLargestUnit;
5117 } else if (largestUnit == TemporalUnit::Auto) {
5118 // Step 21.a
5119 largestUnit = defaultLargestUnit;
5122 // Step 22.
5123 if (!smallestUnitPresent && !largestUnitPresent) {
5124 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5125 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
5126 return false;
5129 // Step 23.
5130 if (largestUnit > smallestUnit) {
5131 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5132 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
5133 return false;
5136 // Steps 24-25.
5137 if (smallestUnit > TemporalUnit::Day) {
5138 // Step 24.
5139 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
5141 // Step 25.
5142 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
5143 false)) {
5144 return false;
5149 // Step 26.
5150 bool hoursToDaysConversionMayOccur = false;
5152 // Step 27.
5153 if (duration.days != 0 && zonedRelativeTo) {
5154 hoursToDaysConversionMayOccur = true;
5157 // Step 28.
5158 else if (std::abs(duration.hours) >= 24) {
5159 hoursToDaysConversionMayOccur = true;
5162 // Step 29.
5163 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
5164 roundingIncrement == Increment{1};
5166 // Step 30.
5167 bool calendarUnitsPresent =
5168 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
5170 // Step 31.
5171 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
5172 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
5173 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
5174 std::abs(duration.milliseconds) < 1000 &&
5175 std::abs(duration.microseconds) < 1000 &&
5176 std::abs(duration.nanoseconds) < 1000) {
5177 // Steps 31.a-b.
5178 auto* obj = CreateTemporalDuration(cx, duration);
5179 if (!obj) {
5180 return false;
5183 args.rval().setObject(*obj);
5184 return true;
5187 // Step 32.
5188 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5190 // Step 33.
5191 bool plainDateTimeOrRelativeToWillBeUsed =
5192 !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
5193 calendarUnitsPresent || duration.days != 0;
5195 // Step 34.
5196 PlainDateTime relativeToDateTime;
5197 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5198 // Steps 34.a-b.
5199 const auto& instant = zonedRelativeTo.instant();
5201 // Step 34.c.
5202 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5203 return false;
5205 precalculatedPlainDateTime =
5206 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5208 // Step 34.d.
5209 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5210 zonedRelativeTo.calendar());
5211 if (!plainRelativeTo) {
5212 return false;
5216 // Step 35.
5217 Rooted<CalendarRecord> calendar(cx);
5218 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5219 zonedRelativeTo,
5221 CalendarMethod::DateAdd,
5222 CalendarMethod::DateUntil,
5224 &calendar)) {
5225 return false;
5228 // Step 36.
5229 DateDuration unbalanceResult;
5230 if (plainRelativeTo) {
5231 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5232 largestUnit, plainRelativeTo, calendar,
5233 &unbalanceResult)) {
5234 return false;
5236 } else {
5237 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5238 largestUnit, &unbalanceResult)) {
5239 return false;
5241 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5243 MOZ_ASSERT(IsValidDuration(unbalanceResult));
5245 // Steps 37-38.
5246 auto roundInput =
5247 NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
5248 RoundedDuration rounded;
5249 if (plainRelativeTo || zonedRelativeTo) {
5250 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5251 roundingMode, plainRelativeTo, calendar,
5252 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5253 ComputeRemainder::No, &rounded)) {
5254 return false;
5256 } else {
5257 MOZ_ASSERT(IsValidDuration(roundInput));
5259 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5260 roundingMode, ComputeRemainder::No, &rounded)) {
5261 return false;
5265 // Step 39.
5266 auto roundResult = rounded.duration;
5268 // Steps 40-41.
5269 TimeDuration balanceResult;
5270 if (zonedRelativeTo) {
5271 // Step 40.a.
5272 NormalizedDuration adjustResult;
5273 if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
5274 smallestUnit, roundingMode, zonedRelativeTo,
5275 calendar, timeZone,
5276 precalculatedPlainDateTime, &adjustResult)) {
5277 return false;
5279 roundResult = adjustResult;
5281 // Step 40.b.
5282 if (!BalanceTimeDurationRelative(
5283 cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
5284 precalculatedPlainDateTime, &balanceResult)) {
5285 return false;
5287 } else {
5288 // Step 41.a.
5289 NormalizedTimeDuration withDays;
5290 if (!Add24HourDaysToNormalizedTimeDuration(
5291 cx, roundResult.time, roundResult.date.days, &withDays)) {
5292 return false;
5295 // Step 41.b.
5296 if (!temporal::BalanceTimeDuration(cx, withDays, largestUnit,
5297 &balanceResult)) {
5298 return false;
5302 // Step 41.
5303 auto balanceInput = DateDuration{
5304 roundResult.date.years,
5305 roundResult.date.months,
5306 roundResult.date.weeks,
5307 balanceResult.days,
5309 DateDuration dateResult;
5310 if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
5311 smallestUnit, plainRelativeTo, calendar,
5312 &dateResult)) {
5313 return false;
5316 // Step 42.
5317 auto result = Duration{
5318 double(dateResult.years), double(dateResult.months),
5319 double(dateResult.weeks), double(dateResult.days),
5320 double(balanceResult.hours), double(balanceResult.minutes),
5321 double(balanceResult.seconds), double(balanceResult.milliseconds),
5322 balanceResult.microseconds, balanceResult.nanoseconds,
5325 auto* obj = CreateTemporalDuration(cx, result);
5326 if (!obj) {
5327 return false;
5330 args.rval().setObject(*obj);
5331 return true;
5335 * Temporal.Duration.prototype.round ( options )
5337 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
5338 // Steps 1-2.
5339 CallArgs args = CallArgsFromVp(argc, vp);
5340 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
5344 * Temporal.Duration.prototype.total ( totalOf )
5346 static bool Duration_total(JSContext* cx, const CallArgs& args) {
5347 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
5348 auto duration = ToDuration(durationObj);
5350 // Steps 3-11.
5351 Rooted<JSObject*> relativeTo(cx);
5352 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5353 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5354 Rooted<TimeZoneRecord> timeZone(cx);
5355 auto unit = TemporalUnit::Auto;
5356 if (args.get(0).isString()) {
5357 // Step 4. (Not applicable in our implementation.)
5359 // Steps 6-10. (Implicit)
5360 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
5362 // Step 11.
5363 Rooted<JSString*> paramString(cx, args[0].toString());
5364 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
5365 TemporalUnitGroup::DateTime, &unit)) {
5366 return false;
5368 } else {
5369 // Steps 3 and 5.
5370 Rooted<JSObject*> totalOf(
5371 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
5372 if (!totalOf) {
5373 return false;
5376 // Steps 6-10.
5377 if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
5378 &zonedRelativeTo, &timeZone)) {
5379 return false;
5381 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5382 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5384 // Step 11.
5385 if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
5386 TemporalUnitGroup::DateTime, &unit)) {
5387 return false;
5390 if (unit == TemporalUnit::Auto) {
5391 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5392 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
5393 return false;
5397 // Step 12.
5398 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5400 // Step 13.
5401 bool plainDateTimeOrRelativeToWillBeUsed =
5402 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
5404 // Step 14.
5405 PlainDateTime relativeToDateTime;
5406 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5407 // Steps 14.a-b.
5408 const auto& instant = zonedRelativeTo.instant();
5410 // Step 14.c.
5411 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5412 return false;
5414 precalculatedPlainDateTime =
5415 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5417 // Step 14.d
5418 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5419 zonedRelativeTo.calendar());
5420 if (!plainRelativeTo) {
5421 return false;
5425 // Step 15.
5426 Rooted<CalendarRecord> calendar(cx);
5427 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5428 zonedRelativeTo,
5430 CalendarMethod::DateAdd,
5431 CalendarMethod::DateUntil,
5433 &calendar)) {
5434 return false;
5437 // Step 16.
5438 DateDuration unbalanceResult;
5439 if (plainRelativeTo) {
5440 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5441 plainRelativeTo, calendar,
5442 &unbalanceResult)) {
5443 return false;
5445 } else {
5446 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5447 &unbalanceResult)) {
5448 return false;
5450 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5453 // Step 17.
5454 int64_t unbalancedDays = unbalanceResult.days;
5456 // Steps 18-19.
5457 int64_t days;
5458 NormalizedTimeDuration normTime;
5459 if (zonedRelativeTo) {
5460 // Step 18.a
5461 Rooted<ZonedDateTime> intermediate(cx);
5462 if (!MoveRelativeZonedDateTime(
5463 cx, zonedRelativeTo, calendar, timeZone,
5464 {unbalanceResult.years, unbalanceResult.months,
5465 unbalanceResult.weeks, 0},
5466 precalculatedPlainDateTime, &intermediate)) {
5467 return false;
5470 // Step 18.b.
5471 auto timeDuration = NormalizeTimeDuration(duration);
5473 // Step 18.c
5474 const auto& startNs = intermediate.instant();
5476 // Step 18.d.
5477 const auto& startInstant = startNs;
5479 // Step 18.e.
5480 mozilla::Maybe<PlainDateTime> startDateTime{};
5482 // Steps 18.f-g.
5483 Instant intermediateNs;
5484 if (unbalancedDays != 0) {
5485 // Step 18.f.i.
5486 PlainDateTime dateTime;
5487 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5488 return false;
5490 startDateTime = mozilla::Some(dateTime);
5492 // Step 18.f.ii.
5493 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
5494 Instant addResult;
5495 if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
5496 isoCalendar, unbalancedDays, &addResult)) {
5497 return false;
5500 // Step 18.f.iii.
5501 intermediateNs = addResult;
5502 } else {
5503 // Step 18.g.
5504 intermediateNs = startNs;
5507 // Step 18.h.
5508 Instant endNs;
5509 if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
5510 return false;
5513 // Step 18.i.
5514 auto difference =
5515 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
5517 // Steps 18.j-k.
5519 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5520 if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
5521 difference != NormalizedTimeDuration{}) {
5522 // Step 18.j.i.
5523 if (!startDateTime) {
5524 PlainDateTime dateTime;
5525 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5526 return false;
5528 startDateTime = mozilla::Some(dateTime);
5531 // Step 18.j.ii.
5532 NormalizedTimeAndDays timeAndDays;
5533 if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
5534 *startDateTime, &timeAndDays)) {
5535 return false;
5538 // Step 18.j.iii.
5539 normTime = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
5541 // Step 18.j.iv.
5542 days = timeAndDays.days;
5543 } else {
5544 // Step 18.k.i.
5545 normTime = difference;
5546 days = 0;
5548 } else {
5549 // Step 19.a.
5550 auto timeDuration = NormalizeTimeDuration(duration);
5552 // Step 19.b.
5553 if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
5554 &normTime)) {
5555 return false;
5558 // Step 19.c.
5559 days = 0;
5561 MOZ_ASSERT(IsValidNormalizedTimeDuration(normTime));
5563 // Step 20.
5564 auto roundInput = NormalizedDuration{
5566 unbalanceResult.years,
5567 unbalanceResult.months,
5568 unbalanceResult.weeks,
5569 days,
5571 normTime,
5573 MOZ_ASSERT_IF(unit > TemporalUnit::Day, IsValidDuration(roundInput.date));
5575 RoundedDuration rounded;
5576 if (plainRelativeTo || zonedRelativeTo) {
5577 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5578 TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
5579 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5580 ComputeRemainder::Yes, &rounded)) {
5581 return false;
5583 } else {
5584 MOZ_ASSERT(IsValidDuration(roundInput));
5586 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5587 TemporalRoundingMode::Trunc, ComputeRemainder::Yes,
5588 &rounded)) {
5589 return false;
5593 // Step 21.
5594 args.rval().setNumber(rounded.total);
5595 return true;
5599 * Temporal.Duration.prototype.total ( totalOf )
5601 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
5602 // Steps 1-2.
5603 CallArgs args = CallArgsFromVp(argc, vp);
5604 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
5608 * Temporal.Duration.prototype.toString ( [ options ] )
5610 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
5611 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5613 // Steps 3-9.
5614 SecondsStringPrecision precision = {Precision::Auto(),
5615 TemporalUnit::Nanosecond, Increment{1}};
5616 auto roundingMode = TemporalRoundingMode::Trunc;
5617 if (args.hasDefined(0)) {
5618 // Step 3.
5619 Rooted<JSObject*> options(
5620 cx, RequireObjectArg(cx, "options", "toString", args[0]));
5621 if (!options) {
5622 return false;
5625 // Steps 4-5.
5626 auto digits = Precision::Auto();
5627 if (!ToFractionalSecondDigits(cx, options, &digits)) {
5628 return false;
5631 // Step 6.
5632 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5633 return false;
5636 // Step 7.
5637 auto smallestUnit = TemporalUnit::Auto;
5638 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5639 TemporalUnitGroup::Time, &smallestUnit)) {
5640 return false;
5643 // Step 8.
5644 if (smallestUnit == TemporalUnit::Hour ||
5645 smallestUnit == TemporalUnit::Minute) {
5646 const char* smallestUnitStr =
5647 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
5648 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5649 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
5650 smallestUnitStr, "smallestUnit");
5651 return false;
5654 // Step 9.
5655 precision = ToSecondsStringPrecision(smallestUnit, digits);
5658 // Steps 10-11.
5659 Duration result;
5660 if (precision.unit != TemporalUnit::Nanosecond ||
5661 precision.increment != Increment{1}) {
5662 // Step 10.a.
5663 auto timeDuration = NormalizeTimeDuration(duration);
5665 // Step 10.b.
5666 auto largestUnit = DefaultTemporalLargestUnit(duration);
5668 // Steps 10.c-d.
5669 NormalizedTimeDuration rounded;
5670 if (!RoundDuration(cx, timeDuration, precision.increment, precision.unit,
5671 roundingMode, &rounded)) {
5672 return false;
5675 // Step 10.e.
5676 auto balanced = BalanceTimeDuration(
5677 rounded, std::min(largestUnit, TemporalUnit::Second));
5679 // Step 10.f.
5680 result = {
5681 duration.years, duration.months,
5682 duration.weeks, duration.days + double(balanced.days),
5683 double(balanced.hours), double(balanced.minutes),
5684 double(balanced.seconds), double(balanced.milliseconds),
5685 balanced.microseconds, balanced.nanoseconds,
5687 MOZ_ASSERT(IsValidDuration(duration));
5688 } else {
5689 // Step 11.
5690 result = duration;
5693 // Steps 12-13.
5694 JSString* str = TemporalDurationToString(cx, result, precision.precision);
5695 if (!str) {
5696 return false;
5699 args.rval().setString(str);
5700 return true;
5704 * Temporal.Duration.prototype.toString ( [ options ] )
5706 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
5707 // Steps 1-2.
5708 CallArgs args = CallArgsFromVp(argc, vp);
5709 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
5713 * Temporal.Duration.prototype.toJSON ( )
5715 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
5716 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5718 // Steps 3-4.
5719 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5720 if (!str) {
5721 return false;
5724 args.rval().setString(str);
5725 return true;
5729 * Temporal.Duration.prototype.toJSON ( )
5731 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
5732 // Steps 1-2.
5733 CallArgs args = CallArgsFromVp(argc, vp);
5734 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
5738 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5740 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
5741 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5743 // Steps 3-4.
5744 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5745 if (!str) {
5746 return false;
5749 args.rval().setString(str);
5750 return true;
5754 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5756 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
5757 // Steps 1-2.
5758 CallArgs args = CallArgsFromVp(argc, vp);
5759 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
5763 * Temporal.Duration.prototype.valueOf ( )
5765 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
5766 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
5767 "Duration", "primitive type");
5768 return false;
5771 const JSClass DurationObject::class_ = {
5772 "Temporal.Duration",
5773 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
5774 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
5775 JS_NULL_CLASS_OPS,
5776 &DurationObject::classSpec_,
5779 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
5781 static const JSFunctionSpec Duration_methods[] = {
5782 JS_FN("from", Duration_from, 1, 0),
5783 JS_FN("compare", Duration_compare, 2, 0),
5784 JS_FS_END,
5787 static const JSFunctionSpec Duration_prototype_methods[] = {
5788 JS_FN("with", Duration_with, 1, 0),
5789 JS_FN("negated", Duration_negated, 0, 0),
5790 JS_FN("abs", Duration_abs, 0, 0),
5791 JS_FN("add", Duration_add, 1, 0),
5792 JS_FN("subtract", Duration_subtract, 1, 0),
5793 JS_FN("round", Duration_round, 1, 0),
5794 JS_FN("total", Duration_total, 1, 0),
5795 JS_FN("toString", Duration_toString, 0, 0),
5796 JS_FN("toJSON", Duration_toJSON, 0, 0),
5797 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
5798 JS_FN("valueOf", Duration_valueOf, 0, 0),
5799 JS_FS_END,
5802 static const JSPropertySpec Duration_prototype_properties[] = {
5803 JS_PSG("years", Duration_years, 0),
5804 JS_PSG("months", Duration_months, 0),
5805 JS_PSG("weeks", Duration_weeks, 0),
5806 JS_PSG("days", Duration_days, 0),
5807 JS_PSG("hours", Duration_hours, 0),
5808 JS_PSG("minutes", Duration_minutes, 0),
5809 JS_PSG("seconds", Duration_seconds, 0),
5810 JS_PSG("milliseconds", Duration_milliseconds, 0),
5811 JS_PSG("microseconds", Duration_microseconds, 0),
5812 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
5813 JS_PSG("sign", Duration_sign, 0),
5814 JS_PSG("blank", Duration_blank, 0),
5815 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
5816 JS_PS_END,
5819 const ClassSpec DurationObject::classSpec_ = {
5820 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
5821 GenericCreatePrototype<DurationObject>,
5822 Duration_methods,
5823 nullptr,
5824 Duration_prototype_methods,
5825 Duration_prototype_properties,
5826 nullptr,
5827 ClassSpec::DontDefineConstructor,