diff --git a/Libraries/LibWeb/HTML/Dates.cpp b/Libraries/LibWeb/HTML/Dates.cpp
index 004a1cf1824..0e3870515f0 100644
--- a/Libraries/LibWeb/HTML/Dates.cpp
+++ b/Libraries/LibWeb/HTML/Dates.cpp
@@ -401,4 +401,103 @@ Optional parse_a_date_string(StringView input_view)
return year_month_day.release_value();
}
+// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-time-component
+static Optional 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();
+ 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();
+ 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();
+ 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 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() };
+}
+
}
diff --git a/Libraries/LibWeb/HTML/Dates.h b/Libraries/LibWeb/HTML/Dates.h
index 80d5500be2b..a3186299452 100644
--- a/Libraries/LibWeb/HTML/Dates.h
+++ b/Libraries/LibWeb/HTML/Dates.h
@@ -40,8 +40,21 @@ struct YearMonthDay {
u32 day;
};
+struct HourMinuteSecond {
+ i32 hour;
+ i32 minute;
+ i32 second;
+};
+
+struct DateAndTime {
+ YearMonthDay date;
+ HourMinuteSecond time;
+};
+
Optional parse_a_date_string(StringView);
+Optional parse_a_local_date_and_time_string(StringView);
+
i32 number_of_months_since_unix_epoch(YearAndMonth);
}
diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp
index 3214cd28050..ae124229180 100644
--- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp
+++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -2170,6 +2170,24 @@ static Optional convert_date_string_to_number(StringView input)
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 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
Optional HTMLInputElement::convert_time_string_to_number(StringView input) const
{
@@ -2205,7 +2223,9 @@ Optional HTMLInputElement::convert_string_to_number(StringView input) co
if (type_state() == TypeAttributeState::Time)
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 {};
}
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-checkValidity.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-checkValidity.txt
index 1df5073404b..d256ffa58dc 100644
--- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-checkValidity.txt
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-checkValidity.txt
@@ -2,8 +2,7 @@ Harness status: OK
Found 132 tests
-126 Pass
-6 Fail
+132 Pass
Pass [INPUT in TEXT status] no constraint
Pass [INPUT in TEXT status] no constraint (in a form)
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 DATETIME-LOCAL status] no constraint
Pass [INPUT in DATETIME-LOCAL status] no constraint (in a form)
-Fail [INPUT in DATETIME-LOCAL status] suffering from an overflow
-Fail [INPUT in DATETIME-LOCAL status] suffering from an overflow (in a form)
-Fail [INPUT in DATETIME-LOCAL status] suffering from an underflow
-Fail [INPUT in DATETIME-LOCAL status] suffering from an underflow (in a form)
-Fail [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 an overflow
+Pass [INPUT in DATETIME-LOCAL status] suffering from an overflow (in a form)
+Pass [INPUT in DATETIME-LOCAL status] suffering from an underflow
+Pass [INPUT in DATETIME-LOCAL status] suffering from an underflow (in a form)
+Pass [INPUT in DATETIME-LOCAL status] suffering from a step mismatch
+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 (in a form)
Pass [INPUT in DATE status] no constraint
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.txt
index 942a7da6894..897e3e2ad25 100644
--- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.txt
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.txt
@@ -2,19 +2,19 @@ Harness status: OK
Found 49 tests
-41 Pass
-8 Fail
+43 Pass
+6 Fail
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] 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 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)
-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 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(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] Value is empty string
Pass [INPUT in DATE status] The max attribute is an invalid date
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.txt
index ffef3c6b306..b75209dfccf 100644
--- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.txt
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.txt
@@ -2,19 +2,19 @@ Harness status: OK
Found 47 tests
-39 Pass
-8 Fail
+41 Pass
+6 Fail
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] 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 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)
-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 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(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 DATE status] The min attribute is not set
Pass [INPUT in DATE status] Value is empty string