Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/TemporalParser.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Range.h"
#include "mozilla/Result.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Try.h"
#include <algorithm>
#include <cstdlib>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <stdint.h>
#include <string_view>
#include <type_traits>
#include <utility>
#include "jsnum.h"
#include "NamespaceImports.h"
#include "builtin/temporal/Duration.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainTime.h"
#include "builtin/temporal/TemporalTypes.h"
#include "builtin/temporal/TemporalUnit.h"
#include "gc/Barrier.h"
#include "gc/Tracer.h"
#include "js/ErrorReport.h"
#include "js/friend/ErrorMessages.h"
#include "js/GCAPI.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "util/Text.h"
#include "vm/JSAtomState.h"
#include "vm/JSContext.h"
#include "vm/StringType.h"
using namespace js;
using namespace js::temporal;
// TODO: Better error message for empty strings?
// TODO: Add string input to error message?
// TODO: Better error messages, for example display current character?
struct StringName final {
// Start position and length of this name.
size_t start = 0;
size_t length = 0;
bool present() const { return length > 0; }
};
static JSLinearString* ToString(JSContext* cx, JSString* string,
const StringName& name) {
MOZ_ASSERT(name.present());
return NewDependentString(cx, string, name.start, name.length);
}
template <typename CharT>
bool EqualCharIgnoreCaseAscii(CharT c1, char c2) {
if constexpr (sizeof(CharT) > sizeof(char)) {
if (!mozilla::IsAscii(c1)) {
return false;
}
}
static constexpr auto toLower = 0x20;
static_assert('a' - 'A' == toLower);
// Convert both characters to lower case before the comparison.
char c = c1;
if (mozilla::IsAsciiUppercaseAlpha(c1)) {
c = char(c + toLower);
}
char d = c2;
if (mozilla::IsAsciiUppercaseAlpha(c2)) {
d = char(d + toLower);
}
return c == d;
}
using CalendarName = StringName;
using AnnotationKey = StringName;
using AnnotationValue = StringName;
using TimeZoneName = StringName;
struct Annotation final {
AnnotationKey key;
AnnotationValue value;
bool critical = false;
};
struct TimeSpec final {
Time time;
};
struct TimeZoneUTCOffset final {
// ±1 for time zones with an offset, otherwise 0.
int32_t sign = 0;
// An integer in the range [0, 23].
int32_t hour = 0;
// An integer in the range [0, 59].
int32_t minute = 0;
};
struct DateTimeUTCOffset final {
// ±1 for time zones with an offset, otherwise 0.
int32_t sign = 0;
// An integer in the range [0, 23].
int32_t hour = 0;
// An integer in the range [0, 59].
int32_t minute = 0;
// An integer in the range [0, 59].
int32_t second = 0;
// An integer in the range [0, 999'999].
int32_t fractionalPart = 0;
// Time zone with sub-minute precision.
bool subMinutePrecision = false;
// Convert to a TimeZoneUTCOffset.
TimeZoneUTCOffset toTimeZoneUTCOffset() const {
MOZ_ASSERT(!subMinutePrecision, "unexpected sub-minute precision");
return {sign, hour, minute};
}
};
/**
* ParseDateTimeUTCOffset ( offsetString )
*/
static int64_t ParseDateTimeUTCOffset(const DateTimeUTCOffset& offset) {
constexpr int64_t nanoPerSec = 1'000'000'000;
MOZ_ASSERT(offset.sign == -1 || offset.sign == +1);
MOZ_ASSERT(0 <= offset.hour && offset.hour < 24);
MOZ_ASSERT(0 <= offset.minute && offset.minute < 60);
MOZ_ASSERT(0 <= offset.second && offset.second < 60);
MOZ_ASSERT(0 <= offset.fractionalPart && offset.fractionalPart < nanoPerSec);
// sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds).
int64_t seconds = (offset.hour * 60 + offset.minute) * 60 + offset.second;
int64_t nanos = (seconds * nanoPerSec) + offset.fractionalPart;
int64_t result = offset.sign * nanos;
MOZ_ASSERT(std::abs(result) < ToNanoseconds(TemporalUnit::Day),
"time zone offset is less than 24:00 hours");
return result;
}
static int32_t ParseTimeZoneOffset(const TimeZoneUTCOffset& offset) {
MOZ_ASSERT(offset.sign == -1 || offset.sign == +1);
MOZ_ASSERT(0 <= offset.hour && offset.hour < 24);
MOZ_ASSERT(0 <= offset.minute && offset.minute < 60);
// sign × (hour × 60 + minute).
int32_t result = offset.sign * (offset.hour * 60 + offset.minute);
MOZ_ASSERT(std::abs(result) < UnitsPerDay(TemporalUnit::Minute),
"time zone offset is less than 24:00 hours");
return result;
}
/**
* Struct to hold time zone annotations.
*/
struct TimeZoneAnnotation final {
// Time zone offset.
TimeZoneUTCOffset offset;
// Time zone name.
TimeZoneName name;
/**
* Returns true iff the time zone has an offset part, e.g. "+01:00".
*/
bool hasOffset() const { return offset.sign != 0; }
/**
* Returns true iff the time zone has an IANA name, e.g. "Asia/Tokyo".
*/
bool hasName() const { return name.present(); }
};
/**
* Struct to hold any time zone parts of a parsed string.
*/
struct TimeZoneString final {
// Date-time UTC offset.
DateTimeUTCOffset offset;
// Time zone annotation;
TimeZoneAnnotation annotation;
// UTC time zone.
bool utc = false;
static auto from(DateTimeUTCOffset offset) {
TimeZoneString timeZone{};
timeZone.offset = offset;
return timeZone;
}
static auto from(TimeZoneUTCOffset offset) {
TimeZoneString timeZone{};
timeZone.annotation.offset = offset;
return timeZone;
}
static auto from(TimeZoneName name) {
TimeZoneString timeZone{};
timeZone.annotation.name = name;
return timeZone;
}
static auto UTC() {
TimeZoneString timeZone{};
timeZone.utc = true;
return timeZone;
}
/**
* Returns true iff the time zone has an offset part, e.g. "+01:00".
*/
bool hasOffset() const { return offset.sign != 0; }
/**
* Returns true iff the time zone has an annotation.
*/
bool hasAnnotation() const {
return annotation.hasName() || annotation.hasOffset();
}
/**
* Returns true iff the time zone uses the "Z" abbrevation to denote UTC time.
*/
bool isUTC() const { return utc; }
};
/**
* Struct to hold the parsed date, time, time zone, and calendar components.
*/
struct ZonedDateTimeString final {
ISODate date;
Time time;
TimeZoneString timeZone;
CalendarName calendar;
bool startOfDay;
};
template <typename CharT>
static bool IsISO8601Calendar(mozilla::Span<const CharT> calendar) {
static constexpr std::string_view iso8601 = "iso8601";
if (calendar.size() != iso8601.length()) {
return false;
}
for (size_t i = 0; i < iso8601.length(); i++) {
if (!EqualCharIgnoreCaseAscii(calendar[i], iso8601[i])) {
return false;
}
}
return true;
}
static constexpr int32_t AbsentYear = INT32_MAX;
/**
* ParseISODateTime ( isoString )
*/
static bool ParseISODateTime(JSContext* cx, const ZonedDateTimeString& parsed,
ISODateTime* result) {
// Steps 1-7, 9, 11-16 (Not applicable here).
ISODateTime dateTime = {parsed.date, parsed.time};
// NOTE: ToIntegerOrInfinity("") is 0.
if (dateTime.date.year == AbsentYear) {
dateTime.date.year = 0;
}
// Step 8.
if (dateTime.date.month == 0) {
dateTime.date.month = 1;
}
// Step 10.
if (dateTime.date.day == 0) {
dateTime.date.day = 1;
}
// Step 17.b.
if (dateTime.time.second == 60) {
dateTime.time.second = 59;
}
// ParseISODateTime, steps 18-19 (Not applicable in our implementation).
// Perform early error checks now that absent |day| and |month| values were
// handled:
// `IsValidDate(DateSpec)` and `IsValidMonthDay(DateSpecMonthDay)` validate
// that |day| doesn't exceed the number of days in |month|. This check can be
// implemented by calling `ThrowIfInvalidISODate`.
//
// All other values are already in-bounds.
MOZ_ASSERT(std::abs(dateTime.date.year) <= 999'999);
MOZ_ASSERT(1 <= dateTime.date.month && dateTime.date.month <= 12);
MOZ_ASSERT(1 <= dateTime.date.day && dateTime.date.day <= 31);
if (!ThrowIfInvalidISODate(cx, dateTime.date)) {
return false;
}
// Step 20.
MOZ_ASSERT(IsValidISODate(dateTime.date));
// Step 21.
MOZ_ASSERT(IsValidTime(dateTime.time));
// Steps 22-28. (Handled in caller.)
*result = dateTime;
return true;
}
static bool ParseTimeZoneAnnotation(JSContext* cx,
const TimeZoneAnnotation& annotation,
JSLinearString* linear,
MutableHandle<ParsedTimeZone> result) {
MOZ_ASSERT(annotation.hasOffset() || annotation.hasName());
if (annotation.hasOffset()) {
int32_t offset = ParseTimeZoneOffset(annotation.offset);
result.set(ParsedTimeZone::fromOffset(offset));
return true;
}
auto* str = ToString(cx, linear, annotation.name);
if (!str) {
return false;
}
result.set(ParsedTimeZone::fromName(str));
return true;
}
/**
* Struct for the parsed duration components.
*/
struct TemporalDurationString final {
// A non-negative integer or +Infinity.
double years = 0;
// A non-negative integer or +Infinity.
double months = 0;
// A non-negative integer or +Infinity.
double weeks = 0;
// A non-negative integer or +Infinity.
double days = 0;
// A non-negative integer or +Infinity.
double hours = 0;
// A non-negative integer or +Infinity.
double minutes = 0;
// A non-negative integer or +Infinity.
double seconds = 0;
// An integer in the range [0, 999'999].
int32_t hoursFraction = 0;
// An integer in the range [0, 999'999].
int32_t minutesFraction = 0;
// An integer in the range [0, 999'999].
int32_t secondsFraction = 0;
// ±1 when an offset is present, otherwise 0.
int32_t sign = 0;
};
class ParserError final {
JSErrNum error_ = JSMSG_NOT_AN_ERROR;
public:
constexpr MOZ_IMPLICIT ParserError(JSErrNum error) : error_(error) {}
constexpr JSErrNum error() const { return error_; }
constexpr operator JSErrNum() const { return error(); }
};
namespace mozilla::detail {
// Zero is used for tagging, so it mustn't be an error.
static_assert(static_cast<JSErrNum>(0) == JSMSG_NOT_AN_ERROR);
// Ensure efficient packing of the error type.
template <>
struct UnusedZero<::ParserError> {
private:
using Error = ::ParserError;
using ErrorKind = JSErrNum;
public:
using StorageType = std::underlying_type_t<ErrorKind>;
static constexpr bool value = true;
static constexpr StorageType nullValue = 0;
static constexpr Error Inspect(const StorageType& aValue) {
return Error(static_cast<ErrorKind>(aValue));
}
static constexpr Error Unwrap(StorageType aValue) {
return Error(static_cast<ErrorKind>(aValue));
}
static constexpr StorageType Store(Error aValue) {
return static_cast<StorageType>(aValue.error());
}
};
} // namespace mozilla::detail
static_assert(mozilla::Result<ZonedDateTimeString, ParserError>::Strategy !=
mozilla::detail::PackingStrategy::Variant);
template <typename CharT>
class StringReader final {
mozilla::Span<const CharT> string_;
// Current position in the string.
size_t index_ = 0;
public:
explicit StringReader(mozilla::Span<const CharT> string) : string_(string) {}
/**
* Returns the input string.
*/
mozilla::Span<const CharT> string() const { return string_; }
/**
* Returns a substring of the input string.
*/
mozilla::Span<const CharT> substring(const StringName& name) const {
MOZ_ASSERT(name.present());
return string_.Subspan(name.start, name.length);
}
/**
* Returns the current parse position.
*/
size_t index() const { return index_; }
/**
* Returns the length of the input string-
*/
size_t length() const { return string_.size(); }
/**
* Returns true iff the whole string has been parsed.
*/
bool atEnd() const { return index() == length(); }
/**
* Reset the parser to a previous parse position.
*/
void reset(size_t index = 0) {
MOZ_ASSERT(index <= length());
index_ = index;
}
/**
* Returns true if at least `amount` characters can be read from the current
* parse position.
*/
bool hasMore(size_t amount) const { return index() + amount <= length(); }
/**
* Advances the parse position by `amount` characters.
*/
void advance(size_t amount) {
MOZ_ASSERT(hasMore(amount));
index_ += amount;
}
/**
* Returns the character at the current parse position.
*/
CharT current() const { return string()[index()]; }
/**
* Returns the character at the next parse position.
*/
CharT next() const { return string()[index() + 1]; }
/**
* Returns the character at position `index`.
*/
CharT at(size_t index) const { return string()[index]; }
};
template <typename CharT>
class TemporalParser final {
StringReader<CharT> reader_;
/**
* Read an unlimited amount of decimal digits, returning `Nothing` if no
* digits were read.
*/
mozilla::Maybe<double> digits(JSContext* cx);
/**
* Read exactly `length` digits, returning `Nothing` on failure.
*/
mozilla::Maybe<int32_t> digits(size_t length) {
MOZ_ASSERT(length > 0, "can't read zero digits");
MOZ_ASSERT(length <= std::numeric_limits<int32_t>::digits10,
"can't read more than digits10 digits without overflow");
if (!reader_.hasMore(length)) {
return mozilla::Nothing();
}
int32_t num = 0;
size_t index = reader_.index();
for (size_t i = 0; i < length; i++) {
auto ch = reader_.at(index + i);
if (!mozilla::IsAsciiDigit(ch)) {
return mozilla::Nothing();
}
num = num * 10 + AsciiDigitToNumber(ch);
}
reader_.advance(length);
return mozilla::Some(num);
}
// TemporalDecimalFraction :::
// TemporalDecimalSeparator DecimalDigit{1,9}
mozilla::Maybe<int32_t> fraction() {
if (!reader_.hasMore(2)) {
return mozilla::Nothing();
}
if (!hasDecimalSeparator() || !mozilla::IsAsciiDigit(reader_.next())) {
return mozilla::Nothing();
}
// Consume the decimal separator.
MOZ_ALWAYS_TRUE(decimalSeparator());
// Maximal nine fractional digits are supported.
constexpr size_t maxFractions = 9;
// Read up to |maxFractions| digits.
int32_t num = 0;
size_t index = reader_.index();
size_t i = 0;
for (; i < std::min(reader_.length() - index, maxFractions); i++) {
CharT ch = reader_.at(index + i);
if (!mozilla::IsAsciiDigit(ch)) {
break;
}
num = num * 10 + AsciiDigitToNumber(ch);
}
// Skip past the read digits.
reader_.advance(i);
// Normalize the fraction to |maxFractions| digits.
for (; i < maxFractions; i++) {
num *= 10;
}
return mozilla::Some(num);
}
/**
* Returns true iff the current character is `ch`.
*/
bool hasCharacter(CharT ch) const {
return reader_.hasMore(1) && reader_.current() == ch;
}
/**
* Consumes the current character if it's equal to `ch` and then returns
* `true`. Otherwise returns `false`.
*/
bool character(CharT ch) {
if (!hasCharacter(ch)) {
return false;
}
reader_.advance(1);
return true;
}
/**
* Consumes the next characters if they're equal to `str` and then returns
* `true`. Otherwise returns `false`.
*/
template <size_t N>
bool string(const char (&str)[N]) {
static_assert(N > 2, "use character() for one element strings");
if (!reader_.hasMore(N - 1)) {
return false;
}
size_t index = reader_.index();
for (size_t i = 0; i < N - 1; i++) {
if (reader_.at(index + i) != str[i]) {
return false;
}
}
reader_.advance(N - 1);
return true;
}
/**
* Returns true if the next two characters are ASCII alphabetic characters.
*/
bool hasTwoAsciiAlpha() {
if (!reader_.hasMore(2)) {
return false;
}
size_t index = reader_.index();
return mozilla::IsAsciiAlpha(reader_.at(index)) &&
mozilla::IsAsciiAlpha(reader_.at(index + 1));
}
/**
* Returns true iff the current character is one of `chars`.
*/
bool hasOneOf(std::initializer_list<char16_t> chars) const {
if (!reader_.hasMore(1)) {
return false;
}
auto ch = reader_.current();
return std::find(chars.begin(), chars.end(), ch) != chars.end();
}
/**
* Consumes the current character if it's in `chars` and then returns `true`.
* Otherwise returns `false`.
*/
bool oneOf(std::initializer_list<char16_t> chars) {
if (!hasOneOf(chars)) {
return false;
}
reader_.advance(1);
return true;
}
/**
* Consumes the current character if it matches the predicate and then returns
* `true`. Otherwise returns `false`.
*/
template <typename Predicate>
bool matches(Predicate&& predicate) {
if (!reader_.hasMore(1)) {
return false;
}
CharT ch = reader_.current();
if (!predicate(ch)) {
return false;
}
reader_.advance(1);
return true;
}
// ASCIISign ::: one of
// + -
bool hasSign() const { return hasOneOf({'+', '-'}); }
/**
* Consumes the current character, which must be a sign character, and returns
* its numeric value.
*/
int32_t sign() {
MOZ_ASSERT(hasSign());
int32_t plus = hasCharacter('+');
reader_.advance(1);
return plus ? 1 : -1;
}
// DateSeparator[Extended] :::
// [+Extended] -
// [~Extended] [empty]
bool dateSeparator() { return character('-'); }
// TimeSeparator[Extended] :::
// [+Extended] :
// [~Extended] [empty]
bool hasTimeSeparator() const { return hasCharacter(':'); }
bool timeSeparator() { return character(':'); }
// TemporalDecimalSeparator ::: one of
// . ,
bool hasDecimalSeparator() const { return hasOneOf({'.', ','}); }
bool decimalSeparator() { return oneOf({'.', ','}); }
// DaysDesignator ::: one of
// D d
bool daysDesignator() { return oneOf({'D', 'd'}); }
// HoursDesignator ::: one of
// H h
bool hoursDesignator() { return oneOf({'H', 'h'}); }
// MinutesDesignator ::: one of
// M m
bool minutesDesignator() { return oneOf({'M', 'm'}); }
// MonthsDesignator ::: one of
// M m
bool monthsDesignator() { return oneOf({'M', 'm'}); }
// DurationDesignator ::: one of
// P p
bool durationDesignator() { return oneOf({'P', 'p'}); }
// SecondsDesignator ::: one of
// S s
bool secondsDesignator() { return oneOf({'S', 's'}); }
// DateTimeSeparator :::
// <SP>
// T
// t
bool dateTimeSeparator() { return oneOf({' ', 'T', 't'}); }
// TimeDesignator ::: one of
// T t
bool hasTimeDesignator() const { return hasOneOf({'T', 't'}); }
bool timeDesignator() { return oneOf({'T', 't'}); }
// WeeksDesignator ::: one of
// W w
bool weeksDesignator() { return oneOf({'W', 'w'}); }
// YearsDesignator ::: one of
// Y y
bool yearsDesignator() { return oneOf({'Y', 'y'}); }
// UTCDesignator ::: one of
// Z z
bool utcDesignator() { return oneOf({'Z', 'z'}); }
// TZLeadingChar :::
// Alpha
// .
// _
bool tzLeadingChar() {
return matches([](auto ch) {
return mozilla::IsAsciiAlpha(ch) || ch == '.' || ch == '_';
});
}
// TZChar :::
// TZLeadingChar
// DecimalDigit
// -
// +
bool tzChar() {
return matches([](auto ch) {
return mozilla::IsAsciiAlphanumeric(ch) || ch == '.' || ch == '_' ||
ch == '-' || ch == '+';
});
}
// AnnotationCriticalFlag :::
// !
bool annotationCriticalFlag() { return character('!'); }
// AKeyLeadingChar :::
// LowercaseAlpha
// _
bool aKeyLeadingChar() {
return matches([](auto ch) {
return mozilla::IsAsciiLowercaseAlpha(ch) || ch == '_';
});
}
// AKeyChar :::
// AKeyLeadingChar
// DecimalDigit
// -
bool aKeyChar() {
return matches([](auto ch) {
return mozilla::IsAsciiLowercaseAlpha(ch) || mozilla::IsAsciiDigit(ch) ||
ch == '-' || ch == '_';
});
}
// AnnotationValueComponent :::
// Alpha AnnotationValueComponent?
// DecimalDigit AnnotationValueComponent?
bool annotationValueComponent() {
size_t index = reader_.index();
size_t i = 0;
for (; index + i < reader_.length(); i++) {
auto ch = reader_.at(index + i);
if (!mozilla::IsAsciiAlphanumeric(ch)) {
break;
}
}
if (i == 0) {
return false;
}
reader_.advance(i);
return true;
}
template <typename T>
static constexpr bool inBounds(const T& x, const T& min, const T& max) {
return min <= x && x <= max;
}
static auto err(JSErrNum error) {
// Explicitly create |ParserError| when JSErrNum is auto-convertible to the
// success type.
return mozilla::Err(ParserError{error});
}
mozilla::Result<int32_t, ParserError> dateYear();
mozilla::Result<int32_t, ParserError> dateMonth();
mozilla::Result<int32_t, ParserError> dateDay();
mozilla::Result<int32_t, ParserError> hour();
mozilla::Result<mozilla::Maybe<int32_t>, ParserError> minute(bool required);
mozilla::Result<mozilla::Maybe<int32_t>, ParserError> second(bool required);
mozilla::Result<mozilla::Maybe<int32_t>, ParserError> timeSecond(
bool required);
mozilla::Result<ISODate, ParserError> date();
mozilla::Result<Time, ParserError> time();
mozilla::Result<ZonedDateTimeString, ParserError> dateTime(bool allowZ);
mozilla::Result<ISODate, ParserError> dateSpecYearMonth();
mozilla::Result<ISODate, ParserError> dateSpecMonthDay();
// Return true when |Annotation| can start at the current position.
bool hasAnnotationStart() const { return hasCharacter('['); }
// Return true when |TimeZoneAnnotation| can start at the current position.
bool hasTimeZoneAnnotationStart() const {
if (!hasCharacter('[')) {
return false;
}
// Ensure no '=' is found before the closing ']', otherwise the opening '['
// may actually start an |Annotation| instead of a |TimeZoneAnnotation|.
for (size_t i = reader_.index() + 1; i < reader_.length(); i++) {
CharT ch = reader_.at(i);
if (ch == '=') {
return false;
}
if (ch == ']') {
break;
}
}
return true;
}
// Return true when |DateTimeUTCOffset| can start at the current position.
bool hasDateTimeUTCOffsetStart() { return hasOneOf({'Z', 'z', '+', '-'}); }
mozilla::Result<TimeZoneString, ParserError> dateTimeUTCOffset(bool allowZ);
mozilla::Result<DateTimeUTCOffset, ParserError> utcOffsetSubMinutePrecision();
mozilla::Result<TimeZoneUTCOffset, ParserError> timeZoneUTCOffsetName();
mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneIdentifier();
mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneAnnotation();
mozilla::Result<TimeZoneName, ParserError> timeZoneIANAName();
mozilla::Result<AnnotationKey, ParserError> annotationKey();
mozilla::Result<AnnotationValue, ParserError> annotationValue();
mozilla::Result<Annotation, ParserError> annotation();
mozilla::Result<CalendarName, ParserError> annotations();
mozilla::Result<ZonedDateTimeString, ParserError> annotatedTime();
mozilla::Result<ZonedDateTimeString, ParserError> annotatedDateTime();
mozilla::Result<ZonedDateTimeString, ParserError>
annotatedDateTimeTimeRequired();
mozilla::Result<ZonedDateTimeString, ParserError> annotatedYearMonth();
mozilla::Result<ZonedDateTimeString, ParserError> annotatedMonthDay();
mozilla::Result<double, ParserError> durationDigits(JSContext* cx);
template <typename T>
mozilla::Result<T, ParserError> parse(
mozilla::Result<T, ParserError>&& result) const;
template <typename T>
mozilla::Result<T, ParserError> complete(const T& value) const;
public:
explicit TemporalParser(mozilla::Span<const CharT> str) : reader_(str) {}
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalInstantString();
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalTimeZoneString();
mozilla::Result<TimeZoneAnnotation, ParserError> parseTimeZoneIdentifier();
mozilla::Result<DateTimeUTCOffset, ParserError> parseDateTimeUTCOffset();
mozilla::Result<TemporalDurationString, ParserError>
parseTemporalDurationString(JSContext* cx);
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalCalendarString();
mozilla::Result<ZonedDateTimeString, ParserError> parseTemporalTimeString();
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalMonthDayString();
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalYearMonthString();
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalDateTimeString();
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalZonedDateTimeString();
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalRelativeToString();
};
template <typename CharT>
template <typename T>
mozilla::Result<T, ParserError> TemporalParser<CharT>::parse(
mozilla::Result<T, ParserError>&& result) const {
if (result.isOk() && !reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return std::move(result);
}
template <typename CharT>
template <typename T>
mozilla::Result<T, ParserError> TemporalParser<CharT>::complete(
const T& result) const {
if (!reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return result;
}
template <typename CharT>
mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::dateYear() {
// DateYear :::
// DecimalDigit{4}
// ASCIISign DecimalDigit{6}
if (auto year = digits(4)) {
return year.value();
}
if (hasSign()) {
int32_t yearSign = sign();
if (auto year = digits(6)) {
int32_t result = yearSign * year.value();
if (yearSign < 0 && result == 0) {
return err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR);
}
return result;
}
return err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR);
}
return err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR);
}
template <typename CharT>
mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::dateMonth() {
// DateMonth :::
// 0 NonzeroDigit
// 10
// 11
// 12
if (auto month = digits(2)) {
int32_t result = month.value();
if (!inBounds(result, 1, 12)) {
return err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
}
return result;
}
return err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
}
template <typename CharT>
mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::dateDay() {
// DateDay :::
// 0 NonzeroDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 30
// 31
if (auto day = digits(2)) {
int32_t result = day.value();
if (!inBounds(result, 1, 31)) {
return err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
}
return result;
}
return err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
}
template <typename CharT>
mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::hour() {
// Hour :::
// 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
// 22
// 23
if (auto hour = digits(2)) {
int32_t result = hour.value();
if (!inBounds(result, 0, 23)) {
return err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
}
return result;
}
return err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
}
template <typename CharT>
mozilla::Result<mozilla::Maybe<int32_t>, ParserError>
TemporalParser<CharT>::minute(bool required) {
// MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto minute = digits(2)) {
if (!inBounds(minute.value(), 0, 59)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
}
return minute;
}
if (!required) {
return mozilla::Maybe<int32_t>{mozilla::Nothing{}};
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
}
template <typename CharT>
mozilla::Result<mozilla::Maybe<int32_t>, ParserError>
TemporalParser<CharT>::second(bool required) {
// MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto minute = digits(2)) {
if (!inBounds(minute.value(), 0, 59)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SECOND);
}
return minute;
}
if (!required) {
return mozilla::Maybe<int32_t>{mozilla::Nothing{}};
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
}
template <typename CharT>
mozilla::Result<mozilla::Maybe<int32_t>, ParserError>
TemporalParser<CharT>::timeSecond(bool required) {
// TimeSecond :::
// MinuteSecond
// 60
//
// MinuteSecond :::
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto minute = digits(2)) {
if (!inBounds(minute.value(), 0, 60)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_LEAPSECOND);
}
return minute;
}
if (!required) {
return mozilla::Maybe<int32_t>{mozilla::Nothing{}};
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
}
template <typename CharT>
mozilla::Result<ISODate, ParserError> TemporalParser<CharT>::date() {
// clang-format off
//
// Date :::
// DateSpec[+Extended]
// DateSpec[~Extended]
//
// DateSpec[Extended] :::
// DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay
//
// clang-format on
ISODate result{};
MOZ_TRY_VAR(result.year, dateYear());
// Optional |DateSeparator|.
bool hasMonthSeparator = dateSeparator();
MOZ_TRY_VAR(result.month, dateMonth());
// Optional |DateSeparator|.
bool hasDaySeparator = dateSeparator();
// Date separators must be consistent.
if (hasMonthSeparator != hasDaySeparator) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INCONSISTENT_DATE_SEPARATOR);
}
MOZ_TRY_VAR(result.day, dateDay());
return result;
}
template <typename CharT>
mozilla::Result<Time, ParserError> TemporalParser<CharT>::time() {
// clang-format off
//
// Time :::
// TimeSpec[+Extended]
// TimeSpec[~Extended]
//
// TimeSpec[Extended] :::
// Hour
// Hour TimeSeparator[?Extended] MinuteSecond
// Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] TimeSecond TemporalDecimalFraction?
//
// clang-format on
Time result{};
MOZ_TRY_VAR(result.hour, hour());
// Optional |TimeSeparator|.
bool hasMinuteSeparator = timeSeparator();
mozilla::Maybe<int32_t> minutes;
MOZ_TRY_VAR(minutes, minute(hasMinuteSeparator));
if (minutes) {
result.minute = minutes.value();
// Optional |TimeSeparator|.
bool hasSecondSeparator = timeSeparator();
mozilla::Maybe<int32_t> seconds;
MOZ_TRY_VAR(seconds, timeSecond(hasSecondSeparator));
if (seconds) {
result.second = seconds.value();
// Time separators must be consistent.
if (hasMinuteSeparator != hasSecondSeparator) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INCONSISTENT_TIME_SEPARATOR);
}
// TemporalDecimalFraction :::
// TemporalDecimalSeparator DecimalDigit{1,9}
if (auto f = fraction()) {
int32_t fractionalPart = f.value();
result.millisecond = fractionalPart / 1'000'000;
result.microsecond = (fractionalPart % 1'000'000) / 1'000;
result.nanosecond = fractionalPart % 1'000;
}
}
}
return result;
}
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::dateTime(bool allowZ) {
// DateTime[Z, TimeRequired] :::
// [~TimeRequired] Date
// Date DateTimeSeparator Time DateTimeUTCOffset[?Z]?
//
// When called as `DateTime[?Z, ~TimeRequired]`.
ZonedDateTimeString result{};
MOZ_TRY_VAR(result.date, date());
if (dateTimeSeparator()) {
MOZ_TRY_VAR(result.time, time());
if (hasDateTimeUTCOffsetStart()) {
MOZ_TRY_VAR(result.timeZone, dateTimeUTCOffset(allowZ));
}
} else {
result.startOfDay = true;
}
return result;
}
template <typename CharT>
mozilla::Result<TimeZoneString, ParserError>
TemporalParser<CharT>::dateTimeUTCOffset(bool allowZ) {
// DateTimeUTCOffset[Z] :::
// [+Z] UTCDesignator
// UTCOffset[+SubMinutePrecision]
if (utcDesignator()) {
if (!allowZ) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
}
return TimeZoneString::UTC();
}
if (hasSign()) {
DateTimeUTCOffset offset;
MOZ_TRY_VAR(offset, utcOffsetSubMinutePrecision());
return TimeZoneString::from(offset);
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
}
template <typename CharT>
mozilla::Result<TimeZoneUTCOffset, ParserError>
TemporalParser<CharT>::timeZoneUTCOffsetName() {
// clang-format off
//
// UTCOffset[SubMinutePrecision] :::
// ASCIISign Hour
// ASCIISign Hour TimeSeparator[+Extended] MinuteSecond
// ASCIISign Hour TimeSeparator[~Extended] MinuteSecond
// [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction?
// [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction?
//
// When called as `UTCOffset[~SubMinutePrecision]`.
//
// clang-format on
TimeZoneUTCOffset result{};
if (!hasSign()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
}
result.sign = sign();
MOZ_TRY_VAR(result.hour, hour());
// Optional |TimeSeparator|.
bool hasMinuteSeparator = timeSeparator();
mozilla::Maybe<int32_t> minutes;
MOZ_TRY_VAR(minutes, minute(hasMinuteSeparator));
if (minutes) {
result.minute = minutes.value();
if (hasTimeSeparator()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
}
}
return result;
}
template <typename CharT>
mozilla::Result<DateTimeUTCOffset, ParserError>
TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
// clang-format off
//
// UTCOffset[SubMinutePrecision] :::
// ASCIISign Hour
// ASCIISign Hour TimeSeparator[+Extended] MinuteSecond
// ASCIISign Hour TimeSeparator[~Extended] MinuteSecond
// [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction?
// [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction?
//
// When called as `UTCOffset[+SubMinutePrecision]`.
//
// clang-format on
DateTimeUTCOffset result{};
if (!hasSign()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
}
result.sign = sign();
MOZ_TRY_VAR(result.hour, hour());
// Optional |TimeSeparator|.
bool hasMinuteSeparator = timeSeparator();
mozilla::Maybe<int32_t> minutes;
MOZ_TRY_VAR(minutes, minute(hasMinuteSeparator));
if (minutes) {
result.minute = minutes.value();
// Optional |TimeSeparator|.
bool hasSecondSeparator = timeSeparator();
mozilla::Maybe<int32_t> seconds;
MOZ_TRY_VAR(seconds, second(hasSecondSeparator));
if (seconds) {
result.second = seconds.value();
// Time separators must be consistent.
if (hasMinuteSeparator != hasSecondSeparator) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INCONSISTENT_TIME_SEPARATOR);
}
if (auto fractionalPart = fraction()) {
result.fractionalPart = fractionalPart.value();
}
result.subMinutePrecision = true;
}
}
return result;
}
template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneIdentifier() {
// TimeZoneIdentifier :::
// UTCOffset[~SubMinutePrecision]
// TimeZoneIANAName
TimeZoneAnnotation result{};
if (hasSign()) {
MOZ_TRY_VAR(result.offset, timeZoneUTCOffsetName());
} else {
MOZ_TRY_VAR(result.name, timeZoneIANAName());
}
return result;
}
template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneAnnotation() {
// TimeZoneAnnotation :::
// [ AnnotationCriticalFlag? TimeZoneIdentifier ]
if (!character('[')) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE);
}
// Skip over the optional critical flag.
annotationCriticalFlag();
auto result = timeZoneIdentifier();
if (result.isErr()) {
return result.propagateErr();
}
if (!character(']')) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_TIMEZONE);
}
return result;
}
template <typename CharT>
mozilla::Result<TimeZoneName, ParserError>
TemporalParser<CharT>::timeZoneIANAName() {
// TimeZoneIANAName :::
// TimeZoneIANANameComponent
// TimeZoneIANAName / TimeZoneIANANameComponent
//
// TimeZoneIANANameComponent :::
// TZLeadingChar
// TimeZoneIANANameComponent TZChar
size_t start = reader_.index();
do {
if (!tzLeadingChar()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_NAME);
}
// Optionally followed by a sequence of |TZChar|.
while (tzChar()) {
}
} while (character('/'));
return TimeZoneName{start, reader_.index() - start};
}
template <typename CharT>
mozilla::Maybe<double> TemporalParser<CharT>::digits(JSContext* cx) {
auto span = reader_.string().Subspan(reader_.index());
// GetPrefixInteger can't fail when integer separator handling is disabled.
const CharT* endp = nullptr;
double num;
MOZ_ALWAYS_TRUE(GetPrefixInteger(span.data(), span.data() + span.size(), 10,
IntegerSeparatorHandling::None, &endp,
&num));
size_t len = endp - span.data();
if (len == 0) {
return mozilla::Nothing();
}
reader_.advance(len);
return mozilla::Some(num);
}
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalInstantString() {
// Initialize all fields to zero.
ZonedDateTimeString result{};
// clang-format off
//
// TemporalInstantString :::
// Date DateTimeSeparator Time DateTimeUTCOffset[+Z] TimeZoneAnnotation? Annotations?
//
// clang-format on
MOZ_TRY_VAR(result.date, date());
if (!dateTimeSeparator()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR);
}
MOZ_TRY_VAR(result.time, time());
MOZ_TRY_VAR(result.timeZone, dateTimeUTCOffset(/* allowZ = */ true));
if (hasTimeZoneAnnotationStart()) {
MOZ_TRY_VAR(result.timeZone.annotation, timeZoneAnnotation());
}
if (hasAnnotationStart()) {
MOZ_TRY(annotations());
}
return complete(result);
}
/**
* ParseTemporalInstantString ( isoString )
*/
template <typename CharT>
static auto ParseTemporalInstantString(mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseTemporalInstantString();
}
/**
* ParseTemporalInstantString ( isoString )
*/
static auto ParseTemporalInstantString(Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseTemporalInstantString<Latin1Char>(str->latin1Range(nogc));
}
return ParseTemporalInstantString<char16_t>(str->twoByteRange(nogc));
}
/**
* ParseTemporalInstantString ( isoString )
*/
bool js::temporal::ParseTemporalInstantString(JSContext* cx,
Handle<JSString*> str,
ISODateTime* result,
int64_t* offset) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
// Step 1.
auto parseResult = ::ParseTemporalInstantString(linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
ZonedDateTimeString parsed = parseResult.unwrap();
// Step 2.
if (!ParseISODateTime(cx, parsed, result)) {
return false;
}
// Steps 3-4.
if (parsed.timeZone.hasOffset()) {
*offset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
} else {
MOZ_ASSERT(parsed.timeZone.isUTC());
*offset = 0;
}
return true;
}
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::parseTemporalTimeZoneString() {
// Handle the common case of a standalone time zone identifier first.
if (auto tz = parse(timeZoneIdentifier()); tz.isOk()) {
auto timeZone = tz.unwrap();
ZonedDateTimeString result{};
if (timeZone.hasOffset()) {
result.timeZone = TimeZoneString::from(timeZone.offset);
} else {
MOZ_ASSERT(timeZone.hasName());
result.timeZone = TimeZoneString::from(timeZone.name);
}
return result;
}
// Try all five parse goals from ParseISODateTime in order.
//
// TemporalDateTimeString
// TemporalInstantString
// TemporalTimeString
// TemporalMonthDayString
// TemporalYearMonthString
// Restart parsing from the start of the string.
reader_.reset();
if (auto dt = parseTemporalDateTimeString(); dt.isOk()) {
return dt.unwrap();
}
// Restart parsing from the start of the string.
reader_.reset();
if (auto dt = parseTemporalInstantString(); dt.isOk()) {
return dt.unwrap();
}
// Restart parsing from the start of the string.
reader_.reset();
if (auto dt = parseTemporalTimeString(); dt.isOk()) {
return dt.unwrap();
}
// Restart parsing from the start of the string.
reader_.reset();
if (auto dt = parseTemporalMonthDayString(); dt.isOk()) {
return dt.unwrap();
}
// Restart parsing from the start of the string.
reader_.reset();
if (auto dt = parseTemporalYearMonthString(); dt.isOk()) {
return dt.unwrap();
} else {
return dt.propagateErr();
}
}
/**
* ParseTemporalTimeZoneString ( timeZoneString )
*/
template <typename CharT>
static auto ParseTemporalTimeZoneString(mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseTemporalTimeZoneString();
}
/**
* ParseTemporalTimeZoneString ( timeZoneString )
*/
static auto ParseTemporalTimeZoneString(Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseTemporalTimeZoneString<Latin1Char>(str->latin1Range(nogc));
}
return ParseTemporalTimeZoneString<char16_t>(str->twoByteRange(nogc));
}
/**
* ParseTemporalTimeZoneString ( timeZoneString )
*/
bool js::temporal::ParseTemporalTimeZoneString(
JSContext* cx, Handle<JSString*> str,
MutableHandle<ParsedTimeZone> result) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
// Steps 1-4.
auto parseResult = ::ParseTemporalTimeZoneString(linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
ZonedDateTimeString parsed = parseResult.unwrap();
const auto& timeZone = parsed.timeZone;
// Step 3.
ISODateTime unused;
if (!ParseISODateTime(cx, parsed, &unused)) {
return false;
}
if (timeZone.hasAnnotation()) {
// Case 1: 19700101T00:00Z[+02:00]
// Case 2: 19700101T00:00+00:00[+02:00]
// Case 3: 19700101T00:00[+02:00]
// Case 4: 19700101T00:00Z[Europe/Berlin]
// Case 5: 19700101T00:00+00:00[Europe/Berlin]
// Case 6: 19700101T00:00[Europe/Berlin]
if (!ParseTimeZoneAnnotation(cx, timeZone.annotation, linear, result)) {
return false;
}
} else if (timeZone.isUTC()) {
result.set(ParsedTimeZone::fromName(cx->names().UTC));
} else if (timeZone.hasOffset()) {
// ToTemporalTimeZoneSlotValue, step 7.
//
// Error reporting for sub-minute precision moved here.
if (timeZone.offset.subMinutePrecision) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
return false;
}
int32_t offset = ParseTimeZoneOffset(timeZone.offset.toTimeZoneUTCOffset());
result.set(ParsedTimeZone::fromOffset(offset));
} else {
// Step 5.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
return false;
}
// Step 6.
return true;
}
template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::parseTimeZoneIdentifier() {
return parse(timeZoneIdentifier());
}
/**
* ParseTimeZoneIdentifier ( identifier )
*/
template <typename CharT>
static auto ParseTimeZoneIdentifier(mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseTimeZoneIdentifier();
}
/**
* ParseTimeZoneIdentifier ( identifier )
*/
static auto ParseTimeZoneIdentifier(Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseTimeZoneIdentifier<Latin1Char>(str->latin1Range(nogc));
}
return ParseTimeZoneIdentifier<char16_t>(str->twoByteRange(nogc));
}
/**
* ParseTimeZoneIdentifier ( identifier )
*/
bool js::temporal::ParseTimeZoneIdentifier(
JSContext* cx, Handle<JSString*> str,
MutableHandle<ParsedTimeZone> result) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
// Steps 1-2.
auto parseResult = ::ParseTimeZoneIdentifier(linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
auto timeZone = parseResult.unwrap();
// Steps 3-4.
return ParseTimeZoneAnnotation(cx, timeZone, linear, result);
}
template <typename CharT>
mozilla::Result<DateTimeUTCOffset, ParserError>
TemporalParser<CharT>::parseDateTimeUTCOffset() {
return parse(utcOffsetSubMinutePrecision());
}
/**
* ParseDateTimeUTCOffset ( offsetString )
*/
template <typename CharT>
static auto ParseDateTimeUTCOffset(mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseDateTimeUTCOffset();
}
/**
* ParseDateTimeUTCOffset ( offsetString )
*/
static auto ParseDateTimeUTCOffset(Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseDateTimeUTCOffset<Latin1Char>(str->latin1Range(nogc));
}
return ParseDateTimeUTCOffset<char16_t>(str->twoByteRange(nogc));
}
/**
* ParseDateTimeUTCOffset ( offsetString )
*/
bool js::temporal::ParseDateTimeUTCOffset(JSContext* cx, Handle<JSString*> str,
int64_t* result) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
// Steps 1-2.
auto parseResult = ::ParseDateTimeUTCOffset(linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
// Steps 3-21.
*result = ParseDateTimeUTCOffset(parseResult.unwrap());
return true;
}
template <typename CharT>
mozilla::Result<double, ParserError> TemporalParser<CharT>::durationDigits(
JSContext* cx) {
auto d = digits(cx);
if (!d) {
return err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
}
return *d;
}
template <typename CharT>
mozilla::Result<TemporalDurationString, ParserError>
TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
// Initialize all fields to zero.
TemporalDurationString result{};
// TemporalDurationString :::
// Duration
//
// Duration :::
// ASCIISign? DurationDesignator DurationDate
// ASCIISign? DurationDesignator DurationTime
if (hasSign()) {
result.sign = sign();
}
if (!durationDesignator()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DESIGNATOR);
}
// DurationDate :::
// DurationYearsPart DurationTime?
// DurationMonthsPart DurationTime?
// DurationWeeksPart DurationTime?
// DurationDaysPart DurationTime?
do {
if (hasTimeDesignator()) {
break;
}
double num;
MOZ_TRY_VAR(num, durationDigits(cx));
// DurationYearsPart :::
// DecimalDigits[~Sep] YearsDesignator DurationMonthsPart
// DecimalDigits[~Sep] YearsDesignator DurationWeeksPart
// DecimalDigits[~Sep] YearsDesignator DurationDaysPart?
if (yearsDesignator()) {
result.years = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
MOZ_TRY_VAR(num, durationDigits(cx));
}
// DurationMonthsPart :::
// DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart
// DecimalDigits[~Sep] MonthsDesignator DurationDaysPart?
if (monthsDesignator()) {
result.months = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
MOZ_TRY_VAR(num, durationDigits(cx));
}
// DurationWeeksPart :::
// DecimalDigits[~Sep] WeeksDesignator DurationDaysPart?
if (weeksDesignator()) {
result.weeks = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
MOZ_TRY_VAR(num, durationDigits(cx));
}
// DurationDaysPart :::
// DecimalDigits[~Sep] DaysDesignator
if (daysDesignator()) {
result.days = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
} while (false);
// DurationTime :::
// TimeDesignator DurationHoursPart
// TimeDesignator DurationMinutesPart
// TimeDesignator DurationSecondsPart
if (!timeDesignator()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIME_DESIGNATOR);
}
double num;
MOZ_TRY_VAR(num, durationDigits(cx));
auto frac = fraction();
// DurationHoursPart :::
// DecimalDigits[~Sep] TemporalDecimalFraction HoursDesignator
// DecimalDigits[~Sep] HoursDesignator DurationMinutesPart
// DecimalDigits[~Sep] HoursDesignator DurationSecondsPart?
bool hasHoursFraction = false;
if (hoursDesignator()) {
hasHoursFraction = bool(frac);
result.hours = num;
result.hoursFraction = frac.valueOr(0);
if (reader_.atEnd()) {
return result;
}
MOZ_TRY_VAR(num, durationDigits(cx));
frac = fraction();
}
// DurationMinutesPart :::
// DecimalDigits[~Sep] TemporalDecimalFraction MinutesDesignator
// DecimalDigits[~Sep] MinutesDesignator DurationSecondsPart?
bool hasMinutesFraction = false;
if (minutesDesignator()) {
if (hasHoursFraction) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_MINUTES);
}
hasMinutesFraction = bool(frac);
result.minutes = num;
result.minutesFraction = frac.valueOr(0);
if (reader_.atEnd()) {
return result;
}
MOZ_TRY_VAR(num, durationDigits(cx));
frac = fraction();
}
// DurationSecondsPart :::
// DecimalDigits[~Sep] TemporalDecimalFraction? SecondsDesignator
if (secondsDesignator()) {
if (hasHoursFraction || hasMinutesFraction) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_SECONDS);
}
result.seconds = num;
result.secondsFraction = frac.valueOr(0);
if (reader_.atEnd()) {
return result;
}
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
/**
* ParseTemporalDurationString ( isoString )
*/
template <typename CharT>
static auto ParseTemporalDurationString(JSContext* cx,
mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseTemporalDurationString(cx);
}
/**
* ParseTemporalDurationString ( isoString )
*/
static auto ParseTemporalDurationString(JSContext* cx,
Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseTemporalDurationString<Latin1Char>(cx, str->latin1Range(nogc));
}
return ParseTemporalDurationString<char16_t>(cx, str->twoByteRange(nogc));
}
/**
* ParseTemporalDurationString ( isoString )
*/
bool js::temporal::ParseTemporalDurationString(JSContext* cx,
Handle<JSString*> str,
Duration* result) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
// Steps 1-3.
auto parseResult = ::ParseTemporalDurationString(cx, linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
TemporalDurationString parsed = parseResult.unwrap();
// Steps 4-8.
double years = parsed.years;
double months = parsed.months;
double weeks = parsed.weeks;
double days = parsed.days;
double hours = parsed.hours;
// Steps 9-17.
double minutes, seconds, milliseconds, microseconds, nanoseconds;
if (parsed.hoursFraction) {
MOZ_ASSERT(parsed.hoursFraction > 0);
MOZ_ASSERT(parsed.hoursFraction < 1'000'000'000);
// Step 9.a.
MOZ_ASSERT(parsed.minutes == 0);
MOZ_ASSERT(parsed.minutesFraction == 0);
MOZ_ASSERT(parsed.seconds == 0);
MOZ_ASSERT(parsed.secondsFraction == 0);
// Steps 9.b-d.
int64_t h = int64_t(parsed.hoursFraction) * 60;
minutes = double(h / 1'000'000'000);
// Steps 13 and 15-17.
int64_t min = (h % 1'000'000'000) * 60;
seconds = double(min / 1'000'000'000);
milliseconds = double((min % 1'000'000'000) / 1'000'000);
microseconds = double((min % 1'000'000) / 1'000);
nanoseconds = double(min % 1'000);
}
// Step 11.
else if (parsed.minutesFraction) {
MOZ_ASSERT(parsed.minutesFraction > 0);
MOZ_ASSERT(parsed.minutesFraction < 1'000'000'000);
// Step 11.a.
MOZ_ASSERT(parsed.seconds == 0);
MOZ_ASSERT(parsed.secondsFraction == 0);
// Step 10.
minutes = parsed.minutes;
// Steps 11.b-d and 15-17.
int64_t min = int64_t(parsed.minutesFraction) * 60;
seconds = double(min / 1'000'000'000);
milliseconds = double((min % 1'000'000'000) / 1'000'000);
microseconds = double((min % 1'000'000) / 1'000);
nanoseconds = double(min % 1'000);
}
// Step 14.
else if (parsed.secondsFraction) {
MOZ_ASSERT(parsed.secondsFraction > 0);
MOZ_ASSERT(parsed.secondsFraction < 1'000'000'000);
// Step 10.
minutes = parsed.minutes;
// Step 12.
seconds = parsed.seconds;
// Steps 14, 16-17
milliseconds = double(parsed.secondsFraction / 1'000'000);
microseconds = double((parsed.secondsFraction % 1'000'000) / 1'000);
nanoseconds = double(parsed.secondsFraction % 1'000);
} else {
// Step 10.
minutes = parsed.minutes;
// Step 12.
seconds = parsed.seconds;
// Steps 15-17
milliseconds = 0;
microseconds = 0;
nanoseconds = 0;
}
// Steps 18-19.
int32_t factor = parsed.sign ? parsed.sign : 1;
MOZ_ASSERT(factor == -1 || factor == 1);
// Steps 20-29.
*result = {
(years * factor) + (+0.0), (months * factor) + (+0.0),
(weeks * factor) + (+0.0), (days * factor) + (+0.0),
(hours * factor) + (+0.0), (minutes * factor) + (+0.0),
(seconds * factor) + (+0.0), (milliseconds * factor) + (+0.0),
(microseconds * factor) + (+0.0), (nanoseconds * factor) + (+0.0),
};
// Steps 30-31.
return ThrowIfInvalidDuration(cx, *result);
}
template <typename CharT>
mozilla::Result<AnnotationKey, ParserError>
TemporalParser<CharT>::annotationKey() {
// AnnotationKey :::
// AKeyLeadingChar
// AnnotationKey AKeyChar
size_t start = reader_.index();
if (!aKeyLeadingChar()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_KEY);
}
// Optionally followed by a sequence of |AKeyChar|.
while (aKeyChar()) {
}
return AnnotationKey{start, reader_.index() - start};
}
template <typename CharT>
mozilla::Result<AnnotationValue, ParserError>
TemporalParser<CharT>::annotationValue() {
// AnnotationValue :::
// AnnotationValueComponent
// AnnotationValueComponent - AnnotationValue
size_t start = reader_.index();
do {
if (!annotationValueComponent()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_VALUE);
}
} while (character('-'));