Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/builtin/temporal/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 115 kB image not shown  

Quelle  Duration.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "builtin/temporal/Duration.h"

#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"

#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <initializer_list>
#include <stdint.h>
#include <type_traits>
#include <utility>

#include "jsnum.h"
#include "jspubtd.h"
#include "NamespaceImports.h"

#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/CalendarFields.h"
#include "builtin/temporal/Instant.h"
#include "builtin/temporal/Int128.h"
#include "builtin/temporal/Int96.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/PlainTime.h"
#include "builtin/temporal/Temporal.h"
#include "builtin/temporal/TemporalParser.h"
#include "builtin/temporal/TemporalRoundingMode.h"
#include "builtin/temporal/TemporalTypes.h"
#include "builtin/temporal/TemporalUnit.h"
#include "builtin/temporal/TimeZone.h"
#include "builtin/temporal/ZonedDateTime.h"
#include "gc/AllocKind.h"
#include "gc/Barrier.h"
#include "gc/GCEnum.h"
#include "js/CallArgs.h"
#include "js/CallNonGenericMethod.h"
#include "js/Class.h"
#include "js/Conversions.h"
#include "js/ErrorReport.h"
#include "js/friend/ErrorMessages.h"
#include "js/Printer.h"
#include "js/PropertyDescriptor.h"
#include "js/PropertySpec.h"
#include "js/RootingAPI.h"
#include "js/Value.h"
#include "util/StringBuilder.h"
#include "vm/BytecodeUtil.h"
#include "vm/GlobalObject.h"
#include "vm/JSAtomState.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/PlainObject.h"
#include "vm/StringType.h"

#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectOperations-inl.h"

using namespace js;
using namespace js::temporal;

static inline bool IsDuration(Handle<Value> v) {
  return v.isObject() && v.toObject().is<DurationObject>();
}

#ifdef DEBUG
static bool IsIntegerOrInfinity(double d) {
  return IsInteger(d) || std::isinf(d);
}

static bool IsIntegerOrInfinityDuration(const Duration& duration) {
  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  // Integers exceeding the Number range are represented as infinity.

  return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
         IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
         IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
         IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
         IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
}

static bool IsIntegerDuration(const Duration& duration) {
  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
         IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
         IsInteger(seconds) && IsInteger(milliseconds) &&
         IsInteger(microseconds) && IsInteger(nanoseconds);
}
#endif

/**
 * DurationSign ( duration )
 */

int32_t js::temporal::DurationSign(const Duration& duration) {
  MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));

  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  // Step 1.
  for (auto v : {years, months, weeks, days, hours, minutes, seconds,
                 milliseconds, microseconds, nanoseconds}) {
    // Step 1.a.
    if (v < 0) {
      return -1;
    }

    // Step 1.b.
    if (v > 0) {
      return 1;
    }
  }

  // Step 2.
  return 0;
}

/**
 * DateDurationSign ( dateDuration )
 */

int32_t js::temporal::DateDurationSign(const DateDuration& duration) {
  const auto& [years, months, weeks, days] = duration;

  // Step 1.
  for (auto v : {years, months, weeks, days}) {
    // Step 1.a.
    if (v < 0) {
      return -1;
    }

    // Step 1.b.
    if (v > 0) {
      return 1;
    }
  }

  // Step 2.
  return 0;
}

/**
 * InternalDurationSign ( internalDuration )
 */

static int32_t InternalDurationSign(const InternalDuration& duration) {
  MOZ_ASSERT(IsValidDuration(duration));

  if (int32_t sign = DateDurationSign(duration.date)) {
    return sign;
  }
  return TimeDurationSign(duration.time);
}

/**
 * Create a time duration from a nanoseconds amount.
 */

static TimeDuration TimeDurationFromNanoseconds(const Int96& nanoseconds) {
  // Split into seconds and nanoseconds.
  auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);

  return {{seconds, nanos}};
}

/**
 * Create a time duration from a nanoseconds amount. Return Nothing if the value
 * is too large.
 */

static mozilla::Maybe<TimeDuration> TimeDurationFromNanoseconds(
    double nanoseconds) {
  MOZ_ASSERT(IsInteger(nanoseconds));

  if (auto int96 = Int96::fromInteger(nanoseconds)) {
    // The number of time duration seconds must not exceed `2**53 - 1`.
    constexpr auto limit =
        Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);

    if (int96->abs() < limit) {
      return mozilla::Some(TimeDurationFromNanoseconds(*int96));
    }
  }
  return mozilla::Nothing();
}

/**
 * Create a time duration from a microseconds amount.
 */

static TimeDuration TimeDurationFromMicroseconds(const Int96& microseconds) {
  // Split into seconds and microseconds.
  auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);

  // Scale microseconds to nanoseconds.
  int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));

  return {{seconds, nanos}};
}

/**
 * Create a time duration from a microseconds amount. Return Nothing if the
 * value is too large.
 */

static mozilla::Maybe<TimeDuration> TimeDurationFromMicroseconds(
    double microseconds) {
  MOZ_ASSERT(IsInteger(microseconds));

  if (auto int96 = Int96::fromInteger(microseconds)) {
    // The number of time duration seconds must not exceed `2**53 - 1`.
    constexpr auto limit =
        Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);

    if (int96->abs() < limit) {
      return mozilla::Some(TimeDurationFromMicroseconds(*int96));
    }
  }
  return mozilla::Nothing();
}

/**
 * Create a time duration from a duration. Return Nothing if any duration
 * value is too large.
 */

static mozilla::Maybe<TimeDuration> TimeDurationFromDuration(
    const Duration& duration) {
  do {
    auto nanoseconds = TimeDurationFromNanoseconds(duration.nanoseconds);
    if (!nanoseconds) {
      break;
    }
    MOZ_ASSERT(IsValidTimeDuration(*nanoseconds));

    auto microseconds = TimeDurationFromMicroseconds(duration.microseconds);
    if (!microseconds) {
      break;
    }
    MOZ_ASSERT(IsValidTimeDuration(*microseconds));

    // Overflows for millis/seconds/minutes/hours/days always result in an
    // invalid time duration.

    int64_t milliseconds;
    if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
      break;
    }

    int64_t seconds;
    if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
      break;
    }

    int64_t minutes;
    if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
      break;
    }

    int64_t hours;
    if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
      break;
    }

    int64_t days;
    if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
      break;
    }

    // Compute the overall amount of milliseconds.
    mozilla::CheckedInt64 millis = days;
    millis *= 24;
    millis += hours;
    millis *= 60;
    millis += minutes;
    millis *= 60;
    millis += seconds;
    millis *= 1000;
    millis += milliseconds;
    if (!millis.isValid()) {
      break;
    }

    auto milli = TimeDuration::fromMilliseconds(millis.value());
    if (!IsValidTimeDuration(milli)) {
      break;
    }

    // Compute the overall time duration.
    auto result = milli + *microseconds + *nanoseconds;
    if (!IsValidTimeDuration(result)) {
      break;
    }

    return mozilla::Some(result);
  } while (false);

  return mozilla::Nothing();
}

/**
 * TimeDurationFromComponents ( hours, minutes, seconds, milliseconds,
 * microseconds, nanoseconds )
 */

static TimeDuration TimeDurationFromComponents(double hours, double minutes,
                                               double seconds,
                                               double milliseconds,
                                               double microseconds,
                                               double nanoseconds) {
  MOZ_ASSERT(IsInteger(hours));
  MOZ_ASSERT(IsInteger(minutes));
  MOZ_ASSERT(IsInteger(seconds));
  MOZ_ASSERT(IsInteger(milliseconds));
  MOZ_ASSERT(IsInteger(microseconds));
  MOZ_ASSERT(IsInteger(nanoseconds));

  // Steps 1-3.
  mozilla::CheckedInt64 millis = int64_t(hours);
  millis *= 60;
  millis += int64_t(minutes);
  millis *= 60;
  millis += int64_t(seconds);
  millis *= 1000;
  millis += int64_t(milliseconds);
  MOZ_ASSERT(millis.isValid());

  auto timeDuration = TimeDuration::fromMilliseconds(millis.value());

  // Step 4.
  auto micros = Int96::fromInteger(microseconds);
  MOZ_ASSERT(micros);

  timeDuration += TimeDurationFromMicroseconds(*micros);

  // Step 5.
  auto nanos = Int96::fromInteger(nanoseconds);
  MOZ_ASSERT(nanos);

  timeDuration += TimeDurationFromNanoseconds(*nanos);

  // Step 6.
  MOZ_ASSERT(IsValidTimeDuration(timeDuration));

  // Step 7.
  return timeDuration;
}

/**
 * TimeDurationFromComponents ( hours, minutes, seconds, milliseconds,
 * microseconds, nanoseconds )
 */

TimeDuration js::temporal::TimeDurationFromComponents(
    const Duration& duration) {
  MOZ_ASSERT(IsValidDuration(duration));

  return ::TimeDurationFromComponents(
      duration.hours, duration.minutes, duration.seconds, duration.milliseconds,
      duration.microseconds, duration.nanoseconds);
}

/**
 * Add24HourDaysToTimeDuration ( d, days )
 */

static bool Add24HourDaysToTimeDuration(JSContext* cx, const TimeDuration& d,
                                        int64_t days, TimeDuration* result) {
  MOZ_ASSERT(IsValidTimeDuration(d));

  // Step 1.
  if (days > TimeDuration::max().toDays()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    return false;
  }

  auto timeDurationDays = TimeDuration::fromDays(days);
  if (!IsValidTimeDuration(timeDurationDays)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    return false;
  }

  // Step 2.
  auto sum = d + timeDurationDays;
  if (!IsValidTimeDuration(sum)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    return false;
  }

  // Step 3.
  *result = sum;
  return true;
}

/**
 * ToInternalDurationRecordWith24HourDays ( duration )
 */

InternalDuration js::temporal::ToInternalDurationRecordWith24HourDays(
    const Duration& duration) {
  MOZ_ASSERT(IsValidDuration(duration));

  // Step 1.
  auto timeDuration = TimeDurationFromComponents(duration);

  // Step 2. (Inlined Add24HourDaysToTimeDuration)
  timeDuration += TimeDuration::fromDays(int64_t(duration.days));

  // Step 3.
  auto dateDuration = DateDuration{
      int64_t(duration.years),
      int64_t(duration.months),
      int64_t(duration.weeks),
      0,
  };

  // Step 4. (Inlined CombineDateAndTimeDuration)
  return InternalDuration{dateDuration, timeDuration};
}

/**
 * ToDateDurationRecordWithoutTime ( duration )
 */

DateDuration js::temporal::ToDateDurationRecordWithoutTime(
    const Duration& duration) {
  // Step 1.
  auto internalDuration = ToInternalDurationRecordWith24HourDays(duration);

  // Step 2.
  int64_t days = internalDuration.time.toDays();

  // Step 3.
  auto result = DateDuration{
      internalDuration.date.years,
      internalDuration.date.months,
      internalDuration.date.weeks,
      days,
  };
  MOZ_ASSERT(IsValidDuration(result));

  return result;
}

/**
 * TemporalDurationFromInternal ( internalDuration, largestUnit )
 */

static Duration TemporalDurationFromInternal(const TimeDuration& timeDuration,
                                             TemporalUnit largestUnit) {
  MOZ_ASSERT(IsValidTimeDuration(timeDuration));
  MOZ_ASSERT(largestUnit <= TemporalUnit::Second,
             "fallible fractional seconds units");

  auto [seconds, nanoseconds] = timeDuration.denormalize();

  // Step 1.
  int64_t days = 0;
  int64_t hours = 0;
  int64_t minutes = 0;
  int64_t milliseconds = 0;
  int64_t microseconds = 0;

  // Steps 2-3. (Not applicable in our implementation.)
  //
  // We don't need to convert to positive numbers, because integer division
  // truncates and the %-operator has modulo semantics.

  // Steps 4-11.
  switch (largestUnit) {
    // Step 4.
    case TemporalUnit::Year:
    case TemporalUnit::Month:
    case TemporalUnit::Week:
    case TemporalUnit::Day: {
      // Step 4.a.
      microseconds = nanoseconds / 1000;

      // Step 4.b.
      nanoseconds = nanoseconds % 1000;

      // Step 4.c.
      milliseconds = microseconds / 1000;

      // Step 4.d.
      microseconds = microseconds % 1000;

      // Steps 4.e-f. (Not applicable)
      MOZ_ASSERT(std::abs(milliseconds) <= 999);

      // Step 4.g.
      minutes = seconds / 60;

      // Step 4.h.
      seconds = seconds % 60;

      // Step 4.i.
      hours = minutes / 60;

      // Step 4.j.
      minutes = minutes % 60;

      // Step 4.k.
      days = hours / 24;

      // Step 4.l.
      hours = hours % 24;

      break;
    }

      // Step 5.
    case TemporalUnit::Hour: {
      // Step 5.a.
      microseconds = nanoseconds / 1000;

      // Step 5.b.
      nanoseconds = nanoseconds % 1000;

      // Step 5.c.
      milliseconds = microseconds / 1000;

      // Step 5.d.
      microseconds = microseconds % 1000;

      // Steps 5.e-f. (Not applicable)
      MOZ_ASSERT(std::abs(milliseconds) <= 999);

      // Step 5.g.
      minutes = seconds / 60;

      // Step 5.h.
      seconds = seconds % 60;

      // Step 5.i.
      hours = minutes / 60;

      // Step 5.j.
      minutes = minutes % 60;

      break;
    }

    case TemporalUnit::Minute: {
      // Step 6.a.
      microseconds = nanoseconds / 1000;

      // Step 6.b.
      nanoseconds = nanoseconds % 1000;

      // Step 6.c.
      milliseconds = microseconds / 1000;

      // Step 6.d.
      microseconds = microseconds % 1000;

      // Steps 6.e-f. (Not applicable)
      MOZ_ASSERT(std::abs(milliseconds) <= 999);

      // Step 6.g.
      minutes = seconds / 60;

      // Step 6.h.
      seconds = seconds % 60;

      break;
    }

    // Step 7.
    case TemporalUnit::Second: {
      // Step 7.a.
      microseconds = nanoseconds / 1000;

      // Step 7.b.
      nanoseconds = nanoseconds % 1000;

      // Step 7.c.
      milliseconds = microseconds / 1000;

      // Step 7.d.
      microseconds = microseconds % 1000;

      // Steps 7.e-f. (Not applicable)
      MOZ_ASSERT(std::abs(milliseconds) <= 999);

      break;
    }

    // Steps 8-11. (Not applicable in our implementation)
    case TemporalUnit::Millisecond:
    case TemporalUnit::Microsecond:
    case TemporalUnit::Nanosecond:
    case TemporalUnit::Auto:
      MOZ_CRASH("Unexpected temporal unit");
  }

  // Step 12.
  auto result = Duration{
      0,
      0,
      0,
      double(days),
      double(hours),
      double(minutes),
      double(seconds),
      double(milliseconds),
      double(microseconds),
      double(nanoseconds),
  };
  MOZ_ASSERT(IsValidDuration(result));
  return result;
}

/**
 * TemporalDurationFromInternal ( internalDuration, largestUnit )
 */

bool js::temporal::TemporalDurationFromInternal(
    JSContext* cx, const TimeDuration& timeDuration, TemporalUnit largestUnit,
    Duration* result) {
  MOZ_ASSERT(IsValidTimeDuration(timeDuration));

  auto [seconds, nanoseconds] = timeDuration.denormalize();

  // Steps 1-3. (Not applicable in our implementation.)
  //
  // We don't need to convert to positive numbers, because integer division
  // truncates and the %-operator has modulo semantics.

  // Steps 4-10.
  switch (largestUnit) {
    // Steps 4-7.
    case TemporalUnit::Year:
    case TemporalUnit::Month:
    case TemporalUnit::Week:
    case TemporalUnit::Day:
    case TemporalUnit::Hour:
    case TemporalUnit::Minute:
    case TemporalUnit::Second:
      *result = ::TemporalDurationFromInternal(timeDuration, largestUnit);
      return true;

    // Step 8.
    case TemporalUnit::Millisecond: {
      // Valid time durations must be below |limit|.
      constexpr auto limit = TimeDuration::max().toMilliseconds() + 1;

      // The largest possible milliseconds value whose double representation
      // doesn't exceed the time duration limit.
      constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);

      // Assert |max| is the maximum allowed milliseconds value.
      static_assert(double(max) < double(limit));
      static_assert(double(max + 1) >= double(limit));

      static_assert((TimeDuration::max().seconds + 1) *
                            ToMilliseconds(TemporalUnit::Second) <=
                        INT64_MAX,
                    "total number duration milliseconds fits into int64");

      // Step 8.a.
      int64_t microseconds = nanoseconds / 1000;

      // Step 8.b.
      nanoseconds = nanoseconds % 1000;

      // Step 8.c.
      int64_t milliseconds = microseconds / 1000;
      MOZ_ASSERT(std::abs(milliseconds) <= 999);

      // Step 8.d.
      microseconds = microseconds % 1000;

      auto millis =
          (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
      if (std::abs(millis) > max) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr,
            JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
        return false;
      }

      // Step 11.
      *result = {0,
                 0,
                 0,
                 0,
                 0,
                 0,
                 0,
                 double(millis),
                 double(microseconds),
                 double(nanoseconds)};
      MOZ_ASSERT(IsValidDuration(*result));
      return true;
    }

    // Step 9.
    case TemporalUnit::Microsecond: {
      // Valid time durations must be below |limit|.
      constexpr auto limit =
          Uint128{TimeDuration::max().toMicroseconds()} + Uint128{1};

      // The largest possible microseconds value whose double representation
      // doesn't exceed the time duration limit.
      constexpr auto max =
          (Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff};
      static_assert(max < limit);

      // Assert |max| is the maximum allowed microseconds value.
      MOZ_ASSERT(double(max) < double(limit));
      MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));

      // Step 9.a.
      int64_t microseconds = nanoseconds / 1000;
      MOZ_ASSERT(std::abs(microseconds) <= 999'999);

      // Step 9.b.
      nanoseconds = nanoseconds % 1000;

      auto micros =
          (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
          Int128{microseconds};
      if (micros.abs() > max) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr,
            JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
        return false;
      }

      // Step 11.
      *result = {0, 0, 0, 0, 0, 0, 0, 0, double(micros), double(nanoseconds)};
      MOZ_ASSERT(IsValidDuration(*result));
      return true;
    }

    // Step 10.
    case TemporalUnit::Nanosecond: {
      // Valid time durations must be below |limit|.
      constexpr auto limit =
          Uint128{TimeDuration::max().toNanoseconds()} + Uint128{1};

      // The largest possible nanoseconds value whose double representation
      // doesn't exceed the time duration limit.
      constexpr auto max =
          (Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff};
      static_assert(max < limit);

      // Assert |max| is the maximum allowed nanoseconds value.
      MOZ_ASSERT(double(max) < double(limit));
      MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));

      MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);

      auto nanos =
          (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
          Int128{nanoseconds};
      if (nanos.abs() > max) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr,
            JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
        return false;
      }

      // Step 11.
      *result = {0, 0, 0, 0, 0, 0, 0, 0, 0, double(nanos)};
      MOZ_ASSERT(IsValidDuration(*result));
      return true;
    }

    case TemporalUnit::Auto:
      break;
  }
  MOZ_CRASH("Unexpected temporal unit");
}

/**
 * TemporalDurationFromInternal ( internalDuration, largestUnit )
 */

bool js::temporal::TemporalDurationFromInternal(
    JSContext* cx, const InternalDuration& internalDuration,
    TemporalUnit largestUnit, Duration* result) {
  MOZ_ASSERT(IsValidDuration(internalDuration.date));
  MOZ_ASSERT(IsValidTimeDuration(internalDuration.time));

  // Steps 1-11.
  Duration duration;
  if (!TemporalDurationFromInternal(cx, internalDuration.time, largestUnit,
                                    &duration)) {
    return false;
  }
  MOZ_ASSERT(IsValidDuration(duration));

  // Step 12.
  auto days = mozilla::CheckedInt64(internalDuration.date.days) +
              mozilla::AssertedCast<int64_t>(duration.days);
  MOZ_ASSERT(days.isValid(), "valid duration days can't overflow");

  *result = {
      double(internalDuration.date.years),
      double(internalDuration.date.months),
      double(internalDuration.date.weeks),
      double(days.value()),
      duration.hours,
      duration.minutes,
      duration.seconds,
      duration.milliseconds,
      duration.microseconds,
      duration.nanoseconds,
  };
  return ThrowIfInvalidDuration(cx, *result);
}

/**
 * TimeDurationFromEpochNanosecondsDifference ( one, two )
 */

TimeDuration js::temporal::TimeDurationFromEpochNanosecondsDifference(
    const EpochNanoseconds& one, const EpochNanoseconds& two) {
  MOZ_ASSERT(IsValidEpochNanoseconds(one));
  MOZ_ASSERT(IsValidEpochNanoseconds(two));

  // Step 1.
  auto result = one - two;

  // Step 2.
  MOZ_ASSERT(IsValidEpochDuration(result));

  // Step 3.
  return result.to<TimeDuration>();
}

#ifdef DEBUG
/**
 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
 * milliseconds, microseconds, nanoseconds )
 */

bool js::temporal::IsValidDuration(const Duration& duration) {
  MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));

  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  // Step 1.
  int32_t sign = 0;

  // Step 2.
  for (auto v : {years, months, weeks, days, hours, minutes, seconds,
                 milliseconds, microseconds, nanoseconds}) {
    // Step 2.a.
    if (!std::isfinite(v)) {
      return false;
    }

    // Step 2.b.
    if (v < 0) {
      // Step 2.b.i.
      if (sign > 0) {
        return false;
      }

      // Step 2.b.ii.
      sign = -1;
    }

    // Step 2.c.
    else if (v > 0) {
      // Step 2.c.i.
      if (sign < 0) {
        return false;
      }

      // Step 2.c.ii.
      sign = 1;
    }
  }

  // Step 3.
  if (std::abs(years) >= double(int64_t(1) << 32)) {
    return false;
  }

  // Step 4.
  if (std::abs(months) >= double(int64_t(1) << 32)) {
    return false;
  }

  // Step 5.
  if (std::abs(weeks) >= double(int64_t(1) << 32)) {
    return false;
  }

  // Steps 6-8.
  if (!TimeDurationFromDuration(duration)) {
    return false;
  }

  // Step 9.
  return true;
}

/**
 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
 * milliseconds, microseconds, nanoseconds )
 */

bool js::temporal::IsValidDuration(const DateDuration& duration) {
  return IsValidDuration(duration.toDuration());
}

/**
 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
 * milliseconds, microseconds, nanoseconds )
 */

bool js::temporal::IsValidDuration(const InternalDuration& duration) {
  if (!IsValidTimeDuration(duration.time)) {
    return false;
  }

  auto d = duration.date.toDuration();
  auto [seconds, nanoseconds] = duration.time.denormalize();
  d.seconds = double(seconds);
  d.nanoseconds = double(nanoseconds);

  return IsValidDuration(d);
}
#endif

static bool ThrowInvalidDurationPart(JSContext* cx, double value,
                                     const char* name, unsigned errorNumber) {
  ToCStringBuf cbuf;
  const char* numStr = NumberToCString(&cbuf, value);

  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
                            numStr);
  return false;
}

/**
 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
 * milliseconds, microseconds, nanoseconds )
 */

bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
                                          const Duration& duration) {
  MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));

  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  // Step 1.
  int32_t sign = DurationSign(duration);

  auto throwIfInvalid = [&](double v, const char* name) {
    // Step 2.a.
    if (!std::isfinite(v)) {
      return ThrowInvalidDurationPart(
          cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
    }

    // Steps 2.b-c.
    if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
      return ThrowInvalidDurationPart(cx, v, name,
                                      JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
    }

    return true;
  };

  auto throwIfTooLarge = [&](double v, const char* name) {
    if (std::abs(v) >= double(int64_t(1) << 32)) {
      return ThrowInvalidDurationPart(
          cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
    }
    return true;
  };

  // Step 2.
  if (!throwIfInvalid(years, "years")) {
    return false;
  }
  if (!throwIfInvalid(months, "months")) {
    return false;
  }
  if (!throwIfInvalid(weeks, "weeks")) {
    return false;
  }
  if (!throwIfInvalid(days, "days")) {
    return false;
  }
  if (!throwIfInvalid(hours, "hours")) {
    return false;
  }
  if (!throwIfInvalid(minutes, "minutes")) {
    return false;
  }
  if (!throwIfInvalid(seconds, "seconds")) {
    return false;
  }
  if (!throwIfInvalid(milliseconds, "milliseconds")) {
    return false;
  }
  if (!throwIfInvalid(microseconds, "microseconds")) {
    return false;
  }
  if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
    return false;
  }

  // Step 3.
  if (!throwIfTooLarge(years, "years")) {
    return false;
  }

  // Step 4.
  if (!throwIfTooLarge(months, "months")) {
    return false;
  }

  // Step 5.
  if (!throwIfTooLarge(weeks, "weeks")) {
    return false;
  }

  // Steps 6-8.
  if (!TimeDurationFromDuration(duration)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    return false;
  }

  MOZ_ASSERT(IsValidDuration(duration));

  // Step 9.
  return true;
}

/**
 * DefaultTemporalLargestUnit ( duration )
 */

static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
  MOZ_ASSERT(IsIntegerDuration(duration));

  // Step 1.
  if (duration.years != 0) {
    return TemporalUnit::Year;
  }

  // Step 2.
  if (duration.months != 0) {
    return TemporalUnit::Month;
  }

  // Step 3.
  if (duration.weeks != 0) {
    return TemporalUnit::Week;
  }

  // Step 4.
  if (duration.days != 0) {
    return TemporalUnit::Day;
  }

  // Step 5.
  if (duration.hours != 0) {
    return TemporalUnit::Hour;
  }

  // Step 6.
  if (duration.minutes != 0) {
    return TemporalUnit::Minute;
  }

  // Step 7.
  if (duration.seconds != 0) {
    return TemporalUnit::Second;
  }

  // Step 8.
  if (duration.milliseconds != 0) {
    return TemporalUnit::Millisecond;
  }

  // Step 9.
  if (duration.microseconds != 0) {
    return TemporalUnit::Microsecond;
  }

  // Step 10.
  return TemporalUnit::Nanosecond;
}

/**
 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
 */

static DurationObject* CreateTemporalDuration(JSContext* cx,
                                              const CallArgs& args,
                                              const Duration& duration) {
  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  // Step 1.
  if (!ThrowIfInvalidDuration(cx, duration)) {
    return nullptr;
  }

  // Steps 2-3.
  Rooted<JSObject*> proto(cx);
  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
    return nullptr;
  }

  auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
  if (!object) {
    return nullptr;
  }

  // Steps 4-13.
  // Add zero to convert -0 to +0.
  object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
  object->setFixedSlot(DurationObject::MONTHS_SLOT,
                       NumberValue(months + (+0.0)));
  object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
  object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
  object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
  object->setFixedSlot(DurationObject::MINUTES_SLOT,
                       NumberValue(minutes + (+0.0)));
  object->setFixedSlot(DurationObject::SECONDS_SLOT,
                       NumberValue(seconds + (+0.0)));
  object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
                       NumberValue(milliseconds + (+0.0)));
  object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
                       NumberValue(microseconds + (+0.0)));
  object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
                       NumberValue(nanoseconds + (+0.0)));

  // Step 14.
  return object;
}

/**
 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
 */

DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
                                                     const Duration& duration) {
  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] = duration;

  MOZ_ASSERT(IsInteger(years));
  MOZ_ASSERT(IsInteger(months));
  MOZ_ASSERT(IsInteger(weeks));
  MOZ_ASSERT(IsInteger(days));
  MOZ_ASSERT(IsInteger(hours));
  MOZ_ASSERT(IsInteger(minutes));
  MOZ_ASSERT(IsInteger(seconds));
  MOZ_ASSERT(IsInteger(milliseconds));
  MOZ_ASSERT(IsInteger(microseconds));
  MOZ_ASSERT(IsInteger(nanoseconds));

  // Step 1.
  if (!ThrowIfInvalidDuration(cx, duration)) {
    return nullptr;
  }

  // Steps 2-3.
  auto* object = NewBuiltinClassInstance<DurationObject>(cx);
  if (!object) {
    return nullptr;
  }

  // Steps 4-13.
  // Add zero to convert -0 to +0.
  object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
  object->setFixedSlot(DurationObject::MONTHS_SLOT,
                       NumberValue(months + (+0.0)));
  object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
  object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
  object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
  object->setFixedSlot(DurationObject::MINUTES_SLOT,
                       NumberValue(minutes + (+0.0)));
  object->setFixedSlot(DurationObject::SECONDS_SLOT,
                       NumberValue(seconds + (+0.0)));
  object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
                       NumberValue(milliseconds + (+0.0)));
  object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
                       NumberValue(microseconds + (+0.0)));
  object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
                       NumberValue(nanoseconds + (+0.0)));

  // Step 14.
  return object;
}

/**
 * ToIntegerIfIntegral ( argument )
 */

static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
                                Handle<Value> argument, double* num) {
  // Step 1.
  double d;
  if (!JS::ToNumber(cx, argument, &d)) {
    return false;
  }

  // Step 2.
  if (!js::IsInteger(d)) {
    ToCStringBuf cbuf;
    const char* numStr = NumberToCString(&cbuf, d);

    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
                              name);
    return false;
  }

  // Step 3.
  *num = d;
  return true;
}

/**
 * ToIntegerIfIntegral ( argument )
 */

static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
                                Handle<Value> argument, double* result) {
  // Step 1.
  double d;
  if (!JS::ToNumber(cx, argument, &d)) {
    return false;
  }

  // Step 2.
  if (!js::IsInteger(d)) {
    if (auto nameStr = js::QuoteString(cx, name)) {
      ToCStringBuf cbuf;
      const char* numStr = NumberToCString(&cbuf, d);

      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
                                nameStr.get());
    }
    return false;
  }

  // Step 3.
  *result = d;
  return true;
}

/**
 * ToTemporalPartialDurationRecord ( temporalDurationLike )
 */

static bool ToTemporalPartialDurationRecord(
    JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
  // Steps 1-3. (Not applicable in our implementation.)

  Rooted<Value> value(cx);
  bool any = false;

  auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
    if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
                     &value)) {
      return false;
    }

    if (!value.isUndefined()) {
      any = true;

      if (!ToIntegerIfIntegral(cx, name, value, num)) {
        return false;
      }
    }
    return true;
  };

  // Steps 4-23.
  if (!getDurationProperty(cx->names().days, &result->days)) {
    return false;
  }
  if (!getDurationProperty(cx->names().hours, &result->hours)) {
    return false;
  }
  if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
    return false;
  }
  if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
    return false;
  }
  if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
    return false;
  }
  if (!getDurationProperty(cx->names().months, &result->months)) {
    return false;
  }
  if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
    return false;
  }
  if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
    return false;
  }
  if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
    return false;
  }
  if (!getDurationProperty(cx->names().years, &result->years)) {
    return false;
  }

  // Step 24.
  if (!any) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
    return false;
  }

  // Step 25.
  return true;
}

/**
 * ToTemporalDuration ( item )
 */

bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
                                      Duration* result) {
  // Steps 1 and 3-15.
  if (item.isObject()) {
    Rooted<JSObject*> itemObj(cx, &item.toObject());

    // Step 1.
    if (auto* duration = itemObj->maybeUnwrapIf<DurationObject>()) {
      *result = ToDuration(duration);
      return true;
    }

    // Step 3. (Reordered)
    Duration duration = {};

    // Steps 4-14.
    if (!ToTemporalPartialDurationRecord(cx, itemObj, &duration)) {
      return false;
    }

    // Step 15.
    if (!ThrowIfInvalidDuration(cx, duration)) {
      return false;
    }

    *result = duration;
    return true;
  }

  // Step 2.a.
  if (!item.isString()) {
    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
                     nullptr, "not a string");
    return false;
  }
  Rooted<JSString*> string(cx, item.toString());

  // Step 2.b.
  return ParseTemporalDurationString(cx, string, result);
}

/**
 * DateDurationDays ( dateDuration, plainRelativeTo )
 */

static bool DateDurationDays(JSContext* cx, const DateDuration& duration,
                             Handle<PlainDate> plainRelativeTo,
                             int64_t* result) {
  MOZ_ASSERT(IsValidDuration(duration));

  auto [years, months, weeks, days] = duration;

  // Step 1.
  auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};

  // Step 2.
  if (yearsMonthsWeeksDuration == DateDuration{}) {
    *result = days;
    return true;
  }

  // Moved from caller.
  if (!plainRelativeTo) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
                              "relativeTo");
    return false;
  }

  // Step 3.
  ISODate later;
  if (!CalendarDateAdd(cx, plainRelativeTo.calendar(), plainRelativeTo,
                       yearsMonthsWeeksDuration, TemporalOverflow::Constrain,
                       &later)) {
    return false;
  }

  // Step 4.
  int32_t epochDays1 = MakeDay(plainRelativeTo);
  MOZ_ASSERT(MinEpochDay <= epochDays1 && epochDays1 <= MaxEpochDay);

  // Step 5.
  int32_t epochDays2 = MakeDay(later);
  MOZ_ASSERT(MinEpochDay <= epochDays2 && epochDays2 <= MaxEpochDay);

  // Step 4.
  int32_t yearsMonthsWeeksInDay = epochDays2 - epochDays1;

  // Step 5.
  *result = days + yearsMonthsWeeksInDay;
  return true;
}

static bool NumberToStringBuilder(JSContext* cx, double num,
                                  JSStringBuilder& sb) {
  MOZ_ASSERT(IsInteger(num));
  MOZ_ASSERT(num >= 0);
  MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);

  ToCStringBuf cbuf;
  size_t length;
  const char* numStr = NumberToCString(&cbuf, num, &length);

  return sb.append(numStr, length);
}

static Duration AbsoluteDuration(const Duration& duration) {
  return {
      std::abs(duration.years),        std::abs(duration.months),
      std::abs(duration.weeks),        std::abs(duration.days),
      std::abs(duration.hours),        std::abs(duration.minutes),
      std::abs(duration.seconds),      std::abs(duration.milliseconds),
      std::abs(duration.microseconds), std::abs(duration.nanoseconds),
  };
}

/**
 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
 */

[[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
                                                  int32_t subSecondNanoseconds,
                                                  Precision precision) {
  MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
  MOZ_ASSERT(precision != Precision::Minute());

  // Steps 1-2.
  if (precision == Precision::Auto()) {
    // Step 1.a.
    if (subSecondNanoseconds == 0) {
      return true;
    }

    // Step 3. (Reordered)
    if (!result.append('.')) {
      return false;
    }

    // Steps 1.b-c.
    int32_t k = 100'000'000;
    do {
      if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
        return false;
      }
      subSecondNanoseconds %= k;
      k /= 10;
    } while (subSecondNanoseconds);
  } else {
    // Step 2.a.
    uint8_t p = precision.value();
    if (p == 0) {
      return true;
    }

    // Step 3. (Reordered)
    if (!result.append('.')) {
      return false;
    }

    // Steps 2.b-c.
    int32_t k = 100'000'000;
    for (uint8_t i = 0; i < precision.value(); i++) {
      if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
        return false;
      }
      subSecondNanoseconds %= k;
      k /= 10;
    }
  }

  return true;
}

/**
 * TemporalDurationToString ( duration, precision )
 */

static JSString* TemporalDurationToString(JSContext* cx,
                                          const Duration& duration,
                                          Precision precision) {
  MOZ_ASSERT(IsValidDuration(duration));
  MOZ_ASSERT(precision != Precision::Minute());

  // Fast path for zero durations.
  if (duration == Duration{} &&
      (precision == Precision::Auto() || precision.value() == 0)) {
    return NewStringCopyZ<CanGC>(cx, "PT0S");
  }

  // Convert to absolute values up front. This is okay to do, because when the
  // duration is valid, all components have the same sign.
  const auto& [years, months, weeks, days, hours, minutes, seconds,
               milliseconds, microseconds, nanoseconds] =
      AbsoluteDuration(duration);

  // Years to seconds parts are all safe integers for valid durations.
  MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
  MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
  MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
  MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
  MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
  MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
  MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);

  // Step 1.
  int32_t sign = DurationSign(duration);

  // Steps 2 and 7.
  JSStringBuilder result(cx);

  // Step 14. (Reordered)
  if (sign < 0) {
    if (!result.append('-')) {
      return nullptr;
    }
  }

  // Step 15. (Reordered)
  if (!result.append('P')) {
    return nullptr;
  }

  // Step 3.
  if (years != 0) {
    if (!NumberToStringBuilder(cx, years, result)) {
      return nullptr;
    }
    if (!result.append('Y')) {
      return nullptr;
    }
  }

  // Step 4.
  if (months != 0) {
    if (!NumberToStringBuilder(cx, months, result)) {
      return nullptr;
    }
    if (!result.append('M')) {
      return nullptr;
    }
  }

  // Step 5.
  if (weeks != 0) {
    if (!NumberToStringBuilder(cx, weeks, result)) {
      return nullptr;
    }
    if (!result.append('W')) {
      return nullptr;
    }
  }

  // Step 6.
  if (days != 0) {
    if (!NumberToStringBuilder(cx, days, result)) {
      return nullptr;
    }
    if (!result.append('D')) {
      return nullptr;
    }
  }

  // Step 7. (Moved above)

  // Steps 10-11. (Reordered)
  bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
                              days == 0 && hours == 0 && minutes == 0;

  // Step 12.
  auto secondsDuration = TimeDurationFromComponents(
      0.0, 0.0, seconds, milliseconds, microseconds, nanoseconds);

  // Steps 8-9, 13, and 16.
  bool hasSecondsPart = (secondsDuration != TimeDuration{}) ||
                        zeroMinutesAndHigher || precision != Precision::Auto();
  if (hours != 0 || minutes != 0 || hasSecondsPart) {
    // Step 16. (Reordered)
    if (!result.append('T')) {
      return nullptr;
    }

    // Step 8.
    if (hours != 0) {
      if (!NumberToStringBuilder(cx, hours, result)) {
        return nullptr;
      }
      if (!result.append('H')) {
        return nullptr;
      }
    }

    // Step 9.
    if (minutes != 0) {
      if (!NumberToStringBuilder(cx, minutes, result)) {
        return nullptr;
      }
      if (!result.append('M')) {
        return nullptr;
      }
    }

    // Step 13.
    if (hasSecondsPart) {
      // Step 13.a.
      if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
        return nullptr;
      }

      // Step 13.b.
      if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
                                   precision)) {
        return nullptr;
      }

      // Step 13.c.
      if (!result.append('S')) {
        return nullptr;
      }
    }
  }

  // Steps 14-16. (Moved above)

  // Step 17.
  return result.finishString();
}

/**
 * GetTemporalRelativeToOption ( options )
 */

static bool GetTemporalRelativeToOption(
    JSContext* cx, Handle<JSObject*> options,
    MutableHandle<PlainDate> plainRelativeTo,
    MutableHandle<ZonedDateTime> zonedRelativeTo) {
  // Default initialize both return values.
  plainRelativeTo.set(PlainDate{});
  zonedRelativeTo.set(ZonedDateTime{});

  // Step 1.
  Rooted<Value> value(cx);
  if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
    return false;
  }

  // Step 2.
  if (value.isUndefined()) {
    return true;
  }

  // Step 3.
  auto offsetBehaviour = OffsetBehaviour::Option;

  // Step 4.
  auto matchBehaviour = MatchBehaviour::MatchExactly;

  // Steps 5-6.
  EpochNanoseconds epochNanoseconds;
  Rooted<TimeZoneValue> timeZone(cx);
  Rooted<CalendarValue> calendar(cx);
  if (value.isObject()) {
    Rooted<JSObject*> obj(cx, &value.toObject());

    // Step 5.a.
    if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
      auto epochNs = zonedDateTime->epochNanoseconds();
      Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
      Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());

      if (!timeZone.wrap(cx)) {
        return false;
      }
      if (!calendar.wrap(cx)) {
        return false;
      }

      // Step 5.a.i.
      zonedRelativeTo.set(ZonedDateTime{epochNs, timeZone, calendar});
      return true;
    }

    // Step 5.b.
    if (auto* plainDate = obj->maybeUnwrapIf<PlainDateObject>()) {
      auto date = plainDate->date();

      Rooted<CalendarValue> calendar(cx, plainDate->calendar());
      if (!calendar.wrap(cx)) {
        return false;
      }

      // Step 5.b.i.
      plainRelativeTo.set(PlainDate{date, calendar});
      return true;
    }

    // Step 5.c.
    if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
      auto date = dateTime->date();

      Rooted<CalendarValue> calendar(cx, dateTime->calendar());
      if (!calendar.wrap(cx)) {
        return false;
      }

      // Steps 5.c.i-ii.
      plainRelativeTo.set(PlainDate{date, calendar});
      return true;
    }

    // Step 5.d.
    if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
      return false;
    }

    // Step 5.e.
    Rooted<CalendarFields> fields(cx);
    if (!PrepareCalendarFields(cx, calendar, obj,
                               {
                                   CalendarField::Year,
                                   CalendarField::Month,
                                   CalendarField::MonthCode,
                                   CalendarField::Day,
                                   CalendarField::Hour,
                                   CalendarField::Minute,
                                   CalendarField::Second,
                                   CalendarField::Millisecond,
                                   CalendarField::Microsecond,
                                   CalendarField::Nanosecond,
                                   CalendarField::Offset,
                                   CalendarField::TimeZone,
                               },
                               &fields)) {
      return false;
    }

    // Step 5.f.
    ISODateTime dateTime;
    if (!InterpretTemporalDateTimeFields(
            cx, calendar, fields, TemporalOverflow::Constrain, &dateTime)) {
      return false;
    }

    // Step 5.g.
    timeZone = fields.timeZone();

    // Step 5.h.
    auto offset = fields.offset();

    // Step 5.j.
    if (!fields.has(CalendarField::Offset)) {
      offsetBehaviour = OffsetBehaviour::Wall;
    }

    // Step 7.
    if (!timeZone) {
      // Steps 7.a-b.
      return CreateTemporalDate(cx, dateTime.date, calendar, plainRelativeTo);
    }

    // Steps 8-9.
    int64_t offsetNs = 0;
    if (offsetBehaviour == OffsetBehaviour::Option) {
      // Step 8.a.
      offsetNs = int64_t(offset);
    }

    // Step 10.
    if (!InterpretISODateTimeOffset(
            cx, dateTime, offsetBehaviour, offsetNs, timeZone,
            TemporalDisambiguation::Compatible, TemporalOffset::Reject,
            matchBehaviour, &epochNanoseconds)) {
      return false;
    }
  } else {
    // Step 6.a.
    if (!value.isString()) {
      ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
                       nullptr, "not a string");
      return false;
    }
    Rooted<JSString*> string(cx, value.toString());

    // Step 6.b.
    Rooted<ParsedZonedDateTime> parsed(cx);
    if (!ParseTemporalRelativeToString(cx, string, &parsed)) {
      return false;
    }

    // Steps 6.c-e. (Not applicable in our implementation.)

    // Step 6.f.
    if (parsed.timeZoneAnnotation()) {
      // Step 6.f.i.
      if (!ToTemporalTimeZone(cx, parsed.timeZoneAnnotation(), &timeZone)) {
        return false;
      }

      // Steps 6.f.ii-iii.
      if (parsed.isUTC()) {
        offsetBehaviour = OffsetBehaviour::Exact;
      } else if (!parsed.hasOffset()) {
        offsetBehaviour = OffsetBehaviour::Wall;
      }

      // Step 6.f.iv.
      matchBehaviour = MatchBehaviour::MatchMinutes;
    } else {
      MOZ_ASSERT(!timeZone);
    }

    // Steps 6.g-i.
    if (parsed.calendar()) {
      if (!CanonicalizeCalendar(cx, parsed.calendar(), &calendar)) {
        return false;
      }
    } else {
      calendar.set(CalendarValue(CalendarId::ISO8601));
    }

    // Step 7.
    if (!timeZone) {
      // Steps 7.a-b.
      return CreateTemporalDate(cx, parsed.dateTime().date, calendar,
                                plainRelativeTo);
    }

    // Steps 8-9.
    int64_t offsetNs;
    if (offsetBehaviour == OffsetBehaviour::Option) {
      MOZ_ASSERT(parsed.hasOffset());

      // Step 8.a.
      offsetNs = parsed.timeZoneOffset();
    } else {
      // Step 9.
      offsetNs = 0;
    }

    // Step 10.
    if (parsed.isStartOfDay()) {
      if (!InterpretISODateTimeOffset(
              cx, parsed.dateTime().date, offsetBehaviour, offsetNs, timeZone,
              TemporalDisambiguation::Compatible, TemporalOffset::Reject,
              matchBehaviour, &epochNanoseconds)) {
        return false;
      }
    } else {
      if (!InterpretISODateTimeOffset(
              cx, parsed.dateTime(), offsetBehaviour, offsetNs, timeZone,
              TemporalDisambiguation::Compatible, TemporalOffset::Reject,
              matchBehaviour, &epochNanoseconds)) {
        return false;
      }
    }
  }
  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));

  // Steps 11-12.
  zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
  return true;
}

/**
 * RoundTimeDurationToIncrement ( d, increment, roundingMode )
 */

static TimeDuration RoundTimeDurationToIncrement(
    const TimeDuration& duration, const TemporalUnit unit, Increment increment,
    TemporalRoundingMode roundingMode) {
  MOZ_ASSERT(IsValidTimeDuration(duration));
  MOZ_ASSERT(unit >= TemporalUnit::Day);
  MOZ_ASSERT_IF(unit >= TemporalUnit::Hour,
                increment <= MaximumTemporalDurationRoundingIncrement(unit));

  auto divisor = Int128{ToNanoseconds(unit)} * Int128{increment.value()};
  MOZ_ASSERT(divisor > Int128{0});
  MOZ_ASSERT_IF(unit >= TemporalUnit::Hour,
                divisor <= Int128{ToNanoseconds(TemporalUnit::Day)});

  auto totalNanoseconds = duration.toNanoseconds();
  auto rounded =
      RoundNumberToIncrement(totalNanoseconds, divisor, roundingMode);
  return TimeDuration::fromNanoseconds(rounded);
}

/**
 * TotalTimeDuration ( timeDuration, unit )
 */

double js::temporal::TotalTimeDuration(const TimeDuration& duration,
                                       TemporalUnit unit) {
  MOZ_ASSERT(IsValidTimeDuration(duration));
  MOZ_ASSERT(unit >= TemporalUnit::Day);

  auto numerator = duration.toNanoseconds();
  auto denominator = Int128{ToNanoseconds(unit)};
  return FractionToDouble(numerator, denominator);
}

/**
 * RoundTimeDuration ( duration, increment, unit, roundingMode )
 */

static bool RoundTimeDuration(JSContext* cx, const TimeDuration& duration,
                              Increment increment, TemporalUnit unit,
                              TemporalRoundingMode roundingMode,
                              TimeDuration* result) {
  MOZ_ASSERT(IsValidTimeDuration(duration));
  MOZ_ASSERT(increment <= Increment::max());
  MOZ_ASSERT(unit > TemporalUnit::Day);

  // Step 1-2.
  auto rounded =
      RoundTimeDurationToIncrement(duration, unit, increment, roundingMode);
  if (!IsValidTimeDuration(rounded)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    return false;
  }
  *result = rounded;
  return true;
}

/**
 * RoundTimeDuration ( duration, increment, unit, roundingMode )
 */

TimeDuration js::temporal::RoundTimeDuration(
    const TimeDuration& duration, Increment increment, TemporalUnit unit,
    TemporalRoundingMode roundingMode) {
  MOZ_ASSERT(IsValidTimeDuration(duration));
  MOZ_ASSERT(increment <= Increment::max());
  MOZ_ASSERT(unit > TemporalUnit::Day);

  auto result =
      RoundTimeDurationToIncrement(duration, unit, increment, roundingMode);
  MOZ_ASSERT(IsValidTimeDuration(result));

  return result;
}

#ifdef DEBUG
/**
 * Return true if the input is within the valid epoch nanoseconds limits with a
 * time zone offset applied, i.e. it's smaller than ±(8.64 × 10^21 + nsPerDay).
 */

static bool IsValidLocalNanoseconds(const EpochNanoseconds& epochNanoseconds) {
  MOZ_ASSERT(0 <= epochNanoseconds.nanoseconds &&
             epochNanoseconds.nanoseconds <= 999'999'999);

  // Time zone offsets can't exceed 24 hours.
  constexpr auto oneDay = EpochDuration::fromDays(1);

  // Exclusive limits.
  constexpr auto min = EpochNanoseconds::min() - oneDay;
  constexpr auto max = EpochNanoseconds::max() + oneDay;

  return min < epochNanoseconds && epochNanoseconds < max;
}
#endif

enum class UnsignedRoundingMode {
  Zero,
  Infinity,
  HalfZero,
  HalfInfinity,
  HalfEven
};

/**
 * GetUnsignedRoundingMode ( roundingMode, sign )
 */

static UnsignedRoundingMode GetUnsignedRoundingMode(
    TemporalRoundingMode roundingMode, bool isNegative) {
  switch (roundingMode) {
    case TemporalRoundingMode::Ceil:
      return isNegative ? UnsignedRoundingMode::Zero
                        : UnsignedRoundingMode::Infinity;
    case TemporalRoundingMode::Floor:
      return isNegative ? UnsignedRoundingMode::Infinity
                        : UnsignedRoundingMode::Zero;
    case TemporalRoundingMode::Expand:
      return UnsignedRoundingMode::Infinity;
    case TemporalRoundingMode::Trunc:
      return UnsignedRoundingMode::Zero;
    case TemporalRoundingMode::HalfCeil:
      return isNegative ? UnsignedRoundingMode::HalfZero
                        : UnsignedRoundingMode::HalfInfinity;
    case TemporalRoundingMode::HalfFloor:
      return isNegative ? UnsignedRoundingMode::HalfInfinity
                        : UnsignedRoundingMode::HalfZero;
    case TemporalRoundingMode::HalfExpand:
      return UnsignedRoundingMode::HalfInfinity;
    case TemporalRoundingMode::HalfTrunc:
      return UnsignedRoundingMode::HalfZero;
    case TemporalRoundingMode::HalfEven:
      return UnsignedRoundingMode::HalfEven;
  }
  MOZ_CRASH("invalid rounding mode");
}

struct DurationNudge {
  InternalDuration duration;
  EpochNanoseconds epochNs;
  double total = 0;
  bool didExpandCalendarUnit = false;
};

/**
 * NudgeToCalendarUnit ( sign, duration, destEpochNs, isoDateTime, timeZone,
 * calendar, increment, unit, roundingMode )
 */

static bool NudgeToCalendarUnit(JSContext* cx, const InternalDuration& duration,
                                const EpochNanoseconds& destEpochNs,
                                const ISODateTime& isoDateTime,
                                Handle<TimeZoneValue> timeZone,
                                Handle<CalendarValue> calendar,
                                Increment increment, TemporalUnit unit,
                                TemporalRoundingMode roundingMode,
                                DurationNudge* result) {
  MOZ_ASSERT(IsValidDuration(duration));
  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs));
  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs));
  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
  MOZ_ASSERT(unit <= TemporalUnit::Day);

  int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1;

  // Steps 1-4.
  int64_t r1;
  int64_t r2;
  DateDuration startDuration;
  DateDuration endDuration;
  if (unit == TemporalUnit::Year) {
    // Step 1.a.
    int64_t years = RoundNumberToIncrement(duration.date.years, increment,
                                           TemporalRoundingMode::Trunc);

    // Step 1.b.
    r1 = years;

    // Step 1.c.
    r2 = years + int64_t(increment.value()) * sign;

    // Step 1.d.
    startDuration = {r1};

    // Step 1.e.
    endDuration = {r2};
  } else if (unit == TemporalUnit::Month) {
    // Step 2.a.
    int64_t months = RoundNumberToIncrement(duration.date.months, increment,
                                            TemporalRoundingMode::Trunc);

    // Step 2.b.
    r1 = months;

    // Step 2.c.
    r2 = months + int64_t(increment.value()) * sign;

    // Step 2.d.
    startDuration = {duration.date.years, r1};

    // Step 2.e.
    endDuration = {duration.date.years, r2};
  } else if (unit == TemporalUnit::Week) {
    // Step 3.a.
    auto yearsMonths = DateDuration{duration.date.years, duration.date.months};

    // Step 3.b.
    ISODate weeksStart;
    if (!CalendarDateAdd(cx, calendar, isoDateTime.date, yearsMonths,
                         TemporalOverflow::Constrain, &weeksStart)) {
      return false;
    }
    MOZ_ASSERT(ISODateWithinLimits(weeksStart));

    // Step 3.c.
    ISODate weeksEnd;
    if (!BalanceISODate(cx, weeksStart, duration.date.days, &weeksEnd)) {
      return false;
    }
    MOZ_ASSERT(ISODateWithinLimits(weeksEnd));

    // Step 3.d.
    DateDuration untilResult;
    if (!CalendarDateUntil(cx, calendar, weeksStart, weeksEnd,
                           TemporalUnit::Week, &untilResult)) {
      return false;
    }

    // Step 3.e.
    int64_t weeks =
        RoundNumberToIncrement(duration.date.weeks + untilResult.weeks,
                               increment, TemporalRoundingMode::Trunc);

    // Step 3.f.
    r1 = weeks;

    // Step 3.g.
    r2 = weeks + int64_t(increment.value()) * sign;

    // Step 3.h.
    startDuration = {duration.date.years, duration.date.months, r1};

    // Step 3.i.
    endDuration = {duration.date.years, duration.date.months, r2};
  } else {
    // Step 4.a.
    MOZ_ASSERT(unit == TemporalUnit::Day);

    // Step 4.b.
    int64_t days = RoundNumberToIncrement(duration.date.days, increment,
                                          TemporalRoundingMode::Trunc);

    // Step 4.c.
    r1 = days;

    // Step 4.d.
    r2 = days + int64_t(increment.value()) * sign;

    // Step 4.e.
    startDuration = {duration.date.years, duration.date.months,
                     duration.date.weeks, r1};

    // Step 4.f.
    endDuration = {duration.date.years, duration.date.months,
                   duration.date.weeks, r2};
  }
  MOZ_ASSERT(IsValidDuration(startDuration));
  MOZ_ASSERT(IsValidDuration(endDuration));

  // Step 5.
  MOZ_ASSERT_IF(sign > 0, r1 >= 0 && r1 < r2);

  // Step 6.
  MOZ_ASSERT_IF(sign < 0, r1 <= 0 && r1 > r2);

  // Step 7.
  ISODate start;
  if (!CalendarDateAdd(cx, calendar, isoDateTime.date, startDuration,
                       TemporalOverflow::Constrain, &start)) {
    return false;
  }

  // Step 8.
  ISODate end;
  if (!CalendarDateAdd(cx, calendar, isoDateTime.date, endDuration,
                       TemporalOverflow::Constrain, &end)) {
    return false;
  }

  // Step 9.
  auto startDateTime = ISODateTime{start, isoDateTime.time};
  MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime));

  // Step 10.
  auto endDateTime = ISODateTime{end, isoDateTime.time};
  MOZ_ASSERT(ISODateTimeWithinLimits(endDateTime));

  // Steps 11-12.
  EpochNanoseconds startEpochNs;
  EpochNanoseconds endEpochNs;
  if (!timeZone) {
    // Step 11.a.
    startEpochNs = GetUTCEpochNanoseconds(startDateTime);

    // Step 11.b.
    endEpochNs = GetUTCEpochNanoseconds(endDateTime);
  } else {
    // Step 12.a.
    if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime,
                                TemporalDisambiguation::Compatible,
                                &startEpochNs)) {
      return false;
    }

    // Step 12.b.
    if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime,
                                TemporalDisambiguation::Compatible,
                                &endEpochNs)) {
      return false;
    }
  }

  // Steps 13-14.
  MOZ_ASSERT_IF(sign > 0,
                startEpochNs <= destEpochNs && destEpochNs <= endEpochNs);
  MOZ_ASSERT_IF(sign < 0,
                endEpochNs <= destEpochNs && destEpochNs <= startEpochNs);

  // Step 15.
  MOZ_ASSERT(startEpochNs != endEpochNs);

  // Step 16.
  auto numerator = (destEpochNs - startEpochNs).toNanoseconds();
  auto denominator = (endEpochNs - startEpochNs).toNanoseconds();
  MOZ_ASSERT(denominator != Int128{0});
  MOZ_ASSERT(numerator.abs() <= denominator.abs());
  MOZ_ASSERT_IF(denominator > Int128{0}, numerator >= Int128{0});
  MOZ_ASSERT_IF(denominator < Int128{0}, numerator <= Int128{0});

  // Ensure |numerator| and |denominator| are both non-negative to simplify the
  // following computations.
  if (denominator < Int128{0}) {
    numerator = -numerator;
    denominator = -denominator;
  }

  // Steps 17-19.
  //
  // |total| must only be computed when called from Duration.prototype.total,
  // which always passes "trunc" rounding mode with an increment of one.
  double total = mozilla::UnspecifiedNaN<double>();
  if (roundingMode == TemporalRoundingMode::Trunc &&
      increment == Increment{1}) {
    // total = r1 + progress × increment × sign
    //       = r1 + (numerator / denominator) × increment × sign
    //       = r1 + (numerator × increment × sign) / denominator
    //       = (r1 × denominator + numerator × increment × sign) / denominator
    //
    // Computing `n` can't overflow, because:
    // - For years, months, and weeks, `abs(r1) ≤ 2^32`.
    // - For days, `abs(r1) < ⌈(2^53) / (24 * 60 * 60)⌉`.
    // - `denominator` and `numerator` are below-or-equal `2 × 8.64 × 10^21`.
    // - And finally `increment ≤ 10^9`.
    auto n = Int128{r1} * denominator + numerator * Int128{sign};
    total = FractionToDouble(n, denominator);
  }

  // Steps 20-21.
  auto unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, sign < 0);

  // Steps 22-23. (Inlined ApplyUnsignedRoundingMode)
  //
  // clang-format off
  //
  // ApplyUnsignedRoundingMode, steps 1-16.
  //
  // `total = r1` iff `progress = 0`. And `progress = 0` iff `numerator = 0`.
  //
  // d1 = total - r1
  //    = (r1 × denominator + numerator × increment × sign) / denominator - r1
  //    = (numerator × increment × sign) / denominator
  //
  // d2 = r2 - total
  //    = r1 + increment - (r1 × denominator + numerator × increment × sign) / denominator
  //    = (increment × denominator - numerator × increment × sign) / denominator
  //
  // d1 < d2
  // ⇔ (numerator × increment × sign) / denominator < (increment × denominator - numerator × increment × sign) / denominator
  // ⇔ (numerator × increment × sign) < (increment × denominator - numerator × increment × sign)
  // ⇔ (numerator × sign) < (denominator - numerator × sign)
  // ⇔ (2 × numerator × sign) < denominator
  //
  // cardinality = (r1 / (r2 – r1)) modulo 2
  //             = (r1 / (r1 + increment - r1)) modulo 2
  //             = (r1 / increment) modulo 2
  //
  // clang-format on
  bool didExpandCalendarUnit;
  if (numerator == denominator) {
    didExpandCalendarUnit = true;
  } else if (numerator == Int128{0}) {
    didExpandCalendarUnit = false;
  } else if (unsignedRoundingMode == UnsignedRoundingMode::Zero) {
    didExpandCalendarUnit = false;
  } else if (unsignedRoundingMode == UnsignedRoundingMode::Infinity) {
    didExpandCalendarUnit = true;
  } else if (numerator + numerator < denominator) {
    didExpandCalendarUnit = false;
  } else if (numerator + numerator > denominator) {
    didExpandCalendarUnit = true;
  } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfZero) {
    didExpandCalendarUnit = false;
  } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfInfinity) {
    didExpandCalendarUnit = true;
  } else if ((r1 / increment.value()) % 2 == 0) {
    didExpandCalendarUnit = false;
  } else {
    didExpandCalendarUnit = true;
  }

  // Steps 24-28.
  auto resultDuration = didExpandCalendarUnit ? endDuration : startDuration;
  auto resultEpochNs = didExpandCalendarUnit ? endEpochNs : startEpochNs;
  *result = {{resultDuration, {}}, resultEpochNs, total, didExpandCalendarUnit};
  return true;
}

#ifdef DEBUG
static bool IsValidTimeFromDateTimeDuration(const TimeDuration& timeDuration) {
  // Time zone adjustment can't exceed 24 hours.
  constexpr auto oneDay = EpochDuration::fromDays(1);

  // Time zone adjusted nsMinInstant and nsMaxInstant.
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=84 H=99 G=91

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.