diff --git a/Libraries/LibWeb/HTML/Dates.cpp b/Libraries/LibWeb/HTML/Dates.cpp
index 2faaf3066c..004a1cf182 100644
--- a/Libraries/LibWeb/HTML/Dates.cpp
+++ b/Libraries/LibWeb/HTML/Dates.cpp
@@ -129,22 +129,6 @@ bool is_valid_date_string(StringView value)
return day >= 1 && day <= AK::days_in_month(year, month);
}
-// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-date-string
-WebIDL::ExceptionOr> parse_date_string(JS::Realm& realm, StringView value)
-{
- // FIXME: Implement spec compliant date string parsing
- auto parts = value.split_view('-', SplitBehavior::KeepEmpty);
- if (parts.size() >= 3) {
- if (auto year = parts.at(0).to_number(); year.has_value()) {
- if (auto month = parts.at(1).to_number(); month.has_value()) {
- if (auto day_of_month = parts.at(2).to_number(); day_of_month.has_value())
- return JS::Date::create(realm, JS::make_date(JS::make_day(*year, *month - 1, *day_of_month), 0));
- }
- }
- }
- return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Can't parse date string"sv };
-}
-
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
bool is_valid_local_date_and_time_string(StringView value)
{
@@ -363,4 +347,58 @@ Optional parse_a_week_string(StringView input_view)
return WeekYearAndWeek { year, week };
}
+// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-date-component
+static Optional parse_a_date_component(GenericLexer& input)
+{
+ // 1. Parse a month component to obtain year and month. If this returns nothing, then fail.
+ auto maybe_month_component = parse_a_month_component(input);
+ if (!maybe_month_component.has_value())
+ return {};
+ auto month_component = maybe_month_component.value();
+
+ // 2. Let maxday be the number of days in month month of year year.
+ u32 maxday = AK::days_in_month(month_component.year, month_component.month);
+
+ // 3. If position is beyond the end of input or if the character at position is not a U+002D HYPHEN-MINUS 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 day.
+ auto day_string = input.consume_while(is_ascii_digit);
+ if (day_string.length() != 2)
+ return {};
+ auto day = day_string.to_number().value();
+
+ // 5. If day is not a number in the range 1 ≤ day ≤ maxday, then fail.
+ if (day < 1 || day > maxday)
+ return {};
+
+ // 6. Return year, month, and day.
+ return YearMonthDay { month_component.year, month_component.month, day };
+}
+
+// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-date-string
+Optional parse_a_date_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 not beyond the end of input, then fail.
+ if (!input.is_eof())
+ return {};
+
+ // 5. Let date be the date with year year, month month, and day day.
+ // 6. Return date.
+ return year_month_day.release_value();
+}
+
}
diff --git a/Libraries/LibWeb/HTML/Dates.h b/Libraries/LibWeb/HTML/Dates.h
index 6f617be969..80d5500be2 100644
--- a/Libraries/LibWeb/HTML/Dates.h
+++ b/Libraries/LibWeb/HTML/Dates.h
@@ -17,7 +17,6 @@ u32 week_number_of_the_last_day(u64 year);
bool is_valid_week_string(StringView value);
bool is_valid_month_string(StringView value);
bool is_valid_date_string(StringView value);
-WebIDL::ExceptionOr> parse_date_string(JS::Realm& realm, StringView value);
bool is_valid_local_date_and_time_string(StringView value);
String normalize_local_date_and_time_string(String const& value);
bool is_valid_time_string(StringView value);
@@ -35,6 +34,14 @@ struct WeekYearAndWeek {
};
Optional parse_a_week_string(StringView);
+struct YearMonthDay {
+ u32 year;
+ u32 month;
+ u32 day;
+};
+
+Optional parse_a_date_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 e934a86565..378d669099 100644
--- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp
+++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -11,6 +11,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include
#include
#include
#include
@@ -2094,6 +2095,22 @@ static Optional convert_week_string_to_number(StringView input)
return UnixDateTime::from_iso8601_week(parsed_week->week_year, parsed_week->week).milliseconds_since_epoch();
}
+// https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-number-string
+static Optional convert_date_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
+ // from input results in an error, then return an error; otherwise, return the number of milliseconds
+ // elapsed from midnight UTC on the morning of 1970-01-01 (the time represented by the value
+ // "1970-01-01T00:00:00.0Z") to midnight UTC on the morning of the parsed date, ignoring leap seconds.
+ auto maybe_date = parse_a_date_string(input);
+ if (!maybe_date.has_value())
+ return {};
+ auto date = maybe_date.value();
+
+ auto date_time = UnixDateTime::from_unix_time_parts(date.year, date.month, date.day, 0, 0, 0, 0);
+ return date_time.milliseconds_since_epoch();
+}
+
// https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number
Optional HTMLInputElement::convert_string_to_number(StringView input) const
{
@@ -2111,6 +2128,9 @@ Optional HTMLInputElement::convert_string_to_number(StringView input) co
if (type_state() == TypeAttributeState::Week)
return convert_week_string_to_number(input);
+ if (type_state() == TypeAttributeState::Date)
+ return convert_date_string_to_number(input);
+
dbgln("HTMLInputElement::convert_string_to_number() not implemented for input type {}", type());
return {};
}
@@ -2156,6 +2176,16 @@ static String convert_number_to_week_string(double input)
return MUST(String::formatted("{:04d}-W{:02d}", year, week));
}
+// https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-number-string
+static String convert_number_to_date_string(double input)
+{
+ // The algorithm to convert a number to a string, given a number input, is as follows: Return a valid
+ // date string that represents the date that, in UTC, is current input milliseconds after midnight UTC
+ // on the morning of 1970-01-01 (the time represented by the value "1970-01-01T00:00:00.0Z").
+ auto date = Core::DateTime::from_timestamp(input / 1000.);
+ return MUST(date.to_string("%Y-%m-%d"sv, Core::DateTime::LocalTime::No));
+}
+
// https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number
String HTMLInputElement::convert_number_to_string(double input) const
{
@@ -2173,6 +2203,9 @@ String HTMLInputElement::convert_number_to_string(double input) const
if (type_state() == TypeAttributeState::Week)
return convert_number_to_week_string(input);
+ if (type_state() == TypeAttributeState::Date)
+ return convert_number_to_date_string(input);
+
dbgln("HTMLInputElement::convert_number_to_string() not implemented for input type {}", type());
return {};
}
@@ -2183,12 +2216,13 @@ WebIDL::ExceptionOr> HTMLInputElement::convert_string_to_date(
// https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-string-date
if (type_state() == TypeAttributeState::Date) {
// If parsing a date from input results in an error, then return an error;
- auto maybe_date = parse_date_string(realm(), input);
- if (maybe_date.is_exception())
- return maybe_date.exception();
+ auto maybe_date = parse_a_date_string(input);
+ if (!maybe_date.has_value())
+ return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Can't parse date string"sv };
+ auto date = maybe_date.value();
// otherwise, return a new Date object representing midnight UTC on the morning of the parsed date.
- return maybe_date.value();
+ return JS::Date::create(realm(), JS::make_date(JS::make_day(date.year, date.month - 1, date.day), 0));
}
// https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-string-date
diff --git a/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt b/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt
index 2961a59540..1169ea0561 100644
--- a/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt
+++ b/Tests/LibWeb/Text/expected/HTML/HTMLInputElement-valueAsNumber.txt
@@ -31,7 +31,7 @@ tel threw exception: InvalidStateError: valueAsNumber: Invalid input type used
url threw exception: InvalidStateError: valueAsNumber: Invalid input type used
email threw exception: InvalidStateError: valueAsNumber: Invalid input type used
password threw exception: InvalidStateError: valueAsNumber: Invalid input type used
-date did not throw: NaN
+date did not throw: 0
month did not throw: 100
week did not throw: 345600000
time did not throw: NaN
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt
index 541bd3bf98..d5156985ce 100644
--- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-valueasnumber.txt
@@ -2,19 +2,19 @@ Harness status: OK
Found 60 tests
-43 Pass
-17 Fail
+48 Pass
+12 Fail
Pass valueAsNumber getter on type date (actual value: , expected valueAsNumber: NaN)
Pass valueAsNumber getter on type date (actual value: 0000-12-10, expected valueAsNumber: NaN)
Pass valueAsNumber getter on type date (actual value: 2019-00-12, expected valueAsNumber: NaN)
Pass valueAsNumber getter on type date (actual value: 2019-12-00, expected valueAsNumber: NaN)
Pass valueAsNumber getter on type date (actual value: 2019-13-10, expected valueAsNumber: NaN)
Pass valueAsNumber getter on type date (actual value: 2019-02-29, expected valueAsNumber: NaN)
-Fail valueAsNumber getter on type date (actual value: 2019-12-10, expected valueAsNumber: 1575936000000)
-Fail valueAsNumber getter on type date (actual value: 2016-02-29, expected valueAsNumber: 1456704000000)
-Fail valueAsNumber setter on type date (actual valueAsNumber: 0, expected value: 1970-01-01)
-Fail valueAsNumber setter on type date (actual valueAsNumber: 1575936000000, expected value: 2019-12-10)
-Fail valueAsNumber setter on type date (actual valueAsNumber: 1456704000000, expected value: 2016-02-29)
+Pass valueAsNumber getter on type date (actual value: 2019-12-10, expected valueAsNumber: 1575936000000)
+Pass valueAsNumber getter on type date (actual value: 2016-02-29, expected valueAsNumber: 1456704000000)
+Pass valueAsNumber setter on type date (actual valueAsNumber: 0, expected value: 1970-01-01)
+Pass valueAsNumber setter on type date (actual valueAsNumber: 1575936000000, expected value: 2019-12-10)
+Pass valueAsNumber setter on type date (actual valueAsNumber: 1456704000000, expected value: 2016-02-29)
Pass valueAsNumber getter on type month (actual value: , expected valueAsNumber: NaN)
Pass valueAsNumber getter on type month (actual value: 0000-12, expected valueAsNumber: NaN)
Pass valueAsNumber getter on type month (actual value: 2019-00, expected valueAsNumber: NaN)