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)