Dates.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, Adam Hodgen <ant1441@gmail.com>
  4. * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
  5. * Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
  6. * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/Time.h>
  11. #include <LibWeb/HTML/Dates.h>
  12. namespace Web::HTML {
  13. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#week-number-of-the-last-day
  14. u32 week_number_of_the_last_day(u64 year)
  15. {
  16. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks
  17. // NOTE: A year is considered to have 53 weeks if either of the following conditions are satisfied:
  18. // - January 1 of that year is a Thursday.
  19. // - January 1 of that year is a Wednesday and the year is divisible by 400, or divisible by 4, but not 100.
  20. // Note: Gauss's algorithm for determining the day of the week with D = 1, and M = 0
  21. // https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Gauss's_algorithm
  22. u8 day_of_week = (1 + 5 * ((year - 1) % 4) + 4 * ((year - 1) % 100) + 6 * ((year - 1) % 400)) % 7;
  23. if (day_of_week == 4 || (day_of_week == 3 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))))
  24. return 53;
  25. return 52;
  26. }
  27. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-week-string
  28. bool is_valid_week_string(StringView value)
  29. {
  30. // 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:
  31. // 1. Four or more ASCII digits, representing year, where year > 0
  32. // 2. A U+002D HYPHEN-MINUS character (-)
  33. // 3. A U+0057 LATIN CAPITAL LETTER W character (W)
  34. // 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
  35. auto parts = value.split_view('-');
  36. if (parts.size() != 2)
  37. return false;
  38. if (parts[0].length() < 4)
  39. return false;
  40. for (auto digit : parts[0])
  41. if (!is_ascii_digit(digit))
  42. return false;
  43. if (parts[1].length() != 3)
  44. return false;
  45. if (!parts[1].starts_with('W'))
  46. return false;
  47. if (!is_ascii_digit(parts[1][1]))
  48. return false;
  49. if (!is_ascii_digit(parts[1][2]))
  50. return false;
  51. u64 year = 0;
  52. for (auto d : parts[0]) {
  53. year *= 10;
  54. year += parse_ascii_digit(d);
  55. }
  56. auto week = (parse_ascii_digit(parts[1][1]) * 10) + parse_ascii_digit(parts[1][2]);
  57. return week >= 1 && week <= week_number_of_the_last_day(year);
  58. }
  59. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string
  60. bool is_valid_month_string(StringView value)
  61. {
  62. // 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:
  63. // 1. Four or more ASCII digits, representing year, where year > 0
  64. // 2. A U+002D HYPHEN-MINUS character (-)
  65. // 3. Two ASCII digits, representing the month month, in the range 1 ≤ month ≤ 12
  66. auto parts = value.split_view('-');
  67. if (parts.size() != 2)
  68. return false;
  69. if (parts[0].length() < 4)
  70. return false;
  71. for (auto digit : parts[0])
  72. if (!is_ascii_digit(digit))
  73. return false;
  74. if (parts[1].length() != 2)
  75. return false;
  76. if (!is_ascii_digit(parts[1][0]))
  77. return false;
  78. if (!is_ascii_digit(parts[1][1]))
  79. return false;
  80. auto month = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]);
  81. return month >= 1 && month <= 12;
  82. }
  83. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
  84. bool is_valid_date_string(StringView value)
  85. {
  86. // 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:
  87. // 1. A valid month string, representing year and month
  88. // 2. A U+002D HYPHEN-MINUS character (-)
  89. // 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
  90. auto parts = value.split_view('-');
  91. if (parts.size() != 3)
  92. return false;
  93. if (!is_valid_month_string(ByteString::formatted("{}-{}", parts[0], parts[1])))
  94. return false;
  95. if (parts[2].length() != 2)
  96. return false;
  97. i64 year = 0;
  98. for (auto d : parts[0]) {
  99. year *= 10;
  100. year += parse_ascii_digit(d);
  101. }
  102. auto month = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]);
  103. i64 day = (parse_ascii_digit(parts[2][0]) * 10) + parse_ascii_digit(parts[2][1]);
  104. return day >= 1 && day <= AK::days_in_month(year, month);
  105. }
  106. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-date-string
  107. WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Date>> parse_date_string(JS::Realm& realm, StringView value)
  108. {
  109. // FIXME: Implement spec compliant date string parsing
  110. auto parts = value.split_view('-');
  111. if (parts.size() >= 3) {
  112. if (auto year = parts.at(0).to_number<u32>(); year.has_value()) {
  113. if (auto month = parts.at(1).to_number<u32>(); month.has_value()) {
  114. if (auto day_of_month = parts.at(2).to_number<u32>(); day_of_month.has_value())
  115. return JS::Date::create(realm, JS::make_date(JS::make_day(*year, *month - 1, *day_of_month), 0));
  116. }
  117. }
  118. }
  119. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Can't parse date string"sv };
  120. }
  121. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
  122. bool is_valid_local_date_and_time_string(StringView value)
  123. {
  124. auto parts_split_by_T = value.split_view('T');
  125. if (parts_split_by_T.size() == 2)
  126. return is_valid_date_string(parts_split_by_T[0]) && is_valid_time_string(parts_split_by_T[1]);
  127. auto parts_split_by_space = value.split_view(' ');
  128. if (parts_split_by_space.size() == 2)
  129. return is_valid_date_string(parts_split_by_space[0]) && is_valid_time_string(parts_split_by_space[1]);
  130. return false;
  131. }
  132. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string
  133. String normalize_local_date_and_time_string(String const& value)
  134. {
  135. VERIFY(value.count(" "sv) == 1);
  136. return MUST(value.replace(" "sv, "T"sv, ReplaceMode::FirstOnly));
  137. }
  138. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-time-string
  139. bool is_valid_time_string(StringView value)
  140. {
  141. // 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:
  142. // 1. Two ASCII digits, representing hour, in the range 0 ≤ hour ≤ 23
  143. // 2. A U+003A COLON character (:)
  144. // 3. Two ASCII digits, representing minute, in the range 0 ≤ minute ≤ 59
  145. // 4. If second is nonzero, or optionally if second is zero:
  146. // 1. A U+003A COLON character (:)
  147. // 2. Two ASCII digits, representing the integer part of second, in the range 0 ≤ s ≤ 59
  148. // 3. If second is not an integer, or optionally if second is an integer:
  149. // 1. A U+002E FULL STOP character (.)
  150. // 2. One, two, or three ASCII digits, representing the fractional part of second
  151. auto parts = value.split_view(':');
  152. if (parts.size() != 2 && parts.size() != 3)
  153. return false;
  154. if (parts[0].length() != 2)
  155. return false;
  156. auto hour = (parse_ascii_digit(parts[0][0]) * 10) + parse_ascii_digit(parts[0][1]);
  157. if (hour > 23)
  158. return false;
  159. if (parts[1].length() != 2)
  160. return false;
  161. auto minute = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]);
  162. if (minute > 59)
  163. return false;
  164. if (parts.size() == 2)
  165. return true;
  166. if (parts[2].length() < 2)
  167. return false;
  168. auto second = (parse_ascii_digit(parts[2][0]) * 10) + parse_ascii_digit(parts[2][1]);
  169. if (second > 59)
  170. return false;
  171. if (parts[2].length() == 2)
  172. return true;
  173. auto second_parts = parts[2].split_view('.');
  174. if (second_parts.size() != 2)
  175. return false;
  176. if (second_parts[1].length() < 1 || second_parts[1].length() > 3)
  177. return false;
  178. for (auto digit : second_parts[1])
  179. if (!is_ascii_digit(digit))
  180. return false;
  181. return true;
  182. }
  183. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-time-string
  184. WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Date>> parse_time_string(JS::Realm& realm, StringView value)
  185. {
  186. // FIXME: Implement spec compliant time string parsing
  187. auto parts = value.split_view(':');
  188. if (parts.size() >= 2) {
  189. if (auto hours = parts.at(0).to_number<u32>(); hours.has_value()) {
  190. if (auto minutes = parts.at(1).to_number<u32>(); minutes.has_value()) {
  191. if (parts.size() >= 3) {
  192. if (auto seconds = parts.at(2).to_number<u32>(); seconds.has_value())
  193. return JS::Date::create(realm, JS::make_time(*hours, *minutes, *seconds, 0));
  194. }
  195. return JS::Date::create(realm, JS::make_date(0, JS::make_time(*hours, *minutes, 0, 0)));
  196. }
  197. }
  198. }
  199. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Can't parse time string"sv };
  200. }
  201. }