Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / js / src / builtin / temporal / Duration.cpp
blobba6cf371b23bf5f3206d03fc5ccb7e9c5eeeb1de
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/StringBuilder.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 int32_t js::temporal::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 * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
169 * milliseconds, microseconds, nanoseconds )
171 int32_t js::temporal::DurationSign(const NormalizedDuration& duration) {
172 MOZ_ASSERT(IsValidDuration(duration));
174 if (int32_t sign = DurationSign(duration.date)) {
175 return sign;
177 return NormalizedTimeDurationSign(duration.time);
181 * Normalize a nanoseconds amount into a time duration.
183 static NormalizedTimeDuration NormalizeNanoseconds(const Int96& nanoseconds) {
184 // Split into seconds and nanoseconds.
185 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
187 return {seconds, nanos};
191 * Normalize a nanoseconds amount into a time duration. Return Nothing if the
192 * value is too large.
194 static mozilla::Maybe<NormalizedTimeDuration> NormalizeNanoseconds(
195 double nanoseconds) {
196 MOZ_ASSERT(IsInteger(nanoseconds));
198 if (auto int96 = Int96::fromInteger(nanoseconds)) {
199 // The number of normalized seconds must not exceed `2**53 - 1`.
200 constexpr auto limit =
201 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
203 if (int96->abs() < limit) {
204 return mozilla::Some(NormalizeNanoseconds(*int96));
207 return mozilla::Nothing();
211 * Normalize a microseconds amount into a time duration.
213 static NormalizedTimeDuration NormalizeMicroseconds(const Int96& microseconds) {
214 // Split into seconds and microseconds.
215 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
217 // Scale microseconds to nanoseconds.
218 int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));
220 return {seconds, nanos};
224 * Normalize a microseconds amount into a time duration. Return Nothing if the
225 * value is too large.
227 static mozilla::Maybe<NormalizedTimeDuration> NormalizeMicroseconds(
228 double microseconds) {
229 MOZ_ASSERT(IsInteger(microseconds));
231 if (auto int96 = Int96::fromInteger(microseconds)) {
232 // The number of normalized seconds must not exceed `2**53 - 1`.
233 constexpr auto limit =
234 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
236 if (int96->abs() < limit) {
237 return mozilla::Some(NormalizeMicroseconds(*int96));
240 return mozilla::Nothing();
244 * Normalize a duration into a time duration. Return Nothing if any duration
245 * value is too large.
247 static mozilla::Maybe<NormalizedTimeDuration> NormalizeSeconds(
248 const Duration& duration) {
249 do {
250 auto nanoseconds = NormalizeNanoseconds(duration.nanoseconds);
251 if (!nanoseconds) {
252 break;
254 MOZ_ASSERT(IsValidNormalizedTimeDuration(*nanoseconds));
256 auto microseconds = NormalizeMicroseconds(duration.microseconds);
257 if (!microseconds) {
258 break;
260 MOZ_ASSERT(IsValidNormalizedTimeDuration(*microseconds));
262 // Overflows for millis/seconds/minutes/hours/days always result in an
263 // invalid normalized time duration.
265 int64_t milliseconds;
266 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
267 break;
270 int64_t seconds;
271 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
272 break;
275 int64_t minutes;
276 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
277 break;
280 int64_t hours;
281 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
282 break;
285 int64_t days;
286 if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
287 break;
290 // Compute the overall amount of milliseconds.
291 mozilla::CheckedInt64 millis = days;
292 millis *= 24;
293 millis += hours;
294 millis *= 60;
295 millis += minutes;
296 millis *= 60;
297 millis += seconds;
298 millis *= 1000;
299 millis += milliseconds;
300 if (!millis.isValid()) {
301 break;
304 auto milli = NormalizedTimeDuration::fromMilliseconds(millis.value());
305 if (!IsValidNormalizedTimeDuration(milli)) {
306 break;
309 // Compute the overall time duration.
310 auto result = milli + *microseconds + *nanoseconds;
311 if (!IsValidNormalizedTimeDuration(result)) {
312 break;
315 return mozilla::Some(result);
316 } while (false);
318 return mozilla::Nothing();
322 * Normalize a days amount into a time duration. Return Nothing if the value is
323 * too large.
325 static mozilla::Maybe<NormalizedTimeDuration> NormalizeDays(int64_t days) {
326 do {
327 // Compute the overall amount of milliseconds.
328 auto millis =
329 mozilla::CheckedInt64(days) * ToMilliseconds(TemporalUnit::Day);
330 if (!millis.isValid()) {
331 break;
334 auto result = NormalizedTimeDuration::fromMilliseconds(millis.value());
335 if (!IsValidNormalizedTimeDuration(result)) {
336 break;
339 return mozilla::Some(result);
340 } while (false);
342 return mozilla::Nothing();
346 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
347 * nanoseconds )
349 static NormalizedTimeDuration NormalizeTimeDuration(
350 double hours, double minutes, double seconds, double milliseconds,
351 double microseconds, double nanoseconds) {
352 MOZ_ASSERT(IsInteger(hours));
353 MOZ_ASSERT(IsInteger(minutes));
354 MOZ_ASSERT(IsInteger(seconds));
355 MOZ_ASSERT(IsInteger(milliseconds));
356 MOZ_ASSERT(IsInteger(microseconds));
357 MOZ_ASSERT(IsInteger(nanoseconds));
359 // Steps 1-3.
360 mozilla::CheckedInt64 millis = int64_t(hours);
361 millis *= 60;
362 millis += int64_t(minutes);
363 millis *= 60;
364 millis += int64_t(seconds);
365 millis *= 1000;
366 millis += int64_t(milliseconds);
367 MOZ_ASSERT(millis.isValid());
369 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
371 // Step 4.
372 auto micros = Int96::fromInteger(microseconds);
373 MOZ_ASSERT(micros);
375 normalized += NormalizeMicroseconds(*micros);
377 // Step 5.
378 auto nanos = Int96::fromInteger(nanoseconds);
379 MOZ_ASSERT(nanos);
381 normalized += NormalizeNanoseconds(*nanos);
383 // Step 6.
384 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
386 // Step 7.
387 return normalized;
391 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
392 * nanoseconds )
394 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
395 int32_t hours, int32_t minutes, int32_t seconds, int32_t milliseconds,
396 int32_t microseconds, int32_t nanoseconds) {
397 // Steps 1-3.
398 mozilla::CheckedInt64 millis = int64_t(hours);
399 millis *= 60;
400 millis += int64_t(minutes);
401 millis *= 60;
402 millis += int64_t(seconds);
403 millis *= 1000;
404 millis += int64_t(milliseconds);
405 MOZ_ASSERT(millis.isValid());
407 auto normalized = NormalizedTimeDuration::fromMilliseconds(millis.value());
409 // Step 4.
410 normalized += NormalizeMicroseconds(Int96{microseconds});
412 // Step 5.
413 normalized += NormalizeNanoseconds(Int96{nanoseconds});
415 // Step 6.
416 MOZ_ASSERT(IsValidNormalizedTimeDuration(normalized));
418 // Step 7.
419 return normalized;
423 * NormalizeTimeDuration ( hours, minutes, seconds, milliseconds, microseconds,
424 * nanoseconds )
426 NormalizedTimeDuration js::temporal::NormalizeTimeDuration(
427 const Duration& duration) {
428 MOZ_ASSERT(IsValidDuration(duration));
430 return ::NormalizeTimeDuration(duration.hours, duration.minutes,
431 duration.seconds, duration.milliseconds,
432 duration.microseconds, duration.nanoseconds);
436 * AddNormalizedTimeDuration ( one, two )
438 static bool AddNormalizedTimeDuration(JSContext* cx,
439 const NormalizedTimeDuration& one,
440 const NormalizedTimeDuration& two,
441 NormalizedTimeDuration* result) {
442 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
443 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
445 // Step 1.
446 auto sum = one + two;
448 // Step 2.
449 if (!IsValidNormalizedTimeDuration(sum)) {
450 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
451 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
452 return false;
455 // Step 3.
456 *result = sum;
457 return true;
461 * SubtractNormalizedTimeDuration ( one, two )
463 static bool SubtractNormalizedTimeDuration(JSContext* cx,
464 const NormalizedTimeDuration& one,
465 const NormalizedTimeDuration& two,
466 NormalizedTimeDuration* result) {
467 MOZ_ASSERT(IsValidNormalizedTimeDuration(one));
468 MOZ_ASSERT(IsValidNormalizedTimeDuration(two));
470 // Step 1.
471 auto sum = one - two;
473 // Step 2.
474 if (!IsValidNormalizedTimeDuration(sum)) {
475 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
476 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
477 return false;
480 // Step 3.
481 *result = sum;
482 return true;
486 * Add24HourDaysToNormalizedTimeDuration ( d, days )
488 bool js::temporal::Add24HourDaysToNormalizedTimeDuration(
489 JSContext* cx, const NormalizedTimeDuration& d, int64_t days,
490 NormalizedTimeDuration* result) {
491 MOZ_ASSERT(IsValidNormalizedTimeDuration(d));
493 // Step 1.
494 auto normalizedDays = NormalizeDays(days);
495 if (!normalizedDays) {
496 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
497 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
498 return false;
501 // Step 2.
502 auto sum = d + *normalizedDays;
503 if (!IsValidNormalizedTimeDuration(sum)) {
504 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
505 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
506 return false;
509 // Step 3.
510 *result = sum;
511 return true;
515 * CombineDateAndNormalizedTimeDuration ( dateDurationRecord, norm )
517 bool js::temporal::CombineDateAndNormalizedTimeDuration(
518 JSContext* cx, const DateDuration& date, const NormalizedTimeDuration& time,
519 NormalizedDuration* result) {
520 MOZ_ASSERT(IsValidDuration(date));
521 MOZ_ASSERT(IsValidNormalizedTimeDuration(time));
523 // Step 1.
524 int32_t dateSign = DurationSign(date);
526 // Step 2.
527 int32_t timeSign = NormalizedTimeDurationSign(time);
529 // Step 3
530 if ((dateSign * timeSign) < 0) {
531 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
532 JSMSG_TEMPORAL_DURATION_COMBINE_INVALID_SIGN);
533 return false;
536 // Step 4.
537 *result = {date, time};
538 return true;
542 * NormalizedTimeDurationFromEpochNanosecondsDifference ( one, two )
544 NormalizedTimeDuration
545 js::temporal::NormalizedTimeDurationFromEpochNanosecondsDifference(
546 const Instant& one, const Instant& two) {
547 MOZ_ASSERT(IsValidEpochInstant(one));
548 MOZ_ASSERT(IsValidEpochInstant(two));
550 // Step 1.
551 auto result = one - two;
553 // Step 2.
554 MOZ_ASSERT(IsValidInstantSpan(result));
556 // Step 3.
557 return result.to<NormalizedTimeDuration>();
561 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
562 * milliseconds, microseconds, nanoseconds )
564 bool js::temporal::IsValidDuration(const Duration& duration) {
565 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
567 const auto& [years, months, weeks, days, hours, minutes, seconds,
568 milliseconds, microseconds, nanoseconds] = duration;
570 // Step 1.
571 int32_t sign = DurationSign(duration);
573 // Step 2.
574 for (auto v : {years, months, weeks, days, hours, minutes, seconds,
575 milliseconds, microseconds, nanoseconds}) {
576 // Step 2.a.
577 if (!std::isfinite(v)) {
578 return false;
581 // Step 2.b.
582 if (v < 0 && sign > 0) {
583 return false;
586 // Step 2.c.
587 if (v > 0 && sign < 0) {
588 return false;
592 // Step 3.
593 if (std::abs(years) >= double(int64_t(1) << 32)) {
594 return false;
597 // Step 4.
598 if (std::abs(months) >= double(int64_t(1) << 32)) {
599 return false;
602 // Step 5.
603 if (std::abs(weeks) >= double(int64_t(1) << 32)) {
604 return false;
607 // Steps 6-8.
608 if (!NormalizeSeconds(duration)) {
609 return false;
612 // Step 9.
613 return true;
616 #ifdef DEBUG
618 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
619 * milliseconds, microseconds, nanoseconds )
621 bool js::temporal::IsValidDuration(const DateDuration& duration) {
622 return IsValidDuration(duration.toDuration());
626 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
627 * milliseconds, microseconds, nanoseconds )
629 bool js::temporal::IsValidDuration(const NormalizedDuration& duration) {
630 if (!IsValidNormalizedTimeDuration(duration.time)) {
631 return false;
634 auto d = duration.date.toDuration();
635 auto [seconds, nanoseconds] = duration.time.denormalize();
636 d.seconds = double(seconds);
637 d.nanoseconds = double(nanoseconds);
639 return IsValidDuration(d);
641 #endif
643 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
644 const char* name, unsigned errorNumber) {
645 ToCStringBuf cbuf;
646 const char* numStr = NumberToCString(&cbuf, value);
648 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
649 numStr);
650 return false;
654 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
655 * milliseconds, microseconds, nanoseconds )
657 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
658 const Duration& duration) {
659 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
661 const auto& [years, months, weeks, days, hours, minutes, seconds,
662 milliseconds, microseconds, nanoseconds] = duration;
664 // Step 1.
665 int32_t sign = DurationSign(duration);
667 auto throwIfInvalid = [&](double v, const char* name) {
668 // Step 2.a.
669 if (!std::isfinite(v)) {
670 return ThrowInvalidDurationPart(
671 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
674 // Steps 2.b-c.
675 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
676 return ThrowInvalidDurationPart(cx, v, name,
677 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
680 return true;
683 auto throwIfTooLarge = [&](double v, const char* name) {
684 if (std::abs(v) >= double(int64_t(1) << 32)) {
685 return ThrowInvalidDurationPart(
686 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
688 return true;
691 // Step 2.
692 if (!throwIfInvalid(years, "years")) {
693 return false;
695 if (!throwIfInvalid(months, "months")) {
696 return false;
698 if (!throwIfInvalid(weeks, "weeks")) {
699 return false;
701 if (!throwIfInvalid(days, "days")) {
702 return false;
704 if (!throwIfInvalid(hours, "hours")) {
705 return false;
707 if (!throwIfInvalid(minutes, "minutes")) {
708 return false;
710 if (!throwIfInvalid(seconds, "seconds")) {
711 return false;
713 if (!throwIfInvalid(milliseconds, "milliseconds")) {
714 return false;
716 if (!throwIfInvalid(microseconds, "microseconds")) {
717 return false;
719 if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
720 return false;
723 // Step 3.
724 if (!throwIfTooLarge(years, "years")) {
725 return false;
728 // Step 4.
729 if (!throwIfTooLarge(months, "months")) {
730 return false;
733 // Step 5.
734 if (!throwIfTooLarge(weeks, "weeks")) {
735 return false;
738 // Steps 6-8.
739 if (!NormalizeSeconds(duration)) {
740 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
741 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
742 return false;
745 MOZ_ASSERT(IsValidDuration(duration));
747 // Step 9.
748 return true;
752 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
753 * milliseconds, microseconds, nanoseconds )
755 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
756 const DateDuration& duration) {
757 const auto& [years, months, weeks, days] = duration;
759 // Step 1.
760 int32_t sign = DurationSign(duration);
762 auto throwIfInvalid = [&](int64_t v, const char* name) {
763 // Step 2.a. (Not applicable)
765 // Steps 2.b-c.
766 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
767 return ThrowInvalidDurationPart(cx, double(v), name,
768 JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
771 return true;
774 auto throwIfTooLarge = [&](int64_t v, const char* name) {
775 if (std::abs(v) >= (int64_t(1) << 32)) {
776 return ThrowInvalidDurationPart(
777 cx, double(v), name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
779 return true;
782 // Step 2.
783 if (!throwIfInvalid(years, "years")) {
784 return false;
786 if (!throwIfInvalid(months, "months")) {
787 return false;
789 if (!throwIfInvalid(weeks, "weeks")) {
790 return false;
792 if (!throwIfInvalid(days, "days")) {
793 return false;
796 // Step 3.
797 if (!throwIfTooLarge(years, "years")) {
798 return false;
801 // Step 4.
802 if (!throwIfTooLarge(months, "months")) {
803 return false;
806 // Step 5.
807 if (!throwIfTooLarge(weeks, "weeks")) {
808 return false;
811 // Steps 6-8.
812 if (std::abs(days) > ((int64_t(1) << 53) / 86400)) {
813 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
814 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
815 return false;
818 MOZ_ASSERT(IsValidDuration(duration));
820 // Step 9.
821 return true;
825 * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
826 * seconds, milliseconds, microseconds )
828 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
829 MOZ_ASSERT(IsIntegerDuration(duration));
831 // Step 1.
832 if (duration.years != 0) {
833 return TemporalUnit::Year;
836 // Step 2.
837 if (duration.months != 0) {
838 return TemporalUnit::Month;
841 // Step 3.
842 if (duration.weeks != 0) {
843 return TemporalUnit::Week;
846 // Step 4.
847 if (duration.days != 0) {
848 return TemporalUnit::Day;
851 // Step 5.
852 if (duration.hours != 0) {
853 return TemporalUnit::Hour;
856 // Step 6.
857 if (duration.minutes != 0) {
858 return TemporalUnit::Minute;
861 // Step 7.
862 if (duration.seconds != 0) {
863 return TemporalUnit::Second;
866 // Step 8.
867 if (duration.milliseconds != 0) {
868 return TemporalUnit::Millisecond;
871 // Step 9.
872 if (duration.microseconds != 0) {
873 return TemporalUnit::Microsecond;
876 // Step 10.
877 return TemporalUnit::Nanosecond;
881 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
882 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
884 static DurationObject* CreateTemporalDuration(JSContext* cx,
885 const CallArgs& args,
886 const Duration& duration) {
887 const auto& [years, months, weeks, days, hours, minutes, seconds,
888 milliseconds, microseconds, nanoseconds] = duration;
890 // Step 1.
891 if (!ThrowIfInvalidDuration(cx, duration)) {
892 return nullptr;
895 // Steps 2-3.
896 Rooted<JSObject*> proto(cx);
897 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
898 return nullptr;
901 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
902 if (!object) {
903 return nullptr;
906 // Steps 4-13.
907 // Add zero to convert -0 to +0.
908 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
909 object->setFixedSlot(DurationObject::MONTHS_SLOT,
910 NumberValue(months + (+0.0)));
911 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
912 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
913 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
914 object->setFixedSlot(DurationObject::MINUTES_SLOT,
915 NumberValue(minutes + (+0.0)));
916 object->setFixedSlot(DurationObject::SECONDS_SLOT,
917 NumberValue(seconds + (+0.0)));
918 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
919 NumberValue(milliseconds + (+0.0)));
920 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
921 NumberValue(microseconds + (+0.0)));
922 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
923 NumberValue(nanoseconds + (+0.0)));
925 // Step 14.
926 return object;
930 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
931 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
933 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
934 const Duration& duration) {
935 const auto& [years, months, weeks, days, hours, minutes, seconds,
936 milliseconds, microseconds, nanoseconds] = duration;
938 MOZ_ASSERT(IsInteger(years));
939 MOZ_ASSERT(IsInteger(months));
940 MOZ_ASSERT(IsInteger(weeks));
941 MOZ_ASSERT(IsInteger(days));
942 MOZ_ASSERT(IsInteger(hours));
943 MOZ_ASSERT(IsInteger(minutes));
944 MOZ_ASSERT(IsInteger(seconds));
945 MOZ_ASSERT(IsInteger(milliseconds));
946 MOZ_ASSERT(IsInteger(microseconds));
947 MOZ_ASSERT(IsInteger(nanoseconds));
949 // Step 1.
950 if (!ThrowIfInvalidDuration(cx, duration)) {
951 return nullptr;
954 // Steps 2-3.
955 auto* object = NewBuiltinClassInstance<DurationObject>(cx);
956 if (!object) {
957 return nullptr;
960 // Steps 4-13.
961 // Add zero to convert -0 to +0.
962 object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
963 object->setFixedSlot(DurationObject::MONTHS_SLOT,
964 NumberValue(months + (+0.0)));
965 object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
966 object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
967 object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
968 object->setFixedSlot(DurationObject::MINUTES_SLOT,
969 NumberValue(minutes + (+0.0)));
970 object->setFixedSlot(DurationObject::SECONDS_SLOT,
971 NumberValue(seconds + (+0.0)));
972 object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
973 NumberValue(milliseconds + (+0.0)));
974 object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
975 NumberValue(microseconds + (+0.0)));
976 object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
977 NumberValue(nanoseconds + (+0.0)));
979 // Step 14.
980 return object;
984 * ToIntegerIfIntegral ( argument )
986 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
987 Handle<Value> argument, double* num) {
988 // Step 1.
989 double d;
990 if (!JS::ToNumber(cx, argument, &d)) {
991 return false;
994 // Step 2.
995 if (!js::IsInteger(d)) {
996 ToCStringBuf cbuf;
997 const char* numStr = NumberToCString(&cbuf, d);
999 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1000 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
1001 name);
1002 return false;
1005 // Step 3.
1006 *num = d;
1007 return true;
1011 * ToIntegerIfIntegral ( argument )
1013 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
1014 Handle<Value> argument, double* result) {
1015 // Step 1.
1016 double d;
1017 if (!JS::ToNumber(cx, argument, &d)) {
1018 return false;
1021 // Step 2.
1022 if (!js::IsInteger(d)) {
1023 if (auto nameStr = js::QuoteString(cx, name)) {
1024 ToCStringBuf cbuf;
1025 const char* numStr = NumberToCString(&cbuf, d);
1027 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1028 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
1029 nameStr.get());
1031 return false;
1034 // Step 3.
1035 *result = d;
1036 return true;
1040 * ToTemporalPartialDurationRecord ( temporalDurationLike )
1042 static bool ToTemporalPartialDurationRecord(
1043 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
1044 // Steps 1-3. (Not applicable in our implementation.)
1046 Rooted<Value> value(cx);
1047 bool any = false;
1049 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
1050 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
1051 &value)) {
1052 return false;
1055 if (!value.isUndefined()) {
1056 any = true;
1058 if (!ToIntegerIfIntegral(cx, name, value, num)) {
1059 return false;
1062 return true;
1065 // Steps 4-23.
1066 if (!getDurationProperty(cx->names().days, &result->days)) {
1067 return false;
1069 if (!getDurationProperty(cx->names().hours, &result->hours)) {
1070 return false;
1072 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
1073 return false;
1075 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
1076 return false;
1078 if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
1079 return false;
1081 if (!getDurationProperty(cx->names().months, &result->months)) {
1082 return false;
1084 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
1085 return false;
1087 if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
1088 return false;
1090 if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
1091 return false;
1093 if (!getDurationProperty(cx->names().years, &result->years)) {
1094 return false;
1097 // Step 24.
1098 if (!any) {
1099 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1100 JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
1101 return false;
1104 // Step 25.
1105 return true;
1109 * ToTemporalDurationRecord ( temporalDurationLike )
1111 bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
1112 Handle<Value> temporalDurationLike,
1113 Duration* result) {
1114 // Step 1.
1115 if (!temporalDurationLike.isObject()) {
1116 // Step 1.a.
1117 if (!temporalDurationLike.isString()) {
1118 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
1119 temporalDurationLike, nullptr, "not a string");
1120 return false;
1122 Rooted<JSString*> string(cx, temporalDurationLike.toString());
1124 // Step 1.b.
1125 return ParseTemporalDurationString(cx, string, result);
1128 Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
1130 // Step 2.
1131 if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
1132 *result = ToDuration(duration);
1133 return true;
1136 // Step 3.
1137 Duration duration = {};
1139 // Steps 4-14.
1140 if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
1141 return false;
1144 // Step 15.
1145 if (!ThrowIfInvalidDuration(cx, duration)) {
1146 return false;
1149 // Step 16.
1150 *result = duration;
1151 return true;
1155 * ToTemporalDuration ( item )
1157 Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
1158 Handle<Value> item) {
1159 // Step 1.
1160 if (item.isObject()) {
1161 JSObject* itemObj = &item.toObject();
1162 if (itemObj->canUnwrapAs<DurationObject>()) {
1163 return itemObj;
1167 // Step 2.
1168 Duration result;
1169 if (!ToTemporalDurationRecord(cx, item, &result)) {
1170 return nullptr;
1173 // Step 3.
1174 return CreateTemporalDuration(cx, result);
1178 * ToTemporalDuration ( item )
1180 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
1181 Duration* result) {
1182 auto obj = ToTemporalDuration(cx, item);
1183 if (!obj) {
1184 return false;
1187 *result = ToDuration(&obj.unwrap());
1188 return true;
1192 * DaysUntil ( earlier, later )
1194 int32_t js::temporal::DaysUntil(const PlainDate& earlier,
1195 const PlainDate& later) {
1196 MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
1197 MOZ_ASSERT(ISODateTimeWithinLimits(later));
1199 // Steps 1-2.
1200 int32_t epochDaysEarlier = MakeDay(earlier);
1201 MOZ_ASSERT(MinEpochDay <= epochDaysEarlier &&
1202 epochDaysEarlier <= MaxEpochDay);
1204 // Steps 3-4.
1205 int32_t epochDaysLater = MakeDay(later);
1206 MOZ_ASSERT(MinEpochDay <= epochDaysLater && epochDaysLater <= MaxEpochDay);
1208 // Step 5.
1209 return epochDaysLater - epochDaysEarlier;
1213 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1214 * microseconds, nanoseconds )
1216 static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
1217 int64_t minutes, int64_t seconds,
1218 int64_t milliseconds,
1219 int64_t microseconds,
1220 int64_t nanoseconds) {
1221 // Step 1.
1222 MOZ_ASSERT(IsValidDuration(
1223 {0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
1224 double(milliseconds), double(microseconds), double(nanoseconds)}));
1226 // All values are safe integers, so we don't need to convert to `double` and
1227 // back for the `ℝ(𝔽(x))` conversion.
1228 MOZ_ASSERT(IsSafeInteger(days));
1229 MOZ_ASSERT(IsSafeInteger(hours));
1230 MOZ_ASSERT(IsSafeInteger(minutes));
1231 MOZ_ASSERT(IsSafeInteger(seconds));
1232 MOZ_ASSERT(IsSafeInteger(milliseconds));
1233 MOZ_ASSERT(IsSafeInteger(microseconds));
1234 MOZ_ASSERT(IsSafeInteger(nanoseconds));
1236 // Step 2.
1237 return {
1238 days,
1239 hours,
1240 minutes,
1241 seconds,
1242 milliseconds,
1243 double(microseconds),
1244 double(nanoseconds),
1249 * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
1250 * microseconds, nanoseconds )
1252 static TimeDuration CreateTimeDurationRecord(int64_t milliseconds,
1253 const Int128& microseconds,
1254 const Int128& nanoseconds) {
1255 // Step 1.
1256 MOZ_ASSERT(IsValidDuration({0, 0, 0, 0, 0, 0, 0, double(milliseconds),
1257 double(microseconds), double(nanoseconds)}));
1259 // Step 2.
1260 return {
1261 0, 0, 0, 0, milliseconds, double(microseconds), double(nanoseconds),
1266 * BalanceTimeDuration ( norm, largestUnit )
1268 TimeDuration js::temporal::BalanceTimeDuration(
1269 const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
1270 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1271 MOZ_ASSERT(largestUnit <= TemporalUnit::Second,
1272 "fallible fractional seconds units");
1274 auto [seconds, nanoseconds] = duration.denormalize();
1276 // Step 1.
1277 int64_t days = 0;
1278 int64_t hours = 0;
1279 int64_t minutes = 0;
1280 int64_t milliseconds = 0;
1281 int64_t microseconds = 0;
1283 // Steps 2-3. (Not applicable in our implementation.)
1285 // We don't need to convert to positive numbers, because integer division
1286 // truncates and the %-operator has modulo semantics.
1288 // Steps 4-10.
1289 switch (largestUnit) {
1290 // Step 4.
1291 case TemporalUnit::Year:
1292 case TemporalUnit::Month:
1293 case TemporalUnit::Week:
1294 case TemporalUnit::Day: {
1295 // Step 4.a.
1296 microseconds = nanoseconds / 1000;
1298 // Step 4.b.
1299 nanoseconds = nanoseconds % 1000;
1301 // Step 4.c.
1302 milliseconds = microseconds / 1000;
1304 // Step 4.d.
1305 microseconds = microseconds % 1000;
1307 // Steps 4.e-f. (Not applicable)
1308 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1310 // Step 4.g.
1311 minutes = seconds / 60;
1313 // Step 4.h.
1314 seconds = seconds % 60;
1316 // Step 4.i.
1317 hours = minutes / 60;
1319 // Step 4.j.
1320 minutes = minutes % 60;
1322 // Step 4.k.
1323 days = hours / 24;
1325 // Step 4.l.
1326 hours = hours % 24;
1328 break;
1331 // Step 5.
1332 case TemporalUnit::Hour: {
1333 // Step 5.a.
1334 microseconds = nanoseconds / 1000;
1336 // Step 5.b.
1337 nanoseconds = nanoseconds % 1000;
1339 // Step 5.c.
1340 milliseconds = microseconds / 1000;
1342 // Step 5.d.
1343 microseconds = microseconds % 1000;
1345 // Steps 5.e-f. (Not applicable)
1346 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1348 // Step 5.g.
1349 minutes = seconds / 60;
1351 // Step 5.h.
1352 seconds = seconds % 60;
1354 // Step 5.i.
1355 hours = minutes / 60;
1357 // Step 5.j.
1358 minutes = minutes % 60;
1360 break;
1363 case TemporalUnit::Minute: {
1364 // Step 6.a.
1365 microseconds = nanoseconds / 1000;
1367 // Step 6.b.
1368 nanoseconds = nanoseconds % 1000;
1370 // Step 6.c.
1371 milliseconds = microseconds / 1000;
1373 // Step 6.d.
1374 microseconds = microseconds % 1000;
1376 // Steps 6.e-f. (Not applicable)
1377 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1379 // Step 6.g.
1380 minutes = seconds / 60;
1382 // Step 6.h.
1383 seconds = seconds % 60;
1385 break;
1388 // Step 7.
1389 case TemporalUnit::Second: {
1390 // Step 7.a.
1391 microseconds = nanoseconds / 1000;
1393 // Step 7.b.
1394 nanoseconds = nanoseconds % 1000;
1396 // Step 7.c.
1397 milliseconds = microseconds / 1000;
1399 // Step 7.d.
1400 microseconds = microseconds % 1000;
1402 // Steps 7.e-f. (Not applicable)
1403 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1405 break;
1408 case TemporalUnit::Millisecond:
1409 case TemporalUnit::Microsecond:
1410 case TemporalUnit::Nanosecond:
1411 case TemporalUnit::Auto:
1412 MOZ_CRASH("Unexpected temporal unit");
1415 // Step 11.
1416 return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
1417 microseconds, nanoseconds);
1421 * BalanceTimeDuration ( norm, largestUnit )
1423 bool js::temporal::BalanceTimeDuration(JSContext* cx,
1424 const NormalizedTimeDuration& duration,
1425 TemporalUnit largestUnit,
1426 TimeDuration* result) {
1427 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
1429 auto [seconds, nanoseconds] = duration.denormalize();
1431 // Steps 1-3. (Not applicable in our implementation.)
1433 // We don't need to convert to positive numbers, because integer division
1434 // truncates and the %-operator has modulo semantics.
1436 // Steps 4-10.
1437 switch (largestUnit) {
1438 // Steps 4-7.
1439 case TemporalUnit::Year:
1440 case TemporalUnit::Month:
1441 case TemporalUnit::Week:
1442 case TemporalUnit::Day:
1443 case TemporalUnit::Hour:
1444 case TemporalUnit::Minute:
1445 case TemporalUnit::Second:
1446 *result = BalanceTimeDuration(duration, largestUnit);
1447 return true;
1449 // Step 8.
1450 case TemporalUnit::Millisecond: {
1451 // The number of normalized seconds must not exceed `2**53 - 1`.
1452 constexpr auto limit =
1453 (int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second);
1455 // The largest possible milliseconds value whose double representation
1456 // doesn't exceed the normalized seconds limit.
1457 constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
1459 // Assert |max| is the maximum allowed milliseconds value.
1460 static_assert(double(max) < double(limit));
1461 static_assert(double(max + 1) >= double(limit));
1463 static_assert((NormalizedTimeDuration::max().seconds + 1) *
1464 ToMilliseconds(TemporalUnit::Second) <=
1465 INT64_MAX,
1466 "total number duration milliseconds fits into int64");
1468 // Step 8.a.
1469 int64_t microseconds = nanoseconds / 1000;
1471 // Step 8.b.
1472 nanoseconds = nanoseconds % 1000;
1474 // Step 8.c.
1475 int64_t milliseconds = microseconds / 1000;
1476 MOZ_ASSERT(std::abs(milliseconds) <= 999);
1478 // Step 8.d.
1479 microseconds = microseconds % 1000;
1481 auto millis =
1482 (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
1483 if (std::abs(millis) > max) {
1484 JS_ReportErrorNumberASCII(
1485 cx, GetErrorMessage, nullptr,
1486 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
1487 return false;
1490 // Step 11.
1491 *result = CreateTimeDurationRecord(millis, Int128{microseconds},
1492 Int128{nanoseconds});
1493 return true;
1496 // Step 9.
1497 case TemporalUnit::Microsecond: {
1498 // The number of normalized seconds must not exceed `2**53 - 1`.
1499 constexpr auto limit = Uint128{int64_t(1) << 53} *
1500 Uint128{ToMicroseconds(TemporalUnit::Second)};
1502 // The largest possible microseconds value whose double representation
1503 // doesn't exceed the normalized seconds limit.
1504 constexpr auto max =
1505 (Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff};
1506 static_assert(max < limit);
1508 // Assert |max| is the maximum allowed microseconds value.
1509 MOZ_ASSERT(double(max) < double(limit));
1510 MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
1512 // Step 9.a.
1513 int64_t microseconds = nanoseconds / 1000;
1514 MOZ_ASSERT(std::abs(microseconds) <= 999'999);
1516 // Step 9.b.
1517 nanoseconds = nanoseconds % 1000;
1519 auto micros =
1520 (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
1521 Int128{microseconds};
1522 if (micros.abs() > max) {
1523 JS_ReportErrorNumberASCII(
1524 cx, GetErrorMessage, nullptr,
1525 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
1526 return false;
1529 // Step 11.
1530 *result = CreateTimeDurationRecord(0, micros, Int128{nanoseconds});
1531 return true;
1534 // Step 10.
1535 case TemporalUnit::Nanosecond: {
1536 // The number of normalized seconds must not exceed `2**53 - 1`.
1537 constexpr auto limit = Uint128{int64_t(1) << 53} *
1538 Uint128{ToNanoseconds(TemporalUnit::Second)};
1540 // The largest possible nanoseconds value whose double representation
1541 // doesn't exceed the normalized seconds limit.
1542 constexpr auto max =
1543 (Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff};
1544 static_assert(max < limit);
1546 // Assert |max| is the maximum allowed nanoseconds value.
1547 MOZ_ASSERT(double(max) < double(limit));
1548 MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
1550 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
1552 auto nanos =
1553 (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
1554 Int128{nanoseconds};
1555 if (nanos.abs() > max) {
1556 JS_ReportErrorNumberASCII(
1557 cx, GetErrorMessage, nullptr,
1558 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
1559 return false;
1562 // Step 11.
1563 *result = CreateTimeDurationRecord(0, Int128{}, nanos);
1564 return true;
1567 case TemporalUnit::Auto:
1568 break;
1570 MOZ_CRASH("Unexpected temporal unit");
1574 * UnbalanceDateDurationRelative ( years, months, weeks, days, plainRelativeTo,
1575 * calendarRec )
1577 static bool UnbalanceDateDurationRelative(
1578 JSContext* cx, const DateDuration& duration,
1579 Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
1580 Handle<CalendarRecord> calendar, int64_t* result) {
1581 MOZ_ASSERT(IsValidDuration(duration));
1583 auto [years, months, weeks, days] = duration;
1585 // Step 1.
1586 if (years == 0 && months == 0 && weeks == 0) {
1587 *result = days;
1588 return true;
1591 // Step 2.
1592 MOZ_ASSERT(
1593 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1595 // Step 3.
1596 auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};
1598 // Step 4.
1599 auto later =
1600 CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
1601 if (!later) {
1602 return false;
1604 auto laterDate = ToPlainDate(&later.unwrap());
1606 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
1607 if (!unwrappedRelativeTo) {
1608 return false;
1610 auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
1612 // Step 5.
1613 int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
1615 // Step 6.
1616 *result = days + yearsMonthsWeeksInDay;
1617 return true;
1620 static bool NumberToStringBuilder(JSContext* cx, double num,
1621 JSStringBuilder& sb) {
1622 MOZ_ASSERT(IsInteger(num));
1623 MOZ_ASSERT(num >= 0);
1624 MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1626 ToCStringBuf cbuf;
1627 size_t length;
1628 const char* numStr = NumberToCString(&cbuf, num, &length);
1630 return sb.append(numStr, length);
1633 static Duration AbsoluteDuration(const Duration& duration) {
1634 return {
1635 std::abs(duration.years), std::abs(duration.months),
1636 std::abs(duration.weeks), std::abs(duration.days),
1637 std::abs(duration.hours), std::abs(duration.minutes),
1638 std::abs(duration.seconds), std::abs(duration.milliseconds),
1639 std::abs(duration.microseconds), std::abs(duration.nanoseconds),
1644 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
1646 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
1647 int32_t subSecondNanoseconds,
1648 Precision precision) {
1649 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
1650 MOZ_ASSERT(precision != Precision::Minute());
1652 // Steps 1-2.
1653 if (precision == Precision::Auto()) {
1654 // Step 1.a.
1655 if (subSecondNanoseconds == 0) {
1656 return true;
1659 // Step 3. (Reordered)
1660 if (!result.append('.')) {
1661 return false;
1664 // Steps 1.b-c.
1665 int32_t k = 100'000'000;
1666 do {
1667 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
1668 return false;
1670 subSecondNanoseconds %= k;
1671 k /= 10;
1672 } while (subSecondNanoseconds);
1673 } else {
1674 // Step 2.a.
1675 uint8_t p = precision.value();
1676 if (p == 0) {
1677 return true;
1680 // Step 3. (Reordered)
1681 if (!result.append('.')) {
1682 return false;
1685 // Steps 2.b-c.
1686 int32_t k = 100'000'000;
1687 for (uint8_t i = 0; i < precision.value(); i++) {
1688 if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
1689 return false;
1691 subSecondNanoseconds %= k;
1692 k /= 10;
1696 return true;
1700 * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
1701 * normSeconds, precision )
1703 static JSString* TemporalDurationToString(JSContext* cx,
1704 const Duration& duration,
1705 Precision precision) {
1706 MOZ_ASSERT(IsValidDuration(duration));
1707 MOZ_ASSERT(precision != Precision::Minute());
1709 // Fast path for zero durations.
1710 if (duration == Duration{} &&
1711 (precision == Precision::Auto() || precision.value() == 0)) {
1712 return NewStringCopyZ<CanGC>(cx, "PT0S");
1715 // Convert to absolute values up front. This is okay to do, because when the
1716 // duration is valid, all components have the same sign.
1717 const auto& [years, months, weeks, days, hours, minutes, seconds,
1718 milliseconds, microseconds, nanoseconds] =
1719 AbsoluteDuration(duration);
1721 // Years to seconds parts are all safe integers for valid durations.
1722 MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1723 MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1724 MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1725 MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1726 MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1727 MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1728 MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
1730 auto secondsDuration = NormalizeTimeDuration(0.0, 0.0, seconds, milliseconds,
1731 microseconds, nanoseconds);
1733 // Step 1.
1734 int32_t sign = DurationSign(duration);
1736 // Steps 2 and 7.
1737 JSStringBuilder result(cx);
1739 // Step 13. (Reordered)
1740 if (sign < 0) {
1741 if (!result.append('-')) {
1742 return nullptr;
1746 // Step 14. (Reordered)
1747 if (!result.append('P')) {
1748 return nullptr;
1751 // Step 3.
1752 if (years != 0) {
1753 if (!NumberToStringBuilder(cx, years, result)) {
1754 return nullptr;
1756 if (!result.append('Y')) {
1757 return nullptr;
1761 // Step 4.
1762 if (months != 0) {
1763 if (!NumberToStringBuilder(cx, months, result)) {
1764 return nullptr;
1766 if (!result.append('M')) {
1767 return nullptr;
1771 // Step 5.
1772 if (weeks != 0) {
1773 if (!NumberToStringBuilder(cx, weeks, result)) {
1774 return nullptr;
1776 if (!result.append('W')) {
1777 return nullptr;
1781 // Step 6.
1782 if (days != 0) {
1783 if (!NumberToStringBuilder(cx, days, result)) {
1784 return nullptr;
1786 if (!result.append('D')) {
1787 return nullptr;
1791 // Step 7. (Moved above)
1793 // Steps 10-11. (Reordered)
1794 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
1795 days == 0 && hours == 0 && minutes == 0;
1797 // Steps 8-9, 12, and 15.
1798 bool hasSecondsPart = (secondsDuration != NormalizedTimeDuration{}) ||
1799 zeroMinutesAndHigher || precision != Precision::Auto();
1800 if (hours != 0 || minutes != 0 || hasSecondsPart) {
1801 // Step 15. (Reordered)
1802 if (!result.append('T')) {
1803 return nullptr;
1806 // Step 8.
1807 if (hours != 0) {
1808 if (!NumberToStringBuilder(cx, hours, result)) {
1809 return nullptr;
1811 if (!result.append('H')) {
1812 return nullptr;
1816 // Step 9.
1817 if (minutes != 0) {
1818 if (!NumberToStringBuilder(cx, minutes, result)) {
1819 return nullptr;
1821 if (!result.append('M')) {
1822 return nullptr;
1826 // Step 12.
1827 if (hasSecondsPart) {
1828 // Step 12.a.
1829 if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
1830 return nullptr;
1833 // Step 12.b.
1834 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
1835 precision)) {
1836 return nullptr;
1839 // Step 12.c.
1840 if (!result.append('S')) {
1841 return nullptr;
1846 // Steps 13-15. (Moved above)
1848 // Step 16.
1849 return result.finishString();
1853 * GetTemporalRelativeToOption ( options )
1855 static bool GetTemporalRelativeToOption(
1856 JSContext* cx, Handle<JSObject*> options,
1857 MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
1858 MutableHandle<ZonedDateTime> zonedRelativeTo,
1859 MutableHandle<TimeZoneRecord> timeZoneRecord) {
1860 // Step 1.
1861 Rooted<Value> value(cx);
1862 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
1863 return false;
1866 // Step 2.
1867 if (value.isUndefined()) {
1868 plainRelativeTo.set(nullptr);
1869 zonedRelativeTo.set(ZonedDateTime{});
1870 timeZoneRecord.set(TimeZoneRecord{});
1871 return true;
1874 // Step 3.
1875 auto offsetBehaviour = OffsetBehaviour::Option;
1877 // Step 4.
1878 auto matchBehaviour = MatchBehaviour::MatchExactly;
1880 // Steps 5-6.
1881 PlainDateTime dateTime;
1882 Rooted<CalendarValue> calendar(cx);
1883 Rooted<TimeZoneValue> timeZone(cx);
1884 int64_t offsetNs;
1885 if (value.isObject()) {
1886 Rooted<JSObject*> obj(cx, &value.toObject());
1888 // Step 5.a.
1889 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
1890 auto instant = ToInstant(zonedDateTime);
1891 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
1892 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
1894 if (!timeZone.wrap(cx)) {
1895 return false;
1897 if (!calendar.wrap(cx)) {
1898 return false;
1901 // Step 5.a.i.
1902 Rooted<TimeZoneRecord> timeZoneRec(cx);
1903 if (!CreateTimeZoneMethodsRecord(
1904 cx, timeZone,
1906 TimeZoneMethod::GetOffsetNanosecondsFor,
1907 TimeZoneMethod::GetPossibleInstantsFor,
1909 &timeZoneRec)) {
1910 return false;
1913 // Step 5.a.ii.
1914 plainRelativeTo.set(nullptr);
1915 zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
1916 timeZoneRecord.set(timeZoneRec);
1917 return true;
1920 // Step 5.b.
1921 if (obj->canUnwrapAs<PlainDateObject>()) {
1922 plainRelativeTo.set(obj);
1923 zonedRelativeTo.set(ZonedDateTime{});
1924 timeZoneRecord.set(TimeZoneRecord{});
1925 return true;
1928 // Step 5.c.
1929 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
1930 auto plainDateTime = ToPlainDate(dateTime);
1932 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
1933 if (!calendar.wrap(cx)) {
1934 return false;
1937 // Step 5.c.i.
1938 auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
1939 if (!plainDate) {
1940 return false;
1943 // Step 5.c.ii.
1944 plainRelativeTo.set(plainDate);
1945 zonedRelativeTo.set(ZonedDateTime{});
1946 timeZoneRecord.set(TimeZoneRecord{});
1947 return true;
1950 // Step 5.d.
1951 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
1952 return false;
1955 // Step 5.e.
1956 Rooted<CalendarRecord> calendarRec(cx);
1957 if (!CreateCalendarMethodsRecord(cx, calendar,
1959 CalendarMethod::DateFromFields,
1960 CalendarMethod::Fields,
1962 &calendarRec)) {
1963 return false;
1966 // Step 5.f.
1967 Rooted<PlainObject*> fields(
1968 cx, PrepareCalendarFields(cx, calendarRec, obj,
1970 CalendarField::Day,
1971 CalendarField::Month,
1972 CalendarField::MonthCode,
1973 CalendarField::Year,
1976 TemporalField::Hour,
1977 TemporalField::Microsecond,
1978 TemporalField::Millisecond,
1979 TemporalField::Minute,
1980 TemporalField::Nanosecond,
1981 TemporalField::Offset,
1982 TemporalField::Second,
1983 TemporalField::TimeZone,
1984 }));
1985 if (!fields) {
1986 return false;
1989 // Step 5.g.
1990 Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
1991 if (!dateOptions) {
1992 return false;
1995 // Step 5.h.
1996 Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
1997 if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
1998 return false;
2001 // Step 5.i.
2002 if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
2003 &dateTime)) {
2004 return false;
2007 // Step 5.j.
2008 Rooted<Value> offset(cx);
2009 if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
2010 return false;
2013 // Step 5.k.
2014 Rooted<Value> timeZoneValue(cx);
2015 if (!GetProperty(cx, fields, fields, cx->names().timeZone,
2016 &timeZoneValue)) {
2017 return false;
2020 // Step 5.l.
2021 if (!timeZoneValue.isUndefined()) {
2022 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
2023 return false;
2027 // Step 5.m.
2028 if (offset.isUndefined()) {
2029 offsetBehaviour = OffsetBehaviour::Wall;
2032 // Steps 8-9.
2033 if (timeZone) {
2034 if (offsetBehaviour == OffsetBehaviour::Option) {
2035 MOZ_ASSERT(!offset.isUndefined());
2036 MOZ_ASSERT(offset.isString());
2038 // Step 8.a.
2039 Rooted<JSString*> offsetString(cx, offset.toString());
2040 if (!offsetString) {
2041 return false;
2044 // Step 8.b.
2045 if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
2046 return false;
2048 } else {
2049 // Step 9.
2050 offsetNs = 0;
2053 } else {
2054 // Step 6.a.
2055 if (!value.isString()) {
2056 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
2057 nullptr, "not a string");
2058 return false;
2060 Rooted<JSString*> string(cx, value.toString());
2062 // Step 6.b.
2063 bool isUTC;
2064 bool hasOffset;
2065 int64_t timeZoneOffset;
2066 Rooted<ParsedTimeZone> timeZoneAnnotation(cx);
2067 Rooted<JSString*> calendarString(cx);
2068 if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
2069 &hasOffset, &timeZoneOffset,
2070 &timeZoneAnnotation, &calendarString)) {
2071 return false;
2074 // Step 6.c. (Not applicable in our implementation.)
2076 // Steps 6.e-f.
2077 if (timeZoneAnnotation) {
2078 // Step 6.f.i.
2079 if (!ToTemporalTimeZone(cx, timeZoneAnnotation, &timeZone)) {
2080 return false;
2083 // Steps 6.f.ii-iii.
2084 if (isUTC) {
2085 offsetBehaviour = OffsetBehaviour::Exact;
2086 } else if (!hasOffset) {
2087 offsetBehaviour = OffsetBehaviour::Wall;
2090 // Step 6.f.iv.
2091 matchBehaviour = MatchBehaviour::MatchMinutes;
2092 } else {
2093 MOZ_ASSERT(!timeZone);
2096 // Steps 6.g-j.
2097 if (calendarString) {
2098 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
2099 return false;
2101 } else {
2102 calendar.set(CalendarValue(CalendarId::ISO8601));
2105 // Steps 8-9.
2106 if (timeZone) {
2107 if (offsetBehaviour == OffsetBehaviour::Option) {
2108 MOZ_ASSERT(hasOffset);
2110 // Step 8.a.
2111 offsetNs = timeZoneOffset;
2112 } else {
2113 // Step 9.
2114 offsetNs = 0;
2119 // Step 7.
2120 if (!timeZone) {
2121 // Step 7.a.
2122 auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
2123 if (!plainDate) {
2124 return false;
2127 plainRelativeTo.set(plainDate);
2128 zonedRelativeTo.set(ZonedDateTime{});
2129 timeZoneRecord.set(TimeZoneRecord{});
2130 return true;
2133 // Steps 8-9. (Moved above)
2135 // Step 10.
2136 Rooted<TimeZoneRecord> timeZoneRec(cx);
2137 if (!CreateTimeZoneMethodsRecord(cx, timeZone,
2139 TimeZoneMethod::GetOffsetNanosecondsFor,
2140 TimeZoneMethod::GetPossibleInstantsFor,
2142 &timeZoneRec)) {
2143 return false;
2146 // Step 11.
2147 Instant epochNanoseconds;
2148 if (!InterpretISODateTimeOffset(
2149 cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
2150 TemporalDisambiguation::Compatible, TemporalOffset::Reject,
2151 matchBehaviour, &epochNanoseconds)) {
2152 return false;
2154 MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
2156 // Step 12.
2157 plainRelativeTo.set(nullptr);
2158 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
2159 timeZoneRecord.set(timeZoneRec);
2160 return true;
2164 * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
2165 * methods )
2167 static bool CreateCalendarMethodsRecordFromRelativeTo(
2168 JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
2169 Handle<ZonedDateTime> zonedRelativeTo,
2170 mozilla::EnumSet<CalendarMethod> methods,
2171 MutableHandle<CalendarRecord> result) {
2172 // Step 1.
2173 if (zonedRelativeTo) {
2174 return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
2175 result);
2178 // Step 2.
2179 if (plainRelativeTo) {
2180 auto* unwrapped = plainRelativeTo.unwrap(cx);
2181 if (!unwrapped) {
2182 return false;
2185 Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
2186 if (!calendar.wrap(cx)) {
2187 return false;
2190 return CreateCalendarMethodsRecord(cx, calendar, methods, result);
2193 // Step 3.
2194 return true;
2198 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
2200 static NormalizedTimeDuration RoundNormalizedTimeDurationToIncrement(
2201 const NormalizedTimeDuration& duration, const TemporalUnit unit,
2202 Increment increment, TemporalRoundingMode roundingMode) {
2203 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
2204 MOZ_ASSERT(unit >= TemporalUnit::Day);
2205 MOZ_ASSERT_IF(unit >= TemporalUnit::Hour,
2206 increment <= MaximumTemporalDurationRoundingIncrement(unit));
2208 auto divisor = Int128{ToNanoseconds(unit)} * Int128{increment.value()};
2209 MOZ_ASSERT(divisor > Int128{0});
2210 MOZ_ASSERT_IF(unit >= TemporalUnit::Hour,
2211 divisor <= Int128{ToNanoseconds(TemporalUnit::Day)});
2213 auto totalNanoseconds = duration.toNanoseconds();
2214 auto rounded =
2215 RoundNumberToIncrement(totalNanoseconds, divisor, roundingMode);
2216 return NormalizedTimeDuration::fromNanoseconds(rounded);
2220 * RoundNormalizedTimeDurationToIncrement ( d, increment, roundingMode )
2222 static bool RoundNormalizedTimeDurationToIncrement(
2223 JSContext* cx, const NormalizedTimeDuration& duration,
2224 const TemporalUnit unit, Increment increment,
2225 TemporalRoundingMode roundingMode, NormalizedTimeDuration* result) {
2226 // Step 1.
2227 auto rounded = RoundNormalizedTimeDurationToIncrement(
2228 duration, unit, increment, roundingMode);
2230 // Step 2.
2231 if (!IsValidNormalizedTimeDuration(rounded)) {
2232 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2233 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
2234 return false;
2237 // Step 3.
2238 *result = rounded;
2239 return true;
2243 * DivideNormalizedTimeDuration ( d, divisor )
2245 double js::temporal::DivideNormalizedTimeDuration(
2246 const NormalizedTimeDuration& duration, TemporalUnit unit) {
2247 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
2248 MOZ_ASSERT(unit >= TemporalUnit::Day);
2250 auto numerator = duration.toNanoseconds();
2251 auto denominator = Int128{ToNanoseconds(unit)};
2252 return FractionToDouble(numerator, denominator);
2255 enum class ComputeRemainder : bool { No, Yes };
2257 #ifdef DEBUG
2258 // Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
2259 static constexpr int64_t MaxDurationDays = (int64_t(1) << 53) / (24 * 60 * 60);
2260 #endif
2262 struct FractionalDays final {
2263 int64_t days = 0;
2264 int64_t time = 0;
2266 explicit FractionalDays(const NormalizedDuration& duration) {
2267 MOZ_ASSERT(IsValidDuration(duration));
2269 auto [seconds, nanoseconds] = duration.time.denormalize();
2271 int64_t days = seconds / ToSeconds(TemporalUnit::Day);
2272 seconds = seconds % ToSeconds(TemporalUnit::Day);
2274 int64_t time = seconds * ToNanoseconds(TemporalUnit::Second) + nanoseconds;
2275 MOZ_ASSERT(std::abs(time) < ToNanoseconds(TemporalUnit::Day));
2277 days += duration.date.days;
2278 MOZ_ASSERT(std::abs(days) <= MaxDurationDays);
2280 this->days = days;
2281 this->time = time;
2285 struct RoundedDays final {
2286 int64_t rounded = 0;
2287 double total = 0;
2290 static RoundedDays RoundNumberToIncrement(const FractionalDays& fractionalDays,
2291 Increment increment,
2292 TemporalRoundingMode roundingMode,
2293 ComputeRemainder computeRemainder) {
2294 MOZ_ASSERT(std::abs(fractionalDays.days) <= MaxDurationDays);
2295 MOZ_ASSERT(std::abs(fractionalDays.time) < ToNanoseconds(TemporalUnit::Day));
2296 MOZ_ASSERT(increment <= Increment::max());
2298 constexpr int64_t dayLength = ToNanoseconds(TemporalUnit::Day);
2300 // Fast-path when no time components are present. Multiplying and later
2301 // dividing by |dayLength| cancel each other out.
2302 if (fractionalDays.time == 0) {
2303 int64_t totalDays = fractionalDays.days;
2305 if (computeRemainder == ComputeRemainder::Yes) {
2306 constexpr int64_t rounded = 0;
2307 double total = FractionToDouble(totalDays, 1);
2308 return {rounded, total};
2311 auto rounded =
2312 RoundNumberToIncrement(totalDays, 1, increment, roundingMode);
2313 MOZ_ASSERT(Int128{INT64_MIN} <= rounded && rounded <= Int128{INT64_MAX},
2314 "rounded days fits in int64");
2315 constexpr double total = 0;
2316 return {int64_t(rounded), total};
2319 // Fast-path when |totalNanoseconds| fits into int64.
2320 do {
2321 auto totalNanoseconds =
2322 mozilla::CheckedInt64(dayLength) * fractionalDays.days;
2323 totalNanoseconds += fractionalDays.time;
2324 if (!totalNanoseconds.isValid()) {
2325 break;
2328 if (computeRemainder == ComputeRemainder::Yes) {
2329 constexpr int64_t rounded = 0;
2330 double total = FractionToDouble(totalNanoseconds.value(), dayLength);
2331 return {rounded, total};
2334 auto rounded = RoundNumberToIncrement(totalNanoseconds.value(), dayLength,
2335 increment, roundingMode);
2336 MOZ_ASSERT(Int128{INT64_MIN} <= rounded && rounded <= Int128{INT64_MAX},
2337 "rounded days fits in int64");
2338 constexpr double total = 0;
2339 return {int64_t(rounded), total};
2340 } while (false);
2342 auto totalNanoseconds = Int128{dayLength} * Int128{fractionalDays.days};
2343 totalNanoseconds += Int128{fractionalDays.time};
2345 if (computeRemainder == ComputeRemainder::Yes) {
2346 constexpr int64_t rounded = 0;
2347 double total = FractionToDouble(totalNanoseconds, Int128{dayLength});
2348 return {rounded, total};
2351 auto rounded = RoundNumberToIncrement(totalNanoseconds, Int128{dayLength},
2352 increment, roundingMode);
2353 MOZ_ASSERT(Int128{INT64_MIN} <= rounded && rounded <= Int128{INT64_MAX},
2354 "rounded days fits in int64");
2355 constexpr double total = 0;
2356 return {int64_t(rounded), total};
2359 struct RoundedDuration final {
2360 NormalizedDuration duration;
2361 double total = 0;
2365 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2367 static RoundedDuration RoundTimeDuration(const NormalizedDuration& duration,
2368 Increment increment, TemporalUnit unit,
2369 TemporalRoundingMode roundingMode,
2370 ComputeRemainder computeRemainder) {
2371 MOZ_ASSERT(IsValidDuration(duration));
2372 MOZ_ASSERT(unit > TemporalUnit::Day);
2374 // The remainder is only needed when called from |Duration_total|. And `total`
2375 // always passes |increment=1| and |roundingMode=trunc|.
2376 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
2377 increment == Increment{1});
2378 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
2379 roundingMode == TemporalRoundingMode::Trunc);
2381 // Step 1.
2382 MOZ_ASSERT(unit > TemporalUnit::Day);
2384 // Step 2. (Not applicable)
2386 // Steps 3.a-d.
2387 NormalizedTimeDuration time;
2388 double total = 0;
2389 if (computeRemainder == ComputeRemainder::No) {
2390 time = RoundNormalizedTimeDurationToIncrement(duration.time, unit,
2391 increment, roundingMode);
2392 } else {
2393 total = DivideNormalizedTimeDuration(duration.time, unit);
2396 // Step 4.
2397 return {NormalizedDuration{duration.date, time}, total};
2401 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2403 static bool RoundTimeDuration(JSContext* cx, const NormalizedDuration& duration,
2404 Increment increment, TemporalUnit unit,
2405 TemporalRoundingMode roundingMode,
2406 ComputeRemainder computeRemainder,
2407 RoundedDuration* result) {
2408 MOZ_ASSERT(IsValidDuration(duration));
2410 // The remainder is only needed when called from |Duration_total|. And `total`
2411 // always passes |increment=1| and |roundingMode=trunc|.
2412 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
2413 increment == Increment{1});
2414 MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
2415 roundingMode == TemporalRoundingMode::Trunc);
2417 // Step 1.
2418 MOZ_ASSERT(unit >= TemporalUnit::Day);
2420 // Steps 2-3.
2421 if (unit == TemporalUnit::Day) {
2422 // Step 2.a.
2423 auto fractionalDays = FractionalDays{duration};
2425 // Steps 2.b-c.
2426 auto [days, total] = RoundNumberToIncrement(fractionalDays, increment,
2427 roundingMode, computeRemainder);
2429 // Step 2.d
2430 constexpr auto time = NormalizedTimeDuration{};
2432 // Step 4.
2433 auto date = DateDuration{0, 0, 0, days};
2434 if (!ThrowIfInvalidDuration(cx, date)) {
2435 return false;
2438 auto normalized = NormalizedDuration{date, time};
2439 MOZ_ASSERT(IsValidDuration(normalized));
2441 *result = {normalized, total};
2442 return true;
2445 // Steps 3.a-d.
2446 auto rounded = RoundTimeDuration(duration, increment, unit, roundingMode,
2447 computeRemainder);
2448 if (!IsValidNormalizedTimeDuration(rounded.duration.time)) {
2449 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2450 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
2451 return false;
2453 MOZ_ASSERT(IsValidDuration(rounded.duration));
2455 // Step 4.
2456 *result = rounded;
2457 return true;
2461 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2463 static bool RoundTimeDuration(JSContext* cx,
2464 const NormalizedTimeDuration& duration,
2465 Increment increment, TemporalUnit unit,
2466 TemporalRoundingMode roundingMode,
2467 NormalizedTimeDuration* result) {
2468 auto normalized = NormalizedDuration{{}, duration};
2470 RoundedDuration rounded;
2471 if (!RoundTimeDuration(cx, normalized, increment, unit, roundingMode,
2472 ComputeRemainder::No, &rounded)) {
2473 return false;
2475 *result = rounded.duration.time;
2476 return true;
2480 * RoundTimeDuration ( days, norm, increment, unit, roundingMode )
2482 NormalizedTimeDuration js::temporal::RoundTimeDuration(
2483 const NormalizedTimeDuration& duration, Increment increment,
2484 TemporalUnit unit, TemporalRoundingMode roundingMode) {
2485 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
2486 MOZ_ASSERT(unit > TemporalUnit::Day);
2488 auto normalized = NormalizedDuration{{}, duration};
2489 auto result = ::RoundTimeDuration(normalized, increment, unit, roundingMode,
2490 ComputeRemainder::No);
2491 MOZ_ASSERT(IsValidNormalizedTimeDuration(result.duration.time));
2493 return result.duration.time;
2496 enum class UnsignedRoundingMode {
2497 Zero,
2498 Infinity,
2499 HalfZero,
2500 HalfInfinity,
2501 HalfEven
2505 * GetUnsignedRoundingMode ( roundingMode, sign )
2507 static UnsignedRoundingMode GetUnsignedRoundingMode(
2508 TemporalRoundingMode roundingMode, bool isNegative) {
2509 switch (roundingMode) {
2510 case TemporalRoundingMode::Ceil:
2511 return isNegative ? UnsignedRoundingMode::Zero
2512 : UnsignedRoundingMode::Infinity;
2513 case TemporalRoundingMode::Floor:
2514 return isNegative ? UnsignedRoundingMode::Infinity
2515 : UnsignedRoundingMode::Zero;
2516 case TemporalRoundingMode::Expand:
2517 return UnsignedRoundingMode::Infinity;
2518 case TemporalRoundingMode::Trunc:
2519 return UnsignedRoundingMode::Zero;
2520 case TemporalRoundingMode::HalfCeil:
2521 return isNegative ? UnsignedRoundingMode::HalfZero
2522 : UnsignedRoundingMode::HalfInfinity;
2523 case TemporalRoundingMode::HalfFloor:
2524 return isNegative ? UnsignedRoundingMode::HalfInfinity
2525 : UnsignedRoundingMode::HalfZero;
2526 case TemporalRoundingMode::HalfExpand:
2527 return UnsignedRoundingMode::HalfInfinity;
2528 case TemporalRoundingMode::HalfTrunc:
2529 return UnsignedRoundingMode::HalfZero;
2530 case TemporalRoundingMode::HalfEven:
2531 return UnsignedRoundingMode::HalfEven;
2533 MOZ_CRASH("invalid rounding mode");
2536 struct DurationNudge {
2537 NormalizedDuration duration;
2538 Instant epochNs;
2539 double total = 0;
2540 bool didExpandCalendarUnit = false;
2544 * NudgeToCalendarUnit ( sign, duration, destEpochNs, dateTime, calendarRec,
2545 * timeZoneRec, increment, unit, roundingMode )
2547 static bool NudgeToCalendarUnit(
2548 JSContext* cx, const NormalizedDuration& duration,
2549 const Instant& destEpochNs, const PlainDateTime& dateTime,
2550 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
2551 Increment increment, TemporalUnit unit, TemporalRoundingMode roundingMode,
2552 DurationNudge* result) {
2553 MOZ_ASSERT(IsValidDuration(duration));
2554 MOZ_ASSERT(IsValidEpochInstant(destEpochNs));
2555 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
2556 MOZ_ASSERT(unit <= TemporalUnit::Day);
2558 int32_t sign = DurationSign(duration) < 0 ? -1 : 1;
2560 // Steps 1-4.
2561 int64_t r1;
2562 int64_t r2;
2563 DateDuration startDuration;
2564 DateDuration endDuration;
2565 if (unit == TemporalUnit::Year) {
2566 // Step 1.a.
2567 int64_t years = RoundNumberToIncrement(duration.date.years, increment,
2568 TemporalRoundingMode::Trunc);
2570 // Step 1.b.
2571 r1 = years;
2573 // Step 1.c.
2574 r2 = years + int64_t(increment.value()) * sign;
2576 // Step 1.d.
2577 startDuration = {r1};
2579 // Step 1.e.
2580 endDuration = {r2};
2581 } else if (unit == TemporalUnit::Month) {
2582 // Step 2.a.
2583 int64_t months = RoundNumberToIncrement(duration.date.months, increment,
2584 TemporalRoundingMode::Trunc);
2586 // Step 2.b.
2587 r1 = months;
2589 // Step 2.c.
2590 r2 = months + int64_t(increment.value()) * sign;
2592 // Step 2.d.
2593 startDuration = {duration.date.years, r1};
2595 // Step 2.e.
2596 endDuration = {duration.date.years, r2};
2597 } else if (unit == TemporalUnit::Week) {
2598 // FIXME: spec bug - CreateTemporalDate is fallible. Also possibly incorrect
2599 // to call BalanceISODate. Just use AddDate for now.
2600 // https://github.com/tc39/proposal-temporal/issues/2881
2602 // Steps 3.a and 3.c.
2603 PlainDate weeksStart;
2604 if (!AddDate(cx, calendar, dateTime.date,
2605 {duration.date.years, duration.date.months}, &weeksStart)) {
2606 return false;
2609 // Steps 3.b and 3.d.
2610 PlainDate weeksEnd;
2611 if (!AddDate(
2612 cx, calendar, dateTime.date,
2613 {duration.date.years, duration.date.months, 0, duration.date.days},
2614 &weeksEnd)) {
2615 return false;
2618 // Steps 3.e-g.
2619 DateDuration untilResult;
2620 if (!DifferenceDate(cx, calendar, weeksStart, weeksEnd, TemporalUnit::Week,
2621 &untilResult)) {
2622 return false;
2625 // Step 3.h.
2626 int64_t weeks =
2627 RoundNumberToIncrement(duration.date.weeks + untilResult.weeks,
2628 increment, TemporalRoundingMode::Trunc);
2630 // Step 3.i.
2631 r1 = weeks;
2633 // Step 3.j.
2634 r2 = weeks + int64_t(increment.value()) * sign;
2636 // Step 3.k.
2637 startDuration = {duration.date.years, duration.date.months, r1};
2639 // Step 3.l.
2640 endDuration = {duration.date.years, duration.date.months, r2};
2641 } else {
2642 // Step 4.a.
2643 MOZ_ASSERT(unit == TemporalUnit::Day);
2645 // Step 4.b.
2646 int64_t days = RoundNumberToIncrement(duration.date.days, increment,
2647 TemporalRoundingMode::Trunc);
2649 // Step 4.c.
2650 r1 = days;
2652 // Step 4.d.
2653 r2 = days + int64_t(increment.value()) * sign;
2655 // Step 4.e.
2656 startDuration = {duration.date.years, duration.date.months,
2657 duration.date.weeks, r1};
2659 // Step 4.f.
2660 endDuration = {duration.date.years, duration.date.months,
2661 duration.date.weeks, r2};
2663 MOZ_ASSERT_IF(sign > 0, r1 >= 0 && r1 < r2);
2664 MOZ_ASSERT_IF(sign < 0, r1 <= 0 && r1 > r2);
2666 // Step 5.
2667 PlainDate start;
2668 if (!AddDate(cx, calendar, dateTime.date, startDuration, &start)) {
2669 return false;
2672 // Step 6.
2673 PlainDate end;
2674 if (!AddDate(cx, calendar, dateTime.date, endDuration, &end)) {
2675 return false;
2678 // Steps 7-8.
2679 Instant startEpochNs;
2680 Instant endEpochNs;
2681 if (!timeZone.receiver()) {
2682 // Step 7.a.
2683 startEpochNs = GetUTCEpochNanoseconds({start, dateTime.time});
2685 // Step 7.b.
2686 endEpochNs = GetUTCEpochNanoseconds({end, dateTime.time});
2687 } else {
2688 // Step 8.a.
2689 Rooted<PlainDateTimeWithCalendar> startDateTime(
2691 PlainDateTimeWithCalendar{{start, dateTime.time}, calendar.receiver()});
2693 // Steps 8.b-c.
2694 if (!GetInstantFor(cx, timeZone, startDateTime,
2695 TemporalDisambiguation::Compatible, &startEpochNs)) {
2696 return false;
2699 // Step 8.d.
2700 Rooted<PlainDateTimeWithCalendar> endDateTime(
2702 PlainDateTimeWithCalendar{{end, dateTime.time}, calendar.receiver()});
2704 // Steps 8.e-f.
2705 if (!GetInstantFor(cx, timeZone, endDateTime,
2706 TemporalDisambiguation::Compatible, &endEpochNs)) {
2707 return false;
2711 if (sign > 0) {
2712 if (startEpochNs > destEpochNs || destEpochNs >= endEpochNs) {
2713 JS_ReportErrorNumberASCII(
2714 cx, GetErrorMessage, nullptr,
2715 JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
2716 return false;
2718 MOZ_ASSERT(startEpochNs <= destEpochNs && destEpochNs < endEpochNs);
2719 } else {
2720 if (endEpochNs >= destEpochNs || destEpochNs > startEpochNs) {
2721 JS_ReportErrorNumberASCII(
2722 cx, GetErrorMessage, nullptr,
2723 JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
2724 return false;
2726 MOZ_ASSERT(endEpochNs < destEpochNs && destEpochNs <= startEpochNs);
2728 MOZ_ASSERT(startEpochNs != endEpochNs);
2730 // Steps 10-11.
2731 auto unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, sign < 0);
2733 // Step 12.
2734 auto numerator = (destEpochNs - startEpochNs).toNanoseconds();
2735 auto denominator = (endEpochNs - startEpochNs).toNanoseconds();
2736 MOZ_ASSERT(denominator != Int128{0});
2737 MOZ_ASSERT(numerator.abs() < denominator.abs());
2738 MOZ_ASSERT_IF(denominator > Int128{0}, numerator >= Int128{0});
2739 MOZ_ASSERT_IF(denominator < Int128{0}, numerator <= Int128{0});
2741 // Ensure |numerator| and |denominator| are both non-negative to simplify the
2742 // following computations.
2743 if (denominator < Int128{0}) {
2744 numerator = -numerator;
2745 denominator = -denominator;
2748 // |total| must only be computed when called from Duration.prototype.total,
2749 // which always passes "trunc" rounding mode with an increment of one.
2750 double total = mozilla::UnspecifiedNaN<double>();
2751 if (roundingMode == TemporalRoundingMode::Trunc &&
2752 increment == Increment{1}) {
2753 // total = r1 + progress × increment × sign
2754 // = r1 + (numerator / denominator) × increment × sign
2755 // = r1 + (numerator × increment × sign) / denominator
2756 // = (r1 × denominator + numerator × increment × sign) / denominator
2758 // Computing `n` can't overflow, because:
2759 // - For years, months, and weeks, `abs(r1) ≤ 2^32`.
2760 // - For days, `abs(r1) < ⌈(2^53) / (24 * 60 * 60)⌉`.
2761 // - `denominator` and `numerator` are below-or-equal `2 × 8.64 × 10^21`.
2762 // - And finally `increment ≤ 10^9`.
2763 auto n = Int128{r1} * denominator + numerator * Int128{sign};
2764 total = FractionToDouble(n, denominator);
2767 // Step 15. (Inlined ApplyUnsignedRoundingMode)
2769 // clang-format off
2771 // ApplyUnsignedRoundingMode, steps 1-16.
2773 // `total = r1` iff `progress = 0`. And `progress = 0` iff `numerator = 0`.
2775 // d1 = total - r1
2776 // = (r1 × denominator + numerator × increment × sign) / denominator - r1
2777 // = (numerator × increment × sign) / denominator
2779 // d2 = r2 - total
2780 // = r1 + increment - (r1 × denominator + numerator × increment × sign) / denominator
2781 // = (increment × denominator - numerator × increment × sign) / denominator
2783 // d1 < d2
2784 // ⇔ (numerator × increment × sign) / denominator < (increment × denominator - numerator × increment × sign) / denominator
2785 // ⇔ (numerator × increment × sign) < (increment × denominator - numerator × increment × sign)
2786 // ⇔ (numerator × sign) < (denominator - numerator × sign)
2787 // ⇔ (2 × numerator × sign) < denominator
2789 // cardinality = (r1 / (r2 – r1)) modulo 2
2790 // = (r1 / (r1 + increment - r1)) modulo 2
2791 // = (r1 / increment) modulo 2
2793 // clang-format on
2794 bool didExpandCalendarUnit;
2795 if (numerator == Int128{0}) {
2796 didExpandCalendarUnit = false;
2797 } else if (unsignedRoundingMode == UnsignedRoundingMode::Zero) {
2798 didExpandCalendarUnit = false;
2799 } else if (unsignedRoundingMode == UnsignedRoundingMode::Infinity) {
2800 didExpandCalendarUnit = true;
2801 } else if (numerator + numerator < denominator) {
2802 didExpandCalendarUnit = false;
2803 } else if (numerator + numerator > denominator) {
2804 didExpandCalendarUnit = true;
2805 } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfZero) {
2806 didExpandCalendarUnit = false;
2807 } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfInfinity) {
2808 didExpandCalendarUnit = true;
2809 } else if ((r1 / increment.value()) % 2 == 0) {
2810 didExpandCalendarUnit = false;
2811 } else {
2812 didExpandCalendarUnit = true;
2815 // FIXME: spec bug - zero progress case incorrect
2816 // https://github.com/tc39/proposal-temporal/issues/2893
2818 // Steps 16-19.
2819 auto resultDuration = didExpandCalendarUnit ? endDuration : startDuration;
2820 auto resultEpochNs = didExpandCalendarUnit ? endEpochNs : startEpochNs;
2821 *result = {{resultDuration, {}}, resultEpochNs, total, didExpandCalendarUnit};
2822 return true;
2826 * NudgeToZonedTime ( sign, duration, dateTime, calendarRec, timeZoneRec,
2827 * increment, unit, roundingMode )
2829 static bool NudgeToZonedTime(JSContext* cx, const NormalizedDuration& duration,
2830 const PlainDateTime& dateTime,
2831 Handle<CalendarRecord> calendar,
2832 Handle<TimeZoneRecord> timeZone,
2833 Increment increment, TemporalUnit unit,
2834 TemporalRoundingMode roundingMode,
2835 DurationNudge* result) {
2836 MOZ_ASSERT(IsValidDuration(duration));
2837 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
2839 int32_t sign = DurationSign(duration) < 0 ? -1 : 1;
2841 // Step 1.
2842 MOZ_ASSERT(unit >= TemporalUnit::Hour);
2844 // Step 2.
2845 PlainDate start;
2846 if (!AddDate(cx, calendar, dateTime.date, duration.date, &start)) {
2847 return false;
2850 // Step 3.
2851 Rooted<PlainDateTimeWithCalendar> startDateTime(
2853 PlainDateTimeWithCalendar{{start, dateTime.time}, calendar.receiver()});
2854 MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime));
2856 // Step 4.
2857 PlainDate end;
2858 if (!BalanceISODate(cx, start, sign, &end)) {
2859 return false;
2862 // Step 5.
2863 Rooted<PlainDateTimeWithCalendar> endDateTime(cx);
2864 if (!CreateTemporalDateTime(cx, {end, dateTime.time}, calendar.receiver(),
2865 &endDateTime)) {
2866 return false;
2869 // Steps 6-7.
2870 Instant startEpochNs;
2871 if (!GetInstantFor(cx, timeZone, startDateTime,
2872 TemporalDisambiguation::Compatible, &startEpochNs)) {
2873 return false;
2876 // Steps 8-9.
2877 Instant endEpochNs;
2878 if (!GetInstantFor(cx, timeZone, endDateTime,
2879 TemporalDisambiguation::Compatible, &endEpochNs)) {
2880 return false;
2883 // Step 10.
2884 auto daySpan = NormalizedTimeDurationFromEpochNanosecondsDifference(
2885 endEpochNs, startEpochNs);
2887 // FIXME: spec bug - how can this assert be valid for custom time zones?
2889 // Step 11.
2890 MOZ_ASSERT(NormalizedTimeDurationSign(daySpan) == sign);
2892 // FIXME: spec issue - Use DifferenceInstant?
2893 // FIXME: spec issue - Is this call really fallible?
2895 // Steps 12-13.
2896 NormalizedTimeDuration roundedTime;
2897 if (!RoundNormalizedTimeDurationToIncrement(
2898 cx, duration.time, unit, increment, roundingMode, &roundedTime)) {
2899 return false;
2902 // Step 14.
2903 NormalizedTimeDuration beyondDaySpan;
2904 if (!SubtractNormalizedTimeDuration(cx, roundedTime, daySpan,
2905 &beyondDaySpan)) {
2906 return false;
2909 // Steps 15-16.
2910 bool didRoundBeyondDay;
2911 int32_t dayDelta;
2912 Instant nudgedEpochNs;
2913 if (NormalizedTimeDurationSign(beyondDaySpan) != -sign) {
2914 // Step 15.a.
2915 didRoundBeyondDay = true;
2917 // Step 15.b.
2918 dayDelta = sign;
2920 // Step 15.c.
2921 if (!RoundNormalizedTimeDurationToIncrement(
2922 cx, beyondDaySpan, unit, increment, roundingMode, &roundedTime)) {
2923 return false;
2926 // Step 15.d. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
2927 nudgedEpochNs = endEpochNs + roundedTime.to<InstantSpan>();
2928 } else {
2929 // Step 16.a.
2930 didRoundBeyondDay = false;
2932 // Step 16.b.
2933 dayDelta = 0;
2935 // Step 16.c. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
2936 nudgedEpochNs = startEpochNs + roundedTime.to<InstantSpan>();
2939 // Step 17.
2940 NormalizedDuration resultDuration;
2941 if (!CreateNormalizedDurationRecord(cx,
2943 duration.date.years,
2944 duration.date.months,
2945 duration.date.weeks,
2946 duration.date.days + dayDelta,
2948 roundedTime, &resultDuration)) {
2949 return false;
2952 // Step 18.
2953 *result = {
2954 resultDuration,
2955 nudgedEpochNs,
2956 mozilla::UnspecifiedNaN<double>(),
2957 didRoundBeyondDay,
2959 return true;
2963 * NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment,
2964 * smallestUnit, roundingMode )
2966 static bool NudgeToDayOrTime(JSContext* cx, const NormalizedDuration& duration,
2967 const Instant& destEpochNs,
2968 TemporalUnit largestUnit, Increment increment,
2969 TemporalUnit smallestUnit,
2970 TemporalRoundingMode roundingMode,
2971 DurationNudge* result) {
2972 MOZ_ASSERT(IsValidDuration(duration));
2973 MOZ_ASSERT(IsValidEpochInstant(destEpochNs));
2975 // FIXME: spec bug - incorrect assertion
2976 // https://github.com/tc39/proposal-temporal/issues/2897
2978 // Step 1.
2979 MOZ_ASSERT(smallestUnit >= TemporalUnit::Day);
2981 // Step 2.
2982 NormalizedTimeDuration withDays;
2983 if (!Add24HourDaysToNormalizedTimeDuration(cx, duration.time,
2984 duration.date.days, &withDays)) {
2985 return false;
2988 // Steps 3-5.
2989 double total = DivideNormalizedTimeDuration(withDays, smallestUnit);
2990 NormalizedTimeDuration roundedTime;
2991 if (!RoundNormalizedTimeDurationToIncrement(
2992 cx, withDays, smallestUnit, increment, roundingMode, &roundedTime)) {
2993 return false;
2996 // Step 6.
2997 NormalizedTimeDuration diffTime;
2998 if (!SubtractNormalizedTimeDuration(cx, roundedTime, withDays, &diffTime)) {
2999 return false;
3002 constexpr int64_t secPerDay = ToSeconds(TemporalUnit::Day);
3004 // Step 7.
3005 int64_t wholeDays = withDays.toSeconds() / secPerDay;
3007 // Steps 8-9.
3008 int64_t roundedWholeDays = roundedTime.toSeconds() / secPerDay;
3010 // Step 10.
3011 int64_t dayDelta = roundedWholeDays - wholeDays;
3013 // Step 11.
3014 int32_t dayDeltaSign = dayDelta < 0 ? -1 : dayDelta > 0 ? 1 : 0;
3016 // Step 12.
3017 bool didExpandDays = dayDeltaSign == NormalizedTimeDurationSign(withDays);
3019 // Step 13. (Inlined AddNormalizedTimeDurationToEpochNanoseconds)
3020 auto nudgedEpochNs = destEpochNs + diffTime.to<InstantSpan>();
3022 // Step 14.
3023 int64_t days = 0;
3025 // Step 15.
3026 auto remainder = roundedTime;
3028 // Step 16.
3029 if (largestUnit <= TemporalUnit::Day) {
3030 // Step 16.a.
3031 days = roundedWholeDays;
3033 // Step 16.b.
3034 remainder = roundedTime - NormalizedTimeDuration::fromSeconds(
3035 roundedWholeDays * secPerDay);
3038 // Step 17.
3039 NormalizedDuration resultDuration;
3040 if (!CreateNormalizedDurationRecord(cx,
3042 duration.date.years,
3043 duration.date.months,
3044 duration.date.weeks,
3045 days,
3047 remainder, &resultDuration)) {
3048 return false;
3051 // Step 18.
3052 *result = {resultDuration, nudgedEpochNs, total, didExpandDays};
3053 return true;
3057 * BubbleRelativeDuration ( sign, duration, nudgedEpochNs, dateTime,
3058 * calendarRec, timeZoneRec, largestUnit, smallestUnit )
3060 static bool BubbleRelativeDuration(
3061 JSContext* cx, const NormalizedDuration& duration,
3062 const DurationNudge& nudge, const PlainDateTime& dateTime,
3063 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
3064 TemporalUnit largestUnit, TemporalUnit smallestUnit,
3065 NormalizedDuration* result) {
3066 MOZ_ASSERT(IsValidDuration(duration));
3067 MOZ_ASSERT(IsValidDuration(nudge.duration));
3068 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
3069 MOZ_ASSERT(largestUnit <= smallestUnit);
3071 int32_t sign = DurationSign(duration) < 0 ? -1 : 1;
3073 // Step 1.
3074 MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
3076 // Step 2.
3077 MOZ_ASSERT(smallestUnit <= TemporalUnit::Day);
3079 // FIXME: spec issue - directly return when `smallestUnit == largestUnit`.
3080 // https://github.com/tc39/proposal-temporal/issues/2890
3082 // Step 3.
3083 if (smallestUnit == largestUnit) {
3084 *result = nudge.duration;
3085 return true;
3087 MOZ_ASSERT(smallestUnit != TemporalUnit::Year);
3089 // FIXME: spec bug - wrong loop condition and "day" case not reachable
3090 // https://github.com/tc39/proposal-temporal/issues/2890
3092 // Steps 4-8.
3093 auto dateDuration = nudge.duration.date;
3094 auto timeDuration = nudge.duration.time;
3095 auto unit = smallestUnit;
3096 while (unit > largestUnit) {
3097 // Steps 6 and 8.c.
3098 using TemporalUnitType = std::underlying_type_t<TemporalUnit>;
3100 static_assert(static_cast<TemporalUnitType>(TemporalUnit::Auto) == 0,
3101 "TemporalUnit::Auto has value zero");
3102 MOZ_ASSERT(unit > TemporalUnit::Auto, "can subtract unit by one");
3104 unit = static_cast<TemporalUnit>(static_cast<TemporalUnitType>(unit) - 1);
3106 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Week);
3108 // Step 8.a. (Not applicable in our implementation.)
3110 // Step 8.b.
3111 if (unit != TemporalUnit::Week || largestUnit == TemporalUnit::Week) {
3112 // Steps 8.b.i-iv.
3113 DateDuration endDuration;
3114 if (unit == TemporalUnit::Year) {
3115 // Step 8.b.i.1.
3116 int64_t years = dateDuration.years + sign;
3118 // Step 8.b.i.2.
3119 endDuration = {years};
3120 } else if (unit == TemporalUnit::Month) {
3121 // Step 8.b.ii.1.
3122 int64_t months = dateDuration.months + sign;
3124 // Step 8.b.ii.2.
3125 endDuration = {dateDuration.years, months};
3126 } else if (unit == TemporalUnit::Week) {
3127 // Step 8.b.iii.1.
3128 int64_t weeks = dateDuration.weeks + sign;
3130 // Step 8.b.iii.2.
3131 endDuration = {dateDuration.years, dateDuration.months, weeks};
3132 } else {
3133 // Step 8.b.iv.1.
3134 MOZ_ASSERT(unit == TemporalUnit::Day);
3136 // Step 8.b.iv.2.
3137 int64_t days = dateDuration.days + sign;
3139 // Step 8.b.iv.2.
3140 endDuration = {dateDuration.years, dateDuration.months,
3141 dateDuration.weeks, days};
3144 // Step 8.b.v.
3145 PlainDate end;
3146 if (!AddDate(cx, calendar, dateTime.date, endDuration, &end)) {
3147 return false;
3150 // Steps 8.b.vi-vii.
3151 Instant endEpochNs;
3152 if (!timeZone.receiver()) {
3153 // Step 8.b.vi.1.
3154 endEpochNs = GetUTCEpochNanoseconds({end, dateTime.time});
3155 } else {
3156 // Step 8.b.vii.1.
3157 Rooted<PlainDateTimeWithCalendar> endDateTime(
3158 cx, PlainDateTimeWithCalendar{{end, dateTime.time},
3159 calendar.receiver()});
3161 // Steps 8.b.vii.2-3.
3162 if (!GetInstantFor(cx, timeZone, endDateTime,
3163 TemporalDisambiguation::Compatible, &endEpochNs)) {
3164 return false;
3168 // Step 8.b.viii.
3170 // NB: |nudge.epochNs| can be outside the valid epoch nanoseconds limits.
3171 auto beyondEnd = nudge.epochNs - endEpochNs;
3173 // Step 8.b.ix.
3174 int32_t beyondEndSign = beyondEnd < InstantSpan{} ? -1
3175 : beyondEnd > InstantSpan{} ? 1
3176 : 0;
3178 // Steps 8.b.x-xi.
3179 if (beyondEndSign != -sign) {
3180 dateDuration = endDuration;
3181 timeDuration = {};
3182 } else {
3183 break;
3187 // Step 8.c. (Moved above)
3190 // Step 9.
3191 *result = {dateDuration, timeDuration};
3192 return true;
3196 * RoundRelativeDuration ( duration, destEpochNs, dateTime, calendarRec,
3197 * timeZoneRec, largestUnit, increment, smallestUnit, roundingMode )
3199 bool js::temporal::RoundRelativeDuration(
3200 JSContext* cx, const NormalizedDuration& duration,
3201 const Instant& destEpochNs, const PlainDateTime& dateTime,
3202 Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
3203 TemporalUnit largestUnit, Increment increment, TemporalUnit smallestUnit,
3204 TemporalRoundingMode roundingMode, RoundedRelativeDuration* result) {
3205 MOZ_ASSERT(IsValidDuration(duration));
3206 MOZ_ASSERT(IsValidEpochInstant(destEpochNs));
3207 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
3208 MOZ_ASSERT(largestUnit <= smallestUnit);
3210 // Steps 1-3.
3211 bool irregularLengthUnit =
3212 (smallestUnit < TemporalUnit::Day) ||
3213 (timeZone.receiver() && smallestUnit == TemporalUnit::Day);
3215 // Step 4. (Not applicable in our implementation.)
3217 // Steps 5-7.
3218 DurationNudge nudge;
3219 if (irregularLengthUnit) {
3220 // Step 5.a.
3221 if (!NudgeToCalendarUnit(cx, duration, destEpochNs, dateTime, calendar,
3222 timeZone, increment, smallestUnit, roundingMode,
3223 &nudge)) {
3224 return false;
3226 } else if (timeZone.receiver()) {
3227 // Step 6.a.
3228 if (!NudgeToZonedTime(cx, duration, dateTime, calendar, timeZone, increment,
3229 smallestUnit, roundingMode, &nudge)) {
3230 return false;
3232 } else {
3233 // Step 7.a.
3234 if (!NudgeToDayOrTime(cx, duration, destEpochNs, largestUnit, increment,
3235 smallestUnit, roundingMode, &nudge)) {
3236 return false;
3240 // Step 8.
3241 auto nudgedDuration = nudge.duration;
3243 // Step 9.
3244 if (nudge.didExpandCalendarUnit && smallestUnit != TemporalUnit::Week) {
3245 // Step 9.a. (Inlined LargerOfTwoTemporalUnits)
3246 auto startUnit = std::min(smallestUnit, TemporalUnit::Day);
3248 // Step 9.b.
3249 if (!BubbleRelativeDuration(cx, duration, nudge, dateTime, calendar,
3250 timeZone, largestUnit, startUnit,
3251 &nudgedDuration)) {
3252 return false;
3256 // Step 10.
3257 largestUnit = std::max(largestUnit, TemporalUnit::Hour);
3259 // Step 11.
3260 TimeDuration balanced;
3261 if (!BalanceTimeDuration(cx, nudgedDuration.time, largestUnit, &balanced)) {
3262 return false;
3265 // Step 12.
3266 auto resultDuration = Duration{
3267 double(nudgedDuration.date.years),
3268 double(nudgedDuration.date.months),
3269 double(nudgedDuration.date.weeks),
3270 double(nudgedDuration.date.days),
3271 double(balanced.hours),
3272 double(balanced.minutes),
3273 double(balanced.seconds),
3274 double(balanced.milliseconds),
3275 balanced.microseconds,
3276 balanced.nanoseconds,
3278 MOZ_ASSERT(IsValidDuration(resultDuration));
3280 *result = {resultDuration, nudge.total};
3281 return true;
3284 enum class DurationOperation { Add, Subtract };
3287 * AddDurations ( operation, duration, other )
3289 static bool AddDurations(JSContext* cx, DurationOperation operation,
3290 const CallArgs& args) {
3291 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
3292 auto duration = ToDuration(durationObj);
3294 // Step 1. (Not applicable in our implementation.)
3296 // Step 2.
3297 Duration other;
3298 if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
3299 return false;
3302 // Steps 3-12. (Not applicable in our implementation.)
3304 // Steps 13-22.
3305 if (operation == DurationOperation::Subtract) {
3306 other = other.negate();
3309 // Step 23.
3310 auto largestUnit1 = DefaultTemporalLargestUnit(duration);
3312 // Step 24.
3313 auto largestUnit2 = DefaultTemporalLargestUnit(other);
3315 // Step 25.
3316 auto largestUnit = std::min(largestUnit1, largestUnit2);
3318 // Step 26.
3319 auto normalized1 = NormalizeTimeDuration(duration);
3321 // Step 27.
3322 auto normalized2 = NormalizeTimeDuration(other);
3324 // Step 28.
3325 if (largestUnit <= TemporalUnit::Week) {
3326 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3327 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3328 "relativeTo");
3329 return false;
3332 // Step 29.
3333 NormalizedTimeDuration normalized;
3334 if (!AddNormalizedTimeDuration(cx, normalized1, normalized2, &normalized)) {
3335 return false;
3338 // Step 30.
3339 int64_t days1 = mozilla::AssertedCast<int64_t>(duration.days);
3340 int64_t days2 = mozilla::AssertedCast<int64_t>(other.days);
3341 auto totalDays = mozilla::CheckedInt64(days1) + days2;
3342 MOZ_ASSERT(totalDays.isValid(), "adding two duration days can't overflow");
3344 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized, totalDays.value(),
3345 &normalized)) {
3346 return false;
3349 // Step 31.
3350 TimeDuration balanced;
3351 if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
3352 return false;
3355 // Step 32.
3356 auto* obj = CreateTemporalDuration(cx, balanced.toDuration());
3357 if (!obj) {
3358 return false;
3361 args.rval().setObject(*obj);
3362 return true;
3366 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
3367 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
3368 * ] ] ] ] ] ] )
3370 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
3371 CallArgs args = CallArgsFromVp(argc, vp);
3373 // Step 1.
3374 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
3375 return false;
3378 // Step 2.
3379 double years = 0;
3380 if (args.hasDefined(0) &&
3381 !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
3382 return false;
3385 // Step 3.
3386 double months = 0;
3387 if (args.hasDefined(1) &&
3388 !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
3389 return false;
3392 // Step 4.
3393 double weeks = 0;
3394 if (args.hasDefined(2) &&
3395 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
3396 return false;
3399 // Step 5.
3400 double days = 0;
3401 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
3402 return false;
3405 // Step 6.
3406 double hours = 0;
3407 if (args.hasDefined(4) &&
3408 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
3409 return false;
3412 // Step 7.
3413 double minutes = 0;
3414 if (args.hasDefined(5) &&
3415 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
3416 return false;
3419 // Step 8.
3420 double seconds = 0;
3421 if (args.hasDefined(6) &&
3422 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
3423 return false;
3426 // Step 9.
3427 double milliseconds = 0;
3428 if (args.hasDefined(7) &&
3429 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
3430 return false;
3433 // Step 10.
3434 double microseconds = 0;
3435 if (args.hasDefined(8) &&
3436 !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
3437 return false;
3440 // Step 11.
3441 double nanoseconds = 0;
3442 if (args.hasDefined(9) &&
3443 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
3444 return false;
3447 // Step 12.
3448 auto* duration = CreateTemporalDuration(
3449 cx, args,
3450 {years, months, weeks, days, hours, minutes, seconds, milliseconds,
3451 microseconds, nanoseconds});
3452 if (!duration) {
3453 return false;
3456 args.rval().setObject(*duration);
3457 return true;
3461 * Temporal.Duration.from ( item )
3463 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
3464 CallArgs args = CallArgsFromVp(argc, vp);
3466 Handle<Value> item = args.get(0);
3468 // Step 1.
3469 if (item.isObject()) {
3470 if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
3471 auto* result = CreateTemporalDuration(cx, ToDuration(duration));
3472 if (!result) {
3473 return false;
3476 args.rval().setObject(*result);
3477 return true;
3481 // Step 2.
3482 auto result = ToTemporalDuration(cx, item);
3483 if (!result) {
3484 return false;
3487 args.rval().setObject(*result);
3488 return true;
3492 * Temporal.Duration.compare ( one, two [ , options ] )
3494 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
3495 CallArgs args = CallArgsFromVp(argc, vp);
3497 // Step 1.
3498 Duration one;
3499 if (!ToTemporalDuration(cx, args.get(0), &one)) {
3500 return false;
3503 // Step 2.
3504 Duration two;
3505 if (!ToTemporalDuration(cx, args.get(1), &two)) {
3506 return false;
3509 // Step 3.
3510 Rooted<JSObject*> options(cx);
3511 if (args.hasDefined(2)) {
3512 options = RequireObjectArg(cx, "options", "compare", args[2]);
3513 if (!options) {
3514 return false;
3518 // Step 4.
3519 if (one == two) {
3520 args.rval().setInt32(0);
3521 return true;
3524 // Steps 5-8.
3525 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
3526 Rooted<ZonedDateTime> zonedRelativeTo(cx);
3527 Rooted<TimeZoneRecord> timeZone(cx);
3528 if (options) {
3529 if (!GetTemporalRelativeToOption(cx, options, &plainRelativeTo,
3530 &zonedRelativeTo, &timeZone)) {
3531 return false;
3533 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
3534 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
3537 // Steps 9-10.
3538 auto hasCalendarUnit = [](const auto& d) {
3539 return d.years != 0 || d.months != 0 || d.weeks != 0;
3541 bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
3543 // Step 11.
3544 Rooted<CalendarRecord> calendar(cx);
3545 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
3546 zonedRelativeTo,
3548 CalendarMethod::DateAdd,
3550 &calendar)) {
3551 return false;
3554 // Step 12.
3555 if (zonedRelativeTo &&
3556 (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
3557 // Step 12.a.
3558 const auto& instant = zonedRelativeTo.instant();
3560 // Step 12.b.
3561 PlainDateTime dateTime;
3562 if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
3563 return false;
3566 // Step 12.c.
3567 auto normalized1 = CreateNormalizedDurationRecord(one);
3569 // Step 12.d.
3570 auto normalized2 = CreateNormalizedDurationRecord(two);
3572 // Step 12.e.
3573 Instant after1;
3574 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized1,
3575 dateTime, &after1)) {
3576 return false;
3579 // Step 12.f.
3580 Instant after2;
3581 if (!AddZonedDateTime(cx, instant, timeZone, calendar, normalized2,
3582 dateTime, &after2)) {
3583 return false;
3586 // Steps 12.g-i.
3587 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
3588 return true;
3591 // Steps 13-14.
3592 int64_t days1, days2;
3593 if (calendarUnitsPresent) {
3594 // Step 13.a.
3595 if (!plainRelativeTo) {
3596 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3597 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
3598 "relativeTo");
3599 return false;
3602 // Step 13.b.
3603 if (!UnbalanceDateDurationRelative(cx, one.toDateDuration(),
3604 plainRelativeTo, calendar, &days1)) {
3605 return false;
3608 // Step 13.c.
3609 if (!UnbalanceDateDurationRelative(cx, two.toDateDuration(),
3610 plainRelativeTo, calendar, &days2)) {
3611 return false;
3613 } else {
3614 // Step 14.a.
3615 days1 = mozilla::AssertedCast<int64_t>(one.days);
3617 // Step 14.b.
3618 days2 = mozilla::AssertedCast<int64_t>(two.days);
3621 // Step 15.
3622 auto normalized1 = NormalizeTimeDuration(one);
3624 // Step 16.
3625 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized1, days1,
3626 &normalized1)) {
3627 return false;
3630 // Step 17.
3631 auto normalized2 = NormalizeTimeDuration(two);
3633 // Step 18.
3634 if (!Add24HourDaysToNormalizedTimeDuration(cx, normalized2, days2,
3635 &normalized2)) {
3636 return false;
3639 // Step 19.
3640 args.rval().setInt32(CompareNormalizedTimeDuration(normalized1, normalized2));
3641 return true;
3645 * get Temporal.Duration.prototype.years
3647 static bool Duration_years(JSContext* cx, const CallArgs& args) {
3648 // Step 3.
3649 auto* duration = &args.thisv().toObject().as<DurationObject>();
3650 args.rval().setNumber(duration->years());
3651 return true;
3655 * get Temporal.Duration.prototype.years
3657 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
3658 // Steps 1-2.
3659 CallArgs args = CallArgsFromVp(argc, vp);
3660 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
3664 * get Temporal.Duration.prototype.months
3666 static bool Duration_months(JSContext* cx, const CallArgs& args) {
3667 // Step 3.
3668 auto* duration = &args.thisv().toObject().as<DurationObject>();
3669 args.rval().setNumber(duration->months());
3670 return true;
3674 * get Temporal.Duration.prototype.months
3676 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
3677 // Steps 1-2.
3678 CallArgs args = CallArgsFromVp(argc, vp);
3679 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
3683 * get Temporal.Duration.prototype.weeks
3685 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
3686 // Step 3.
3687 auto* duration = &args.thisv().toObject().as<DurationObject>();
3688 args.rval().setNumber(duration->weeks());
3689 return true;
3693 * get Temporal.Duration.prototype.weeks
3695 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
3696 // Steps 1-2.
3697 CallArgs args = CallArgsFromVp(argc, vp);
3698 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
3702 * get Temporal.Duration.prototype.days
3704 static bool Duration_days(JSContext* cx, const CallArgs& args) {
3705 // Step 3.
3706 auto* duration = &args.thisv().toObject().as<DurationObject>();
3707 args.rval().setNumber(duration->days());
3708 return true;
3712 * get Temporal.Duration.prototype.days
3714 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
3715 // Steps 1-2.
3716 CallArgs args = CallArgsFromVp(argc, vp);
3717 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
3721 * get Temporal.Duration.prototype.hours
3723 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
3724 // Step 3.
3725 auto* duration = &args.thisv().toObject().as<DurationObject>();
3726 args.rval().setNumber(duration->hours());
3727 return true;
3731 * get Temporal.Duration.prototype.hours
3733 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
3734 // Steps 1-2.
3735 CallArgs args = CallArgsFromVp(argc, vp);
3736 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
3740 * get Temporal.Duration.prototype.minutes
3742 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
3743 // Step 3.
3744 auto* duration = &args.thisv().toObject().as<DurationObject>();
3745 args.rval().setNumber(duration->minutes());
3746 return true;
3750 * get Temporal.Duration.prototype.minutes
3752 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
3753 // Steps 1-2.
3754 CallArgs args = CallArgsFromVp(argc, vp);
3755 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
3759 * get Temporal.Duration.prototype.seconds
3761 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
3762 // Step 3.
3763 auto* duration = &args.thisv().toObject().as<DurationObject>();
3764 args.rval().setNumber(duration->seconds());
3765 return true;
3769 * get Temporal.Duration.prototype.seconds
3771 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
3772 // Steps 1-2.
3773 CallArgs args = CallArgsFromVp(argc, vp);
3774 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
3778 * get Temporal.Duration.prototype.milliseconds
3780 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
3781 // Step 3.
3782 auto* duration = &args.thisv().toObject().as<DurationObject>();
3783 args.rval().setNumber(duration->milliseconds());
3784 return true;
3788 * get Temporal.Duration.prototype.milliseconds
3790 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
3791 // Steps 1-2.
3792 CallArgs args = CallArgsFromVp(argc, vp);
3793 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
3797 * get Temporal.Duration.prototype.microseconds
3799 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
3800 // Step 3.
3801 auto* duration = &args.thisv().toObject().as<DurationObject>();
3802 args.rval().setNumber(duration->microseconds());
3803 return true;
3807 * get Temporal.Duration.prototype.microseconds
3809 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
3810 // Steps 1-2.
3811 CallArgs args = CallArgsFromVp(argc, vp);
3812 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
3816 * get Temporal.Duration.prototype.nanoseconds
3818 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
3819 // Step 3.
3820 auto* duration = &args.thisv().toObject().as<DurationObject>();
3821 args.rval().setNumber(duration->nanoseconds());
3822 return true;
3826 * get Temporal.Duration.prototype.nanoseconds
3828 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
3829 // Steps 1-2.
3830 CallArgs args = CallArgsFromVp(argc, vp);
3831 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
3835 * get Temporal.Duration.prototype.sign
3837 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
3838 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
3840 // Step 3.
3841 args.rval().setInt32(DurationSign(duration));
3842 return true;
3846 * get Temporal.Duration.prototype.sign
3848 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
3849 // Steps 1-2.
3850 CallArgs args = CallArgsFromVp(argc, vp);
3851 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
3855 * get Temporal.Duration.prototype.blank
3857 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
3858 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
3860 // Steps 3-5.
3861 args.rval().setBoolean(duration == Duration{});
3862 return true;
3866 * get Temporal.Duration.prototype.blank
3868 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
3869 // Steps 1-2.
3870 CallArgs args = CallArgsFromVp(argc, vp);
3871 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
3875 * Temporal.Duration.prototype.with ( temporalDurationLike )
3877 * ToPartialDuration ( temporalDurationLike )
3879 static bool Duration_with(JSContext* cx, const CallArgs& args) {
3880 // Absent values default to the corresponding values of |this| object.
3881 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
3883 // Steps 3-23.
3884 Rooted<JSObject*> temporalDurationLike(
3885 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
3886 if (!temporalDurationLike) {
3887 return false;
3889 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
3890 return false;
3893 // Step 24.
3894 auto* result = CreateTemporalDuration(cx, duration);
3895 if (!result) {
3896 return false;
3899 args.rval().setObject(*result);
3900 return true;
3904 * Temporal.Duration.prototype.with ( temporalDurationLike )
3906 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
3907 // Steps 1-2.
3908 CallArgs args = CallArgsFromVp(argc, vp);
3909 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
3913 * Temporal.Duration.prototype.negated ( )
3915 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
3916 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
3918 // Step 3.
3919 auto* result = CreateTemporalDuration(cx, duration.negate());
3920 if (!result) {
3921 return false;
3924 args.rval().setObject(*result);
3925 return true;
3929 * Temporal.Duration.prototype.negated ( )
3931 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
3932 // Steps 1-2.
3933 CallArgs args = CallArgsFromVp(argc, vp);
3934 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
3938 * Temporal.Duration.prototype.abs ( )
3940 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
3941 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
3943 // Step 3.
3944 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
3945 if (!result) {
3946 return false;
3949 args.rval().setObject(*result);
3950 return true;
3954 * Temporal.Duration.prototype.abs ( )
3956 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
3957 // Steps 1-2.
3958 CallArgs args = CallArgsFromVp(argc, vp);
3959 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
3963 * Temporal.Duration.prototype.add ( other )
3965 static bool Duration_add(JSContext* cx, const CallArgs& args) {
3966 // Step 3.
3967 return AddDurations(cx, DurationOperation::Add, args);
3971 * Temporal.Duration.prototype.add ( other )
3973 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
3974 // Steps 1-2.
3975 CallArgs args = CallArgsFromVp(argc, vp);
3976 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
3980 * Temporal.Duration.prototype.subtract ( other )
3982 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
3983 // Step 3.
3984 return AddDurations(cx, DurationOperation::Subtract, args);
3988 * Temporal.Duration.prototype.subtract ( other )
3990 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
3991 // Steps 1-2.
3992 CallArgs args = CallArgsFromVp(argc, vp);
3993 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
3997 * Temporal.Duration.prototype.round ( roundTo )
3999 static bool Duration_round(JSContext* cx, const CallArgs& args) {
4000 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4002 // Step 18. (Reordered)
4003 auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
4005 // Steps 3-25.
4006 auto smallestUnit = TemporalUnit::Auto;
4007 TemporalUnit largestUnit;
4008 auto roundingMode = TemporalRoundingMode::HalfExpand;
4009 auto roundingIncrement = Increment{1};
4010 Rooted<JSObject*> relativeTo(cx);
4011 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4012 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4013 Rooted<TimeZoneRecord> timeZone(cx);
4014 if (args.get(0).isString()) {
4015 // Step 4. (Not applicable in our implementation.)
4017 // Steps 6-15. (Not applicable)
4019 // Step 16.
4020 Rooted<JSString*> paramString(cx, args[0].toString());
4021 if (!GetTemporalUnitValuedOption(
4022 cx, paramString, TemporalUnitKey::SmallestUnit,
4023 TemporalUnitGroup::DateTime, &smallestUnit)) {
4024 return false;
4027 // Step 17. (Not applicable)
4029 // Step 18. (Moved above)
4031 // Step 19.
4032 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4034 // Step 20. (Not applicable)
4036 // Step 20.a. (Not applicable)
4038 // Step 20.b.
4039 largestUnit = defaultLargestUnit;
4041 // Steps 21-25. (Not applicable)
4042 } else {
4043 // Steps 3 and 5.
4044 Rooted<JSObject*> options(
4045 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
4046 if (!options) {
4047 return false;
4050 // Step 6.
4051 bool smallestUnitPresent = true;
4053 // Step 7.
4054 bool largestUnitPresent = true;
4056 // Steps 8-9.
4058 // Inlined GetTemporalUnitValuedOption and GetOption so we can more easily
4059 // detect an absent "largestUnit" value.
4060 Rooted<Value> largestUnitValue(cx);
4061 if (!GetProperty(cx, options, options, cx->names().largestUnit,
4062 &largestUnitValue)) {
4063 return false;
4066 if (!largestUnitValue.isUndefined()) {
4067 Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
4068 if (!largestUnitStr) {
4069 return false;
4072 largestUnit = TemporalUnit::Auto;
4073 if (!GetTemporalUnitValuedOption(
4074 cx, largestUnitStr, TemporalUnitKey::LargestUnit,
4075 TemporalUnitGroup::DateTime, &largestUnit)) {
4076 return false;
4080 // Steps 10-13.
4081 if (!GetTemporalRelativeToOption(cx, options, &plainRelativeTo,
4082 &zonedRelativeTo, &timeZone)) {
4083 return false;
4085 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4086 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4088 // Step 14.
4089 if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) {
4090 return false;
4093 // Step 15.
4094 if (!GetRoundingModeOption(cx, options, &roundingMode)) {
4095 return false;
4098 // Step 16.
4099 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
4100 TemporalUnitGroup::DateTime,
4101 &smallestUnit)) {
4102 return false;
4105 // Step 17.
4106 if (smallestUnit == TemporalUnit::Auto) {
4107 // Step 17.a.
4108 smallestUnitPresent = false;
4110 // Step 17.b.
4111 smallestUnit = TemporalUnit::Nanosecond;
4114 // Step 18. (Moved above)
4116 // Step 19.
4117 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
4119 // Steps 20-21.
4120 if (largestUnitValue.isUndefined()) {
4121 // Step 20.a.
4122 largestUnitPresent = false;
4124 // Step 20.b.
4125 largestUnit = defaultLargestUnit;
4126 } else if (largestUnit == TemporalUnit::Auto) {
4127 // Step 21.a
4128 largestUnit = defaultLargestUnit;
4131 // Step 22.
4132 if (!smallestUnitPresent && !largestUnitPresent) {
4133 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4134 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
4135 return false;
4138 // Step 23.
4139 if (largestUnit > smallestUnit) {
4140 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4141 JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
4142 return false;
4145 // Steps 24-25.
4146 if (smallestUnit > TemporalUnit::Day) {
4147 // Step 24.
4148 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
4150 // Step 25.
4151 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
4152 false)) {
4153 return false;
4158 // Step 26.
4159 bool hoursToDaysConversionMayOccur = false;
4161 // Step 27.
4162 if (duration.days != 0 && zonedRelativeTo) {
4163 hoursToDaysConversionMayOccur = true;
4166 // Step 28.
4167 else if (std::abs(duration.hours) >= 24) {
4168 hoursToDaysConversionMayOccur = true;
4171 // Step 29.
4172 bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
4173 roundingIncrement == Increment{1};
4175 // Step 30.
4176 bool calendarUnitsPresent =
4177 duration.years != 0 || duration.months != 0 || duration.weeks != 0;
4179 // Step 31.
4180 if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
4181 !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
4182 std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
4183 std::abs(duration.milliseconds) < 1000 &&
4184 std::abs(duration.microseconds) < 1000 &&
4185 std::abs(duration.nanoseconds) < 1000) {
4186 // Steps 31.a-b.
4187 auto* obj = CreateTemporalDuration(cx, duration);
4188 if (!obj) {
4189 return false;
4192 args.rval().setObject(*obj);
4193 return true;
4196 // Step 32.
4197 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4199 // Step 33.
4200 bool plainDateTimeOrRelativeToWillBeUsed = largestUnit <= TemporalUnit::Day ||
4201 calendarUnitsPresent ||
4202 duration.days != 0;
4204 // Step 34.
4205 PlainDateTime relativeToDateTime;
4206 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
4207 // Steps 34.a-b.
4208 const auto& instant = zonedRelativeTo.instant();
4210 // Step 34.c.
4211 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
4212 return false;
4214 precalculatedPlainDateTime =
4215 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
4217 // FIXME: spec issue - Unnecessary CreateTemporalDate call
4219 // https://github.com/tc39/proposal-temporal/issues/2873
4222 // Step 35.
4223 Rooted<CalendarRecord> calendar(cx);
4224 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4225 zonedRelativeTo,
4227 CalendarMethod::DateAdd,
4228 CalendarMethod::DateUntil,
4230 &calendar)) {
4231 return false;
4234 // Step 36.
4235 auto normDuration = CreateNormalizedDurationRecord(duration);
4237 // Step 37. (Not applicable in our implementation.)
4239 // Steps 38-40.
4240 Duration roundResult;
4241 if (zonedRelativeTo) {
4242 // Step 38.a.
4243 auto relativeEpochNs = zonedRelativeTo.instant();
4245 // Step 38.b.
4246 const auto& relativeInstant = relativeEpochNs;
4248 // Steps 38.c-d.
4249 if (precalculatedPlainDateTime) {
4250 // Step 38.c.
4251 Instant targetEpochNs;
4252 if (!AddZonedDateTime(cx, relativeInstant, timeZone, calendar,
4253 normDuration, *precalculatedPlainDateTime,
4254 &targetEpochNs)) {
4255 return false;
4258 // Step 38.d.
4259 if (!DifferenceZonedDateTimeWithRounding(
4260 cx, relativeEpochNs, targetEpochNs, timeZone, calendar,
4261 *precalculatedPlainDateTime,
4263 smallestUnit,
4264 largestUnit,
4265 roundingMode,
4266 roundingIncrement,
4268 &roundResult)) {
4269 return false;
4271 } else {
4272 // Step 38.c.
4273 Instant targetEpochNs;
4274 if (!AddZonedDateTime(cx, relativeInstant, timeZone, calendar,
4275 normDuration, &targetEpochNs)) {
4276 return false;
4279 // Step 38.d.
4280 if (!DifferenceZonedDateTimeWithRounding(cx, relativeEpochNs,
4281 targetEpochNs,
4283 smallestUnit,
4284 largestUnit,
4285 roundingMode,
4286 roundingIncrement,
4288 &roundResult)) {
4289 return false;
4292 } else if (plainRelativeTo) {
4293 // Step 39.a.
4294 auto targetTime = AddTime(PlainTime{}, normDuration.time);
4296 // Step 39.b.
4297 auto dateDuration = DateDuration{
4298 normDuration.date.years,
4299 normDuration.date.months,
4300 normDuration.date.weeks,
4301 normDuration.date.days + targetTime.days,
4303 MOZ_ASSERT(IsValidDuration(dateDuration));
4305 // Step 39.c.
4306 PlainDate targetDate;
4307 if (!AddDate(cx, calendar, plainRelativeTo, dateDuration, &targetDate)) {
4308 return false;
4310 auto targetDateTime = PlainDateTime{targetDate, targetTime.time};
4312 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
4313 if (!unwrappedRelativeTo) {
4314 return false;
4316 auto sourceDateTime = PlainDateTime{ToPlainDate(unwrappedRelativeTo), {}};
4318 // Step 39.d.
4319 if (!DifferencePlainDateTimeWithRounding(cx, sourceDateTime, targetDateTime,
4320 calendar,
4322 smallestUnit,
4323 largestUnit,
4324 roundingMode,
4325 roundingIncrement,
4327 &roundResult)) {
4328 return false;
4330 } else {
4331 // Step 40.a.
4332 if (calendarUnitsPresent || largestUnit < TemporalUnit::Day) {
4333 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4334 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
4335 "relativeTo");
4336 return false;
4339 // Step 40.b.
4340 MOZ_ASSERT(smallestUnit >= TemporalUnit::Day);
4342 // FIXME: spec issue - can with switch the call order, so that
4343 // Add24HourDaysToNormalizedTimeDuration is first called. That way we don't
4344 // have to add the additional `days` parameter to RoundTimeDuration.
4346 // Step 40.c.
4347 RoundedDuration rounded;
4348 if (!::RoundTimeDuration(cx, normDuration, roundingIncrement, smallestUnit,
4349 roundingMode, ComputeRemainder::No, &rounded)) {
4350 return false;
4353 // Step 40.d.
4354 NormalizedTimeDuration withDays;
4355 if (!Add24HourDaysToNormalizedTimeDuration(
4356 cx, rounded.duration.time, rounded.duration.date.days, &withDays)) {
4357 return false;
4360 // Step 40.e.
4361 TimeDuration balanceResult;
4362 if (!temporal::BalanceTimeDuration(cx, withDays, largestUnit,
4363 &balanceResult)) {
4364 return false;
4367 // Step 40.f.
4368 roundResult = balanceResult.toDuration();
4371 // Step 41.
4372 auto* obj = CreateTemporalDuration(cx, roundResult);
4373 if (!obj) {
4374 return false;
4377 args.rval().setObject(*obj);
4378 return true;
4382 * Temporal.Duration.prototype.round ( options )
4384 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
4385 // Steps 1-2.
4386 CallArgs args = CallArgsFromVp(argc, vp);
4387 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
4391 * Temporal.Duration.prototype.total ( totalOf )
4393 static bool Duration_total(JSContext* cx, const CallArgs& args) {
4394 auto* durationObj = &args.thisv().toObject().as<DurationObject>();
4395 auto duration = ToDuration(durationObj);
4397 // Steps 3-11.
4398 Rooted<JSObject*> relativeTo(cx);
4399 Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
4400 Rooted<ZonedDateTime> zonedRelativeTo(cx);
4401 Rooted<TimeZoneRecord> timeZone(cx);
4402 auto unit = TemporalUnit::Auto;
4403 if (args.get(0).isString()) {
4404 // Step 4. (Not applicable in our implementation.)
4406 // Steps 6-10. (Implicit)
4407 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
4409 // Step 11.
4410 Rooted<JSString*> paramString(cx, args[0].toString());
4411 if (!GetTemporalUnitValuedOption(cx, paramString, TemporalUnitKey::Unit,
4412 TemporalUnitGroup::DateTime, &unit)) {
4413 return false;
4415 } else {
4416 // Steps 3 and 5.
4417 Rooted<JSObject*> totalOf(
4418 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
4419 if (!totalOf) {
4420 return false;
4423 // Steps 6-10.
4424 if (!GetTemporalRelativeToOption(cx, totalOf, &plainRelativeTo,
4425 &zonedRelativeTo, &timeZone)) {
4426 return false;
4428 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
4429 MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
4431 // Step 11.
4432 if (!GetTemporalUnitValuedOption(cx, totalOf, TemporalUnitKey::Unit,
4433 TemporalUnitGroup::DateTime, &unit)) {
4434 return false;
4437 if (unit == TemporalUnit::Auto) {
4438 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4439 JSMSG_TEMPORAL_MISSING_OPTION, "unit");
4440 return false;
4444 // Step 12.
4445 mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
4447 // Step 13.
4448 bool plainDateTimeOrRelativeToWillBeUsed =
4449 unit <= TemporalUnit::Day || duration.toDateDuration() != DateDuration{};
4451 // Step 14.
4452 PlainDateTime relativeToDateTime;
4453 if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
4454 // Steps 14.a-b.
4455 const auto& instant = zonedRelativeTo.instant();
4457 // Step 14.c.
4458 if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
4459 return false;
4461 precalculatedPlainDateTime =
4462 mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
4464 // FIXME: spec issue - Unnecessary CreateTemporalDate call
4466 // https://github.com/tc39/proposal-temporal/issues/2873
4469 // Step 15.
4470 Rooted<CalendarRecord> calendar(cx);
4471 if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
4472 zonedRelativeTo,
4474 CalendarMethod::DateAdd,
4475 CalendarMethod::DateUntil,
4477 &calendar)) {
4478 return false;
4481 // Step 16.
4482 auto normDuration = CreateNormalizedDurationRecord(duration);
4484 // Step 17. (Not applicable in our implementation.)
4486 // Steps 18-20.
4487 double total;
4488 if (zonedRelativeTo) {
4489 // Step 18.a.
4490 auto relativeEpochNs = zonedRelativeTo.instant();
4492 // Step 18.b.
4493 const auto& relativeInstant = relativeEpochNs;
4495 // Step 18.c.
4496 Instant targetEpochNs;
4497 if (precalculatedPlainDateTime) {
4498 if (!AddZonedDateTime(cx, relativeInstant, timeZone, calendar,
4499 normDuration, *precalculatedPlainDateTime,
4500 &targetEpochNs)) {
4501 return false;
4503 } else {
4504 if (!AddZonedDateTime(cx, relativeInstant, timeZone, calendar,
4505 normDuration, &targetEpochNs)) {
4506 return false;
4510 // Step 18.d.
4511 if (unit <= TemporalUnit::Day) {
4512 if (!DifferenceZonedDateTimeWithRounding(
4513 cx, relativeEpochNs, targetEpochNs, timeZone, calendar,
4514 *precalculatedPlainDateTime, unit, &total)) {
4515 return false;
4517 } else {
4518 total = DifferenceZonedDateTimeWithRounding(targetEpochNs,
4519 relativeEpochNs, unit);
4521 } else if (plainRelativeTo) {
4522 // Step 19.a.
4523 auto targetTime = AddTime(PlainTime{}, normDuration.time);
4525 // Step 19.b.
4526 auto dateDuration = DateDuration{
4527 normDuration.date.years,
4528 normDuration.date.months,
4529 normDuration.date.weeks,
4530 normDuration.date.days + targetTime.days,
4532 MOZ_ASSERT(IsValidDuration(dateDuration));
4534 // Step 19.c.
4535 PlainDate targetDate;
4536 if (!AddDate(cx, calendar, plainRelativeTo, dateDuration, &targetDate)) {
4537 return false;
4539 auto targetDateTime = PlainDateTime{targetDate, targetTime.time};
4541 auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
4542 if (!unwrappedRelativeTo) {
4543 return false;
4545 auto sourceDateTime = PlainDateTime{ToPlainDate(unwrappedRelativeTo), {}};
4547 // Step 19.d.
4548 if (!::DifferencePlainDateTimeWithRounding(
4549 cx, sourceDateTime, targetDateTime, calendar, unit, &total)) {
4550 return false;
4552 } else {
4553 // Step 20.a.
4554 if (normDuration.date.years || normDuration.date.months ||
4555 normDuration.date.weeks || unit < TemporalUnit::Day) {
4556 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4557 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
4558 "relativeTo");
4559 return false;
4562 // FIXME: spec issue - Add24HourDaysToNormalizedTimeDuration and
4563 // RoundTimeDuration are probably both infallible
4565 // Step 20.b.
4566 NormalizedTimeDuration withDays;
4567 if (!Add24HourDaysToNormalizedTimeDuration(
4568 cx, normDuration.time, normDuration.date.days, &withDays)) {
4569 return false;
4572 // Step 20.c.
4573 auto roundInput = NormalizedDuration{{}, withDays};
4574 RoundedDuration rounded;
4575 if (!::RoundTimeDuration(cx, roundInput, Increment{1}, unit,
4576 TemporalRoundingMode::Trunc, ComputeRemainder::Yes,
4577 &rounded)) {
4578 return false;
4580 total = rounded.total;
4583 // Step 21.
4584 MOZ_ASSERT(!std::isnan(total));
4586 // Step 22.
4587 args.rval().setNumber(total);
4588 return true;
4592 * Temporal.Duration.prototype.total ( totalOf )
4594 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
4595 // Steps 1-2.
4596 CallArgs args = CallArgsFromVp(argc, vp);
4597 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
4601 * Temporal.Duration.prototype.toString ( [ options ] )
4603 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
4604 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4606 // Steps 3-9.
4607 SecondsStringPrecision precision = {Precision::Auto(),
4608 TemporalUnit::Nanosecond, Increment{1}};
4609 auto roundingMode = TemporalRoundingMode::Trunc;
4610 if (args.hasDefined(0)) {
4611 // Step 3.
4612 Rooted<JSObject*> options(
4613 cx, RequireObjectArg(cx, "options", "toString", args[0]));
4614 if (!options) {
4615 return false;
4618 // Steps 4-5.
4619 auto digits = Precision::Auto();
4620 if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) {
4621 return false;
4624 // Step 6.
4625 if (!GetRoundingModeOption(cx, options, &roundingMode)) {
4626 return false;
4629 // Step 7.
4630 auto smallestUnit = TemporalUnit::Auto;
4631 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
4632 TemporalUnitGroup::Time, &smallestUnit)) {
4633 return false;
4636 // Step 8.
4637 if (smallestUnit == TemporalUnit::Hour ||
4638 smallestUnit == TemporalUnit::Minute) {
4639 const char* smallestUnitStr =
4640 smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
4641 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
4642 JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
4643 smallestUnitStr, "smallestUnit");
4644 return false;
4647 // Step 9.
4648 precision = ToSecondsStringPrecision(smallestUnit, digits);
4651 // Steps 10-11.
4652 Duration result;
4653 if (precision.unit != TemporalUnit::Nanosecond ||
4654 precision.increment != Increment{1}) {
4655 // Step 10.a.
4656 auto timeDuration = NormalizeTimeDuration(duration);
4658 // Step 10.b.
4659 auto largestUnit = DefaultTemporalLargestUnit(duration);
4661 // Steps 10.c-d.
4662 NormalizedTimeDuration rounded;
4663 if (!RoundTimeDuration(cx, timeDuration, precision.increment,
4664 precision.unit, roundingMode, &rounded)) {
4665 return false;
4668 // Step 10.e.
4669 auto balanced = BalanceTimeDuration(
4670 rounded, std::min(largestUnit, TemporalUnit::Second));
4672 // Step 10.f.
4673 result = {
4674 duration.years, duration.months,
4675 duration.weeks, duration.days + double(balanced.days),
4676 double(balanced.hours), double(balanced.minutes),
4677 double(balanced.seconds), double(balanced.milliseconds),
4678 balanced.microseconds, balanced.nanoseconds,
4680 MOZ_ASSERT(IsValidDuration(duration));
4681 } else {
4682 // Step 11.
4683 result = duration;
4686 // Steps 12-13.
4687 JSString* str = TemporalDurationToString(cx, result, precision.precision);
4688 if (!str) {
4689 return false;
4692 args.rval().setString(str);
4693 return true;
4697 * Temporal.Duration.prototype.toString ( [ options ] )
4699 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
4700 // Steps 1-2.
4701 CallArgs args = CallArgsFromVp(argc, vp);
4702 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
4706 * Temporal.Duration.prototype.toJSON ( )
4708 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
4709 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4711 // Steps 3-4.
4712 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
4713 if (!str) {
4714 return false;
4717 args.rval().setString(str);
4718 return true;
4722 * Temporal.Duration.prototype.toJSON ( )
4724 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
4725 // Steps 1-2.
4726 CallArgs args = CallArgsFromVp(argc, vp);
4727 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
4731 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
4733 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
4734 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
4736 // Steps 3-4.
4737 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
4738 if (!str) {
4739 return false;
4742 args.rval().setString(str);
4743 return true;
4747 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
4749 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
4750 // Steps 1-2.
4751 CallArgs args = CallArgsFromVp(argc, vp);
4752 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
4756 * Temporal.Duration.prototype.valueOf ( )
4758 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
4759 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
4760 "Duration", "primitive type");
4761 return false;
4764 const JSClass DurationObject::class_ = {
4765 "Temporal.Duration",
4766 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
4767 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
4768 JS_NULL_CLASS_OPS,
4769 &DurationObject::classSpec_,
4772 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
4774 static const JSFunctionSpec Duration_methods[] = {
4775 JS_FN("from", Duration_from, 1, 0),
4776 JS_FN("compare", Duration_compare, 2, 0),
4777 JS_FS_END,
4780 static const JSFunctionSpec Duration_prototype_methods[] = {
4781 JS_FN("with", Duration_with, 1, 0),
4782 JS_FN("negated", Duration_negated, 0, 0),
4783 JS_FN("abs", Duration_abs, 0, 0),
4784 JS_FN("add", Duration_add, 1, 0),
4785 JS_FN("subtract", Duration_subtract, 1, 0),
4786 JS_FN("round", Duration_round, 1, 0),
4787 JS_FN("total", Duration_total, 1, 0),
4788 JS_FN("toString", Duration_toString, 0, 0),
4789 JS_FN("toJSON", Duration_toJSON, 0, 0),
4790 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
4791 JS_FN("valueOf", Duration_valueOf, 0, 0),
4792 JS_FS_END,
4795 static const JSPropertySpec Duration_prototype_properties[] = {
4796 JS_PSG("years", Duration_years, 0),
4797 JS_PSG("months", Duration_months, 0),
4798 JS_PSG("weeks", Duration_weeks, 0),
4799 JS_PSG("days", Duration_days, 0),
4800 JS_PSG("hours", Duration_hours, 0),
4801 JS_PSG("minutes", Duration_minutes, 0),
4802 JS_PSG("seconds", Duration_seconds, 0),
4803 JS_PSG("milliseconds", Duration_milliseconds, 0),
4804 JS_PSG("microseconds", Duration_microseconds, 0),
4805 JS_PSG("nanoseconds", Duration_nanoseconds, 0),
4806 JS_PSG("sign", Duration_sign, 0),
4807 JS_PSG("blank", Duration_blank, 0),
4808 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
4809 JS_PS_END,
4812 const ClassSpec DurationObject::classSpec_ = {
4813 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
4814 GenericCreatePrototype<DurationObject>,
4815 Duration_methods,
4816 nullptr,
4817 Duration_prototype_methods,
4818 Duration_prototype_properties,
4819 nullptr,
4820 ClassSpec::DontDefineConstructor,