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 <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 = c + toLower;
}
char d = c2;
if (mozilla::IsAsciiUppercaseAlpha(c2)) {
d = 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 {
PlainTime 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 {
PlainDate date;
PlainTime time;
TimeZoneString timeZone;
CalendarName calendar;
};
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,
PlainDateTime* result) {
// Steps 1-6, 8, 10-13 (Not applicable here).
PlainDateTime dateTime = {parsed.date, parsed.time};
// NOTE: ToIntegerOrInfinity("") is 0.
if (dateTime.date.year == AbsentYear) {
dateTime.date.year = 0;
}
// Step 7.
if (dateTime.date.month == 0) {
dateTime.date.month = 1;
}
// Step 9.
if (dateTime.date.day == 0) {
dateTime.date.day = 1;
}
// Step 14.
if (dateTime.time.second == 60) {
dateTime.time.second = 59;
}
// ParseISODateTime, steps 15-16 (Not applicable in our implementation).
// Call ThrowIfInvalidISODate to report an error if |days| exceeds the number
// of days in the month. 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);
// ParseISODateTime, step 17.
if (!ThrowIfInvalidISODate(cx, dateTime.date)) {
return false;
}
// ParseISODateTime, step 18.
MOZ_ASSERT(IsValidTime(dateTime.time));
// Steps 19-25. (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);
}
// TimeFractionalPart :
// Digit{1, 9}
//
// Fraction :
// DecimalSeparator TimeFractionalPart
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;
}
// Sign :
// ASCIISign
// U+2212
//
// ASCIISign : one of
// + -
bool hasSign() const { return hasOneOf({'+', '-', 0x2212}); }
/**
* 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;
}
// DecimalSeparator : 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;
}
mozilla::Result<ZonedDateTimeString, ParserError> dateTime();
mozilla::Result<PlainDate, ParserError> date();
mozilla::Result<PlainDate, ParserError> dateSpecYearMonth();
mozilla::Result<PlainDate, ParserError> dateSpecMonthDay();
mozilla::Result<PlainDate, ParserError> validMonthDay();
mozilla::Result<PlainTime, ParserError> timeSpec();
// 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', '+', '-', 0x2212});
}
mozilla::Result<TimeZoneString, ParserError> dateTimeUTCOffset();
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();
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<TimeZoneUTCOffset, ParserError> parseTimeZoneOffsetString();
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();
};
template <typename CharT>
mozilla::Result<ZonedDateTimeString, ParserError>
TemporalParser<CharT>::dateTime() {
// DateTime :
// Date
// Date DateTimeSeparator TimeSpec DateTimeUTCOffset?
ZonedDateTimeString result = {};
auto dt = date();
if (dt.isErr()) {
return dt.propagateErr();
}
result.date = dt.unwrap();
if (dateTimeSeparator()) {
auto time = timeSpec();
if (time.isErr()) {
return time.propagateErr();
}
result.time = time.unwrap();
if (hasDateTimeUTCOffsetStart()) {
auto tz = dateTimeUTCOffset();
if (tz.isErr()) {
return tz.propagateErr();
}
result.timeZone = tz.unwrap();
}
}
return result;
}
template <typename CharT>
mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
// Date :
// DateYear - DateMonth - DateDay
// DateYear DateMonth DateDay
PlainDate result = {};
// DateYear :
// DecimalDigit{4}
// Sign DecimalDigit{6}
if (auto year = digits(4)) {
result.year = year.value();
} else if (hasSign()) {
int32_t yearSign = sign();
if (auto year = digits(6)) {
result.year = yearSign * year.value();
if (yearSign < 0 && result.year == 0) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR);
}
// Optional: -
character('-');
// DateMonth :
// 0 NonzeroDigit
// 10
// 11
// 12
if (auto month = digits(2)) {
result.month = month.value();
if (!inBounds(result.month, 1, 12)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
}
// Optional: -
character('-');
// DateDay :
// 0 NonzeroDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 30
// 31
if (auto day = digits(2)) {
result.day = day.value();
if (!inBounds(result.day, 1, 31)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
}
return result;
}
template <typename CharT>
mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
// TimeSpec :
// TimeHour
// TimeHour : TimeMinute
// TimeHour TimeMinute
// TimeHour : TimeMinute : TimeSecond TimeFraction?
// TimeHour TimeMinute TimeSecond TimeFraction?
PlainTime result = {};
// TimeHour :
// Hour[+Padded]
//
// Hour[Padded] :
// [~Padded] DecimalDigit
// [~Padded] 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
// 22
// 23
if (auto hour = digits(2)) {
result.hour = hour.value();
if (!inBounds(result.hour, 0, 23)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
}
// Optional: :
bool needsMinutes = character(':');
// TimeMinute :
// MinuteSecond
//
// MinuteSecond :
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto minute = digits(2)) {
result.minute = minute.value();
if (!inBounds(result.minute, 0, 59)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
}
// Optional: :
bool needsSeconds = needsMinutes && character(':');
// TimeSecond :
// MinuteSecond
// 60
if (auto second = digits(2)) {
result.second = second.value();
if (!inBounds(result.second, 0, 60)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_LEAPSECOND);
}
// TimeFraction :
// Fraction
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;
}
} else if (needsSeconds) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
}
} else if (needsMinutes) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
}
return result;
}
template <typename CharT>
mozilla::Result<TimeZoneString, ParserError>
TemporalParser<CharT>::dateTimeUTCOffset() {
// DateTimeUTCOffset :
// UTCDesignator
// UTCOffsetSubMinutePrecision
if (utcDesignator()) {
return TimeZoneString::UTC();
}
if (hasSign()) {
auto offset = utcOffsetSubMinutePrecision();
if (offset.isErr()) {
return offset.propagateErr();
}
return TimeZoneString::from(offset.unwrap());
}
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
}
template <typename CharT>
mozilla::Result<TimeZoneUTCOffset, ParserError>
TemporalParser<CharT>::timeZoneUTCOffsetName() {
// TimeZoneUTCOffsetName :
// UTCOffsetMinutePrecision
//
// UTCOffsetMinutePrecision :
// Sign Hour[+Padded]
// Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
// Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
TimeZoneUTCOffset result = {};
if (!hasSign()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
}
result.sign = sign();
// Hour[Padded] :
// [~Padded] DecimalDigit
// [+Padded] 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
// 22
// 23
if (auto hour = digits(2)) {
result.hour = hour.value();
if (!inBounds(result.hour, 0, 23)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
}
// TimeSeparator[Extended] :
// [+Extended] :
// [~Extended] [empty]
bool needsMinutes = character(':');
// MinuteSecond :
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto minute = digits(2)) {
result.minute = minute.value();
if (!inBounds(result.minute, 0, 59)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
}
if (hasCharacter(':')) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
}
} else if (needsMinutes) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
}
return result;
}
template <typename CharT>
mozilla::Result<DateTimeUTCOffset, ParserError>
TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
// clang-format off
//
// UTCOffsetSubMinutePrecision :
// UTCOffsetMinutePrecision
// UTCOffsetWithSubMinuteComponents[+Extended]
// UTCOffsetWithSubMinuteComponents[~Extended]
//
// UTCOffsetMinutePrecision :
// Sign Hour[+Padded]
// Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
// Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
//
// UTCOffsetWithSubMinuteComponents[Extended] :
// Sign Hour[+Padded] TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond Fraction?
//
// clang-format on
DateTimeUTCOffset result = {};
if (!hasSign()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
}
result.sign = sign();
// Hour[Padded] :
// [~Padded] DecimalDigit
// [+Padded] 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
// 22
// 23
if (auto hour = digits(2)) {
result.hour = hour.value();
if (!inBounds(result.hour, 0, 23)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
}
} else {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
}
// TimeSeparator[Extended] :
// [+Extended] :
// [~Extended] [empty]
bool needsMinutes = character(':');
// MinuteSecond :
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto minute = digits(2)) {
result.minute = minute.value();
if (!inBounds(result.minute, 0, 59)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
}
// TimeSeparator[Extended] :
// [+Extended] :
// [~Extended] [empty]
bool needsSeconds = needsMinutes && character(':');
// MinuteSecond :
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
if (auto second = digits(2)) {
result.second = second.value();
if (!inBounds(result.second, 0, 59)) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SECOND);
}
if (auto fractionalPart = fraction()) {
result.fractionalPart = fractionalPart.value();
}
result.subMinutePrecision = true;
} else if (needsSeconds) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
}
} else if (needsMinutes) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
}
return result;
}
template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneIdentifier() {
// TimeZoneIdentifier :
// TimeZoneUTCOffsetName
// TimeZoneIANAName
TimeZoneAnnotation result = {};
if (hasSign()) {
auto offset = timeZoneUTCOffsetName();
if (offset.isErr()) {
return offset.propagateErr();
}
result.offset = offset.unwrap();
} else {
auto name = timeZoneIANAName();
if (name.isErr()) {
return name.propagateErr();
}
result.name = name.unwrap();
}
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 TimeSpec DateTimeUTCOffset TimeZoneAnnotation? Annotations?
//
// clang-format on
auto dt = date();
if (dt.isErr()) {
return dt.propagateErr();
}
result.date = dt.unwrap();
if (!dateTimeSeparator()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR);
}
auto time = timeSpec();
if (time.isErr()) {
return time.propagateErr();
}
result.time = time.unwrap();
auto tz = dateTimeUTCOffset();
if (tz.isErr()) {
return tz.propagateErr();
}
result.timeZone = tz.unwrap();
if (hasTimeZoneAnnotationStart()) {
auto annotation = timeZoneAnnotation();
if (annotation.isErr()) {
return annotation.propagateErr();
}
result.timeZone.annotation = annotation.unwrap();
}
if (hasAnnotationStart()) {
if (auto cal = annotations(); cal.isErr()) {
return cal.propagateErr();
}
}
if (!reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return 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,
PlainDateTime* 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() {
// TimeZoneIdentifier :
// TimeZoneUTCOffsetName
// TimeZoneIANAName
if (hasSign()) {
if (auto offset = timeZoneUTCOffsetName();
offset.isOk() && reader_.atEnd()) {
ZonedDateTimeString result = {};
result.timeZone = TimeZoneString::from(offset.unwrap());
return result;
}
} else {
if (auto name = timeZoneIANAName(); name.isOk() && reader_.atEnd()) {
ZonedDateTimeString result = {};
result.timeZone = TimeZoneString::from(name.unwrap());
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.
PlainDateTime 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() {
auto result = timeZoneIdentifier();
if (result.isErr()) {
return result.propagateErr();
}
if (!reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return result;
}
/**
* 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<TimeZoneUTCOffset, ParserError>
TemporalParser<CharT>::parseTimeZoneOffsetString() {
auto offset = timeZoneUTCOffsetName();
if (offset.isErr()) {
return offset.propagateErr();
}
if (!reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return offset.unwrap();
}
/**
* ParseTimeZoneOffsetString ( isoString )
*/
template <typename CharT>
static auto ParseTimeZoneOffsetString(mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseTimeZoneOffsetString();
}
/**
* ParseTimeZoneOffsetString ( isoString )
*/
static auto ParseTimeZoneOffsetString(Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseTimeZoneOffsetString<Latin1Char>(str->latin1Range(nogc));
}
return ParseTimeZoneOffsetString<char16_t>(str->twoByteRange(nogc));
}
/**
* ParseTimeZoneOffsetString ( isoString )
*/
bool js::temporal::ParseTimeZoneOffsetString(JSContext* cx,
Handle<JSString*> str,
int32_t* result) {
// Step 1. (Not applicable in our implementation.)
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
// Step 2.
auto parseResult = ::ParseTimeZoneOffsetString(linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
// Steps 3-13.
*result = ParseTimeZoneOffset(parseResult.unwrap());
return true;
}
template <typename CharT>
mozilla::Result<DateTimeUTCOffset, ParserError>
TemporalParser<CharT>::parseDateTimeUTCOffset() {
auto offset = utcOffsetSubMinutePrecision();
if (offset.isErr()) {
return offset.propagateErr();
}
if (!reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return offset.unwrap();
}
/**
* 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<TemporalDurationString, ParserError>
TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
// Initialize all fields to zero.
TemporalDurationString result = {};
// TemporalDurationString :
// Duration
//
// Duration :
// Sign? DurationDesignator DurationDate
// Sign? 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 {
double num;
if (hasTimeDesignator()) {
break;
}
if (auto d = digits(cx); !d) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
} else {
num = *d;
}
// DurationYearsPart :
// DurationYears YearsDesignator DurationMonthsPart
// DurationYears YearsDesignator DurationWeeksPart
// DurationYears YearsDesignator DurationDaysPart?
//
// DurationYears :
// DecimalDigits[~Sep]
if (yearsDesignator()) {
result.years = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
if (auto d = digits(cx); !d) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
} else {
num = *d;
}
}
// DurationMonthsPart :
// DurationMonths MonthsDesignator DurationWeeksPart
// DurationMonths MonthsDesignator DurationDaysPart?
//
// DurationMonths :
// DecimalDigits[~Sep]
if (monthsDesignator()) {
result.months = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
if (auto d = digits(cx); !d) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
} else {
num = *d;
}
}
// DurationWeeksPart :
// DurationWeeks WeeksDesignator DurationDaysPart?
//
// DurationWeeks :
// DecimalDigits[~Sep]
if (weeksDesignator()) {
result.weeks = num;
if (reader_.atEnd()) {
return result;
}
if (hasTimeDesignator()) {
break;
}
if (auto d = digits(cx); !d) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
} else {
num = *d;
}
}
// DurationDaysPart :
// DurationDays DaysDesignator
//
// DurationDays :
// DecimalDigits[~Sep]
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 :
// DurationTimeDesignator DurationHoursPart
// DurationTimeDesignator DurationMinutesPart
// DurationTimeDesignator DurationSecondsPart
if (!timeDesignator()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIME_DESIGNATOR);
}
double num;
mozilla::Maybe<int32_t> frac;
auto digitsAndFraction = [&]() {
auto d = digits(cx);
if (!d) {
return false;
}
num = *d;
frac = fraction();
return true;
};
if (!digitsAndFraction()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
}
// clang-format off
//
// DurationHoursPart :
// DurationWholeHours DurationHoursFraction HoursDesignator
// DurationWholeHours HoursDesignator DurationMinutesPart
// DurationWholeHours HoursDesignator DurationSecondsPart?
//
// DurationWholeHours :
// DecimalDigits[~Sep]
//
// DurationHoursFraction :
// TimeFraction
//
// TimeFraction :
// Fraction
//
// clang-format on
bool hasHoursFraction = false;
if (hoursDesignator()) {
hasHoursFraction = bool(frac);
result.hours = num;
result.hoursFraction = frac.valueOr(0);
if (reader_.atEnd()) {
return result;
}
if (!digitsAndFraction()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
}
}
// clang-format off
//
// DurationMinutesPart :
// DurationWholeMinutes DurationMinutesFraction MinutesDesignator
// DurationWholeMinutes MinutesDesignator DurationSecondsPart?
//
// DurationWholeMinutes :
// DecimalDigits[~Sep]
//
// DurationMinutesFraction :
// TimeFraction
//
// TimeFraction :
// Fraction
//
// clang-format on
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;
}
if (!digitsAndFraction()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
}
}
// DurationSecondsPart :
// DurationWholeSeconds DurationSecondsFraction? SecondsDesignator
//
// DurationWholeSeconds :
// DecimalDigits[~Sep]
//
// DurationSecondsFraction :
// TimeFraction
//
// TimeFraction :
// Fraction
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 = h / 1'000'000'000;
// Steps 13 and 15-17.
int64_t min = (h % 1'000'000'000) * 60;
seconds = min / 1'000'000'000;
milliseconds = (min % 1'000'000'000) / 1'000'000;
microseconds = (min % 1'000'000) / 1'000;
nanoseconds = (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;