Bug 1874684 - Part 3: Update spec step number references. r=dminor
[gecko.git] / js / src / builtin / temporal / TemporalParser.cpp
blob02c1bd7897e7cd6f5a256f9fa8d88237b6f18e17
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "builtin/temporal/TemporalParser.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/Range.h"
13 #include "mozilla/Result.h"
14 #include "mozilla/Span.h"
15 #include "mozilla/TextUtils.h"
17 #include <algorithm>
18 #include <cstdlib>
19 #include <initializer_list>
20 #include <iterator>
21 #include <limits>
22 #include <stdint.h>
23 #include <string_view>
24 #include <type_traits>
25 #include <utility>
27 #include "jsnum.h"
28 #include "NamespaceImports.h"
30 #include "builtin/temporal/Duration.h"
31 #include "builtin/temporal/PlainDate.h"
32 #include "builtin/temporal/PlainTime.h"
33 #include "builtin/temporal/TemporalTypes.h"
34 #include "builtin/temporal/TemporalUnit.h"
35 #include "gc/Barrier.h"
36 #include "gc/Tracer.h"
37 #include "js/ErrorReport.h"
38 #include "js/friend/ErrorMessages.h"
39 #include "js/GCAPI.h"
40 #include "js/RootingAPI.h"
41 #include "js/TypeDecls.h"
42 #include "util/Text.h"
43 #include "vm/JSAtomState.h"
44 #include "vm/JSContext.h"
45 #include "vm/StringType.h"
47 using namespace js;
48 using namespace js::temporal;
50 // TODO: Better error message for empty strings?
51 // TODO: Add string input to error message?
52 // TODO: Better error messages, for example display current character?
53 // https://bugzilla.mozilla.org/show_bug.cgi?id=1839676
55 struct StringName final {
56 // Start position and length of this name.
57 size_t start = 0;
58 size_t length = 0;
60 bool present() const { return length > 0; }
63 static JSLinearString* ToString(JSContext* cx, JSString* string,
64 const StringName& name) {
65 MOZ_ASSERT(name.present());
66 return NewDependentString(cx, string, name.start, name.length);
69 template <typename CharT>
70 bool EqualCharIgnoreCaseAscii(CharT c1, char c2) {
71 if constexpr (sizeof(CharT) > sizeof(char)) {
72 if (!mozilla::IsAscii(c1)) {
73 return false;
77 static constexpr auto toLower = 0x20;
78 static_assert('a' - 'A' == toLower);
80 // Convert both characters to lower case before the comparison.
81 char c = c1;
82 if (mozilla::IsAsciiUppercaseAlpha(c1)) {
83 c = c + toLower;
85 char d = c2;
86 if (mozilla::IsAsciiUppercaseAlpha(c2)) {
87 d = d + toLower;
89 return c == d;
92 using CalendarName = StringName;
93 using AnnotationKey = StringName;
94 using AnnotationValue = StringName;
95 using TimeZoneName = StringName;
97 struct Annotation final {
98 AnnotationKey key;
99 AnnotationValue value;
100 bool critical = false;
103 struct TimeSpec final {
104 PlainTime time;
107 struct TimeZoneUTCOffset final {
108 // ±1 for time zones with an offset, otherwise 0.
109 int32_t sign = 0;
111 // An integer in the range [0, 23].
112 int32_t hour = 0;
114 // An integer in the range [0, 59].
115 int32_t minute = 0;
118 struct DateTimeUTCOffset final {
119 // ±1 for time zones with an offset, otherwise 0.
120 int32_t sign = 0;
122 // An integer in the range [0, 23].
123 int32_t hour = 0;
125 // An integer in the range [0, 59].
126 int32_t minute = 0;
128 // An integer in the range [0, 59].
129 int32_t second = 0;
131 // An integer in the range [0, 999'999].
132 int32_t fractionalPart = 0;
134 // Time zone with sub-minute precision.
135 bool subMinutePrecision = false;
137 // Convert to a TimeZoneUTCOffset.
138 TimeZoneUTCOffset toTimeZoneUTCOffset() const {
139 MOZ_ASSERT(!subMinutePrecision, "unexpected sub-minute precision");
140 return {sign, hour, minute};
145 * ParseDateTimeUTCOffset ( offsetString )
147 static int64_t ParseDateTimeUTCOffset(const DateTimeUTCOffset& offset) {
148 constexpr int64_t nanoPerSec = 1'000'000'000;
150 MOZ_ASSERT(offset.sign == -1 || offset.sign == +1);
151 MOZ_ASSERT(0 <= offset.hour && offset.hour < 24);
152 MOZ_ASSERT(0 <= offset.minute && offset.minute < 60);
153 MOZ_ASSERT(0 <= offset.second && offset.second < 60);
154 MOZ_ASSERT(0 <= offset.fractionalPart && offset.fractionalPart < nanoPerSec);
156 // sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds).
157 int64_t seconds = (offset.hour * 60 + offset.minute) * 60 + offset.second;
158 int64_t nanos = (seconds * nanoPerSec) + offset.fractionalPart;
159 int64_t result = offset.sign * nanos;
161 MOZ_ASSERT(std::abs(result) < ToNanoseconds(TemporalUnit::Day),
162 "time zone offset is less than 24:00 hours");
164 return result;
167 static int32_t ParseTimeZoneOffset(const TimeZoneUTCOffset& offset) {
168 MOZ_ASSERT(offset.sign == -1 || offset.sign == +1);
169 MOZ_ASSERT(0 <= offset.hour && offset.hour < 24);
170 MOZ_ASSERT(0 <= offset.minute && offset.minute < 60);
172 // sign × (hour × 60 + minute).
173 int32_t result = offset.sign * (offset.hour * 60 + offset.minute);
175 MOZ_ASSERT(std::abs(result) < UnitsPerDay(TemporalUnit::Minute),
176 "time zone offset is less than 24:00 hours");
178 return result;
182 * Struct to hold time zone annotations.
184 struct TimeZoneAnnotation final {
185 // Time zone offset.
186 TimeZoneUTCOffset offset;
188 // Time zone name.
189 TimeZoneName name;
192 * Returns true iff the time zone has an offset part, e.g. "+01:00".
194 bool hasOffset() const { return offset.sign != 0; }
197 * Returns true iff the time zone has an IANA name, e.g. "Asia/Tokyo".
199 bool hasName() const { return name.present(); }
203 * Struct to hold any time zone parts of a parsed string.
205 struct TimeZoneString final {
206 // Date-time UTC offset.
207 DateTimeUTCOffset offset;
209 // Time zone annotation;
210 TimeZoneAnnotation annotation;
212 // UTC time zone.
213 bool utc = false;
215 static auto from(DateTimeUTCOffset offset) {
216 TimeZoneString timeZone{};
217 timeZone.offset = offset;
218 return timeZone;
221 static auto from(TimeZoneUTCOffset offset) {
222 TimeZoneString timeZone{};
223 timeZone.annotation.offset = offset;
224 return timeZone;
227 static auto from(TimeZoneName name) {
228 TimeZoneString timeZone{};
229 timeZone.annotation.name = name;
230 return timeZone;
233 static auto UTC() {
234 TimeZoneString timeZone{};
235 timeZone.utc = true;
236 return timeZone;
240 * Returns true iff the time zone has an offset part, e.g. "+01:00".
242 bool hasOffset() const { return offset.sign != 0; }
245 * Returns true iff the time zone has an annotation.
247 bool hasAnnotation() const {
248 return annotation.hasName() || annotation.hasOffset();
252 * Returns true iff the time zone uses the "Z" abbrevation to denote UTC time.
254 bool isUTC() const { return utc; }
258 * Struct to hold the parsed date, time, time zone, and calendar components.
260 struct ZonedDateTimeString final {
261 PlainDate date;
262 PlainTime time;
263 TimeZoneString timeZone;
264 CalendarName calendar;
267 template <typename CharT>
268 static bool IsISO8601Calendar(mozilla::Span<const CharT> calendar) {
269 static constexpr std::string_view iso8601 = "iso8601";
271 if (calendar.size() != iso8601.length()) {
272 return false;
275 for (size_t i = 0; i < iso8601.length(); i++) {
276 if (!EqualCharIgnoreCaseAscii(calendar[i], iso8601[i])) {
277 return false;
280 return true;
283 static constexpr int32_t AbsentYear = INT32_MAX;
286 * ParseISODateTime ( isoString )
288 static bool ParseISODateTime(JSContext* cx, const ZonedDateTimeString& parsed,
289 PlainDateTime* result) {
290 // Steps 1-6, 8, 10-13 (Not applicable here).
292 PlainDateTime dateTime = {parsed.date, parsed.time};
294 // NOTE: ToIntegerOrInfinity("") is 0.
295 if (dateTime.date.year == AbsentYear) {
296 dateTime.date.year = 0;
299 // Step 7.
300 if (dateTime.date.month == 0) {
301 dateTime.date.month = 1;
304 // Step 9.
305 if (dateTime.date.day == 0) {
306 dateTime.date.day = 1;
309 // Step 14.
310 if (dateTime.time.second == 60) {
311 dateTime.time.second = 59;
314 // ParseISODateTime, steps 15-16 (Not applicable in our implementation).
316 // Call ThrowIfInvalidISODate to report an error if |days| exceeds the number
317 // of days in the month. All other values are already in-bounds.
318 MOZ_ASSERT(std::abs(dateTime.date.year) <= 999'999);
319 MOZ_ASSERT(1 <= dateTime.date.month && dateTime.date.month <= 12);
320 MOZ_ASSERT(1 <= dateTime.date.day && dateTime.date.day <= 31);
322 // ParseISODateTime, step 17.
323 if (!ThrowIfInvalidISODate(cx, dateTime.date)) {
324 return false;
327 // ParseISODateTime, step 18.
328 MOZ_ASSERT(IsValidTime(dateTime.time));
330 // Steps 19-25. (Handled in caller.)
332 *result = dateTime;
333 return true;
336 static bool ParseTimeZoneAnnotation(JSContext* cx,
337 const TimeZoneAnnotation& annotation,
338 JSLinearString* linear,
339 MutableHandle<ParsedTimeZone> result) {
340 MOZ_ASSERT(annotation.hasOffset() || annotation.hasName());
342 if (annotation.hasOffset()) {
343 int32_t offset = ParseTimeZoneOffset(annotation.offset);
344 result.set(ParsedTimeZone::fromOffset(offset));
345 return true;
348 auto* str = ToString(cx, linear, annotation.name);
349 if (!str) {
350 return false;
352 result.set(ParsedTimeZone::fromName(str));
353 return true;
357 * Struct for the parsed duration components.
359 struct TemporalDurationString final {
360 // A non-negative integer or +Infinity.
361 double years = 0;
363 // A non-negative integer or +Infinity.
364 double months = 0;
366 // A non-negative integer or +Infinity.
367 double weeks = 0;
369 // A non-negative integer or +Infinity.
370 double days = 0;
372 // A non-negative integer or +Infinity.
373 double hours = 0;
375 // A non-negative integer or +Infinity.
376 double minutes = 0;
378 // A non-negative integer or +Infinity.
379 double seconds = 0;
381 // An integer in the range [0, 999'999].
382 int32_t hoursFraction = 0;
384 // An integer in the range [0, 999'999].
385 int32_t minutesFraction = 0;
387 // An integer in the range [0, 999'999].
388 int32_t secondsFraction = 0;
390 // ±1 when an offset is present, otherwise 0.
391 int32_t sign = 0;
394 class ParserError final {
395 JSErrNum error_ = JSMSG_NOT_AN_ERROR;
397 public:
398 constexpr MOZ_IMPLICIT ParserError(JSErrNum error) : error_(error) {}
400 constexpr JSErrNum error() const { return error_; }
402 constexpr operator JSErrNum() const { return error(); }
405 namespace mozilla::detail {
406 // Zero is used for tagging, so it mustn't be an error.
407 static_assert(static_cast<JSErrNum>(0) == JSMSG_NOT_AN_ERROR);
409 // Ensure efficient packing of the error type.
410 template <>
411 struct UnusedZero<::ParserError> {
412 private:
413 using Error = ::ParserError;
414 using ErrorKind = JSErrNum;
416 public:
417 using StorageType = std::underlying_type_t<ErrorKind>;
419 static constexpr bool value = true;
420 static constexpr StorageType nullValue = 0;
422 static constexpr Error Inspect(const StorageType& aValue) {
423 return Error(static_cast<ErrorKind>(aValue));
425 static constexpr Error Unwrap(StorageType aValue) {
426 return Error(static_cast<ErrorKind>(aValue));
428 static constexpr StorageType Store(Error aValue) {
429 return static_cast<StorageType>(aValue.error());
432 } // namespace mozilla::detail
434 static_assert(mozilla::Result<ZonedDateTimeString, ParserError>::Strategy !=
435 mozilla::detail::PackingStrategy::Variant);
437 template <typename CharT>
438 class StringReader final {
439 mozilla::Span<const CharT> string_;
441 // Current position in the string.
442 size_t index_ = 0;
444 public:
445 explicit StringReader(mozilla::Span<const CharT> string) : string_(string) {}
448 * Returns the input string.
450 mozilla::Span<const CharT> string() const { return string_; }
453 * Returns a substring of the input string.
455 mozilla::Span<const CharT> substring(const StringName& name) const {
456 MOZ_ASSERT(name.present());
457 return string_.Subspan(name.start, name.length);
461 * Returns the current parse position.
463 size_t index() const { return index_; }
466 * Returns the length of the input string-
468 size_t length() const { return string_.size(); }
471 * Returns true iff the whole string has been parsed.
473 bool atEnd() const { return index() == length(); }
476 * Reset the parser to a previous parse position.
478 void reset(size_t index = 0) {
479 MOZ_ASSERT(index <= length());
480 index_ = index;
484 * Returns true if at least `amount` characters can be read from the current
485 * parse position.
487 bool hasMore(size_t amount) const { return index() + amount <= length(); }
490 * Advances the parse position by `amount` characters.
492 void advance(size_t amount) {
493 MOZ_ASSERT(hasMore(amount));
494 index_ += amount;
498 * Returns the character at the current parse position.
500 CharT current() const { return string()[index()]; }
503 * Returns the character at the next parse position.
505 CharT next() const { return string()[index() + 1]; }
508 * Returns the character at position `index`.
510 CharT at(size_t index) const { return string()[index]; }
513 template <typename CharT>
514 class TemporalParser final {
515 StringReader<CharT> reader_;
518 * Read an unlimited amount of decimal digits, returning `Nothing` if no
519 * digits were read.
521 mozilla::Maybe<double> digits(JSContext* cx);
524 * Read exactly `length` digits, returning `Nothing` on failure.
526 mozilla::Maybe<int32_t> digits(size_t length) {
527 MOZ_ASSERT(length > 0, "can't read zero digits");
528 MOZ_ASSERT(length <= std::numeric_limits<int32_t>::digits10,
529 "can't read more than digits10 digits without overflow");
531 if (!reader_.hasMore(length)) {
532 return mozilla::Nothing();
534 int32_t num = 0;
535 size_t index = reader_.index();
536 for (size_t i = 0; i < length; i++) {
537 auto ch = reader_.at(index + i);
538 if (!mozilla::IsAsciiDigit(ch)) {
539 return mozilla::Nothing();
541 num = num * 10 + AsciiDigitToNumber(ch);
543 reader_.advance(length);
544 return mozilla::Some(num);
547 // TimeFractionalPart :
548 // Digit{1, 9}
550 // Fraction :
551 // DecimalSeparator TimeFractionalPart
552 mozilla::Maybe<int32_t> fraction() {
553 if (!reader_.hasMore(2)) {
554 return mozilla::Nothing();
556 if (!hasDecimalSeparator() || !mozilla::IsAsciiDigit(reader_.next())) {
557 return mozilla::Nothing();
560 // Consume the decimal separator.
561 MOZ_ALWAYS_TRUE(decimalSeparator());
563 // Maximal nine fractional digits are supported.
564 constexpr size_t maxFractions = 9;
566 // Read up to |maxFractions| digits.
567 int32_t num = 0;
568 size_t index = reader_.index();
569 size_t i = 0;
570 for (; i < std::min(reader_.length() - index, maxFractions); i++) {
571 CharT ch = reader_.at(index + i);
572 if (!mozilla::IsAsciiDigit(ch)) {
573 break;
575 num = num * 10 + AsciiDigitToNumber(ch);
578 // Skip past the read digits.
579 reader_.advance(i);
581 // Normalize the fraction to |maxFractions| digits.
582 for (; i < maxFractions; i++) {
583 num *= 10;
585 return mozilla::Some(num);
589 * Returns true iff the current character is `ch`.
591 bool hasCharacter(CharT ch) const {
592 return reader_.hasMore(1) && reader_.current() == ch;
596 * Consumes the current character if it's equal to `ch` and then returns
597 * `true`. Otherwise returns `false`.
599 bool character(CharT ch) {
600 if (!hasCharacter(ch)) {
601 return false;
603 reader_.advance(1);
604 return true;
608 * Consumes the next characters if they're equal to `str` and then returns
609 * `true`. Otherwise returns `false`.
611 template <size_t N>
612 bool string(const char (&str)[N]) {
613 static_assert(N > 2, "use character() for one element strings");
615 if (!reader_.hasMore(N - 1)) {
616 return false;
618 size_t index = reader_.index();
619 for (size_t i = 0; i < N - 1; i++) {
620 if (reader_.at(index + i) != str[i]) {
621 return false;
624 reader_.advance(N - 1);
625 return true;
629 * Returns true if the next two characters are ASCII alphabetic characters.
631 bool hasTwoAsciiAlpha() {
632 if (!reader_.hasMore(2)) {
633 return false;
635 size_t index = reader_.index();
636 return mozilla::IsAsciiAlpha(reader_.at(index)) &&
637 mozilla::IsAsciiAlpha(reader_.at(index + 1));
641 * Returns true iff the current character is one of `chars`.
643 bool hasOneOf(std::initializer_list<char16_t> chars) const {
644 if (!reader_.hasMore(1)) {
645 return false;
647 auto ch = reader_.current();
648 return std::find(chars.begin(), chars.end(), ch) != chars.end();
652 * Consumes the current character if it's in `chars` and then returns `true`.
653 * Otherwise returns `false`.
655 bool oneOf(std::initializer_list<char16_t> chars) {
656 if (!hasOneOf(chars)) {
657 return false;
659 reader_.advance(1);
660 return true;
664 * Consumes the current character if it matches the predicate and then returns
665 * `true`. Otherwise returns `false`.
667 template <typename Predicate>
668 bool matches(Predicate&& predicate) {
669 if (!reader_.hasMore(1)) {
670 return false;
673 CharT ch = reader_.current();
674 if (!predicate(ch)) {
675 return false;
678 reader_.advance(1);
679 return true;
682 // Sign :
683 // ASCIISign
684 // U+2212
686 // ASCIISign : one of
687 // + -
688 bool hasSign() const { return hasOneOf({'+', '-', 0x2212}); }
691 * Consumes the current character, which must be a sign character, and returns
692 * its numeric value.
694 int32_t sign() {
695 MOZ_ASSERT(hasSign());
696 int32_t plus = hasCharacter('+');
697 reader_.advance(1);
698 return plus ? 1 : -1;
701 // DecimalSeparator : one of
702 // . ,
703 bool hasDecimalSeparator() const { return hasOneOf({'.', ','}); }
705 bool decimalSeparator() { return oneOf({'.', ','}); }
707 // DaysDesignator : one of
708 // D d
709 bool daysDesignator() { return oneOf({'D', 'd'}); }
711 // HoursDesignator : one of
712 // H h
713 bool hoursDesignator() { return oneOf({'H', 'h'}); }
715 // MinutesDesignator : one of
716 // M m
717 bool minutesDesignator() { return oneOf({'M', 'm'}); }
719 // MonthsDesignator : one of
720 // M m
721 bool monthsDesignator() { return oneOf({'M', 'm'}); }
723 // DurationDesignator : one of
724 // P p
725 bool durationDesignator() { return oneOf({'P', 'p'}); }
727 // SecondsDesignator : one of
728 // S s
729 bool secondsDesignator() { return oneOf({'S', 's'}); }
731 // DateTimeSeparator :
732 // <SP>
733 // T
734 // t
735 bool dateTimeSeparator() { return oneOf({' ', 'T', 't'}); }
737 // TimeDesignator : one of
738 // T t
739 bool hasTimeDesignator() const { return hasOneOf({'T', 't'}); }
741 bool timeDesignator() { return oneOf({'T', 't'}); }
743 // WeeksDesignator : one of
744 // W w
745 bool weeksDesignator() { return oneOf({'W', 'w'}); }
747 // YearsDesignator : one of
748 // Y y
749 bool yearsDesignator() { return oneOf({'Y', 'y'}); }
751 // UTCDesignator : one of
752 // Z z
753 bool utcDesignator() { return oneOf({'Z', 'z'}); }
755 // TZLeadingChar :
756 // Alpha
757 // .
758 // _
759 bool tzLeadingChar() {
760 return matches([](auto ch) {
761 return mozilla::IsAsciiAlpha(ch) || ch == '.' || ch == '_';
765 // TZChar :
766 // TZLeadingChar
767 // DecimalDigit
768 // -
769 // +
770 bool tzChar() {
771 return matches([](auto ch) {
772 return mozilla::IsAsciiAlphanumeric(ch) || ch == '.' || ch == '_' ||
773 ch == '-' || ch == '+';
777 // AnnotationCriticalFlag :
778 // !
779 bool annotationCriticalFlag() { return character('!'); }
781 // AKeyLeadingChar :
782 // LowercaseAlpha
783 // _
784 bool aKeyLeadingChar() {
785 return matches([](auto ch) {
786 return mozilla::IsAsciiLowercaseAlpha(ch) || ch == '_';
790 // AKeyChar :
791 // AKeyLeadingChar
792 // DecimalDigit
793 // -
794 bool aKeyChar() {
795 return matches([](auto ch) {
796 return mozilla::IsAsciiLowercaseAlpha(ch) || mozilla::IsAsciiDigit(ch) ||
797 ch == '-' || ch == '_';
801 // AnnotationValueComponent :
802 // Alpha AnnotationValueComponent?
803 // DecimalDigit AnnotationValueComponent?
804 bool annotationValueComponent() {
805 size_t index = reader_.index();
806 size_t i = 0;
807 for (; index + i < reader_.length(); i++) {
808 auto ch = reader_.at(index + i);
809 if (!mozilla::IsAsciiAlphanumeric(ch)) {
810 break;
813 if (i == 0) {
814 return false;
816 reader_.advance(i);
817 return true;
820 template <typename T>
821 static constexpr bool inBounds(const T& x, const T& min, const T& max) {
822 return min <= x && x <= max;
825 mozilla::Result<ZonedDateTimeString, ParserError> dateTime();
827 mozilla::Result<PlainDate, ParserError> date();
829 mozilla::Result<PlainDate, ParserError> dateSpecYearMonth();
831 mozilla::Result<PlainDate, ParserError> dateSpecMonthDay();
833 mozilla::Result<PlainDate, ParserError> validMonthDay();
835 mozilla::Result<PlainTime, ParserError> timeSpec();
837 // Return true when |Annotation| can start at the current position.
838 bool hasAnnotationStart() const { return hasCharacter('['); }
840 // Return true when |TimeZoneAnnotation| can start at the current position.
841 bool hasTimeZoneAnnotationStart() const {
842 if (!hasCharacter('[')) {
843 return false;
846 // Ensure no '=' is found before the closing ']', otherwise the opening '['
847 // may actually start an |Annotation| instead of a |TimeZoneAnnotation|.
848 for (size_t i = reader_.index() + 1; i < reader_.length(); i++) {
849 CharT ch = reader_.at(i);
850 if (ch == '=') {
851 return false;
853 if (ch == ']') {
854 break;
857 return true;
860 // Return true when |DateTimeUTCOffset| can start at the current position.
861 bool hasDateTimeUTCOffsetStart() {
862 return hasOneOf({'Z', 'z', '+', '-', 0x2212});
865 mozilla::Result<TimeZoneString, ParserError> dateTimeUTCOffset();
867 mozilla::Result<DateTimeUTCOffset, ParserError> utcOffsetSubMinutePrecision();
869 mozilla::Result<TimeZoneUTCOffset, ParserError> timeZoneUTCOffsetName();
871 mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneIdentifier();
873 mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneAnnotation();
875 mozilla::Result<TimeZoneName, ParserError> timeZoneIANAName();
877 mozilla::Result<AnnotationKey, ParserError> annotationKey();
878 mozilla::Result<AnnotationValue, ParserError> annotationValue();
879 mozilla::Result<Annotation, ParserError> annotation();
880 mozilla::Result<CalendarName, ParserError> annotations();
882 mozilla::Result<ZonedDateTimeString, ParserError> annotatedTime();
884 mozilla::Result<ZonedDateTimeString, ParserError> annotatedDateTime();
886 mozilla::Result<ZonedDateTimeString, ParserError>
887 annotatedDateTimeTimeRequired();
889 mozilla::Result<ZonedDateTimeString, ParserError> annotatedYearMonth();
891 mozilla::Result<ZonedDateTimeString, ParserError> annotatedMonthDay();
893 public:
894 explicit TemporalParser(mozilla::Span<const CharT> str) : reader_(str) {}
896 mozilla::Result<ZonedDateTimeString, ParserError>
897 parseTemporalInstantString();
899 mozilla::Result<ZonedDateTimeString, ParserError>
900 parseTemporalTimeZoneString();
902 mozilla::Result<TimeZoneAnnotation, ParserError> parseTimeZoneIdentifier();
904 mozilla::Result<TimeZoneUTCOffset, ParserError> parseTimeZoneOffsetString();
906 mozilla::Result<DateTimeUTCOffset, ParserError> parseDateTimeUTCOffset();
908 mozilla::Result<TemporalDurationString, ParserError>
909 parseTemporalDurationString(JSContext* cx);
911 mozilla::Result<ZonedDateTimeString, ParserError>
912 parseTemporalCalendarString();
914 mozilla::Result<ZonedDateTimeString, ParserError> parseTemporalTimeString();
916 mozilla::Result<ZonedDateTimeString, ParserError>
917 parseTemporalMonthDayString();
919 mozilla::Result<ZonedDateTimeString, ParserError>
920 parseTemporalYearMonthString();
922 mozilla::Result<ZonedDateTimeString, ParserError>
923 parseTemporalDateTimeString();
925 mozilla::Result<ZonedDateTimeString, ParserError>
926 parseTemporalZonedDateTimeString();
929 template <typename CharT>
930 mozilla::Result<ZonedDateTimeString, ParserError>
931 TemporalParser<CharT>::dateTime() {
932 // DateTime :
933 // Date
934 // Date DateTimeSeparator TimeSpec DateTimeUTCOffset?
935 ZonedDateTimeString result = {};
937 auto dt = date();
938 if (dt.isErr()) {
939 return dt.propagateErr();
941 result.date = dt.unwrap();
943 if (dateTimeSeparator()) {
944 auto time = timeSpec();
945 if (time.isErr()) {
946 return time.propagateErr();
948 result.time = time.unwrap();
950 if (hasDateTimeUTCOffsetStart()) {
951 auto tz = dateTimeUTCOffset();
952 if (tz.isErr()) {
953 return tz.propagateErr();
955 result.timeZone = tz.unwrap();
959 return result;
962 template <typename CharT>
963 mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
964 // Date :
965 // DateYear - DateMonth - DateDay
966 // DateYear DateMonth DateDay
967 PlainDate result = {};
969 // DateYear :
970 // DecimalDigit{4}
971 // Sign DecimalDigit{6}
972 if (auto year = digits(4)) {
973 result.year = year.value();
974 } else if (hasSign()) {
975 int32_t yearSign = sign();
976 if (auto year = digits(6)) {
977 result.year = yearSign * year.value();
978 if (yearSign < 0 && result.year == 0) {
979 return mozilla::Err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR);
981 } else {
982 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR);
984 } else {
985 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR);
988 // Optional: -
989 character('-');
991 // DateMonth :
992 // 0 NonzeroDigit
993 // 10
994 // 11
995 // 12
996 if (auto month = digits(2)) {
997 result.month = month.value();
998 if (!inBounds(result.month, 1, 12)) {
999 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
1001 } else {
1002 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
1005 // Optional: -
1006 character('-');
1008 // DateDay :
1009 // 0 NonzeroDigit
1010 // 1 DecimalDigit
1011 // 2 DecimalDigit
1012 // 30
1013 // 31
1014 if (auto day = digits(2)) {
1015 result.day = day.value();
1016 if (!inBounds(result.day, 1, 31)) {
1017 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
1019 } else {
1020 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
1023 return result;
1026 template <typename CharT>
1027 mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
1028 // TimeSpec :
1029 // TimeHour
1030 // TimeHour : TimeMinute
1031 // TimeHour TimeMinute
1032 // TimeHour : TimeMinute : TimeSecond TimeFraction?
1033 // TimeHour TimeMinute TimeSecond TimeFraction?
1034 PlainTime result = {};
1036 // TimeHour :
1037 // Hour[+Padded]
1039 // Hour[Padded] :
1040 // [~Padded] DecimalDigit
1041 // [~Padded] 0 DecimalDigit
1042 // 1 DecimalDigit
1043 // 20
1044 // 21
1045 // 22
1046 // 23
1047 if (auto hour = digits(2)) {
1048 result.hour = hour.value();
1049 if (!inBounds(result.hour, 0, 23)) {
1050 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
1052 } else {
1053 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
1056 // Optional: :
1057 bool needsMinutes = character(':');
1059 // TimeMinute :
1060 // MinuteSecond
1062 // MinuteSecond :
1063 // 0 DecimalDigit
1064 // 1 DecimalDigit
1065 // 2 DecimalDigit
1066 // 3 DecimalDigit
1067 // 4 DecimalDigit
1068 // 5 DecimalDigit
1069 if (auto minute = digits(2)) {
1070 result.minute = minute.value();
1071 if (!inBounds(result.minute, 0, 59)) {
1072 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
1075 // Optional: :
1076 bool needsSeconds = needsMinutes && character(':');
1078 // TimeSecond :
1079 // MinuteSecond
1080 // 60
1081 if (auto second = digits(2)) {
1082 result.second = second.value();
1083 if (!inBounds(result.second, 0, 60)) {
1084 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_LEAPSECOND);
1087 // TimeFraction :
1088 // Fraction
1089 if (auto f = fraction()) {
1090 int32_t fractionalPart = f.value();
1091 result.millisecond = fractionalPart / 1'000'000;
1092 result.microsecond = (fractionalPart % 1'000'000) / 1'000;
1093 result.nanosecond = fractionalPart % 1'000;
1095 } else if (needsSeconds) {
1096 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
1098 } else if (needsMinutes) {
1099 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
1102 return result;
1105 template <typename CharT>
1106 mozilla::Result<TimeZoneString, ParserError>
1107 TemporalParser<CharT>::dateTimeUTCOffset() {
1108 // DateTimeUTCOffset :
1109 // UTCDesignator
1110 // UTCOffsetSubMinutePrecision
1112 if (utcDesignator()) {
1113 return TimeZoneString::UTC();
1116 if (hasSign()) {
1117 auto offset = utcOffsetSubMinutePrecision();
1118 if (offset.isErr()) {
1119 return offset.propagateErr();
1121 return TimeZoneString::from(offset.unwrap());
1124 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
1127 template <typename CharT>
1128 mozilla::Result<TimeZoneUTCOffset, ParserError>
1129 TemporalParser<CharT>::timeZoneUTCOffsetName() {
1130 // TimeZoneUTCOffsetName :
1131 // UTCOffsetMinutePrecision
1133 // UTCOffsetMinutePrecision :
1134 // Sign Hour[+Padded]
1135 // Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
1136 // Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
1138 TimeZoneUTCOffset result = {};
1140 if (!hasSign()) {
1141 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
1143 result.sign = sign();
1145 // Hour[Padded] :
1146 // [~Padded] DecimalDigit
1147 // [+Padded] 0 DecimalDigit
1148 // 1 DecimalDigit
1149 // 20
1150 // 21
1151 // 22
1152 // 23
1153 if (auto hour = digits(2)) {
1154 result.hour = hour.value();
1155 if (!inBounds(result.hour, 0, 23)) {
1156 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
1158 } else {
1159 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
1162 // TimeSeparator[Extended] :
1163 // [+Extended] :
1164 // [~Extended] [empty]
1165 bool needsMinutes = character(':');
1167 // MinuteSecond :
1168 // 0 DecimalDigit
1169 // 1 DecimalDigit
1170 // 2 DecimalDigit
1171 // 3 DecimalDigit
1172 // 4 DecimalDigit
1173 // 5 DecimalDigit
1174 if (auto minute = digits(2)) {
1175 result.minute = minute.value();
1176 if (!inBounds(result.minute, 0, 59)) {
1177 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
1180 if (hasCharacter(':')) {
1181 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
1183 } else if (needsMinutes) {
1184 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
1187 return result;
1190 template <typename CharT>
1191 mozilla::Result<DateTimeUTCOffset, ParserError>
1192 TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
1193 // clang-format off
1195 // UTCOffsetSubMinutePrecision :
1196 // UTCOffsetMinutePrecision
1197 // UTCOffsetWithSubMinuteComponents[+Extended]
1198 // UTCOffsetWithSubMinuteComponents[~Extended]
1200 // UTCOffsetMinutePrecision :
1201 // Sign Hour[+Padded]
1202 // Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
1203 // Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
1205 // UTCOffsetWithSubMinuteComponents[Extended] :
1206 // Sign Hour[+Padded] TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond Fraction?
1208 // clang-format on
1210 DateTimeUTCOffset result = {};
1212 if (!hasSign()) {
1213 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
1215 result.sign = sign();
1217 // Hour[Padded] :
1218 // [~Padded] DecimalDigit
1219 // [+Padded] 0 DecimalDigit
1220 // 1 DecimalDigit
1221 // 20
1222 // 21
1223 // 22
1224 // 23
1225 if (auto hour = digits(2)) {
1226 result.hour = hour.value();
1227 if (!inBounds(result.hour, 0, 23)) {
1228 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
1230 } else {
1231 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
1234 // TimeSeparator[Extended] :
1235 // [+Extended] :
1236 // [~Extended] [empty]
1237 bool needsMinutes = character(':');
1239 // MinuteSecond :
1240 // 0 DecimalDigit
1241 // 1 DecimalDigit
1242 // 2 DecimalDigit
1243 // 3 DecimalDigit
1244 // 4 DecimalDigit
1245 // 5 DecimalDigit
1246 if (auto minute = digits(2)) {
1247 result.minute = minute.value();
1248 if (!inBounds(result.minute, 0, 59)) {
1249 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
1252 // TimeSeparator[Extended] :
1253 // [+Extended] :
1254 // [~Extended] [empty]
1255 bool needsSeconds = needsMinutes && character(':');
1257 // MinuteSecond :
1258 // 0 DecimalDigit
1259 // 1 DecimalDigit
1260 // 2 DecimalDigit
1261 // 3 DecimalDigit
1262 // 4 DecimalDigit
1263 // 5 DecimalDigit
1264 if (auto second = digits(2)) {
1265 result.second = second.value();
1266 if (!inBounds(result.second, 0, 59)) {
1267 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SECOND);
1270 if (auto fractionalPart = fraction()) {
1271 result.fractionalPart = fractionalPart.value();
1274 result.subMinutePrecision = true;
1275 } else if (needsSeconds) {
1276 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
1278 } else if (needsMinutes) {
1279 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
1282 return result;
1285 template <typename CharT>
1286 mozilla::Result<TimeZoneAnnotation, ParserError>
1287 TemporalParser<CharT>::timeZoneIdentifier() {
1288 // TimeZoneIdentifier :
1289 // TimeZoneUTCOffsetName
1290 // TimeZoneIANAName
1292 TimeZoneAnnotation result = {};
1293 if (hasSign()) {
1294 auto offset = timeZoneUTCOffsetName();
1295 if (offset.isErr()) {
1296 return offset.propagateErr();
1298 result.offset = offset.unwrap();
1299 } else {
1300 auto name = timeZoneIANAName();
1301 if (name.isErr()) {
1302 return name.propagateErr();
1304 result.name = name.unwrap();
1307 return result;
1310 template <typename CharT>
1311 mozilla::Result<TimeZoneAnnotation, ParserError>
1312 TemporalParser<CharT>::timeZoneAnnotation() {
1313 // TimeZoneAnnotation :
1314 // [ AnnotationCriticalFlag? TimeZoneIdentifier ]
1316 if (!character('[')) {
1317 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE);
1320 // Skip over the optional critical flag.
1321 annotationCriticalFlag();
1323 auto result = timeZoneIdentifier();
1324 if (result.isErr()) {
1325 return result.propagateErr();
1328 if (!character(']')) {
1329 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_TIMEZONE);
1332 return result;
1335 template <typename CharT>
1336 mozilla::Result<TimeZoneName, ParserError>
1337 TemporalParser<CharT>::timeZoneIANAName() {
1338 // TimeZoneIANAName :
1339 // TimeZoneIANANameComponent
1340 // TimeZoneIANAName / TimeZoneIANANameComponent
1342 // TimeZoneIANANameComponent :
1343 // TZLeadingChar
1344 // TimeZoneIANANameComponent TZChar
1346 size_t start = reader_.index();
1348 do {
1349 if (!tzLeadingChar()) {
1350 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_NAME);
1353 // Optionally followed by a sequence of |TZChar|.
1354 while (tzChar()) {
1356 } while (character('/'));
1358 return TimeZoneName{start, reader_.index() - start};
1361 template <typename CharT>
1362 mozilla::Maybe<double> TemporalParser<CharT>::digits(JSContext* cx) {
1363 auto span = reader_.string().Subspan(reader_.index());
1365 // GetPrefixInteger can't fail when integer separator handling is disabled.
1366 const CharT* endp = nullptr;
1367 double num;
1368 MOZ_ALWAYS_TRUE(GetPrefixInteger(span.data(), span.data() + span.size(), 10,
1369 IntegerSeparatorHandling::None, &endp,
1370 &num));
1372 size_t len = endp - span.data();
1373 if (len == 0) {
1374 return mozilla::Nothing();
1376 reader_.advance(len);
1377 return mozilla::Some(num);
1380 template <typename CharT>
1381 mozilla::Result<ZonedDateTimeString, ParserError>
1382 TemporalParser<CharT>::parseTemporalInstantString() {
1383 // Initialize all fields to zero.
1384 ZonedDateTimeString result = {};
1386 // clang-format off
1388 // TemporalInstantString :
1389 // Date DateTimeSeparator TimeSpec DateTimeUTCOffset TimeZoneAnnotation? Annotations?
1391 // clang-format on
1393 auto dt = date();
1394 if (dt.isErr()) {
1395 return dt.propagateErr();
1397 result.date = dt.unwrap();
1399 if (!dateTimeSeparator()) {
1400 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR);
1403 auto time = timeSpec();
1404 if (time.isErr()) {
1405 return time.propagateErr();
1407 result.time = time.unwrap();
1409 auto tz = dateTimeUTCOffset();
1410 if (tz.isErr()) {
1411 return tz.propagateErr();
1413 result.timeZone = tz.unwrap();
1415 if (hasTimeZoneAnnotationStart()) {
1416 auto annotation = timeZoneAnnotation();
1417 if (annotation.isErr()) {
1418 return annotation.propagateErr();
1420 result.timeZone.annotation = annotation.unwrap();
1423 if (hasAnnotationStart()) {
1424 if (auto cal = annotations(); cal.isErr()) {
1425 return cal.propagateErr();
1429 if (!reader_.atEnd()) {
1430 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
1433 return result;
1437 * ParseTemporalInstantString ( isoString )
1439 template <typename CharT>
1440 static auto ParseTemporalInstantString(mozilla::Span<const CharT> str) {
1441 TemporalParser<CharT> parser(str);
1442 return parser.parseTemporalInstantString();
1446 * ParseTemporalInstantString ( isoString )
1448 static auto ParseTemporalInstantString(Handle<JSLinearString*> str) {
1449 JS::AutoCheckCannotGC nogc;
1450 if (str->hasLatin1Chars()) {
1451 return ParseTemporalInstantString<Latin1Char>(str->latin1Range(nogc));
1453 return ParseTemporalInstantString<char16_t>(str->twoByteRange(nogc));
1457 * ParseTemporalInstantString ( isoString )
1459 bool js::temporal::ParseTemporalInstantString(JSContext* cx,
1460 Handle<JSString*> str,
1461 PlainDateTime* result,
1462 int64_t* offset) {
1463 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1464 if (!linear) {
1465 return false;
1468 // Step 1.
1469 auto parseResult = ::ParseTemporalInstantString(linear);
1470 if (parseResult.isErr()) {
1471 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1472 parseResult.unwrapErr());
1473 return false;
1475 ZonedDateTimeString parsed = parseResult.unwrap();
1477 // Step 2.
1478 if (!ParseISODateTime(cx, parsed, result)) {
1479 return false;
1482 // Steps 3-4.
1483 if (parsed.timeZone.hasOffset()) {
1484 *offset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
1485 } else {
1486 MOZ_ASSERT(parsed.timeZone.isUTC());
1487 *offset = 0;
1489 return true;
1492 template <typename CharT>
1493 mozilla::Result<ZonedDateTimeString, ParserError>
1494 TemporalParser<CharT>::parseTemporalTimeZoneString() {
1495 // TimeZoneIdentifier :
1496 // TimeZoneUTCOffsetName
1497 // TimeZoneIANAName
1499 if (hasSign()) {
1500 if (auto offset = timeZoneUTCOffsetName();
1501 offset.isOk() && reader_.atEnd()) {
1502 ZonedDateTimeString result = {};
1503 result.timeZone = TimeZoneString::from(offset.unwrap());
1504 return result;
1506 } else {
1507 if (auto name = timeZoneIANAName(); name.isOk() && reader_.atEnd()) {
1508 ZonedDateTimeString result = {};
1509 result.timeZone = TimeZoneString::from(name.unwrap());
1510 return result;
1514 // Try all five parse goals from ParseISODateTime in order.
1516 // TemporalDateTimeString
1517 // TemporalInstantString
1518 // TemporalTimeString
1519 // TemporalMonthDayString
1520 // TemporalYearMonthString
1522 // Restart parsing from the start of the string.
1523 reader_.reset();
1525 if (auto dt = parseTemporalDateTimeString(); dt.isOk()) {
1526 return dt.unwrap();
1529 // Restart parsing from the start of the string.
1530 reader_.reset();
1532 if (auto dt = parseTemporalInstantString(); dt.isOk()) {
1533 return dt.unwrap();
1536 // Restart parsing from the start of the string.
1537 reader_.reset();
1539 if (auto dt = parseTemporalTimeString(); dt.isOk()) {
1540 return dt.unwrap();
1543 // Restart parsing from the start of the string.
1544 reader_.reset();
1546 if (auto dt = parseTemporalMonthDayString(); dt.isOk()) {
1547 return dt.unwrap();
1550 // Restart parsing from the start of the string.
1551 reader_.reset();
1553 if (auto dt = parseTemporalYearMonthString(); dt.isOk()) {
1554 return dt.unwrap();
1555 } else {
1556 return dt.propagateErr();
1561 * ParseTemporalTimeZoneString ( timeZoneString )
1563 template <typename CharT>
1564 static auto ParseTemporalTimeZoneString(mozilla::Span<const CharT> str) {
1565 TemporalParser<CharT> parser(str);
1566 return parser.parseTemporalTimeZoneString();
1570 * ParseTemporalTimeZoneString ( timeZoneString )
1572 static auto ParseTemporalTimeZoneString(Handle<JSLinearString*> str) {
1573 JS::AutoCheckCannotGC nogc;
1574 if (str->hasLatin1Chars()) {
1575 return ParseTemporalTimeZoneString<Latin1Char>(str->latin1Range(nogc));
1577 return ParseTemporalTimeZoneString<char16_t>(str->twoByteRange(nogc));
1581 * ParseTemporalTimeZoneString ( timeZoneString )
1583 bool js::temporal::ParseTemporalTimeZoneString(
1584 JSContext* cx, Handle<JSString*> str,
1585 MutableHandle<ParsedTimeZone> result) {
1586 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1587 if (!linear) {
1588 return false;
1591 // Steps 1-4.
1592 auto parseResult = ::ParseTemporalTimeZoneString(linear);
1593 if (parseResult.isErr()) {
1594 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1595 parseResult.unwrapErr());
1596 return false;
1598 ZonedDateTimeString parsed = parseResult.unwrap();
1599 const auto& timeZone = parsed.timeZone;
1601 // Step 3.
1602 PlainDateTime unused;
1603 if (!ParseISODateTime(cx, parsed, &unused)) {
1604 return false;
1607 if (timeZone.hasAnnotation()) {
1608 // Case 1: 19700101T00:00Z[+02:00]
1609 // Case 2: 19700101T00:00+00:00[+02:00]
1610 // Case 3: 19700101T00:00[+02:00]
1611 // Case 4: 19700101T00:00Z[Europe/Berlin]
1612 // Case 5: 19700101T00:00+00:00[Europe/Berlin]
1613 // Case 6: 19700101T00:00[Europe/Berlin]
1615 if (!ParseTimeZoneAnnotation(cx, timeZone.annotation, linear, result)) {
1616 return false;
1618 } else if (timeZone.isUTC()) {
1619 result.set(ParsedTimeZone::fromName(cx->names().UTC));
1620 } else if (timeZone.hasOffset()) {
1621 // ToTemporalTimeZoneSlotValue, step 7.
1623 // Error reporting for sub-minute precision moved here.
1624 if (timeZone.offset.subMinutePrecision) {
1625 JS_ReportErrorNumberASCII(
1626 cx, GetErrorMessage, nullptr,
1627 JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
1628 return false;
1631 int32_t offset = ParseTimeZoneOffset(timeZone.offset.toTimeZoneUTCOffset());
1632 result.set(ParsedTimeZone::fromOffset(offset));
1633 } else {
1634 // Step 5.
1635 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1636 JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
1637 return false;
1640 // Step 6.
1641 return true;
1644 template <typename CharT>
1645 mozilla::Result<TimeZoneAnnotation, ParserError>
1646 TemporalParser<CharT>::parseTimeZoneIdentifier() {
1647 auto result = timeZoneIdentifier();
1648 if (result.isErr()) {
1649 return result.propagateErr();
1651 if (!reader_.atEnd()) {
1652 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
1654 return result;
1658 * ParseTimeZoneIdentifier ( identifier )
1660 template <typename CharT>
1661 static auto ParseTimeZoneIdentifier(mozilla::Span<const CharT> str) {
1662 TemporalParser<CharT> parser(str);
1663 return parser.parseTimeZoneIdentifier();
1667 * ParseTimeZoneIdentifier ( identifier )
1669 static auto ParseTimeZoneIdentifier(Handle<JSLinearString*> str) {
1670 JS::AutoCheckCannotGC nogc;
1671 if (str->hasLatin1Chars()) {
1672 return ParseTimeZoneIdentifier<Latin1Char>(str->latin1Range(nogc));
1674 return ParseTimeZoneIdentifier<char16_t>(str->twoByteRange(nogc));
1678 * ParseTimeZoneIdentifier ( identifier )
1680 bool js::temporal::ParseTimeZoneIdentifier(
1681 JSContext* cx, Handle<JSString*> str,
1682 MutableHandle<ParsedTimeZone> result) {
1683 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1684 if (!linear) {
1685 return false;
1688 // Steps 1-2.
1689 auto parseResult = ::ParseTimeZoneIdentifier(linear);
1690 if (parseResult.isErr()) {
1691 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1692 parseResult.unwrapErr());
1693 return false;
1695 auto timeZone = parseResult.unwrap();
1697 // Steps 3-4.
1698 return ParseTimeZoneAnnotation(cx, timeZone, linear, result);
1701 template <typename CharT>
1702 mozilla::Result<TimeZoneUTCOffset, ParserError>
1703 TemporalParser<CharT>::parseTimeZoneOffsetString() {
1704 auto offset = timeZoneUTCOffsetName();
1705 if (offset.isErr()) {
1706 return offset.propagateErr();
1708 if (!reader_.atEnd()) {
1709 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
1711 return offset.unwrap();
1715 * ParseTimeZoneOffsetString ( isoString )
1717 template <typename CharT>
1718 static auto ParseTimeZoneOffsetString(mozilla::Span<const CharT> str) {
1719 TemporalParser<CharT> parser(str);
1720 return parser.parseTimeZoneOffsetString();
1724 * ParseTimeZoneOffsetString ( isoString )
1726 static auto ParseTimeZoneOffsetString(Handle<JSLinearString*> str) {
1727 JS::AutoCheckCannotGC nogc;
1728 if (str->hasLatin1Chars()) {
1729 return ParseTimeZoneOffsetString<Latin1Char>(str->latin1Range(nogc));
1731 return ParseTimeZoneOffsetString<char16_t>(str->twoByteRange(nogc));
1735 * ParseTimeZoneOffsetString ( isoString )
1737 bool js::temporal::ParseTimeZoneOffsetString(JSContext* cx,
1738 Handle<JSString*> str,
1739 int32_t* result) {
1740 // Step 1. (Not applicable in our implementation.)
1742 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1743 if (!linear) {
1744 return false;
1747 // Step 2.
1748 auto parseResult = ::ParseTimeZoneOffsetString(linear);
1749 if (parseResult.isErr()) {
1750 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1751 parseResult.unwrapErr());
1752 return false;
1755 // Steps 3-13.
1756 *result = ParseTimeZoneOffset(parseResult.unwrap());
1757 return true;
1760 template <typename CharT>
1761 mozilla::Result<DateTimeUTCOffset, ParserError>
1762 TemporalParser<CharT>::parseDateTimeUTCOffset() {
1763 auto offset = utcOffsetSubMinutePrecision();
1764 if (offset.isErr()) {
1765 return offset.propagateErr();
1767 if (!reader_.atEnd()) {
1768 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
1770 return offset.unwrap();
1774 * ParseDateTimeUTCOffset ( offsetString )
1776 template <typename CharT>
1777 static auto ParseDateTimeUTCOffset(mozilla::Span<const CharT> str) {
1778 TemporalParser<CharT> parser(str);
1779 return parser.parseDateTimeUTCOffset();
1783 * ParseDateTimeUTCOffset ( offsetString )
1785 static auto ParseDateTimeUTCOffset(Handle<JSLinearString*> str) {
1786 JS::AutoCheckCannotGC nogc;
1787 if (str->hasLatin1Chars()) {
1788 return ParseDateTimeUTCOffset<Latin1Char>(str->latin1Range(nogc));
1790 return ParseDateTimeUTCOffset<char16_t>(str->twoByteRange(nogc));
1794 * ParseDateTimeUTCOffset ( offsetString )
1796 bool js::temporal::ParseDateTimeUTCOffset(JSContext* cx, Handle<JSString*> str,
1797 int64_t* result) {
1798 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1799 if (!linear) {
1800 return false;
1803 // Steps 1-2.
1804 auto parseResult = ::ParseDateTimeUTCOffset(linear);
1805 if (parseResult.isErr()) {
1806 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1807 parseResult.unwrapErr());
1808 return false;
1811 // Steps 3-21.
1812 *result = ParseDateTimeUTCOffset(parseResult.unwrap());
1813 return true;
1816 template <typename CharT>
1817 mozilla::Result<TemporalDurationString, ParserError>
1818 TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
1819 // Initialize all fields to zero.
1820 TemporalDurationString result = {};
1822 // TemporalDurationString :
1823 // Duration
1825 // Duration :
1826 // Sign? DurationDesignator DurationDate
1827 // Sign? DurationDesignator DurationTime
1829 if (hasSign()) {
1830 result.sign = sign();
1833 if (!durationDesignator()) {
1834 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DESIGNATOR);
1837 // DurationDate :
1838 // DurationYearsPart DurationTime?
1839 // DurationMonthsPart DurationTime?
1840 // DurationWeeksPart DurationTime?
1841 // DurationDaysPart DurationTime?
1843 do {
1844 double num;
1845 if (hasTimeDesignator()) {
1846 break;
1848 if (auto d = digits(cx); !d) {
1849 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
1850 } else {
1851 num = *d;
1854 // DurationYearsPart :
1855 // DurationYears YearsDesignator DurationMonthsPart
1856 // DurationYears YearsDesignator DurationWeeksPart
1857 // DurationYears YearsDesignator DurationDaysPart?
1859 // DurationYears :
1860 // DecimalDigits[~Sep]
1861 if (yearsDesignator()) {
1862 result.years = num;
1863 if (reader_.atEnd()) {
1864 return result;
1866 if (hasTimeDesignator()) {
1867 break;
1869 if (auto d = digits(cx); !d) {
1870 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
1871 } else {
1872 num = *d;
1876 // DurationMonthsPart :
1877 // DurationMonths MonthsDesignator DurationWeeksPart
1878 // DurationMonths MonthsDesignator DurationDaysPart?
1880 // DurationMonths :
1881 // DecimalDigits[~Sep]
1882 if (monthsDesignator()) {
1883 result.months = num;
1884 if (reader_.atEnd()) {
1885 return result;
1887 if (hasTimeDesignator()) {
1888 break;
1890 if (auto d = digits(cx); !d) {
1891 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
1892 } else {
1893 num = *d;
1897 // DurationWeeksPart :
1898 // DurationWeeks WeeksDesignator DurationDaysPart?
1900 // DurationWeeks :
1901 // DecimalDigits[~Sep]
1902 if (weeksDesignator()) {
1903 result.weeks = num;
1904 if (reader_.atEnd()) {
1905 return result;
1907 if (hasTimeDesignator()) {
1908 break;
1910 if (auto d = digits(cx); !d) {
1911 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
1912 } else {
1913 num = *d;
1917 // DurationDaysPart :
1918 // DurationDays DaysDesignator
1920 // DurationDays :
1921 // DecimalDigits[~Sep]
1922 if (daysDesignator()) {
1923 result.days = num;
1924 if (reader_.atEnd()) {
1925 return result;
1927 if (hasTimeDesignator()) {
1928 break;
1932 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
1933 } while (false);
1935 // DurationTime :
1936 // DurationTimeDesignator DurationHoursPart
1937 // DurationTimeDesignator DurationMinutesPart
1938 // DurationTimeDesignator DurationSecondsPart
1939 if (!timeDesignator()) {
1940 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIME_DESIGNATOR);
1943 double num;
1944 mozilla::Maybe<int32_t> frac;
1945 auto digitsAndFraction = [&]() {
1946 auto d = digits(cx);
1947 if (!d) {
1948 return false;
1950 num = *d;
1951 frac = fraction();
1952 return true;
1955 if (!digitsAndFraction()) {
1956 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
1959 // clang-format off
1961 // DurationHoursPart :
1962 // DurationWholeHours DurationHoursFraction HoursDesignator
1963 // DurationWholeHours HoursDesignator DurationMinutesPart
1964 // DurationWholeHours HoursDesignator DurationSecondsPart?
1966 // DurationWholeHours :
1967 // DecimalDigits[~Sep]
1969 // DurationHoursFraction :
1970 // TimeFraction
1972 // TimeFraction :
1973 // Fraction
1975 // clang-format on
1976 bool hasHoursFraction = false;
1977 if (hoursDesignator()) {
1978 hasHoursFraction = bool(frac);
1979 result.hours = num;
1980 result.hoursFraction = frac.valueOr(0);
1981 if (reader_.atEnd()) {
1982 return result;
1984 if (!digitsAndFraction()) {
1985 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
1989 // clang-format off
1991 // DurationMinutesPart :
1992 // DurationWholeMinutes DurationMinutesFraction MinutesDesignator
1993 // DurationWholeMinutes MinutesDesignator DurationSecondsPart?
1995 // DurationWholeMinutes :
1996 // DecimalDigits[~Sep]
1998 // DurationMinutesFraction :
1999 // TimeFraction
2001 // TimeFraction :
2002 // Fraction
2004 // clang-format on
2005 bool hasMinutesFraction = false;
2006 if (minutesDesignator()) {
2007 if (hasHoursFraction) {
2008 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_MINUTES);
2010 hasMinutesFraction = bool(frac);
2011 result.minutes = num;
2012 result.minutesFraction = frac.valueOr(0);
2013 if (reader_.atEnd()) {
2014 return result;
2016 if (!digitsAndFraction()) {
2017 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
2021 // DurationSecondsPart :
2022 // DurationWholeSeconds DurationSecondsFraction? SecondsDesignator
2024 // DurationWholeSeconds :
2025 // DecimalDigits[~Sep]
2027 // DurationSecondsFraction :
2028 // TimeFraction
2030 // TimeFraction :
2031 // Fraction
2032 if (secondsDesignator()) {
2033 if (hasHoursFraction || hasMinutesFraction) {
2034 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_SECONDS);
2036 result.seconds = num;
2037 result.secondsFraction = frac.valueOr(0);
2038 if (reader_.atEnd()) {
2039 return result;
2043 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
2047 * ParseTemporalDurationString ( isoString )
2049 template <typename CharT>
2050 static auto ParseTemporalDurationString(JSContext* cx,
2051 mozilla::Span<const CharT> str) {
2052 TemporalParser<CharT> parser(str);
2053 return parser.parseTemporalDurationString(cx);
2057 * ParseTemporalDurationString ( isoString )
2059 static auto ParseTemporalDurationString(JSContext* cx,
2060 Handle<JSLinearString*> str) {
2061 JS::AutoCheckCannotGC nogc;
2062 if (str->hasLatin1Chars()) {
2063 return ParseTemporalDurationString<Latin1Char>(cx, str->latin1Range(nogc));
2065 return ParseTemporalDurationString<char16_t>(cx, str->twoByteRange(nogc));
2069 * ParseTemporalDurationString ( isoString )
2071 bool js::temporal::ParseTemporalDurationString(JSContext* cx,
2072 Handle<JSString*> str,
2073 Duration* result) {
2074 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
2075 if (!linear) {
2076 return false;
2079 // Steps 1-3.
2080 auto parseResult = ::ParseTemporalDurationString(cx, linear);
2081 if (parseResult.isErr()) {
2082 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2083 parseResult.unwrapErr());
2084 return false;
2086 TemporalDurationString parsed = parseResult.unwrap();
2088 // Steps 4-8.
2089 double years = parsed.years;
2090 double months = parsed.months;
2091 double weeks = parsed.weeks;
2092 double days = parsed.days;
2093 double hours = parsed.hours;
2095 // Steps 9-17.
2096 double minutes, seconds, milliseconds, microseconds, nanoseconds;
2097 if (parsed.hoursFraction) {
2098 MOZ_ASSERT(parsed.hoursFraction > 0);
2099 MOZ_ASSERT(parsed.hoursFraction < 1'000'000'000);
2101 // Step 9.a.
2102 MOZ_ASSERT(parsed.minutes == 0);
2103 MOZ_ASSERT(parsed.minutesFraction == 0);
2104 MOZ_ASSERT(parsed.seconds == 0);
2105 MOZ_ASSERT(parsed.secondsFraction == 0);
2107 // Steps 9.b-d.
2108 int64_t h = int64_t(parsed.hoursFraction) * 60;
2109 minutes = h / 1'000'000'000;
2111 // Steps 13 and 15-17.
2112 int64_t min = (h % 1'000'000'000) * 60;
2113 seconds = min / 1'000'000'000;
2114 milliseconds = (min % 1'000'000'000) / 1'000'000;
2115 microseconds = (min % 1'000'000) / 1'000;
2116 nanoseconds = (min % 1'000);
2119 // Step 11.
2120 else if (parsed.minutesFraction) {
2121 MOZ_ASSERT(parsed.minutesFraction > 0);
2122 MOZ_ASSERT(parsed.minutesFraction < 1'000'000'000);
2124 // Step 11.a.
2125 MOZ_ASSERT(parsed.seconds == 0);
2126 MOZ_ASSERT(parsed.secondsFraction == 0);
2128 // Step 10.
2129 minutes = parsed.minutes;
2131 // Steps 11.b-d and 15-17.
2132 int64_t min = int64_t(parsed.minutesFraction) * 60;
2133 seconds = min / 1'000'000'000;
2134 milliseconds = (min % 1'000'000'000) / 1'000'000;
2135 microseconds = (min % 1'000'000) / 1'000;
2136 nanoseconds = (min % 1'000);
2139 // Step 14.
2140 else if (parsed.secondsFraction) {
2141 MOZ_ASSERT(parsed.secondsFraction > 0);
2142 MOZ_ASSERT(parsed.secondsFraction < 1'000'000'000);
2144 // Step 10.
2145 minutes = parsed.minutes;
2147 // Step 12.
2148 seconds = parsed.seconds;
2150 // Steps 14, 16-17
2151 milliseconds = (parsed.secondsFraction / 1'000'000);
2152 microseconds = ((parsed.secondsFraction % 1'000'000) / 1'000);
2153 nanoseconds = (parsed.secondsFraction % 1'000);
2154 } else {
2155 // Step 10.
2156 minutes = parsed.minutes;
2158 // Step 12.
2159 seconds = parsed.seconds;
2161 // Steps 15-17
2162 milliseconds = 0;
2163 microseconds = 0;
2164 nanoseconds = 0;
2167 // Steps 18-19.
2168 int32_t factor = parsed.sign ? parsed.sign : 1;
2169 MOZ_ASSERT(factor == -1 || factor == 1);
2171 // Steps 20-29.
2172 *result = {
2173 (years * factor) + (+0.0), (months * factor) + (+0.0),
2174 (weeks * factor) + (+0.0), (days * factor) + (+0.0),
2175 (hours * factor) + (+0.0), (minutes * factor) + (+0.0),
2176 (seconds * factor) + (+0.0), (milliseconds * factor) + (+0.0),
2177 (microseconds * factor) + (+0.0), (nanoseconds * factor) + (+0.0),
2180 // Step 30
2181 if (!ThrowIfInvalidDuration(cx, *result)) {
2182 return false;
2185 // Step 31.
2186 return true;
2189 template <typename CharT>
2190 mozilla::Result<AnnotationKey, ParserError>
2191 TemporalParser<CharT>::annotationKey() {
2192 // AnnotationKey :
2193 // AKeyLeadingChar
2194 // AnnotationKey AKeyChar
2196 size_t start = reader_.index();
2198 if (!aKeyLeadingChar()) {
2199 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_KEY);
2202 // Optionally followed by a sequence of |AKeyChar|.
2203 while (aKeyChar()) {
2206 return AnnotationKey{start, reader_.index() - start};
2209 template <typename CharT>
2210 mozilla::Result<AnnotationValue, ParserError>
2211 TemporalParser<CharT>::annotationValue() {
2212 // AnnotationValue :
2213 // AnnotationValueComponent
2214 // AnnotationValueComponent - AnnotationValue
2216 size_t start = reader_.index();
2218 do {
2219 if (!annotationValueComponent()) {
2220 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_VALUE);
2222 } while (character('-'));
2224 return AnnotationValue{start, reader_.index() - start};
2227 template <typename CharT>
2228 mozilla::Result<Annotation, ParserError> TemporalParser<CharT>::annotation() {
2229 // Annotation :
2230 // [ AnnotationCriticalFlag? AnnotationKey = AnnotationValue ]
2232 if (!character('[')) {
2233 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_ANNOTATION);
2236 bool critical = annotationCriticalFlag();
2238 auto key = annotationKey();
2239 if (key.isErr()) {
2240 return key.propagateErr();
2243 if (!character('=')) {
2244 return mozilla::Err(JSMSG_TEMPORAL_PARSER_ASSIGNMENT_IN_ANNOTATION);
2247 auto value = annotationValue();
2248 if (value.isErr()) {
2249 return value.propagateErr();
2252 if (!character(']')) {
2253 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_ANNOTATION);
2256 return Annotation{key.unwrap(), value.unwrap(), critical};
2259 template <typename CharT>
2260 mozilla::Result<CalendarName, ParserError>
2261 TemporalParser<CharT>::annotations() {
2262 // Annotations:
2263 // Annotation Annotations?
2265 MOZ_ASSERT(hasAnnotationStart());
2267 CalendarName calendar;
2268 bool calendarWasCritical = false;
2269 while (hasAnnotationStart()) {
2270 auto anno = annotation();
2271 if (anno.isErr()) {
2272 return anno.propagateErr();
2274 auto [key, value, critical] = anno.unwrap();
2276 // FIXME: spec issue - ignore case for "[u-ca=" to match BCP47?
2277 // https://github.com/tc39/proposal-temporal/issues/2524
2279 static constexpr std::string_view ca = "u-ca";
2281 auto keySpan = reader_.substring(key);
2282 if (keySpan.size() == ca.length() &&
2283 std::equal(ca.begin(), ca.end(), keySpan.data())) {
2284 if (!calendar.present()) {
2285 calendar = value;
2286 calendarWasCritical = critical;
2287 } else if (critical || calendarWasCritical) {
2288 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_CRITICAL_ANNOTATION);
2290 } else if (critical) {
2291 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_CRITICAL_ANNOTATION);
2294 return calendar;
2297 template <typename CharT>
2298 mozilla::Result<ZonedDateTimeString, ParserError>
2299 TemporalParser<CharT>::annotatedTime() {
2300 // clang-format off
2302 // AnnotatedTime :
2303 // TimeDesignator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations?
2304 // TimeSpecWithOptionalOffsetNotAmbiguous TimeZoneAnnotation? Annotations?
2306 // clang-format on
2308 if (timeDesignator()) {
2309 ZonedDateTimeString result = {};
2311 auto time = timeSpec();
2312 if (time.isErr()) {
2313 return time.propagateErr();
2315 result.time = time.unwrap();
2317 if (hasDateTimeUTCOffsetStart()) {
2318 auto tz = dateTimeUTCOffset();
2319 if (tz.isErr()) {
2320 return tz.propagateErr();
2322 result.timeZone = tz.unwrap();
2325 if (hasTimeZoneAnnotationStart()) {
2326 auto annotation = timeZoneAnnotation();
2327 if (annotation.isErr()) {
2328 return annotation.propagateErr();
2330 result.timeZone.annotation = annotation.unwrap();
2333 if (hasAnnotationStart()) {
2334 auto cal = annotations();
2335 if (cal.isErr()) {
2336 return cal.propagateErr();
2338 result.calendar = cal.unwrap();
2341 return result;
2344 // clang-format off
2346 // TimeSpecWithOptionalOffsetNotAmbiguous :
2347 // TimeSpec DateTimeUTCOffset? but not one of ValidMonthDay or DateSpecYearMonth
2349 // clang-format on
2351 size_t start = reader_.index();
2353 ZonedDateTimeString result = {};
2355 auto time = timeSpec();
2356 if (time.isErr()) {
2357 return time.propagateErr();
2359 result.time = time.unwrap();
2361 if (hasDateTimeUTCOffsetStart()) {
2362 auto tz = dateTimeUTCOffset();
2363 if (tz.isErr()) {
2364 return tz.propagateErr();
2366 result.timeZone = tz.unwrap();
2369 size_t end = reader_.index();
2371 // Reset and check if the input can also be parsed as ValidMonthDay.
2372 reader_.reset(start);
2374 if (validMonthDay().isOk()) {
2375 if (reader_.index() == end) {
2376 return mozilla::Err(JSMSG_TEMPORAL_PARSER_AMBIGUOUS_TIME_MONTH_DAY);
2380 // Reset and check if the input can also be parsed as DateSpecYearMonth.
2381 reader_.reset(start);
2383 if (dateSpecYearMonth().isOk()) {
2384 if (reader_.index() == end) {
2385 return mozilla::Err(JSMSG_TEMPORAL_PARSER_AMBIGUOUS_TIME_YEAR_MONTH);
2389 // Input can neither be parsed as ValidMonthDay nor DateSpecYearMonth.
2390 reader_.reset(end);
2392 if (hasTimeZoneAnnotationStart()) {
2393 auto annotation = timeZoneAnnotation();
2394 if (annotation.isErr()) {
2395 return annotation.propagateErr();
2397 result.timeZone.annotation = annotation.unwrap();
2400 if (hasAnnotationStart()) {
2401 auto cal = annotations();
2402 if (cal.isErr()) {
2403 return cal.propagateErr();
2405 result.calendar = cal.unwrap();
2408 return result;
2411 template <typename CharT>
2412 mozilla::Result<ZonedDateTimeString, ParserError>
2413 TemporalParser<CharT>::annotatedDateTime() {
2414 // AnnotatedDateTime[Zoned] :
2415 // [~Zoned] DateTime TimeZoneAnnotation? Annotations?
2416 // [+Zoned] DateTime TimeZoneAnnotation Annotations?
2418 auto dt = dateTime();
2419 if (dt.isErr()) {
2420 return dt.propagateErr();
2422 auto result = dt.unwrap();
2424 if (hasTimeZoneAnnotationStart()) {
2425 auto annotation = timeZoneAnnotation();
2426 if (annotation.isErr()) {
2427 return annotation.propagateErr();
2429 result.timeZone.annotation = annotation.unwrap();
2432 if (hasAnnotationStart()) {
2433 auto cal = annotations();
2434 if (cal.isErr()) {
2435 return cal.propagateErr();
2437 result.calendar = cal.unwrap();
2440 return result;
2443 template <typename CharT>
2444 mozilla::Result<ZonedDateTimeString, ParserError>
2445 TemporalParser<CharT>::annotatedDateTimeTimeRequired() {
2446 // clang-format off
2448 // AnnotatedDateTimeTimeRequired :
2449 // Date DateTimeSeparator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations?
2451 // clang-format on
2453 ZonedDateTimeString result = {};
2455 auto dt = date();
2456 if (dt.isErr()) {
2457 return dt.propagateErr();
2459 result.date = dt.unwrap();
2461 if (!dateTimeSeparator()) {
2462 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR);
2465 auto time = timeSpec();
2466 if (time.isErr()) {
2467 return time.propagateErr();
2469 result.time = time.unwrap();
2471 if (hasDateTimeUTCOffsetStart()) {
2472 auto tz = dateTimeUTCOffset();
2473 if (tz.isErr()) {
2474 return tz.propagateErr();
2476 result.timeZone = tz.unwrap();
2479 if (hasTimeZoneAnnotationStart()) {
2480 auto annotation = timeZoneAnnotation();
2481 if (annotation.isErr()) {
2482 return annotation.propagateErr();
2484 result.timeZone.annotation = annotation.unwrap();
2487 if (hasAnnotationStart()) {
2488 auto cal = annotations();
2489 if (cal.isErr()) {
2490 return cal.propagateErr();
2492 result.calendar = cal.unwrap();
2495 return result;
2498 template <typename CharT>
2499 mozilla::Result<ZonedDateTimeString, ParserError>
2500 TemporalParser<CharT>::annotatedYearMonth() {
2501 // AnnotatedYearMonth :
2502 // DateSpecYearMonth TimeZoneAnnotation? Annotations?
2504 ZonedDateTimeString result = {};
2506 auto yearMonth = dateSpecYearMonth();
2507 if (yearMonth.isErr()) {
2508 return yearMonth.propagateErr();
2510 result.date = yearMonth.unwrap();
2512 if (hasTimeZoneAnnotationStart()) {
2513 auto annotation = timeZoneAnnotation();
2514 if (annotation.isErr()) {
2515 return annotation.propagateErr();
2517 result.timeZone.annotation = annotation.unwrap();
2520 if (hasAnnotationStart()) {
2521 auto cal = annotations();
2522 if (cal.isErr()) {
2523 return cal.propagateErr();
2525 result.calendar = cal.unwrap();
2528 return result;
2531 template <typename CharT>
2532 mozilla::Result<ZonedDateTimeString, ParserError>
2533 TemporalParser<CharT>::annotatedMonthDay() {
2534 // AnnotatedMonthDay :
2535 // DateSpecMonthDay TimeZoneAnnotation? Annotations?
2537 ZonedDateTimeString result = {};
2539 auto monthDay = dateSpecMonthDay();
2540 if (monthDay.isErr()) {
2541 return monthDay.propagateErr();
2543 result.date = monthDay.unwrap();
2545 if (hasTimeZoneAnnotationStart()) {
2546 auto annotation = timeZoneAnnotation();
2547 if (annotation.isErr()) {
2548 return annotation.propagateErr();
2550 result.timeZone.annotation = annotation.unwrap();
2553 if (hasAnnotationStart()) {
2554 auto cal = annotations();
2555 if (cal.isErr()) {
2556 return cal.propagateErr();
2558 result.calendar = cal.unwrap();
2561 return result;
2564 template <typename CharT>
2565 mozilla::Result<PlainDate, ParserError>
2566 TemporalParser<CharT>::dateSpecYearMonth() {
2567 // DateSpecYearMonth :
2568 // DateYear -? DateMonth
2569 PlainDate result = {};
2571 // DateYear :
2572 // DecimalDigit{4}
2573 // Sign DecimalDigit{6}
2574 if (auto year = digits(4)) {
2575 result.year = year.value();
2576 } else if (hasSign()) {
2577 int32_t yearSign = sign();
2578 if (auto year = digits(6)) {
2579 result.year = yearSign * year.value();
2580 if (yearSign < 0 && result.year == 0) {
2581 return mozilla::Err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR);
2583 } else {
2584 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR);
2586 } else {
2587 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR);
2590 character('-');
2592 // DateMonth :
2593 // 0 NonzeroDigit
2594 // 10
2595 // 11
2596 // 12
2597 if (auto month = digits(2)) {
2598 result.month = month.value();
2599 if (!inBounds(result.month, 1, 12)) {
2600 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
2602 } else {
2603 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
2606 // Absent days default to 1, cf. ParseISODateTime.
2607 result.day = 1;
2609 return result;
2612 template <typename CharT>
2613 mozilla::Result<PlainDate, ParserError>
2614 TemporalParser<CharT>::dateSpecMonthDay() {
2615 // DateSpecMonthDay :
2616 // -- DateMonth -? DateDay
2617 // DateMonth -? DateDay
2618 PlainDate result = {};
2620 string("--");
2622 result.year = AbsentYear;
2624 // DateMonth :
2625 // 0 NonzeroDigit
2626 // 10
2627 // 11
2628 // 12
2629 if (auto month = digits(2)) {
2630 result.month = month.value();
2631 if (!inBounds(result.month, 1, 12)) {
2632 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
2634 } else {
2635 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
2638 character('-');
2640 // DateDay :
2641 // 0 NonzeroDigit
2642 // 1 DecimalDigit
2643 // 2 DecimalDigit
2644 // 30
2645 // 31
2646 if (auto day = digits(2)) {
2647 result.day = day.value();
2648 if (!inBounds(result.day, 1, 31)) {
2649 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
2651 } else {
2652 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
2655 return result;
2658 template <typename CharT>
2659 mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::validMonthDay() {
2660 // ValidMonthDay :
2661 // DateMonth -? 0 NonZeroDigit
2662 // DateMonth -? 1 DecimalDigit
2663 // DateMonth -? 2 DecimalDigit
2664 // DateMonth -? 30 but not one of 0230 or 02-30
2665 // DateMonthWithThirtyOneDays -? 31
2667 // DateMonthWithThirtyOneDays : one of
2668 // 01 03 05 07 08 10 12
2670 PlainDate result = {};
2672 // DateMonth :
2673 // 0 NonzeroDigit
2674 // 10
2675 // 11
2676 // 12
2677 if (auto month = digits(2)) {
2678 result.month = month.value();
2679 if (!inBounds(result.month, 1, 12)) {
2680 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
2682 } else {
2683 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
2686 character('-');
2688 if (auto day = digits(2)) {
2689 result.day = day.value();
2690 if (!inBounds(result.day, 1, 31)) {
2691 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
2693 } else {
2694 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
2697 if (result.month == 2 && result.day > 29) {
2698 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
2701 if (result.day > 30) {
2702 MOZ_ASSERT(result.day == 31);
2704 static constexpr int32_t monthsWithThirtyOneDays[] = {
2705 1, 3, 5, 7, 8, 10, 12,
2708 if (std::find(std::begin(monthsWithThirtyOneDays),
2709 std::end(monthsWithThirtyOneDays),
2710 result.month) == std::end(monthsWithThirtyOneDays)) {
2711 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
2715 return result;
2718 template <typename CharT>
2719 mozilla::Result<ZonedDateTimeString, ParserError>
2720 TemporalParser<CharT>::parseTemporalCalendarString() {
2721 // Handle the common case of a standalone calendar name first.
2723 // All valid calendar names start with two alphabetic characters and none of
2724 // the ParseISODateTime parse goals can start with two alphabetic characters.
2725 // TemporalTimeString can start with 'T', so we can't only check the first
2726 // character.
2727 if (hasTwoAsciiAlpha()) {
2728 auto cal = annotationValue();
2729 if (cal.isErr()) {
2730 return cal.propagateErr();
2732 if (!reader_.atEnd()) {
2733 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
2736 ZonedDateTimeString result = {};
2737 result.calendar = cal.unwrap();
2738 return result;
2741 // Try all five parse goals from ParseISODateTime in order.
2743 // TemporalDateTimeString
2744 // TemporalInstantString
2745 // TemporalTimeString
2746 // TemporalZonedDateTimeString
2747 // TemporalMonthDayString
2748 // TemporalYearMonthString
2750 if (auto dt = parseTemporalDateTimeString(); dt.isOk()) {
2751 return dt.unwrap();
2754 // Restart parsing from the start of the string.
2755 reader_.reset();
2757 if (auto dt = parseTemporalInstantString(); dt.isOk()) {
2758 return dt.unwrap();
2761 // Restart parsing from the start of the string.
2762 reader_.reset();
2764 if (auto dt = parseTemporalTimeString(); dt.isOk()) {
2765 return dt.unwrap();
2768 // Restart parsing from the start of the string.
2769 reader_.reset();
2771 if (auto dt = parseTemporalMonthDayString(); dt.isOk()) {
2772 return dt.unwrap();
2775 // Restart parsing from the start of the string.
2776 reader_.reset();
2778 if (auto dt = parseTemporalYearMonthString(); dt.isOk()) {
2779 return dt.unwrap();
2780 } else {
2781 return dt.propagateErr();
2786 * ParseTemporalCalendarString ( isoString )
2788 template <typename CharT>
2789 static auto ParseTemporalCalendarString(mozilla::Span<const CharT> str) {
2790 TemporalParser<CharT> parser(str);
2791 return parser.parseTemporalCalendarString();
2795 * ParseTemporalCalendarString ( isoString )
2797 static auto ParseTemporalCalendarString(Handle<JSLinearString*> str) {
2798 JS::AutoCheckCannotGC nogc;
2799 if (str->hasLatin1Chars()) {
2800 return ParseTemporalCalendarString<Latin1Char>(str->latin1Range(nogc));
2802 return ParseTemporalCalendarString<char16_t>(str->twoByteRange(nogc));
2806 * ParseTemporalCalendarString ( isoString )
2808 JSLinearString* js::temporal::ParseTemporalCalendarString(
2809 JSContext* cx, Handle<JSString*> str) {
2810 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
2811 if (!linear) {
2812 return nullptr;
2815 // Steps 1-3.
2816 auto parseResult = ::ParseTemporalCalendarString(linear);
2817 if (parseResult.isErr()) {
2818 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2819 parseResult.unwrapErr());
2820 return nullptr;
2822 ZonedDateTimeString parsed = parseResult.unwrap();
2824 PlainDateTime unused;
2825 if (!ParseISODateTime(cx, parsed, &unused)) {
2826 return nullptr;
2829 // Step 2.b.
2830 if (!parsed.calendar.present()) {
2831 return cx->names().iso8601;
2834 // Steps 2.c and 3.c
2835 return ToString(cx, linear, parsed.calendar);
2838 template <typename CharT>
2839 mozilla::Result<ZonedDateTimeString, ParserError>
2840 TemporalParser<CharT>::parseTemporalTimeString() {
2841 // TemporalTimeString :
2842 // AnnotatedTime
2843 // AnnotatedDateTimeTimeRequired
2845 if (auto time = annotatedTime(); time.isOk() && reader_.atEnd()) {
2846 return time.unwrap();
2849 // Reset and try the next option.
2850 reader_.reset();
2852 auto dt = annotatedDateTimeTimeRequired();
2853 if (dt.isErr()) {
2854 return dt.propagateErr();
2856 if (!reader_.atEnd()) {
2857 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
2859 return dt.unwrap();
2863 * ParseTemporalTimeString ( isoString )
2865 template <typename CharT>
2866 static auto ParseTemporalTimeString(mozilla::Span<const CharT> str) {
2867 TemporalParser<CharT> parser(str);
2868 return parser.parseTemporalTimeString();
2872 * ParseTemporalTimeString ( isoString )
2874 static auto ParseTemporalTimeString(Handle<JSLinearString*> str) {
2875 JS::AutoCheckCannotGC nogc;
2876 if (str->hasLatin1Chars()) {
2877 return ParseTemporalTimeString<Latin1Char>(str->latin1Range(nogc));
2879 return ParseTemporalTimeString<char16_t>(str->twoByteRange(nogc));
2883 * ParseTemporalTimeString ( isoString )
2885 bool js::temporal::ParseTemporalTimeString(JSContext* cx, Handle<JSString*> str,
2886 PlainTime* result) {
2887 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
2888 if (!linear) {
2889 return false;
2892 // Steps 1-2.
2893 auto parseResult = ::ParseTemporalTimeString(linear);
2894 if (parseResult.isErr()) {
2895 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2896 parseResult.unwrapErr());
2897 return false;
2899 ZonedDateTimeString parsed = parseResult.unwrap();
2901 // Step 3.
2902 if (parsed.timeZone.isUTC()) {
2903 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2904 JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
2905 return false;
2908 // Step 4.
2909 PlainDateTime dateTime;
2910 if (!ParseISODateTime(cx, parsed, &dateTime)) {
2911 return false;
2913 *result = dateTime.time;
2915 // Step 5.
2916 return true;
2919 template <typename CharT>
2920 mozilla::Result<ZonedDateTimeString, ParserError>
2921 TemporalParser<CharT>::parseTemporalMonthDayString() {
2922 // TemporalMonthDayString :
2923 // AnnotatedMonthDay
2924 // AnnotatedDateTime[~Zoned]
2926 if (auto monthDay = annotatedMonthDay(); monthDay.isOk() && reader_.atEnd()) {
2927 auto result = monthDay.unwrap();
2929 // ParseISODateTime, step 3.
2930 if (result.calendar.present() &&
2931 !IsISO8601Calendar(reader_.substring(result.calendar))) {
2932 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MONTH_DAY_CALENDAR_NOT_ISO8601);
2934 return result;
2937 // Reset and try the next option.
2938 reader_.reset();
2940 auto dt = annotatedDateTime();
2941 if (dt.isErr()) {
2942 return dt.propagateErr();
2944 if (!reader_.atEnd()) {
2945 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
2947 return dt.unwrap();
2951 * ParseTemporalMonthDayString ( isoString )
2953 template <typename CharT>
2954 static auto ParseTemporalMonthDayString(mozilla::Span<const CharT> str) {
2955 TemporalParser<CharT> parser(str);
2956 return parser.parseTemporalMonthDayString();
2960 * ParseTemporalMonthDayString ( isoString )
2962 static auto ParseTemporalMonthDayString(Handle<JSLinearString*> str) {
2963 JS::AutoCheckCannotGC nogc;
2964 if (str->hasLatin1Chars()) {
2965 return ParseTemporalMonthDayString<Latin1Char>(str->latin1Range(nogc));
2967 return ParseTemporalMonthDayString<char16_t>(str->twoByteRange(nogc));
2971 * ParseTemporalMonthDayString ( isoString )
2973 bool js::temporal::ParseTemporalMonthDayString(
2974 JSContext* cx, Handle<JSString*> str, PlainDate* result, bool* hasYear,
2975 MutableHandle<JSString*> calendar) {
2976 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
2977 if (!linear) {
2978 return false;
2981 // Steps 1-2 .
2982 auto parseResult = ::ParseTemporalMonthDayString(linear);
2983 if (parseResult.isErr()) {
2984 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2985 parseResult.unwrapErr());
2986 return false;
2988 ZonedDateTimeString parsed = parseResult.unwrap();
2990 // Step 3.
2991 if (parsed.timeZone.isUTC()) {
2992 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2993 JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
2994 return false;
2997 // Step 4.
2998 PlainDateTime dateTime;
2999 if (!ParseISODateTime(cx, parsed, &dateTime)) {
3000 return false;
3002 *result = dateTime.date;
3004 // Steps 5-6.
3005 *hasYear = parsed.date.year != AbsentYear;
3007 if (parsed.calendar.present()) {
3008 calendar.set(ToString(cx, linear, parsed.calendar));
3009 if (!calendar) {
3010 return false;
3014 // Step 7.
3015 return true;
3018 template <typename CharT>
3019 mozilla::Result<ZonedDateTimeString, ParserError>
3020 TemporalParser<CharT>::parseTemporalYearMonthString() {
3021 // TemporalYearMonthString :
3022 // AnnotatedYearMonth
3023 // AnnotatedDateTime[~Zoned]
3025 if (auto yearMonth = annotatedYearMonth();
3026 yearMonth.isOk() && reader_.atEnd()) {
3027 auto result = yearMonth.unwrap();
3029 // ParseISODateTime, step 3.
3030 if (result.calendar.present() &&
3031 !IsISO8601Calendar(reader_.substring(result.calendar))) {
3032 return mozilla::Err(
3033 JSMSG_TEMPORAL_PARSER_YEAR_MONTH_CALENDAR_NOT_ISO8601);
3035 return result;
3038 // Reset and try the next option.
3039 reader_.reset();
3041 auto dt = annotatedDateTime();
3042 if (dt.isErr()) {
3043 return dt.propagateErr();
3045 if (!reader_.atEnd()) {
3046 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
3048 return dt.unwrap();
3052 * ParseTemporalYearMonthString ( isoString )
3054 template <typename CharT>
3055 static auto ParseTemporalYearMonthString(mozilla::Span<const CharT> str) {
3056 TemporalParser<CharT> parser(str);
3057 return parser.parseTemporalYearMonthString();
3061 * ParseTemporalYearMonthString ( isoString )
3063 static auto ParseTemporalYearMonthString(Handle<JSLinearString*> str) {
3064 JS::AutoCheckCannotGC nogc;
3065 if (str->hasLatin1Chars()) {
3066 return ParseTemporalYearMonthString<Latin1Char>(str->latin1Range(nogc));
3068 return ParseTemporalYearMonthString<char16_t>(str->twoByteRange(nogc));
3072 * ParseTemporalYearMonthString ( isoString )
3074 bool js::temporal::ParseTemporalYearMonthString(
3075 JSContext* cx, Handle<JSString*> str, PlainDate* result,
3076 MutableHandle<JSString*> calendar) {
3077 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
3078 if (!linear) {
3079 return false;
3082 // Steps 1-2.
3083 auto parseResult = ::ParseTemporalYearMonthString(linear);
3084 if (parseResult.isErr()) {
3085 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3086 parseResult.unwrapErr());
3087 return false;
3089 ZonedDateTimeString parsed = parseResult.unwrap();
3091 // Step 3.
3092 if (parsed.timeZone.isUTC()) {
3093 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3094 JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
3095 return false;
3098 // Step 4.
3099 PlainDateTime dateTime;
3100 if (!ParseISODateTime(cx, parsed, &dateTime)) {
3101 return false;
3103 *result = dateTime.date;
3105 if (parsed.calendar.present()) {
3106 calendar.set(ToString(cx, linear, parsed.calendar));
3107 if (!calendar) {
3108 return false;
3112 // Step 5.
3113 return true;
3116 template <typename CharT>
3117 mozilla::Result<ZonedDateTimeString, ParserError>
3118 TemporalParser<CharT>::parseTemporalDateTimeString() {
3119 // TemporalDateTimeString[Zoned] :
3120 // AnnotatedDateTime[?Zoned]
3122 auto dateTime = annotatedDateTime();
3123 if (dateTime.isErr()) {
3124 return dateTime.propagateErr();
3126 if (!reader_.atEnd()) {
3127 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
3129 return dateTime.unwrap();
3133 * ParseTemporalDateTimeString ( isoString )
3135 template <typename CharT>
3136 static auto ParseTemporalDateTimeString(mozilla::Span<const CharT> str) {
3137 TemporalParser<CharT> parser(str);
3138 return parser.parseTemporalDateTimeString();
3142 * ParseTemporalDateTimeString ( isoString )
3144 static auto ParseTemporalDateTimeString(Handle<JSLinearString*> str) {
3145 JS::AutoCheckCannotGC nogc;
3146 if (str->hasLatin1Chars()) {
3147 return ParseTemporalDateTimeString<Latin1Char>(str->latin1Range(nogc));
3149 return ParseTemporalDateTimeString<char16_t>(str->twoByteRange(nogc));
3153 * ParseTemporalDateTimeString ( isoString )
3155 bool js::temporal::ParseTemporalDateTimeString(
3156 JSContext* cx, Handle<JSString*> str, PlainDateTime* result,
3157 MutableHandle<JSString*> calendar) {
3158 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
3159 if (!linear) {
3160 return false;
3163 // Steps 1-2.
3164 auto parseResult = ::ParseTemporalDateTimeString(linear);
3165 if (parseResult.isErr()) {
3166 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3167 parseResult.unwrapErr());
3168 return false;
3170 ZonedDateTimeString parsed = parseResult.unwrap();
3172 // Step 3.
3173 if (parsed.timeZone.isUTC()) {
3174 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3175 JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
3176 return false;
3179 // Step 4.
3180 if (!ParseISODateTime(cx, parsed, result)) {
3181 return false;
3184 if (parsed.calendar.present()) {
3185 calendar.set(ToString(cx, linear, parsed.calendar));
3186 if (!calendar) {
3187 return false;
3191 return true;
3195 * ParseTemporalDateString ( isoString )
3197 bool js::temporal::ParseTemporalDateString(JSContext* cx, Handle<JSString*> str,
3198 PlainDate* result,
3199 MutableHandle<JSString*> calendar) {
3200 // Step 1.
3201 PlainDateTime dateTime;
3202 if (!ParseTemporalDateTimeString(cx, str, &dateTime, calendar)) {
3203 return false;
3206 // Step 2.
3207 *result = dateTime.date;
3208 return true;
3211 template <typename CharT>
3212 mozilla::Result<ZonedDateTimeString, ParserError>
3213 TemporalParser<CharT>::parseTemporalZonedDateTimeString() {
3214 // Parse goal: TemporalDateTimeString[+Zoned]
3216 // TemporalDateTimeString[Zoned] :
3217 // AnnotatedDateTime[?Zoned]
3219 // AnnotatedDateTime[Zoned] :
3220 // [~Zoned] DateTime TimeZoneAnnotation? Annotations?
3221 // [+Zoned] DateTime TimeZoneAnnotation Annotations?
3223 auto dt = dateTime();
3224 if (dt.isErr()) {
3225 return dt.propagateErr();
3227 auto result = dt.unwrap();
3229 auto annotation = timeZoneAnnotation();
3230 if (annotation.isErr()) {
3231 return annotation.propagateErr();
3233 result.timeZone.annotation = annotation.unwrap();
3235 if (hasAnnotationStart()) {
3236 auto cal = annotations();
3237 if (cal.isErr()) {
3238 return cal.propagateErr();
3240 result.calendar = cal.unwrap();
3243 if (!reader_.atEnd()) {
3244 return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
3247 return result;
3251 * ParseTemporalZonedDateTimeString ( isoString )
3253 template <typename CharT>
3254 static auto ParseTemporalZonedDateTimeString(mozilla::Span<const CharT> str) {
3255 TemporalParser<CharT> parser(str);
3256 return parser.parseTemporalZonedDateTimeString();
3260 * ParseTemporalZonedDateTimeString ( isoString )
3262 static auto ParseTemporalZonedDateTimeString(Handle<JSLinearString*> str) {
3263 JS::AutoCheckCannotGC nogc;
3264 if (str->hasLatin1Chars()) {
3265 return ParseTemporalZonedDateTimeString<Latin1Char>(str->latin1Range(nogc));
3267 return ParseTemporalZonedDateTimeString<char16_t>(str->twoByteRange(nogc));
3271 * ParseTemporalZonedDateTimeString ( isoString )
3273 bool js::temporal::ParseTemporalZonedDateTimeString(
3274 JSContext* cx, Handle<JSString*> str, PlainDateTime* dateTime, bool* isUTC,
3275 bool* hasOffset, int64_t* timeZoneOffset,
3276 MutableHandle<ParsedTimeZone> timeZoneName,
3277 MutableHandle<JSString*> calendar) {
3278 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
3279 if (!linear) {
3280 return false;
3283 // Step 1.
3284 auto parseResult = ::ParseTemporalZonedDateTimeString(linear);
3285 if (parseResult.isErr()) {
3286 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3287 parseResult.unwrapErr());
3288 return false;
3290 ZonedDateTimeString parsed = parseResult.unwrap();
3292 // Step 2. (ParseISODateTime, steps 1-18.)
3293 if (!ParseISODateTime(cx, parsed, dateTime)) {
3294 return false;
3297 // Step 2. (ParseISODateTime, steps 19-21.)
3299 MOZ_ASSERT(parsed.timeZone.hasAnnotation());
3301 // Case 1: 19700101T00:00Z[+02:00]
3302 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
3304 // Case 2: 19700101T00:00+02:00[+02:00]
3305 // { [[Z]]: false, [[OffsetString]]: "+02:00", [[Name]]: "+02:00" }
3307 // Case 3: 19700101[+02:00]
3308 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
3310 // Case 4: 19700101T00:00Z[Europe/Berlin]
3311 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
3313 // Case 5: 19700101T00:00+01:00[Europe/Berlin]
3314 // { [[Z]]: false, [[OffsetString]]: "+01:00", [[Name]]: "Europe/Berlin" }
3316 // Case 6: 19700101[Europe/Berlin]
3317 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
3319 const auto& annotation = parsed.timeZone.annotation;
3320 if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneName)) {
3321 return false;
3324 if (parsed.timeZone.isUTC()) {
3325 *isUTC = true;
3326 *hasOffset = false;
3327 *timeZoneOffset = 0;
3328 } else if (parsed.timeZone.hasOffset()) {
3329 *isUTC = false;
3330 *hasOffset = true;
3331 *timeZoneOffset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
3332 } else {
3333 *isUTC = false;
3334 *hasOffset = false;
3335 *timeZoneOffset = 0;
3339 // Step 2. (ParseISODateTime, steps 23-24.)
3340 if (parsed.calendar.present()) {
3341 calendar.set(ToString(cx, linear, parsed.calendar));
3342 if (!calendar) {
3343 return false;
3347 // Step 2. (ParseISODateTime, step 25.)
3348 return true;
3352 * ParseTemporalRelativeToString ( isoString )
3354 template <typename CharT>
3355 static auto ParseTemporalRelativeToString(mozilla::Span<const CharT> str) {
3356 TemporalParser<CharT> parser(str);
3357 return parser.parseTemporalDateTimeString();
3361 * ParseTemporalRelativeToString ( isoString )
3363 static auto ParseTemporalRelativeToString(Handle<JSLinearString*> str) {
3364 JS::AutoCheckCannotGC nogc;
3365 if (str->hasLatin1Chars()) {
3366 return ParseTemporalRelativeToString<Latin1Char>(str->latin1Range(nogc));
3368 return ParseTemporalRelativeToString<char16_t>(str->twoByteRange(nogc));
3372 * ParseTemporalRelativeToString ( isoString )
3374 bool js::temporal::ParseTemporalRelativeToString(
3375 JSContext* cx, Handle<JSString*> str, PlainDateTime* dateTime, bool* isUTC,
3376 bool* hasOffset, int64_t* timeZoneOffset,
3377 MutableHandle<ParsedTimeZone> timeZoneName,
3378 MutableHandle<JSString*> calendar) {
3379 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
3380 if (!linear) {
3381 return false;
3384 // Steps 1-2.
3385 auto parseResult = ::ParseTemporalRelativeToString(linear);
3386 if (parseResult.isErr()) {
3387 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3388 parseResult.unwrapErr());
3389 return false;
3391 ZonedDateTimeString parsed = parseResult.unwrap();
3393 // Step 3.
3394 if (parsed.timeZone.isUTC() && !parsed.timeZone.hasAnnotation()) {
3395 JS_ReportErrorNumberASCII(
3396 cx, GetErrorMessage, nullptr,
3397 JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR_WITHOUT_NAME);
3398 return false;
3401 // Step 4. (ParseISODateTime, steps 1-18.)
3402 if (!ParseISODateTime(cx, parsed, dateTime)) {
3403 return false;
3406 // Step 4. (ParseISODateTime, steps 19-22.)
3407 if (parsed.timeZone.hasAnnotation()) {
3408 // Case 1: 19700101Z[+02:00]
3409 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
3411 // Case 2: 19700101+00:00[+02:00]
3412 // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" }
3414 // Case 3: 19700101[+02:00]
3415 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
3417 // Case 4: 19700101Z[Europe/Berlin]
3418 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
3420 // Case 5: 19700101+00:00[Europe/Berlin]
3421 // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" }
3423 // Case 6: 19700101[Europe/Berlin]
3424 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
3426 const auto& annotation = parsed.timeZone.annotation;
3427 if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneName)) {
3428 return false;
3431 if (parsed.timeZone.isUTC()) {
3432 *isUTC = true;
3433 *hasOffset = false;
3434 *timeZoneOffset = 0;
3435 } else if (parsed.timeZone.hasOffset()) {
3436 *isUTC = false;
3437 *hasOffset = true;
3438 *timeZoneOffset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
3439 } else {
3440 *isUTC = false;
3441 *hasOffset = false;
3442 *timeZoneOffset = 0;
3444 } else {
3445 // ToRelativeTemporalObject ignores any other time zone information when no
3446 // bracketed time zone annotation is present.
3448 *isUTC = false;
3449 *hasOffset = false;
3450 *timeZoneOffset = 0;
3451 timeZoneName.set(ParsedTimeZone{});
3454 // Step 4. (ParseISODateTime, steps 23-24.)
3455 if (parsed.calendar.present()) {
3456 calendar.set(ToString(cx, linear, parsed.calendar));
3457 if (!calendar) {
3458 return false;
3462 // Step 4. (Return)
3463 return true;
3466 void js::temporal::ParsedTimeZone::trace(JSTracer* trc) {
3467 TraceNullableRoot(trc, &name, "ParsedTimeZone::name");