TemporalParser.cpp (87061B)
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/. */ 6 7 #include "builtin/temporal/TemporalParser.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/Attributes.h" 11 #include "mozilla/Maybe.h" 12 #include "mozilla/Result.h" 13 #include "mozilla/Span.h" 14 #include "mozilla/TextUtils.h" 15 #include "mozilla/Try.h" 16 17 #include <algorithm> 18 #include <cstdlib> 19 #include <initializer_list> 20 #include <limits> 21 #include <stdint.h> 22 #include <string_view> 23 #include <type_traits> 24 #include <utility> 25 26 #include "jsnum.h" 27 #include "NamespaceImports.h" 28 29 #include "builtin/temporal/Calendar.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" 46 47 using namespace js; 48 using namespace js::temporal; 49 50 struct StringName final { 51 // Start position and length of this name. 52 size_t start = 0; 53 size_t length = 0; 54 55 bool present() const { return length > 0; } 56 }; 57 58 static JSLinearString* ToString(JSContext* cx, JSString* string, 59 const StringName& name) { 60 MOZ_ASSERT(name.present()); 61 return NewDependentString(cx, string, name.start, name.length); 62 } 63 64 template <typename CharT> 65 bool EqualCharIgnoreCaseAscii(CharT c1, char c2) { 66 if constexpr (sizeof(CharT) > sizeof(char)) { 67 if (!mozilla::IsAscii(c1)) { 68 return false; 69 } 70 } 71 72 static constexpr auto toLower = 0x20; 73 static_assert('a' - 'A' == toLower); 74 75 // Convert both characters to lower case before the comparison. 76 char c = c1; 77 if (mozilla::IsAsciiUppercaseAlpha(c1)) { 78 c = char(c + toLower); 79 } 80 char d = c2; 81 if (mozilla::IsAsciiUppercaseAlpha(c2)) { 82 d = char(d + toLower); 83 } 84 return c == d; 85 } 86 87 using CalendarName = StringName; 88 using AnnotationKey = StringName; 89 using AnnotationValue = StringName; 90 using TimeZoneName = StringName; 91 92 struct Annotation final { 93 AnnotationKey key; 94 AnnotationValue value; 95 bool critical = false; 96 }; 97 98 struct TimeSpec final { 99 Time time; 100 }; 101 102 struct TimeZoneUTCOffset final { 103 // ±1 for time zones with an offset, otherwise 0. 104 int32_t sign = 0; 105 106 // An integer in the range [0, 23]. 107 int32_t hour = 0; 108 109 // An integer in the range [0, 59]. 110 int32_t minute = 0; 111 }; 112 113 struct DateTimeUTCOffset final { 114 // ±1 for time zones with an offset, otherwise 0. 115 int32_t sign = 0; 116 117 // An integer in the range [0, 23]. 118 int32_t hour = 0; 119 120 // An integer in the range [0, 59]. 121 int32_t minute = 0; 122 123 // An integer in the range [0, 59]. 124 int32_t second = 0; 125 126 // An integer in the range [0, 999'999]. 127 int32_t fractionalPart = 0; 128 129 // Time zone with sub-minute precision. 130 bool subMinutePrecision = false; 131 132 // Convert to a TimeZoneUTCOffset. 133 TimeZoneUTCOffset toTimeZoneUTCOffset() const { 134 MOZ_ASSERT(!subMinutePrecision, "unexpected sub-minute precision"); 135 return {sign, hour, minute}; 136 } 137 }; 138 139 /** 140 * ParseDateTimeUTCOffset ( offsetString ) 141 */ 142 static auto ParseDateTimeUTCOffset(const DateTimeUTCOffset& offset) { 143 constexpr int64_t nanoPerSec = 1'000'000'000; 144 145 MOZ_ASSERT(offset.sign == -1 || offset.sign == +1); 146 MOZ_ASSERT(0 <= offset.hour && offset.hour < 24); 147 MOZ_ASSERT(0 <= offset.minute && offset.minute < 60); 148 MOZ_ASSERT(0 <= offset.second && offset.second < 60); 149 MOZ_ASSERT(0 <= offset.fractionalPart && offset.fractionalPart < nanoPerSec); 150 151 // sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds). 152 int64_t seconds = (offset.hour * 60 + offset.minute) * 60 + offset.second; 153 int64_t nanos = (seconds * nanoPerSec) + offset.fractionalPart; 154 int64_t result = offset.sign * nanos; 155 156 MOZ_ASSERT(std::abs(result) < ToNanoseconds(TemporalUnit::Day), 157 "time zone offset is less than 24:00 hours"); 158 159 return OffsetTimeZone{result, offset.subMinutePrecision}; 160 } 161 162 static int32_t ParseTimeZoneOffset(const TimeZoneUTCOffset& offset) { 163 MOZ_ASSERT(offset.sign == -1 || offset.sign == +1); 164 MOZ_ASSERT(0 <= offset.hour && offset.hour < 24); 165 MOZ_ASSERT(0 <= offset.minute && offset.minute < 60); 166 167 // sign × (hour × 60 + minute). 168 int32_t result = offset.sign * (offset.hour * 60 + offset.minute); 169 170 MOZ_ASSERT(std::abs(result) < UnitsPerDay(TemporalUnit::Minute), 171 "time zone offset is less than 24:00 hours"); 172 173 return result; 174 } 175 176 /** 177 * Struct to hold time zone annotations. 178 */ 179 struct TimeZoneAnnotation final { 180 // Time zone offset. 181 TimeZoneUTCOffset offset; 182 183 // Time zone name. 184 TimeZoneName name; 185 186 /** 187 * Returns true iff the time zone has an offset part, e.g. "+01:00". 188 */ 189 bool hasOffset() const { return offset.sign != 0; } 190 191 /** 192 * Returns true iff the time zone has an IANA name, e.g. "Asia/Tokyo". 193 */ 194 bool hasName() const { return name.present(); } 195 }; 196 197 /** 198 * Struct to hold any time zone parts of a parsed string. 199 */ 200 struct TimeZoneString final { 201 // Date-time UTC offset. 202 DateTimeUTCOffset offset; 203 204 // Time zone annotation; 205 TimeZoneAnnotation annotation; 206 207 // UTC time zone. 208 bool utc = false; 209 210 static auto from(DateTimeUTCOffset offset) { 211 TimeZoneString timeZone{}; 212 timeZone.offset = offset; 213 return timeZone; 214 } 215 216 static auto from(TimeZoneUTCOffset offset) { 217 TimeZoneString timeZone{}; 218 timeZone.annotation.offset = offset; 219 return timeZone; 220 } 221 222 static auto from(TimeZoneName name) { 223 TimeZoneString timeZone{}; 224 timeZone.annotation.name = name; 225 return timeZone; 226 } 227 228 static auto UTC() { 229 TimeZoneString timeZone{}; 230 timeZone.utc = true; 231 return timeZone; 232 } 233 234 /** 235 * Returns true iff the time zone has an offset part, e.g. "+01:00". 236 */ 237 bool hasOffset() const { return offset.sign != 0; } 238 239 /** 240 * Returns true iff the time zone has an annotation. 241 */ 242 bool hasAnnotation() const { 243 return annotation.hasName() || annotation.hasOffset(); 244 } 245 246 /** 247 * Returns true iff the time zone uses the "Z" abbrevation to denote UTC time. 248 */ 249 bool isUTC() const { return utc; } 250 }; 251 252 /** 253 * Struct to hold the parsed date, time, time zone, and calendar components. 254 */ 255 struct ZonedDateTimeString final { 256 ISODate date; 257 Time time; 258 TimeZoneString timeZone; 259 CalendarName calendar; 260 bool startOfDay; 261 }; 262 263 template <typename CharT> 264 static bool IsISO8601Calendar(mozilla::Span<const CharT> calendar) { 265 static constexpr std::string_view iso8601 = "iso8601"; 266 267 if (calendar.size() != iso8601.length()) { 268 return false; 269 } 270 271 for (size_t i = 0; i < iso8601.length(); i++) { 272 if (!EqualCharIgnoreCaseAscii(calendar[i], iso8601[i])) { 273 return false; 274 } 275 } 276 return true; 277 } 278 279 static constexpr int32_t AbsentYear = INT32_MAX; 280 281 /** 282 * ParseISODateTime ( isoString ) 283 */ 284 static bool ParseISODateTime(JSContext* cx, const ZonedDateTimeString& parsed, 285 ISODateTime* result) { 286 // Steps 1-7, 9, 11-16 (Not applicable here). 287 288 ISODateTime dateTime = {parsed.date, parsed.time}; 289 290 // NOTE: ToIntegerOrInfinity("") is 0. 291 if (dateTime.date.year == AbsentYear) { 292 dateTime.date.year = 0; 293 } 294 295 // Step 8. 296 if (dateTime.date.month == 0) { 297 dateTime.date.month = 1; 298 } 299 300 // Step 10. 301 if (dateTime.date.day == 0) { 302 dateTime.date.day = 1; 303 } 304 305 // Step 17.b. 306 if (dateTime.time.second == 60) { 307 dateTime.time.second = 59; 308 } 309 310 // ParseISODateTime, steps 18-19 (Not applicable in our implementation). 311 312 // Perform early error checks now that absent |day| and |month| values were 313 // handled: 314 // `IsValidDate(DateSpec)` and `IsValidMonthDay(DateSpecMonthDay)` validate 315 // that |day| doesn't exceed the number of days in |month|. This check can be 316 // implemented by calling `ThrowIfInvalidISODate`. 317 // 318 // All other values are already in-bounds. 319 MOZ_ASSERT(std::abs(dateTime.date.year) <= 999'999); 320 MOZ_ASSERT(1 <= dateTime.date.month && dateTime.date.month <= 12); 321 MOZ_ASSERT(1 <= dateTime.date.day && dateTime.date.day <= 31); 322 323 if (!ThrowIfInvalidISODate(cx, dateTime.date)) { 324 return false; 325 } 326 327 // Step 20. 328 MOZ_ASSERT(IsValidISODate(dateTime.date)); 329 330 // Step 21. 331 MOZ_ASSERT(IsValidTime(dateTime.time)); 332 333 // Steps 22-28. (Handled in caller.) 334 335 *result = dateTime; 336 return true; 337 } 338 339 static bool ParseTimeZoneAnnotation(JSContext* cx, 340 const TimeZoneAnnotation& annotation, 341 JSLinearString* linear, 342 MutableHandle<ParsedTimeZone> result) { 343 MOZ_ASSERT(annotation.hasOffset() || annotation.hasName()); 344 345 if (annotation.hasOffset()) { 346 int32_t offset = ParseTimeZoneOffset(annotation.offset); 347 result.set(ParsedTimeZone::fromOffset(offset)); 348 return true; 349 } 350 351 auto* str = ToString(cx, linear, annotation.name); 352 if (!str) { 353 return false; 354 } 355 result.set(ParsedTimeZone::fromName(str)); 356 return true; 357 } 358 359 /** 360 * Struct for the parsed duration components. 361 */ 362 struct TemporalDurationString final { 363 // A non-negative integer or +Infinity. 364 double years = 0; 365 366 // A non-negative integer or +Infinity. 367 double months = 0; 368 369 // A non-negative integer or +Infinity. 370 double weeks = 0; 371 372 // A non-negative integer or +Infinity. 373 double days = 0; 374 375 // A non-negative integer or +Infinity. 376 double hours = 0; 377 378 // A non-negative integer or +Infinity. 379 double minutes = 0; 380 381 // A non-negative integer or +Infinity. 382 double seconds = 0; 383 384 // An integer in the range [0, 999'999]. 385 int32_t hoursFraction = 0; 386 387 // An integer in the range [0, 999'999]. 388 int32_t minutesFraction = 0; 389 390 // An integer in the range [0, 999'999]. 391 int32_t secondsFraction = 0; 392 393 // ±1 when an offset is present, otherwise 0. 394 int32_t sign = 0; 395 }; 396 397 class ParserError final { 398 JSErrNum error_ = JSMSG_NOT_AN_ERROR; 399 400 public: 401 constexpr ParserError() = default; 402 403 constexpr MOZ_IMPLICIT ParserError(JSErrNum error) : error_(error) {} 404 405 constexpr JSErrNum error() const { return error_; } 406 407 constexpr operator JSErrNum() const { return error(); } 408 }; 409 410 namespace mozilla::detail { 411 // Zero is used for tagging, so it mustn't be an error. 412 static_assert(static_cast<JSErrNum>(0) == JSMSG_NOT_AN_ERROR); 413 414 // Ensure efficient packing of the error type. 415 template <> 416 struct UnusedZero<::ParserError> { 417 private: 418 using Error = ::ParserError; 419 using ErrorKind = JSErrNum; 420 421 public: 422 using StorageType = std::underlying_type_t<ErrorKind>; 423 424 static constexpr bool value = true; 425 static constexpr StorageType nullValue = 0; 426 427 static constexpr Error Inspect(const StorageType& aValue) { 428 return Error(static_cast<ErrorKind>(aValue)); 429 } 430 static constexpr Error Unwrap(StorageType aValue) { 431 return Error(static_cast<ErrorKind>(aValue)); 432 } 433 static constexpr StorageType Store(Error aValue) { 434 return static_cast<StorageType>(aValue.error()); 435 } 436 }; 437 } // namespace mozilla::detail 438 439 static_assert(mozilla::Result<ZonedDateTimeString, ParserError>::Strategy != 440 mozilla::detail::PackingStrategy::Variant); 441 442 /** 443 * Track the error and reader index of the last largest successful parse. 444 */ 445 class LikelyError final { 446 size_t index_ = 0; 447 ParserError error_{}; 448 449 public: 450 template <typename V> 451 void update(const mozilla::Result<V, ParserError>& result, size_t index) { 452 MOZ_ASSERT(result.isErr()); 453 454 if (index >= index_) { 455 index_ = index; 456 error_ = result.inspectErr(); 457 } 458 } 459 460 size_t index() const { return index_; } 461 462 auto propagate() const { return mozilla::Err(error_); } 463 }; 464 465 template <typename CharT> 466 class StringReader final { 467 mozilla::Span<const CharT> string_; 468 469 // Current position in the string. 470 size_t index_ = 0; 471 472 public: 473 explicit StringReader(mozilla::Span<const CharT> string) : string_(string) {} 474 475 /** 476 * Returns the input string. 477 */ 478 mozilla::Span<const CharT> string() const { return string_; } 479 480 /** 481 * Returns a substring of the input string. 482 */ 483 mozilla::Span<const CharT> substring(const StringName& name) const { 484 MOZ_ASSERT(name.present()); 485 return string_.Subspan(name.start, name.length); 486 } 487 488 /** 489 * Returns the current parse position. 490 */ 491 size_t index() const { return index_; } 492 493 /** 494 * Returns the length of the input string- 495 */ 496 size_t length() const { return string_.size(); } 497 498 /** 499 * Returns true iff the whole string has been parsed. 500 */ 501 bool atEnd() const { return index() == length(); } 502 503 /** 504 * Reset the parser to a previous parse position. 505 */ 506 void reset(size_t index = 0) { 507 MOZ_ASSERT(index <= length()); 508 index_ = index; 509 } 510 511 /** 512 * Returns true if at least `amount` characters can be read from the current 513 * parse position. 514 */ 515 bool hasMore(size_t amount) const { return index() + amount <= length(); } 516 517 /** 518 * Advances the parse position by `amount` characters. 519 */ 520 void advance(size_t amount) { 521 MOZ_ASSERT(hasMore(amount)); 522 index_ += amount; 523 } 524 525 /** 526 * Returns the character at the current parse position. 527 */ 528 CharT current() const { return string()[index()]; } 529 530 /** 531 * Returns the character at the next parse position. 532 */ 533 CharT next() const { return string()[index() + 1]; } 534 535 /** 536 * Returns the character at position `index`. 537 */ 538 CharT at(size_t index) const { return string()[index]; } 539 }; 540 541 template <typename CharT> 542 class TemporalParser final { 543 StringReader<CharT> reader_; 544 545 /** 546 * Read an unlimited amount of decimal digits, returning `Nothing` if no 547 * digits were read. 548 */ 549 mozilla::Maybe<double> digits(JSContext* cx); 550 551 /** 552 * Read exactly `length` digits, returning `Nothing` on failure. 553 */ 554 mozilla::Maybe<int32_t> digits(size_t length) { 555 MOZ_ASSERT(length > 0, "can't read zero digits"); 556 MOZ_ASSERT(length <= std::numeric_limits<int32_t>::digits10, 557 "can't read more than digits10 digits without overflow"); 558 559 if (!reader_.hasMore(length)) { 560 return mozilla::Nothing(); 561 } 562 int32_t num = 0; 563 size_t index = reader_.index(); 564 for (size_t i = 0; i < length; i++) { 565 auto ch = reader_.at(index + i); 566 if (!mozilla::IsAsciiDigit(ch)) { 567 return mozilla::Nothing(); 568 } 569 num = num * 10 + AsciiDigitToNumber(ch); 570 } 571 reader_.advance(length); 572 return mozilla::Some(num); 573 } 574 575 // TemporalDecimalFraction ::: 576 // TemporalDecimalSeparator DecimalDigit{1,9} 577 mozilla::Maybe<int32_t> fraction() { 578 if (!reader_.hasMore(2)) { 579 return mozilla::Nothing(); 580 } 581 if (!hasDecimalSeparator() || !mozilla::IsAsciiDigit(reader_.next())) { 582 return mozilla::Nothing(); 583 } 584 585 // Consume the decimal separator. 586 MOZ_ALWAYS_TRUE(decimalSeparator()); 587 588 // Maximal nine fractional digits are supported. 589 constexpr size_t maxFractions = 9; 590 591 // Read up to |maxFractions| digits. 592 int32_t num = 0; 593 size_t index = reader_.index(); 594 size_t i = 0; 595 for (; i < std::min(reader_.length() - index, maxFractions); i++) { 596 CharT ch = reader_.at(index + i); 597 if (!mozilla::IsAsciiDigit(ch)) { 598 break; 599 } 600 num = num * 10 + AsciiDigitToNumber(ch); 601 } 602 603 // Skip past the read digits. 604 reader_.advance(i); 605 606 // Normalize the fraction to |maxFractions| digits. 607 for (; i < maxFractions; i++) { 608 num *= 10; 609 } 610 return mozilla::Some(num); 611 } 612 613 /** 614 * Returns true iff the current character is `ch`. 615 */ 616 bool hasCharacter(CharT ch) const { 617 return reader_.hasMore(1) && reader_.current() == ch; 618 } 619 620 /** 621 * Consumes the current character if it's equal to `ch` and then returns 622 * `true`. Otherwise returns `false`. 623 */ 624 bool character(CharT ch) { 625 if (!hasCharacter(ch)) { 626 return false; 627 } 628 reader_.advance(1); 629 return true; 630 } 631 632 /** 633 * Consumes the next characters if they're equal to `str` and then returns 634 * `true`. Otherwise returns `false`. 635 */ 636 template <size_t N> 637 bool string(const char (&str)[N]) { 638 static_assert(N > 2, "use character() for one element strings"); 639 640 if (!reader_.hasMore(N - 1)) { 641 return false; 642 } 643 size_t index = reader_.index(); 644 for (size_t i = 0; i < N - 1; i++) { 645 if (reader_.at(index + i) != str[i]) { 646 return false; 647 } 648 } 649 reader_.advance(N - 1); 650 return true; 651 } 652 653 /** 654 * Returns true if the next two characters are ASCII alphabetic characters. 655 */ 656 bool hasTwoAsciiAlpha() { 657 if (!reader_.hasMore(2)) { 658 return false; 659 } 660 size_t index = reader_.index(); 661 return mozilla::IsAsciiAlpha(reader_.at(index)) && 662 mozilla::IsAsciiAlpha(reader_.at(index + 1)); 663 } 664 665 /** 666 * Returns true iff the current character is one of `chars`. 667 */ 668 bool hasOneOf(std::initializer_list<char> chars) const { 669 if (!reader_.hasMore(1)) { 670 return false; 671 } 672 auto ch = reader_.current(); 673 return std::find(chars.begin(), chars.end(), ch) != chars.end(); 674 } 675 676 /** 677 * Consumes the current character if it's in `chars` and then returns `true`. 678 * Otherwise returns `false`. 679 */ 680 bool oneOf(std::initializer_list<char> chars) { 681 if (!hasOneOf(chars)) { 682 return false; 683 } 684 reader_.advance(1); 685 return true; 686 } 687 688 /** 689 * Consumes the current character if it matches the predicate and then returns 690 * `true`. Otherwise returns `false`. 691 */ 692 template <typename Predicate> 693 bool matches(Predicate&& predicate) { 694 if (!reader_.hasMore(1)) { 695 return false; 696 } 697 698 CharT ch = reader_.current(); 699 if (!predicate(ch)) { 700 return false; 701 } 702 703 reader_.advance(1); 704 return true; 705 } 706 707 // ASCIISign ::: one of 708 // + - 709 bool hasSign() const { return hasOneOf({'+', '-'}); } 710 711 /** 712 * Consumes the current character, which must be a sign character, and returns 713 * its numeric value. 714 */ 715 int32_t sign() { 716 MOZ_ASSERT(hasSign()); 717 int32_t plus = hasCharacter('+'); 718 reader_.advance(1); 719 return plus ? 1 : -1; 720 } 721 722 // DateSeparator[Extended] ::: 723 // [+Extended] - 724 // [~Extended] [empty] 725 bool dateSeparator() { return character('-'); } 726 727 // TimeSeparator[Extended] ::: 728 // [+Extended] : 729 // [~Extended] [empty] 730 bool hasTimeSeparator() const { return hasCharacter(':'); } 731 732 bool timeSeparator() { return character(':'); } 733 734 // TemporalDecimalSeparator ::: one of 735 // . , 736 bool hasDecimalSeparator() const { return hasOneOf({'.', ','}); } 737 738 bool decimalSeparator() { return oneOf({'.', ','}); } 739 740 // DaysDesignator ::: one of 741 // D d 742 bool daysDesignator() { return oneOf({'D', 'd'}); } 743 744 // HoursDesignator ::: one of 745 // H h 746 bool hoursDesignator() { return oneOf({'H', 'h'}); } 747 748 // MinutesDesignator ::: one of 749 // M m 750 bool minutesDesignator() { return oneOf({'M', 'm'}); } 751 752 // MonthsDesignator ::: one of 753 // M m 754 bool monthsDesignator() { return oneOf({'M', 'm'}); } 755 756 // DurationDesignator ::: one of 757 // P p 758 bool durationDesignator() { return oneOf({'P', 'p'}); } 759 760 // SecondsDesignator ::: one of 761 // S s 762 bool secondsDesignator() { return oneOf({'S', 's'}); } 763 764 // DateTimeSeparator ::: 765 // <SP> 766 // T 767 // t 768 bool dateTimeSeparator() { return oneOf({' ', 'T', 't'}); } 769 770 // TimeDesignator ::: one of 771 // T t 772 bool hasTimeDesignator() const { return hasOneOf({'T', 't'}); } 773 774 bool timeDesignator() { return oneOf({'T', 't'}); } 775 776 // WeeksDesignator ::: one of 777 // W w 778 bool weeksDesignator() { return oneOf({'W', 'w'}); } 779 780 // YearsDesignator ::: one of 781 // Y y 782 bool yearsDesignator() { return oneOf({'Y', 'y'}); } 783 784 // UTCDesignator ::: one of 785 // Z z 786 bool utcDesignator() { return oneOf({'Z', 'z'}); } 787 788 // TZLeadingChar ::: 789 // Alpha 790 // . 791 // _ 792 bool tzLeadingChar() { 793 return matches([](auto ch) { 794 return mozilla::IsAsciiAlpha(ch) || ch == '.' || ch == '_'; 795 }); 796 } 797 798 // TZChar ::: 799 // TZLeadingChar 800 // DecimalDigit 801 // - 802 // + 803 bool tzChar() { 804 return matches([](auto ch) { 805 return mozilla::IsAsciiAlphanumeric(ch) || ch == '.' || ch == '_' || 806 ch == '-' || ch == '+'; 807 }); 808 } 809 810 // AnnotationCriticalFlag ::: 811 // ! 812 bool annotationCriticalFlag() { return character('!'); } 813 814 // AKeyLeadingChar ::: 815 // LowercaseAlpha 816 // _ 817 bool aKeyLeadingChar() { 818 return matches([](auto ch) { 819 return mozilla::IsAsciiLowercaseAlpha(ch) || ch == '_'; 820 }); 821 } 822 823 // AKeyChar ::: 824 // AKeyLeadingChar 825 // DecimalDigit 826 // - 827 bool aKeyChar() { 828 return matches([](auto ch) { 829 return mozilla::IsAsciiLowercaseAlpha(ch) || mozilla::IsAsciiDigit(ch) || 830 ch == '-' || ch == '_'; 831 }); 832 } 833 834 // AnnotationValueComponent ::: 835 // Alpha AnnotationValueComponent? 836 // DecimalDigit AnnotationValueComponent? 837 bool annotationValueComponent() { 838 size_t index = reader_.index(); 839 size_t i = 0; 840 for (; index + i < reader_.length(); i++) { 841 auto ch = reader_.at(index + i); 842 if (!mozilla::IsAsciiAlphanumeric(ch)) { 843 break; 844 } 845 } 846 if (i == 0) { 847 return false; 848 } 849 reader_.advance(i); 850 return true; 851 } 852 853 template <typename T> 854 static constexpr bool inBounds(const T& x, const T& min, const T& max) { 855 return min <= x && x <= max; 856 } 857 858 static auto err(JSErrNum error) { 859 // Explicitly create |ParserError| when JSErrNum is auto-convertible to the 860 // success type. 861 return mozilla::Err(ParserError{error}); 862 } 863 864 mozilla::Result<int32_t, ParserError> dateYear(); 865 mozilla::Result<int32_t, ParserError> dateMonth(); 866 mozilla::Result<int32_t, ParserError> dateDay(); 867 mozilla::Result<int32_t, ParserError> hour(); 868 mozilla::Result<mozilla::Maybe<int32_t>, ParserError> minute(bool required); 869 mozilla::Result<mozilla::Maybe<int32_t>, ParserError> second(bool required); 870 mozilla::Result<mozilla::Maybe<int32_t>, ParserError> timeSecond( 871 bool required); 872 873 mozilla::Result<ISODate, ParserError> date(); 874 875 mozilla::Result<Time, ParserError> time(); 876 877 mozilla::Result<ZonedDateTimeString, ParserError> dateTime(bool allowZ); 878 879 mozilla::Result<ISODate, ParserError> dateSpecYearMonth(); 880 881 mozilla::Result<ISODate, ParserError> dateSpecMonthDay(); 882 883 // Return true when |Annotation| can start at the current position. 884 bool hasAnnotationStart() const { return hasCharacter('['); } 885 886 // Return true when |TimeZoneAnnotation| can start at the current position. 887 bool hasTimeZoneAnnotationStart() const { 888 if (!hasCharacter('[')) { 889 return false; 890 } 891 892 // Ensure no '=' is found before the closing ']', otherwise the opening '[' 893 // may actually start an |Annotation| instead of a |TimeZoneAnnotation|. 894 for (size_t i = reader_.index() + 1; i < reader_.length(); i++) { 895 CharT ch = reader_.at(i); 896 if (ch == '=') { 897 return false; 898 } 899 if (ch == ']') { 900 break; 901 } 902 } 903 return true; 904 } 905 906 // Return true when |DateTimeUTCOffset| can start at the current position. 907 bool hasDateTimeUTCOffsetStart() { return hasOneOf({'Z', 'z', '+', '-'}); } 908 909 mozilla::Result<TimeZoneString, ParserError> dateTimeUTCOffset(bool allowZ); 910 911 mozilla::Result<DateTimeUTCOffset, ParserError> utcOffsetSubMinutePrecision(); 912 913 mozilla::Result<TimeZoneUTCOffset, ParserError> timeZoneUTCOffsetName(); 914 915 mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneIdentifier(); 916 917 mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneAnnotation(); 918 919 mozilla::Result<TimeZoneName, ParserError> timeZoneIANAName(); 920 921 mozilla::Result<AnnotationKey, ParserError> annotationKey(); 922 mozilla::Result<AnnotationValue, ParserError> annotationValue(); 923 mozilla::Result<Annotation, ParserError> annotation(); 924 mozilla::Result<CalendarName, ParserError> annotations(); 925 926 mozilla::Result<ZonedDateTimeString, ParserError> annotatedTime(); 927 928 mozilla::Result<ZonedDateTimeString, ParserError> annotatedDateTime(); 929 930 mozilla::Result<ZonedDateTimeString, ParserError> 931 annotatedDateTimeTimeRequired(); 932 933 mozilla::Result<ZonedDateTimeString, ParserError> annotatedYearMonth(); 934 935 mozilla::Result<ZonedDateTimeString, ParserError> annotatedMonthDay(); 936 937 mozilla::Result<double, ParserError> durationDigits(JSContext* cx); 938 939 template <typename T> 940 mozilla::Result<T, ParserError> parse( 941 mozilla::Result<T, ParserError>&& result) const; 942 943 template <typename T> 944 mozilla::Result<T, ParserError> complete(const T& value) const; 945 946 mozilla::Result<mozilla::Ok, ParserError> nonempty() const; 947 948 public: 949 explicit TemporalParser(mozilla::Span<const CharT> str) : reader_(str) {} 950 951 mozilla::Result<ZonedDateTimeString, ParserError> 952 parseTemporalInstantString(); 953 954 mozilla::Result<ZonedDateTimeString, ParserError> 955 parseTemporalTimeZoneString(); 956 957 mozilla::Result<TimeZoneAnnotation, ParserError> parseTimeZoneIdentifier(); 958 959 mozilla::Result<DateTimeUTCOffset, ParserError> parseDateTimeUTCOffset(); 960 961 mozilla::Result<TemporalDurationString, ParserError> 962 parseTemporalDurationString(JSContext* cx); 963 964 mozilla::Result<ZonedDateTimeString, ParserError> 965 parseTemporalCalendarString(); 966 967 mozilla::Result<ZonedDateTimeString, ParserError> parseTemporalTimeString(); 968 969 mozilla::Result<ZonedDateTimeString, ParserError> 970 parseTemporalMonthDayString(); 971 972 mozilla::Result<ZonedDateTimeString, ParserError> 973 parseTemporalYearMonthString(); 974 975 mozilla::Result<ZonedDateTimeString, ParserError> 976 parseTemporalDateTimeString(); 977 978 mozilla::Result<ZonedDateTimeString, ParserError> 979 parseTemporalZonedDateTimeString(); 980 981 mozilla::Result<ZonedDateTimeString, ParserError> 982 parseTemporalRelativeToString(); 983 }; 984 985 template <typename CharT> 986 template <typename T> 987 mozilla::Result<T, ParserError> TemporalParser<CharT>::parse( 988 mozilla::Result<T, ParserError>&& result) const { 989 if (result.isOk() && !reader_.atEnd()) { 990 return mozilla::Err(JSMSG_TEMPORAL_PARSER_UNEXPECTED_CHARACTERS_AT_END); 991 } 992 return std::move(result); 993 } 994 995 template <typename CharT> 996 template <typename T> 997 mozilla::Result<T, ParserError> TemporalParser<CharT>::complete( 998 const T& result) const { 999 if (!reader_.atEnd()) { 1000 return mozilla::Err(JSMSG_TEMPORAL_PARSER_UNEXPECTED_CHARACTERS_AT_END); 1001 } 1002 return result; 1003 } 1004 1005 template <typename CharT> 1006 mozilla::Result<mozilla::Ok, ParserError> TemporalParser<CharT>::nonempty() 1007 const { 1008 if (reader_.length() == 0) { 1009 return mozilla::Err(JSMSG_TEMPORAL_PARSER_EMPTY_STRING); 1010 } 1011 return mozilla::Ok{}; 1012 } 1013 1014 template <typename CharT> 1015 mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::dateYear() { 1016 // DateYear ::: 1017 // DecimalDigit{4} 1018 // ASCIISign DecimalDigit{6} 1019 1020 if (auto year = digits(4)) { 1021 return year.value(); 1022 } 1023 if (hasSign()) { 1024 int32_t yearSign = sign(); 1025 if (auto year = digits(6)) { 1026 int32_t result = yearSign * year.value(); 1027 if (yearSign < 0 && result == 0) { 1028 return err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR); 1029 } 1030 return result; 1031 } 1032 return err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR); 1033 } 1034 return err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR); 1035 } 1036 1037 template <typename CharT> 1038 mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::dateMonth() { 1039 // DateMonth ::: 1040 // 0 NonzeroDigit 1041 // 10 1042 // 11 1043 // 12 1044 if (auto month = digits(2)) { 1045 int32_t result = month.value(); 1046 if (!inBounds(result, 1, 12)) { 1047 return err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH); 1048 } 1049 return result; 1050 } 1051 return err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH); 1052 } 1053 1054 template <typename CharT> 1055 mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::dateDay() { 1056 // DateDay ::: 1057 // 0 NonzeroDigit 1058 // 1 DecimalDigit 1059 // 2 DecimalDigit 1060 // 30 1061 // 31 1062 if (auto day = digits(2)) { 1063 int32_t result = day.value(); 1064 if (!inBounds(result, 1, 31)) { 1065 return err(JSMSG_TEMPORAL_PARSER_INVALID_DAY); 1066 } 1067 return result; 1068 } 1069 return err(JSMSG_TEMPORAL_PARSER_MISSING_DAY); 1070 } 1071 1072 template <typename CharT> 1073 mozilla::Result<int32_t, ParserError> TemporalParser<CharT>::hour() { 1074 // Hour ::: 1075 // 0 DecimalDigit 1076 // 1 DecimalDigit 1077 // 20 1078 // 21 1079 // 22 1080 // 23 1081 if (auto hour = digits(2)) { 1082 int32_t result = hour.value(); 1083 if (!inBounds(result, 0, 23)) { 1084 return err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR); 1085 } 1086 return result; 1087 } 1088 return err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR); 1089 } 1090 1091 template <typename CharT> 1092 mozilla::Result<mozilla::Maybe<int32_t>, ParserError> 1093 TemporalParser<CharT>::minute(bool required) { 1094 // MinuteSecond ::: 1095 // 0 DecimalDigit 1096 // 1 DecimalDigit 1097 // 2 DecimalDigit 1098 // 3 DecimalDigit 1099 // 4 DecimalDigit 1100 // 5 DecimalDigit 1101 if (auto minute = digits(2)) { 1102 if (!inBounds(minute.value(), 0, 59)) { 1103 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE); 1104 } 1105 return minute; 1106 } 1107 if (!required) { 1108 return mozilla::Maybe<int32_t>{mozilla::Nothing{}}; 1109 } 1110 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE); 1111 } 1112 1113 template <typename CharT> 1114 mozilla::Result<mozilla::Maybe<int32_t>, ParserError> 1115 TemporalParser<CharT>::second(bool required) { 1116 // MinuteSecond ::: 1117 // 0 DecimalDigit 1118 // 1 DecimalDigit 1119 // 2 DecimalDigit 1120 // 3 DecimalDigit 1121 // 4 DecimalDigit 1122 // 5 DecimalDigit 1123 if (auto minute = digits(2)) { 1124 if (!inBounds(minute.value(), 0, 59)) { 1125 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SECOND); 1126 } 1127 return minute; 1128 } 1129 if (!required) { 1130 return mozilla::Maybe<int32_t>{mozilla::Nothing{}}; 1131 } 1132 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND); 1133 } 1134 1135 template <typename CharT> 1136 mozilla::Result<mozilla::Maybe<int32_t>, ParserError> 1137 TemporalParser<CharT>::timeSecond(bool required) { 1138 // TimeSecond ::: 1139 // MinuteSecond 1140 // 60 1141 // 1142 // MinuteSecond ::: 1143 // 0 DecimalDigit 1144 // 1 DecimalDigit 1145 // 2 DecimalDigit 1146 // 3 DecimalDigit 1147 // 4 DecimalDigit 1148 // 5 DecimalDigit 1149 if (auto minute = digits(2)) { 1150 if (!inBounds(minute.value(), 0, 60)) { 1151 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_LEAPSECOND); 1152 } 1153 return minute; 1154 } 1155 if (!required) { 1156 return mozilla::Maybe<int32_t>{mozilla::Nothing{}}; 1157 } 1158 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND); 1159 } 1160 1161 template <typename CharT> 1162 mozilla::Result<ISODate, ParserError> TemporalParser<CharT>::date() { 1163 // clang-format off 1164 // 1165 // Date ::: 1166 // DateSpec[+Extended] 1167 // DateSpec[~Extended] 1168 // 1169 // DateSpec[Extended] ::: 1170 // DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay 1171 // 1172 // clang-format on 1173 1174 ISODate result{}; 1175 1176 result.year = MOZ_TRY(dateYear()); 1177 1178 // Optional |DateSeparator|. 1179 bool hasMonthSeparator = dateSeparator(); 1180 1181 result.month = MOZ_TRY(dateMonth()); 1182 1183 // Optional |DateSeparator|. 1184 bool hasDaySeparator = dateSeparator(); 1185 1186 // Date separators must be consistent. 1187 if (hasMonthSeparator != hasDaySeparator) { 1188 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INCONSISTENT_DATE_SEPARATOR); 1189 } 1190 1191 result.day = MOZ_TRY(dateDay()); 1192 1193 return result; 1194 } 1195 1196 template <typename CharT> 1197 mozilla::Result<Time, ParserError> TemporalParser<CharT>::time() { 1198 // clang-format off 1199 // 1200 // Time ::: 1201 // TimeSpec[+Extended] 1202 // TimeSpec[~Extended] 1203 // 1204 // TimeSpec[Extended] ::: 1205 // Hour 1206 // Hour TimeSeparator[?Extended] MinuteSecond 1207 // Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] TimeSecond TemporalDecimalFraction? 1208 // 1209 // clang-format on 1210 1211 Time result{}; 1212 1213 result.hour = MOZ_TRY(hour()); 1214 1215 // Optional |TimeSeparator|. 1216 bool hasMinuteSeparator = timeSeparator(); 1217 1218 mozilla::Maybe<int32_t> minutes = MOZ_TRY(minute(hasMinuteSeparator)); 1219 if (minutes) { 1220 result.minute = minutes.value(); 1221 1222 // Optional |TimeSeparator|. 1223 bool hasSecondSeparator = timeSeparator(); 1224 1225 mozilla::Maybe<int32_t> seconds = MOZ_TRY(timeSecond(hasSecondSeparator)); 1226 if (seconds) { 1227 result.second = seconds.value(); 1228 1229 // Time separators must be consistent. 1230 if (hasMinuteSeparator != hasSecondSeparator) { 1231 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INCONSISTENT_TIME_SEPARATOR); 1232 } 1233 1234 // TemporalDecimalFraction ::: 1235 // TemporalDecimalSeparator DecimalDigit{1,9} 1236 if (auto f = fraction()) { 1237 int32_t fractionalPart = f.value(); 1238 result.millisecond = fractionalPart / 1'000'000; 1239 result.microsecond = (fractionalPart % 1'000'000) / 1'000; 1240 result.nanosecond = fractionalPart % 1'000; 1241 } 1242 } 1243 } 1244 1245 return result; 1246 } 1247 1248 template <typename CharT> 1249 mozilla::Result<ZonedDateTimeString, ParserError> 1250 TemporalParser<CharT>::dateTime(bool allowZ) { 1251 // DateTime[Z, TimeRequired] ::: 1252 // [~TimeRequired] Date 1253 // Date DateTimeSeparator Time DateTimeUTCOffset[?Z]? 1254 // 1255 // When called as `DateTime[?Z, ~TimeRequired]`. 1256 1257 ZonedDateTimeString result{}; 1258 1259 result.date = MOZ_TRY(date()); 1260 1261 if (dateTimeSeparator()) { 1262 result.time = MOZ_TRY(time()); 1263 1264 if (hasDateTimeUTCOffsetStart()) { 1265 result.timeZone = MOZ_TRY(dateTimeUTCOffset(allowZ)); 1266 } 1267 } else { 1268 result.startOfDay = true; 1269 } 1270 1271 return result; 1272 } 1273 1274 template <typename CharT> 1275 mozilla::Result<TimeZoneString, ParserError> 1276 TemporalParser<CharT>::dateTimeUTCOffset(bool allowZ) { 1277 // DateTimeUTCOffset[Z] ::: 1278 // [+Z] UTCDesignator 1279 // UTCOffset[+SubMinutePrecision] 1280 1281 if (utcDesignator()) { 1282 if (!allowZ) { 1283 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR); 1284 } 1285 return TimeZoneString::UTC(); 1286 } 1287 1288 if (hasSign()) { 1289 DateTimeUTCOffset offset = MOZ_TRY(utcOffsetSubMinutePrecision()); 1290 1291 return TimeZoneString::from(offset); 1292 } 1293 1294 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE); 1295 } 1296 1297 template <typename CharT> 1298 mozilla::Result<TimeZoneUTCOffset, ParserError> 1299 TemporalParser<CharT>::timeZoneUTCOffsetName() { 1300 // clang-format off 1301 // 1302 // UTCOffset[SubMinutePrecision] ::: 1303 // ASCIISign Hour 1304 // ASCIISign Hour TimeSeparator[+Extended] MinuteSecond 1305 // ASCIISign Hour TimeSeparator[~Extended] MinuteSecond 1306 // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction? 1307 // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction? 1308 // 1309 // When called as `UTCOffset[~SubMinutePrecision]`. 1310 // 1311 // clang-format on 1312 1313 TimeZoneUTCOffset result{}; 1314 1315 if (!hasSign()) { 1316 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN); 1317 } 1318 result.sign = sign(); 1319 1320 result.hour = MOZ_TRY(hour()); 1321 1322 // Optional |TimeSeparator|. 1323 bool hasMinuteSeparator = timeSeparator(); 1324 1325 mozilla::Maybe<int32_t> minutes = MOZ_TRY(minute(hasMinuteSeparator)); 1326 if (minutes) { 1327 result.minute = minutes.value(); 1328 1329 if (hasTimeSeparator()) { 1330 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE); 1331 } 1332 } 1333 1334 return result; 1335 } 1336 1337 template <typename CharT> 1338 mozilla::Result<DateTimeUTCOffset, ParserError> 1339 TemporalParser<CharT>::utcOffsetSubMinutePrecision() { 1340 // clang-format off 1341 // 1342 // UTCOffset[SubMinutePrecision] ::: 1343 // ASCIISign Hour 1344 // ASCIISign Hour TimeSeparator[+Extended] MinuteSecond 1345 // ASCIISign Hour TimeSeparator[~Extended] MinuteSecond 1346 // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction? 1347 // [+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction? 1348 // 1349 // When called as `UTCOffset[+SubMinutePrecision]`. 1350 // 1351 // clang-format on 1352 1353 DateTimeUTCOffset result{}; 1354 1355 if (!hasSign()) { 1356 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN); 1357 } 1358 result.sign = sign(); 1359 1360 result.hour = MOZ_TRY(hour()); 1361 1362 // Optional |TimeSeparator|. 1363 bool hasMinuteSeparator = timeSeparator(); 1364 1365 mozilla::Maybe<int32_t> minutes = MOZ_TRY(minute(hasMinuteSeparator)); 1366 if (minutes) { 1367 result.minute = minutes.value(); 1368 1369 // Optional |TimeSeparator|. 1370 bool hasSecondSeparator = timeSeparator(); 1371 1372 mozilla::Maybe<int32_t> seconds = MOZ_TRY(second(hasSecondSeparator)); 1373 if (seconds) { 1374 result.second = seconds.value(); 1375 1376 // Time separators must be consistent. 1377 if (hasMinuteSeparator != hasSecondSeparator) { 1378 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INCONSISTENT_TIME_SEPARATOR); 1379 } 1380 1381 if (auto fractionalPart = fraction()) { 1382 result.fractionalPart = fractionalPart.value(); 1383 } 1384 1385 result.subMinutePrecision = true; 1386 } 1387 } 1388 1389 return result; 1390 } 1391 1392 template <typename CharT> 1393 mozilla::Result<TimeZoneAnnotation, ParserError> 1394 TemporalParser<CharT>::timeZoneIdentifier() { 1395 // TimeZoneIdentifier ::: 1396 // UTCOffset[~SubMinutePrecision] 1397 // TimeZoneIANAName 1398 1399 TimeZoneAnnotation result{}; 1400 if (hasSign()) { 1401 result.offset = MOZ_TRY(timeZoneUTCOffsetName()); 1402 } else { 1403 result.name = MOZ_TRY(timeZoneIANAName()); 1404 } 1405 1406 return result; 1407 } 1408 1409 template <typename CharT> 1410 mozilla::Result<TimeZoneAnnotation, ParserError> 1411 TemporalParser<CharT>::timeZoneAnnotation() { 1412 // TimeZoneAnnotation ::: 1413 // [ AnnotationCriticalFlag? TimeZoneIdentifier ] 1414 1415 if (!character('[')) { 1416 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE); 1417 } 1418 1419 // Skip over the optional critical flag. 1420 annotationCriticalFlag(); 1421 1422 auto result = timeZoneIdentifier(); 1423 if (result.isErr()) { 1424 return result.propagateErr(); 1425 } 1426 1427 if (!character(']')) { 1428 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_TIMEZONE); 1429 } 1430 1431 return result; 1432 } 1433 1434 template <typename CharT> 1435 mozilla::Result<TimeZoneName, ParserError> 1436 TemporalParser<CharT>::timeZoneIANAName() { 1437 // TimeZoneIANAName ::: 1438 // TimeZoneIANANameComponent 1439 // TimeZoneIANAName / TimeZoneIANANameComponent 1440 // 1441 // TimeZoneIANANameComponent ::: 1442 // TZLeadingChar 1443 // TimeZoneIANANameComponent TZChar 1444 1445 size_t start = reader_.index(); 1446 1447 do { 1448 if (!tzLeadingChar()) { 1449 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_NAME); 1450 } 1451 1452 // Optionally followed by a sequence of |TZChar|. 1453 while (tzChar()) { 1454 } 1455 } while (character('/')); 1456 1457 return TimeZoneName{start, reader_.index() - start}; 1458 } 1459 1460 template <typename CharT> 1461 mozilla::Maybe<double> TemporalParser<CharT>::digits(JSContext* cx) { 1462 auto span = reader_.string().Subspan(reader_.index()); 1463 1464 // GetPrefixInteger can't fail when integer separator handling is disabled. 1465 const CharT* endp = nullptr; 1466 double num; 1467 MOZ_ALWAYS_TRUE(GetPrefixInteger(span.data(), span.data() + span.size(), 10, 1468 IntegerSeparatorHandling::None, &endp, 1469 &num)); 1470 1471 size_t len = endp - span.data(); 1472 if (len == 0) { 1473 return mozilla::Nothing(); 1474 } 1475 reader_.advance(len); 1476 return mozilla::Some(num); 1477 } 1478 1479 template <typename CharT> 1480 mozilla::Result<ZonedDateTimeString, ParserError> 1481 TemporalParser<CharT>::parseTemporalInstantString() { 1482 MOZ_TRY(nonempty()); 1483 1484 // Initialize all fields to zero. 1485 ZonedDateTimeString result{}; 1486 1487 // clang-format off 1488 // 1489 // TemporalInstantString ::: 1490 // Date DateTimeSeparator Time DateTimeUTCOffset[+Z] TimeZoneAnnotation? Annotations? 1491 // 1492 // clang-format on 1493 1494 result.date = MOZ_TRY(date()); 1495 1496 if (!dateTimeSeparator()) { 1497 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR); 1498 } 1499 1500 result.time = MOZ_TRY(time()); 1501 1502 result.timeZone = MOZ_TRY(dateTimeUTCOffset(/* allowZ = */ true)); 1503 1504 if (hasTimeZoneAnnotationStart()) { 1505 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 1506 } 1507 1508 if (hasAnnotationStart()) { 1509 MOZ_TRY(annotations()); 1510 } 1511 1512 return complete(result); 1513 } 1514 1515 /** 1516 * ParseTemporalInstantString ( isoString ) 1517 */ 1518 template <typename CharT> 1519 static auto ParseTemporalInstantString(mozilla::Span<const CharT> str) { 1520 TemporalParser<CharT> parser(str); 1521 return parser.parseTemporalInstantString(); 1522 } 1523 1524 /** 1525 * ParseTemporalInstantString ( isoString ) 1526 */ 1527 static auto ParseTemporalInstantString(Handle<JSLinearString*> str) { 1528 JS::AutoCheckCannotGC nogc; 1529 if (str->hasLatin1Chars()) { 1530 return ParseTemporalInstantString<Latin1Char>(str->latin1Range(nogc)); 1531 } 1532 return ParseTemporalInstantString<char16_t>(str->twoByteRange(nogc)); 1533 } 1534 1535 /** 1536 * ParseTemporalInstantString ( isoString ) 1537 */ 1538 bool js::temporal::ParseTemporalInstantString(JSContext* cx, 1539 Handle<JSString*> str, 1540 ISODateTime* result, 1541 int64_t* offset) { 1542 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 1543 if (!linear) { 1544 return false; 1545 } 1546 1547 // Step 1. 1548 auto parseResult = ::ParseTemporalInstantString(linear); 1549 if (parseResult.isErr()) { 1550 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1551 parseResult.unwrapErr(), "instant"); 1552 return false; 1553 } 1554 ZonedDateTimeString parsed = parseResult.unwrap(); 1555 1556 // Step 2. 1557 if (!ParseISODateTime(cx, parsed, result)) { 1558 return false; 1559 } 1560 1561 // Steps 3-4. 1562 if (parsed.timeZone.hasOffset()) { 1563 *offset = ParseDateTimeUTCOffset(parsed.timeZone.offset).offset; 1564 } else { 1565 MOZ_ASSERT(parsed.timeZone.isUTC()); 1566 *offset = 0; 1567 } 1568 return true; 1569 } 1570 1571 template <typename CharT> 1572 mozilla::Result<ZonedDateTimeString, ParserError> 1573 TemporalParser<CharT>::parseTemporalTimeZoneString() { 1574 MOZ_TRY(nonempty()); 1575 1576 // Handle the common case of a standalone time zone identifier first. 1577 if (auto tz = parse(timeZoneIdentifier()); tz.isOk()) { 1578 auto timeZone = tz.unwrap(); 1579 1580 ZonedDateTimeString result{}; 1581 if (timeZone.hasOffset()) { 1582 result.timeZone = TimeZoneString::from(timeZone.offset); 1583 } else { 1584 MOZ_ASSERT(timeZone.hasName()); 1585 result.timeZone = TimeZoneString::from(timeZone.name); 1586 } 1587 return result; 1588 } 1589 1590 LikelyError likelyError{}; 1591 1592 // Try all five parse goals from ParseISODateTime in order. 1593 // 1594 // TemporalDateTimeString 1595 // TemporalInstantString 1596 // TemporalTimeString 1597 // TemporalMonthDayString 1598 // TemporalYearMonthString 1599 1600 // Restart parsing from the start of the string. 1601 reader_.reset(); 1602 1603 auto dateTime = parseTemporalDateTimeString(); 1604 if (dateTime.isOk()) { 1605 return dateTime; 1606 } 1607 likelyError.update(dateTime, reader_.index()); 1608 1609 // Restart parsing from the start of the string. 1610 reader_.reset(); 1611 1612 auto instant = parseTemporalInstantString(); 1613 if (instant.isOk()) { 1614 return instant; 1615 } 1616 likelyError.update(instant, reader_.index()); 1617 1618 // Restart parsing from the start of the string. 1619 reader_.reset(); 1620 1621 auto time = parseTemporalTimeString(); 1622 if (time.isOk()) { 1623 return time; 1624 } 1625 likelyError.update(time, reader_.index()); 1626 1627 // Restart parsing from the start of the string. 1628 reader_.reset(); 1629 1630 auto monthDay = parseTemporalMonthDayString(); 1631 if (monthDay.isOk()) { 1632 return monthDay; 1633 } 1634 likelyError.update(monthDay, reader_.index()); 1635 1636 // Restart parsing from the start of the string. 1637 reader_.reset(); 1638 1639 auto yearMonth = parseTemporalYearMonthString(); 1640 if (yearMonth.isOk()) { 1641 return yearMonth; 1642 } 1643 likelyError.update(yearMonth, reader_.index()); 1644 1645 return likelyError.propagate(); 1646 } 1647 1648 /** 1649 * ParseTemporalTimeZoneString ( timeZoneString ) 1650 */ 1651 template <typename CharT> 1652 static auto ParseTemporalTimeZoneString(mozilla::Span<const CharT> str) { 1653 TemporalParser<CharT> parser(str); 1654 return parser.parseTemporalTimeZoneString(); 1655 } 1656 1657 /** 1658 * ParseTemporalTimeZoneString ( timeZoneString ) 1659 */ 1660 static auto ParseTemporalTimeZoneString(Handle<JSLinearString*> str) { 1661 JS::AutoCheckCannotGC nogc; 1662 if (str->hasLatin1Chars()) { 1663 return ParseTemporalTimeZoneString<Latin1Char>(str->latin1Range(nogc)); 1664 } 1665 return ParseTemporalTimeZoneString<char16_t>(str->twoByteRange(nogc)); 1666 } 1667 1668 /** 1669 * ParseTemporalTimeZoneString ( timeZoneString ) 1670 */ 1671 bool js::temporal::ParseTemporalTimeZoneString( 1672 JSContext* cx, Handle<JSString*> str, 1673 MutableHandle<ParsedTimeZone> result) { 1674 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 1675 if (!linear) { 1676 return false; 1677 } 1678 1679 // Steps 1-4. 1680 auto parseResult = ::ParseTemporalTimeZoneString(linear); 1681 if (parseResult.isErr()) { 1682 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1683 parseResult.unwrapErr(), "time zone"); 1684 return false; 1685 } 1686 ZonedDateTimeString parsed = parseResult.unwrap(); 1687 const auto& timeZone = parsed.timeZone; 1688 1689 // Step 3. 1690 ISODateTime unused; 1691 if (!ParseISODateTime(cx, parsed, &unused)) { 1692 return false; 1693 } 1694 1695 if (timeZone.hasAnnotation()) { 1696 // Case 1: 19700101T00:00Z[+02:00] 1697 // Case 2: 19700101T00:00+00:00[+02:00] 1698 // Case 3: 19700101T00:00[+02:00] 1699 // Case 4: 19700101T00:00Z[Europe/Berlin] 1700 // Case 5: 19700101T00:00+00:00[Europe/Berlin] 1701 // Case 6: 19700101T00:00[Europe/Berlin] 1702 1703 if (!ParseTimeZoneAnnotation(cx, timeZone.annotation, linear, result)) { 1704 return false; 1705 } 1706 } else if (timeZone.isUTC()) { 1707 result.set(ParsedTimeZone::fromName(cx->names().UTC)); 1708 } else if (timeZone.hasOffset()) { 1709 // ToTemporalTimeZoneSlotValue, step 7. 1710 // 1711 // Error reporting for sub-minute precision moved here. 1712 if (timeZone.offset.subMinutePrecision) { 1713 JS_ReportErrorNumberASCII( 1714 cx, GetErrorMessage, nullptr, 1715 JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE, "time zone"); 1716 return false; 1717 } 1718 1719 int32_t offset = ParseTimeZoneOffset(timeZone.offset.toTimeZoneUTCOffset()); 1720 result.set(ParsedTimeZone::fromOffset(offset)); 1721 } else { 1722 // Step 5. 1723 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1724 JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE, 1725 "time zone"); 1726 return false; 1727 } 1728 1729 // Step 6. 1730 return true; 1731 } 1732 1733 template <typename CharT> 1734 mozilla::Result<TimeZoneAnnotation, ParserError> 1735 TemporalParser<CharT>::parseTimeZoneIdentifier() { 1736 MOZ_TRY(nonempty()); 1737 return parse(timeZoneIdentifier()); 1738 } 1739 1740 /** 1741 * ParseTimeZoneIdentifier ( identifier ) 1742 */ 1743 template <typename CharT> 1744 static auto ParseTimeZoneIdentifier(mozilla::Span<const CharT> str) { 1745 TemporalParser<CharT> parser(str); 1746 return parser.parseTimeZoneIdentifier(); 1747 } 1748 1749 /** 1750 * ParseTimeZoneIdentifier ( identifier ) 1751 */ 1752 static auto ParseTimeZoneIdentifier(Handle<JSLinearString*> str) { 1753 JS::AutoCheckCannotGC nogc; 1754 if (str->hasLatin1Chars()) { 1755 return ParseTimeZoneIdentifier<Latin1Char>(str->latin1Range(nogc)); 1756 } 1757 return ParseTimeZoneIdentifier<char16_t>(str->twoByteRange(nogc)); 1758 } 1759 1760 /** 1761 * ParseTimeZoneIdentifier ( identifier ) 1762 */ 1763 bool js::temporal::ParseTimeZoneIdentifier( 1764 JSContext* cx, Handle<JSString*> str, 1765 MutableHandle<ParsedTimeZone> result) { 1766 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 1767 if (!linear) { 1768 return false; 1769 } 1770 1771 // Steps 1-2. 1772 auto parseResult = ::ParseTimeZoneIdentifier(linear); 1773 if (parseResult.isErr()) { 1774 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1775 parseResult.unwrapErr(), "time zone identifier"); 1776 return false; 1777 } 1778 auto timeZone = parseResult.unwrap(); 1779 1780 // Steps 3-4. 1781 return ParseTimeZoneAnnotation(cx, timeZone, linear, result); 1782 } 1783 1784 template <typename CharT> 1785 mozilla::Result<DateTimeUTCOffset, ParserError> 1786 TemporalParser<CharT>::parseDateTimeUTCOffset() { 1787 MOZ_TRY(nonempty()); 1788 return parse(utcOffsetSubMinutePrecision()); 1789 } 1790 1791 /** 1792 * ParseDateTimeUTCOffset ( offsetString ) 1793 */ 1794 template <typename CharT> 1795 static auto ParseDateTimeUTCOffset(mozilla::Span<const CharT> str) { 1796 TemporalParser<CharT> parser(str); 1797 return parser.parseDateTimeUTCOffset(); 1798 } 1799 1800 /** 1801 * ParseDateTimeUTCOffset ( offsetString ) 1802 */ 1803 static auto ParseDateTimeUTCOffset(Handle<JSLinearString*> str) { 1804 JS::AutoCheckCannotGC nogc; 1805 if (str->hasLatin1Chars()) { 1806 return ParseDateTimeUTCOffset<Latin1Char>(str->latin1Range(nogc)); 1807 } 1808 return ParseDateTimeUTCOffset<char16_t>(str->twoByteRange(nogc)); 1809 } 1810 1811 /** 1812 * ParseDateTimeUTCOffset ( offsetString ) 1813 */ 1814 bool js::temporal::ParseDateTimeUTCOffset(JSContext* cx, Handle<JSString*> str, 1815 int64_t* result) { 1816 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 1817 if (!linear) { 1818 return false; 1819 } 1820 1821 // Steps 1-2. 1822 auto parseResult = ::ParseDateTimeUTCOffset(linear); 1823 if (parseResult.isErr()) { 1824 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1825 parseResult.unwrapErr(), "UTC offset"); 1826 return false; 1827 } 1828 1829 // Steps 3-21. 1830 *result = ParseDateTimeUTCOffset(parseResult.unwrap()).offset; 1831 return true; 1832 } 1833 1834 template <typename CharT> 1835 mozilla::Result<double, ParserError> TemporalParser<CharT>::durationDigits( 1836 JSContext* cx) { 1837 auto d = digits(cx); 1838 if (!d) { 1839 return err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS); 1840 } 1841 return *d; 1842 } 1843 1844 template <typename CharT> 1845 mozilla::Result<TemporalDurationString, ParserError> 1846 TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) { 1847 MOZ_TRY(nonempty()); 1848 1849 // Initialize all fields to zero. 1850 TemporalDurationString result{}; 1851 1852 // TemporalDurationString ::: 1853 // Duration 1854 // 1855 // Duration ::: 1856 // ASCIISign? DurationDesignator DurationDate 1857 // ASCIISign? DurationDesignator DurationTime 1858 1859 if (hasSign()) { 1860 result.sign = sign(); 1861 } 1862 1863 if (!durationDesignator()) { 1864 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DESIGNATOR); 1865 } 1866 1867 // DurationDate ::: 1868 // DurationYearsPart DurationTime? 1869 // DurationMonthsPart DurationTime? 1870 // DurationWeeksPart DurationTime? 1871 // DurationDaysPart DurationTime? 1872 1873 do { 1874 if (hasTimeDesignator()) { 1875 break; 1876 } 1877 1878 double num = MOZ_TRY(durationDigits(cx)); 1879 1880 // DurationYearsPart ::: 1881 // DecimalDigits[~Sep] YearsDesignator DurationMonthsPart 1882 // DecimalDigits[~Sep] YearsDesignator DurationWeeksPart 1883 // DecimalDigits[~Sep] YearsDesignator DurationDaysPart? 1884 if (yearsDesignator()) { 1885 result.years = num; 1886 if (reader_.atEnd()) { 1887 return result; 1888 } 1889 if (hasTimeDesignator()) { 1890 break; 1891 } 1892 num = MOZ_TRY(durationDigits(cx)); 1893 } 1894 1895 // DurationMonthsPart ::: 1896 // DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart 1897 // DecimalDigits[~Sep] MonthsDesignator DurationDaysPart? 1898 if (monthsDesignator()) { 1899 result.months = num; 1900 if (reader_.atEnd()) { 1901 return result; 1902 } 1903 if (hasTimeDesignator()) { 1904 break; 1905 } 1906 num = MOZ_TRY(durationDigits(cx)); 1907 } 1908 1909 // DurationWeeksPart ::: 1910 // DecimalDigits[~Sep] WeeksDesignator DurationDaysPart? 1911 if (weeksDesignator()) { 1912 result.weeks = num; 1913 if (reader_.atEnd()) { 1914 return result; 1915 } 1916 if (hasTimeDesignator()) { 1917 break; 1918 } 1919 num = MOZ_TRY(durationDigits(cx)); 1920 } 1921 1922 // DurationDaysPart ::: 1923 // DecimalDigits[~Sep] DaysDesignator 1924 if (daysDesignator()) { 1925 result.days = num; 1926 if (reader_.atEnd()) { 1927 return result; 1928 } 1929 if (hasTimeDesignator()) { 1930 break; 1931 } 1932 } 1933 1934 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_UNIT_DESIGNATOR); 1935 } while (false); 1936 1937 // DurationTime ::: 1938 // TimeDesignator DurationHoursPart 1939 // TimeDesignator DurationMinutesPart 1940 // TimeDesignator DurationSecondsPart 1941 if (!timeDesignator()) { 1942 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIME_DESIGNATOR); 1943 } 1944 1945 double num = MOZ_TRY(durationDigits(cx)); 1946 1947 auto frac = fraction(); 1948 1949 // DurationHoursPart ::: 1950 // DecimalDigits[~Sep] TemporalDecimalFraction HoursDesignator 1951 // DecimalDigits[~Sep] HoursDesignator DurationMinutesPart 1952 // DecimalDigits[~Sep] HoursDesignator DurationSecondsPart? 1953 bool hasHoursFraction = false; 1954 if (hoursDesignator()) { 1955 hasHoursFraction = bool(frac); 1956 result.hours = num; 1957 result.hoursFraction = frac.valueOr(0); 1958 if (reader_.atEnd()) { 1959 return result; 1960 } 1961 1962 num = MOZ_TRY(durationDigits(cx)); 1963 frac = fraction(); 1964 } 1965 1966 // DurationMinutesPart ::: 1967 // DecimalDigits[~Sep] TemporalDecimalFraction MinutesDesignator 1968 // DecimalDigits[~Sep] MinutesDesignator DurationSecondsPart? 1969 bool hasMinutesFraction = false; 1970 if (minutesDesignator()) { 1971 if (hasHoursFraction) { 1972 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_MINUTES); 1973 } 1974 hasMinutesFraction = bool(frac); 1975 result.minutes = num; 1976 result.minutesFraction = frac.valueOr(0); 1977 if (reader_.atEnd()) { 1978 return result; 1979 } 1980 1981 num = MOZ_TRY(durationDigits(cx)); 1982 frac = fraction(); 1983 } 1984 1985 // DurationSecondsPart ::: 1986 // DecimalDigits[~Sep] TemporalDecimalFraction? SecondsDesignator 1987 if (secondsDesignator()) { 1988 if (hasHoursFraction || hasMinutesFraction) { 1989 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_SECONDS); 1990 } 1991 result.seconds = num; 1992 result.secondsFraction = frac.valueOr(0); 1993 if (reader_.atEnd()) { 1994 return result; 1995 } 1996 } 1997 1998 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_UNIT_DESIGNATOR); 1999 } 2000 2001 /** 2002 * ParseTemporalDurationString ( isoString ) 2003 */ 2004 template <typename CharT> 2005 static auto ParseTemporalDurationString(JSContext* cx, 2006 mozilla::Span<const CharT> str) { 2007 TemporalParser<CharT> parser(str); 2008 return parser.parseTemporalDurationString(cx); 2009 } 2010 2011 /** 2012 * ParseTemporalDurationString ( isoString ) 2013 */ 2014 static auto ParseTemporalDurationString(JSContext* cx, 2015 Handle<JSLinearString*> str) { 2016 JS::AutoCheckCannotGC nogc; 2017 if (str->hasLatin1Chars()) { 2018 return ParseTemporalDurationString<Latin1Char>(cx, str->latin1Range(nogc)); 2019 } 2020 return ParseTemporalDurationString<char16_t>(cx, str->twoByteRange(nogc)); 2021 } 2022 2023 /** 2024 * ParseTemporalDurationString ( isoString ) 2025 */ 2026 bool js::temporal::ParseTemporalDurationString(JSContext* cx, 2027 Handle<JSString*> str, 2028 Duration* result) { 2029 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2030 if (!linear) { 2031 return false; 2032 } 2033 2034 // Steps 1-3. 2035 auto parseResult = ::ParseTemporalDurationString(cx, linear); 2036 if (parseResult.isErr()) { 2037 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2038 parseResult.unwrapErr(), "duration"); 2039 return false; 2040 } 2041 TemporalDurationString parsed = parseResult.unwrap(); 2042 2043 // Steps 4-8. 2044 double years = parsed.years; 2045 double months = parsed.months; 2046 double weeks = parsed.weeks; 2047 double days = parsed.days; 2048 double hours = parsed.hours; 2049 2050 // Steps 9-17. 2051 double minutes, seconds, milliseconds, microseconds, nanoseconds; 2052 if (parsed.hoursFraction) { 2053 MOZ_ASSERT(parsed.hoursFraction > 0); 2054 MOZ_ASSERT(parsed.hoursFraction < 1'000'000'000); 2055 2056 // Step 9.a. 2057 MOZ_ASSERT(parsed.minutes == 0); 2058 MOZ_ASSERT(parsed.minutesFraction == 0); 2059 MOZ_ASSERT(parsed.seconds == 0); 2060 MOZ_ASSERT(parsed.secondsFraction == 0); 2061 2062 // Steps 9.b-d. 2063 int64_t h = int64_t(parsed.hoursFraction) * 60; 2064 minutes = double(h / 1'000'000'000); 2065 2066 // Steps 13 and 15-17. 2067 int64_t min = (h % 1'000'000'000) * 60; 2068 seconds = double(min / 1'000'000'000); 2069 milliseconds = double((min % 1'000'000'000) / 1'000'000); 2070 microseconds = double((min % 1'000'000) / 1'000); 2071 nanoseconds = double(min % 1'000); 2072 } 2073 2074 // Step 11. 2075 else if (parsed.minutesFraction) { 2076 MOZ_ASSERT(parsed.minutesFraction > 0); 2077 MOZ_ASSERT(parsed.minutesFraction < 1'000'000'000); 2078 2079 // Step 11.a. 2080 MOZ_ASSERT(parsed.seconds == 0); 2081 MOZ_ASSERT(parsed.secondsFraction == 0); 2082 2083 // Step 10. 2084 minutes = parsed.minutes; 2085 2086 // Steps 11.b-d and 15-17. 2087 int64_t min = int64_t(parsed.minutesFraction) * 60; 2088 seconds = double(min / 1'000'000'000); 2089 milliseconds = double((min % 1'000'000'000) / 1'000'000); 2090 microseconds = double((min % 1'000'000) / 1'000); 2091 nanoseconds = double(min % 1'000); 2092 } 2093 2094 // Step 14. 2095 else if (parsed.secondsFraction) { 2096 MOZ_ASSERT(parsed.secondsFraction > 0); 2097 MOZ_ASSERT(parsed.secondsFraction < 1'000'000'000); 2098 2099 // Step 10. 2100 minutes = parsed.minutes; 2101 2102 // Step 12. 2103 seconds = parsed.seconds; 2104 2105 // Steps 14, 16-17 2106 milliseconds = double(parsed.secondsFraction / 1'000'000); 2107 microseconds = double((parsed.secondsFraction % 1'000'000) / 1'000); 2108 nanoseconds = double(parsed.secondsFraction % 1'000); 2109 } else { 2110 // Step 10. 2111 minutes = parsed.minutes; 2112 2113 // Step 12. 2114 seconds = parsed.seconds; 2115 2116 // Steps 15-17 2117 milliseconds = 0; 2118 microseconds = 0; 2119 nanoseconds = 0; 2120 } 2121 2122 // Steps 18-19. 2123 int32_t factor = parsed.sign ? parsed.sign : 1; 2124 MOZ_ASSERT(factor == -1 || factor == 1); 2125 2126 // Steps 20-29. 2127 *result = { 2128 (years * factor) + (+0.0), (months * factor) + (+0.0), 2129 (weeks * factor) + (+0.0), (days * factor) + (+0.0), 2130 (hours * factor) + (+0.0), (minutes * factor) + (+0.0), 2131 (seconds * factor) + (+0.0), (milliseconds * factor) + (+0.0), 2132 (microseconds * factor) + (+0.0), (nanoseconds * factor) + (+0.0), 2133 }; 2134 2135 // Steps 30-31. 2136 return ThrowIfInvalidDuration(cx, *result); 2137 } 2138 2139 template <typename CharT> 2140 mozilla::Result<AnnotationKey, ParserError> 2141 TemporalParser<CharT>::annotationKey() { 2142 // AnnotationKey ::: 2143 // AKeyLeadingChar 2144 // AnnotationKey AKeyChar 2145 2146 size_t start = reader_.index(); 2147 2148 if (!aKeyLeadingChar()) { 2149 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_KEY); 2150 } 2151 2152 // Optionally followed by a sequence of |AKeyChar|. 2153 while (aKeyChar()) { 2154 } 2155 2156 return AnnotationKey{start, reader_.index() - start}; 2157 } 2158 2159 template <typename CharT> 2160 mozilla::Result<AnnotationValue, ParserError> 2161 TemporalParser<CharT>::annotationValue() { 2162 // AnnotationValue ::: 2163 // AnnotationValueComponent 2164 // AnnotationValueComponent - AnnotationValue 2165 2166 size_t start = reader_.index(); 2167 2168 do { 2169 if (!annotationValueComponent()) { 2170 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_VALUE); 2171 } 2172 } while (character('-')); 2173 2174 return AnnotationValue{start, reader_.index() - start}; 2175 } 2176 2177 template <typename CharT> 2178 mozilla::Result<Annotation, ParserError> TemporalParser<CharT>::annotation() { 2179 // Annotation ::: 2180 // [ AnnotationCriticalFlag? AnnotationKey = AnnotationValue ] 2181 2182 if (!character('[')) { 2183 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_ANNOTATION); 2184 } 2185 2186 Annotation result{}; 2187 2188 result.critical = annotationCriticalFlag(); 2189 2190 result.key = MOZ_TRY(annotationKey()); 2191 2192 if (!character('=')) { 2193 return mozilla::Err(JSMSG_TEMPORAL_PARSER_ASSIGNMENT_IN_ANNOTATION); 2194 } 2195 2196 result.value = MOZ_TRY(annotationValue()); 2197 2198 if (!character(']')) { 2199 return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_ANNOTATION); 2200 } 2201 2202 return result; 2203 } 2204 2205 template <typename CharT> 2206 mozilla::Result<CalendarName, ParserError> 2207 TemporalParser<CharT>::annotations() { 2208 // Annotations ::: 2209 // Annotation Annotations? 2210 2211 MOZ_ASSERT(hasAnnotationStart()); 2212 2213 CalendarName calendar; 2214 bool calendarWasCritical = false; 2215 while (hasAnnotationStart()) { 2216 Annotation anno = MOZ_TRY(annotation()); 2217 2218 auto [key, value, critical] = anno; 2219 2220 static constexpr std::string_view ca = "u-ca"; 2221 2222 auto keySpan = reader_.substring(key); 2223 if (keySpan.size() == ca.length() && 2224 std::equal(ca.begin(), ca.end(), keySpan.data())) { 2225 if (!calendar.present()) { 2226 calendar = value; 2227 calendarWasCritical = critical; 2228 } else if (critical || calendarWasCritical) { 2229 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_CRITICAL_ANNOTATION); 2230 } 2231 } else if (critical) { 2232 return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_CRITICAL_ANNOTATION); 2233 } 2234 } 2235 return calendar; 2236 } 2237 2238 template <typename CharT> 2239 mozilla::Result<ZonedDateTimeString, ParserError> 2240 TemporalParser<CharT>::annotatedTime() { 2241 // clang-format off 2242 // 2243 // AnnotatedTime ::: 2244 // TimeDesignator Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations? 2245 // Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations? 2246 // 2247 // clang-format on 2248 2249 ZonedDateTimeString result{}; 2250 2251 size_t start = reader_.index(); 2252 bool hasTimeDesignator = timeDesignator(); 2253 2254 result.time = MOZ_TRY(time()); 2255 2256 if (hasDateTimeUTCOffsetStart()) { 2257 result.timeZone = MOZ_TRY(dateTimeUTCOffset(/* allowZ = */ false)); 2258 } 2259 2260 // Early error if `Time DateTimeUTCOffset[~Z]` can be parsed as either 2261 // `DateSpecMonthDay` or `DateSpecYearMonth`. 2262 if (!hasTimeDesignator) { 2263 size_t end = reader_.index(); 2264 2265 auto isValidMonthDay = [](const ISODate& date) { 2266 MOZ_ASSERT(date.year == AbsentYear); 2267 MOZ_ASSERT(1 <= date.month && date.month <= 12); 2268 MOZ_ASSERT(1 <= date.day && date.day <= 31); 2269 2270 constexpr int32_t leapYear = 0; 2271 return date.day <= ISODaysInMonth(leapYear, date.month); 2272 }; 2273 2274 // Reset and check if the input can also be parsed as DateSpecMonthDay. 2275 reader_.reset(start); 2276 2277 if (auto monthDay = dateSpecMonthDay(); monthDay.isOk()) { 2278 if (reader_.index() == end && isValidMonthDay(monthDay.unwrap())) { 2279 return mozilla::Err(JSMSG_TEMPORAL_PARSER_AMBIGUOUS_TIME_MONTH_DAY); 2280 } 2281 } 2282 2283 // Reset and check if the input can also be parsed as DateSpecYearMonth. 2284 reader_.reset(start); 2285 2286 if (dateSpecYearMonth().isOk()) { 2287 if (reader_.index() == end) { 2288 return mozilla::Err(JSMSG_TEMPORAL_PARSER_AMBIGUOUS_TIME_YEAR_MONTH); 2289 } 2290 } 2291 2292 // Input can neither be parsed as DateSpecMonthDay nor DateSpecYearMonth. 2293 reader_.reset(end); 2294 } 2295 2296 if (hasTimeZoneAnnotationStart()) { 2297 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 2298 } 2299 2300 if (hasAnnotationStart()) { 2301 result.calendar = MOZ_TRY(annotations()); 2302 } 2303 2304 return result; 2305 } 2306 2307 template <typename CharT> 2308 mozilla::Result<ZonedDateTimeString, ParserError> 2309 TemporalParser<CharT>::annotatedDateTime() { 2310 // AnnotatedDateTime[Zoned, TimeRequired] ::: 2311 // [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation? Annotations? 2312 // [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations? 2313 // 2314 // When called as `AnnotatedDateTime[~Zoned, ~TimeRequired]`. 2315 2316 ZonedDateTimeString result = MOZ_TRY(dateTime(/* allowZ = */ false)); 2317 2318 if (hasTimeZoneAnnotationStart()) { 2319 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 2320 } 2321 2322 if (hasAnnotationStart()) { 2323 result.calendar = MOZ_TRY(annotations()); 2324 } 2325 2326 return result; 2327 } 2328 2329 template <typename CharT> 2330 mozilla::Result<ZonedDateTimeString, ParserError> 2331 TemporalParser<CharT>::annotatedDateTimeTimeRequired() { 2332 // AnnotatedDateTime[Zoned, TimeRequired] ::: 2333 // [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation? Annotations? 2334 // [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations? 2335 // 2336 // DateTime[Z, TimeRequired] ::: 2337 // [~TimeRequired] Date 2338 // Date DateTimeSeparator Time DateTimeUTCOffset[?Z]? 2339 // 2340 // When called as `AnnotatedDateTime[~Zoned, +TimeRequired]`. 2341 2342 ZonedDateTimeString result{}; 2343 2344 result.date = MOZ_TRY(date()); 2345 2346 if (!dateTimeSeparator()) { 2347 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR); 2348 } 2349 2350 result.time = MOZ_TRY(time()); 2351 2352 if (hasDateTimeUTCOffsetStart()) { 2353 result.timeZone = MOZ_TRY(dateTimeUTCOffset(/* allowZ = */ false)); 2354 } 2355 2356 if (hasTimeZoneAnnotationStart()) { 2357 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 2358 } 2359 2360 if (hasAnnotationStart()) { 2361 result.calendar = MOZ_TRY(annotations()); 2362 } 2363 2364 return result; 2365 } 2366 2367 template <typename CharT> 2368 mozilla::Result<ZonedDateTimeString, ParserError> 2369 TemporalParser<CharT>::annotatedYearMonth() { 2370 // AnnotatedYearMonth ::: 2371 // DateSpecYearMonth TimeZoneAnnotation? Annotations? 2372 2373 ZonedDateTimeString result{}; 2374 2375 result.date = MOZ_TRY(dateSpecYearMonth()); 2376 2377 if (hasTimeZoneAnnotationStart()) { 2378 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 2379 } 2380 2381 if (hasAnnotationStart()) { 2382 result.calendar = MOZ_TRY(annotations()); 2383 } 2384 2385 return result; 2386 } 2387 2388 template <typename CharT> 2389 mozilla::Result<ZonedDateTimeString, ParserError> 2390 TemporalParser<CharT>::annotatedMonthDay() { 2391 // AnnotatedMonthDay ::: 2392 // DateSpecMonthDay TimeZoneAnnotation? Annotations? 2393 2394 ZonedDateTimeString result{}; 2395 2396 result.date = MOZ_TRY(dateSpecMonthDay()); 2397 2398 if (hasTimeZoneAnnotationStart()) { 2399 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 2400 } 2401 2402 if (hasAnnotationStart()) { 2403 result.calendar = MOZ_TRY(annotations()); 2404 } 2405 2406 return result; 2407 } 2408 2409 template <typename CharT> 2410 mozilla::Result<ISODate, ParserError> 2411 TemporalParser<CharT>::dateSpecYearMonth() { 2412 // DateSpecYearMonth ::: 2413 // DateYear DateSeparator[+Extended] DateMonth 2414 // DateYear DateSeparator[~Extended] DateMonth 2415 2416 ISODate result{}; 2417 2418 result.year = MOZ_TRY(dateYear()); 2419 2420 // Optional |DateSeparator|. 2421 dateSeparator(); 2422 2423 result.month = MOZ_TRY(dateMonth()); 2424 2425 return result; 2426 } 2427 2428 template <typename CharT> 2429 mozilla::Result<ISODate, ParserError> 2430 TemporalParser<CharT>::dateSpecMonthDay() { 2431 // DateSpecMonthDay ::: 2432 // --? DateMonth DateSeparator[+Extended] DateDay 2433 // --? DateMonth DateSeparator[~Extended] DateDay 2434 2435 ISODate result{}; 2436 2437 // Optional: -- 2438 string("--"); 2439 2440 result.year = AbsentYear; 2441 2442 result.month = MOZ_TRY(dateMonth()); 2443 2444 // Optional |DateSeparator|. 2445 dateSeparator(); 2446 2447 result.day = MOZ_TRY(dateDay()); 2448 2449 return result; 2450 } 2451 2452 template <typename CharT> 2453 mozilla::Result<ZonedDateTimeString, ParserError> 2454 TemporalParser<CharT>::parseTemporalCalendarString() { 2455 MOZ_TRY(nonempty()); 2456 2457 // Handle the common case of a standalone calendar name first. 2458 // 2459 // All valid calendar names start with two alphabetic characters and none of 2460 // the ParseISODateTime parse goals can start with two alphabetic characters. 2461 // TemporalTimeString can start with 'T', so we can't only check the first 2462 // character. 2463 if (hasTwoAsciiAlpha()) { 2464 ZonedDateTimeString result{}; 2465 2466 result.calendar = MOZ_TRY(parse(annotationValue())); 2467 2468 return result; 2469 } 2470 2471 LikelyError likelyError{}; 2472 2473 // Try all five parse goals from ParseISODateTime in order. 2474 // 2475 // TemporalDateTimeString 2476 // TemporalInstantString 2477 // TemporalTimeString 2478 // TemporalMonthDayString 2479 // TemporalYearMonthString 2480 2481 auto dateTime = parseTemporalDateTimeString(); 2482 if (dateTime.isOk()) { 2483 return dateTime; 2484 } 2485 likelyError.update(dateTime, reader_.index()); 2486 2487 // Restart parsing from the start of the string. 2488 reader_.reset(); 2489 2490 auto instant = parseTemporalInstantString(); 2491 if (instant.isOk()) { 2492 return instant; 2493 } 2494 likelyError.update(instant, reader_.index()); 2495 2496 // Restart parsing from the start of the string. 2497 reader_.reset(); 2498 2499 auto time = parseTemporalTimeString(); 2500 if (time.isOk()) { 2501 return time; 2502 } 2503 likelyError.update(time, reader_.index()); 2504 2505 // Restart parsing from the start of the string. 2506 reader_.reset(); 2507 2508 auto monthDay = parseTemporalMonthDayString(); 2509 if (monthDay.isOk()) { 2510 return monthDay; 2511 } 2512 likelyError.update(monthDay, reader_.index()); 2513 2514 // Restart parsing from the start of the string. 2515 reader_.reset(); 2516 2517 auto yearMonth = parseTemporalYearMonthString(); 2518 if (yearMonth.isOk()) { 2519 return yearMonth; 2520 } 2521 likelyError.update(yearMonth, reader_.index()); 2522 2523 return likelyError.propagate(); 2524 } 2525 2526 /** 2527 * ParseTemporalCalendarString ( isoString ) 2528 */ 2529 template <typename CharT> 2530 static auto ParseTemporalCalendarString(mozilla::Span<const CharT> str) { 2531 TemporalParser<CharT> parser(str); 2532 return parser.parseTemporalCalendarString(); 2533 } 2534 2535 /** 2536 * ParseTemporalCalendarString ( isoString ) 2537 */ 2538 static auto ParseTemporalCalendarString(Handle<JSLinearString*> str) { 2539 JS::AutoCheckCannotGC nogc; 2540 if (str->hasLatin1Chars()) { 2541 return ParseTemporalCalendarString<Latin1Char>(str->latin1Range(nogc)); 2542 } 2543 return ParseTemporalCalendarString<char16_t>(str->twoByteRange(nogc)); 2544 } 2545 2546 /** 2547 * ParseTemporalCalendarString ( isoString ) 2548 */ 2549 JSLinearString* js::temporal::ParseTemporalCalendarString( 2550 JSContext* cx, Handle<JSString*> str) { 2551 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2552 if (!linear) { 2553 return nullptr; 2554 } 2555 2556 // Steps 1 and 3.a. 2557 auto parseResult = ::ParseTemporalCalendarString(linear); 2558 if (parseResult.isErr()) { 2559 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2560 parseResult.unwrapErr(), "calendar"); 2561 return nullptr; 2562 } 2563 ZonedDateTimeString parsed = parseResult.unwrap(); 2564 2565 ISODateTime unused; 2566 if (!ParseISODateTime(cx, parsed, &unused)) { 2567 return nullptr; 2568 } 2569 2570 // Step 2.b. 2571 if (!parsed.calendar.present()) { 2572 return cx->names().iso8601; 2573 } 2574 2575 // Steps 2.c and 3.c 2576 return ToString(cx, linear, parsed.calendar); 2577 } 2578 2579 template <typename CharT> 2580 mozilla::Result<ZonedDateTimeString, ParserError> 2581 TemporalParser<CharT>::parseTemporalTimeString() { 2582 MOZ_TRY(nonempty()); 2583 2584 // TemporalTimeString ::: 2585 // AnnotatedTime 2586 // AnnotatedDateTime[~Zoned, +TimeRequired] 2587 2588 LikelyError likelyError{}; 2589 2590 auto time = parse(annotatedTime()); 2591 if (time.isOk()) { 2592 return time; 2593 } 2594 likelyError.update(time, reader_.index()); 2595 2596 // Reset and try the next option. 2597 reader_.reset(); 2598 2599 auto dateTime = parse(annotatedDateTimeTimeRequired()); 2600 if (dateTime.isOk()) { 2601 return dateTime; 2602 } 2603 likelyError.update(time, reader_.index()); 2604 2605 // Set current index to the likely error index to give better error messages 2606 // when called from parserTemporal{Calendar,TimeZone}String. 2607 reader_.reset(likelyError.index()); 2608 2609 return likelyError.propagate(); 2610 } 2611 2612 /** 2613 * ParseTemporalTimeString ( isoString ) 2614 */ 2615 template <typename CharT> 2616 static auto ParseTemporalTimeString(mozilla::Span<const CharT> str) { 2617 TemporalParser<CharT> parser(str); 2618 return parser.parseTemporalTimeString(); 2619 } 2620 2621 /** 2622 * ParseTemporalTimeString ( isoString ) 2623 */ 2624 static auto ParseTemporalTimeString(Handle<JSLinearString*> str) { 2625 JS::AutoCheckCannotGC nogc; 2626 if (str->hasLatin1Chars()) { 2627 return ParseTemporalTimeString<Latin1Char>(str->latin1Range(nogc)); 2628 } 2629 return ParseTemporalTimeString<char16_t>(str->twoByteRange(nogc)); 2630 } 2631 2632 /** 2633 * ParseTemporalTimeString ( isoString ) 2634 */ 2635 bool js::temporal::ParseTemporalTimeString(JSContext* cx, Handle<JSString*> str, 2636 Time* result) { 2637 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2638 if (!linear) { 2639 return false; 2640 } 2641 2642 // Steps 1-2. 2643 auto parseResult = ::ParseTemporalTimeString(linear); 2644 if (parseResult.isErr()) { 2645 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2646 parseResult.unwrapErr(), "time"); 2647 return false; 2648 } 2649 ZonedDateTimeString parsed = parseResult.unwrap(); 2650 2651 // Step 3. 2652 ISODateTime dateTime; 2653 if (!ParseISODateTime(cx, parsed, &dateTime)) { 2654 return false; 2655 } 2656 *result = dateTime.time; 2657 2658 // Step 4. 2659 MOZ_ASSERT(!parsed.startOfDay); 2660 2661 // Step 5. 2662 return true; 2663 } 2664 2665 template <typename CharT> 2666 mozilla::Result<ZonedDateTimeString, ParserError> 2667 TemporalParser<CharT>::parseTemporalMonthDayString() { 2668 MOZ_TRY(nonempty()); 2669 2670 // TemporalMonthDayString ::: 2671 // AnnotatedMonthDay 2672 // AnnotatedDateTime[~Zoned, ~TimeRequired] 2673 2674 LikelyError likelyError{}; 2675 2676 auto monthDay = parse(annotatedMonthDay()); 2677 if (monthDay.isOk()) { 2678 auto result = monthDay.unwrap(); 2679 2680 // ParseISODateTime, step 3. 2681 if (result.calendar.present() && 2682 !IsISO8601Calendar(reader_.substring(result.calendar))) { 2683 return mozilla::Err(JSMSG_TEMPORAL_PARSER_MONTH_DAY_CALENDAR_NOT_ISO8601); 2684 } 2685 return result; 2686 } 2687 likelyError.update(monthDay, reader_.index()); 2688 2689 // Reset and try the next option. 2690 reader_.reset(); 2691 2692 auto dateTime = parse(annotatedDateTime()); 2693 if (dateTime.isOk()) { 2694 return dateTime; 2695 } 2696 likelyError.update(dateTime, reader_.index()); 2697 2698 // Set current index to the likely error index to give better error messages 2699 // when called from parserTemporal{Calendar,TimeZone}String. 2700 reader_.reset(likelyError.index()); 2701 2702 return likelyError.propagate(); 2703 } 2704 2705 /** 2706 * ParseTemporalMonthDayString ( isoString ) 2707 */ 2708 template <typename CharT> 2709 static auto ParseTemporalMonthDayString(mozilla::Span<const CharT> str) { 2710 TemporalParser<CharT> parser(str); 2711 return parser.parseTemporalMonthDayString(); 2712 } 2713 2714 /** 2715 * ParseTemporalMonthDayString ( isoString ) 2716 */ 2717 static auto ParseTemporalMonthDayString(Handle<JSLinearString*> str) { 2718 JS::AutoCheckCannotGC nogc; 2719 if (str->hasLatin1Chars()) { 2720 return ParseTemporalMonthDayString<Latin1Char>(str->latin1Range(nogc)); 2721 } 2722 return ParseTemporalMonthDayString<char16_t>(str->twoByteRange(nogc)); 2723 } 2724 2725 /** 2726 * ParseTemporalMonthDayString ( isoString ) 2727 */ 2728 bool js::temporal::ParseTemporalMonthDayString( 2729 JSContext* cx, Handle<JSString*> str, ISODate* result, bool* hasYear, 2730 MutableHandle<JSString*> calendar) { 2731 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2732 if (!linear) { 2733 return false; 2734 } 2735 2736 // Steps 1-2. 2737 auto parseResult = ::ParseTemporalMonthDayString(linear); 2738 if (parseResult.isErr()) { 2739 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2740 parseResult.unwrapErr(), "month-day"); 2741 return false; 2742 } 2743 ZonedDateTimeString parsed = parseResult.unwrap(); 2744 2745 // Step 3. 2746 ISODateTime dateTime; 2747 if (!ParseISODateTime(cx, parsed, &dateTime)) { 2748 return false; 2749 } 2750 *result = dateTime.date; 2751 2752 // Steps 4-5. 2753 *hasYear = parsed.date.year != AbsentYear; 2754 2755 if (parsed.calendar.present()) { 2756 calendar.set(ToString(cx, linear, parsed.calendar)); 2757 if (!calendar) { 2758 return false; 2759 } 2760 } 2761 2762 // Step 6. 2763 return true; 2764 } 2765 2766 template <typename CharT> 2767 mozilla::Result<ZonedDateTimeString, ParserError> 2768 TemporalParser<CharT>::parseTemporalYearMonthString() { 2769 MOZ_TRY(nonempty()); 2770 2771 // TemporalYearMonthString ::: 2772 // AnnotatedYearMonth 2773 // AnnotatedDateTime[~Zoned, ~TimeRequired] 2774 2775 LikelyError likelyError{}; 2776 2777 auto yearMonth = parse(annotatedYearMonth()); 2778 if (yearMonth.isOk()) { 2779 auto result = yearMonth.unwrap(); 2780 2781 // ParseISODateTime, step 3. 2782 if (result.calendar.present() && 2783 !IsISO8601Calendar(reader_.substring(result.calendar))) { 2784 return mozilla::Err( 2785 JSMSG_TEMPORAL_PARSER_YEAR_MONTH_CALENDAR_NOT_ISO8601); 2786 } 2787 return result; 2788 } 2789 likelyError.update(yearMonth, reader_.index()); 2790 2791 // Reset and try the next option. 2792 reader_.reset(); 2793 2794 auto dateTime = parse(annotatedDateTime()); 2795 if (dateTime.isOk()) { 2796 return dateTime; 2797 } 2798 likelyError.update(dateTime, reader_.index()); 2799 2800 // Set current index to the likely error index to give better error messages 2801 // when called from parserTemporal{Calendar,TimeZone}String. 2802 reader_.reset(likelyError.index()); 2803 2804 return likelyError.propagate(); 2805 } 2806 2807 /** 2808 * ParseTemporalYearMonthString ( isoString ) 2809 */ 2810 template <typename CharT> 2811 static auto ParseTemporalYearMonthString(mozilla::Span<const CharT> str) { 2812 TemporalParser<CharT> parser(str); 2813 return parser.parseTemporalYearMonthString(); 2814 } 2815 2816 /** 2817 * ParseTemporalYearMonthString ( isoString ) 2818 */ 2819 static auto ParseTemporalYearMonthString(Handle<JSLinearString*> str) { 2820 JS::AutoCheckCannotGC nogc; 2821 if (str->hasLatin1Chars()) { 2822 return ParseTemporalYearMonthString<Latin1Char>(str->latin1Range(nogc)); 2823 } 2824 return ParseTemporalYearMonthString<char16_t>(str->twoByteRange(nogc)); 2825 } 2826 2827 /** 2828 * ParseTemporalYearMonthString ( isoString ) 2829 */ 2830 bool js::temporal::ParseTemporalYearMonthString( 2831 JSContext* cx, Handle<JSString*> str, ISODate* result, 2832 MutableHandle<JSString*> calendar) { 2833 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2834 if (!linear) { 2835 return false; 2836 } 2837 2838 // Steps 1-2. 2839 auto parseResult = ::ParseTemporalYearMonthString(linear); 2840 if (parseResult.isErr()) { 2841 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2842 parseResult.unwrapErr(), "year-month"); 2843 return false; 2844 } 2845 ZonedDateTimeString parsed = parseResult.unwrap(); 2846 2847 // Step 3. 2848 ISODateTime dateTime; 2849 if (!ParseISODateTime(cx, parsed, &dateTime)) { 2850 return false; 2851 } 2852 *result = dateTime.date; 2853 2854 if (parsed.calendar.present()) { 2855 calendar.set(ToString(cx, linear, parsed.calendar)); 2856 if (!calendar) { 2857 return false; 2858 } 2859 } 2860 2861 return true; 2862 } 2863 2864 template <typename CharT> 2865 mozilla::Result<ZonedDateTimeString, ParserError> 2866 TemporalParser<CharT>::parseTemporalDateTimeString() { 2867 MOZ_TRY(nonempty()); 2868 2869 // TemporalDateTimeString[Zoned] ::: 2870 // AnnotatedDateTime[?Zoned, ~TimeRequired] 2871 2872 return parse(annotatedDateTime()); 2873 } 2874 2875 /** 2876 * ParseTemporalDateTimeString ( isoString ) 2877 */ 2878 template <typename CharT> 2879 static auto ParseTemporalDateTimeString(mozilla::Span<const CharT> str) { 2880 TemporalParser<CharT> parser(str); 2881 return parser.parseTemporalDateTimeString(); 2882 } 2883 2884 /** 2885 * ParseTemporalDateTimeString ( isoString ) 2886 */ 2887 static auto ParseTemporalDateTimeString(Handle<JSLinearString*> str) { 2888 JS::AutoCheckCannotGC nogc; 2889 if (str->hasLatin1Chars()) { 2890 return ParseTemporalDateTimeString<Latin1Char>(str->latin1Range(nogc)); 2891 } 2892 return ParseTemporalDateTimeString<char16_t>(str->twoByteRange(nogc)); 2893 } 2894 2895 /** 2896 * ParseTemporalDateTimeString ( isoString ) 2897 */ 2898 bool js::temporal::ParseTemporalDateTimeString( 2899 JSContext* cx, Handle<JSString*> str, ISODateTime* result, 2900 MutableHandle<JSString*> calendar) { 2901 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2902 if (!linear) { 2903 return false; 2904 } 2905 2906 // Steps 1-2. 2907 auto parseResult = ::ParseTemporalDateTimeString(linear); 2908 if (parseResult.isErr()) { 2909 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2910 parseResult.unwrapErr(), "date-time"); 2911 return false; 2912 } 2913 ZonedDateTimeString parsed = parseResult.unwrap(); 2914 2915 // Step 3. 2916 if (!ParseISODateTime(cx, parsed, result)) { 2917 return false; 2918 } 2919 2920 if (parsed.calendar.present()) { 2921 calendar.set(ToString(cx, linear, parsed.calendar)); 2922 if (!calendar) { 2923 return false; 2924 } 2925 } 2926 2927 return true; 2928 } 2929 2930 template <typename CharT> 2931 mozilla::Result<ZonedDateTimeString, ParserError> 2932 TemporalParser<CharT>::parseTemporalZonedDateTimeString() { 2933 MOZ_TRY(nonempty()); 2934 2935 // Parse goal: TemporalDateTimeString[+Zoned] 2936 // 2937 // TemporalDateTimeString[Zoned] ::: 2938 // AnnotatedDateTime[?Zoned, ~TimeRequired] 2939 // 2940 // AnnotatedDateTime[Zoned, TimeRequired] ::: 2941 // [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation? Annotations? 2942 // [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations? 2943 2944 ZonedDateTimeString result{}; 2945 2946 result = MOZ_TRY(dateTime(/* allowZ = */ true)); 2947 2948 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 2949 2950 if (hasAnnotationStart()) { 2951 result.calendar = MOZ_TRY(annotations()); 2952 } 2953 2954 return complete(result); 2955 } 2956 2957 /** 2958 * ParseTemporalZonedDateTimeString ( isoString ) 2959 */ 2960 template <typename CharT> 2961 static auto ParseTemporalZonedDateTimeString(mozilla::Span<const CharT> str) { 2962 TemporalParser<CharT> parser(str); 2963 return parser.parseTemporalZonedDateTimeString(); 2964 } 2965 2966 /** 2967 * ParseTemporalZonedDateTimeString ( isoString ) 2968 */ 2969 static auto ParseTemporalZonedDateTimeString(Handle<JSLinearString*> str) { 2970 JS::AutoCheckCannotGC nogc; 2971 if (str->hasLatin1Chars()) { 2972 return ParseTemporalZonedDateTimeString<Latin1Char>(str->latin1Range(nogc)); 2973 } 2974 return ParseTemporalZonedDateTimeString<char16_t>(str->twoByteRange(nogc)); 2975 } 2976 2977 /** 2978 * ParseTemporalZonedDateTimeString ( isoString ) 2979 */ 2980 bool js::temporal::ParseTemporalZonedDateTimeString( 2981 JSContext* cx, Handle<JSString*> str, 2982 JS::MutableHandle<ParsedZonedDateTime> result) { 2983 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 2984 if (!linear) { 2985 return false; 2986 } 2987 2988 // Step 1. 2989 auto parseResult = ::ParseTemporalZonedDateTimeString(linear); 2990 if (parseResult.isErr()) { 2991 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2992 parseResult.unwrapErr(), "zoned date-time"); 2993 return false; 2994 } 2995 ZonedDateTimeString parsed = parseResult.unwrap(); 2996 2997 // Step 2. (ParseISODateTime, steps 2-3.) 2998 Rooted<JSLinearString*> calendar(cx); 2999 if (parsed.calendar.present()) { 3000 calendar = ToString(cx, linear, parsed.calendar); 3001 if (!calendar) { 3002 return false; 3003 } 3004 } 3005 3006 // Step 2. (ParseISODateTime, steps 4-21.) 3007 ISODateTime dateTime; 3008 if (!ParseISODateTime(cx, parsed, &dateTime)) { 3009 return false; 3010 } 3011 3012 // Step 2. (ParseISODateTime, steps 22-23.) 3013 bool isStartOfDay = parsed.startOfDay; 3014 3015 // Step 2. (ParseISODateTime, steps 24-27.) 3016 Rooted<ParsedTimeZone> timeZoneAnnotation(cx); 3017 mozilla::MaybeOneOf<UTCTimeZone, OffsetTimeZone> timeZone; 3018 { 3019 MOZ_ASSERT(parsed.timeZone.hasAnnotation()); 3020 3021 // Case 1: 19700101T00:00Z[+02:00] 3022 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" } 3023 // 3024 // Case 2: 19700101T00:00+02:00[+02:00] 3025 // { [[Z]]: false, [[OffsetString]]: "+02:00", [[Name]]: "+02:00" } 3026 // 3027 // Case 3: 19700101[+02:00] 3028 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" } 3029 // 3030 // Case 4: 19700101T00:00Z[Europe/Berlin] 3031 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" } 3032 // 3033 // Case 5: 19700101T00:00+01:00[Europe/Berlin] 3034 // { [[Z]]: false, [[OffsetString]]: "+01:00", [[Name]]: "Europe/Berlin" } 3035 // 3036 // Case 6: 19700101[Europe/Berlin] 3037 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" } 3038 3039 const auto& annotation = parsed.timeZone.annotation; 3040 if (!ParseTimeZoneAnnotation(cx, annotation, linear, &timeZoneAnnotation)) { 3041 return false; 3042 } 3043 3044 if (parsed.timeZone.isUTC()) { 3045 timeZone.construct<UTCTimeZone>(); 3046 } else if (parsed.timeZone.hasOffset()) { 3047 timeZone.construct<OffsetTimeZone>( 3048 ParseDateTimeUTCOffset(parsed.timeZone.offset)); 3049 } 3050 } 3051 3052 // Step 2. (ParseISODateTime, step 28.) 3053 result.set(ParsedZonedDateTime{ 3054 dateTime, 3055 calendar, 3056 timeZoneAnnotation.get(), 3057 std::move(timeZone), 3058 isStartOfDay, 3059 }); 3060 return true; 3061 } 3062 3063 template <typename CharT> 3064 mozilla::Result<ZonedDateTimeString, ParserError> 3065 TemporalParser<CharT>::parseTemporalRelativeToString() { 3066 MOZ_TRY(nonempty()); 3067 3068 // Parse goals: 3069 // TemporalDateTimeString[+Zoned] and TemporalDateTimeString[~Zoned] 3070 // 3071 // TemporalDateTimeString[Zoned] ::: 3072 // AnnotatedDateTime[?Zoned, ~TimeRequired] 3073 // 3074 // AnnotatedDateTime[Zoned, TimeRequired] ::: 3075 // [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation? Annotations? 3076 // [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations? 3077 3078 ZonedDateTimeString result{}; 3079 3080 result = MOZ_TRY(dateTime(/* allowZ = */ true)); 3081 3082 if (hasTimeZoneAnnotationStart()) { 3083 result.timeZone.annotation = MOZ_TRY(timeZoneAnnotation()); 3084 } 3085 3086 if (hasAnnotationStart()) { 3087 result.calendar = MOZ_TRY(annotations()); 3088 } 3089 3090 return complete(result); 3091 } 3092 3093 /** 3094 * ParseTemporalRelativeToString ( isoString ) 3095 */ 3096 template <typename CharT> 3097 static auto ParseTemporalRelativeToString(mozilla::Span<const CharT> str) { 3098 TemporalParser<CharT> parser(str); 3099 return parser.parseTemporalRelativeToString(); 3100 } 3101 3102 /** 3103 * ParseTemporalRelativeToString ( isoString ) 3104 */ 3105 static auto ParseTemporalRelativeToString(Handle<JSLinearString*> str) { 3106 JS::AutoCheckCannotGC nogc; 3107 if (str->hasLatin1Chars()) { 3108 return ParseTemporalRelativeToString<Latin1Char>(str->latin1Range(nogc)); 3109 } 3110 return ParseTemporalRelativeToString<char16_t>(str->twoByteRange(nogc)); 3111 } 3112 3113 /** 3114 * ParseTemporalRelativeToString ( isoString ) 3115 */ 3116 bool js::temporal::ParseTemporalRelativeToString( 3117 JSContext* cx, Handle<JSString*> str, 3118 MutableHandle<ParsedZonedDateTime> result) { 3119 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); 3120 if (!linear) { 3121 return false; 3122 } 3123 3124 // Steps 1-2. 3125 auto parseResult = ::ParseTemporalRelativeToString(linear); 3126 if (parseResult.isErr()) { 3127 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3128 parseResult.unwrapErr(), "relative date-time"); 3129 return false; 3130 } 3131 ZonedDateTimeString parsed = parseResult.unwrap(); 3132 3133 // Step 3. 3134 if (parsed.timeZone.isUTC() && !parsed.timeZone.hasAnnotation()) { 3135 JS_ReportErrorNumberASCII( 3136 cx, GetErrorMessage, nullptr, 3137 JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR_WITHOUT_NAME, 3138 "relative date-time"); 3139 return false; 3140 } 3141 3142 // Step 4. (ParseISODateTime, steps 1-18.) 3143 ISODateTime dateTime; 3144 if (!ParseISODateTime(cx, parsed, &dateTime)) { 3145 return false; 3146 } 3147 bool isStartOfDay = parsed.startOfDay; 3148 3149 // Step 4. (ParseISODateTime, steps 19-22.) 3150 Rooted<ParsedTimeZone> timeZoneAnnotation(cx); 3151 mozilla::MaybeOneOf<UTCTimeZone, OffsetTimeZone> timeZone; 3152 if (parsed.timeZone.hasAnnotation()) { 3153 // Case 1: 19700101Z[+02:00] 3154 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" } 3155 // 3156 // Case 2: 19700101+00:00[+02:00] 3157 // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" } 3158 // 3159 // Case 3: 19700101[+02:00] 3160 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" } 3161 // 3162 // Case 4: 19700101Z[Europe/Berlin] 3163 // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" } 3164 // 3165 // Case 5: 19700101+00:00[Europe/Berlin] 3166 // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" } 3167 // 3168 // Case 6: 19700101[Europe/Berlin] 3169 // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" } 3170 3171 const auto& annotation = parsed.timeZone.annotation; 3172 if (!ParseTimeZoneAnnotation(cx, annotation, linear, &timeZoneAnnotation)) { 3173 return false; 3174 } 3175 3176 if (parsed.timeZone.isUTC()) { 3177 timeZone.construct<UTCTimeZone>(); 3178 } else if (parsed.timeZone.hasOffset()) { 3179 timeZone.construct<OffsetTimeZone>( 3180 ParseDateTimeUTCOffset(parsed.timeZone.offset)); 3181 } 3182 } else { 3183 // GetTemporalRelativeToOption ignores any other time zone information when 3184 // no bracketed time zone annotation is present. 3185 timeZoneAnnotation.set(ParsedTimeZone{}); 3186 } 3187 3188 // Step 4. (ParseISODateTime, steps 23-24.) 3189 JSLinearString* calendar = nullptr; 3190 if (parsed.calendar.present()) { 3191 calendar = ToString(cx, linear, parsed.calendar); 3192 if (!calendar) { 3193 return false; 3194 } 3195 } 3196 3197 // Step 4. (Return) 3198 result.set(ParsedZonedDateTime{ 3199 dateTime, 3200 calendar, 3201 timeZoneAnnotation.get(), 3202 std::move(timeZone), 3203 isStartOfDay, 3204 }); 3205 return true; 3206 } 3207 3208 void js::temporal::ParsedTimeZone::trace(JSTracer* trc) { 3209 TraceNullableRoot(trc, &name, "ParsedTimeZone::name"); 3210 } 3211 3212 void js::temporal::ParsedZonedDateTime::trace(JSTracer* trc) { 3213 TraceNullableRoot(trc, &calendar, "ParsedZonedDateTime::calendar"); 3214 timeZoneAnnotation.trace(trc); 3215 }