Bug 1874684 - Part 22: Compute the precise fraction in Duration.p.total and ZonedDate...
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blob9aad0e880f254cd1a586eda401745de676c6b041
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/Duration.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/EnumSet.h"
13 #include "mozilla/FloatingPoint.h"
14 #include "mozilla/Maybe.h"
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdlib>
19 #include <initializer_list>
20 #include <stdint.h>
21 #include <type_traits>
22 #include <utility>
24 #include "jsnum.h"
25 #include "jspubtd.h"
26 #include "NamespaceImports.h"
28 #include "builtin/temporal/Calendar.h"
29 #include "builtin/temporal/Instant.h"
30 #include "builtin/temporal/Int128.h"
31 #include "builtin/temporal/Int96.h"
32 #include "builtin/temporal/PlainDate.h"
33 #include "builtin/temporal/PlainDateTime.h"
34 #include "builtin/temporal/Temporal.h"
35 #include "builtin/temporal/TemporalFields.h"
36 #include "builtin/temporal/TemporalParser.h"
37 #include "builtin/temporal/TemporalRoundingMode.h"
38 #include "builtin/temporal/TemporalTypes.h"
39 #include "builtin/temporal/TemporalUnit.h"
40 #include "builtin/temporal/TimeZone.h"
41 #include "builtin/temporal/Wrapped.h"
42 #include "builtin/temporal/ZonedDateTime.h"
43 #include "gc/AllocKind.h"
44 #include "gc/Barrier.h"
45 #include "gc/GCEnum.h"
46 #include "js/CallArgs.h"
47 #include "js/CallNonGenericMethod.h"
48 #include "js/Class.h"
49 #include "js/Conversions.h"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
53 #include "js/Id.h"
54 #include "js/Printer.h"
55 #include "js/PropertyDescriptor.h"
56 #include "js/PropertySpec.h"
57 #include "js/RootingAPI.h"
58 #include "js/Value.h"
59 #include "util/StringBuffer.h"
60 #include "vm/BytecodeUtil.h"
61 #include "vm/GlobalObject.h"
62 #include "vm/JSAtomState.h"
63 #include "vm/JSContext.h"
64 #include "vm/JSObject.h"
65 #include "vm/ObjectOperations.h"
66 #include "vm/PlainObject.h"
67 #include "vm/StringType.h"
69 #include "vm/JSObject-inl.h"
70 #include "vm/NativeObject-inl.h"
71 #include "vm/ObjectOperations-inl.h"
73 using namespace js;
74 using namespace js::temporal;
76 static inline bool IsDuration(Handle<Value> v) {
77 return v.isObject() && v.toObject().is<DurationObject>();
80 #ifdef DEBUG
81 static bool IsIntegerOrInfinity(double d) {
82 return IsInteger(d) || std::isinf(d);
85 static bool IsIntegerOrInfinityDuration(const Duration& duration) {
86 const auto& [years, months, weeks, days, hours, minutes, seconds,
87 milliseconds, microseconds, nanoseconds] = duration;
89 // Integers exceeding the Number range are represented as infinity.
91 return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
92 IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
93 IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
94 IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
95 IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
98 static bool IsIntegerDuration(const Duration& duration) {
99 const auto& [years, months, weeks, days, hours, minutes, seconds,
100 milliseconds, microseconds, nanoseconds] = duration;
102 return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
103 IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
104 IsInteger(seconds) && IsInteger(milliseconds) &&
105 IsInteger(microseconds) && IsInteger(nanoseconds);
107 #endif
109 static constexpr bool IsSafeInteger(int64_t x) {
110 constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
111 constexpr int64_t MinSafeInteger = -MaxSafeInteger;
112 return MinSafeInteger < x && x < MaxSafeInteger;
116 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
117 * milliseconds, microseconds, nanoseconds )
119 int32_t js::temporal::DurationSign(const Duration& duration) {
120 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
122 const auto& [years, months, weeks, days, hours, minutes, seconds,
123 milliseconds, microseconds, nanoseconds] = duration;
125 // Step 1.
126 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
127 milliseconds, microseconds, nanoseconds}) {
128 // Step 1.a.
129 if (v < 0) {
130 return -1;
133 // Step 1.b.
134 if (v > 0) {
135 return 1;
139 // Step 2.
140 return 0;
144 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
145 * milliseconds, microseconds, nanoseconds )
147 static int32_t DurationSign(const DateDuration& duration) {
148 const auto& [years, months, weeks, days] = duration;
150 // Step 1.
151 for (auto v : {years, months, weeks, days}) {
152 // Step 1.a.
153 if (v < 0) {
154 return -1;
157 // Step 1.b.
158 if (v > 0) {
159 return 1;
163 // Step 2.
164 return 0;
168 * Normalize a nanoseconds amount into a time duration.
170 static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
171 // Split into seconds and nanoseconds.
172 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
174 return {seconds, nanos};
178 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
179 * value is too large.
181 static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
182 double nanoseconds) {
183 MOZ_ASSERT(IsInteger(nanoseconds));
185 if (auto int96 = Int96::fromInteger(nanoseconds)) {
186 // The number of normalized seconds must not exceed `2**53 - 1`.
187 constexpr auto limit =
188 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
190 if (int96->abs() < limit) {
191 return mozilla::Some(NormalizeNanoseconds(*int96));
194 return mozilla::Nothing();
198 * Normalize a microseconds amount into a time duration.
200 static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
201 // Split into seconds and microseconds.
202 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
204 // Scale microseconds to nanoseconds.
205 int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));
207 return {seconds, nanos};
211 * Normalize a microseconds amount into a time duration. Return Nothing if the
212 * value is too large.
214 static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
215 double microseconds) {
216 MOZ_ASSERT(IsInteger(microseconds));
218 if (auto int96 = Int96::fromInteger(microseconds)) {
219 // The number of normalized seconds must not exceed `2**53 - 1`.
220 constexpr auto limit =
221 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
223 if (int96->abs() < limit) {
224 return mozilla::Some(NormalizeMicroseconds(*int96));
227 return mozilla::Nothing();
231 * Normalize a duration into a time duration. Return Nothing if any duration
232 * value is too large.
234 static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
235 const Duration& duration) {
236 do {
237 auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
238 if (!nanoseconds) {
239 break;
241 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
243 auto microseconds = NormalizeMicroseconds(duration.microseconds);
244 if (!microseconds) {
245 break;
247 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
249 // Overflows for millis/seconds/minutes/hours/days always result in an
250 // invalid normalized time duration.
252 int64_t milliseconds;
253 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
254 break;
257 int64_t seconds;
258 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
259 break;
262 int64_t minutes;
263 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
264 break;
267 int64_t hours;
268 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
269 break;
272 int64_t days;
273 if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
274 break;
277 // Compute the overall amount of milliseconds.
278 mozilla::CheckedInt64 millis = days;
279 millis *= 24;
280 millis += hours;
281 millis *= 60;
282 millis += minutes;
283 millis *= 60;
284 millis += seconds;
285 millis *= 1000;
286 millis += milliseconds;
287 if (!millis.isValid()) {
288 break;
291 auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
292 if (!IsValidNormalizedTimeDuration(milli)) {
293 break;
296 // Compute the overall time duration.
297 auto result = milli + *microseconds + *nanoseconds;
298 if (!IsValidNormalizedTimeDuration(result)) {
299 break;
302 return mozilla::Some(result);
303 } while (false);
305 return mozilla::Nothing();
309 * Normalize a days amount into a time duration. Return Nothing if the value is
310 * too large.
312 static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(int64_t days) {
313 do {
314 // Compute the overall amount of milliseconds.
315 auto millis =
316 mozilla::CheckedInt64(days) * ToMilliseconds(TemporalUnit::Day);
317 if (!millis.isValid()) {
318 break;
321 auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
322 if (!IsValidNormalizedTimeDuration(result)) {
323 break;
326 return mozilla::Some(result);
327 } while (false);
329 return mozilla::Nothing();
333 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
334 * nanoseconds )
336 static NormalizedTimeDuration NormalizeTimeDuration(
337 double hours, double minutes, double seconds, double milliseconds,
338 double microseconds, double nanoseconds) {
339 MOZ_ASSERT(IsInteger(hours));
340 MOZ_ASSERT(IsInteger(minutes));
341 MOZ_ASSERT(IsInteger(seconds));
342 MOZ_ASSERT(IsInteger(milliseconds));
343 MOZ_ASSERT(IsInteger(microseconds));
344 MOZ_ASSERT(IsInteger(nanoseconds));
346 // Steps 1-3.
347 mozilla::CheckedInt64 millis = int64_t(hours);
348 millis *= 60;
349 millis += int64_t(minutes);
350 millis *= 60;
351 millis += int64_t(seconds);
352 millis *= 1000;
353 millis += int64_t(milliseconds);
354 MOZ_ASSERT(millis.isValid());
356 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
358 // Step 4.
359 auto micros = Int96::fromInteger(microseconds);
360 MOZ_ASSERT(micros);
362 normalized += NormalizeMicroseconds(*micros);
364 // Step 5.
365 auto nanos = Int96::fromInteger(nanoseconds);
366 MOZ_ASSERT(nanos);
368 normalized += NormalizeNanoseconds(*nanos);
370 // Step 6.
371 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
373 // Step 7.
374 return normalized;
378 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
379 * nanoseconds )
381 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
382 int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
383 int32_t microseconds, int32_t nanoseconds) {
384 // Steps 1-3.
385 mozilla::CheckedInt64 millis = int64_t(hours);
386 millis *= 60;
387 millis += int64_t(minutes);
388 millis *= 60;
389 millis += int64_t(seconds);
390 millis *= 1000;
391 millis += int64_t(milliseconds);
392 MOZ_ASSERT(millis.isValid());
394 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
396 // Step 4.
397 normalized += NormalizeMicroseconds(Int96{microseconds});
399 // Step 5.
400 normalized += NormalizeNanoseconds(Int96{nanoseconds});
402 // Step 6.
403 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
405 // Step 7.
406 return normalized;
410 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
411 * nanoseconds )
413 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
414 const Duration& duration) {
415 MOZ_ASSERT(IsValidDuration(duration));
417 return ::NormalizeTimeDuration(duration.hours, duration.minutes,
418 duration.seconds, duration.milliseconds,
419 duration.microseconds, duration.nanoseconds);
423 * AddNormalizedTimeDuration ( one, two )
425 static bool AddNormalizedTimeDuration(JSContext* cx,
426 const NormalizedTimeDuration& one,
427 const NormalizedTimeDuration& two,
428 NormalizedTimeDuration* result) {
429 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
430 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
432 // Step 1.
433 auto sum = one + two;
435 // Step 2.
436 if (!IsValidNormalizedTimeDuration(sum)) {
437 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
438 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
439 return false;
442 // Step 3.
443 *result = sum;
444 return true;
448 * SubtractNormalizedTimeDuration ( one, two )
450 static bool SubtractNormalizedTimeDuration(JSContext* cx,
451 const NormalizedTimeDuration& one,
452 const NormalizedTimeDuration& two,
453 NormalizedTimeDuration* result) {
454 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
455 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
457 // Step 1.
458 auto sum = one - two;
460 // Step 2.
461 if (!IsValidNormalizedTimeDuration(sum)) {
462 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
463 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
464 return false;
467 // Step 3.
468 *result = sum;
469 return true;
473 * Add24HourDaysToNormalizedTimeDuration ( d, days )
475 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
476 JSContext* cx, const NormalizedTimeDuration& d, int64_t days,
477 NormalizedTimeDuration* result) {
478 MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
480 // Step 1.
481 auto normalizedDays = NormalizeDays(days);
482 if (!normalizedDays) {
483 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
484 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
485 return false;
488 // Step 2.
489 auto sum = d + *normalizedDays;
490 if (!IsValidNormalizedTimeDuration(sum)) {
491 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
492 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
493 return false;
496 // Step 3.
497 *result = sum;
498 return true;
502 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
504 bool js::temporal::CombineDateAndNormalizedTimeDuration(
505 JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
506 NormalizedDuration* result) {
507 MOZ_ASSERT(IsValidDuration(date));
508 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
510 // Step 1.
511 int32_t dateSign = ::DurationSign(date);
513 // Step 2.
514 int32_t timeSign = NormalizedTimeDurationSign(time);
516 // Step 3
517 if ((dateSign * timeSign) < 0) {
518 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
519 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
520 return false;
523 // Step 4.
524 *result = {date, time};
525 return true;
529 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
531 NormalizedTimeDuration
532 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
533 const Instant& one, const Instant& two) {
534 MOZ_ASSERT(IsValidEpochInstant(one));
535 MOZ_ASSERT(IsValidEpochInstant(two));
537 // Step 1.
538 auto result = one - two;
540 // Step 2.
541 MOZ_ASSERT(IsValidInstantSpan(result));
543 // Step 3.
544 return result.to<NormalizedTimeDuration>();
548 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
549 * milliseconds, microseconds, nanoseconds )
551 bool js::temporal::IsValidDuration(const Duration& duration) {
552 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
554 const auto& [years, months, weeks, days, hours, minutes, seconds,
555 milliseconds, microseconds, nanoseconds] = duration;
557 // Step 1.
558 int32_t sign = DurationSign(duration);
560 // Step 2.
561 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
562 milliseconds, microseconds, nanoseconds}) {
563 // Step 2.a.
564 if (!std::isfinite(v)) {
565 return false;
568 // Step 2.b.
569 if (v < 0 && sign > 0) {
570 return false;
573 // Step 2.c.
574 if (v > 0 && sign < 0) {
575 return false;
579 // Step 3.
580 if (std::abs(years) >= double(int64_t(1) << 32)) {
581 return false;
584 // Step 4.
585 if (std::abs(months) >= double(int64_t(1) << 32)) {
586 return false;
589 // Step 5.
590 if (std::abs(weeks) >= double(int64_t(1) << 32)) {
591 return false;
594 // Steps 6-8.
595 if (!NormalizeSeconds(duration)) {
596 return false;
599 // Step 9.
600 return true;
603 #ifdef DEBUG
605 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
606 * milliseconds, microseconds, nanoseconds )
608 bool js::temporal::IsValidDuration(const DateDuration& duration) {
609 return IsValidDuration(duration.toDuration());
613 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
614 * milliseconds, microseconds, nanoseconds )
616 bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
617 return IsValidDuration(duration.date) &&
618 IsValidNormalizedTimeDuration(duration.time) &&
619 (::DurationSign(duration.date) *
620 NormalizedTimeDurationSign(duration.time) >=
623 #endif
625 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
626 const char* name, unsigned errorNumber) {
627 ToCStringBuf cbuf;
628 const char* numStr = NumberToCString(&cbuf, value);
630 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
631 numStr);
632 return false;
636 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
637 * milliseconds, microseconds, nanoseconds )
639 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
640 const Duration& duration) {
641 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
643 const auto& [years, months, weeks, days, hours, minutes, seconds,
644 milliseconds, microseconds, nanoseconds] = duration;
646 // Step 1.
647 int32_t sign = DurationSign(duration);
649 auto throwIfInvalid = [&](double v, const char* name) {
650 // Step 2.a.
651 if (!std::isfinite(v)) {
652 return ThrowInvalidDurationPart(
653 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
656 // Steps 2.b-c.
657 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
658 return ThrowInvalidDurationPart(cx, v, name,
659 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
662 return true;
665 auto throwIfTooLarge = [&](double v, const char* name) {
666 if (std::abs(v) >= double(int64_t(1) << 32)) {
667 return ThrowInvalidDurationPart(
668 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
670 return true;
673 // Step 2.
674 if (!throwIfInvalid(years, "years")) {
675 return false;
677 if (!throwIfInvalid(months, "months")) {
678 return false;
680 if (!throwIfInvalid(weeks, "weeks")) {
681 return false;
683 if (!throwIfInvalid(days, "days")) {
684 return false;
686 if (!throwIfInvalid(hours, "hours")) {
687 return false;
689 if (!throwIfInvalid(minutes, "minutes")) {
690 return false;
692 if (!throwIfInvalid(seconds, "seconds")) {
693 return false;
695 if (!throwIfInvalid(milliseconds, "milliseconds")) {
696 return false;
698 if (!throwIfInvalid(microseconds, "microseconds")) {
699 return false;
701 if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
702 return false;
705 // Step 3.
706 if (!throwIfTooLarge(years, "years")) {
707 return false;
710 // Step 4.
711 if (!throwIfTooLarge(months, "months")) {
712 return false;
715 // Step 5.
716 if (!throwIfTooLarge(weeks, "weeks")) {
717 return false;
720 // Steps 6-8.
721 if (!NormalizeSeconds(duration)) {
722 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
723 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
724 return false;
727 MOZ_ASSERT(IsValidDuration(duration));
729 // Step 9.
730 return true;
734 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
735 * milliseconds, microseconds, nanoseconds )
737 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
738 const DateDuration& duration) {
739 const auto& [years, months, weeks, days] = duration;
741 // Step 1.
742 int32_t sign = ::DurationSign(duration);
744 auto throwIfInvalid = [&](int64_t v, const char* name) {
745 // Step 2.a. (Not applicable)
747 // Steps 2.b-c.
748 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
749 return ThrowInvalidDurationPart(cx, double(v), name,
750 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
753 return true;
756 auto throwIfTooLarge = [&](int64_t v, const char* name) {
757 if (std::abs(v) >= (int64_t(1) << 32)) {
758 return ThrowInvalidDurationPart(
759 cx, double(v), name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
761 return true;
764 // Step 2.
765 if (!throwIfInvalid(years, "years")) {
766 return false;
768 if (!throwIfInvalid(months, "months")) {
769 return false;
771 if (!throwIfInvalid(weeks, "weeks")) {
772 return false;
774 if (!throwIfInvalid(days, "days")) {
775 return false;
778 // Step 3.
779 if (!throwIfTooLarge(years, "years")) {
780 return false;
783 // Step 4.
784 if (!throwIfTooLarge(months, "months")) {
785 return false;
788 // Step 5.
789 if (!throwIfTooLarge(weeks, "weeks")) {
790 return false;
793 // Steps 6-8.
794 if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
795 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
796 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
797 return false;
800 MOZ_ASSERT(IsValidDuration(duration));
802 // Step 9.
803 return true;
807 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
808 * seconds, milliseconds, microseconds )
810 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
811 MOZ_ASSERT(IsIntegerDuration(duration));
813 // Step 1.
814 if (duration.years != 0) {
815 return TemporalUnit::Year;
818 // Step 2.
819 if (duration.months != 0) {
820 return TemporalUnit::Month;
823 // Step 3.
824 if (duration.weeks != 0) {
825 return TemporalUnit::Week;
828 // Step 4.
829 if (duration.days != 0) {
830 return TemporalUnit::Day;
833 // Step 5.
834 if (duration.hours != 0) {
835 return TemporalUnit::Hour;
838 // Step 6.
839 if (duration.minutes != 0) {
840 return TemporalUnit::Minute;
843 // Step 7.
844 if (duration.seconds != 0) {
845 return TemporalUnit::Second;
848 // Step 8.
849 if (duration.milliseconds != 0) {
850 return TemporalUnit::Millisecond;
853 // Step 9.
854 if (duration.microseconds != 0) {
855 return TemporalUnit::Microsecond;
858 // Step 10.
859 return TemporalUnit::Nanosecond;
863 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
864 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
866 static DurationObject* CreateTemporalDuration(JSContext* cx,
867 const CallArgs& args,
868 const Duration& duration) {
869 const auto& [years, months, weeks, days, hours, minutes, seconds,
870 milliseconds, microseconds, nanoseconds] = duration;
872 // Step 1.
873 if (!ThrowIfInvalidDuration(cx, duration)) {
874 return nullptr;
877 // Steps 2-3.
878 Rooted<JSObject*> proto(cx);
879 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
880 return nullptr;
883 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
884 if (!object) {
885 return nullptr;
888 // Steps 4-13.
889 // Add zero to convert -0 to +0.
890 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
891 object->setFixedSlot(DurationObject::MONTHS_SLOT,
892 NumberValue(months + (+0.0)));
893 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
894 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
895 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
896 object->setFixedSlot(DurationObject::MINUTES_SLOT,
897 NumberValue(minutes + (+0.0)));
898 object->setFixedSlot(DurationObject::SECONDS_SLOT,
899 NumberValue(seconds + (+0.0)));
900 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
901 NumberValue(milliseconds + (+0.0)));
902 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
903 NumberValue(microseconds + (+0.0)));
904 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
905 NumberValue(nanoseconds + (+0.0)));
907 // Step 14.
908 return object;
912 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
913 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
915 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
916 const Duration& duration) {
917 const auto& [years, months, weeks, days, hours, minutes, seconds,
918 milliseconds, microseconds, nanoseconds] = duration;
920 MOZ_ASSERT(IsInteger(years));
921 MOZ_ASSERT(IsInteger(months));
922 MOZ_ASSERT(IsInteger(weeks));
923 MOZ_ASSERT(IsInteger(days));
924 MOZ_ASSERT(IsInteger(hours));
925 MOZ_ASSERT(IsInteger(minutes));
926 MOZ_ASSERT(IsInteger(seconds));
927 MOZ_ASSERT(IsInteger(milliseconds));
928 MOZ_ASSERT(IsInteger(microseconds));
929 MOZ_ASSERT(IsInteger(nanoseconds));
931 // Step 1.
932 if (!ThrowIfInvalidDuration(cx, duration)) {
933 return nullptr;
936 // Steps 2-3.
937 auto* object = NewBuiltinClassInstance<DurationObject>(cx);
938 if (!object) {
939 return nullptr;
942 // Steps 4-13.
943 // Add zero to convert -0 to +0.
944 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
945 object->setFixedSlot(DurationObject::MONTHS_SLOT,
946 NumberValue(months + (+0.0)));
947 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
948 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
949 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
950 object->setFixedSlot(DurationObject::MINUTES_SLOT,
951 NumberValue(minutes + (+0.0)));
952 object->setFixedSlot(DurationObject::SECONDS_SLOT,
953 NumberValue(seconds + (+0.0)));
954 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
955 NumberValue(milliseconds + (+0.0)));
956 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
957 NumberValue(microseconds + (+0.0)));
958 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
959 NumberValue(nanoseconds + (+0.0)));
961 // Step 14.
962 return object;
966 * ToIntegerIfIntegral ( argument )
968 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
969 Handle<Value> argument, double* num) {
970 // Step 1.
971 double d;
972 if (!JS::ToNumber(cx, argument, &d)) {
973 return false;
976 // Step 2.
977 if (!js::IsInteger(d)) {
978 ToCStringBuf cbuf;
979 const char* numStr = NumberToCString(&cbuf, d);
981 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
982 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
983 name);
984 return false;
987 // Step 3.
988 *num = d;
989 return true;
993 * ToIntegerIfIntegral ( argument )
995 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
996 Handle<Value> argument, double* result) {
997 // Step 1.
998 double d;
999 if (!JS::ToNumber(cx, argument, &d)) {
1000 return false;
1003 // Step 2.
1004 if (!js::IsInteger(d)) {
1005 if (auto nameStr = js::QuoteString(cx, name)) {
1006 ToCStringBuf cbuf;
1007 const char* numStr = NumberToCString(&cbuf, d);
1009 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1010 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
1011 nameStr.get());
1013 return false;
1016 // Step 3.
1017 *result = d;
1018 return true;
1022 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1024 static bool ToTemporalPartialDurationRecord(
1025 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
1026 // Steps 1-3. (Not applicable in our implementation.)
1028 Rooted<Value> value(cx);
1029 bool any = false;
1031 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
1032 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
1033 &value)) {
1034 return false;
1037 if (!value.isUndefined()) {
1038 any = true;
1040 if (!ToIntegerIfIntegral(cx, name, value, num)) {
1041 return false;
1044 return true;
1047 // Steps 4-23.
1048 if (!getDurationProperty(cx->names().days, &result->days)) {
1049 return false;
1051 if (!getDurationProperty(cx->names().hours, &result->hours)) {
1052 return false;
1054 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
1055 return false;
1057 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
1058 return false;
1060 if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
1061 return false;
1063 if (!getDurationProperty(cx->names().months, &result->months)) {
1064 return false;
1066 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
1067 return false;
1069 if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
1070 return false;
1072 if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
1073 return false;
1075 if (!getDurationProperty(cx->names().years, &result->years)) {
1076 return false;
1079 // Step 24.
1080 if (!any) {
1081 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1082 JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
1083 return false;
1086 // Step 25.
1087 return true;
1091 * ToTemporalDurationRecord ( temporalDurationLike )
1093 bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
1094 Handle<Value> temporalDurationLike,
1095 Duration* result) {
1096 // Step 1.
1097 if (!temporalDurationLike.isObject()) {
1098 // Step 1.a.
1099 if (!temporalDurationLike.isString()) {
1100 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
1101 temporalDurationLike, nullptr, "not a string");
1102 return false;
1104 Rooted<JSString*> string(cx, temporalDurationLike.toString());
1106 // Step 1.b.
1107 return ParseTemporalDurationString(cx, string, result);
1110 Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
1112 // Step 2.
1113 if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
1114 *result = ToDuration(duration);
1115 return true;
1118 // Step 3.
1119 Duration duration = {};
1121 // Steps 4-14.
1122 if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
1123 return false;
1126 // Step 15.
1127 if (!ThrowIfInvalidDuration(cx, duration)) {
1128 return false;
1131 // Step 16.
1132 *result = duration;
1133 return true;
1137 * ToTemporalDuration ( item )
1139 Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
1140 Handle<Value> item) {
1141 // Step 1.
1142 if (item.isObject()) {
1143 JSObject* itemObj = &item.toObject();
1144 if (itemObj->canUnwrapAs<DurationObject>()) {
1145 return itemObj;
1149 // Step 2.
1150 Duration result;
1151 if (!ToTemporalDurationRecord(cx, item, &result)) {
1152 return nullptr;
1155 // Step 3.
1156 return CreateTemporalDuration(cx, result);
1160 * ToTemporalDuration ( item )
1162 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
1163 Duration* result) {
1164 auto obj = ToTemporalDuration(cx, item);
1165 if (!obj) {
1166 return false;
1169 *result = ToDuration(&obj.unwrap());
1170 return true;
1174 * DaysUntil ( earlier, later )
1176 int32_t js::temporal::DaysUntil(const PlainDate& earlier,
1177 const PlainDate& later) {
1178 MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
1179 MOZ_ASSERT(ISODateTimeWithinLimits(later));
1181 // Steps 1-2.
1182 int32_t epochDaysEarlier = MakeDay(earlier);
1183 MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
1185 // Steps 3-4.
1186 int32_t epochDaysLater = MakeDay(later);
1187 MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
1189 // Step 5.
1190 return epochDaysLater - epochDaysEarlier;
1194 * MoveRelativeDate ( calendarRec, relativeTo, duration )
1196 static bool MoveRelativeDate(
1197 JSContext* cx, Handle<CalendarRecord> calendar,
1198 Handle<Wrapped<PlainDateObject*>> relativeTo, const DateDuration& duration,
1199 MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
1200 int32_t* daysResult) {
1201 auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
1202 if (!unwrappedRelativeTo) {
1203 return false;
1205 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1207 // Step 1.
1208 auto newDate = AddDate(cx, calendar, relativeTo, duration);
1209 if (!newDate) {
1210 return false;
1212 auto later = ToPlainDate(&newDate.unwrap());
1213 relativeToResult.set(newDate);
1215 // Step 2.
1216 *daysResult = DaysUntil(relativeToDate, later);
1217 MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
1219 // Step 3.
1220 return true;
1224 * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
1225 * months, weeks, days, precalculatedPlainDateTime )
1227 static bool MoveRelativeZonedDateTime(
1228 JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
1229 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
1230 const DateDuration& duration,
1231 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1232 MutableHandle<ZonedDateTime> result) {
1233 // Step 1.
1234 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1235 timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
1237 // Step 2.
1238 MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
1239 timeZone, TimeZoneMethod::GetPossibleInstantsFor));
1241 // Step 3.
1242 Instant intermediateNs;
1243 if (precalculatedPlainDateTime) {
1244 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1245 duration, *precalculatedPlainDateTime,
1246 &intermediateNs)) {
1247 return false;
1249 } else {
1250 if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
1251 duration, &intermediateNs)) {
1252 return false;
1255 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
1257 // Step 4.
1258 result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
1259 zonedDateTime.calendar()});
1260 return true;
1264 * Split duration into full days and remainding nanoseconds.
1266 static NormalizedTimeAndDays NormalizedTimeDurationToDays(
1267 const NormalizedTimeDuration& duration) {
1268 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1270 auto [seconds, nanoseconds] = duration;
1271 if (seconds < 0 && nanoseconds > 0) {
1272 seconds += 1;
1273 nanoseconds -= 1'000'000'000;
1276 int64_t days = seconds / ToSeconds(TemporalUnit::Day);
1277 seconds = seconds % ToSeconds(TemporalUnit::Day);
1279 int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
1281 constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
1282 MOZ_ASSERT(std::abs(time) < dayLength);
1284 return {days, time, dayLength};
1288 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1289 * microseconds, nanoseconds )
1291 static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
1292 int64_t minutes, int64_t seconds,
1293 int64_t milliseconds,
1294 double microseconds,
1295 double nanoseconds) {
1296 // Step 1.
1297 MOZ_ASSERT(IsValidDuration(
1298 {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
1299 double(milliseconds), microseconds, nanoseconds}));
1301 // |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
1302 // need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
1303 MOZ_ASSERT(IsSafeInteger(days));
1304 MOZ_ASSERT(IsSafeInteger(hours));
1305 MOZ_ASSERT(IsSafeInteger(minutes));
1306 MOZ_ASSERT(IsSafeInteger(seconds));
1308 // |milliseconds| is explicitly casted to double by consumers, so we can also
1309 // omit the `ℝ(𝔽(x))` conversion.
1311 // Step 2.
1312 // NB: Adds +0.0 to correctly handle negative zero.
1313 return {
1314 days,
1315 hours,
1316 minutes,
1317 seconds,
1318 milliseconds,
1319 microseconds + (+0.0),
1320 nanoseconds + (+0.0),
1325 * BalanceTimeDuration ( norm, largestUnit )
1327 TimeDuration js::temporal::BalanceTimeDuration(
1328 const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
1329 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1331 auto [seconds, nanoseconds] = duration;
1333 // Negative nanoseconds are represented as the difference to 1'000'000'000.
1334 // Convert these back to their absolute value and adjust the seconds part
1335 // accordingly.
1337 // For example the nanoseconds duration |-1n| is represented as the
1338 // duration {seconds: -1, nanoseconds: 999'999'999}.
1339 if (seconds < 0 && nanoseconds > 0) {
1340 seconds += 1;
1341 nanoseconds -= ToNanoseconds(TemporalUnit::Second);
1344 // Step 1.
1345 int64_t days = 0;
1346 int64_t hours = 0;
1347 int64_t minutes = 0;
1348 int64_t milliseconds = 0;
1349 int64_t microseconds = 0;
1351 // Steps 2-3. (Not applicable in our implementation.)
1353 // We don't need to convert to positive numbers, because integer division
1354 // truncates and the %-operator has modulo semantics.
1356 // Steps 4-10.
1357 switch (largestUnit) {
1358 // Step 4.
1359 case TemporalUnit::Year:
1360 case TemporalUnit::Month:
1361 case TemporalUnit::Week:
1362 case TemporalUnit::Day: {
1363 // Step 4.a.
1364 microseconds = nanoseconds / 1000;
1366 // Step 4.b.
1367 nanoseconds = nanoseconds % 1000;
1369 // Step 4.c.
1370 milliseconds = microseconds / 1000;
1372 // Step 4.d.
1373 microseconds = microseconds % 1000;
1375 // Steps 4.e-f. (Not applicable)
1376 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1378 // Step 4.g.
1379 minutes = seconds / 60;
1381 // Step 4.h.
1382 seconds = seconds % 60;
1384 // Step 4.i.
1385 hours = minutes / 60;
1387 // Step 4.j.
1388 minutes = minutes % 60;
1390 // Step 4.k.
1391 days = hours / 24;
1393 // Step 4.l.
1394 hours = hours % 24;
1396 break;
1399 // Step 5.
1400 case TemporalUnit::Hour: {
1401 // Step 5.a.
1402 microseconds = nanoseconds / 1000;
1404 // Step 5.b.
1405 nanoseconds = nanoseconds % 1000;
1407 // Step 5.c.
1408 milliseconds = microseconds / 1000;
1410 // Step 5.d.
1411 microseconds = microseconds % 1000;
1413 // Steps 5.e-f. (Not applicable)
1414 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1416 // Step 5.g.
1417 minutes = seconds / 60;
1419 // Step 5.h.
1420 seconds = seconds % 60;
1422 // Step 5.i.
1423 hours = minutes / 60;
1425 // Step 5.j.
1426 minutes = minutes % 60;
1428 break;
1431 case TemporalUnit::Minute: {
1432 // Step 6.a.
1433 microseconds = nanoseconds / 1000;
1435 // Step 6.b.
1436 nanoseconds = nanoseconds % 1000;
1438 // Step 6.c.
1439 milliseconds = microseconds / 1000;
1441 // Step 6.d.
1442 microseconds = microseconds % 1000;
1444 // Steps 6.e-f. (Not applicable)
1445 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1447 // Step 6.g.
1448 minutes = seconds / 60;
1450 // Step 6.h.
1451 seconds = seconds % 60;
1453 break;
1456 // Step 7.
1457 case TemporalUnit::Second: {
1458 // Step 7.a.
1459 microseconds = nanoseconds / 1000;
1461 // Step 7.b.
1462 nanoseconds = nanoseconds % 1000;
1464 // Step 7.c.
1465 milliseconds = microseconds / 1000;
1467 // Step 7.d.
1468 microseconds = microseconds % 1000;
1470 // Steps 7.e-f. (Not applicable)
1471 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1473 break;
1476 // Step 8.
1477 case TemporalUnit::Millisecond: {
1478 static_assert((NormalizedTimeDuration::max().seconds + 1) *
1479 ToMilliseconds(TemporalUnit::Second) <=
1480 INT64_MAX,
1481 "total number duration milliseconds fits into int64");
1483 int64_t millis = seconds * ToMilliseconds(TemporalUnit::Second);
1485 // Set to zero per step 1.
1486 seconds = 0;
1488 // Step 8.a.
1489 microseconds = nanoseconds / 1000;
1491 // Step 8.b.
1492 nanoseconds = nanoseconds % 1000;
1494 // Step 8.c.
1495 milliseconds = microseconds / 1000;
1497 // Step 8.d.
1498 microseconds = microseconds % 1000;
1500 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1501 milliseconds += millis;
1503 break;
1506 // Step 9.
1507 case TemporalUnit::Microsecond: {
1508 // Step 9.a.
1509 int64_t microseconds = nanoseconds / 1000;
1511 // Step 9.b.
1512 nanoseconds = nanoseconds % 1000;
1514 MOZ_ASSERT(std::abs(microseconds) <= 999'999);
1515 double micros =
1516 std::fma(double(seconds), ToMicroseconds(TemporalUnit::Second),
1517 double(microseconds));
1519 // Step 11.
1520 return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros,
1521 double(nanoseconds));
1524 // Step 10.
1525 case TemporalUnit::Nanosecond: {
1526 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
1527 double nanos =
1528 std::fma(double(seconds), ToNanoseconds(TemporalUnit::Second),
1529 double(nanoseconds));
1531 // Step 11.
1532 return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos);
1535 case TemporalUnit::Auto:
1536 MOZ_CRASH("Unexpected temporal unit");
1539 // Step 11.
1540 return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
1541 double(microseconds), double(nanoseconds));
1545 * BalanceTimeDurationRelative ( days, norm, largestUnit, zonedRelativeTo,
1546 * timeZoneRec, precalculatedPlainDateTime )
1548 static bool BalanceTimeDurationRelative(
1549 JSContext* cx, const NormalizedDuration& duration, TemporalUnit largestUnit,
1550 Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
1551 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
1552 TimeDuration* result) {
1553 MOZ_ASSERT(IsValidDuration(duration));
1555 // Step 1.
1556 const auto& startNs = relativeTo.instant();
1558 // Step 2.
1559 const auto& startInstant = startNs;
1561 // Step 3.
1562 auto intermediateNs = startNs;
1564 // Step 4.
1565 PlainDateTime startDateTime;
1566 if (duration.date.days != 0) {
1567 // Step 4.a.
1568 if (!precalculatedPlainDateTime) {
1569 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1570 return false;
1572 precalculatedPlainDateTime =
1573 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1576 // Steps 4.b-c.
1577 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
1578 if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
1579 timeZone, isoCalendar, duration.date.days,
1580 &intermediateNs)) {
1581 return false;
1585 // Step 5.
1586 Instant endNs;
1587 if (!AddInstant(cx, intermediateNs, duration.time, &endNs)) {
1588 return false;
1590 MOZ_ASSERT(IsValidEpochInstant(endNs));
1592 // Step 6.
1593 auto normalized =
1594 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startInstant);
1596 // Step 7.
1597 if (normalized == NormalizedTimeDuration{}) {
1598 *result = {};
1599 return true;
1602 // Steps 8-9.
1603 int64_t days = 0;
1604 if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
1605 // Step 8.a.
1606 if (!precalculatedPlainDateTime) {
1607 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
1608 return false;
1610 precalculatedPlainDateTime =
1611 mozilla::SomeRef<const PlainDateTime>(startDateTime);
1614 // Step 8.b.
1615 NormalizedTimeAndDays timeAndDays;
1616 if (!NormalizedTimeDurationToDays(cx, normalized, relativeTo, timeZone,
1617 *precalculatedPlainDateTime,
1618 &timeAndDays)) {
1619 return false;
1622 // Step 8.c.
1623 days = timeAndDays.days;
1625 // Step 8.d.
1626 normalized = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
1627 MOZ_ASSERT_IF(days > 0, normalized >= NormalizedTimeDuration{});
1628 MOZ_ASSERT_IF(days < 0, normalized <= NormalizedTimeDuration{});
1630 // Step 8.e.
1631 largestUnit = TemporalUnit::Hour;
1634 // Step 10.
1635 auto balanceResult = BalanceTimeDuration(normalized, largestUnit);
1637 // Step 11.
1638 *result = {
1639 days,
1640 balanceResult.hours,
1641 balanceResult.minutes,
1642 balanceResult.seconds,
1643 balanceResult.milliseconds,
1644 balanceResult.microseconds,
1645 balanceResult.nanoseconds,
1647 MOZ_ASSERT(IsValidDuration(result->toDuration()));
1648 return true;
1652 * CreateDateDurationRecord ( years, months, weeks, days )
1654 static DateDuration CreateDateDurationRecord(int64_t years, int64_t months,
1655 int64_t weeks, int64_t days) {
1656 MOZ_ASSERT(IsValidDuration(Duration{
1657 double(years),
1658 double(months),
1659 double(weeks),
1660 double(days),
1661 }));
1662 return {years, months, weeks, days};
1666 * CreateDateDurationRecord ( years, months, weeks, days )
1668 static bool CreateDateDurationRecord(JSContext* cx, int64_t years,
1669 int64_t months, int64_t weeks,
1670 int64_t days, DateDuration* result) {
1671 auto duration = DateDuration{years, months, weeks, days};
1672 if (!ThrowIfInvalidDuration(cx, duration)) {
1673 return false;
1676 *result = duration;
1677 return true;
1680 static bool UnbalanceDateDurationRelativeHasEffect(const DateDuration& duration,
1681 TemporalUnit largestUnit) {
1682 MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
1684 // Steps 2, 3.a-b, 4.a-b, 6-7.
1685 return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
1686 (largestUnit > TemporalUnit::Month && duration.months != 0) ||
1687 (largestUnit > TemporalUnit::Week && duration.weeks != 0);
1691 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1692 * plainRelativeTo, calendarRec )
1694 static bool UnbalanceDateDurationRelative(
1695 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1696 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1697 Handle<CalendarRecord> calendar, DateDuration* result) {
1698 MOZ_ASSERT(IsValidDuration(duration));
1700 auto [years, months, weeks, days] = duration;
1702 // Step 1. (Not applicable in our implementation.)
1704 // Steps 2, 3.a, 4.a, and 6.
1705 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1706 // Steps 2.a, 3.a, 4.a, and 6.
1707 *result = duration;
1708 return true;
1711 // Step 3.
1712 if (largestUnit == TemporalUnit::Month) {
1713 // Step 3.a. (Handled above)
1714 MOZ_ASSERT(years != 0);
1716 // Step 3.b. (Not applicable in our implementation.)
1718 // Step 3.c.
1719 MOZ_ASSERT(
1720 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1722 // Step 3.d.
1723 MOZ_ASSERT(
1724 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1726 // Step 3.e.
1727 auto yearsDuration = DateDuration{years};
1729 // Step 3.f.
1730 Rooted<Wrapped<PlainDateObject*>> later(
1731 cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
1732 if (!later) {
1733 return false;
1736 // Steps 3.g-i.
1737 Duration untilResult;
1738 if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
1739 TemporalUnit::Month, &untilResult)) {
1740 return false;
1743 // Step 3.j.
1744 int64_t yearsInMonths = int64_t(untilResult.months);
1746 // Step 3.k.
1747 return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
1748 result);
1751 // Step 4.
1752 if (largestUnit == TemporalUnit::Week) {
1753 // Step 4.a. (Handled above)
1754 MOZ_ASSERT(years != 0 || months != 0);
1756 // Step 4.b. (Not applicable in our implementation.)
1758 // Step 4.c.
1759 MOZ_ASSERT(
1760 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1762 // Step 4.d.
1763 auto yearsMonthsDuration = DateDuration{years, months};
1765 // Step 4.e.
1766 auto later =
1767 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
1768 if (!later) {
1769 return false;
1771 auto laterDate = ToPlainDate(&later.unwrap());
1773 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1774 if (!unwrappedRelativeTo) {
1775 return false;
1777 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1779 // Step 4.f.
1780 int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
1782 // Step 4.g.
1783 return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
1784 result);
1787 // Step 5. (Not applicable in our implementation.)
1789 // Step 6. (Handled above)
1790 MOZ_ASSERT(years != 0 || months != 0 || weeks != 0);
1792 // FIXME: why don't we unconditionally throw an error for missing calendars?
1794 // Step 7. (Not applicable in our implementation.)
1796 // Step 8.
1797 MOZ_ASSERT(
1798 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1800 // Step 9.
1801 auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};
1803 // Step 10.
1804 auto later =
1805 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
1806 if (!later) {
1807 return false;
1809 auto laterDate = ToPlainDate(&later.unwrap());
1811 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1812 if (!unwrappedRelativeTo) {
1813 return false;
1815 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1817 // Step 11.
1818 int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
1820 // Step 12.
1821 return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
1822 result);
1826 * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1827 * plainRelativeTo, calendarRec )
1829 static bool UnbalanceDateDurationRelative(JSContext* cx,
1830 const DateDuration& duration,
1831 TemporalUnit largestUnit,
1832 DateDuration* result) {
1833 MOZ_ASSERT(IsValidDuration(duration));
1835 // Step 1. (Not applicable.)
1837 // Steps 2, 3.a, 4.a, and 6.
1838 if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
1839 // Steps 2.a, 3.a, 4.a, and 6.
1840 *result = duration;
1841 return true;
1844 // Step 5. (Not applicable.)
1846 // Steps 3.b, 4.b, and 7.
1847 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1848 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
1849 return false;
1853 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
1854 * smallestUnit, plainRelativeTo, calendarRec )
1856 static bool BalanceDateDurationRelative(
1857 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
1858 TemporalUnit smallestUnit,
1859 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1860 Handle<CalendarRecord> calendar, DateDuration* result) {
1861 MOZ_ASSERT(IsValidDuration(duration));
1862 MOZ_ASSERT(largestUnit <= smallestUnit);
1864 auto [years, months, weeks, days] = duration;
1866 // FIXME: spec issue - effectful code paths should be more fine-grained
1867 // similar to UnbalanceDateDurationRelative. For example:
1868 // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
1869 // 2. Else if largestUnit = "month" and days = 0, then no-op.
1870 // 3. Else if days = 0, then no-op.
1872 // Also note that |weeks| is never balanced, even when non-zero.
1874 // Step 1. (Not applicable in our implementation.)
1876 // Steps 2-4.
1877 if (largestUnit > TemporalUnit::Week ||
1878 (years == 0 && months == 0 && weeks == 0 && days == 0)) {
1879 // Step 4.a.
1880 *result = duration;
1881 return true;
1884 // Step 5.
1885 if (!plainRelativeTo) {
1886 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1887 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
1888 "relativeTo");
1889 return false;
1892 // Step 6.
1893 MOZ_ASSERT(
1894 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1896 // Step 7.
1897 MOZ_ASSERT(
1898 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
1900 // Steps 8-9. (Not applicable in our implementation.)
1902 auto untilAddedDate = [&](const DateDuration& duration,
1903 Duration* untilResult) {
1904 Rooted<Wrapped<PlainDateObject*>> later(
1905 cx, AddDate(cx, calendar, plainRelativeTo, duration));
1906 if (!later) {
1907 return false;
1910 return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
1911 untilResult);
1914 // Step 10.
1915 if (largestUnit == TemporalUnit::Year) {
1916 // Step 10.a.
1917 if (smallestUnit == TemporalUnit::Week) {
1918 // Step 10.a.i.
1919 MOZ_ASSERT(days == 0);
1921 // Step 10.a.ii.
1922 auto yearsMonthsDuration = DateDuration{years, months};
1924 // Steps 10.a.iii-iv.
1925 Duration untilResult;
1926 if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
1927 return false;
1930 // Step 10.a.v.
1931 *result = CreateDateDurationRecord(int64_t(untilResult.years),
1932 int64_t(untilResult.months), weeks, 0);
1933 return true;
1936 // Step 10.b.
1937 const auto& yearsMonthsWeeksDaysDuration = duration;
1939 // Steps 10.c-d.
1940 Duration untilResult;
1941 if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
1942 return false;
1945 // FIXME: spec bug - CreateDateDurationRecord is infallible
1946 // https://github.com/tc39/proposal-temporal/issues/2750
1948 // Step 10.e.
1949 *result = CreateDateDurationRecord(
1950 int64_t(untilResult.years), int64_t(untilResult.months),
1951 int64_t(untilResult.weeks), int64_t(untilResult.days));
1952 return true;
1955 // Step 11.
1956 if (largestUnit == TemporalUnit::Month) {
1957 // Step 11.a.
1958 MOZ_ASSERT(years == 0);
1960 // Step 11.b.
1961 if (smallestUnit == TemporalUnit::Week) {
1962 // Step 10.b.i.
1963 MOZ_ASSERT(days == 0);
1965 // Step 10.b.ii.
1966 *result = CreateDateDurationRecord(0, months, weeks, 0);
1967 return true;
1970 // Step 11.c.
1971 const auto& monthsWeeksDaysDuration = duration;
1973 // Steps 11.d-e.
1974 Duration untilResult;
1975 if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
1976 return false;
1979 // FIXME: spec bug - CreateDateDurationRecord is infallible
1980 // https://github.com/tc39/proposal-temporal/issues/2750
1982 // Step 11.f.
1983 *result = CreateDateDurationRecord(0, int64_t(untilResult.months),
1984 int64_t(untilResult.weeks),
1985 int64_t(untilResult.days));
1986 return true;
1989 // Step 12.
1990 MOZ_ASSERT(largestUnit == TemporalUnit::Week);
1992 // Step 13.
1993 MOZ_ASSERT(years == 0);
1995 // Step 14.
1996 MOZ_ASSERT(months == 0);
1998 // Step 15.
1999 const auto& weeksDaysDuration = duration;
2001 // Steps 16-17.
2002 Duration untilResult;
2003 if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
2004 return false;
2007 // FIXME: spec bug - CreateDateDurationRecord is infallible
2008 // https://github.com/tc39/proposal-temporal/issues/2750
2010 // Step 18.
2011 *result = CreateDateDurationRecord(0, 0, int64_t(untilResult.weeks),
2012 int64_t(untilResult.days));
2013 return true;
2017 * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
2018 * smallestUnit, plainRelativeTo, calendarRec )
2020 bool js::temporal::BalanceDateDurationRelative(
2021 JSContext* cx, const DateDuration& duration, TemporalUnit largestUnit,
2022 TemporalUnit smallestUnit,
2023 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2024 Handle<CalendarRecord> calendar, DateDuration* result) {
2025 MOZ_ASSERT(plainRelativeTo);
2026 MOZ_ASSERT(calendar.receiver());
2028 return ::BalanceDateDurationRelative(cx, duration, largestUnit, smallestUnit,
2029 plainRelativeTo, calendar, result);
2033 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2034 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2035 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2037 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2038 Duration* result) {
2039 MOZ_ASSERT(IsValidDuration(one));
2040 MOZ_ASSERT(IsValidDuration(two));
2042 // Steps 1-2. (Not applicable)
2044 // Step 3.
2045 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2047 // Step 4.
2048 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2050 // Step 5.
2051 auto largestUnit = std::min(largestUnit1, largestUnit2);
2053 // Step 6.a.
2054 if (largestUnit <= TemporalUnit::Week) {
2055 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2056 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
2057 "relativeTo");
2058 return false;
2061 // Step 6.b.
2062 auto normalized1 = NormalizeTimeDuration(one);
2064 // Step 6.c.
2065 auto normalized2 = NormalizeTimeDuration(two);
2067 // Step 6.d.
2068 NormalizedTimeDuration normalized;
2069 if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
2070 return false;
2073 // Step 6.e.
2074 int64_t days1 = mozilla::AssertedCast<int64_t>(one.days);
2075 int64_t days2 = mozilla::AssertedCast<int64_t>(two.days);
2076 auto totalDays = mozilla::CheckedInt64(days1) + days2;
2077 MOZ_ASSERT(totalDays.isValid(), "adding two duration days can't overflow");
2079 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized, totalDays.value(),
2080 &normalized)) {
2081 return false;
2084 // Step 6.f.
2085 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2087 // Steps 6.g.
2088 *result = balanced.toDuration();
2089 return true;
2093 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2094 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2095 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2097 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2098 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2099 Handle<CalendarRecord> calendar, Duration* result) {
2100 MOZ_ASSERT(IsValidDuration(one));
2101 MOZ_ASSERT(IsValidDuration(two));
2103 // Steps 1-2. (Not applicable)
2105 // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
2106 // not undefined.
2108 // Step 3.
2109 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2111 // Step 4.
2112 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2114 // Step 5.
2115 auto largestUnit = std::min(largestUnit1, largestUnit2);
2117 // Step 6. (Not applicable)
2119 // Step 7.a. (Not applicable in our implementation.)
2121 // Step 7.b.
2122 auto dateDuration1 = one.toDateDuration();
2124 // Step 7.c.
2125 auto dateDuration2 = two.toDateDuration();
2127 // FIXME: spec issue - calendarUnitsPresent is unused.
2129 // Step 7.d.
2130 [[maybe_unused]] bool calendarUnitsPresent = true;
2132 // Step 7.e.
2133 if (dateDuration1.years == 0 && dateDuration1.months == 0 &&
2134 dateDuration1.weeks == 0 && dateDuration2.years == 0 &&
2135 dateDuration2.months == 0 && dateDuration2.weeks == 0) {
2136 calendarUnitsPresent = false;
2139 // Step 7.f.
2140 Rooted<Wrapped<PlainDateObject*>> intermediate(
2141 cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
2142 if (!intermediate) {
2143 return false;
2146 // Step 7.g.
2147 Rooted<Wrapped<PlainDateObject*>> end(
2148 cx, AddDate(cx, calendar, intermediate, dateDuration2));
2149 if (!end) {
2150 return false;
2153 // Step 7.h.
2154 auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
2156 // Steps 7.i-k.
2157 DateDuration dateDifference;
2158 if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
2159 &dateDifference)) {
2160 return false;
2163 // Step 7.l.
2164 auto normalized1 = NormalizeTimeDuration(one);
2166 // Step 7.m.
2167 auto normalized2 = NormalizeTimeDuration(two);
2169 // Step 7.n.
2170 NormalizedTimeDuration normalized1WithDays;
2171 if (!Add24HourDaysToNormalizedTimeDuration(
2172 cx, normalized1, dateDifference.days, &normalized1WithDays)) {
2173 return false;
2176 // Step 7.o.
2177 NormalizedTimeDuration normalized;
2178 if (!AddNormalizedTimeDuration(cx, normalized1WithDays, normalized2,
2179 &normalized)) {
2180 return false;
2183 // Step 7.p.
2184 auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
2186 // Steps 7.q.
2187 *result = {
2188 double(dateDifference.years), double(dateDifference.months),
2189 double(dateDifference.weeks), double(balanced.days),
2190 double(balanced.hours), double(balanced.minutes),
2191 double(balanced.seconds), double(balanced.milliseconds),
2192 balanced.microseconds, balanced.nanoseconds,
2194 MOZ_ASSERT(IsValidDuration(*result));
2195 return true;
2199 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2200 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2201 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2203 static bool AddDuration(
2204 JSContext* cx, const Duration& one, const Duration& two,
2205 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2206 Handle<TimeZoneRecord> timeZone,
2207 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2208 Duration* result) {
2209 // Steps 1-2. (Not applicable)
2211 // Step 3.
2212 auto largestUnit1 = DefaultTemporalLargestUnit(one);
2214 // Step 4.
2215 auto largestUnit2 = DefaultTemporalLargestUnit(two);
2217 // Step 5.
2218 auto largestUnit = std::min(largestUnit1, largestUnit2);
2220 // Steps 6-7. (Not applicable)
2222 // Steps 8-9. (Not applicable in our implementation.)
2224 // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
2226 // clang-format off
2228 // 10. If largestUnit is one of "year", "month", "week", or "day", then
2229 // a. If precalculatedPlainDateTime is undefined, then
2230 // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
2231 // b. Else,
2232 // i. Let startDateTime be precalculatedPlainDateTime.
2233 // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
2234 // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
2235 // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
2236 // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
2237 // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
2238 // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
2239 // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
2241 // clang-format on
2243 // Step 10.
2244 bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
2246 // Steps 11-17.
2247 if (!startDateTimeNeeded) {
2248 // Steps 11-12. (Not applicable)
2250 // Step 13.
2251 auto normalized1 = NormalizeTimeDuration(one);
2253 // Step 14.
2254 auto normalized2 = NormalizeTimeDuration(two);
2256 // Step 15. (Inlined AddZonedDateTime, step 6.)
2257 Instant intermediateNs;
2258 if (!AddInstant(cx, zonedRelativeTo.instant(), normalized1,
2259 &intermediateNs)) {
2260 return false;
2262 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2264 // Step 16. (Inlined AddZonedDateTime, step 6.)
2265 Instant endNs;
2266 if (!AddInstant(cx, intermediateNs, normalized2, &endNs)) {
2267 return false;
2269 MOZ_ASSERT(IsValidEpochInstant(endNs));
2271 // Step 17.a.
2272 auto normalized = NormalizedTimeDurationFromEpochNanosecondsDifference(
2273 endNs, zonedRelativeTo.instant());
2275 // Step 17.b.
2276 auto balanced = BalanceTimeDuration(normalized, largestUnit);
2278 // Step 17.c.
2279 *result = balanced.toDuration();
2280 return true;
2283 // Steps 11-12.
2284 PlainDateTime startDateTime;
2285 if (!precalculatedPlainDateTime) {
2286 if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
2287 &startDateTime)) {
2288 return false;
2290 } else {
2291 startDateTime = *precalculatedPlainDateTime;
2294 // Step 13.
2295 auto normalized1 = CreateNormalizedDurationRecord(one);
2297 // Step 14.
2298 auto normalized2 = CreateNormalizedDurationRecord(two);
2300 // Step 15.
2301 Instant intermediateNs;
2302 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2303 normalized1, startDateTime, &intermediateNs)) {
2304 return false;
2306 MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
2308 // Step 16.
2309 Instant endNs;
2310 if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, normalized2,
2311 &endNs)) {
2312 return false;
2314 MOZ_ASSERT(IsValidEpochInstant(endNs));
2316 // Step 17. (Not applicable)
2318 // Step 18.
2319 NormalizedDuration difference;
2320 if (!DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs, timeZone,
2321 calendar, largestUnit, startDateTime,
2322 &difference)) {
2323 return false;
2326 // Step 19.
2327 auto balanced = BalanceTimeDuration(difference.time, TemporalUnit::Hour);
2329 // Step 20.
2330 *result = {
2331 double(difference.date.years), double(difference.date.months),
2332 double(difference.date.weeks), double(difference.date.days),
2333 double(balanced.hours), double(balanced.minutes),
2334 double(balanced.seconds), double(balanced.milliseconds),
2335 balanced.microseconds, balanced.nanoseconds,
2337 MOZ_ASSERT(IsValidDuration(*result));
2338 return true;
2342 * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
2343 * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
2344 * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
2346 static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
2347 Handle<ZonedDateTime> zonedRelativeTo,
2348 Handle<CalendarRecord> calendar,
2349 Handle<TimeZoneRecord> timeZone, Duration* result) {
2350 return AddDuration(cx, one, two, zonedRelativeTo, calendar, timeZone,
2351 mozilla::Nothing(), result);
2355 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2356 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2357 * precalculatedPlainDateTime )
2359 static bool AdjustRoundedDurationDays(
2360 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2361 TemporalUnit unit, TemporalRoundingMode roundingMode,
2362 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2363 Handle<TimeZoneRecord> timeZone,
2364 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
2365 NormalizedDuration* result) {
2366 MOZ_ASSERT(IsValidDuration(duration));
2368 // Step 1.
2369 if ((TemporalUnit::Year <= unit && unit <= TemporalUnit::Day) ||
2370 (unit == TemporalUnit::Nanosecond && increment == Increment{1})) {
2371 *result = duration;
2372 return true;
2375 // The increment is limited for all smaller temporal units.
2376 MOZ_ASSERT(increment < MaximumTemporalDurationRoundingIncrement(unit));
2378 // Step 2.
2379 MOZ_ASSERT(precalculatedPlainDateTime);
2381 // Step 3.
2382 int32_t direction = NormalizedTimeDurationSign(duration.time);
2384 // Steps 4-5.
2385 Instant dayStart;
2386 if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
2387 duration.date, *precalculatedPlainDateTime,
2388 &dayStart)) {
2389 return false;
2391 MOZ_ASSERT(IsValidEpochInstant(dayStart));
2393 // Step 6.
2394 PlainDateTime dayStartDateTime;
2395 if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
2396 return false;
2399 // Step 7.
2400 Instant dayEnd;
2401 if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
2402 zonedRelativeTo.calendar(), direction, &dayEnd)) {
2403 return false;
2405 MOZ_ASSERT(IsValidEpochInstant(dayEnd));
2407 // Step 8.
2408 auto dayLengthNs =
2409 NormalizedTimeDurationFromEpochNanosecondsDifference(dayEnd, dayStart);
2410 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs.to<InstantSpan>()));
2412 // Step 9.
2413 NormalizedTimeDuration oneDayLess;
2414 if (!SubtractNormalizedTimeDuration(cx, duration.time, dayLengthNs,
2415 &oneDayLess)) {
2416 return false;
2419 // Step 10.
2420 int32_t oneDayLessSign = NormalizedTimeDurationSign(oneDayLess);
2421 if ((direction > 0 && oneDayLessSign < 0) ||
2422 (direction < 0 && oneDayLessSign > 0)) {
2423 *result = duration;
2424 return true;
2427 // Step 11.
2428 Duration adjustedDateDuration;
2429 if (!AddDuration(cx, duration.date.toDuration(), {0, 0, 0, double(direction)},
2430 zonedRelativeTo, calendar, timeZone,
2431 precalculatedPlainDateTime, &adjustedDateDuration)) {
2432 return false;
2435 // Step 12.
2436 auto roundedTime = RoundDuration(oneDayLess, increment, unit, roundingMode);
2438 // Step 13.
2439 return CombineDateAndNormalizedTimeDuration(
2440 cx, adjustedDateDuration.toDateDuration(), roundedTime, result);
2444 * AdjustRoundedDurationDays ( years, months, weeks, days, norm, increment,
2445 * unit, roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
2446 * precalculatedPlainDateTime )
2448 bool js::temporal::AdjustRoundedDurationDays(
2449 JSContext* cx, const NormalizedDuration& duration, Increment increment,
2450 TemporalUnit unit, TemporalRoundingMode roundingMode,
2451 Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
2452 Handle<TimeZoneRecord> timeZone,
2453 const PlainDateTime& precalculatedPlainDateTime,
2454 NormalizedDuration* result) {
2455 return ::AdjustRoundedDurationDays(
2456 cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
2457 timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
2460 static bool NumberToStringBuilder(JSContext* cx, double num,
2461 JSStringBuilder& sb) {
2462 MOZ_ASSERT(IsInteger(num));
2463 MOZ_ASSERT(num >= 0);
2464 MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2466 ToCStringBuf cbuf;
2467 size_t length;
2468 const char* numStr = NumberToCString(&cbuf, num, &length);
2470 return sb.append(numStr, length);
2473 static Duration AbsoluteDuration(const Duration& duration) {
2474 return {
2475 std::abs(duration.years), std::abs(duration.months),
2476 std::abs(duration.weeks), std::abs(duration.days),
2477 std::abs(duration.hours), std::abs(duration.minutes),
2478 std::abs(duration.seconds), std::abs(duration.milliseconds),
2479 std::abs(duration.microseconds), std::abs(duration.nanoseconds),
2484 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
2486 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
2487 int32_t subSecondNanoseconds,
2488 Precision precision) {
2489 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
2490 MOZ_ASSERT(precision != Precision::Minute());
2492 // Steps 1-2.
2493 if (precision == Precision::Auto()) {
2494 // Step 1.a.
2495 if (subSecondNanoseconds == 0) {
2496 return true;
2499 // Step 3. (Reordered)
2500 if (!result.append('.')) {
2501 return false;
2504 // Steps 1.b-c.
2505 int32_t k = 100'000'000;
2506 do {
2507 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2508 return false;
2510 subSecondNanoseconds %= k;
2511 k /= 10;
2512 } while (subSecondNanoseconds);
2513 } else {
2514 // Step 2.a.
2515 uint8_t p = precision.value();
2516 if (p == 0) {
2517 return true;
2520 // Step 3. (Reordered)
2521 if (!result.append('.')) {
2522 return false;
2525 // Steps 2.b-c.
2526 int32_t k = 100'000'000;
2527 for (uint8_t i = 0; i < precision.value(); i++) {
2528 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
2529 return false;
2531 subSecondNanoseconds %= k;
2532 k /= 10;
2536 return true;
2540 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
2541 * normSeconds, precision )
2543 static JSString* TemporalDurationToString(JSContext* cx,
2544 const Duration& duration,
2545 Precision precision) {
2546 MOZ_ASSERT(IsValidDuration(duration));
2547 MOZ_ASSERT(precision != Precision::Minute());
2549 // Fast path for zero durations.
2550 if (duration == Duration{} &&
2551 (precision == Precision::Auto() || precision.value() == 0)) {
2552 return NewStringCopyZ<CanGC>(cx, "PT0S");
2555 // Convert to absolute values up front. This is okay to do, because when the
2556 // duration is valid, all components have the same sign.
2557 const auto& [years, months, weeks, days, hours, minutes, seconds,
2558 milliseconds, microseconds, nanoseconds] =
2559 AbsoluteDuration(duration);
2561 // Years to seconds parts are all safe integers for valid durations.
2562 MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2563 MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2564 MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2565 MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2566 MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2567 MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2568 MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
2570 auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
2571 microseconds, nanoseconds);
2573 // Step 1.
2574 int32_t sign = DurationSign(duration);
2576 // Steps 2 and 7.
2577 JSStringBuilder result(cx);
2579 // Step 13. (Reordered)
2580 if (sign < 0) {
2581 if (!result.append('-')) {
2582 return nullptr;
2586 // Step 14. (Reordered)
2587 if (!result.append('P')) {
2588 return nullptr;
2591 // Step 3.
2592 if (years != 0) {
2593 if (!NumberToStringBuilder(cx, years, result)) {
2594 return nullptr;
2596 if (!result.append('Y')) {
2597 return nullptr;
2601 // Step 4.
2602 if (months != 0) {
2603 if (!NumberToStringBuilder(cx, months, result)) {
2604 return nullptr;
2606 if (!result.append('M')) {
2607 return nullptr;
2611 // Step 5.
2612 if (weeks != 0) {
2613 if (!NumberToStringBuilder(cx, weeks, result)) {
2614 return nullptr;
2616 if (!result.append('W')) {
2617 return nullptr;
2621 // Step 6.
2622 if (days != 0) {
2623 if (!NumberToStringBuilder(cx, days, result)) {
2624 return nullptr;
2626 if (!result.append('D')) {
2627 return nullptr;
2631 // Step 7. (Moved above)
2633 // Steps 10-11. (Reordered)
2634 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
2635 days == 0 && hours == 0 && minutes == 0;
2637 // Steps 8-9, 12, and 15.
2638 bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
2639 zeroMinutesAndHigher || precision != Precision::Auto();
2640 if (hours != 0 || minutes != 0 || hasSecondsPart) {
2641 // Step 15. (Reordered)
2642 if (!result.append('T')) {
2643 return nullptr;
2646 // Step 8.
2647 if (hours != 0) {
2648 if (!NumberToStringBuilder(cx, hours, result)) {
2649 return nullptr;
2651 if (!result.append('H')) {
2652 return nullptr;
2656 // Step 9.
2657 if (minutes != 0) {
2658 if (!NumberToStringBuilder(cx, minutes, result)) {
2659 return nullptr;
2661 if (!result.append('M')) {
2662 return nullptr;
2666 // Step 12.
2667 if (hasSecondsPart) {
2668 // Step 12.a.
2669 if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
2670 return nullptr;
2673 // Step 12.b.
2674 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
2675 precision)) {
2676 return nullptr;
2679 // Step 12.c.
2680 if (!result.append('S')) {
2681 return nullptr;
2686 // Steps 13-15. (Moved above)
2688 // Step 16.
2689 return result.finishString();
2693 * ToRelativeTemporalObject ( options )
2695 static bool ToRelativeTemporalObject(
2696 JSContext* cx, Handle<JSObject*> options,
2697 MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
2698 MutableHandle<ZonedDateTime> zonedRelativeTo,
2699 MutableHandle<TimeZoneRecord> timeZoneRecord) {
2700 // Step 1.
2701 Rooted<Value> value(cx);
2702 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
2703 return false;
2706 // Step 2.
2707 if (value.isUndefined()) {
2708 // FIXME: spec issue - switch return record fields for consistency.
2709 // FIXME: spec bug - [[TimeZoneRec]] field not created
2711 plainRelativeTo.set(nullptr);
2712 zonedRelativeTo.set(ZonedDateTime{});
2713 timeZoneRecord.set(TimeZoneRecord{});
2714 return true;
2717 // Step 3.
2718 auto offsetBehaviour = OffsetBehaviour::Option;
2720 // Step 4.
2721 auto matchBehaviour = MatchBehaviour::MatchExactly;
2723 // Steps 5-6.
2724 PlainDateTime dateTime;
2725 Rooted<CalendarValue> calendar(cx);
2726 Rooted<TimeZoneValue> timeZone(cx);
2727 int64_t offsetNs;
2728 if (value.isObject()) {
2729 Rooted<JSObject*> obj(cx, &value.toObject());
2731 // Step 5.a.
2732 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
2733 auto instant = ToInstant(zonedDateTime);
2734 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
2735 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
2737 if (!timeZone.wrap(cx)) {
2738 return false;
2740 if (!calendar.wrap(cx)) {
2741 return false;
2744 // Step 5.a.i.
2745 Rooted<TimeZoneRecord> timeZoneRec(cx);
2746 if (!CreateTimeZoneMethodsRecord(
2747 cx, timeZone,
2749 TimeZoneMethod::GetOffsetNanosecondsFor,
2750 TimeZoneMethod::GetPossibleInstantsFor,
2752 &timeZoneRec)) {
2753 return false;
2756 // Step 5.a.ii.
2757 plainRelativeTo.set(nullptr);
2758 zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
2759 timeZoneRecord.set(timeZoneRec);
2760 return true;
2763 // Step 5.b.
2764 if (obj->canUnwrapAs<PlainDateObject>()) {
2765 plainRelativeTo.set(obj);
2766 zonedRelativeTo.set(ZonedDateTime{});
2767 timeZoneRecord.set(TimeZoneRecord{});
2768 return true;
2771 // Step 5.c.
2772 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
2773 auto plainDateTime = ToPlainDate(dateTime);
2775 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
2776 if (!calendar.wrap(cx)) {
2777 return false;
2780 // Step 5.c.i.
2781 auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
2782 if (!plainDate) {
2783 return false;
2786 // Step 5.c.ii.
2787 plainRelativeTo.set(plainDate);
2788 zonedRelativeTo.set(ZonedDateTime{});
2789 timeZoneRecord.set(TimeZoneRecord{});
2790 return true;
2793 // Step 5.d.
2794 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
2795 return false;
2798 // Step 5.e.
2799 Rooted<CalendarRecord> calendarRec(cx);
2800 if (!CreateCalendarMethodsRecord(cx, calendar,
2802 CalendarMethod::DateFromFields,
2803 CalendarMethod::Fields,
2805 &calendarRec)) {
2806 return false;
2809 // Step 5.f.
2810 JS::RootedVector<PropertyKey> fieldNames(cx);
2811 if (!CalendarFields(cx, calendarRec,
2812 {CalendarField::Day, CalendarField::Month,
2813 CalendarField::MonthCode, CalendarField::Year},
2814 &fieldNames)) {
2815 return false;
2818 // Step 5.g.
2819 if (!AppendSorted(cx, fieldNames.get(),
2821 TemporalField::Hour,
2822 TemporalField::Microsecond,
2823 TemporalField::Millisecond,
2824 TemporalField::Minute,
2825 TemporalField::Nanosecond,
2826 TemporalField::Offset,
2827 TemporalField::Second,
2828 TemporalField::TimeZone,
2829 })) {
2830 return false;
2833 // Step 5.h.
2834 Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, obj, fieldNames));
2835 if (!fields) {
2836 return false;
2839 // Step 5.i.
2840 Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
2841 if (!dateOptions) {
2842 return false;
2845 // Step 5.j.
2846 Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
2847 if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
2848 return false;
2851 // Step 5.k.
2852 if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
2853 &dateTime)) {
2854 return false;
2857 // Step 5.l.
2858 Rooted<Value> offset(cx);
2859 if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
2860 return false;
2863 // Step 5.m.
2864 Rooted<Value> timeZoneValue(cx);
2865 if (!GetProperty(cx, fields, fields, cx->names().timeZone,
2866 &timeZoneValue)) {
2867 return false;
2870 // Step 5.n.
2871 if (!timeZoneValue.isUndefined()) {
2872 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
2873 return false;
2877 // Step 5.o.
2878 if (offset.isUndefined()) {
2879 offsetBehaviour = OffsetBehaviour::Wall;
2882 // Steps 8-9.
2883 if (timeZone) {
2884 if (offsetBehaviour == OffsetBehaviour::Option) {
2885 MOZ_ASSERT(!offset.isUndefined());
2886 MOZ_ASSERT(offset.isString());
2888 // Step 8.a.
2889 Rooted<JSString*> offsetString(cx, offset.toString());
2890 if (!offsetString) {
2891 return false;
2894 // Step 8.b.
2895 if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
2896 return false;
2898 } else {
2899 // Step 9.
2900 offsetNs = 0;
2903 } else {
2904 // Step 6.a.
2905 if (!value.isString()) {
2906 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
2907 nullptr, "not a string");
2908 return false;
2910 Rooted<JSString*> string(cx, value.toString());
2912 // Step 6.b.
2913 bool isUTC;
2914 bool hasOffset;
2915 int64_t timeZoneOffset;
2916 Rooted<ParsedTimeZone> timeZoneName(cx);
2917 Rooted<JSString*> calendarString(cx);
2918 if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
2919 &hasOffset, &timeZoneOffset,
2920 &timeZoneName, &calendarString)) {
2921 return false;
2924 // Step 6.c. (Not applicable in our implementation.)
2926 // Steps 6.e-f.
2927 if (timeZoneName) {
2928 // Step 6.f.i.
2929 if (!ToTemporalTimeZone(cx, timeZoneName, &timeZone)) {
2930 return false;
2933 // Steps 6.f.ii-iii.
2934 if (isUTC) {
2935 offsetBehaviour = OffsetBehaviour::Exact;
2936 } else if (!hasOffset) {
2937 offsetBehaviour = OffsetBehaviour::Wall;
2940 // Step 6.f.iv.
2941 matchBehaviour = MatchBehaviour::MatchMinutes;
2942 } else {
2943 MOZ_ASSERT(!timeZone);
2946 // Steps 6.g-j.
2947 if (calendarString) {
2948 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
2949 return false;
2951 } else {
2952 calendar.set(CalendarValue(cx->names().iso8601));
2955 // Steps 8-9.
2956 if (timeZone) {
2957 if (offsetBehaviour == OffsetBehaviour::Option) {
2958 MOZ_ASSERT(hasOffset);
2960 // Step 8.a.
2961 offsetNs = timeZoneOffset;
2962 } else {
2963 // Step 9.
2964 offsetNs = 0;
2969 // Step 7.
2970 if (!timeZone) {
2971 // Step 7.a.
2972 auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
2973 if (!plainDate) {
2974 return false;
2977 plainRelativeTo.set(plainDate);
2978 zonedRelativeTo.set(ZonedDateTime{});
2979 timeZoneRecord.set(TimeZoneRecord{});
2980 return true;
2983 // Steps 8-9. (Moved above)
2985 // Step 10.
2986 Rooted<TimeZoneRecord> timeZoneRec(cx);
2987 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2989 TimeZoneMethod::GetOffsetNanosecondsFor,
2990 TimeZoneMethod::GetPossibleInstantsFor,
2992 &timeZoneRec)) {
2993 return false;
2996 // Step 11.
2997 Instant epochNanoseconds;
2998 if (!InterpretISODateTimeOffset(
2999 cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
3000 TemporalDisambiguation::Compatible, TemporalOffset::Reject,
3001 matchBehaviour, &epochNanoseconds)) {
3002 return false;
3004 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
3006 // Step 12.
3007 plainRelativeTo.set(nullptr);
3008 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
3009 timeZoneRecord.set(timeZoneRec);
3010 return true;
3014 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
3015 * methods )
3017 static bool CreateCalendarMethodsRecordFromRelativeTo(
3018 JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3019 Handle<ZonedDateTime> zonedRelativeTo,
3020 mozilla::EnumSet<CalendarMethod> methods,
3021 MutableHandle<CalendarRecord> result) {
3022 // Step 1.
3023 if (zonedRelativeTo) {
3024 return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
3025 result);
3028 // Step 2.
3029 if (plainRelativeTo) {
3030 auto* unwrapped = plainRelativeTo.unwrap(cx);
3031 if (!unwrapped) {
3032 return false;
3035 Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
3036 if (!calendar.wrap(cx)) {
3037 return false;
3040 return CreateCalendarMethodsRecord(cx, calendar, methods, result);
3043 // Step 3.
3044 return true;
3047 struct RoundedDuration final {
3048 NormalizedDuration duration;
3049 double total = 0;
3052 enum class ComputeRemainder : bool { No, Yes };
3055 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
3057 static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
3058 const NormalizedTimeDuration& duration, const TemporalUnit unit,
3059 Increment increment, TemporalRoundingMode roundingMode) {
3060 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3061 MOZ_ASSERT(unit > TemporalUnit::Day);
3062 MOZ_ASSERT(increment <= MaximumTemporalDurationRoundingIncrement(unit));
3064 int64_t divisor = ToNanoseconds(unit) * increment.value();
3065 MOZ_ASSERT(divisor > 0);
3066 MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
3068 auto totalNanoseconds = duration.toNanoseconds();
3069 auto rounded =
3070 RoundNumberToIncrement(totalNanoseconds, Int128{divisor}, roundingMode);
3071 return NormalizedTimeDuration::fromNanoseconds(rounded);
3075 * DivideNormalizedTimeDuration ( d, divisor )
3077 static double TotalNormalizedTimeDuration(
3078 const NormalizedTimeDuration& duration, const TemporalUnit unit) {
3079 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3080 MOZ_ASSERT(unit > TemporalUnit::Day);
3082 auto numerator = duration.toNanoseconds();
3083 auto denominator = Int128{ToNanoseconds(unit)};
3084 return FractionToDouble(numerator, denominator);
3088 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3089 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3090 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3092 NormalizedTimeDuration js::temporal::RoundDuration(
3093 const NormalizedTimeDuration& duration, Increment increment,
3094 TemporalUnit unit, TemporalRoundingMode roundingMode) {
3095 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
3096 MOZ_ASSERT(unit > TemporalUnit::Day);
3098 // Steps 1-13. (Not applicable)
3100 // Steps 14-19.
3101 auto rounded = RoundNormalizedTimeDurationToIncrement(
3102 duration, unit, increment, roundingMode);
3103 MOZ_ASSERT(IsValidNormalizedTimeDuration(rounded));
3105 // Step 20.
3106 return rounded;
3109 struct FractionalDays final {
3110 int64_t days = 0;
3111 int64_t time = 0;
3112 int64_t dayLength = 0;
3114 FractionalDays() = default;
3116 explicit FractionalDays(int64_t durationDays,
3117 const NormalizedTimeAndDays& timeAndDays)
3118 : days(durationDays + timeAndDays.days),
3119 time(timeAndDays.time),
3120 dayLength(timeAndDays.dayLength) {
3121 MOZ_ASSERT(durationDays <= (int64_t(1) << 53) / (24 * 60 * 60));
3122 MOZ_ASSERT(timeAndDays.days <= (int64_t(1) << 53) / (24 * 60 * 60));
3124 // NormalizedTimeDurationToDays guarantees that |dayLength| is strictly
3125 // positive and less than 2**53.
3126 MOZ_ASSERT(dayLength > 0);
3127 MOZ_ASSERT(dayLength < int64_t(1) << 53);
3129 // NormalizedTimeDurationToDays guarantees that |abs(timeAndDays.time)| is
3130 // less than |timeAndDays.dayLength|.
3131 MOZ_ASSERT(std::abs(time) < dayLength);
3134 FractionalDays operator+=(int32_t epochDays) {
3135 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3136 days += epochDays;
3137 return *this;
3140 FractionalDays operator-=(int32_t epochDays) {
3141 MOZ_ASSERT(std::abs(epochDays) <= 200'000'000);
3142 days -= epochDays;
3143 return *this;
3146 int64_t truncate() const {
3147 int64_t truncatedDays = days;
3148 if (time > 0) {
3149 // Round toward positive infinity when the integer days are negative and
3150 // the fractional part is positive.
3151 if (truncatedDays < 0) {
3152 truncatedDays += 1;
3154 } else if (time < 0) {
3155 // Round toward negative infinity when the integer days are positive and
3156 // the fractional part is negative.
3157 if (truncatedDays > 0) {
3158 truncatedDays -= 1;
3161 return truncatedDays;
3164 int32_t sign() const {
3165 if (days != 0) {
3166 return days < 0 ? -1 : 1;
3168 return time < 0 ? -1 : time > 0 ? 1 : 0;
3172 struct Fraction final {
3173 int64_t numerator = 0;
3174 int32_t denominator = 0;
3176 constexpr Fraction() = default;
3178 constexpr Fraction(int64_t numerator, int32_t denominator)
3179 : numerator(numerator), denominator(denominator) {
3180 MOZ_ASSERT(denominator > 0);
3184 struct RoundedNumber final {
3185 Int128 rounded;
3186 double total = 0;
3189 static RoundedNumber RoundNumberToIncrement(
3190 const Fraction& fraction, const FractionalDays& fractionalDays,
3191 Increment increment, TemporalRoundingMode roundingMode,
3192 ComputeRemainder computeRemainder) {
3193 #ifdef DEBUG
3194 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
3195 static constexpr int64_t maxDurationDays =
3196 (int64_t(1) << 53) / (24 * 60 * 60);
3198 // Numbers of days between nsMinInstant and nsMaxInstant.
3199 static constexpr int32_t epochDays = 200'000'000;
3201 // Maximum number of days in |fractionalDays|.
3202 static constexpr int64_t maxFractionalDays =
3203 2 * maxDurationDays + 2 * epochDays;
3204 #endif
3206 MOZ_ASSERT(std::abs(fraction.numerator) < (int64_t(1) << 32) * 2);
3207 MOZ_ASSERT(fraction.denominator > 0);
3208 MOZ_ASSERT(fraction.denominator <= epochDays);
3209 MOZ_ASSERT(std::abs(fractionalDays.days) <= maxFractionalDays);
3210 MOZ_ASSERT(fractionalDays.dayLength > 0);
3211 MOZ_ASSERT(fractionalDays.dayLength < (int64_t(1) << 53));
3212 MOZ_ASSERT(std::abs(fractionalDays.time) < fractionalDays.dayLength);
3213 MOZ_ASSERT(increment <= Increment::max());
3215 // clang-format off
3217 // Change the representation of |fractionalWeeks| from a real number to a
3218 // rational number, because we don't support arbitrary precision real
3219 // numbers.
3221 // |fractionalWeeks| is defined as:
3223 // fractionalWeeks
3224 // = weeks + days' / abs(oneWeekDays)
3226 // where days' = days + nanoseconds / dayLength.
3228 // The fractional part |nanoseconds / dayLength| is from step 7.
3230 // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
3232 // fractionalWeeks
3233 // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
3234 // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
3235 // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
3237 // Because |abs(nanoseconds / dayLength) < 0|, this operation can be rewritten
3238 // to omit the multiplication by |dayLength| when the rounding conditions are
3239 // appropriately modified to account for the |nanoseconds / dayLength| part.
3240 // This allows to implement rounding using only int64 values.
3242 // This optimization is currently only implemented when |nanoseconds| is zero.
3244 // Example how to expand this optimization for non-zero |nanoseconds|:
3246 // |Round(fraction / increment) * increment| with:
3247 // fraction = numerator / denominator
3248 // numerator = weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds
3249 // denominator = dayLength * abs(oneWeekDays)
3251 // When ignoring the |nanoseconds / dayLength| part, this can be simplified to:
3253 // |Round(fraction / increment) * increment| with:
3254 // fraction = numerator / denominator
3255 // numerator = weeks * abs(oneWeekDays) + days
3256 // denominator = abs(oneWeekDays)
3258 // Where:
3259 // fraction / increment
3260 // = (numerator / denominator) / increment
3261 // = numerator / (denominator * increment)
3263 // And |numerator| and |denominator * increment| both fit into int64.
3265 // The "ceiling" operation has to be modified from:
3267 // CeilDiv(dividend, divisor)
3268 // quot, rem = dividend / divisor
3269 // return quot + (rem > 0)
3271 // To:
3273 // CeilDiv(dividend, divisor, fractional)
3274 // quot, rem = dividend / divisor
3275 // return quot + ((rem > 0) || (fractional > 0))
3277 // To properly account for the fractional |nanoseconds| part. Alternatively
3278 // |dividend| can be modified before calling `CeilDiv`.
3280 // clang-format on
3282 if (fractionalDays.time == 0) {
3283 auto [numerator, denominator] = fraction;
3284 int64_t totalDays = fractionalDays.days + denominator * numerator;
3286 if (computeRemainder == ComputeRemainder::Yes) {
3287 constexpr auto rounded = Int128{0};
3288 double total = FractionToDouble(totalDays, denominator);
3289 return {rounded, total};
3292 auto rounded =
3293 RoundNumberToIncrement(totalDays, denominator, increment, roundingMode);
3294 constexpr double total = 0;
3295 return {rounded, total};
3298 do {
3299 auto dayLength = mozilla::CheckedInt64(fractionalDays.dayLength);
3301 auto denominator = dayLength * fraction.denominator;
3302 if (!denominator.isValid()) {
3303 break;
3306 auto amountNanos = denominator * fraction.numerator;
3307 if (!amountNanos.isValid()) {
3308 break;
3311 auto totalNanoseconds = dayLength * fractionalDays.days;
3312 totalNanoseconds += fractionalDays.time;
3313 totalNanoseconds += amountNanos;
3314 if (!totalNanoseconds.isValid()) {
3315 break;
3318 if (computeRemainder == ComputeRemainder::Yes) {
3319 constexpr auto rounded = Int128{0};
3320 double total =
3321 FractionToDouble(totalNanoseconds.value(), denominator.value());
3322 return {rounded, total};
3325 auto rounded = RoundNumberToIncrement(
3326 totalNanoseconds.value(), denominator.value(), increment, roundingMode);
3327 constexpr double total = 0;
3328 return {rounded, total};
3329 } while (false);
3331 // Use int128 when values are too large for int64. Additionally assert all
3332 // values fit into int128.
3334 // `dayLength` < 2**53
3335 auto dayLength = Int128{fractionalDays.dayLength};
3336 MOZ_ASSERT(dayLength < Int128{1} << 53);
3338 // `fraction.denominator` < 200'000'000, log2(200'000'000) = ~27.57.
3339 auto denominator = dayLength * Int128{fraction.denominator};
3340 MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
3342 // log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
3344 // `abs(maxFractionalDays)`
3345 // = `abs(2 * maxDurationDays + 2 * epochDays)`
3346 // = `abs(2 * 2**(53 - 16) + 2 * 200'000'000)`
3347 // ≤ 2 * 2**37 + 2**29
3348 // ≤ 2**39
3349 auto totalDays = Int128{fractionalDays.days};
3350 MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
3352 // `abs(fraction.numerator)` ≤ (2**33)
3353 auto totalAmount = Int128{fraction.numerator};
3354 MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
3356 // `denominator` < 2**(53 + 28)
3357 // `abs(totalAmount)` <= 2**33
3359 // `denominator * totalAmount`
3360 // ≤ 2**(53 + 28) * 2**33
3361 // = 2**(53 + 28 + 33)
3362 // = 2**114
3363 auto amountNanos = denominator * totalAmount;
3364 MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
3366 // `dayLength` < 2**53
3367 // `totalDays` ≤ 2**39
3368 // `fractionalDays.time` < `dayLength` < 2**53
3369 // `amountNanos` ≤ 2**114
3371 // `dayLength * totalDays`
3372 // ≤ 2**(53 + 39) = 2**92
3374 // `dayLength * totalDays + fractionalDays.time`
3375 // ≤ 2**93
3377 // `dayLength * totalDays + fractionalDays.time + amountNanos`
3378 // ≤ 2**115
3379 auto totalNanoseconds = dayLength * totalDays;
3380 totalNanoseconds += Int128{fractionalDays.time};
3381 totalNanoseconds += amountNanos;
3382 MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
3384 if (computeRemainder == ComputeRemainder::Yes) {
3385 constexpr auto rounded = Int128{0};
3386 double total = FractionToDouble(totalNanoseconds, denominator);
3387 return {rounded, total};
3390 auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
3391 increment, roundingMode);
3392 constexpr double total = 0;
3393 return {rounded, total};
3396 static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
3397 FractionalDays fractionalDays,
3398 Increment increment,
3399 TemporalRoundingMode roundingMode,
3400 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3401 Handle<CalendarRecord> calendar,
3402 ComputeRemainder computeRemainder,
3403 RoundedDuration* result) {
3404 // Numbers of days between nsMinInstant and nsMaxInstant.
3405 static constexpr int32_t epochDays = 200'000'000;
3407 auto [years, months, weeks, days] = duration.date;
3409 // Step 10.a.
3410 auto yearsDuration = DateDuration{years};
3412 // Step 10.b.
3413 auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
3414 if (!yearsLater) {
3415 return false;
3417 auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
3419 // Step 10.f. (Reordered)
3420 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
3422 // Step 10.c.
3423 auto yearsMonthsWeeks = DateDuration{years, months, weeks};
3425 // Step 10.d.
3426 PlainDate yearsMonthsWeeksLater;
3427 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3428 &yearsMonthsWeeksLater)) {
3429 return false;
3432 // Step 10.e.
3433 int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
3434 MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
3436 // Step 10.f. (Moved up)
3438 // Step 10.g.
3439 fractionalDays += monthsWeeksInDays;
3441 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3442 // https://github.com/tc39/proposal-temporal/issues/2540
3444 // Step 10.h.
3445 PlainDate isoResult;
3446 if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, fractionalDays.truncate()},
3447 TemporalOverflow::Constrain, &isoResult)) {
3448 return false;
3451 // Step 10.i.
3452 Rooted<PlainDateObject*> wholeDaysLater(
3453 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3454 if (!wholeDaysLater) {
3455 return false;
3458 // Steps 10.j-l.
3459 DateDuration timePassed;
3460 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3461 TemporalUnit::Year, &timePassed)) {
3462 return false;
3465 // Step 10.m.
3466 int64_t yearsPassed = timePassed.years;
3468 // Step 10.n.
3469 years += yearsPassed;
3471 // Step 10.o.
3472 auto yearsPassedDuration = DateDuration{yearsPassed};
3474 // Steps 10.p-r.
3475 int32_t daysPassed;
3476 if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
3477 &newRelativeTo, &daysPassed)) {
3478 return false;
3480 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3482 // Step 10.s.
3483 fractionalDays -= daysPassed;
3485 // Steps 10.t.
3486 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3488 // Step 10.u.
3489 auto oneYear = DateDuration{sign};
3491 // Steps 10.v-w.
3492 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3493 int32_t oneYearDays;
3494 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
3495 &moveResultIgnored, &oneYearDays)) {
3496 return false;
3499 // Step 10.x.
3500 if (oneYearDays == 0) {
3501 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3502 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3503 return false;
3506 // Steps 10.y.
3507 auto fractionalYears = Fraction{years, std::abs(oneYearDays)};
3509 // Steps 10.z-aa.
3510 auto [numYears, total] =
3511 RoundNumberToIncrement(fractionalYears, fractionalDays, increment,
3512 roundingMode, computeRemainder);
3514 // Step 10.ab.
3515 int64_t numMonths = 0;
3516 int64_t numWeeks = 0;
3518 // Step 10.ac.
3519 constexpr auto time = NormalizedTimeDuration{};
3521 // Step 20.
3522 if (numYears.abs() >= (Uint128{1} << 32)) {
3523 return ThrowInvalidDurationPart(cx, double(numYears), "years",
3524 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3527 auto resultDuration = DateDuration{int64_t(numYears), numMonths, numWeeks};
3528 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3529 return false;
3532 *result = {{resultDuration, time}, total};
3533 return true;
3536 static bool RoundDurationMonth(JSContext* cx,
3537 const NormalizedDuration& duration,
3538 FractionalDays fractionalDays,
3539 Increment increment,
3540 TemporalRoundingMode roundingMode,
3541 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3542 Handle<CalendarRecord> calendar,
3543 ComputeRemainder computeRemainder,
3544 RoundedDuration* result) {
3545 // Numbers of days between nsMinInstant and nsMaxInstant.
3546 static constexpr int32_t epochDays = 200'000'000;
3548 auto [years, months, weeks, days] = duration.date;
3550 // Step 11.a.
3551 auto yearsMonths = DateDuration{years, months};
3553 // Step 11.b.
3554 auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
3555 if (!yearsMonthsLater) {
3556 return false;
3558 auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
3560 // Step 11.f. (Reordered)
3561 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
3563 // Step 11.c.
3564 auto yearsMonthsWeeks = DateDuration{years, months, weeks};
3566 // Step 11.d.
3567 PlainDate yearsMonthsWeeksLater;
3568 if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
3569 &yearsMonthsWeeksLater)) {
3570 return false;
3573 // Step 11.e.
3574 int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
3575 MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
3577 // Step 11.f. (Moved up)
3579 // Step 11.g.
3580 fractionalDays += weeksInDays;
3582 // FIXME: spec issue - truncation doesn't match the spec polyfill.
3583 // https://github.com/tc39/proposal-temporal/issues/2540
3585 // Step 11.h.
3586 PlainDate isoResult;
3587 if (!AddISODate(cx, yearsMonthsLaterDate,
3588 {0, 0, 0, fractionalDays.truncate()},
3589 TemporalOverflow::Constrain, &isoResult)) {
3590 return false;
3593 // Step 11.i.
3594 Rooted<PlainDateObject*> wholeDaysLater(
3595 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3596 if (!wholeDaysLater) {
3597 return false;
3600 // Steps 11.j-l.
3601 DateDuration timePassed;
3602 if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
3603 TemporalUnit::Month, &timePassed)) {
3604 return false;
3607 // Step 11.m.
3608 int64_t monthsPassed = timePassed.months;
3610 // Step 11.n.
3611 months += monthsPassed;
3613 // Step 11.o.
3614 auto monthsPassedDuration = DateDuration{0, monthsPassed};
3616 // Steps 11.p-r.
3617 int32_t daysPassed;
3618 if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
3619 &newRelativeTo, &daysPassed)) {
3620 return false;
3622 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3624 // Step 11.s.
3625 fractionalDays -= daysPassed;
3627 // Steps 11.t.
3628 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3630 // Step 11.u.
3631 auto oneMonth = DateDuration{0, sign};
3633 // Steps 11.v-w.
3634 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3635 int32_t oneMonthDays;
3636 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
3637 &moveResultIgnored, &oneMonthDays)) {
3638 return false;
3641 // Step 11.x.
3642 if (oneMonthDays == 0) {
3643 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3644 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3645 return false;
3648 // Step 11.y.
3649 auto fractionalMonths = Fraction{months, std::abs(oneMonthDays)};
3651 // Steps 11.z-aa.
3652 auto [numMonths, total] =
3653 RoundNumberToIncrement(fractionalMonths, fractionalDays, increment,
3654 roundingMode, computeRemainder);
3656 // Step 11.ab.
3657 int64_t numWeeks = 0;
3659 // Step 11.ac.
3660 constexpr auto time = NormalizedTimeDuration{};
3662 // Step 21.
3663 if (numMonths.abs() >= (Uint128{1} << 32)) {
3664 return ThrowInvalidDurationPart(cx, double(numMonths), "months",
3665 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3668 auto resultDuration = DateDuration{years, int64_t(numMonths), numWeeks};
3669 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3670 return false;
3673 *result = {{resultDuration, time}, total};
3674 return true;
3677 static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
3678 FractionalDays fractionalDays,
3679 Increment increment,
3680 TemporalRoundingMode roundingMode,
3681 Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
3682 Handle<CalendarRecord> calendar,
3683 ComputeRemainder computeRemainder,
3684 RoundedDuration* result) {
3685 // Numbers of days between nsMinInstant and nsMaxInstant.
3686 static constexpr int32_t epochDays = 200'000'000;
3688 auto [years, months, weeks, days] = duration.date;
3690 auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
3691 if (!unwrappedRelativeTo) {
3692 return false;
3694 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
3696 // Step 12.a
3697 PlainDate isoResult;
3698 if (!AddISODate(cx, relativeToDate, {0, 0, 0, fractionalDays.truncate()},
3699 TemporalOverflow::Constrain, &isoResult)) {
3700 return false;
3703 // Step 12.b.
3704 Rooted<PlainDateObject*> wholeDaysLater(
3705 cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
3706 if (!wholeDaysLater) {
3707 return false;
3710 // Steps 12.c-e.
3711 DateDuration timePassed;
3712 if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
3713 TemporalUnit::Week, &timePassed)) {
3714 return false;
3717 // Step 12.f.
3718 int64_t weeksPassed = timePassed.weeks;
3720 // Step 12.g.
3721 weeks += weeksPassed;
3723 // Step 12.h.
3724 auto weeksPassedDuration = DateDuration{0, 0, weeksPassed};
3726 // Steps 12.i-k.
3727 Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
3728 int32_t daysPassed;
3729 if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
3730 &newRelativeTo, &daysPassed)) {
3731 return false;
3733 MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
3735 // Step 12.l.
3736 fractionalDays -= daysPassed;
3738 // Steps 12.m.
3739 int32_t sign = fractionalDays.sign() < 0 ? -1 : 1;
3741 // Step 12.n.
3742 auto oneWeek = DateDuration{0, 0, sign};
3744 // Steps 12.o-p.
3745 Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
3746 int32_t oneWeekDays;
3747 if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
3748 &moveResultIgnored, &oneWeekDays)) {
3749 return false;
3752 // Step 12.q.
3753 if (oneWeekDays == 0) {
3754 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3755 JSMSG_TEMPORAL_INVALID_NUMBER, "days");
3756 return false;
3759 // Step 12.r.
3760 auto fractionalWeeks = Fraction{weeks, std::abs(oneWeekDays)};
3762 // Steps 12.s-t.
3763 auto [numWeeks, total] =
3764 RoundNumberToIncrement(fractionalWeeks, fractionalDays, increment,
3765 roundingMode, computeRemainder);
3767 // Step 12.u.
3768 constexpr auto time = NormalizedTimeDuration{};
3770 // Step 20.
3771 if (numWeeks.abs() >= (Uint128{1} << 32)) {
3772 return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
3773 JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
3776 auto resultDuration = DateDuration{years, months, int64_t(numWeeks)};
3777 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3778 return false;
3781 *result = {{resultDuration, time}, total};
3782 return true;
3785 static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
3786 const FractionalDays& fractionalDays,
3787 Increment increment,
3788 TemporalRoundingMode roundingMode,
3789 ComputeRemainder computeRemainder,
3790 RoundedDuration* result) {
3791 auto [years, months, weeks, days] = duration.date;
3793 // Pass zero fraction.
3794 constexpr auto zero = Fraction{0, 1};
3796 // Steps 13.a-b.
3797 auto [numDays, total] = RoundNumberToIncrement(
3798 zero, fractionalDays, increment, roundingMode, computeRemainder);
3800 MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
3801 "rounded days fits in int64");
3803 // Step 13.c.
3804 constexpr auto time = NormalizedTimeDuration{};
3806 // Step 20.
3807 auto resultDuration = DateDuration{years, months, weeks, int64_t(numDays)};
3808 if (!ThrowIfInvalidDuration(cx, resultDuration)) {
3809 return false;
3812 *result = {{resultDuration, time}, total};
3813 return true;
3817 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3818 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3819 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3821 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
3822 Increment increment, TemporalUnit unit,
3823 TemporalRoundingMode roundingMode,
3824 ComputeRemainder computeRemainder,
3825 RoundedDuration* result) {
3826 // The remainder is only needed when called from |Duration_total|. And `total`
3827 // always passes |increment=1| and |roundingMode=trunc|.
3828 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3829 increment == Increment{1});
3830 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3831 roundingMode == TemporalRoundingMode::Trunc);
3833 // Steps 1-5. (Not applicable.)
3835 // Step 6.
3836 if (unit <= TemporalUnit::Week) {
3837 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3838 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3839 "relativeTo");
3840 return false;
3843 // TODO: We could directly return here if unit=nanoseconds and increment=1,
3844 // because in that case this operation is a no-op. This case happens for
3845 // example when calling Temporal.PlainTime.prototype.{since,until} without an
3846 // options object.
3848 // But maybe this can be even more efficiently handled in the callers. For
3849 // example when Temporal.PlainTime.prototype.{since,until} is called without
3850 // an options object, we can not only skip the RoundDuration call, but also
3851 // the following BalanceTimeDuration call.
3853 // Step 7. (Moved below.)
3855 // Steps 8-9. (Not applicable.)
3857 // Steps 10-12. (Not applicable.)
3859 // Step 13.
3860 if (unit == TemporalUnit::Day) {
3861 // Step 7.
3862 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3863 auto fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3865 return RoundDurationDay(cx, duration, fractionalDays, increment,
3866 roundingMode, computeRemainder, result);
3869 MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Nanosecond);
3871 // Steps 14-19.
3872 auto time = duration.time;
3873 double total = 0;
3874 if (computeRemainder == ComputeRemainder::No) {
3875 time = RoundNormalizedTimeDurationToIncrement(time, unit, increment,
3876 roundingMode);
3877 } else {
3878 MOZ_ASSERT(increment == Increment{1});
3879 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
3881 total = TotalNormalizedTimeDuration(duration.time, unit);
3883 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
3885 // Step 20.
3886 MOZ_ASSERT(IsValidDuration(duration.date));
3887 *result = {{duration.date, time}, total};
3888 return true;
3892 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
3893 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
3894 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
3896 static bool RoundDuration(
3897 JSContext* cx, const NormalizedDuration& duration, Increment increment,
3898 TemporalUnit unit, TemporalRoundingMode roundingMode,
3899 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
3900 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
3901 Handle<TimeZoneRecord> timeZone,
3902 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
3903 ComputeRemainder computeRemainder, RoundedDuration* result) {
3904 // Note: |duration.days| can have a different sign than the other date
3905 // components. The date and time components can have different signs, too.
3906 MOZ_ASSERT(IsValidDuration(Duration{double(duration.date.years),
3907 double(duration.date.months),
3908 double(duration.date.weeks)}));
3909 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration.time));
3911 MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
3912 "Use RoundDuration without relativeTo when plainRelativeTo and "
3913 "zonedRelativeTo are both undefined");
3915 // The remainder is only needed when called from |Duration_total|. And `total`
3916 // always passes |increment=1| and |roundingMode=trunc|.
3917 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3918 increment == Increment{1});
3919 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
3920 roundingMode == TemporalRoundingMode::Trunc);
3922 // Steps 1-5. (Not applicable in our implementation.)
3924 // Step 6.a. (Not applicable in our implementation.)
3925 MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
3927 // Step 6.b.
3928 MOZ_ASSERT_IF(
3929 unit <= TemporalUnit::Week,
3930 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3932 // Step 6.c.
3933 MOZ_ASSERT_IF(
3934 unit <= TemporalUnit::Week,
3935 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3937 switch (unit) {
3938 case TemporalUnit::Year:
3939 case TemporalUnit::Month:
3940 case TemporalUnit::Week:
3941 break;
3942 case TemporalUnit::Day:
3943 // We can't take the faster code path when |zonedRelativeTo| is present.
3944 if (zonedRelativeTo) {
3945 break;
3947 [[fallthrough]];
3948 case TemporalUnit::Hour:
3949 case TemporalUnit::Minute:
3950 case TemporalUnit::Second:
3951 case TemporalUnit::Millisecond:
3952 case TemporalUnit::Microsecond:
3953 case TemporalUnit::Nanosecond:
3954 // Steps 7-9 and 13-21.
3955 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
3956 computeRemainder, result);
3957 case TemporalUnit::Auto:
3958 MOZ_CRASH("Unexpected temporal unit");
3961 // Step 7.
3962 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
3964 // Steps 7.a-c.
3965 FractionalDays fractionalDays;
3966 if (zonedRelativeTo) {
3967 // Step 7.a.i.
3968 Rooted<ZonedDateTime> intermediate(cx);
3969 if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
3970 duration.date, precalculatedPlainDateTime,
3971 &intermediate)) {
3972 return false;
3975 // Steps 7.a.ii.
3976 NormalizedTimeAndDays timeAndDays;
3977 if (!NormalizedTimeDurationToDays(cx, duration.time, intermediate, timeZone,
3978 &timeAndDays)) {
3979 return false;
3982 // Step 7.a.iii.
3983 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3984 } else {
3985 // Step 7.b.
3986 auto timeAndDays = NormalizedTimeDurationToDays(duration.time);
3987 fractionalDays = FractionalDays{duration.date.days, timeAndDays};
3990 // Step 7.c. (Moved below)
3992 // Step 8. (Not applicable)
3994 // Step 9.
3995 // FIXME: spec issue - `total` doesn't need be initialised.
3997 // Steps 10-20.
3998 switch (unit) {
3999 // Steps 10 and 20.
4000 case TemporalUnit::Year:
4001 return RoundDurationYear(cx, duration, fractionalDays, increment,
4002 roundingMode, plainRelativeTo, calendar,
4003 computeRemainder, result);
4005 // Steps 11 and 20.
4006 case TemporalUnit::Month:
4007 return RoundDurationMonth(cx, duration, fractionalDays, increment,
4008 roundingMode, plainRelativeTo, calendar,
4009 computeRemainder, result);
4011 // Steps 12 and 20.
4012 case TemporalUnit::Week:
4013 return RoundDurationWeek(cx, duration, fractionalDays, increment,
4014 roundingMode, plainRelativeTo, calendar,
4015 computeRemainder, result);
4017 // Steps 13 and 20.
4018 case TemporalUnit::Day:
4019 return RoundDurationDay(cx, duration, fractionalDays, increment,
4020 roundingMode, computeRemainder, result);
4022 // Steps 14-19. (Handled elsewhere)
4023 case TemporalUnit::Auto:
4024 case TemporalUnit::Hour:
4025 case TemporalUnit::Minute:
4026 case TemporalUnit::Second:
4027 case TemporalUnit::Millisecond:
4028 case TemporalUnit::Microsecond:
4029 case TemporalUnit::Nanosecond:
4030 break;
4033 MOZ_CRASH("Unexpected temporal unit");
4037 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4038 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4039 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4041 static bool RoundDuration(
4042 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4043 TemporalUnit unit, TemporalRoundingMode roundingMode,
4044 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4045 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4046 Handle<TimeZoneRecord> timeZone,
4047 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4048 double* result) {
4049 // Only called from |Duration_total|, which always passes |increment=1| and
4050 // |roundingMode=trunc|.
4051 MOZ_ASSERT(increment == Increment{1});
4052 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4054 RoundedDuration rounded;
4055 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4056 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4057 precalculatedPlainDateTime, ComputeRemainder::Yes,
4058 &rounded)) {
4059 return false;
4062 *result = rounded.total;
4063 return true;
4067 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4068 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4069 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4071 static bool RoundDuration(
4072 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4073 TemporalUnit unit, TemporalRoundingMode roundingMode,
4074 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4075 Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
4076 Handle<TimeZoneRecord> timeZone,
4077 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
4078 NormalizedDuration* result) {
4079 RoundedDuration rounded;
4080 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4081 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4082 precalculatedPlainDateTime, ComputeRemainder::No,
4083 &rounded)) {
4084 return false;
4087 *result = rounded.duration;
4088 return true;
4092 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4093 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4094 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4096 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4097 Increment increment, TemporalUnit unit,
4098 TemporalRoundingMode roundingMode, double* result) {
4099 MOZ_ASSERT(IsValidDuration(duration));
4101 // Only called from |Duration_total|, which always passes |increment=1| and
4102 // |roundingMode=trunc|.
4103 MOZ_ASSERT(increment == Increment{1});
4104 MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
4106 RoundedDuration rounded;
4107 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4108 ComputeRemainder::Yes, &rounded)) {
4109 return false;
4112 *result = rounded.total;
4113 return true;
4117 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4118 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4119 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4121 static bool RoundDuration(JSContext* cx, const NormalizedDuration& duration,
4122 Increment increment, TemporalUnit unit,
4123 TemporalRoundingMode roundingMode,
4124 NormalizedDuration* result) {
4125 MOZ_ASSERT(IsValidDuration(duration));
4127 RoundedDuration rounded;
4128 if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
4129 ComputeRemainder::No, &rounded)) {
4130 return false;
4133 *result = rounded.duration;
4134 return true;
4138 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4139 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4140 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4142 bool js::temporal::RoundDuration(
4143 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4144 TemporalUnit unit, TemporalRoundingMode roundingMode,
4145 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
4146 Handle<CalendarRecord> calendar, NormalizedDuration* result) {
4147 MOZ_ASSERT(IsValidDuration(duration));
4149 Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
4150 Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
4151 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4152 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4153 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4154 precalculatedPlainDateTime, result);
4158 * RoundDuration ( years, months, weeks, days, norm, increment, unit,
4159 * roundingMode [ , plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ ,
4160 * timeZoneRec [ , precalculatedPlainDateTime ] ] ] ] ] )
4162 bool js::temporal::RoundDuration(
4163 JSContext* cx, const NormalizedDuration& duration, Increment increment,
4164 TemporalUnit unit, TemporalRoundingMode roundingMode,
4165 Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
4166 Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
4167 const PlainDateTime& precalculatedPlainDateTime,
4168 NormalizedDuration* result) {
4169 MOZ_ASSERT(IsValidDuration(duration));
4171 return ::RoundDuration(cx, duration, increment, unit, roundingMode,
4172 plainRelativeTo, calendar, zonedRelativeTo, timeZone,
4173 mozilla::SomeRef(precalculatedPlainDateTime), result);
4176 enum class DurationOperation { Add, Subtract };
4179 * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
4180 * options )
4182 static bool AddDurationToOrSubtractDurationFromDuration(
4183 JSContext* cx, DurationOperation operation, const CallArgs& args) {
4184 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4185 auto duration = ToDuration(durationObj);
4187 // Step 1. (Not applicable in our implementation.)
4189 // Step 2.
4190 Duration other;
4191 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
4192 return false;
4195 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4196 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4197 Rooted<TimeZoneRecord> timeZone(cx);
4198 if (args.hasDefined(1)) {
4199 const char* name = operation == DurationOperation::Add ? "add" : "subtract";
4201 // Step 3.
4202 Rooted<JSObject*> options(cx,
4203 RequireObjectArg(cx, "options", name, args[1]));
4204 if (!options) {
4205 return false;
4208 // Steps 4-7.
4209 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4210 &zonedRelativeTo, &timeZone)) {
4211 return false;
4213 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4214 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4217 // Step 8.
4218 Rooted<CalendarRecord> calendar(cx);
4219 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4220 zonedRelativeTo,
4222 CalendarMethod::DateAdd,
4223 CalendarMethod::DateUntil,
4225 &calendar)) {
4226 return false;
4229 // Step 9.
4230 if (operation == DurationOperation::Subtract) {
4231 other = other.negate();
4234 Duration result;
4235 if (plainRelativeTo) {
4236 if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
4237 return false;
4239 } else if (zonedRelativeTo) {
4240 if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
4241 &result)) {
4242 return false;
4244 } else {
4245 if (!AddDuration(cx, duration, other, &result)) {
4246 return false;
4250 // Step 10.
4251 auto* obj = CreateTemporalDuration(cx, result);
4252 if (!obj) {
4253 return false;
4256 args.rval().setObject(*obj);
4257 return true;
4261 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
4262 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
4263 * ] ] ] ] ] ] )
4265 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
4266 CallArgs args = CallArgsFromVp(argc, vp);
4268 // Step 1.
4269 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
4270 return false;
4273 // Step 2.
4274 double years = 0;
4275 if (args.hasDefined(0) &&
4276 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
4277 return false;
4280 // Step 3.
4281 double months = 0;
4282 if (args.hasDefined(1) &&
4283 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
4284 return false;
4287 // Step 4.
4288 double weeks = 0;
4289 if (args.hasDefined(2) &&
4290 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
4291 return false;
4294 // Step 5.
4295 double days = 0;
4296 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
4297 return false;
4300 // Step 6.
4301 double hours = 0;
4302 if (args.hasDefined(4) &&
4303 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
4304 return false;
4307 // Step 7.
4308 double minutes = 0;
4309 if (args.hasDefined(5) &&
4310 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
4311 return false;
4314 // Step 8.
4315 double seconds = 0;
4316 if (args.hasDefined(6) &&
4317 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
4318 return false;
4321 // Step 9.
4322 double milliseconds = 0;
4323 if (args.hasDefined(7) &&
4324 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
4325 return false;
4328 // Step 10.
4329 double microseconds = 0;
4330 if (args.hasDefined(8) &&
4331 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
4332 return false;
4335 // Step 11.
4336 double nanoseconds = 0;
4337 if (args.hasDefined(9) &&
4338 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
4339 return false;
4342 // Step 12.
4343 auto* duration = CreateTemporalDuration(
4344 cx, args,
4345 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
4346 microseconds, nanoseconds});
4347 if (!duration) {
4348 return false;
4351 args.rval().setObject(*duration);
4352 return true;
4356 * Temporal.Duration.from ( item )
4358 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
4359 CallArgs args = CallArgsFromVp(argc, vp);
4361 Handle<Value> item = args.get(0);
4363 // Step 1.
4364 if (item.isObject()) {
4365 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
4366 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
4367 if (!result) {
4368 return false;
4371 args.rval().setObject(*result);
4372 return true;
4376 // Step 2.
4377 auto result = ToTemporalDuration(cx, item);
4378 if (!result) {
4379 return false;
4382 args.rval().setObject(*result);
4383 return true;
4387 * Temporal.Duration.compare ( one, two [ , options ] )
4389 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
4390 CallArgs args = CallArgsFromVp(argc, vp);
4392 // Step 1.
4393 Duration one;
4394 if (!ToTemporalDuration(cx, args.get(0), &one)) {
4395 return false;
4398 // Step 2.
4399 Duration two;
4400 if (!ToTemporalDuration(cx, args.get(1), &two)) {
4401 return false;
4404 // Step 3.
4405 Rooted<JSObject*> options(cx);
4406 if (args.hasDefined(2)) {
4407 options = RequireObjectArg(cx, "options", "compare", args[2]);
4408 if (!options) {
4409 return false;
4413 // Step 4.
4414 if (one == two) {
4415 args.rval().setInt32(0);
4416 return true;
4419 // Steps 5-8.
4420 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4421 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4422 Rooted<TimeZoneRecord> timeZone(cx);
4423 if (options) {
4424 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4425 &zonedRelativeTo, &timeZone)) {
4426 return false;
4428 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4429 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4432 // Steps 9-10.
4433 auto hasCalendarUnit = [](const auto& d) {
4434 return d.years != 0 || d.months != 0 || d.weeks != 0;
4436 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
4438 // Step 11.
4439 Rooted<CalendarRecord> calendar(cx);
4440 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4441 zonedRelativeTo,
4443 CalendarMethod::DateAdd,
4445 &calendar)) {
4446 return false;
4449 // Step 12.
4450 if (zonedRelativeTo &&
4451 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
4452 // Step 12.a.
4453 const auto& instant = zonedRelativeTo.instant();
4455 // Step 12.b.
4456 PlainDateTime dateTime;
4457 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
4458 return false;
4461 // Step 12.c.
4462 auto normalized1 = CreateNormalizedDurationRecord(one);
4464 // Step 12.d.
4465 auto normalized2 = CreateNormalizedDurationRecord(two);
4467 // Step 12.e.
4468 Instant after1;
4469 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
4470 dateTime, &after1)) {
4471 return false;
4474 // Step 12.f.
4475 Instant after2;
4476 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
4477 dateTime, &after2)) {
4478 return false;
4481 // Steps 12.g-i.
4482 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
4483 return true;
4486 // Steps 13-14.
4487 int64_t days1, days2;
4488 if (calendarUnitsPresent) {
4489 // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
4491 // Step 13.a.
4492 DateDuration unbalanceResult1;
4493 if (plainRelativeTo) {
4494 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
4495 TemporalUnit::Day, plainRelativeTo,
4496 calendar, &unbalanceResult1)) {
4497 return false;
4499 } else {
4500 if (!UnbalanceDateDurationRelative(
4501 cx, one.toDateDuration(), TemporalUnit::Day, &unbalanceResult1)) {
4502 return false;
4504 MOZ_ASSERT(one.toDateDuration() == unbalanceResult1);
4507 // Step 13.b.
4508 DateDuration unbalanceResult2;
4509 if (plainRelativeTo) {
4510 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
4511 TemporalUnit::Day, plainRelativeTo,
4512 calendar, &unbalanceResult2)) {
4513 return false;
4515 } else {
4516 if (!UnbalanceDateDurationRelative(
4517 cx, two.toDateDuration(), TemporalUnit::Day, &unbalanceResult2)) {
4518 return false;
4520 MOZ_ASSERT(two.toDateDuration() == unbalanceResult2);
4523 // Step 13.c.
4524 days1 = unbalanceResult1.days;
4526 // Step 13.d.
4527 days2 = unbalanceResult2.days;
4528 } else {
4529 // Step 14.a.
4530 days1 = mozilla::AssertedCast<int64_t>(one.days);
4532 // Step 14.b.
4533 days2 = mozilla::AssertedCast<int64_t>(two.days);
4536 // Step 15.
4537 auto normalized1 = NormalizeTimeDuration(one);
4539 // Step 16.
4540 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
4541 &normalized1)) {
4542 return false;
4545 // Step 17.
4546 auto normalized2 = NormalizeTimeDuration(two);
4548 // Step 18.
4549 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
4550 &normalized2)) {
4551 return false;
4554 // Step 19.
4555 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
4556 return true;
4560 * get Temporal.Duration.prototype.years
4562 static bool Duration_years(JSContext* cx, const CallArgs& args) {
4563 // Step 3.
4564 auto* duration = &args.thisv().toObject().as<DurationObject>();
4565 args.rval().setNumber(duration->years());
4566 return true;
4570 * get Temporal.Duration.prototype.years
4572 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
4573 // Steps 1-2.
4574 CallArgs args = CallArgsFromVp(argc, vp);
4575 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
4579 * get Temporal.Duration.prototype.months
4581 static bool Duration_months(JSContext* cx, const CallArgs& args) {
4582 // Step 3.
4583 auto* duration = &args.thisv().toObject().as<DurationObject>();
4584 args.rval().setNumber(duration->months());
4585 return true;
4589 * get Temporal.Duration.prototype.months
4591 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
4592 // Steps 1-2.
4593 CallArgs args = CallArgsFromVp(argc, vp);
4594 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
4598 * get Temporal.Duration.prototype.weeks
4600 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
4601 // Step 3.
4602 auto* duration = &args.thisv().toObject().as<DurationObject>();
4603 args.rval().setNumber(duration->weeks());
4604 return true;
4608 * get Temporal.Duration.prototype.weeks
4610 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
4611 // Steps 1-2.
4612 CallArgs args = CallArgsFromVp(argc, vp);
4613 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
4617 * get Temporal.Duration.prototype.days
4619 static bool Duration_days(JSContext* cx, const CallArgs& args) {
4620 // Step 3.
4621 auto* duration = &args.thisv().toObject().as<DurationObject>();
4622 args.rval().setNumber(duration->days());
4623 return true;
4627 * get Temporal.Duration.prototype.days
4629 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
4630 // Steps 1-2.
4631 CallArgs args = CallArgsFromVp(argc, vp);
4632 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
4636 * get Temporal.Duration.prototype.hours
4638 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
4639 // Step 3.
4640 auto* duration = &args.thisv().toObject().as<DurationObject>();
4641 args.rval().setNumber(duration->hours());
4642 return true;
4646 * get Temporal.Duration.prototype.hours
4648 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
4649 // Steps 1-2.
4650 CallArgs args = CallArgsFromVp(argc, vp);
4651 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
4655 * get Temporal.Duration.prototype.minutes
4657 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
4658 // Step 3.
4659 auto* duration = &args.thisv().toObject().as<DurationObject>();
4660 args.rval().setNumber(duration->minutes());
4661 return true;
4665 * get Temporal.Duration.prototype.minutes
4667 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
4668 // Steps 1-2.
4669 CallArgs args = CallArgsFromVp(argc, vp);
4670 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
4674 * get Temporal.Duration.prototype.seconds
4676 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
4677 // Step 3.
4678 auto* duration = &args.thisv().toObject().as<DurationObject>();
4679 args.rval().setNumber(duration->seconds());
4680 return true;
4684 * get Temporal.Duration.prototype.seconds
4686 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
4687 // Steps 1-2.
4688 CallArgs args = CallArgsFromVp(argc, vp);
4689 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
4693 * get Temporal.Duration.prototype.milliseconds
4695 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
4696 // Step 3.
4697 auto* duration = &args.thisv().toObject().as<DurationObject>();
4698 args.rval().setNumber(duration->milliseconds());
4699 return true;
4703 * get Temporal.Duration.prototype.milliseconds
4705 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
4706 // Steps 1-2.
4707 CallArgs args = CallArgsFromVp(argc, vp);
4708 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
4712 * get Temporal.Duration.prototype.microseconds
4714 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
4715 // Step 3.
4716 auto* duration = &args.thisv().toObject().as<DurationObject>();
4717 args.rval().setNumber(duration->microseconds());
4718 return true;
4722 * get Temporal.Duration.prototype.microseconds
4724 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
4725 // Steps 1-2.
4726 CallArgs args = CallArgsFromVp(argc, vp);
4727 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
4731 * get Temporal.Duration.prototype.nanoseconds
4733 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
4734 // Step 3.
4735 auto* duration = &args.thisv().toObject().as<DurationObject>();
4736 args.rval().setNumber(duration->nanoseconds());
4737 return true;
4741 * get Temporal.Duration.prototype.nanoseconds
4743 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
4744 // Steps 1-2.
4745 CallArgs args = CallArgsFromVp(argc, vp);
4746 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
4750 * get Temporal.Duration.prototype.sign
4752 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
4753 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4755 // Step 3.
4756 args.rval().setInt32(DurationSign(duration));
4757 return true;
4761 * get Temporal.Duration.prototype.sign
4763 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
4764 // Steps 1-2.
4765 CallArgs args = CallArgsFromVp(argc, vp);
4766 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
4770 * get Temporal.Duration.prototype.blank
4772 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
4773 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4775 // Steps 3-5.
4776 args.rval().setBoolean(duration == Duration{});
4777 return true;
4781 * get Temporal.Duration.prototype.blank
4783 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
4784 // Steps 1-2.
4785 CallArgs args = CallArgsFromVp(argc, vp);
4786 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
4790 * Temporal.Duration.prototype.with ( temporalDurationLike )
4792 * ToPartialDuration ( temporalDurationLike )
4794 static bool Duration_with(JSContext* cx, const CallArgs& args) {
4795 // Absent values default to the corresponding values of |this| object.
4796 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4798 // Steps 3-23.
4799 Rooted<JSObject*> temporalDurationLike(
4800 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
4801 if (!temporalDurationLike) {
4802 return false;
4804 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
4805 return false;
4808 // Step 24.
4809 auto* result = CreateTemporalDuration(cx, duration);
4810 if (!result) {
4811 return false;
4814 args.rval().setObject(*result);
4815 return true;
4819 * Temporal.Duration.prototype.with ( temporalDurationLike )
4821 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
4822 // Steps 1-2.
4823 CallArgs args = CallArgsFromVp(argc, vp);
4824 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
4828 * Temporal.Duration.prototype.negated ( )
4830 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
4831 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4833 // Step 3.
4834 auto* result = CreateTemporalDuration(cx, duration.negate());
4835 if (!result) {
4836 return false;
4839 args.rval().setObject(*result);
4840 return true;
4844 * Temporal.Duration.prototype.negated ( )
4846 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
4847 // Steps 1-2.
4848 CallArgs args = CallArgsFromVp(argc, vp);
4849 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
4853 * Temporal.Duration.prototype.abs ( )
4855 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
4856 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4858 // Step 3.
4859 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
4860 if (!result) {
4861 return false;
4864 args.rval().setObject(*result);
4865 return true;
4869 * Temporal.Duration.prototype.abs ( )
4871 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
4872 // Steps 1-2.
4873 CallArgs args = CallArgsFromVp(argc, vp);
4874 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
4878 * Temporal.Duration.prototype.add ( other [ , options ] )
4880 static bool Duration_add(JSContext* cx, const CallArgs& args) {
4881 return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
4882 args);
4886 * Temporal.Duration.prototype.add ( other [ , options ] )
4888 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
4889 // Steps 1-2.
4890 CallArgs args = CallArgsFromVp(argc, vp);
4891 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
4895 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4897 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
4898 return AddDurationToOrSubtractDurationFromDuration(
4899 cx, DurationOperation::Subtract, args);
4903 * Temporal.Duration.prototype.subtract ( other [ , options ] )
4905 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
4906 // Steps 1-2.
4907 CallArgs args = CallArgsFromVp(argc, vp);
4908 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
4912 * Temporal.Duration.prototype.round ( roundTo )
4914 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4915 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4917 // Step 18. (Reordered)
4918 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4920 // Steps 3-25.
4921 auto smallestUnit = TemporalUnit::Auto;
4922 TemporalUnit largestUnit;
4923 auto roundingMode = TemporalRoundingMode::HalfExpand;
4924 auto roundingIncrement = Increment{1};
4925 Rooted<JSObject*> relativeTo(cx);
4926 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4927 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4928 Rooted<TimeZoneRecord> timeZone(cx);
4929 if (args.get(0).isString()) {
4930 // Step 4. (Not applicable in our implementation.)
4932 // Steps 6-15. (Not applicable)
4934 // Step 16.
4935 Rooted<JSString*> paramString(cx, args[0].toString());
4936 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
4937 TemporalUnitGroup::DateTime, &smallestUnit)) {
4938 return false;
4941 // Step 17. (Not applicable)
4943 // Step 18. (Moved above)
4945 // Step 19.
4946 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4948 // Step 20. (Not applicable)
4950 // Step 20.a. (Not applicable)
4952 // Step 20.b.
4953 largestUnit = defaultLargestUnit;
4955 // Steps 21-25. (Not applicable)
4956 } else {
4957 // Steps 3 and 5.
4958 Rooted<JSObject*> options(
4959 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
4960 if (!options) {
4961 return false;
4964 // Step 6.
4965 bool smallestUnitPresent = true;
4967 // Step 7.
4968 bool largestUnitPresent = true;
4970 // Steps 8-9.
4972 // Inlined GetTemporalUnit and GetOption so we can more easily detect an
4973 // absent "largestUnit" value.
4974 Rooted<Value> largestUnitValue(cx);
4975 if (!GetProperty(cx, options, options, cx->names().largestUnit,
4976 &largestUnitValue)) {
4977 return false;
4980 if (!largestUnitValue.isUndefined()) {
4981 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
4982 if (!largestUnitStr) {
4983 return false;
4986 largestUnit = TemporalUnit::Auto;
4987 if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
4988 TemporalUnitGroup::DateTime, &largestUnit)) {
4989 return false;
4993 // Steps 10-13.
4994 if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
4995 &zonedRelativeTo, &timeZone)) {
4996 return false;
4998 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4999 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5001 // Step 14.
5002 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
5003 return false;
5006 // Step 15.
5007 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5008 return false;
5011 // Step 16.
5012 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5013 TemporalUnitGroup::DateTime, &smallestUnit)) {
5014 return false;
5017 // Step 17.
5018 if (smallestUnit == TemporalUnit::Auto) {
5019 // Step 17.a.
5020 smallestUnitPresent = false;
5022 // Step 17.b.
5023 smallestUnit = TemporalUnit::Nanosecond;
5026 // Step 18. (Moved above)
5028 // Step 19.
5029 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
5031 // Steps 20-21.
5032 if (largestUnitValue.isUndefined()) {
5033 // Step 20.a.
5034 largestUnitPresent = false;
5036 // Step 20.b.
5037 largestUnit = defaultLargestUnit;
5038 } else if (largestUnit == TemporalUnit::Auto) {
5039 // Step 21.a
5040 largestUnit = defaultLargestUnit;
5043 // Step 22.
5044 if (!smallestUnitPresent && !largestUnitPresent) {
5045 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5046 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
5047 return false;
5050 // Step 23.
5051 if (largestUnit > smallestUnit) {
5052 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5053 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
5054 return false;
5057 // Steps 24-25.
5058 if (smallestUnit > TemporalUnit::Day) {
5059 // Step 24.
5060 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
5062 // Step 25.
5063 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
5064 false)) {
5065 return false;
5070 // Step 26.
5071 bool hoursToDaysConversionMayOccur = false;
5073 // Step 27.
5074 if (duration.days != 0 && zonedRelativeTo) {
5075 hoursToDaysConversionMayOccur = true;
5078 // Step 28.
5079 else if (std::abs(duration.hours) >= 24) {
5080 hoursToDaysConversionMayOccur = true;
5083 // Step 29.
5084 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
5085 roundingIncrement == Increment{1};
5087 // Step 30.
5088 bool calendarUnitsPresent =
5089 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
5091 // Step 31.
5092 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
5093 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
5094 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
5095 std::abs(duration.milliseconds) < 1000 &&
5096 std::abs(duration.microseconds) < 1000 &&
5097 std::abs(duration.nanoseconds) < 1000) {
5098 // Steps 31.a-b.
5099 auto* obj = CreateTemporalDuration(cx, duration);
5100 if (!obj) {
5101 return false;
5104 args.rval().setObject(*obj);
5105 return true;
5108 // Step 32.
5109 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5111 // Step 33.
5112 bool plainDateTimeOrRelativeToWillBeUsed =
5113 !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
5114 calendarUnitsPresent || duration.days != 0;
5116 // Step 34.
5117 PlainDateTime relativeToDateTime;
5118 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5119 // Steps 34.a-b.
5120 const auto& instant = zonedRelativeTo.instant();
5122 // Step 34.c.
5123 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5124 return false;
5126 precalculatedPlainDateTime =
5127 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5129 // Step 34.d.
5130 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5131 zonedRelativeTo.calendar());
5132 if (!plainRelativeTo) {
5133 return false;
5137 // Step 35.
5138 Rooted<CalendarRecord> calendar(cx);
5139 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5140 zonedRelativeTo,
5142 CalendarMethod::DateAdd,
5143 CalendarMethod::DateUntil,
5145 &calendar)) {
5146 return false;
5149 // Step 36.
5150 DateDuration unbalanceResult;
5151 if (plainRelativeTo) {
5152 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5153 largestUnit, plainRelativeTo, calendar,
5154 &unbalanceResult)) {
5155 return false;
5157 } else {
5158 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(),
5159 largestUnit, &unbalanceResult)) {
5160 return false;
5162 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5165 // Steps 37-39.
5166 auto roundInput =
5167 NormalizedDuration{unbalanceResult, NormalizeTimeDuration(duration)};
5168 NormalizedDuration roundResult;
5169 if (plainRelativeTo || zonedRelativeTo) {
5170 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5171 roundingMode, plainRelativeTo, calendar,
5172 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5173 &roundResult)) {
5174 return false;
5176 } else {
5177 if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
5178 roundingMode, &roundResult)) {
5179 return false;
5183 // Steps 40-41.
5184 TimeDuration balanceResult;
5185 if (zonedRelativeTo) {
5186 // Step 40.a.
5187 NormalizedDuration adjustResult;
5188 if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
5189 smallestUnit, roundingMode, zonedRelativeTo,
5190 calendar, timeZone,
5191 precalculatedPlainDateTime, &adjustResult)) {
5192 return false;
5194 roundResult = adjustResult;
5196 // Step 40.b.
5197 if (!BalanceTimeDurationRelative(
5198 cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
5199 precalculatedPlainDateTime, &balanceResult)) {
5200 return false;
5202 } else {
5203 // Step 41.a.
5204 NormalizedTimeDuration withDays;
5205 if (!Add24HourDaysToNormalizedTimeDuration(
5206 cx, roundResult.time, roundResult.date.days, &withDays)) {
5207 return false;
5210 // Step 41.b.
5211 balanceResult = temporal::BalanceTimeDuration(withDays, largestUnit);
5214 // Step 41.
5215 auto balanceInput = DateDuration{
5216 roundResult.date.years,
5217 roundResult.date.months,
5218 roundResult.date.weeks,
5219 balanceResult.days,
5221 DateDuration dateResult;
5222 if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
5223 smallestUnit, plainRelativeTo, calendar,
5224 &dateResult)) {
5225 return false;
5228 // Step 42.
5229 auto result = Duration{
5230 double(dateResult.years), double(dateResult.months),
5231 double(dateResult.weeks), double(dateResult.days),
5232 double(balanceResult.hours), double(balanceResult.minutes),
5233 double(balanceResult.seconds), double(balanceResult.milliseconds),
5234 balanceResult.microseconds, balanceResult.nanoseconds,
5236 MOZ_ASSERT(IsValidDuration(result));
5237 auto* obj = CreateTemporalDuration(cx, result);
5238 if (!obj) {
5239 return false;
5242 args.rval().setObject(*obj);
5243 return true;
5247 * Temporal.Duration.prototype.round ( options )
5249 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
5250 // Steps 1-2.
5251 CallArgs args = CallArgsFromVp(argc, vp);
5252 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
5256 * Temporal.Duration.prototype.total ( totalOf )
5258 static bool Duration_total(JSContext* cx, const CallArgs& args) {
5259 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
5260 auto duration = ToDuration(durationObj);
5262 // Steps 3-11.
5263 Rooted<JSObject*> relativeTo(cx);
5264 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
5265 Rooted<ZonedDateTime> zonedRelativeTo(cx);
5266 Rooted<TimeZoneRecord> timeZone(cx);
5267 auto unit = TemporalUnit::Auto;
5268 if (args.get(0).isString()) {
5269 // Step 4. (Not applicable in our implementation.)
5271 // Steps 6-10. (Implicit)
5272 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
5274 // Step 11.
5275 Rooted<JSString*> paramString(cx, args[0].toString());
5276 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
5277 TemporalUnitGroup::DateTime, &unit)) {
5278 return false;
5280 } else {
5281 // Steps 3 and 5.
5282 Rooted<JSObject*> totalOf(
5283 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
5284 if (!totalOf) {
5285 return false;
5288 // Steps 6-10.
5289 if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
5290 &zonedRelativeTo, &timeZone)) {
5291 return false;
5293 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
5294 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
5296 // Step 11.
5297 if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
5298 TemporalUnitGroup::DateTime, &unit)) {
5299 return false;
5302 if (unit == TemporalUnit::Auto) {
5303 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5304 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
5305 return false;
5309 // Step 12.
5310 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
5312 // Step 13.
5313 bool plainDateTimeOrRelativeToWillBeUsed =
5314 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
5316 // Step 14.
5317 PlainDateTime relativeToDateTime;
5318 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
5319 // Steps 14.a-b.
5320 const auto& instant = zonedRelativeTo.instant();
5322 // Step 14.c.
5323 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
5324 return false;
5326 precalculatedPlainDateTime =
5327 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
5329 // Step 14.d
5330 plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
5331 zonedRelativeTo.calendar());
5332 if (!plainRelativeTo) {
5333 return false;
5337 // Step 15.
5338 Rooted<CalendarRecord> calendar(cx);
5339 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
5340 zonedRelativeTo,
5342 CalendarMethod::DateAdd,
5343 CalendarMethod::DateUntil,
5345 &calendar)) {
5346 return false;
5349 // Step 16.
5350 DateDuration unbalanceResult;
5351 if (plainRelativeTo) {
5352 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5353 plainRelativeTo, calendar,
5354 &unbalanceResult)) {
5355 return false;
5357 } else {
5358 if (!UnbalanceDateDurationRelative(cx, duration.toDateDuration(), unit,
5359 &unbalanceResult)) {
5360 return false;
5362 MOZ_ASSERT(duration.toDateDuration() == unbalanceResult);
5365 // Step 17.
5366 int64_t unbalancedDays = unbalanceResult.days;
5368 // Steps 18-19.
5369 int64_t days;
5370 NormalizedTimeDuration balanceResult;
5371 if (zonedRelativeTo) {
5372 // Step 18.a
5373 Rooted<ZonedDateTime> intermediate(cx);
5374 if (!MoveRelativeZonedDateTime(
5375 cx, zonedRelativeTo, calendar, timeZone,
5376 {unbalanceResult.years, unbalanceResult.months,
5377 unbalanceResult.weeks, 0},
5378 precalculatedPlainDateTime, &intermediate)) {
5379 return false;
5382 // Step 18.b.
5383 auto timeDuration = NormalizeTimeDuration(duration);
5385 // Step 18.c
5386 const auto& startNs = intermediate.instant();
5388 // Step 18.d.
5389 const auto& startInstant = startNs;
5391 // Step 18.e.
5392 mozilla::Maybe<PlainDateTime> startDateTime{};
5394 // Steps 18.f-g.
5395 Instant intermediateNs;
5396 if (unbalancedDays != 0) {
5397 // Step 18.f.i.
5398 PlainDateTime dateTime;
5399 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5400 return false;
5402 startDateTime = mozilla::Some(dateTime);
5404 // Step 18.f.ii.
5405 Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
5406 Instant addResult;
5407 if (!AddDaysToZonedDateTime(cx, startInstant, dateTime, timeZone,
5408 isoCalendar, unbalancedDays, &addResult)) {
5409 return false;
5412 // Step 18.f.iii.
5413 intermediateNs = addResult;
5414 } else {
5415 // Step 18.g.
5416 intermediateNs = startNs;
5419 // Step 18.h.
5420 Instant endNs;
5421 if (!AddInstant(cx, intermediateNs, timeDuration, &endNs)) {
5422 return false;
5425 // Step 18.i.
5426 auto difference =
5427 NormalizedTimeDurationFromEpochNanosecondsDifference(endNs, startNs);
5429 // Steps 18.j-k.
5431 // Avoid calling NormalizedTimeDurationToDays for a zero time difference.
5432 if (TemporalUnit::Year <= unit && unit <= TemporalUnit::Day &&
5433 difference != NormalizedTimeDuration{}) {
5434 // Step 18.j.i.
5435 if (!startDateTime) {
5436 PlainDateTime dateTime;
5437 if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &dateTime)) {
5438 return false;
5440 startDateTime = mozilla::Some(dateTime);
5443 // Step 18.j.ii.
5444 NormalizedTimeAndDays timeAndDays;
5445 if (!NormalizedTimeDurationToDays(cx, difference, intermediate, timeZone,
5446 *startDateTime, &timeAndDays)) {
5447 return false;
5450 // Step 18.j.iii.
5451 balanceResult = NormalizedTimeDuration::fromNanoseconds(timeAndDays.time);
5453 // Step 18.j.iv.
5454 days = timeAndDays.days;
5455 } else {
5456 // Step 18.k.i.
5457 balanceResult = difference;
5458 days = 0;
5460 } else {
5461 // Step 19.a.
5462 auto timeDuration = NormalizeTimeDuration(duration);
5464 // Step 19.b.
5465 if (!Add24HourDaysToNormalizedTimeDuration(cx, timeDuration, unbalancedDays,
5466 &balanceResult)) {
5467 return false;
5470 // Step 19.c.
5471 days = 0;
5473 MOZ_ASSERT(IsValidNormalizedTimeDuration(balanceResult));
5475 // Step 20.
5476 auto roundInput = NormalizedDuration{
5478 unbalanceResult.years,
5479 unbalanceResult.months,
5480 unbalanceResult.weeks,
5481 days,
5483 balanceResult,
5485 double total;
5486 if (plainRelativeTo || zonedRelativeTo) {
5487 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5488 TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
5489 zonedRelativeTo, timeZone, precalculatedPlainDateTime,
5490 &total)) {
5491 return false;
5493 } else {
5494 if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
5495 TemporalRoundingMode::Trunc, &total)) {
5496 return false;
5500 // Step 21.
5501 args.rval().setNumber(total);
5502 return true;
5506 * Temporal.Duration.prototype.total ( totalOf )
5508 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
5509 // Steps 1-2.
5510 CallArgs args = CallArgsFromVp(argc, vp);
5511 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
5515 * Temporal.Duration.prototype.toString ( [ options ] )
5517 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
5518 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5520 // Steps 3-9.
5521 SecondsStringPrecision precision = {Precision::Auto(),
5522 TemporalUnit::Nanosecond, Increment{1}};
5523 auto roundingMode = TemporalRoundingMode::Trunc;
5524 if (args.hasDefined(0)) {
5525 // Step 3.
5526 Rooted<JSObject*> options(
5527 cx, RequireObjectArg(cx, "options", "toString", args[0]));
5528 if (!options) {
5529 return false;
5532 // Steps 4-5.
5533 auto digits = Precision::Auto();
5534 if (!ToFractionalSecondDigits(cx, options, &digits)) {
5535 return false;
5538 // Step 6.
5539 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
5540 return false;
5543 // Step 7.
5544 auto smallestUnit = TemporalUnit::Auto;
5545 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
5546 TemporalUnitGroup::Time, &smallestUnit)) {
5547 return false;
5550 // Step 8.
5551 if (smallestUnit == TemporalUnit::Hour ||
5552 smallestUnit == TemporalUnit::Minute) {
5553 const char* smallestUnitStr =
5554 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
5555 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
5556 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
5557 smallestUnitStr, "smallestUnit");
5558 return false;
5561 // Step 9.
5562 precision = ToSecondsStringPrecision(smallestUnit, digits);
5565 // Steps 10-11.
5566 Duration result;
5567 if (precision.unit != TemporalUnit::Nanosecond ||
5568 precision.increment != Increment{1}) {
5569 // Step 10.a.
5570 auto timeDuration = NormalizeTimeDuration(duration);
5572 // Step 10.b.
5573 auto largestUnit = DefaultTemporalLargestUnit(duration);
5575 // Steps 10.c-d.
5576 auto rounded = RoundDuration(timeDuration, precision.increment,
5577 precision.unit, roundingMode);
5579 // Step 10.e.
5580 auto balanced = BalanceTimeDuration(
5581 rounded, std::min(largestUnit, TemporalUnit::Second));
5583 // Step 10.f.
5584 result = {
5585 duration.years, duration.months,
5586 duration.weeks, duration.days + double(balanced.days),
5587 double(balanced.hours), double(balanced.minutes),
5588 double(balanced.seconds), double(balanced.milliseconds),
5589 balanced.microseconds, balanced.nanoseconds,
5591 MOZ_ASSERT(IsValidDuration(duration));
5592 } else {
5593 // Step 11.
5594 result = duration;
5597 // Steps 12-13.
5598 JSString* str = TemporalDurationToString(cx, result, precision.precision);
5599 if (!str) {
5600 return false;
5603 args.rval().setString(str);
5604 return true;
5608 * Temporal.Duration.prototype.toString ( [ options ] )
5610 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
5611 // Steps 1-2.
5612 CallArgs args = CallArgsFromVp(argc, vp);
5613 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
5617 * Temporal.Duration.prototype.toJSON ( )
5619 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
5620 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5622 // Steps 3-4.
5623 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5624 if (!str) {
5625 return false;
5628 args.rval().setString(str);
5629 return true;
5633 * Temporal.Duration.prototype.toJSON ( )
5635 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
5636 // Steps 1-2.
5637 CallArgs args = CallArgsFromVp(argc, vp);
5638 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
5642 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5644 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
5645 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
5647 // Steps 3-4.
5648 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
5649 if (!str) {
5650 return false;
5653 args.rval().setString(str);
5654 return true;
5658 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
5660 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
5661 // Steps 1-2.
5662 CallArgs args = CallArgsFromVp(argc, vp);
5663 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
5667 * Temporal.Duration.prototype.valueOf ( )
5669 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
5670 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
5671 "Duration", "primitive type");
5672 return false;
5675 const JSClass DurationObject::class_ = {
5676 "Temporal.Duration",
5677 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
5678 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
5679 JS_NULL_CLASS_OPS,
5680 &DurationObject::classSpec_,
5683 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
5685 static const JSFunctionSpec Duration_methods[] = {
5686 JS_FN("from", Duration_from, 1, 0),
5687 JS_FN("compare", Duration_compare, 2, 0),
5688 JS_FS_END,
5691 static const JSFunctionSpec Duration_prototype_methods[] = {
5692 JS_FN("with", Duration_with, 1, 0),
5693 JS_FN("negated", Duration_negated, 0, 0),
5694 JS_FN("abs", Duration_abs, 0, 0),
5695 JS_FN("add", Duration_add, 1, 0),
5696 JS_FN("subtract", Duration_subtract, 1, 0),
5697 JS_FN("round", Duration_round, 1, 0),
5698 JS_FN("total", Duration_total, 1, 0),
5699 JS_FN("toString", Duration_toString, 0, 0),
5700 JS_FN("toJSON", Duration_toJSON, 0, 0),
5701 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
5702 JS_FN("valueOf", Duration_valueOf, 0, 0),
5703 JS_FS_END,
5706 static const JSPropertySpec Duration_prototype_properties[] = {
5707 JS_PSG("years", Duration_years, 0),
5708 JS_PSG("months", Duration_months, 0),
5709 JS_PSG("weeks", Duration_weeks, 0),
5710 JS_PSG("days", Duration_days, 0),
5711 JS_PSG("hours", Duration_hours, 0),
5712 JS_PSG("minutes", Duration_minutes, 0),
5713 JS_PSG("seconds", Duration_seconds, 0),
5714 JS_PSG("milliseconds", Duration_milliseconds, 0),
5715 JS_PSG("microseconds", Duration_microseconds, 0),
5716 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
5717 JS_PSG("sign", Duration_sign, 0),
5718 JS_PSG("blank", Duration_blank, 0),
5719 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
5720 JS_PS_END,
5723 const ClassSpec DurationObject::classSpec_ = {
5724 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
5725 GenericCreatePrototype<DurationObject>,
5726 Duration_methods,
5727 nullptr,
5728 Duration_prototype_methods,
5729 Duration_prototype_properties,
5730 nullptr,
5731 ClassSpec::DontDefineConstructor,