mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibWeb: Implement string->number for type=datetime-local input elements
This commit is contained in:
parent
90b303215e
commit
4def3fe567
Notes:
github-actions[bot]
2025-03-10 10:35:00 +00:00
Author: https://github.com/sideshowbarker
Commit: 4def3fe567
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3849
Reviewed-by: https://github.com/tcl3
6 changed files with 148 additions and 17 deletions
|
@ -401,4 +401,103 @@ Optional<YearMonthDay> parse_a_date_string(StringView input_view)
|
||||||
return year_month_day.release_value();
|
return year_month_day.release_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-time-component
|
||||||
|
static Optional<HourMinuteSecond> parse_a_time_component(GenericLexer& input)
|
||||||
|
{
|
||||||
|
// 1. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence
|
||||||
|
// is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten
|
||||||
|
// integer. Let that number be the hour.
|
||||||
|
auto hour_string = input.consume_while(is_ascii_digit);
|
||||||
|
if (hour_string.length() != 2)
|
||||||
|
return {};
|
||||||
|
auto maybe_hour = hour_string.to_number<i32>();
|
||||||
|
if (!maybe_hour.has_value())
|
||||||
|
return {};
|
||||||
|
auto hour = maybe_hour.value();
|
||||||
|
// 2. If hour is not a number in the range 0 ≤ hour ≤ 23, then fail.
|
||||||
|
if (hour < 0 || hour > 23)
|
||||||
|
return {};
|
||||||
|
// 3. If position is beyond the end of input or if the character at position is not a U+003A COLON character, then
|
||||||
|
// fail. Otherwise, move position forwards one character.
|
||||||
|
if (!input.consume_specific(':'))
|
||||||
|
return {};
|
||||||
|
// 4. Collect a sequence of code points that are ASCII digits from input given position. If the collected sequence
|
||||||
|
// is not exactly two characters long, then fail. Otherwise, interpret the resulting sequence as a base-ten integer.
|
||||||
|
// Let that number be the minute.
|
||||||
|
auto minute_string = input.consume_while(is_ascii_digit);
|
||||||
|
if (minute_string.length() != 2)
|
||||||
|
return {};
|
||||||
|
auto maybe_minute = minute_string.to_number<i32>();
|
||||||
|
if (!maybe_minute.has_value())
|
||||||
|
return {};
|
||||||
|
auto minute = maybe_minute.value();
|
||||||
|
// 5. If minute is not a number in the range 0 ≤ minute ≤ 59, then fail.
|
||||||
|
if (minute < 0 || hour > 59)
|
||||||
|
return {};
|
||||||
|
// 6. Let second be 0.
|
||||||
|
i32 second = 0;
|
||||||
|
// 7. If position is not beyond the end of input and the character at position is U+003A (:), then:
|
||||||
|
if (!input.consume_specific(':'))
|
||||||
|
return {};
|
||||||
|
// 7.1. Advance position to the next character in input.
|
||||||
|
// 7.2. If position is beyond the end of input, or at the last character in input, or if the next two characters in
|
||||||
|
// input starting at position are not both ASCII digits, then fail.
|
||||||
|
if (input.is_eof() || input.tell_remaining() == 1 || (!is_ascii_digit(input.peek()) && !is_ascii_digit(input.peek(1))))
|
||||||
|
return {};
|
||||||
|
// 7.3. Collect a sequence of code points that are either ASCII digits or U+002E FULL STOP characters from input
|
||||||
|
// given position.
|
||||||
|
auto second_string = input.consume_while([](auto ch) { return is_ascii_digit(ch) || ch == '.'; });
|
||||||
|
// If the collected sequence is three characters long, or if it is longer than three characters long and the third
|
||||||
|
// character is not a U+002E FULL STOP character, or if it has more than one U+002E FULL STOP character, then fail.
|
||||||
|
if (second_string.length() == 3)
|
||||||
|
return {};
|
||||||
|
if (second_string.length() > 3 && second_string[2] != '.')
|
||||||
|
return {};
|
||||||
|
if (second_string.find_all("."sv).size() > 1)
|
||||||
|
return {};
|
||||||
|
// Otherwise, interpret the resulting sequence as a base-ten number (possibly with a fractional part). Set second
|
||||||
|
// to that number.
|
||||||
|
// NB: The spec doesn't state requirements for what we must do with the fractional part of the second(s) string.
|
||||||
|
// The spec neither requires that we separately preserve it nor requires that we completely discard it. If we
|
||||||
|
// did have any reason at all to preserve it, we could parse the string into a float here. But there doesn't
|
||||||
|
// seem to be any point in doing that, because there’s nothing in the corresponding calling algorithm(s) in the
|
||||||
|
// spec that takes a milliseconds field and does anything with it anyway.
|
||||||
|
auto maybe_second = second_string.to_number<i32>();
|
||||||
|
if (!maybe_second.has_value())
|
||||||
|
return {};
|
||||||
|
second = maybe_second.value();
|
||||||
|
// 7.4. If second is not a number in the range 0 ≤ second < 60, then fail.
|
||||||
|
if (second < 0 || second > 60)
|
||||||
|
return {};
|
||||||
|
// 8. Return hour, minute, and second.
|
||||||
|
return HourMinuteSecond { hour, minute, second };
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-local-date-and-time-string
|
||||||
|
Optional<DateAndTime> parse_a_local_date_and_time_string(StringView input_view)
|
||||||
|
{
|
||||||
|
// 1. Let input be the string being parsed.
|
||||||
|
// 2. Let position be a pointer into input, initially pointing at the start of the string.
|
||||||
|
GenericLexer input { input_view };
|
||||||
|
// 3. Parse a date component to obtain year, month, and day. If this returns nothing, then fail.
|
||||||
|
auto year_month_day = parse_a_date_component(input);
|
||||||
|
if (!year_month_day.has_value())
|
||||||
|
return {};
|
||||||
|
// 4. If position is beyond the end of input or if the character at position is neither a U+0054 LATIN CAPITAL
|
||||||
|
// LETTER T character (T) nor a U+0020 SPACE character, then fail. Otherwise, move position forwards one character.
|
||||||
|
if (!input.consume_specific("T") && !input.consume_specific(" "))
|
||||||
|
return {};
|
||||||
|
// 5. Parse a time component to obtain hour, minute, and second. If this returns nothing, then fail.
|
||||||
|
auto hour_minute_second = parse_a_time_component(input);
|
||||||
|
if (!hour_minute_second.has_value())
|
||||||
|
return {};
|
||||||
|
// 6. If position is not beyond the end of input, then fail.
|
||||||
|
if (!input.is_eof())
|
||||||
|
return {};
|
||||||
|
// 7. Let date be the date with year year, month month, and day day.
|
||||||
|
// 8. Let time be the time with hour hour, minute minute, and second second.
|
||||||
|
// 9. Return date and time.
|
||||||
|
return DateAndTime { year_month_day.release_value(), hour_minute_second.release_value() };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,21 @@ struct YearMonthDay {
|
||||||
u32 day;
|
u32 day;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HourMinuteSecond {
|
||||||
|
i32 hour;
|
||||||
|
i32 minute;
|
||||||
|
i32 second;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DateAndTime {
|
||||||
|
YearMonthDay date;
|
||||||
|
HourMinuteSecond time;
|
||||||
|
};
|
||||||
|
|
||||||
Optional<YearMonthDay> parse_a_date_string(StringView);
|
Optional<YearMonthDay> parse_a_date_string(StringView);
|
||||||
|
|
||||||
|
Optional<DateAndTime> parse_a_local_date_and_time_string(StringView);
|
||||||
|
|
||||||
i32 number_of_months_since_unix_epoch(YearAndMonth);
|
i32 number_of_months_since_unix_epoch(YearAndMonth);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2170,6 +2170,24 @@ static Optional<double> convert_date_string_to_number(StringView input)
|
||||||
return date_time.milliseconds_since_epoch();
|
return date_time.milliseconds_since_epoch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):parse-a-local-date-and-time-string-2
|
||||||
|
static Optional<double> convert_local_date_and_time_string_to_number(StringView input)
|
||||||
|
{
|
||||||
|
// The algorithm to convert a string to a number, given a string input, is as follows: If parsing a date and time
|
||||||
|
// from input results in an error, then return an error; otherwise, return the number of milliseconds elapsed from
|
||||||
|
// midnight on the morning of 1970-01-01 (the time represented by the value "1970-01-01T00:00:00.0") to the parsed
|
||||||
|
// local date and time, ignoring leap seconds.
|
||||||
|
auto maybe_date_and_time = parse_a_local_date_and_time_string(input);
|
||||||
|
if (!maybe_date_and_time.has_value())
|
||||||
|
return {};
|
||||||
|
auto date_and_time = maybe_date_and_time.value();
|
||||||
|
auto date = date_and_time.date;
|
||||||
|
auto time = date_and_time.time;
|
||||||
|
|
||||||
|
auto date_time = UnixDateTime::from_unix_time_parts(date.year, date.month, date.day, time.hour, time.minute, time.second, 0);
|
||||||
|
return date_time.milliseconds_since_epoch();
|
||||||
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-string-number
|
// https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-string-number
|
||||||
Optional<double> HTMLInputElement::convert_time_string_to_number(StringView input) const
|
Optional<double> HTMLInputElement::convert_time_string_to_number(StringView input) const
|
||||||
{
|
{
|
||||||
|
@ -2205,7 +2223,9 @@ Optional<double> HTMLInputElement::convert_string_to_number(StringView input) co
|
||||||
if (type_state() == TypeAttributeState::Time)
|
if (type_state() == TypeAttributeState::Time)
|
||||||
return convert_time_string_to_number(input);
|
return convert_time_string_to_number(input);
|
||||||
|
|
||||||
dbgln("HTMLInputElement::convert_string_to_number() not implemented for input type {}", type());
|
if (type_state() == TypeAttributeState::LocalDateAndTime)
|
||||||
|
return convert_local_date_and_time_string_to_number(input);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
||||||
|
|
||||||
Found 132 tests
|
Found 132 tests
|
||||||
|
|
||||||
126 Pass
|
132 Pass
|
||||||
6 Fail
|
|
||||||
Pass [INPUT in TEXT status] no constraint
|
Pass [INPUT in TEXT status] no constraint
|
||||||
Pass [INPUT in TEXT status] no constraint (in a form)
|
Pass [INPUT in TEXT status] no constraint (in a form)
|
||||||
Pass [INPUT in TEXT status] not suffering from being too long
|
Pass [INPUT in TEXT status] not suffering from being too long
|
||||||
|
@ -58,12 +57,12 @@ Pass [INPUT in EMAIL status] suffering from being missing
|
||||||
Pass [INPUT in EMAIL status] suffering from being missing (in a form)
|
Pass [INPUT in EMAIL status] suffering from being missing (in a form)
|
||||||
Pass [INPUT in DATETIME-LOCAL status] no constraint
|
Pass [INPUT in DATETIME-LOCAL status] no constraint
|
||||||
Pass [INPUT in DATETIME-LOCAL status] no constraint (in a form)
|
Pass [INPUT in DATETIME-LOCAL status] no constraint (in a form)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] suffering from an overflow
|
Pass [INPUT in DATETIME-LOCAL status] suffering from an overflow
|
||||||
Fail [INPUT in DATETIME-LOCAL status] suffering from an overflow (in a form)
|
Pass [INPUT in DATETIME-LOCAL status] suffering from an overflow (in a form)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] suffering from an underflow
|
Pass [INPUT in DATETIME-LOCAL status] suffering from an underflow
|
||||||
Fail [INPUT in DATETIME-LOCAL status] suffering from an underflow (in a form)
|
Pass [INPUT in DATETIME-LOCAL status] suffering from an underflow (in a form)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] suffering from a step mismatch
|
Pass [INPUT in DATETIME-LOCAL status] suffering from a step mismatch
|
||||||
Fail [INPUT in DATETIME-LOCAL status] suffering from a step mismatch (in a form)
|
Pass [INPUT in DATETIME-LOCAL status] suffering from a step mismatch (in a form)
|
||||||
Pass [INPUT in DATETIME-LOCAL status] suffering from being missing
|
Pass [INPUT in DATETIME-LOCAL status] suffering from being missing
|
||||||
Pass [INPUT in DATETIME-LOCAL status] suffering from being missing (in a form)
|
Pass [INPUT in DATETIME-LOCAL status] suffering from being missing (in a form)
|
||||||
Pass [INPUT in DATE status] no constraint
|
Pass [INPUT in DATE status] no constraint
|
||||||
|
|
|
@ -2,19 +2,19 @@ Harness status: OK
|
||||||
|
|
||||||
Found 49 tests
|
Found 49 tests
|
||||||
|
|
||||||
41 Pass
|
43 Pass
|
||||||
8 Fail
|
6 Fail
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The max attribute is not set
|
Pass [INPUT in DATETIME-LOCAL status] The max attribute is not set
|
||||||
Pass [INPUT in DATETIME-LOCAL status] Value is empty string
|
Pass [INPUT in DATETIME-LOCAL status] Value is empty string
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The max attribute is an invalid local date time string
|
Pass [INPUT in DATETIME-LOCAL status] The max attribute is an invalid local date time string
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The max attribute is greater than the value attribute
|
Pass [INPUT in DATETIME-LOCAL status] The max attribute is greater than the value attribute
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The value is an invalid local date time string(hour is greater than 23)
|
Pass [INPUT in DATETIME-LOCAL status] The value is an invalid local date time string(hour is greater than 23)
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The value if an invalid local date time string(year is two digits)
|
Pass [INPUT in DATETIME-LOCAL status] The value if an invalid local date time string(year is two digits)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max
|
Pass [INPUT in DATETIME-LOCAL status] The value is greater than max
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(with millisecond in 1 digit)
|
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(with millisecond in 1 digit)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(with millisecond in 2 digits)
|
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(with millisecond in 2 digits)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(with millisecond in 3 digits)
|
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(with millisecond in 3 digits)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is greater than max(Year is 10000 should be valid)
|
Pass [INPUT in DATETIME-LOCAL status] The value is greater than max(Year is 10000 should be valid)
|
||||||
Pass [INPUT in DATE status] The max attribute is not set
|
Pass [INPUT in DATE status] The max attribute is not set
|
||||||
Pass [INPUT in DATE status] Value is empty string
|
Pass [INPUT in DATE status] Value is empty string
|
||||||
Pass [INPUT in DATE status] The max attribute is an invalid date
|
Pass [INPUT in DATE status] The max attribute is an invalid date
|
||||||
|
|
|
@ -2,19 +2,19 @@ Harness status: OK
|
||||||
|
|
||||||
Found 47 tests
|
Found 47 tests
|
||||||
|
|
||||||
39 Pass
|
41 Pass
|
||||||
8 Fail
|
6 Fail
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The min attribute is not set
|
Pass [INPUT in DATETIME-LOCAL status] The min attribute is not set
|
||||||
Pass [INPUT in DATETIME-LOCAL status] Value is empty string
|
Pass [INPUT in DATETIME-LOCAL status] Value is empty string
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The min attribute is an invalid local date time string
|
Pass [INPUT in DATETIME-LOCAL status] The min attribute is an invalid local date time string
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The min attribute is less than the value attribute
|
Pass [INPUT in DATETIME-LOCAL status] The min attribute is less than the value attribute
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The value is an invalid local date time string(hour is greater than 23)
|
Pass [INPUT in DATETIME-LOCAL status] The value is an invalid local date time string(hour is greater than 23)
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The value is an invalid local date time string(year is two digits)
|
Pass [INPUT in DATETIME-LOCAL status] The value is an invalid local date time string(year is two digits)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is less than min
|
Pass [INPUT in DATETIME-LOCAL status] The value is less than min
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(with millisecond in 1 digit)
|
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(with millisecond in 1 digit)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(with millisecond in 2 digits)
|
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(with millisecond in 2 digits)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(with millisecond in 3 digits)
|
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(with millisecond in 3 digits)
|
||||||
Fail [INPUT in DATETIME-LOCAL status] The value is less than min(Year is 10000 should be valid)
|
Pass [INPUT in DATETIME-LOCAL status] The value is less than min(Year is 10000 should be valid)
|
||||||
Pass [INPUT in DATETIME-LOCAL status] The value is greater than max
|
Pass [INPUT in DATETIME-LOCAL status] The value is greater than max
|
||||||
Pass [INPUT in DATE status] The min attribute is not set
|
Pass [INPUT in DATE status] The min attribute is not set
|
||||||
Pass [INPUT in DATE status] Value is empty string
|
Pass [INPUT in DATE status] Value is empty string
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue