static double parseESDate()

in lib/VM/JSLib/DateUtil.cpp [869:1096]


static double parseESDate(StringView str) {
  constexpr double nan = std::numeric_limits<double>::quiet_NaN();
  StringView tok = str;

  // Initialize these fields to their defaults.
  int32_t y, m{1}, d{1}, h{0}, min{0}, s{0}, ms{0}, tzh{0}, tzm{0};
  double sign = 1;

  // Example strings to parse:
  // Mon Jul 15 2019 14:33:22 GMT-0700 (PDT)
  // Mon, 15 Jul 2019 14:33:22 GMT
  // The comma, time zone adjustment, and description are optional,

  // Current index we are parsing.
  auto it = str.begin();
  auto end = str.end();

  /// Read a string starting at `it` into `tok`.
  /// \p len the number of characters to scan in the string.
  /// \return true if successful, false if failed.
  auto scanStr = [&str, &tok, &it](int32_t len) -> bool {
    if (it + len > str.end()) {
      return false;
    }
    tok = str.slice(it, it + len);
    it += len;
    return true;
  };

  /// Reads the next \p len characters into `tok`,
  /// but instead of consuming \p len chars, it consumes a single word
  /// whatever how long it is (i.e. until a space is encountered).
  /// e.g.
  ///     &str ="Garbage G MayG"
  ///     scanStrAndSkipWord(3); consumeSpaces();  // &str="G MayG", &tok="Gar"
  ///     scanStrAndSkipWord(3); consumeSpaces();  // &str="MayG"  , &tok="G M"
  ///     scanStrAndSkipWord(3); consumeSpaces();  // &str=""      , &tok="May"
  ///     scanStrAndSkipWord(3);                   // -> false
  /// \return true if successful, false if failed.
  auto scanStrAndSkipWord = [&str, &tok, &it](int32_t len) -> bool {
    if (it + len > str.end())
      return false;
    tok = str.slice(it, it + len);
    while (it != str.end() && !std::isspace(*it))
      it++;
    return true;
  };

  auto consume = [&](char16_t ch) {
    if (it != str.end() && *it == ch) {
      ++it;
      return true;
    }
    return false;
  };

  auto consumeSpaces = [&]() {
    while (it != str.end() && std::isspace(*it))
      ++it;
  };

  // Weekday
  if (!scanStr(3))
    return nan;
  bool foundWeekday = false;
  for (const char *name : weekdayNames) {
    if (tok.equals(llvh::arrayRefFromStringRef(name))) {
      foundWeekday = true;
      break;
    }
  }
  if (!foundWeekday)
    return nan;

  /// If we found a valid Month string from the current `tok`.
  auto tokIsMonth = [&]() -> bool {
    for (uint32_t i = 0; i < sizeof(monthNames) / sizeof(monthNames[0]); ++i) {
      if (tok.equals(llvh::arrayRefFromStringRef(monthNames[i]))) {
        // m is 1-indexed.
        m = i + 1;
        return true;
      }
    }
    return false;
  };

  // Day Month Year
  // or
  // Month Day Year
  while (it != str.end()) {
    if (isDigit(*it)) {
      // Day
      scanInt(it, end, d);
      // Month
      consumeSpaces();
      // e.g. `Janwhatever` will get read as `Jan`
      if (!scanStrAndSkipWord(3))
        return nan;
      // `tok` is now set to the Month candidate.
      if (!tokIsMonth())
        return nan;
      break;
    }
    if (isAlpha(*it)) {
      // try Month
      if (!scanStrAndSkipWord(3))
        return nan;
      // `tok` is now set to the Month candidate.
      if (tokIsMonth()) {
        // Day
        consumeSpaces();
        if (!scanInt(it, end, d))
          return nan;
        break;
      }
      // Continue scanning for Month.
      continue;
    }
    // Ignore any garbage.
    ++it;
  }

  // Year
  consumeSpaces();
  if (!scanInt(it, end, y))
    return nan;

  // Hour:minute:second.
  consumeSpaces();

  if (it != end) {
    if (!scanInt(it, end, h))
      return nan;
    if (!consume(':'))
      return nan;
    if (!scanInt(it, end, min))
      return nan;
    if (!consume(':'))
      return nan;
    if (!scanInt(it, end, s))
      return nan;
  }

  // Space and time zone.
  consumeSpaces();

  if (it == end) {
    // Default to local time zone if no time zone provided
    double t = makeDate(makeDay(y, m - 1, d), makeTime(h, min, s, ms));
    t = utcTime(t);
    return t;
  }

  struct KnownTZ {
    const char *tz;
    int32_t tzh;
  };

  // Known time zones per RFC 2822.
  // All other obsolete time zones that aren't in this array treated as +00:00.
  static constexpr KnownTZ knownTZs[] = {
      {"GMT", 0},
      {"EDT", -4},
      {"EST", -5},
      {"CDT", -5},
      {"CST", -6},
      {"MDT", -6},
      {"MST", -7},
      {"PDT", -7},
      {"PST", -8},
  };

  // TZ name is optional, but if there is a letter, it is the only option.
  if ('A' <= *it && *it <= 'Z') {
    if (!scanStr(3))
      return nan;
    for (const KnownTZ &knownTZ : knownTZs) {
      if (tok.equals(llvh::arrayRefFromStringRef(knownTZ.tz))) {
        tzh = knownTZ.tzh;
        break;
      }
    }
  }

  if (it == end)
    goto complete;

  // Prevent "CDT+0700", for example.
  if (tzh != 0 && it != end)
    return nan;

  // Sign of the timezone adjustment.
  if (consume('+'))
    sign = 1;
  else if (consume('-'))
    sign = -1;
  else
    return nan;

  // Hour and minute of timezone adjustment.
  if (it > end - 4)
    return nan;
  if (!scanInt(it, it + 2, tzh))
    return nan;
  tzh *= sign;
  if (!scanInt(it, it + 2, tzm))
    return nan;
  tzm *= sign;

  if (it != end) {
    // Optional parenthesized description of timezone (must be at the end).
    if (!consume(' '))
      return nan;
    if (!consume('('))
      return nan;
    while (it != end && *it != ')')
      ++it;
    if (!consume(')'))
      return nan;
  }

  if (it != end)
    return nan;

complete:
  // Account for the fact that m was 1-indexed and the timezone offset.
  return makeDate(makeDay(y, m - 1, d), makeTime(h - tzh, min - tzm, s, ms));
}