2023-12-11 15:36:59 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2023 , Andreas Kling < andreas @ ladybird . org >
2023-12-11 15:36:59 +01:00
* Copyright ( c ) 2022 , Adam Hodgen < ant1441 @ gmail . com >
* Copyright ( c ) 2022 , Andrew Kaster < akaster @ serenityos . org >
* Copyright ( c ) 2023 , Shannon Booth < shannon @ serenityos . org >
* Copyright ( c ) 2023 , stelar7 < dudedbz @ gmail . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Time.h>
# include <LibWeb/HTML/Dates.h>
namespace Web : : HTML {
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#week-number-of-the-last-day
u32 week_number_of_the_last_day ( u64 year )
{
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks
// NOTE: A year is considered to have 53 weeks if either of the following conditions are satisfied:
// - January 1 of that year is a Thursday.
// - January 1 of that year is a Wednesday and the year is divisible by 400, or divisible by 4, but not 100.
// Note: Gauss's algorithm for determining the day of the week with D = 1, and M = 0
// https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Gauss's_algorithm
u8 day_of_week = ( 1 + 5 * ( ( year - 1 ) % 4 ) + 4 * ( ( year - 1 ) % 100 ) + 6 * ( ( year - 1 ) % 400 ) ) % 7 ;
if ( day_of_week = = 4 | | ( day_of_week = = 3 & & ( year % 400 = = 0 | | ( year % 4 = = 0 & & year % 100 ! = 0 ) ) ) )
return 53 ;
return 52 ;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-week-string
bool is_valid_week_string ( StringView value )
{
// A string is a valid week string representing a week-year year and week week if it consists of the following components in the given order:
// 1. Four or more ASCII digits, representing year, where year > 0
// 2. A U+002D HYPHEN-MINUS character (-)
// 3. A U+0057 LATIN CAPITAL LETTER W character (W)
// 4. Two ASCII digits, representing the week week, in the range 1 ≤ week ≤ maxweek, where maxweek is the week number of the last day of week-year year
2024-09-10 16:53:05 +01:00
auto parts = value . split_view ( ' - ' , SplitBehavior : : KeepEmpty ) ;
2023-12-11 15:36:59 +01:00
if ( parts . size ( ) ! = 2 )
return false ;
if ( parts [ 0 ] . length ( ) < 4 )
return false ;
for ( auto digit : parts [ 0 ] )
if ( ! is_ascii_digit ( digit ) )
return false ;
if ( parts [ 1 ] . length ( ) ! = 3 )
return false ;
if ( ! parts [ 1 ] . starts_with ( ' W ' ) )
return false ;
if ( ! is_ascii_digit ( parts [ 1 ] [ 1 ] ) )
return false ;
if ( ! is_ascii_digit ( parts [ 1 ] [ 2 ] ) )
return false ;
u64 year = 0 ;
for ( auto d : parts [ 0 ] ) {
year * = 10 ;
year + = parse_ascii_digit ( d ) ;
}
auto week = ( parse_ascii_digit ( parts [ 1 ] [ 1 ] ) * 10 ) + parse_ascii_digit ( parts [ 1 ] [ 2 ] ) ;
return week > = 1 & & week < = week_number_of_the_last_day ( year ) ;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string
bool is_valid_month_string ( StringView value )
{
// A string is a valid month string representing a year year and month month if it consists of the following components in the given order:
// 1. Four or more ASCII digits, representing year, where year > 0
// 2. A U+002D HYPHEN-MINUS character (-)
// 3. Two ASCII digits, representing the month month, in the range 1 ≤ month ≤ 12
2024-09-10 16:53:05 +01:00
auto parts = value . split_view ( ' - ' , SplitBehavior : : KeepEmpty ) ;
2023-12-11 15:36:59 +01:00
if ( parts . size ( ) ! = 2 )
return false ;
if ( parts [ 0 ] . length ( ) < 4 )
return false ;
for ( auto digit : parts [ 0 ] )
if ( ! is_ascii_digit ( digit ) )
return false ;
if ( parts [ 1 ] . length ( ) ! = 2 )
return false ;
if ( ! is_ascii_digit ( parts [ 1 ] [ 0 ] ) )
return false ;
if ( ! is_ascii_digit ( parts [ 1 ] [ 1 ] ) )
return false ;
auto month = ( parse_ascii_digit ( parts [ 1 ] [ 0 ] ) * 10 ) + parse_ascii_digit ( parts [ 1 ] [ 1 ] ) ;
return month > = 1 & & month < = 12 ;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
bool is_valid_date_string ( StringView value )
{
// A string is a valid date string representing a year year, month month, and day day if it consists of the following components in the given order:
// 1. A valid month string, representing year and month
// 2. A U+002D HYPHEN-MINUS character (-)
// 3. Two ASCII digits, representing day, in the range 1 ≤ day ≤ maxday where maxday is the number of days in the month month and year year
2024-09-10 16:53:05 +01:00
auto parts = value . split_view ( ' - ' , SplitBehavior : : KeepEmpty ) ;
2023-12-11 15:36:59 +01:00
if ( parts . size ( ) ! = 3 )
return false ;
2023-12-16 17:49:34 +03:30
if ( ! is_valid_month_string ( ByteString : : formatted ( " {}-{} " , parts [ 0 ] , parts [ 1 ] ) ) )
2023-12-11 15:36:59 +01:00
return false ;
if ( parts [ 2 ] . length ( ) ! = 2 )
return false ;
i64 year = 0 ;
for ( auto d : parts [ 0 ] ) {
year * = 10 ;
year + = parse_ascii_digit ( d ) ;
}
auto month = ( parse_ascii_digit ( parts [ 1 ] [ 0 ] ) * 10 ) + parse_ascii_digit ( parts [ 1 ] [ 1 ] ) ;
i64 day = ( parse_ascii_digit ( parts [ 2 ] [ 0 ] ) * 10 ) + parse_ascii_digit ( parts [ 2 ] [ 1 ] ) ;
return day > = 1 & & day < = AK : : days_in_month ( year , month ) ;
}
2023-12-15 18:23:29 +01:00
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-date-string
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < JS : : Date > > parse_date_string ( JS : : Realm & realm , StringView value )
2023-12-15 18:23:29 +01:00
{
// FIXME: Implement spec compliant date string parsing
2024-09-10 16:53:05 +01:00
auto parts = value . split_view ( ' - ' , SplitBehavior : : KeepEmpty ) ;
2023-12-15 18:23:29 +01:00
if ( parts . size ( ) > = 3 ) {
2024-01-06 10:27:24 -07:00
if ( auto year = parts . at ( 0 ) . to_number < u32 > ( ) ; year . has_value ( ) ) {
if ( auto month = parts . at ( 1 ) . to_number < u32 > ( ) ; month . has_value ( ) ) {
if ( auto day_of_month = parts . at ( 2 ) . to_number < u32 > ( ) ; day_of_month . has_value ( ) )
2023-12-15 18:23:29 +01:00
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 } ;
}
2023-12-11 15:36:59 +01:00
// 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 )
{
2024-09-10 16:53:05 +01:00
auto parts_split_by_T = value . split_view ( ' T ' , SplitBehavior : : KeepEmpty ) ;
2023-12-11 15:36:59 +01:00
if ( parts_split_by_T . size ( ) = = 2 )
return is_valid_date_string ( parts_split_by_T [ 0 ] ) & & is_valid_time_string ( parts_split_by_T [ 1 ] ) ;
2024-09-10 16:53:05 +01:00
auto parts_split_by_space = value . split_view ( ' ' , SplitBehavior : : KeepEmpty ) ;
2023-12-11 15:36:59 +01:00
if ( parts_split_by_space . size ( ) = = 2 )
return is_valid_date_string ( parts_split_by_space [ 0 ] ) & & is_valid_time_string ( parts_split_by_space [ 1 ] ) ;
return false ;
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string
String normalize_local_date_and_time_string ( String const & value )
{
2024-09-10 10:44:18 +01:00
if ( auto spaces = value . count ( " " sv ) ; spaces > 0 ) {
VERIFY ( spaces = = 1 ) ;
return MUST ( value . replace ( " " sv , " T " sv , ReplaceMode : : FirstOnly ) ) ;
}
VERIFY ( value . count ( " T " sv ) = = 1 ) ;
return value ;
2023-12-11 15:36:59 +01:00
}
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-time-string
bool is_valid_time_string ( StringView value )
{
// A string is a valid time string representing an hour hour, a minute minute, and a second second if it consists of the following components in the given order:
// 1. Two ASCII digits, representing hour, in the range 0 ≤ hour ≤ 23
// 2. A U+003A COLON character (:)
// 3. Two ASCII digits, representing minute, in the range 0 ≤ minute ≤ 59
// 4. If second is nonzero, or optionally if second is zero:
// 1. A U+003A COLON character (:)
// 2. Two ASCII digits, representing the integer part of second, in the range 0 ≤ s ≤ 59
// 3. If second is not an integer, or optionally if second is an integer:
// 1. A U+002E FULL STOP character (.)
// 2. One, two, or three ASCII digits, representing the fractional part of second
2024-09-10 16:53:05 +01:00
auto parts = value . split_view ( ' : ' , SplitBehavior : : KeepEmpty ) ;
2023-12-15 18:23:11 +01:00
if ( parts . size ( ) ! = 2 & & parts . size ( ) ! = 3 )
2023-12-11 15:36:59 +01:00
return false ;
if ( parts [ 0 ] . length ( ) ! = 2 )
return false ;
2024-08-20 21:56:04 +01:00
if ( ! ( is_ascii_digit ( parts [ 0 ] [ 0 ] ) & & is_ascii_digit ( parts [ 0 ] [ 1 ] ) ) )
return false ;
2023-12-11 15:36:59 +01:00
auto hour = ( parse_ascii_digit ( parts [ 0 ] [ 0 ] ) * 10 ) + parse_ascii_digit ( parts [ 0 ] [ 1 ] ) ;
if ( hour > 23 )
return false ;
if ( parts [ 1 ] . length ( ) ! = 2 )
return false ;
2024-08-20 21:56:04 +01:00
if ( ! ( is_ascii_digit ( parts [ 1 ] [ 0 ] ) & & is_ascii_digit ( parts [ 1 ] [ 1 ] ) ) )
return false ;
2023-12-11 15:36:59 +01:00
auto minute = ( parse_ascii_digit ( parts [ 1 ] [ 0 ] ) * 10 ) + parse_ascii_digit ( parts [ 1 ] [ 1 ] ) ;
if ( minute > 59 )
return false ;
if ( parts . size ( ) = = 2 )
return true ;
if ( parts [ 2 ] . length ( ) < 2 )
return false ;
2024-08-20 21:56:04 +01:00
if ( ! ( is_ascii_digit ( parts [ 2 ] [ 0 ] ) & & is_ascii_digit ( parts [ 2 ] [ 1 ] ) ) )
return false ;
2023-12-11 15:36:59 +01:00
auto second = ( parse_ascii_digit ( parts [ 2 ] [ 0 ] ) * 10 ) + parse_ascii_digit ( parts [ 2 ] [ 1 ] ) ;
if ( second > 59 )
return false ;
if ( parts [ 2 ] . length ( ) = = 2 )
return true ;
2024-09-10 16:53:05 +01:00
auto second_parts = parts [ 2 ] . split_view ( ' . ' , SplitBehavior : : KeepEmpty ) ;
2023-12-11 15:36:59 +01:00
if ( second_parts . size ( ) ! = 2 )
return false ;
if ( second_parts [ 1 ] . length ( ) < 1 | | second_parts [ 1 ] . length ( ) > 3 )
return false ;
for ( auto digit : second_parts [ 1 ] )
if ( ! is_ascii_digit ( digit ) )
return false ;
return true ;
}
2023-12-15 18:23:29 +01:00
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-time-string
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < JS : : Date > > parse_time_string ( JS : : Realm & realm , StringView value )
2023-12-15 18:23:29 +01:00
{
// FIXME: Implement spec compliant time string parsing
2024-09-10 16:53:05 +01:00
auto parts = value . split_view ( ' : ' , SplitBehavior : : KeepEmpty ) ;
2023-12-15 18:23:29 +01:00
if ( parts . size ( ) > = 2 ) {
2024-01-06 10:27:24 -07:00
if ( auto hours = parts . at ( 0 ) . to_number < u32 > ( ) ; hours . has_value ( ) ) {
if ( auto minutes = parts . at ( 1 ) . to_number < u32 > ( ) ; minutes . has_value ( ) ) {
2023-12-15 18:23:29 +01:00
if ( parts . size ( ) > = 3 ) {
2024-01-06 10:27:24 -07:00
if ( auto seconds = parts . at ( 2 ) . to_number < u32 > ( ) ; seconds . has_value ( ) )
2023-12-15 18:23:29 +01:00
return JS : : Date : : create ( realm , JS : : make_time ( * hours , * minutes , * seconds , 0 ) ) ;
}
return JS : : Date : : create ( realm , JS : : make_date ( 0 , JS : : make_time ( * hours , * minutes , 0 , 0 ) ) ) ;
}
}
}
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : TypeError , " Can't parse time string " sv } ;
}
2023-12-11 15:36:59 +01:00
}