ISO8601.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. /*
  2. * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/CharacterTypes.h>
  7. #include <LibJS/Runtime/Temporal/ISO8601.h>
  8. namespace JS::Temporal {
  9. namespace Detail {
  10. // https://tc39.es/proposal-temporal/#prod-DecimalDigit
  11. bool ISO8601Parser::parse_decimal_digit()
  12. {
  13. // DecimalDigit : one of
  14. // 0 1 2 3 4 5 6 7 8 9
  15. if (m_state.lexer.next_is(is_ascii_digit)) {
  16. m_state.lexer.consume();
  17. return true;
  18. }
  19. return false;
  20. }
  21. // https://tc39.es/proposal-temporal/#prod-NonZeroDigit
  22. bool ISO8601Parser::parse_non_zero_digit()
  23. {
  24. // NonZeroDigit : one of
  25. // 1 2 3 4 5 6 7 8 9
  26. if (m_state.lexer.next_is(is_ascii_digit) && !m_state.lexer.next_is('0')) {
  27. m_state.lexer.consume();
  28. return true;
  29. }
  30. return false;
  31. }
  32. // https://tc39.es/proposal-temporal/#prod-ASCIISign
  33. bool ISO8601Parser::parse_ascii_sign()
  34. {
  35. // ASCIISign : one of
  36. // + -
  37. return m_state.lexer.consume_specific('+')
  38. || m_state.lexer.consume_specific('-');
  39. }
  40. // https://tc39.es/proposal-temporal/#prod-Sign
  41. bool ISO8601Parser::parse_sign()
  42. {
  43. // Sign :
  44. // ASCIISign
  45. // U+2212
  46. StateTransaction transaction { *this };
  47. auto success = parse_ascii_sign()
  48. || m_state.lexer.consume_specific("\xE2\x88\x92"sv);
  49. if (!success)
  50. return false;
  51. m_state.parse_result.sign = transaction.parsed_string_view();
  52. transaction.commit();
  53. return true;
  54. }
  55. // https://tc39.es/proposal-temporal/#prod-Hour
  56. bool ISO8601Parser::parse_hour()
  57. {
  58. // Hour :
  59. // 0 DecimalDigit
  60. // 1 DecimalDigit
  61. // 20
  62. // 21
  63. // 22
  64. // 23
  65. StateTransaction transaction { *this };
  66. if (m_state.lexer.consume_specific('0') || m_state.lexer.consume_specific('1')) {
  67. if (!parse_decimal_digit())
  68. return false;
  69. } else {
  70. auto success = m_state.lexer.consume_specific("20"sv)
  71. || m_state.lexer.consume_specific("21"sv)
  72. || m_state.lexer.consume_specific("22"sv)
  73. || m_state.lexer.consume_specific("23"sv);
  74. if (!success)
  75. return false;
  76. }
  77. transaction.commit();
  78. return true;
  79. }
  80. // https://tc39.es/proposal-temporal/#prod-MinuteSecond
  81. bool ISO8601Parser::parse_minute_second()
  82. {
  83. // MinuteSecond :
  84. // 0 DecimalDigit
  85. // 1 DecimalDigit
  86. // 2 DecimalDigit
  87. // 3 DecimalDigit
  88. // 4 DecimalDigit
  89. // 5 DecimalDigit
  90. StateTransaction transaction { *this };
  91. auto success = m_state.lexer.consume_specific('0')
  92. || m_state.lexer.consume_specific('1')
  93. || m_state.lexer.consume_specific('2')
  94. || m_state.lexer.consume_specific('3')
  95. || m_state.lexer.consume_specific('4')
  96. || m_state.lexer.consume_specific('5');
  97. if (!success)
  98. return false;
  99. if (!parse_decimal_digit())
  100. return false;
  101. transaction.commit();
  102. return true;
  103. }
  104. // https://tc39.es/proposal-temporal/#prod-DecimalSeparator
  105. bool ISO8601Parser::parse_decimal_separator()
  106. {
  107. // DecimalSeparator : one of
  108. // . ,
  109. return m_state.lexer.consume_specific('.')
  110. || m_state.lexer.consume_specific(',');
  111. }
  112. // https://tc39.es/proposal-temporal/#prod-DateTimeSeparator
  113. bool ISO8601Parser::parse_date_time_separator()
  114. {
  115. // DateTimeSeparator :
  116. // <SP>
  117. // T
  118. // t
  119. return m_state.lexer.consume_specific(' ')
  120. || m_state.lexer.consume_specific('T')
  121. || m_state.lexer.consume_specific('t');
  122. }
  123. // https://tc39.es/proposal-temporal/#prod-DateYear
  124. bool ISO8601Parser::parse_date_year()
  125. {
  126. // DateFourDigitYear :
  127. // DecimalDigit DecimalDigit DecimalDigit DecimalDigit
  128. // DateExtendedYear :
  129. // Sign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
  130. // DateYear :
  131. // DateFourDigitYear
  132. // DateExtendedYear
  133. StateTransaction transaction { *this };
  134. if (parse_sign()) {
  135. for (size_t i = 0; i < 6; ++i) {
  136. if (!parse_decimal_digit())
  137. return false;
  138. }
  139. } else {
  140. for (size_t i = 0; i < 4; ++i) {
  141. if (!parse_decimal_digit())
  142. return false;
  143. }
  144. }
  145. m_state.parse_result.date_year = transaction.parsed_string_view();
  146. transaction.commit();
  147. return true;
  148. }
  149. // https://tc39.es/proposal-temporal/#prod-DateMonth
  150. bool ISO8601Parser::parse_date_month()
  151. {
  152. // DateMonth :
  153. // 0 NonZeroDigit
  154. // 10
  155. // 11
  156. // 12
  157. StateTransaction transaction { *this };
  158. if (m_state.lexer.consume_specific('0')) {
  159. if (!parse_non_zero_digit())
  160. return false;
  161. } else {
  162. auto success = m_state.lexer.consume_specific("10"sv)
  163. || m_state.lexer.consume_specific("11"sv)
  164. || m_state.lexer.consume_specific("12"sv);
  165. if (!success)
  166. return false;
  167. }
  168. m_state.parse_result.date_month = transaction.parsed_string_view();
  169. transaction.commit();
  170. return true;
  171. }
  172. // https://tc39.es/proposal-temporal/#prod-DateDay
  173. bool ISO8601Parser::parse_date_day()
  174. {
  175. // DateDay :
  176. // 0 NonZeroDigit
  177. // 1 DecimalDigit
  178. // 2 DecimalDigit
  179. // 30
  180. // 31
  181. StateTransaction transaction { *this };
  182. if (m_state.lexer.consume_specific('0')) {
  183. if (!parse_non_zero_digit())
  184. return false;
  185. } else if (m_state.lexer.consume_specific('1') || m_state.lexer.consume_specific('2')) {
  186. if (!parse_decimal_digit())
  187. return false;
  188. } else {
  189. auto success = m_state.lexer.consume_specific("30"sv)
  190. || m_state.lexer.consume_specific("31"sv);
  191. if (!success)
  192. return false;
  193. }
  194. m_state.parse_result.date_day = transaction.parsed_string_view();
  195. transaction.commit();
  196. return true;
  197. }
  198. // https://tc39.es/proposal-temporal/#prod-Date
  199. bool ISO8601Parser::parse_date()
  200. {
  201. // Date :
  202. // DateYear - DateMonth - DateDay
  203. // DateYear DateMonth DateDay
  204. StateTransaction transaction { *this };
  205. if (!parse_date_year())
  206. return false;
  207. auto with_dashes = m_state.lexer.consume_specific('-');
  208. if (!parse_date_month())
  209. return false;
  210. if (with_dashes && !m_state.lexer.consume_specific('-'))
  211. return false;
  212. if (!parse_date_day())
  213. return false;
  214. transaction.commit();
  215. return true;
  216. }
  217. // https://tc39.es/proposal-temporal/#prod-TimeHour
  218. bool ISO8601Parser::parse_time_hour()
  219. {
  220. // TimeHour :
  221. // Hour
  222. StateTransaction transaction { *this };
  223. if (!parse_hour())
  224. return false;
  225. m_state.parse_result.time_hour = transaction.parsed_string_view();
  226. transaction.commit();
  227. return true;
  228. }
  229. // https://tc39.es/proposal-temporal/#prod-TimeMinute
  230. bool ISO8601Parser::parse_time_minute()
  231. {
  232. // TimeMinute :
  233. // MinuteSecond
  234. StateTransaction transaction { *this };
  235. if (!parse_minute_second())
  236. return false;
  237. m_state.parse_result.time_minute = transaction.parsed_string_view();
  238. transaction.commit();
  239. return true;
  240. }
  241. // https://tc39.es/proposal-temporal/#prod-TimeSecond
  242. bool ISO8601Parser::parse_time_second()
  243. {
  244. // TimeSecond :
  245. // MinuteSecond
  246. // 60
  247. StateTransaction transaction { *this };
  248. auto success = parse_minute_second()
  249. || m_state.lexer.consume_specific("60"sv);
  250. if (!success)
  251. return false;
  252. m_state.parse_result.time_second = transaction.parsed_string_view();
  253. transaction.commit();
  254. return true;
  255. }
  256. // https://tc39.es/proposal-temporal/#prod-FractionalPart
  257. bool ISO8601Parser::parse_fractional_part()
  258. {
  259. // FractionalPart :
  260. // DecimalDigit DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt]
  261. if (!parse_decimal_digit())
  262. return false;
  263. for (size_t i = 0; i < 8; ++i) {
  264. if (!parse_decimal_digit())
  265. break;
  266. }
  267. return true;
  268. }
  269. // https://tc39.es/proposal-temporal/#prod-TimeFractionalPart
  270. bool ISO8601Parser::parse_time_fractional_part()
  271. {
  272. // TimeFractionalPart :
  273. // FractionalPart
  274. StateTransaction transaction { *this };
  275. if (!parse_fractional_part())
  276. return false;
  277. m_state.parse_result.time_fractional_part = transaction.parsed_string_view();
  278. transaction.commit();
  279. return true;
  280. }
  281. // https://tc39.es/proposal-temporal/#prod-Fraction
  282. bool ISO8601Parser::parse_fraction()
  283. {
  284. // Fraction :
  285. // DecimalSeparator TimeFractionalPart
  286. StateTransaction transaction { *this };
  287. if (!parse_decimal_separator())
  288. return false;
  289. if (!parse_time_fractional_part())
  290. return false;
  291. transaction.commit();
  292. return true;
  293. }
  294. // https://tc39.es/proposal-temporal/#prod-TimeFraction
  295. bool ISO8601Parser::parse_time_fraction()
  296. {
  297. // TimeFraction :
  298. // Fraction
  299. return parse_fraction();
  300. }
  301. // https://tc39.es/proposal-temporal/#prod-TimeZoneOffsetRequired
  302. bool ISO8601Parser::parse_time_zone_offset_required()
  303. {
  304. // TimeZoneOffsetRequired :
  305. // TimeZoneUTCOffset TimeZoneBracketedAnnotation[opt]
  306. return false;
  307. }
  308. // https://tc39.es/proposal-temporal/#prod-TimeZoneNameRequired
  309. bool ISO8601Parser::parse_time_zone_name_required()
  310. {
  311. // TimeZoneNameRequired :
  312. // TimeZoneUTCOffset[opt] TimeZoneBracketedAnnotation
  313. return false;
  314. }
  315. // https://tc39.es/proposal-temporal/#prod-TimeZone
  316. bool ISO8601Parser::parse_time_zone()
  317. {
  318. // TimeZone :
  319. // TimeZoneOffsetRequired
  320. // TimeZoneNameRequired
  321. return parse_time_zone_offset_required()
  322. || parse_time_zone_name_required();
  323. }
  324. // https://tc39.es/proposal-temporal/#prod-CalendarName
  325. bool ISO8601Parser::parse_calendar_name()
  326. {
  327. // CalChar :
  328. // Alpha
  329. // DecimalDigit
  330. // CalendarNameComponent :
  331. // CalChar CalChar CalChar CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt]
  332. // CalendarNameTail :
  333. // CalendarNameComponent
  334. // CalendarNameComponent - CalendarNameTail
  335. // CalendarName :
  336. // CalendarNameTail
  337. auto parse_calendar_name_component = [&] {
  338. for (size_t i = 0; i < 8; ++i) {
  339. if (!m_state.lexer.next_is(is_ascii_alphanumeric))
  340. return i > 2;
  341. m_state.lexer.consume();
  342. }
  343. return true;
  344. };
  345. StateTransaction transaction { *this };
  346. do {
  347. if (!parse_calendar_name_component())
  348. return false;
  349. } while (m_state.lexer.consume_specific('-'));
  350. m_state.parse_result.calendar_name = transaction.parsed_string_view();
  351. transaction.commit();
  352. return true;
  353. }
  354. // https://tc39.es/proposal-temporal/#prod-Calendar
  355. bool ISO8601Parser::parse_calendar()
  356. {
  357. // Calendar :
  358. // [u-ca= CalendarName ]
  359. StateTransaction transaction { *this };
  360. if (!m_state.lexer.consume_specific("[u-ca="sv))
  361. return false;
  362. if (!parse_calendar_name())
  363. return false;
  364. if (!m_state.lexer.consume_specific(']'))
  365. return false;
  366. transaction.commit();
  367. return true;
  368. }
  369. // https://tc39.es/proposal-temporal/#prod-TimeSpec
  370. bool ISO8601Parser::parse_time_spec()
  371. {
  372. // TimeSpec :
  373. // TimeHour
  374. // TimeHour : TimeMinute
  375. // TimeHour TimeMinute
  376. // TimeHour : TimeMinute : TimeSecond TimeFraction[opt]
  377. // TimeHour TimeMinute TimeSecond TimeFraction[opt]
  378. StateTransaction transaction { *this };
  379. if (!parse_time_hour())
  380. return false;
  381. if (m_state.lexer.consume_specific(':')) {
  382. if (!parse_time_minute())
  383. return false;
  384. if (m_state.lexer.consume_specific(':')) {
  385. if (!parse_time_second())
  386. return false;
  387. (void)parse_time_fraction();
  388. }
  389. } else if (parse_time_minute()) {
  390. if (parse_time_second())
  391. (void)parse_time_fraction();
  392. }
  393. transaction.commit();
  394. return true;
  395. }
  396. // https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator
  397. bool ISO8601Parser::parse_time_spec_separator()
  398. {
  399. // TimeSpecSeparator :
  400. // DateTimeSeparator TimeSpec
  401. StateTransaction transaction { *this };
  402. if (!parse_date_time_separator())
  403. return false;
  404. if (!parse_time_spec())
  405. return false;
  406. transaction.commit();
  407. return true;
  408. }
  409. // https://tc39.es/proposal-temporal/#prod-DateTime
  410. bool ISO8601Parser::parse_date_time()
  411. {
  412. // DateTime :
  413. // Date TimeSpecSeparator[opt] TimeZone[opt]
  414. if (!parse_date())
  415. return false;
  416. (void)parse_time_spec_separator();
  417. (void)parse_time_zone();
  418. return true;
  419. }
  420. // https://tc39.es/proposal-temporal/#prod-CalendarDateTime
  421. bool ISO8601Parser::parse_calendar_date_time()
  422. {
  423. // CalendarDateTime :
  424. // DateTime Calendar[opt]
  425. if (!parse_date_time())
  426. return false;
  427. (void)parse_calendar();
  428. return true;
  429. }
  430. // https://tc39.es/proposal-temporal/#prod-TemporalDateString
  431. bool ISO8601Parser::parse_temporal_date_string()
  432. {
  433. // TemporalDateString :
  434. // CalendarDateTime
  435. return parse_calendar_date_time();
  436. }
  437. }
  438. #define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \
  439. __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string)
  440. Optional<ParseResult> parse_iso8601(Production production, StringView input)
  441. {
  442. auto parser = Detail::ISO8601Parser { input };
  443. switch (production) {
  444. #define __JS_ENUMERATE(ProductionName, parse_production) \
  445. case Production::ProductionName: \
  446. if (!parser.parse_production()) \
  447. return {}; \
  448. break;
  449. JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS
  450. #undef __JS_ENUMERATE
  451. default:
  452. VERIFY_NOT_REACHED();
  453. }
  454. // If we parsed successfully but didn't reach the end, the string doesn't match the given production.
  455. if (!parser.lexer().is_eof())
  456. return {};
  457. return parser.parse_result();
  458. }
  459. }