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));
}