Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  CalendarFields.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/CalendarFields.h"

#include "mozilla/Assertions.h"
#include "mozilla/EnumTypeTraits.h"
#include "mozilla/Maybe.h"
#include "mozilla/Range.h"
#include "mozilla/TextUtils.h"

#include <stdint.h>
#include <string_view>

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

#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Crash.h"
#include "builtin/temporal/Era.h"
#include "builtin/temporal/Temporal.h"
#include "builtin/temporal/TemporalParser.h"
#include "builtin/temporal/TimeZone.h"
#include "gc/Barrier.h"
#include "gc/Tracer.h"
#include "js/Conversions.h"
#include "js/ErrorReport.h"
#include "js/friend/ErrorMessages.h"
#include "js/GCAPI.h"
#include "js/Printer.h"
#include "js/RootingAPI.h"
#include "js/Value.h"
#include "util/Text.h"
#include "vm/BytecodeUtil.h"
#include "vm/JSAtomState.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/StringType.h"

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

using namespace js;
using namespace js::temporal;

void CalendarFields::trace(JSTracer* trc) {
  TraceNullableRoot(trc, &era_, "CalendarFields::era");
  timeZone_.trace(trc);
}

void CalendarFields::setFrom(CalendarField field,
                             const CalendarFields& source) {
  MOZ_ASSERT(source.has(field));

  switch (field) {
    case CalendarField::Era:
      setEra(source.era());
      return;
    case CalendarField::EraYear:
      setEraYear(source.eraYear());
      return;
    case CalendarField::Year:
      setYear(source.year());
      return;
    case CalendarField::Month:
      setMonth(source.month());
      return;
    case CalendarField::MonthCode:
      setMonthCode(source.monthCode());
      return;
    case CalendarField::Day:
      setDay(source.day());
      return;
    case CalendarField::Hour:
      setHour(source.hour());
      return;
    case CalendarField::Minute:
      setMinute(source.minute());
      return;
    case CalendarField::Second:
      setSecond(source.second());
      return;
    case CalendarField::Millisecond:
      setMillisecond(source.millisecond());
      return;
    case CalendarField::Microsecond:
      setMicrosecond(source.microsecond());
      return;
    case CalendarField::Nanosecond:
      setNanosecond(source.nanosecond());
      return;
    case CalendarField::Offset:
      setOffset(source.offset());
      return;
    case CalendarField::TimeZone:
      setTimeZone(source.timeZone());
      return;
  }
  MOZ_CRASH("invalid temporal field");
}

static PropertyName* ToPropertyName(JSContext* cx, CalendarField field) {
  switch (field) {
    case CalendarField::Era:
      return cx->names().era;
    case CalendarField::EraYear:
      return cx->names().eraYear;
    case CalendarField::Year:
      return cx->names().year;
    case CalendarField::Month:
      return cx->names().month;
    case CalendarField::MonthCode:
      return cx->names().monthCode;
    case CalendarField::Day:
      return cx->names().day;
    case CalendarField::Hour:
      return cx->names().hour;
    case CalendarField::Minute:
      return cx->names().minute;
    case CalendarField::Second:
      return cx->names().second;
    case CalendarField::Millisecond:
      return cx->names().millisecond;
    case CalendarField::Microsecond:
      return cx->names().microsecond;
    case CalendarField::Nanosecond:
      return cx->names().nanosecond;
    case CalendarField::Offset:
      return cx->names().offset;
    case CalendarField::TimeZone:
      return cx->names().timeZone;
  }
  MOZ_CRASH("invalid temporal field name");
}

static constexpr const char* ToCString(CalendarField field) {
  switch (field) {
    case CalendarField::Era:
      return "era";
    case CalendarField::EraYear:
      return "eraYear";
    case CalendarField::Year:
      return "year";
    case CalendarField::Month:
      return "month";
    case CalendarField::MonthCode:
      return "monthCode";
    case CalendarField::Day:
      return "day";
    case CalendarField::Hour:
      return "hour";
    case CalendarField::Minute:
      return "minute";
    case CalendarField::Second:
      return "second";
    case CalendarField::Millisecond:
      return "millisecond";
    case CalendarField::Microsecond:
      return "microsecond";
    case CalendarField::Nanosecond:
      return "nanosecond";
    case CalendarField::Offset:
      return "offset";
    case CalendarField::TimeZone:
      return "timeZone";
  }
  JS_CONSTEXPR_CRASH("invalid temporal field name");
}

static constexpr bool CalendarFieldsAreSorted() {
  constexpr auto min = mozilla::ContiguousEnumValues<CalendarField>::min;
  constexpr auto max = mozilla::ContiguousEnumValues<CalendarField>::max;

  auto field = min;
  while (field != max) {
    auto next = static_cast<CalendarField>(mozilla::UnderlyingValue(field) + 1);

    auto a = std::string_view{ToCString(field)};
    auto b = std::string_view{ToCString(next)};
    if (a.compare(b) >= 0) {
      return false;
    }
    field = next;
  }
  return true;
}

/**
 * CalendarExtraFields ( calendar, fields )
 */

static mozilla::EnumSet<CalendarField> CalendarExtraFields(
    CalendarId calendar, mozilla::EnumSet<CalendarField> fields) {
  // Step 1.
  if (calendar == CalendarId::ISO8601) {
    return {};
  }

  // Step 2.

  // "era" and "eraYear" are relevant for calendars with multiple eras when
  // "year" is present.
  if (fields.contains(CalendarField::Year) && CalendarEraRelevant(calendar)) {
    return {CalendarField::Era, CalendarField::EraYear};
  }
  return {};
}

/**
 * ToMonthCode ( argument )
 */

template <typename CharT>
static mozilla::Maybe<MonthCodeField> ToMonthCode(
    mozilla::Range<const CharT> chars) {
  // Steps 1-2. (Not applicable)

  // Step 3.
  //
  // Caller is responsible to ensure the string has the correct length.
  MOZ_ASSERT(chars.length() >= 3 && chars.length() <= 4);

  // Steps 4 and 7.
  //
  // Starts with capital letter 'M'. Leap months end with capital letter 'L'.
  bool isLeapMonth = chars.length() == 4;
  if (chars[0] != 'M' || (isLeapMonth && chars[3] != 'L')) {
    return mozilla::Nothing();
  }

  // Steps 5-6.
  //
  // Month numbers are ASCII digits.
  if (!mozilla::IsAsciiDigit(chars[1]) || !mozilla::IsAsciiDigit(chars[2])) {
    return mozilla::Nothing();
  }

  // Steps 8-9.
  int32_t ordinal =
      AsciiDigitToNumber(chars[1]) * 10 + AsciiDigitToNumber(chars[2]);

  // Step 10.
  if (ordinal == 0 && !isLeapMonth) {
    return mozilla::Nothing();
  }

  // Step 11.
  return mozilla::Some(MonthCodeField{ordinal, isLeapMonth});
}

/**
 * ToMonthCode ( argument )
 */

static auto ToMonthCode(const JSLinearString* linear) {
  JS::AutoCheckCannotGC nogc;

  if (linear->hasLatin1Chars()) {
    return ToMonthCode(linear->latin1Range(nogc));
  }
  return ToMonthCode(linear->twoByteRange(nogc));
}

/**
 * ToMonthCode ( argument )
 */

static bool ToMonthCode(JSContext* cx, Handle<Value> value,
                        MonthCodeField* result) {
  auto reportInvalidMonthCode = [&](JSLinearString* monthCode) {
    if (auto code = QuoteString(cx, monthCode)) {
      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
                               code.get());
    }
    return false;
  };

  // Step 1.
  Rooted<Value> monthCode(cx, value);
  if (!ToPrimitive(cx, JSTYPE_STRING, &monthCode)) {
    return false;
  }

  // Step 2.
  if (!monthCode.isString()) {
    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, monthCode,
                     nullptr, "not a string");
    return false;
  }

  JSLinearString* monthCodeStr = monthCode.toString()->ensureLinear(cx);
  if (!monthCodeStr) {
    return false;
  }

  // Step 3.
  if (monthCodeStr->length() < 3 || monthCodeStr->length() > 4) {
    return reportInvalidMonthCode(monthCodeStr);
  }

  // Steps 4-11.
  auto parsed = ToMonthCode(monthCodeStr);
  if (!parsed) {
    return reportInvalidMonthCode(monthCodeStr);
  }

  *result = *parsed;
  return true;
}

/**
 * ToOffsetString ( argument )
 */

static bool ToOffsetString(JSContext* cx, Handle<Value> value,
                           int64_t* result) {
  // Step 1.
  Rooted<Value> offset(cx, value);
  if (!ToPrimitive(cx, JSTYPE_STRING, &offset)) {
    return false;
  }

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

  // Steps 3-4.
  return ParseDateTimeUTCOffset(cx, offsetStr, result);
}

enum class Partial : bool { No, Yes };

/**
 * PrepareCalendarFields ( calendar, fields, calendarFieldNames,
 * nonCalendarFieldNames, requiredFieldNames )
 */

static bool PrepareCalendarFields(
    JSContext* cx, Handle<CalendarValue> calendar, Handle<JSObject*> fields,
    mozilla::EnumSet<CalendarField> fieldNames,
    mozilla::EnumSet<CalendarField> requiredFields, Partial partial,
    MutableHandle<CalendarFields> result) {
  MOZ_ASSERT_IF(partial == Partial::Yes, requiredFields.isEmpty());

  // Steps 1-2. (Not applicable in our implementation.)

  // Step 3.
  auto extraFieldNames = CalendarExtraFields(calendar.identifier(), fieldNames);

  // Step 4.
  fieldNames += extraFieldNames;

  // Step 5. (Not applicable in our implementation.)

  // Step 6.
  //
  // Default initialize the result.
  result.set(CalendarFields{});

  // Step 7. (Not applicable in our implementation.)

  // Step 8.
  static_assert(CalendarFieldsAreSorted(),
                "EnumSet iteration is sorted");

  // Step 9.
  Rooted<Value> value(cx);
  for (auto fieldName : fieldNames) {
    auto* propertyName = ToPropertyName(cx, fieldName);
    const auto* cstr = ToCString(fieldName);

    // Step 9.a. (Not applicable in our implementation.)

    // Step 9.b.
    if (!GetProperty(cx, fields, fields, propertyName, &value)) {
      return false;
    }

    // Steps 9.c-d.
    if (!value.isUndefined()) {
      // Steps 9.c.i-ii. (Not applicable in our implementation.)

      // Steps 9.c.iii-ix.
      switch (fieldName) {
        case CalendarField::Era: {
          JSString* era = ToString(cx, value);
          if (!era) {
            return false;
          }
          result.setEra(era);
          break;
        }
        case CalendarField::EraYear: {
          double eraYear;
          if (!ToIntegerWithTruncation(cx, value, cstr, &eraYear)) {
            return false;
          }
          result.setEraYear(eraYear);
          break;
        }
        case CalendarField::Year: {
          double year;
          if (!ToIntegerWithTruncation(cx, value, cstr, &year)) {
            return false;
          }
          result.setYear(year);
          break;
        }
        case CalendarField::Month: {
          double month;
          if (!ToPositiveIntegerWithTruncation(cx, value, cstr, &month)) {
            return false;
          }
          result.setMonth(month);
          break;
        }
        case CalendarField::MonthCode: {
          MonthCodeField monthCode;
          if (!ToMonthCode(cx, value, &monthCode)) {
            return false;
          }
          result.setMonthCode(monthCode);
          break;
        }
        case CalendarField::Day: {
          double day;
          if (!ToPositiveIntegerWithTruncation(cx, value, cstr, &day)) {
            return false;
          }
          result.setDay(day);
          break;
        }
        case CalendarField::Hour: {
          double hour;
          if (!ToIntegerWithTruncation(cx, value, cstr, &hour)) {
            return false;
          }
          result.setHour(hour);
          break;
        }
        case CalendarField::Minute: {
          double minute;
          if (!ToIntegerWithTruncation(cx, value, cstr, &minute)) {
            return false;
          }
          result.setMinute(minute);
          break;
        }
        case CalendarField::Second: {
          double second;
          if (!ToIntegerWithTruncation(cx, value, cstr, &second)) {
            return false;
          }
          result.setSecond(second);
          break;
        }
        case CalendarField::Millisecond: {
          double millisecond;
          if (!ToIntegerWithTruncation(cx, value, cstr, &millisecond)) {
            return false;
          }
          result.setMillisecond(millisecond);
          break;
        }
        case CalendarField::Microsecond: {
          double microsecond;
          if (!ToIntegerWithTruncation(cx, value, cstr, µsecond)) {
            return false;
          }
          result.setMicrosecond(microsecond);
          break;
        }
        case CalendarField::Nanosecond: {
          double nanosecond;
          if (!ToIntegerWithTruncation(cx, value, cstr, &nanosecond)) {
            return false;
          }
          result.setNanosecond(nanosecond);
          break;
        }
        case CalendarField::Offset: {
          int64_t offset;
          if (!ToOffsetString(cx, value, &offset)) {
            return false;
          }
          result.setOffset(OffsetField{offset});
          break;
        }
        case CalendarField::TimeZone:
          Rooted<TimeZoneValue> timeZone(cx);
          if (!ToTemporalTimeZone(cx, value, &timeZone)) {
            return false;
          }
          result.setTimeZone(timeZone);
          break;
      }
    } else if (partial == Partial::No) {
      // Step 9.d.i.
      if (requiredFields.contains(fieldName)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_TEMPORAL_MISSING_PROPERTY, cstr);
        return false;
      }

      // Step 9.d.ii.
      result.setDefault(fieldName);
    }
  }

  // Step 10.
  if (partial == Partial::Yes && result.keys().isEmpty()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_MISSING_TEMPORAL_FIELDS);
    return false;
  }

  // Step 11.
  return true;
}

/**
 * PrepareCalendarFields ( calendar, fields, calendarFieldNames,
 * nonCalendarFieldNames, requiredFieldNames )
 */

bool js::temporal::PrepareCalendarFields(
    JSContext* cx, Handle<CalendarValue> calendar, Handle<JSObject*> fields,
    mozilla::EnumSet<CalendarField> fieldNames,
    mozilla::EnumSet<CalendarField> requiredFields,
    MutableHandle<CalendarFields> result) {
  return PrepareCalendarFields(cx, calendar, fields, fieldNames, requiredFields,
                               Partial::No, result);
}

/**
 * PrepareCalendarFields ( calendar, fields, calendarFieldNames,
 * nonCalendarFieldNames, requiredFieldNames )
 */

bool js::temporal::PreparePartialCalendarFields(
    JSContext* cx, Handle<CalendarValue> calendar, Handle<JSObject*> fields,
    mozilla::EnumSet<CalendarField> fieldNames,
    JS::MutableHandle<CalendarFields> result) {
  return PrepareCalendarFields(cx, calendar, fields, fieldNames, {},
                               Partial::Yes, result);
}

/**
 * CalendarFieldKeysToIgnore ( calendar, keys )
 */

static auto CalendarFieldKeysToIgnore(CalendarId calendar,
                                      mozilla::EnumSet<CalendarField> keys) {
  // Step 1.
  if (calendar == CalendarId::ISO8601) {
    // Steps 1.a and 1.b.i.
    auto ignoredKeys = keys;

    // Step 1.b.ii.
    if (keys.contains(CalendarField::Month)) {
      ignoredKeys += CalendarField::MonthCode;
    }

    // Step 1.b.iii.
    else if (keys.contains(CalendarField::MonthCode)) {
      ignoredKeys += CalendarField::Month;
    }

    // Steps 1.c-d.
    return ignoredKeys;
  }

  // Step 2.

  static constexpr auto eraOrEraYear = mozilla::EnumSet{
      CalendarField::Era,
      CalendarField::EraYear,
  };

  static constexpr auto eraOrAnyYear = mozilla::EnumSet{
      CalendarField::Era,
      CalendarField::EraYear,
      CalendarField::Year,
  };

  static constexpr auto monthOrMonthCode = mozilla::EnumSet{
      CalendarField::Month,
      CalendarField::MonthCode,
  };

  static constexpr auto dayOrAnyMonth = mozilla::EnumSet{
      CalendarField::Day,
      CalendarField::Month,
      CalendarField::MonthCode,
  };

  // A field always invalidates at least itself, so start with ignoring all
  // input fields.
  auto result = keys;

  // "month" and "monthCode" are mutually exclusive.
  if (!(keys & monthOrMonthCode).isEmpty()) {
    result += monthOrMonthCode;
  }

  // "era", "eraYear", and "year" are mutually exclusive in non-single era
  // calendar systems.
  if (CalendarEraRelevant(calendar) && !(keys & eraOrAnyYear).isEmpty()) {
    result += eraOrAnyYear;
  }

  // If eras don't start at year boundaries, we have to ignore "era" and
  // "eraYear" if any of "day", "month", or "monthCode" is present.
  if (!CalendarEraStartsAtYearBoundary(calendar) &&
      !(keys & dayOrAnyMonth).isEmpty()) {
    result += eraOrEraYear;
  }

  return result;
}

/**
 * CalendarMergeFields ( calendar, fields, additionalFields )
 */

CalendarFields js::temporal::CalendarMergeFields(
    const CalendarValue& calendar, const CalendarFields& fields,
    const CalendarFields& additionalFields) {
  auto calendarId = calendar.identifier();

  // Steps 1.
  auto additionalKeys = additionalFields.keys();

  // Step 2.
  auto overriddenKeys = CalendarFieldKeysToIgnore(calendarId, additionalKeys);
  MOZ_ASSERT(overriddenKeys.contains(additionalKeys));

  // Step 3.
  auto merged = CalendarFields{};

  // Step 4.
  auto fieldsKeys = fields.keys();

  // Step 5.b.
  for (auto key : (fieldsKeys - overriddenKeys)) {
    merged.setFrom(key, fields);
  }

  // Step 5.c.
  for (auto key : additionalKeys) {
    merged.setFrom(key, additionalFields);
  }

  // Step 6.
  return merged;
}

Messung V0.5
C=89 H=98 G=93

¤ Dauer der Verarbeitung: 0.13 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.