mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 09:47:35 +00:00
LibJS: Make parse_simplified_iso8601() use Optional<int> instead of -1
...for the non-success state. This fixes a crash when parsing dates with the year -1, as we would assert successful parsing ("year != -1"). Mixing Optional and -1 seems worse and more complicated than just using Optional for all the values, so I did that instead.
This commit is contained in:
parent
39cdffd78d
commit
a647f0abf6
2 changed files with 36 additions and 29 deletions
|
@ -25,7 +25,7 @@ static Value parse_simplified_iso8601(const String& iso_8601)
|
||||||
// Date.parse() is allowed to accept many formats. We strictly only accept things matching
|
// Date.parse() is allowed to accept many formats. We strictly only accept things matching
|
||||||
// 21.4.1.15 Date Time String Format, https://tc39.es/ecma262/#sec-date-time-string-format
|
// 21.4.1.15 Date Time String Format, https://tc39.es/ecma262/#sec-date-time-string-format
|
||||||
GenericLexer lexer(iso_8601);
|
GenericLexer lexer(iso_8601);
|
||||||
auto lex_n_digits = [&](size_t n, int& out) {
|
auto lex_n_digits = [&](size_t n, Optional<int>& out) {
|
||||||
if (lexer.tell_remaining() < n)
|
if (lexer.tell_remaining() < n)
|
||||||
return false;
|
return false;
|
||||||
int r = 0;
|
int r = 0;
|
||||||
|
@ -39,36 +39,44 @@ static Value parse_simplified_iso8601(const String& iso_8601)
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
int year = -1, month = -1, day = -1;
|
Optional<int> year;
|
||||||
int hours = -1, minutes = -1, seconds = -1, milliseconds = -1;
|
Optional<int> month;
|
||||||
int timezone = -1;
|
Optional<int> day;
|
||||||
int timezone_hours = -1, timezone_minutes = -1;
|
Optional<int> hours;
|
||||||
|
Optional<int> minutes;
|
||||||
|
Optional<int> seconds;
|
||||||
|
Optional<int> milliseconds;
|
||||||
|
Optional<char> timezone;
|
||||||
|
Optional<int> timezone_hours;
|
||||||
|
Optional<int> timezone_minutes;
|
||||||
|
|
||||||
auto lex_year = [&]() {
|
auto lex_year = [&]() {
|
||||||
if (lexer.consume_specific('+'))
|
if (lexer.consume_specific('+'))
|
||||||
return lex_n_digits(6, year);
|
return lex_n_digits(6, year);
|
||||||
if (lexer.consume_specific('-')) {
|
if (lexer.consume_specific('-')) {
|
||||||
int absolute_year;
|
Optional<int> absolute_year;
|
||||||
if (!lex_n_digits(6, absolute_year))
|
if (!lex_n_digits(6, absolute_year))
|
||||||
return false;
|
return false;
|
||||||
year = -absolute_year;
|
year = -absolute_year.value();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return lex_n_digits(4, year);
|
return lex_n_digits(4, year);
|
||||||
};
|
};
|
||||||
auto lex_month = [&]() { return lex_n_digits(2, month) && month >= 1 && month <= 12; };
|
auto lex_month = [&]() { return lex_n_digits(2, month) && *month >= 1 && *month <= 12; };
|
||||||
auto lex_day = [&]() { return lex_n_digits(2, day) && day >= 1 && day <= 31; };
|
auto lex_day = [&]() { return lex_n_digits(2, day) && *day >= 1 && *day <= 31; };
|
||||||
auto lex_date = [&]() { return lex_year() && (!lexer.consume_specific('-') || (lex_month() && (!lexer.consume_specific('-') || lex_day()))); };
|
auto lex_date = [&]() { return lex_year() && (!lexer.consume_specific('-') || (lex_month() && (!lexer.consume_specific('-') || lex_day()))); };
|
||||||
|
|
||||||
auto lex_hours_minutes = [&](int& out_h, int& out_m) {
|
auto lex_hours_minutes = [&](Optional<int>& out_h, Optional<int>& out_m) {
|
||||||
int h, m;
|
Optional<int> h;
|
||||||
if (lex_n_digits(2, h) && h >= 0 && h <= 24 && lexer.consume_specific(':') && lex_n_digits(2, m) && m >= 0 && m <= 59) {
|
Optional<int> m;
|
||||||
out_h = h;
|
if (lex_n_digits(2, h) && *h >= 0 && *h <= 24 && lexer.consume_specific(':') && lex_n_digits(2, m) && *m >= 0 && *m <= 59) {
|
||||||
out_m = m;
|
out_h = move(h);
|
||||||
|
out_m = move(m);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
auto lex_seconds = [&]() { return lex_n_digits(2, seconds) && seconds >= 0 && seconds <= 59; };
|
auto lex_seconds = [&]() { return lex_n_digits(2, seconds) && *seconds >= 0 && *seconds <= 59; };
|
||||||
auto lex_milliseconds = [&]() { return lex_n_digits(3, milliseconds); };
|
auto lex_milliseconds = [&]() { return lex_n_digits(3, milliseconds); };
|
||||||
auto lex_seconds_milliseconds = [&]() { return lex_seconds() && (!lexer.consume_specific('.') || lex_milliseconds()); };
|
auto lex_seconds_milliseconds = [&]() { return lex_seconds() && (!lexer.consume_specific('.') || lex_milliseconds()); };
|
||||||
auto lex_timezone = [&]() {
|
auto lex_timezone = [&]() {
|
||||||
|
@ -90,34 +98,32 @@ static Value parse_simplified_iso8601(const String& iso_8601)
|
||||||
return js_nan();
|
return js_nan();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We parsed a valid date simplified ISO 8601 string. Values not present in the string are -1.
|
// We parsed a valid date simplified ISO 8601 string.
|
||||||
VERIFY(year != -1); // A valid date string always has at least a year.
|
VERIFY(year.has_value()); // A valid date string always has at least a year.
|
||||||
struct tm tm = {};
|
struct tm tm = {};
|
||||||
tm.tm_year = year - 1900;
|
tm.tm_year = *year - 1900;
|
||||||
tm.tm_mon = month == -1 ? 0 : month - 1;
|
tm.tm_mon = !month.has_value() ? 0 : *month - 1;
|
||||||
tm.tm_mday = day == -1 ? 1 : day;
|
tm.tm_mday = day.value_or(1);
|
||||||
tm.tm_hour = hours == -1 ? 0 : hours;
|
tm.tm_hour = hours.value_or(0);
|
||||||
tm.tm_min = minutes == -1 ? 0 : minutes;
|
tm.tm_min = minutes.value_or(0);
|
||||||
tm.tm_sec = seconds == -1 ? 0 : seconds;
|
tm.tm_sec = seconds.value_or(0);
|
||||||
|
|
||||||
// https://tc39.es/ecma262/#sec-date.parse:
|
// https://tc39.es/ecma262/#sec-date.parse:
|
||||||
// "When the UTC offset representation is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time."
|
// "When the UTC offset representation is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time."
|
||||||
time_t timestamp;
|
time_t timestamp;
|
||||||
if (timezone != -1 || hours == -1)
|
if (timezone.has_value() || !hours.has_value())
|
||||||
timestamp = timegm(&tm);
|
timestamp = timegm(&tm);
|
||||||
else
|
else
|
||||||
timestamp = mktime(&tm);
|
timestamp = mktime(&tm);
|
||||||
|
|
||||||
if (timezone == '-')
|
if (timezone == '-')
|
||||||
timestamp += (timezone_hours * 60 + timezone_minutes) * 60;
|
timestamp += (*timezone_hours * 60 + *timezone_minutes) * 60;
|
||||||
else if (timezone == '+')
|
else if (timezone == '+')
|
||||||
timestamp -= (timezone_hours * 60 + timezone_minutes) * 60;
|
timestamp -= (*timezone_hours * 60 + *timezone_minutes) * 60;
|
||||||
|
|
||||||
// FIXME: reject timestamp if resulting value wouldn't fit in a double
|
// FIXME: reject timestamp if resulting value wouldn't fit in a double
|
||||||
|
|
||||||
if (milliseconds == -1)
|
return Value(1000.0 * timestamp + milliseconds.value_or(0));
|
||||||
milliseconds = 0;
|
|
||||||
return Value(1000.0 * timestamp + milliseconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DateConstructor::DateConstructor(GlobalObject& global_object)
|
DateConstructor::DateConstructor(GlobalObject& global_object)
|
||||||
|
|
|
@ -10,6 +10,7 @@ test("basic functionality", () => {
|
||||||
expect(Date.parse("2020T23:59Z")).toBe(1577923140000);
|
expect(Date.parse("2020T23:59Z")).toBe(1577923140000);
|
||||||
|
|
||||||
expect(Date.parse("+020000")).toBe(568971820800000);
|
expect(Date.parse("+020000")).toBe(568971820800000);
|
||||||
|
expect(Date.parse("-000001")).toBe(-62198755200000);
|
||||||
expect(Date.parse("+020000-01")).toBe(568971820800000);
|
expect(Date.parse("+020000-01")).toBe(568971820800000);
|
||||||
expect(Date.parse("+020000-01T00:00:00.000Z")).toBe(568971820800000);
|
expect(Date.parse("+020000-01T00:00:00.000Z")).toBe(568971820800000);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue