ISO8601.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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-DateSpecYearMonth
  199. bool ISO8601Parser::parse_date_spec_year_month()
  200. {
  201. // DateSpecYearMonth :
  202. // DateYear -[opt] DateMonth
  203. StateTransaction transaction { *this };
  204. if (!parse_date_year())
  205. return false;
  206. m_state.lexer.consume_specific('-');
  207. if (!parse_date_month())
  208. return false;
  209. transaction.commit();
  210. return true;
  211. }
  212. // https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay
  213. bool ISO8601Parser::parse_date_spec_month_day()
  214. {
  215. // TwoDashes :
  216. // --
  217. // DateSpecMonthDay :
  218. // TwoDashes[opt] DateMonth -[opt] DateDay
  219. StateTransaction transaction { *this };
  220. m_state.lexer.consume_specific("--"sv);
  221. if (!parse_date_month())
  222. return false;
  223. m_state.lexer.consume_specific('-');
  224. if (!parse_date_day())
  225. return false;
  226. transaction.commit();
  227. return true;
  228. }
  229. // https://tc39.es/proposal-temporal/#prod-Date
  230. bool ISO8601Parser::parse_date()
  231. {
  232. // Date :
  233. // DateYear - DateMonth - DateDay
  234. // DateYear DateMonth DateDay
  235. StateTransaction transaction { *this };
  236. if (!parse_date_year())
  237. return false;
  238. auto with_dashes = m_state.lexer.consume_specific('-');
  239. if (!parse_date_month())
  240. return false;
  241. if (with_dashes && !m_state.lexer.consume_specific('-'))
  242. return false;
  243. if (!parse_date_day())
  244. return false;
  245. transaction.commit();
  246. return true;
  247. }
  248. // https://tc39.es/proposal-temporal/#prod-TimeHour
  249. bool ISO8601Parser::parse_time_hour()
  250. {
  251. // TimeHour :
  252. // Hour
  253. StateTransaction transaction { *this };
  254. if (!parse_hour())
  255. return false;
  256. m_state.parse_result.time_hour = transaction.parsed_string_view();
  257. transaction.commit();
  258. return true;
  259. }
  260. // https://tc39.es/proposal-temporal/#prod-TimeMinute
  261. bool ISO8601Parser::parse_time_minute()
  262. {
  263. // TimeMinute :
  264. // MinuteSecond
  265. StateTransaction transaction { *this };
  266. if (!parse_minute_second())
  267. return false;
  268. m_state.parse_result.time_minute = transaction.parsed_string_view();
  269. transaction.commit();
  270. return true;
  271. }
  272. // https://tc39.es/proposal-temporal/#prod-TimeSecond
  273. bool ISO8601Parser::parse_time_second()
  274. {
  275. // TimeSecond :
  276. // MinuteSecond
  277. // 60
  278. StateTransaction transaction { *this };
  279. auto success = parse_minute_second()
  280. || m_state.lexer.consume_specific("60"sv);
  281. if (!success)
  282. return false;
  283. m_state.parse_result.time_second = transaction.parsed_string_view();
  284. transaction.commit();
  285. return true;
  286. }
  287. // https://tc39.es/proposal-temporal/#prod-FractionalPart
  288. bool ISO8601Parser::parse_fractional_part()
  289. {
  290. // FractionalPart :
  291. // DecimalDigit DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt]
  292. if (!parse_decimal_digit())
  293. return false;
  294. for (size_t i = 0; i < 8; ++i) {
  295. if (!parse_decimal_digit())
  296. break;
  297. }
  298. return true;
  299. }
  300. // https://tc39.es/proposal-temporal/#prod-TimeFractionalPart
  301. bool ISO8601Parser::parse_time_fractional_part()
  302. {
  303. // TimeFractionalPart :
  304. // FractionalPart
  305. StateTransaction transaction { *this };
  306. if (!parse_fractional_part())
  307. return false;
  308. m_state.parse_result.time_fractional_part = transaction.parsed_string_view();
  309. transaction.commit();
  310. return true;
  311. }
  312. // https://tc39.es/proposal-temporal/#prod-Fraction
  313. bool ISO8601Parser::parse_fraction()
  314. {
  315. // Fraction :
  316. // DecimalSeparator TimeFractionalPart
  317. StateTransaction transaction { *this };
  318. if (!parse_decimal_separator())
  319. return false;
  320. if (!parse_time_fractional_part())
  321. return false;
  322. transaction.commit();
  323. return true;
  324. }
  325. // https://tc39.es/proposal-temporal/#prod-TimeFraction
  326. bool ISO8601Parser::parse_time_fraction()
  327. {
  328. // TimeFraction :
  329. // Fraction
  330. return parse_fraction();
  331. }
  332. // https://tc39.es/proposal-temporal/#prod-TimeZoneOffsetRequired
  333. bool ISO8601Parser::parse_time_zone_offset_required()
  334. {
  335. // TimeZoneOffsetRequired :
  336. // TimeZoneUTCOffset TimeZoneBracketedAnnotation[opt]
  337. return false;
  338. }
  339. // https://tc39.es/proposal-temporal/#prod-TimeZoneNameRequired
  340. bool ISO8601Parser::parse_time_zone_name_required()
  341. {
  342. // TimeZoneNameRequired :
  343. // TimeZoneUTCOffset[opt] TimeZoneBracketedAnnotation
  344. return false;
  345. }
  346. // https://tc39.es/proposal-temporal/#prod-TimeZone
  347. bool ISO8601Parser::parse_time_zone()
  348. {
  349. // TimeZone :
  350. // TimeZoneOffsetRequired
  351. // TimeZoneNameRequired
  352. return parse_time_zone_offset_required()
  353. || parse_time_zone_name_required();
  354. }
  355. // https://tc39.es/proposal-temporal/#prod-CalendarName
  356. bool ISO8601Parser::parse_calendar_name()
  357. {
  358. // CalChar :
  359. // Alpha
  360. // DecimalDigit
  361. // CalendarNameComponent :
  362. // CalChar CalChar CalChar CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt] CalChar[opt]
  363. // CalendarNameTail :
  364. // CalendarNameComponent
  365. // CalendarNameComponent - CalendarNameTail
  366. // CalendarName :
  367. // CalendarNameTail
  368. auto parse_calendar_name_component = [&] {
  369. for (size_t i = 0; i < 8; ++i) {
  370. if (!m_state.lexer.next_is(is_ascii_alphanumeric))
  371. return i > 2;
  372. m_state.lexer.consume();
  373. }
  374. return true;
  375. };
  376. StateTransaction transaction { *this };
  377. do {
  378. if (!parse_calendar_name_component())
  379. return false;
  380. } while (m_state.lexer.consume_specific('-'));
  381. m_state.parse_result.calendar_name = transaction.parsed_string_view();
  382. transaction.commit();
  383. return true;
  384. }
  385. // https://tc39.es/proposal-temporal/#prod-Calendar
  386. bool ISO8601Parser::parse_calendar()
  387. {
  388. // Calendar :
  389. // [u-ca= CalendarName ]
  390. StateTransaction transaction { *this };
  391. if (!m_state.lexer.consume_specific("[u-ca="sv))
  392. return false;
  393. if (!parse_calendar_name())
  394. return false;
  395. if (!m_state.lexer.consume_specific(']'))
  396. return false;
  397. transaction.commit();
  398. return true;
  399. }
  400. // https://tc39.es/proposal-temporal/#prod-TimeSpec
  401. bool ISO8601Parser::parse_time_spec()
  402. {
  403. // TimeSpec :
  404. // TimeHour
  405. // TimeHour : TimeMinute
  406. // TimeHour TimeMinute
  407. // TimeHour : TimeMinute : TimeSecond TimeFraction[opt]
  408. // TimeHour TimeMinute TimeSecond TimeFraction[opt]
  409. StateTransaction transaction { *this };
  410. if (!parse_time_hour())
  411. return false;
  412. if (m_state.lexer.consume_specific(':')) {
  413. if (!parse_time_minute())
  414. return false;
  415. if (m_state.lexer.consume_specific(':')) {
  416. if (!parse_time_second())
  417. return false;
  418. (void)parse_time_fraction();
  419. }
  420. } else if (parse_time_minute()) {
  421. if (parse_time_second())
  422. (void)parse_time_fraction();
  423. }
  424. transaction.commit();
  425. return true;
  426. }
  427. // https://tc39.es/proposal-temporal/#prod-Time
  428. bool ISO8601Parser::parse_time()
  429. {
  430. // Time :
  431. // TimeSpec TimeZone[opt]
  432. if (!parse_time_spec())
  433. return false;
  434. (void)parse_time_zone();
  435. return true;
  436. }
  437. // https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator
  438. bool ISO8601Parser::parse_time_spec_separator()
  439. {
  440. // TimeSpecSeparator :
  441. // DateTimeSeparator TimeSpec
  442. StateTransaction transaction { *this };
  443. if (!parse_date_time_separator())
  444. return false;
  445. if (!parse_time_spec())
  446. return false;
  447. transaction.commit();
  448. return true;
  449. }
  450. // https://tc39.es/proposal-temporal/#prod-DateTime
  451. bool ISO8601Parser::parse_date_time()
  452. {
  453. // DateTime :
  454. // Date TimeSpecSeparator[opt] TimeZone[opt]
  455. if (!parse_date())
  456. return false;
  457. (void)parse_time_spec_separator();
  458. (void)parse_time_zone();
  459. return true;
  460. }
  461. // https://tc39.es/proposal-temporal/#prod-CalendarDateTime
  462. bool ISO8601Parser::parse_calendar_date_time()
  463. {
  464. // CalendarDateTime :
  465. // DateTime Calendar[opt]
  466. if (!parse_date_time())
  467. return false;
  468. (void)parse_calendar();
  469. return true;
  470. }
  471. // https://tc39.es/proposal-temporal/#prod-TemporalDateString
  472. bool ISO8601Parser::parse_temporal_date_string()
  473. {
  474. // TemporalDateString :
  475. // CalendarDateTime
  476. return parse_calendar_date_time();
  477. }
  478. // https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString
  479. bool ISO8601Parser::parse_temporal_date_time_string()
  480. {
  481. // TemporalDateTimeString :
  482. // CalendarDateTime
  483. return parse_calendar_date_time();
  484. }
  485. // https://tc39.es/proposal-temporal/#prod-TemporalMonthDayString
  486. bool ISO8601Parser::parse_temporal_month_day_string()
  487. {
  488. // TemporalMonthDayString :
  489. // DateSpecMonthDay
  490. // DateTime
  491. // NOTE: Reverse order here because `DateSpecMonthDay` can be a subset of `DateTime`,
  492. // so we'd not attempt to parse that but may not exhaust the input string.
  493. return parse_date_time()
  494. || parse_date_spec_month_day();
  495. }
  496. // https://tc39.es/proposal-temporal/#prod-TemporalTimeString
  497. bool ISO8601Parser::parse_temporal_time_string()
  498. {
  499. // TemporalTimeString :
  500. // Time
  501. // DateTime
  502. // NOTE: Reverse order here because `Time` can be a subset of `DateTime`,
  503. // so we'd not attempt to parse that but may not exhaust the input string.
  504. return parse_date_time()
  505. || parse_time();
  506. }
  507. // https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString
  508. bool ISO8601Parser::parse_temporal_year_month_string()
  509. {
  510. // TemporalYearMonthString :
  511. // DateSpecYearMonth
  512. // DateTime
  513. // NOTE: Reverse order here because `DateSpecYearMonth` can be a subset of `DateTime`,
  514. // so we'd not attempt to parse that but may not exhaust the input string.
  515. return parse_date_time()
  516. || parse_date_spec_year_month();
  517. }
  518. // https://tc39.es/proposal-temporal/#prod-TemporalZonedDateTimeString
  519. bool ISO8601Parser::parse_temporal_zoned_date_time_string()
  520. {
  521. // TemporalZonedDateTimeString :
  522. // Date TimeSpecSeparator[opt] TimeZoneNameRequired Calendar[opt]
  523. StateTransaction transaction { *this };
  524. if (!parse_date())
  525. return false;
  526. (void)parse_time_spec_separator();
  527. if (!parse_time_zone_name_required())
  528. return false;
  529. (void)parse_calendar();
  530. transaction.commit();
  531. return true;
  532. }
  533. }
  534. #define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \
  535. __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string) \
  536. __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \
  537. __JS_ENUMERATE(TemporalMonthDayString, parse_temporal_month_day_string) \
  538. __JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string) \
  539. __JS_ENUMERATE(TemporalYearMonthString, parse_temporal_year_month_string) \
  540. __JS_ENUMERATE(TemporalZonedDateTimeString, parse_temporal_zoned_date_time_string)
  541. Optional<ParseResult> parse_iso8601(Production production, StringView input)
  542. {
  543. auto parser = Detail::ISO8601Parser { input };
  544. switch (production) {
  545. #define __JS_ENUMERATE(ProductionName, parse_production) \
  546. case Production::ProductionName: \
  547. if (!parser.parse_production()) \
  548. return {}; \
  549. break;
  550. JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS
  551. #undef __JS_ENUMERATE
  552. default:
  553. VERIFY_NOT_REACHED();
  554. }
  555. // If we parsed successfully but didn't reach the end, the string doesn't match the given production.
  556. if (!parser.lexer().is_eof())
  557. return {};
  558. return parser.parse_result();
  559. }
  560. }