AbstractOperations.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/CharacterTypes.h>
  8. #include <AK/DateTimeLexer.h>
  9. #include <LibJS/Runtime/IteratorOperations.h>
  10. #include <LibJS/Runtime/PropertyName.h>
  11. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  12. #include <LibJS/Runtime/Temporal/Calendar.h>
  13. #include <LibJS/Runtime/Temporal/Duration.h>
  14. #include <LibJS/Runtime/Temporal/PlainDate.h>
  15. #include <LibJS/Runtime/Temporal/PlainTime.h>
  16. #include <LibJS/Runtime/Temporal/TimeZone.h>
  17. namespace JS::Temporal {
  18. static Optional<OptionType> to_option_type(Value value)
  19. {
  20. if (value.is_boolean())
  21. return OptionType::Boolean;
  22. if (value.is_string())
  23. return OptionType::String;
  24. if (value.is_number())
  25. return OptionType::Number;
  26. return {};
  27. }
  28. // 13.1 IterableToListOfType ( items, elementTypes ), https://tc39.es/proposal-temporal/#sec-iterabletolistoftype
  29. MarkedValueList iterable_to_list_of_type(GlobalObject& global_object, Value items, Vector<OptionType> const& element_types)
  30. {
  31. auto& vm = global_object.vm();
  32. auto& heap = global_object.heap();
  33. // 1. Let iteratorRecord be ? GetIterator(items, sync).
  34. auto iterator_record = get_iterator(global_object, items, IteratorHint::Sync);
  35. if (vm.exception())
  36. return MarkedValueList { heap };
  37. // 2. Let values be a new empty List.
  38. MarkedValueList values(heap);
  39. // 3. Let next be true.
  40. auto next = true;
  41. // 4. Repeat, while next is not false,
  42. while (next) {
  43. // a. Set next to ? IteratorStep(iteratorRecord).
  44. auto* iterator_result = iterator_step(global_object, *iterator_record);
  45. if (vm.exception())
  46. return MarkedValueList { heap };
  47. next = iterator_result;
  48. // b. If next is not false, then
  49. if (next) {
  50. // i. Let nextValue be ? IteratorValue(next).
  51. auto next_value = iterator_value(global_object, *iterator_result);
  52. if (vm.exception())
  53. return MarkedValueList { heap };
  54. // ii. If Type(nextValue) is not an element of elementTypes, then
  55. if (auto type = to_option_type(next_value); !type.has_value() || !element_types.contains_slow(*type)) {
  56. // 1. Let completion be ThrowCompletion(a newly created TypeError object).
  57. vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
  58. // 2. Return ? IteratorClose(iteratorRecord, completion).
  59. iterator_close(*iterator_record);
  60. return MarkedValueList { heap };
  61. }
  62. // iii. Append nextValue to the end of the List values.
  63. values.append(next_value);
  64. }
  65. }
  66. // 5. Return values.
  67. return values;
  68. }
  69. // 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
  70. Object* get_options_object(GlobalObject& global_object, Value options)
  71. {
  72. auto& vm = global_object.vm();
  73. // 1. If options is undefined, then
  74. if (options.is_undefined()) {
  75. // a. Return ! OrdinaryObjectCreate(null).
  76. return Object::create(global_object, nullptr);
  77. }
  78. // 2. If Type(options) is Object, then
  79. if (options.is_object()) {
  80. // a. Return options.
  81. return &options.as_object();
  82. }
  83. // 3. Throw a TypeError exception.
  84. vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Options");
  85. return {};
  86. }
  87. // 13.3 GetOption ( options, property, types, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption
  88. Value get_option(GlobalObject& global_object, Object& options, PropertyName const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback)
  89. {
  90. VERIFY(property.is_string());
  91. auto& vm = global_object.vm();
  92. // 1. Assert: Type(options) is Object.
  93. // 2. Assert: Each element of types is Boolean, String, or Number.
  94. // 3. Let value be ? Get(options, property).
  95. auto value = options.get(property);
  96. if (vm.exception())
  97. return {};
  98. // 4. If value is undefined, return fallback.
  99. if (value.is_undefined())
  100. return fallback;
  101. OptionType type;
  102. // 5. If types contains Type(value), then
  103. if (auto value_type = to_option_type(value); value_type.has_value() && types.contains_slow(*value_type)) {
  104. // a. Let type be Type(value).
  105. type = *value_type;
  106. }
  107. // 6. Else,
  108. else {
  109. // a. Let type be the last element of types.
  110. type = types.last();
  111. }
  112. // 7. If type is Boolean, then
  113. if (type == OptionType::Boolean) {
  114. // a. Set value to ! ToBoolean(value).
  115. value = Value(value.to_boolean());
  116. }
  117. // 8. Else if type is Number, then
  118. else if (type == OptionType::Number) {
  119. // a. Set value to ? ToNumber(value).
  120. value = value.to_number(global_object);
  121. if (vm.exception())
  122. return {};
  123. // b. If value is NaN, throw a RangeError exception.
  124. if (value.is_nan()) {
  125. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.NaN.as_string(), property.as_string());
  126. return {};
  127. }
  128. }
  129. // 9. Else,
  130. else {
  131. // a. Set value to ? ToString(value).
  132. value = value.to_primitive_string(global_object);
  133. if (vm.exception())
  134. return {};
  135. }
  136. // 10. If values is not empty, then
  137. if (!values.is_empty()) {
  138. VERIFY(value.is_string());
  139. // a. If values does not contain value, throw a RangeError exception.
  140. if (!values.contains_slow(value.as_string().string())) {
  141. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  142. return {};
  143. }
  144. }
  145. // 11. Return value.
  146. return value;
  147. }
  148. // 13.6 ToTemporalOverflow ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
  149. Optional<String> to_temporal_overflow(GlobalObject& global_object, Object& normalized_options)
  150. {
  151. auto& vm = global_object.vm();
  152. // 1. Return ? GetOption(normalizedOptions, "overflow", « String », « "constrain", "reject" », "constrain").
  153. auto option = get_option(global_object, normalized_options, vm.names.overflow, { OptionType::String }, { "constrain"sv, "reject"sv }, js_string(vm, "constrain"));
  154. if (vm.exception())
  155. return {};
  156. VERIFY(option.is_string());
  157. return option.as_string().string();
  158. }
  159. // 13.8 ToTemporalRoundingMode ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingmode
  160. Optional<String> to_temporal_rounding_mode(GlobalObject& global_object, Object& normalized_options, String const& fallback)
  161. {
  162. auto& vm = global_object.vm();
  163. // 1. Return ? GetOption(normalizedOptions, "roundingMode", « String », « "ceil", "floor", "trunc", "halfExpand" », fallback).
  164. auto option = get_option(global_object, normalized_options, vm.names.roundingMode, { OptionType::String }, { "ceil"sv, "floor"sv, "trunc"sv, "halfExpand"sv }, js_string(vm, fallback));
  165. if (vm.exception())
  166. return {};
  167. VERIFY(option.is_string());
  168. return option.as_string().string();
  169. }
  170. // 13.11 ToShowCalendarOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
  171. Optional<String> to_show_calendar_option(GlobalObject& global_object, Object& normalized_options)
  172. {
  173. auto& vm = global_object.vm();
  174. // 1. Return ? GetOption(normalizedOptions, "calendarName", « String », « "auto", "always", "never" », "auto").
  175. auto option = get_option(global_object, normalized_options, vm.names.calendarName, { OptionType::String }, { "auto"sv, "always"sv, "never"sv }, js_string(vm, "auto"sv));
  176. if (vm.exception())
  177. return {};
  178. VERIFY(option.is_string());
  179. return option.as_string().string();
  180. }
  181. // 13.14 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
  182. u64 to_temporal_rounding_increment(GlobalObject& global_object, Object& normalized_options, Optional<double> dividend, bool inclusive)
  183. {
  184. auto& vm = global_object.vm();
  185. double maximum;
  186. // 1. If dividend is undefined, then
  187. if (!dividend.has_value()) {
  188. // a. Let maximum be +∞.
  189. maximum = INFINITY;
  190. }
  191. // 2. Else if inclusive is true, then
  192. else if (inclusive) {
  193. // a. Let maximum be dividend.
  194. maximum = *dividend;
  195. }
  196. // 3. Else if dividend is more than 1, then
  197. else if (*dividend > 1) {
  198. // a. Let maximum be dividend − 1.
  199. maximum = *dividend - 1;
  200. }
  201. // 4. Else,
  202. else {
  203. // a. Let maximum be 1.
  204. maximum = 1;
  205. }
  206. // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", « Number », empty, 1).
  207. auto increment_value = get_option(global_object, normalized_options, vm.names.roundingIncrement, { OptionType::Number }, {}, Value(1));
  208. if (vm.exception())
  209. return {};
  210. VERIFY(increment_value.is_number());
  211. auto increment = increment_value.as_double();
  212. // 6. If increment < 1 or increment > maximum, throw a RangeError exception.
  213. if (increment < 1 || increment > maximum) {
  214. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  215. return {};
  216. }
  217. // 7. Set increment to floor(ℝ(increment)).
  218. auto floored_increment = static_cast<u64>(increment);
  219. // 8. If dividend is not undefined and dividend modulo increment is not zero, then
  220. if (dividend.has_value() && static_cast<u64>(*dividend) % floored_increment != 0) {
  221. // a. Throw a RangeError exception.
  222. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  223. return {};
  224. }
  225. // 9. Return increment.
  226. return floored_increment;
  227. }
  228. // https://tc39.es/proposal-temporal/#table-temporal-singular-and-plural-units
  229. static HashMap<StringView, StringView> plural_to_singular_units = {
  230. { "years"sv, "year"sv },
  231. { "months"sv, "month"sv },
  232. { "weeks"sv, "week"sv },
  233. { "days"sv, "day"sv },
  234. { "hours"sv, "hour"sv },
  235. { "minutes"sv, "minute"sv },
  236. { "seconds"sv, "second"sv },
  237. { "milliseconds"sv, "millisecond"sv },
  238. { "microseconds"sv, "microsecond"sv },
  239. { "nanoseconds"sv, "nanosecond"sv }
  240. };
  241. // 13.18 ToSmallestTemporalUnit ( normalizedOptions, disallowedUnits, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-tosmallesttemporalunit
  242. Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback)
  243. {
  244. auto& vm = global_object.vm();
  245. // 1. Assert: disallowedUnits does not contain fallback.
  246. // 2. Let smallestUnit be ? GetOption(normalizedOptions, "smallestUnit", « String », « "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" », fallback).
  247. auto smallest_unit_value = get_option(global_object, normalized_options, vm.names.smallestUnit, { OptionType::String }, { "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv, "minutes"sv, "second"sv, "seconds"sv, "millisecond"sv, "milliseconds"sv, "microsecond"sv, "microseconds"sv, "nanosecond"sv, "nanoseconds"sv }, fallback.has_value() ? js_string(vm, *fallback) : js_undefined());
  248. if (vm.exception())
  249. return {};
  250. // OPTIMIZATION: We skip the following string-only checks for the fallback to tidy up the code a bit
  251. if (smallest_unit_value.is_undefined())
  252. return {};
  253. VERIFY(smallest_unit_value.is_string());
  254. auto smallest_unit = smallest_unit_value.as_string().string();
  255. // 3. If smallestUnit is in the Plural column of Table 12, then
  256. if (auto singular_unit = plural_to_singular_units.get(smallest_unit); singular_unit.has_value()) {
  257. // a. Set smallestUnit to the corresponding Singular value of the same row.
  258. smallest_unit = singular_unit.value();
  259. }
  260. // 4. If disallowedUnits contains smallestUnit, then
  261. if (disallowed_units.contains_slow(smallest_unit)) {
  262. // a. Throw a RangeError exception.
  263. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, smallest_unit, "smallestUnit");
  264. return {};
  265. }
  266. // 5. Return smallestUnit.
  267. return smallest_unit;
  268. }
  269. // 13.29 ConstrainToRange ( x, minimum, maximum ), https://tc39.es/proposal-temporal/#sec-temporal-constraintorange
  270. double constrain_to_range(double x, double minimum, double maximum)
  271. {
  272. return min(max(x, minimum), maximum);
  273. }
  274. // 13.32 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
  275. BigInt* round_number_to_increment(GlobalObject& global_object, BigInt const& x, u64 increment, String const& rounding_mode)
  276. {
  277. auto& heap = global_object.heap();
  278. // 1. Assert: x and increment are mathematical values.
  279. // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand".
  280. VERIFY(rounding_mode == "ceil" || rounding_mode == "floor" || rounding_mode == "trunc" || rounding_mode == "halfExpand");
  281. // OPTIMIZATION: If the increment is 1 the number is always rounded
  282. if (increment == 1)
  283. return js_bigint(heap, x.big_integer());
  284. auto increment_big_int = Crypto::UnsignedBigInteger::create_from(increment);
  285. // 3. Let quotient be x / increment.
  286. auto division_result = x.big_integer().divided_by(increment_big_int);
  287. // OPTIMIZATION: If theres no remainder there number is already rounded
  288. if (division_result.remainder == Crypto::UnsignedBigInteger { 0 })
  289. return js_bigint(heap, x.big_integer());
  290. Crypto::SignedBigInteger rounded = move(division_result.quotient);
  291. // 4. If roundingMode is "ceil", then
  292. if (rounding_mode == "ceil") {
  293. // a. Let rounded be −floor(−quotient).
  294. if (!division_result.remainder.is_negative())
  295. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  296. }
  297. // 5. Else if roundingMode is "floor", then
  298. else if (rounding_mode == "floor") {
  299. // a. Let rounded be floor(quotient).
  300. if (division_result.remainder.is_negative())
  301. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  302. }
  303. // 6. Else if roundingMode is "trunc", then
  304. else if (rounding_mode == "trunc") {
  305. // a. Let rounded be the integral part of quotient, removing any fractional digits.
  306. // NOTE: This is a no-op
  307. }
  308. // 7. Else,
  309. else {
  310. // a. Let rounded be ! RoundHalfAwayFromZero(quotient).
  311. if (division_result.remainder.multiplied_by(Crypto::UnsignedBigInteger { 2 }).unsigned_value() >= increment_big_int) {
  312. if (division_result.remainder.is_negative())
  313. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  314. else
  315. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  316. }
  317. }
  318. // 8. Return rounded × increment.
  319. return js_bigint(heap, rounded.multiplied_by(increment_big_int));
  320. }
  321. // 13.34 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
  322. Optional<ISODateTime> parse_iso_date_time(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  323. {
  324. auto& vm = global_object.vm();
  325. // 1. Assert: Type(isoString) is String.
  326. // 2. Let year, month, day, hour, minute, second, fraction, and calendar be the parts of isoString produced respectively by the DateYear, DateMonth, DateDay, TimeHour, TimeMinute, TimeSecond, TimeFractionalPart, and CalendarName productions, or undefined if not present.
  327. Optional<StringView> year_part;
  328. Optional<StringView> month_part;
  329. Optional<StringView> day_part;
  330. Optional<StringView> hour_part;
  331. Optional<StringView> minute_part;
  332. Optional<StringView> second_part;
  333. Optional<StringView> fraction_part;
  334. Optional<StringView> calendar_part;
  335. TODO();
  336. // 3. Let year be the part of isoString produced by the DateYear production.
  337. // 4. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS).
  338. String normalized_year;
  339. if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv))
  340. normalized_year = String::formatted("-{}", year_part->substring_view(3));
  341. else
  342. normalized_year = year_part.value_or("");
  343. // 5. Set year to ! ToIntegerOrInfinity(year).
  344. i32 year = Value(js_string(vm, normalized_year)).to_integer_or_infinity(global_object);
  345. u8 month;
  346. // 6. If month is undefined, then
  347. if (!month_part.has_value()) {
  348. // a. Set month to 1.
  349. month = 1;
  350. }
  351. // 7. Else,
  352. else {
  353. // a. Set month to ! ToIntegerOrInfinity(month).
  354. month = *month_part->to_uint<u8>();
  355. }
  356. u8 day;
  357. // 8. If day is undefined, then
  358. if (!day_part.has_value()) {
  359. // a. Set day to 1.
  360. day = 1;
  361. }
  362. // 9. Else,
  363. else {
  364. // a. Set day to ! ToIntegerOrInfinity(day).
  365. day = *day_part->to_uint<u8>();
  366. }
  367. // 10. Set hour to ! ToIntegerOrInfinity(hour).
  368. u8 hour = hour_part->to_uint<u8>().value_or(0);
  369. // 11. Set minute to ! ToIntegerOrInfinity(minute).
  370. u8 minute = minute_part->to_uint<u8>().value_or(0);
  371. // 12. Set second to ! ToIntegerOrInfinity(second).
  372. u8 second = second_part->to_uint<u8>().value_or(0);
  373. // 13. If second is 60, then
  374. if (second == 60) {
  375. // a. Set second to 59.
  376. second = 59;
  377. }
  378. u16 millisecond;
  379. u16 microsecond;
  380. u16 nanosecond;
  381. // 14. If fraction is not undefined, then
  382. if (fraction_part.has_value()) {
  383. // a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  384. auto fraction = String::formatted("{}000000000", *fraction_part);
  385. // b. Let millisecond be the String value equal to the substring of fraction from 0 to 3.
  386. // c. Set millisecond to ! ToIntegerOrInfinity(millisecond).
  387. millisecond = *fraction.substring(0, 3).to_uint<u16>();
  388. // d. Let microsecond be the String value equal to the substring of fraction from 3 to 6.
  389. // e. Set microsecond to ! ToIntegerOrInfinity(microsecond).
  390. microsecond = *fraction.substring(3, 3).to_uint<u16>();
  391. // f. Let nanosecond be the String value equal to the substring of fraction from 6 to 9.
  392. // g. Set nanosecond to ! ToIntegerOrInfinity(nanosecond).
  393. nanosecond = *fraction.substring(6, 3).to_uint<u16>();
  394. }
  395. // 15. Else,
  396. else {
  397. // a. Let millisecond be 0.
  398. millisecond = 0;
  399. // b. Let microsecond be 0.
  400. microsecond = 0;
  401. // c. Let nanosecond be 0.
  402. nanosecond = 0;
  403. }
  404. // 16. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception.
  405. if (!is_valid_iso_date(year, month, day)) {
  406. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidISODate);
  407. return {};
  408. }
  409. // 17. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
  410. if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) {
  411. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTime);
  412. return {};
  413. }
  414. // 18. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }.
  415. return ISODateTime { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond, .calendar = calendar_part.has_value() ? *calendar_part : Optional<String>() };
  416. }
  417. // 13.35 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring
  418. Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string)
  419. {
  420. auto& vm = global_object.vm();
  421. // 1. Assert: Type(isoString) is String.
  422. // 2. If isoString does not satisfy the syntax of a TemporalInstantString (see 13.33), then
  423. // a. Throw a RangeError exception.
  424. // TODO
  425. // 3. Let result be ! ParseISODateTime(isoString).
  426. auto result = parse_iso_date_time(global_object, iso_string);
  427. // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  428. auto time_zone_result = parse_temporal_time_zone_string(global_object, iso_string);
  429. if (vm.exception())
  430. return {};
  431. // 5. Assert: timeZoneResult.[[OffsetString]] is not undefined.
  432. VERIFY(time_zone_result->offset.has_value());
  433. // 6. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[TimeZoneOffsetString]]: timeZoneResult.[[OffsetString]] }.
  434. return TemporalInstant { .year = result->year, .month = result->month, .day = result->day, .hour = result->hour, .minute = result->minute, .second = result->second, .millisecond = result->millisecond, .microsecond = result->microsecond, .nanosecond = result->nanosecond, .time_zone_offset = move(time_zone_result->offset) };
  435. }
  436. // 13.37 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
  437. Optional<String> parse_temporal_calendar_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  438. {
  439. auto& vm = global_object.vm();
  440. // 1. Assert: Type(isoString) is String.
  441. // 2. If isoString does not satisfy the syntax of a TemporalCalendarString (see 13.33), then
  442. // a. Throw a RangeError exception.
  443. // 3. Let id be the part of isoString produced by the CalendarName production, or undefined if not present.
  444. Optional<StringView> id_part;
  445. TODO();
  446. // 4. If id is undefined, then
  447. if (!id_part.has_value()) {
  448. // a. Return "iso8601".
  449. return "iso8601";
  450. }
  451. // 5. If ! IsBuiltinCalendar(id) is false, then
  452. if (!is_builtin_calendar(*id_part)) {
  453. // a. Throw a RangeError exception.
  454. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidCalendarIdentifier, *id_part);
  455. return {};
  456. }
  457. // 6. Return id.
  458. return id_part.value();
  459. }
  460. // 13.38 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring
  461. Optional<TemporalDate> parse_temporal_date_string(GlobalObject& global_object, String const& iso_string)
  462. {
  463. auto& vm = global_object.vm();
  464. // 1. Assert: Type(isoString) is String.
  465. // 2. If isoString does not satisfy the syntax of a TemporalDateString (see 13.33), then
  466. // a. Throw a RangeError exception.
  467. // TODO
  468. // 3. Let result be ? ParseISODateTime(isoString).
  469. auto result = parse_iso_date_time(global_object, iso_string);
  470. if (vm.exception())
  471. return {};
  472. // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  473. return TemporalDate { .year = result->year, .month = result->month, .day = result->day, .calendar = move(result->calendar) };
  474. }
  475. // 13.39 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring
  476. Optional<ISODateTime> parse_temporal_date_time_string(GlobalObject& global_object, String const& iso_string)
  477. {
  478. auto& vm = global_object.vm();
  479. // 1. Assert: Type(isoString) is String.
  480. // 2. If isoString does not satisfy the syntax of a TemporalDateTimeString (see 13.33), then
  481. // a. Throw a RangeError exception.
  482. // TODO
  483. // 3. Let result be ? ParseISODateTime(isoString).
  484. auto result = parse_iso_date_time(global_object, iso_string);
  485. if (vm.exception())
  486. return {};
  487. // 4. Return result.
  488. return result;
  489. }
  490. // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
  491. Optional<TemporalDuration> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string)
  492. {
  493. (void)global_object;
  494. (void)iso_string;
  495. TODO();
  496. }
  497. // 13.43 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring
  498. Optional<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  499. {
  500. auto& vm = global_object.vm();
  501. // 1. Assert: Type(isoString) is String.
  502. // 2. If isoString does not satisfy the syntax of a TemporalTimeString (see 13.33), then
  503. // a. Throw a RangeError exception.
  504. // TODO
  505. // 3. Let result be ? ParseISODateTime(isoString).
  506. auto result = parse_iso_date_time(global_object, iso_string);
  507. if (vm.exception())
  508. return {};
  509. // 4. Return the Record { [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]] }.
  510. return TemporalTime { .hour = result->hour, .minute = result->minute, .second = result->second, .millisecond = result->millisecond, .microsecond = result->microsecond, .nanosecond = result->nanosecond, .calendar = move(result->calendar) };
  511. }
  512. // 13.44 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring
  513. Optional<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  514. {
  515. auto& vm = global_object.vm();
  516. // 1. Assert: Type(isoString) is String.
  517. // 2. If isoString does not satisfy the syntax of a TemporalTimeZoneString (see 13.33), then
  518. // a. Throw a RangeError exception.
  519. // 3. Let z, sign, hours, minutes, seconds, fraction and name be the parts of isoString produced respectively by the UTCDesignator, TimeZoneUTCOffsetSign, TimeZoneUTCOffsetHour, TimeZoneUTCOffsetMinute, TimeZoneUTCOffsetSecond, TimeZoneUTCOffsetFraction, and TimeZoneIANAName productions, or undefined if not present.
  520. Optional<StringView> z_part;
  521. Optional<StringView> sign_part;
  522. Optional<StringView> hours_part;
  523. Optional<StringView> minutes_part;
  524. Optional<StringView> seconds_part;
  525. Optional<StringView> fraction_part;
  526. Optional<StringView> name_part;
  527. TODO();
  528. // 4. If z is not undefined, then
  529. if (z_part.has_value()) {
  530. // a. Return the Record { [[Z]]: "Z", [[OffsetString]]: "+00:00", [[Name]]: undefined }.
  531. return TemporalTimeZone { .z = true, .offset = "+00:00", .name = {} };
  532. }
  533. Optional<String> offset;
  534. // 5. If hours is undefined, then
  535. if (!hours_part.has_value()) {
  536. // a. Let offsetString be undefined.
  537. // NOTE: No-op.
  538. }
  539. // 6. Else,
  540. else {
  541. // a. Assert: sign is not undefined.
  542. VERIFY(sign_part.has_value());
  543. // b. Set hours to ! ToIntegerOrInfinity(hours).
  544. u8 hours = Value(js_string(vm, *hours_part)).to_integer_or_infinity(global_object);
  545. u8 sign;
  546. // c. If sign is the code unit 0x002D (HYPHEN-MINUS) or the code unit 0x2212 (MINUS SIGN), then
  547. if (sign_part->is_one_of("-", "\u2212")) {
  548. // i. Set sign to −1.
  549. sign = -1;
  550. }
  551. // d. Else,
  552. else {
  553. // i. Set sign to 1.
  554. sign = 1;
  555. }
  556. // e. Set minutes to ! ToIntegerOrInfinity(minutes).
  557. u8 minutes = Value(js_string(vm, minutes_part.value_or(""sv))).to_integer_or_infinity(global_object);
  558. // f. Set seconds to ! ToIntegerOrInfinity(seconds).
  559. u8 seconds = Value(js_string(vm, seconds_part.value_or(""sv))).to_integer_or_infinity(global_object);
  560. i32 nanoseconds;
  561. // g. If fraction is not undefined, then
  562. if (fraction_part.has_value()) {
  563. // i. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  564. auto fraction = String::formatted("{}000000000", *fraction_part);
  565. // ii. Let nanoseconds be the String value equal to the substring of fraction from 0 to 9.
  566. // iii. Set nanoseconds to ! ToIntegerOrInfinity(nanoseconds).
  567. nanoseconds = Value(js_string(vm, fraction.substring(0, 9))).to_integer_or_infinity(global_object);
  568. }
  569. // h. Else,
  570. else {
  571. // i. Let nanoseconds be 0.
  572. nanoseconds = 0;
  573. }
  574. // i. Let offsetNanoseconds be sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds).
  575. auto offset_nanoseconds = sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000 + nanoseconds);
  576. // j. Let offsetString be ! FormatTimeZoneOffsetString(offsetNanoseconds).
  577. offset = format_time_zone_offset_string(offset_nanoseconds);
  578. }
  579. Optional<String> name;
  580. // 7. If name is not undefined, then
  581. if (name_part.has_value()) {
  582. // a. If ! IsValidTimeZoneName(name) is false, throw a RangeError exception.
  583. if (!is_valid_time_zone_name(*name_part)) {
  584. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName);
  585. return {};
  586. }
  587. // b. Set name to ! CanonicalizeTimeZoneName(name).
  588. name = canonicalize_time_zone_name(*name_part);
  589. }
  590. // 8. Return the Record { [[Z]]: undefined, [[OffsetString]]: offsetString, [[Name]]: name }.
  591. return TemporalTimeZone { .z = false, .offset = offset, .name = name };
  592. }
  593. // 13.46 ToPositiveIntegerOrInfinity ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-topositiveintegerorinfinity
  594. double to_positive_integer_or_infinity(GlobalObject& global_object, Value argument)
  595. {
  596. auto& vm = global_object.vm();
  597. // 1. Let integer be ? ToIntegerOrInfinity(argument).
  598. auto integer = argument.to_integer_or_infinity(global_object);
  599. if (vm.exception())
  600. return {};
  601. // 2. If integer is -∞𝔽, then
  602. if (Value(integer).is_negative_infinity()) {
  603. // a. Throw a RangeError exception.
  604. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalPropertyMustBePositiveInteger);
  605. return {};
  606. }
  607. // 3. If integer ≤ 0, then
  608. if (integer <= 0) {
  609. // a. Throw a RangeError exception.
  610. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalPropertyMustBePositiveInteger);
  611. return {};
  612. }
  613. // 4. Return integer.
  614. return integer;
  615. }
  616. // 13.48 PrepareTemporalFields ( fields, fieldNames, requiredFields ), https://tc39.es/proposal-temporal/#sec-temporal-preparetemporalfields
  617. Object* prepare_temporal_fields(GlobalObject& global_object, Object& fields, Vector<String> const& field_names, Vector<StringView> const& required_fields)
  618. {
  619. auto& vm = global_object.vm();
  620. // 1. Assert: Type(fields) is Object.
  621. // 2. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
  622. auto* result = Object::create(global_object, global_object.object_prototype());
  623. VERIFY(result);
  624. // 3. For each value property of fieldNames, do
  625. for (auto& property : field_names) {
  626. // a. Let value be ? Get(fields, property).
  627. auto value = fields.get(property);
  628. if (vm.exception())
  629. return {};
  630. // b. If value is undefined, then
  631. if (value.is_undefined()) {
  632. // i. If requiredFields contains property, then
  633. if (required_fields.contains_slow(property)) {
  634. // 1. Throw a TypeError exception.
  635. vm.throw_exception<TypeError>(global_object, ErrorType::TemporalMissingRequiredProperty, property);
  636. return {};
  637. }
  638. // ii. If property is in the Property column of Table 13, then
  639. // NOTE: The other properties in the table are automatically handled as their default value is undefined
  640. if (property.is_one_of("hour", "minute", "second", "millisecond", "microsecond", "nanosecond")) {
  641. // 1. Set value to the corresponding Default value of the same row.
  642. value = Value(0);
  643. }
  644. }
  645. // c. Else,
  646. else {
  647. // i. If property is in the Property column of Table 13 and there is a Conversion value in the same row, then
  648. // 1. Let Conversion represent the abstract operation named by the Conversion value of the same row.
  649. // 2. Set value to ? Conversion(value).
  650. if (property.is_one_of("year", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", "eraYear")) {
  651. value = Value(value.to_integer_or_infinity(global_object));
  652. if (vm.exception())
  653. return {};
  654. } else if (property.is_one_of("month", "day")) {
  655. value = Value(to_positive_integer_or_infinity(global_object, value));
  656. if (vm.exception())
  657. return {};
  658. } else if (property.is_one_of("monthCode", "offset", "era")) {
  659. value = value.to_primitive_string(global_object);
  660. if (vm.exception())
  661. return {};
  662. }
  663. }
  664. // d. Perform ! CreateDataPropertyOrThrow(result, property, value).
  665. result->create_data_property_or_throw(property, value);
  666. }
  667. // 4. Return result.
  668. return result;
  669. }
  670. }