AbstractOperations.cpp 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  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 <AK/TypeCasts.h>
  10. #include <AK/Variant.h>
  11. #include <LibJS/Runtime/IteratorOperations.h>
  12. #include <LibJS/Runtime/PropertyName.h>
  13. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  14. #include <LibJS/Runtime/Temporal/Calendar.h>
  15. #include <LibJS/Runtime/Temporal/Duration.h>
  16. #include <LibJS/Runtime/Temporal/PlainDate.h>
  17. #include <LibJS/Runtime/Temporal/PlainDateTime.h>
  18. #include <LibJS/Runtime/Temporal/PlainTime.h>
  19. #include <LibJS/Runtime/Temporal/TimeZone.h>
  20. #include <LibJS/Runtime/Temporal/ZonedDateTime.h>
  21. namespace JS::Temporal {
  22. static Optional<OptionType> to_option_type(Value value)
  23. {
  24. if (value.is_boolean())
  25. return OptionType::Boolean;
  26. if (value.is_string())
  27. return OptionType::String;
  28. if (value.is_number())
  29. return OptionType::Number;
  30. return {};
  31. }
  32. // 13.1 IterableToListOfType ( items, elementTypes ), https://tc39.es/proposal-temporal/#sec-iterabletolistoftype
  33. MarkedValueList iterable_to_list_of_type(GlobalObject& global_object, Value items, Vector<OptionType> const& element_types)
  34. {
  35. auto& vm = global_object.vm();
  36. auto& heap = global_object.heap();
  37. // 1. Let iteratorRecord be ? GetIterator(items, sync).
  38. auto iterator_record = get_iterator(global_object, items, IteratorHint::Sync);
  39. if (vm.exception())
  40. return MarkedValueList { heap };
  41. // 2. Let values be a new empty List.
  42. MarkedValueList values(heap);
  43. // 3. Let next be true.
  44. auto next = true;
  45. // 4. Repeat, while next is not false,
  46. while (next) {
  47. // a. Set next to ? IteratorStep(iteratorRecord).
  48. auto* iterator_result = iterator_step(global_object, *iterator_record);
  49. if (vm.exception())
  50. return MarkedValueList { heap };
  51. next = iterator_result;
  52. // b. If next is not false, then
  53. if (next) {
  54. // i. Let nextValue be ? IteratorValue(next).
  55. auto next_value = iterator_value(global_object, *iterator_result);
  56. if (vm.exception())
  57. return MarkedValueList { heap };
  58. // ii. If Type(nextValue) is not an element of elementTypes, then
  59. if (auto type = to_option_type(next_value); !type.has_value() || !element_types.contains_slow(*type)) {
  60. // 1. Let completion be ThrowCompletion(a newly created TypeError object).
  61. vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
  62. // 2. Return ? IteratorClose(iteratorRecord, completion).
  63. iterator_close(*iterator_record);
  64. return MarkedValueList { heap };
  65. }
  66. // iii. Append nextValue to the end of the List values.
  67. values.append(next_value);
  68. }
  69. }
  70. // 5. Return values.
  71. return values;
  72. }
  73. // 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
  74. Object* get_options_object(GlobalObject& global_object, Value options)
  75. {
  76. auto& vm = global_object.vm();
  77. // 1. If options is undefined, then
  78. if (options.is_undefined()) {
  79. // a. Return ! OrdinaryObjectCreate(null).
  80. return Object::create(global_object, nullptr);
  81. }
  82. // 2. If Type(options) is Object, then
  83. if (options.is_object()) {
  84. // a. Return options.
  85. return &options.as_object();
  86. }
  87. // 3. Throw a TypeError exception.
  88. vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Options");
  89. return {};
  90. }
  91. // 13.3 GetOption ( options, property, types, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption
  92. Value get_option(GlobalObject& global_object, Object const& options, PropertyName const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback)
  93. {
  94. VERIFY(property.is_string());
  95. auto& vm = global_object.vm();
  96. // 1. Assert: Type(options) is Object.
  97. // 2. Assert: Each element of types is Boolean, String, or Number.
  98. // 3. Let value be ? Get(options, property).
  99. auto value = options.get(property);
  100. if (vm.exception())
  101. return {};
  102. // 4. If value is undefined, return fallback.
  103. if (value.is_undefined())
  104. return fallback;
  105. OptionType type;
  106. // 5. If types contains Type(value), then
  107. if (auto value_type = to_option_type(value); value_type.has_value() && types.contains_slow(*value_type)) {
  108. // a. Let type be Type(value).
  109. type = *value_type;
  110. }
  111. // 6. Else,
  112. else {
  113. // a. Let type be the last element of types.
  114. type = types.last();
  115. }
  116. // 7. If type is Boolean, then
  117. if (type == OptionType::Boolean) {
  118. // a. Set value to ! ToBoolean(value).
  119. value = Value(value.to_boolean());
  120. }
  121. // 8. Else if type is Number, then
  122. else if (type == OptionType::Number) {
  123. // a. Set value to ? ToNumber(value).
  124. value = value.to_number(global_object);
  125. if (vm.exception())
  126. return {};
  127. // b. If value is NaN, throw a RangeError exception.
  128. if (value.is_nan()) {
  129. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.NaN.as_string(), property.as_string());
  130. return {};
  131. }
  132. }
  133. // 9. Else,
  134. else {
  135. // a. Set value to ? ToString(value).
  136. value = value.to_primitive_string(global_object);
  137. if (vm.exception())
  138. return {};
  139. }
  140. // 10. If values is not empty, then
  141. if (!values.is_empty()) {
  142. VERIFY(value.is_string());
  143. // a. If values does not contain value, throw a RangeError exception.
  144. if (!values.contains_slow(value.as_string().string())) {
  145. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  146. return {};
  147. }
  148. }
  149. // 11. Return value.
  150. return value;
  151. }
  152. // 13.4 GetStringOrNumberOption ( options, property, stringValues, minimum, maximum, fallback ), https://tc39.es/proposal-temporal/#sec-getstringornumberoption
  153. template<typename NumberType>
  154. Optional<Variant<String, NumberType>> get_string_or_number_option(GlobalObject& global_object, Object const& options, PropertyName const& property, Vector<StringView> const& string_values, NumberType minimum, NumberType maximum, Value fallback)
  155. {
  156. auto& vm = global_object.vm();
  157. // 1. Assert: Type(options) is Object.
  158. // 2. Let value be ? GetOption(options, property, « Number, String », empty, fallback).
  159. auto value = get_option(global_object, options, property, { OptionType::Number, OptionType::String }, {}, fallback);
  160. if (vm.exception())
  161. return {};
  162. // 3. If Type(value) is Number, then
  163. if (value.is_number()) {
  164. // a. If value < minimum or value > maximum, throw a RangeError exception.
  165. if (value.as_double() < minimum || value.as_double() > maximum) {
  166. vm.template throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_double(), property.as_string());
  167. return {};
  168. }
  169. // b. Return floor(ℝ(value)).
  170. return floor(value.as_double());
  171. }
  172. // 4. Assert: Type(value) is String.
  173. VERIFY(value.is_string());
  174. // 5. If stringValues does not contain value, throw a RangeError exception.
  175. if (!string_values.contains_slow(value.as_string().string())) {
  176. vm.template throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  177. return {};
  178. }
  179. // 6. Return value.
  180. return value.as_string().string();
  181. }
  182. // 13.6 ToTemporalOverflow ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
  183. Optional<String> to_temporal_overflow(GlobalObject& global_object, Object const& normalized_options)
  184. {
  185. auto& vm = global_object.vm();
  186. // 1. Return ? GetOption(normalizedOptions, "overflow", « String », « "constrain", "reject" », "constrain").
  187. auto option = get_option(global_object, normalized_options, vm.names.overflow, { OptionType::String }, { "constrain"sv, "reject"sv }, js_string(vm, "constrain"));
  188. if (vm.exception())
  189. return {};
  190. VERIFY(option.is_string());
  191. return option.as_string().string();
  192. }
  193. // 13.8 ToTemporalRoundingMode ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingmode
  194. Optional<String> to_temporal_rounding_mode(GlobalObject& global_object, Object const& normalized_options, String const& fallback)
  195. {
  196. auto& vm = global_object.vm();
  197. // 1. Return ? GetOption(normalizedOptions, "roundingMode", « String », « "ceil", "floor", "trunc", "halfExpand" », fallback).
  198. 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));
  199. if (vm.exception())
  200. return {};
  201. VERIFY(option.is_string());
  202. return option.as_string().string();
  203. }
  204. // 13.11 ToShowCalendarOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
  205. Optional<String> to_show_calendar_option(GlobalObject& global_object, Object const& normalized_options)
  206. {
  207. auto& vm = global_object.vm();
  208. // 1. Return ? GetOption(normalizedOptions, "calendarName", « String », « "auto", "always", "never" », "auto").
  209. auto option = get_option(global_object, normalized_options, vm.names.calendarName, { OptionType::String }, { "auto"sv, "always"sv, "never"sv }, js_string(vm, "auto"sv));
  210. if (vm.exception())
  211. return {};
  212. VERIFY(option.is_string());
  213. return option.as_string().string();
  214. }
  215. // 13.14 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
  216. u64 to_temporal_rounding_increment(GlobalObject& global_object, Object const& normalized_options, Optional<double> dividend, bool inclusive)
  217. {
  218. auto& vm = global_object.vm();
  219. double maximum;
  220. // 1. If dividend is undefined, then
  221. if (!dividend.has_value()) {
  222. // a. Let maximum be +∞.
  223. maximum = INFINITY;
  224. }
  225. // 2. Else if inclusive is true, then
  226. else if (inclusive) {
  227. // a. Let maximum be dividend.
  228. maximum = *dividend;
  229. }
  230. // 3. Else if dividend is more than 1, then
  231. else if (*dividend > 1) {
  232. // a. Let maximum be dividend − 1.
  233. maximum = *dividend - 1;
  234. }
  235. // 4. Else,
  236. else {
  237. // a. Let maximum be 1.
  238. maximum = 1;
  239. }
  240. // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", « Number », empty, 1).
  241. auto increment_value = get_option(global_object, normalized_options, vm.names.roundingIncrement, { OptionType::Number }, {}, Value(1));
  242. if (vm.exception())
  243. return {};
  244. VERIFY(increment_value.is_number());
  245. auto increment = increment_value.as_double();
  246. // 6. If increment < 1 or increment > maximum, throw a RangeError exception.
  247. if (increment < 1 || increment > maximum) {
  248. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  249. return {};
  250. }
  251. // 7. Set increment to floor(ℝ(increment)).
  252. auto floored_increment = static_cast<u64>(increment);
  253. // 8. If dividend is not undefined and dividend modulo increment is not zero, then
  254. if (dividend.has_value() && static_cast<u64>(*dividend) % floored_increment != 0) {
  255. // a. Throw a RangeError exception.
  256. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  257. return {};
  258. }
  259. // 9. Return increment.
  260. return floored_increment;
  261. }
  262. // 13.16 ToSecondsStringPrecision ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecision
  263. Optional<SecondsStringPrecision> to_seconds_string_precision(GlobalObject& global_object, Object const& normalized_options)
  264. {
  265. auto& vm = global_object.vm();
  266. // Let smallestUnit be ? ToSmallestTemporalUnit(normalizedOptions, « "year", "month", "week", "day", "hour" », undefined).
  267. auto smallest_unit = to_smallest_temporal_unit(global_object, normalized_options, { "year"sv, "month"sv, "week"sv, "day"sv, "hour"sv }, {});
  268. if (vm.exception())
  269. return {};
  270. // 2. If smallestUnit is "minute", then
  271. if (smallest_unit == "minute"sv) {
  272. // a. Return the Record { [[Precision]]: "minute", [[Unit]]: "minute", [[Increment]]: 1 }.
  273. return SecondsStringPrecision { .precision = "minute"sv, .unit = "minute"sv, .increment = 1 };
  274. }
  275. // 3. If smallestUnit is "second", then
  276. if (smallest_unit == "second"sv) {
  277. // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
  278. return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
  279. }
  280. // 4. If smallestUnit is "millisecond", then
  281. if (smallest_unit == "millisecond"sv) {
  282. // a. Return the Record { [[Precision]]: 3, [[Unit]]: "millisecond", [[Increment]]: 1 }.
  283. return SecondsStringPrecision { .precision = 3, .unit = "millisecond"sv, .increment = 1 };
  284. }
  285. // 5. If smallestUnit is "microsecond", then
  286. if (smallest_unit == "microsecond"sv) {
  287. // a. Return the Record { [[Precision]]: 6, [[Unit]]: "microsecond", [[Increment]]: 1 }.
  288. return SecondsStringPrecision { .precision = 6, .unit = "microsecond"sv, .increment = 1 };
  289. }
  290. // 6. If smallestUnit is "nanosecond", then
  291. if (smallest_unit == "nanosecond"sv) {
  292. // a. Return the Record { [[Precision]]: 9, [[Unit]]: "nanosecond", [[Increment]]: 1 }.
  293. return SecondsStringPrecision { .precision = 9, .unit = "nanosecond"sv, .increment = 1 };
  294. }
  295. // 7. Assert: smallestUnit is undefined.
  296. VERIFY(!smallest_unit.has_value());
  297. // 8. Let digits be ? GetStringOrNumberOption(normalizedOptions, "fractionalSecondDigits", « "auto" », 0, 9, "auto").
  298. auto digits_variant = get_string_or_number_option<u8>(global_object, normalized_options, vm.names.fractionalSecondDigits, { "auto"sv }, 0, 9, js_string(vm, "auto"sv));
  299. if (vm.exception())
  300. return {};
  301. // 9. If digits is "auto", then
  302. if (digits_variant->has<String>()) {
  303. VERIFY(digits_variant->get<String>() == "auto"sv);
  304. // a. Return the Record { [[Precision]]: "auto", [[Unit]]: "nanosecond", [[Increment]]: 1 }.
  305. return SecondsStringPrecision { .precision = "auto"sv, .unit = "nanosecond"sv, .increment = 1 };
  306. }
  307. auto digits = digits_variant->get<u8>();
  308. // 10. If digits is 0, then
  309. if (digits == 0) {
  310. // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
  311. return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
  312. }
  313. // 11. If digits is 1, 2, or 3, then
  314. if (digits == 1 || digits == 2 || digits == 3) {
  315. // a. Return the Record { [[Precision]]: digits, [[Unit]]: "millisecond", [[Increment]]: 10^(3 − digits) }.
  316. return SecondsStringPrecision { .precision = digits, .unit = "millisecond"sv, .increment = (u32)pow(10, 3 - digits) };
  317. }
  318. // 12. If digits is 4, 5, or 6, then
  319. if (digits == 4 || digits == 5 || digits == 6) {
  320. // a. Return the Record { [[Precision]]: digits, [[Unit]]: "microsecond", [[Increment]]: 10^(6 − digits) }.
  321. return SecondsStringPrecision { .precision = digits, .unit = "microsecond"sv, .increment = (u32)pow(10, 6 - digits) };
  322. }
  323. // 13. Assert: digits is 7, 8, or 9.
  324. VERIFY(digits == 7 || digits == 8 || digits == 9);
  325. // 14. Return the Record { [[Precision]]: digits, [[Unit]]: "nanosecond", [[Increment]]: 10^(9 − digits) }.
  326. return SecondsStringPrecision { .precision = digits, .unit = "nanosecond"sv, .increment = (u32)pow(10, 9 - digits) };
  327. }
  328. // https://tc39.es/proposal-temporal/#table-temporal-singular-and-plural-units
  329. static HashMap<StringView, StringView> plural_to_singular_units = {
  330. { "years"sv, "year"sv },
  331. { "months"sv, "month"sv },
  332. { "weeks"sv, "week"sv },
  333. { "days"sv, "day"sv },
  334. { "hours"sv, "hour"sv },
  335. { "minutes"sv, "minute"sv },
  336. { "seconds"sv, "second"sv },
  337. { "milliseconds"sv, "millisecond"sv },
  338. { "microseconds"sv, "microsecond"sv },
  339. { "nanoseconds"sv, "nanosecond"sv }
  340. };
  341. // 13.17 ToLargestTemporalUnit ( normalizedOptions, disallowedUnits, fallback [ , autoValue ] ), https://tc39.es/proposal-temporal/#sec-temporal-tolargesttemporalunit
  342. Optional<String> to_largest_temporal_unit(GlobalObject& global_object, Object const& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value)
  343. {
  344. auto& vm = global_object.vm();
  345. // 1. Assert: disallowedUnits does not contain fallback.
  346. // 2. Assert: disallowedUnits does not contain "auto".
  347. // 3. Assert: autoValue is not present or fallback is "auto".
  348. VERIFY(!auto_value.has_value() || fallback == "auto"sv);
  349. // 4. Assert: autoValue is not present or disallowedUnits does not contain autoValue.
  350. // 5. Let largestUnit be ? GetOption(normalizedOptions, "largestUnit", « String », « "auto", "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" », fallback).
  351. auto largest_unit_value = get_option(global_object, normalized_options, vm.names.largestUnit, { OptionType::String }, { "auto"sv, "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 }, js_string(vm, fallback));
  352. if (vm.exception())
  353. return {};
  354. auto largest_unit = largest_unit_value.as_string().string();
  355. // 6. If largestUnit is "auto" and autoValue is present, then
  356. if (largest_unit == "auto"sv && auto_value.has_value()) {
  357. // a. Return autoValue.
  358. return auto_value;
  359. }
  360. // 7. If largestUnit is in the Plural column of Table 12, then
  361. if (auto singular_unit = plural_to_singular_units.get(largest_unit); singular_unit.has_value()) {
  362. // a. Set largestUnit to the corresponding Singular value of the same row.
  363. largest_unit = singular_unit.value();
  364. }
  365. // 8. If disallowedUnits contains largestUnit, then
  366. if (disallowed_units.contains_slow(largest_unit)) {
  367. // a. Throw a RangeError exception.
  368. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, largest_unit, vm.names.largestUnit.as_string());
  369. return {};
  370. }
  371. // 9. Return largestUnit.
  372. return largest_unit;
  373. }
  374. // 13.18 ToSmallestTemporalUnit ( normalizedOptions, disallowedUnits, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-tosmallesttemporalunit
  375. Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object const& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback)
  376. {
  377. auto& vm = global_object.vm();
  378. // 1. Assert: disallowedUnits does not contain fallback.
  379. // 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).
  380. 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());
  381. if (vm.exception())
  382. return {};
  383. // OPTIMIZATION: We skip the following string-only checks for the fallback to tidy up the code a bit
  384. if (smallest_unit_value.is_undefined())
  385. return {};
  386. VERIFY(smallest_unit_value.is_string());
  387. auto smallest_unit = smallest_unit_value.as_string().string();
  388. // 3. If smallestUnit is in the Plural column of Table 12, then
  389. if (auto singular_unit = plural_to_singular_units.get(smallest_unit); singular_unit.has_value()) {
  390. // a. Set smallestUnit to the corresponding Singular value of the same row.
  391. smallest_unit = singular_unit.value();
  392. }
  393. // 4. If disallowedUnits contains smallestUnit, then
  394. if (disallowed_units.contains_slow(smallest_unit)) {
  395. // a. Throw a RangeError exception.
  396. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, smallest_unit, vm.names.smallestUnit.as_string());
  397. return {};
  398. }
  399. // 5. Return smallestUnit.
  400. return smallest_unit;
  401. }
  402. // 13.22 ValidateTemporalUnitRange ( largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-validatetemporalunitrange
  403. void validate_temporal_unit_range(GlobalObject& global_object, StringView largest_unit, StringView smallest_unit)
  404. {
  405. auto& vm = global_object.vm();
  406. // 1. If smallestUnit is "year" and largestUnit is not "year", then
  407. if (smallest_unit == "year"sv && largest_unit != "year"sv) {
  408. // a. Throw a RangeError exception.
  409. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  410. return;
  411. }
  412. // 2. If smallestUnit is "month" and largestUnit is not "year" or "month", then
  413. if (smallest_unit == "month"sv && !largest_unit.is_one_of("year"sv, "month"sv)) {
  414. // a. Throw a RangeError exception.
  415. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  416. return;
  417. }
  418. // 3. If smallestUnit is "week" and largestUnit is not one of "year", "month", or "week", then
  419. if (smallest_unit == "week"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv)) {
  420. // a. Throw a RangeError exception.
  421. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  422. return;
  423. }
  424. // 4. If smallestUnit is "day" and largestUnit is not one of "year", "month", "week", or "day", then
  425. if (smallest_unit == "day"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) {
  426. // a. Throw a RangeError exception.
  427. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  428. return;
  429. }
  430. // 5. If smallestUnit is "hour" and largestUnit is not one of "year", "month", "week", "day", or "hour", then
  431. if (smallest_unit == "hour"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv, "hour"sv)) {
  432. // a. Throw a RangeError exception.
  433. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  434. return;
  435. }
  436. // 6. If smallestUnit is "minute" and largestUnit is "second", "millisecond", "microsecond", or "nanosecond", then
  437. if (smallest_unit == "minute"sv && largest_unit.is_one_of("second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
  438. // a. Throw a RangeError exception.
  439. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  440. return;
  441. }
  442. // 7. If smallestUnit is "second" and largestUnit is "millisecond", "microsecond", or "nanosecond", then
  443. if (smallest_unit == "second"sv && largest_unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
  444. // a. Throw a RangeError exception.
  445. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  446. return;
  447. }
  448. // 8. If smallestUnit is "millisecond" and largestUnit is "microsecond" or "nanosecond", then
  449. if (smallest_unit == "millisecond"sv && largest_unit.is_one_of("microsecond"sv, "nanosecond"sv)) {
  450. // a. Throw a RangeError exception.
  451. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  452. return;
  453. }
  454. // 9. If smallestUnit is "microsecond" and largestUnit is "nanosecond", then
  455. if (smallest_unit == "microsecond"sv && largest_unit == "nanosecond"sv) {
  456. // a. Throw a RangeError exception.
  457. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  458. return;
  459. }
  460. }
  461. // 13.23 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits
  462. String larger_of_two_temporal_units(StringView unit1, StringView unit2)
  463. {
  464. // 1. If either u1 or u2 is "year", return "year".
  465. if (unit1 == "year"sv || unit2 == "year"sv)
  466. return "year"sv;
  467. // 2. If either u1 or u2 is "month", return "month".
  468. if (unit1 == "month"sv || unit2 == "month"sv)
  469. return "month"sv;
  470. // 3. If either u1 or u2 is "week", return "week".
  471. if (unit1 == "week"sv || unit2 == "week"sv)
  472. return "week"sv;
  473. // 4. If either u1 or u2 is "day", return "day".
  474. if (unit1 == "day"sv || unit2 == "day"sv)
  475. return "day"sv;
  476. // 5. If either u1 or u2 is "hour", return "hour".
  477. if (unit1 == "hour"sv || unit2 == "hour"sv)
  478. return "hour"sv;
  479. // 6. If either u1 or u2 is "minute", return "minute".
  480. if (unit1 == "minute"sv || unit2 == "minute"sv)
  481. return "minute"sv;
  482. // 7. If either u1 or u2 is "second", return "second".
  483. if (unit1 == "second"sv || unit2 == "second"sv)
  484. return "second"sv;
  485. // 8. If either u1 or u2 is "millisecond", return "millisecond".
  486. if (unit1 == "millisecond"sv || unit2 == "millisecond"sv)
  487. return "millisecond"sv;
  488. // 9. If either u1 or u2 is "microsecond", return "microsecond".
  489. if (unit1 == "microsecond"sv || unit2 == "microsecond"sv)
  490. return "microsecond"sv;
  491. // 10. Return "nanosecond".
  492. return "nanosecond"sv;
  493. }
  494. // 13.25 MaximumTemporalDurationRoundingIncrement ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement
  495. Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit)
  496. {
  497. // 1. If unit is "year", "month", "week", or "day", then
  498. if (unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) {
  499. // a. Return undefined.
  500. return {};
  501. }
  502. // 2. If unit is "hour", then
  503. if (unit == "hour"sv) {
  504. // a. Return 24.
  505. return 24;
  506. }
  507. // 3. If unit is "minute" or "second", then
  508. if (unit.is_one_of("minute"sv, "second"sv)) {
  509. // a. Return 60.
  510. return 60;
  511. }
  512. // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond".
  513. VERIFY(unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv));
  514. // 5. Return 1000.
  515. return 1000;
  516. }
  517. // 13.26 RejectTemporalCalendarType ( object ), https://tc39.es/proposal-temporal/#sec-temporal-rejecttemporalcalendartype
  518. void reject_temporal_calendar_type(GlobalObject& global_object, Object& object)
  519. {
  520. auto& vm = global_object.vm();
  521. // 1. Assert: Type(object) is Object.
  522. // 2. If object has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
  523. if (is<PlainDate>(object) || is<PlainDateTime>(object) || is<PlainMonthDay>(object) || is<PlainTime>(object) || is<PlainYearMonth>(object) || is<ZonedDateTime>(object)) {
  524. // a. Throw a TypeError exception.
  525. vm.throw_exception<TypeError>(global_object, ErrorType::TemporalPlainTimeWithArgumentMustNotHave, "calendar or timeZone");
  526. }
  527. }
  528. // 13.27 FormatSecondsStringPart ( second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatsecondsstringpart
  529. String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision)
  530. {
  531. // 1. Assert: second, millisecond, microsecond and nanosecond are integers.
  532. // Non-standard sanity check
  533. if (precision.has<StringView>())
  534. VERIFY(precision.get<StringView>().is_one_of("minute"sv, "auto"sv));
  535. // 2. If precision is "minute", return "".
  536. if (precision.has<StringView>() && precision.get<StringView>() == "minute"sv)
  537. return String::empty();
  538. // 3. Let secondsString be the string-concatenation of the code unit 0x003A (COLON) and second formatted as a two-digit decimal number, padded to the left with zeroes if necessary.
  539. auto seconds_string = String::formatted(":{:02}", second);
  540. // 4. Let fraction be millisecond × 10^6 + microsecond × 10^3 + nanosecond.
  541. u32 fraction = millisecond * 1'000'000 + microsecond * 1'000 + nanosecond;
  542. String fraction_string;
  543. // 5. If precision is "auto", then
  544. if (precision.has<StringView>() && precision.get<StringView>() == "auto"sv) {
  545. // a. If fraction is 0, return secondsString.
  546. if (fraction == 0)
  547. return seconds_string;
  548. // b. Set fraction to fraction formatted as a nine-digit decimal number, padded to the left with zeroes if necessary.
  549. fraction_string = String::formatted("{:09}", fraction);
  550. // c. Set fraction to the longest possible substring of fraction starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO).
  551. fraction_string = fraction_string.trim("0"sv, TrimMode::Right);
  552. }
  553. // 6. Else,
  554. else {
  555. // a. If precision is 0, return secondsString.
  556. if (precision.get<u8>() == 0)
  557. return seconds_string;
  558. // b. Set fraction to fraction formatted as a nine-digit decimal number, padded to the left with zeroes if necessary.
  559. fraction_string = String::formatted("{:09}", fraction);
  560. // c. Set fraction to the substring of fraction from 0 to precision.
  561. fraction_string = fraction_string.substring(0, precision.get<u8>());
  562. }
  563. // 7. Return the string-concatenation of secondsString, the code unit 0x002E (FULL STOP), and fraction.
  564. return String::formatted("{}.{}", seconds_string, fraction_string);
  565. }
  566. // 13.29 ConstrainToRange ( x, minimum, maximum ), https://tc39.es/proposal-temporal/#sec-temporal-constraintorange
  567. double constrain_to_range(double x, double minimum, double maximum)
  568. {
  569. return min(max(x, minimum), maximum);
  570. }
  571. // 13.32 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
  572. BigInt* round_number_to_increment(GlobalObject& global_object, BigInt const& x, u64 increment, StringView rounding_mode)
  573. {
  574. auto& heap = global_object.heap();
  575. // 1. Assert: x and increment are mathematical values.
  576. // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand".
  577. VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv);
  578. // OPTIMIZATION: If the increment is 1 the number is always rounded
  579. if (increment == 1)
  580. return js_bigint(heap, x.big_integer());
  581. auto increment_big_int = Crypto::UnsignedBigInteger::create_from(increment);
  582. // 3. Let quotient be x / increment.
  583. auto division_result = x.big_integer().divided_by(increment_big_int);
  584. // OPTIMIZATION: If theres no remainder there number is already rounded
  585. if (division_result.remainder == Crypto::UnsignedBigInteger { 0 })
  586. return js_bigint(heap, x.big_integer());
  587. Crypto::SignedBigInteger rounded = move(division_result.quotient);
  588. // 4. If roundingMode is "ceil", then
  589. if (rounding_mode == "ceil"sv) {
  590. // a. Let rounded be −floor(−quotient).
  591. if (!division_result.remainder.is_negative())
  592. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  593. }
  594. // 5. Else if roundingMode is "floor", then
  595. else if (rounding_mode == "floor"sv) {
  596. // a. Let rounded be floor(quotient).
  597. if (division_result.remainder.is_negative())
  598. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  599. }
  600. // 6. Else if roundingMode is "trunc", then
  601. else if (rounding_mode == "trunc"sv) {
  602. // a. Let rounded be the integral part of quotient, removing any fractional digits.
  603. // NOTE: This is a no-op
  604. }
  605. // 7. Else,
  606. else {
  607. // a. Let rounded be ! RoundHalfAwayFromZero(quotient).
  608. if (division_result.remainder.multiplied_by(Crypto::UnsignedBigInteger { 2 }).unsigned_value() >= increment_big_int) {
  609. if (division_result.remainder.is_negative())
  610. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  611. else
  612. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  613. }
  614. }
  615. // 8. Return rounded × increment.
  616. return js_bigint(heap, rounded.multiplied_by(increment_big_int));
  617. }
  618. // 13.34 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
  619. Optional<ISODateTime> parse_iso_date_time(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  620. {
  621. auto& vm = global_object.vm();
  622. // 1. Assert: Type(isoString) is String.
  623. // 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.
  624. Optional<StringView> year_part;
  625. Optional<StringView> month_part;
  626. Optional<StringView> day_part;
  627. Optional<StringView> hour_part;
  628. Optional<StringView> minute_part;
  629. Optional<StringView> second_part;
  630. Optional<StringView> fraction_part;
  631. Optional<StringView> calendar_part;
  632. TODO();
  633. // 3. Let year be the part of isoString produced by the DateYear production.
  634. // 4. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS).
  635. String normalized_year;
  636. if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv))
  637. normalized_year = String::formatted("-{}", year_part->substring_view(3));
  638. else
  639. normalized_year = year_part.value_or("");
  640. // 5. Set year to ! ToIntegerOrInfinity(year).
  641. i32 year = Value(js_string(vm, normalized_year)).to_integer_or_infinity(global_object);
  642. u8 month;
  643. // 6. If month is undefined, then
  644. if (!month_part.has_value()) {
  645. // a. Set month to 1.
  646. month = 1;
  647. }
  648. // 7. Else,
  649. else {
  650. // a. Set month to ! ToIntegerOrInfinity(month).
  651. month = *month_part->to_uint<u8>();
  652. }
  653. u8 day;
  654. // 8. If day is undefined, then
  655. if (!day_part.has_value()) {
  656. // a. Set day to 1.
  657. day = 1;
  658. }
  659. // 9. Else,
  660. else {
  661. // a. Set day to ! ToIntegerOrInfinity(day).
  662. day = *day_part->to_uint<u8>();
  663. }
  664. // 10. Set hour to ! ToIntegerOrInfinity(hour).
  665. u8 hour = hour_part->to_uint<u8>().value_or(0);
  666. // 11. Set minute to ! ToIntegerOrInfinity(minute).
  667. u8 minute = minute_part->to_uint<u8>().value_or(0);
  668. // 12. Set second to ! ToIntegerOrInfinity(second).
  669. u8 second = second_part->to_uint<u8>().value_or(0);
  670. // 13. If second is 60, then
  671. if (second == 60) {
  672. // a. Set second to 59.
  673. second = 59;
  674. }
  675. u16 millisecond;
  676. u16 microsecond;
  677. u16 nanosecond;
  678. // 14. If fraction is not undefined, then
  679. if (fraction_part.has_value()) {
  680. // a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  681. auto fraction = String::formatted("{}000000000", *fraction_part);
  682. // b. Let millisecond be the String value equal to the substring of fraction from 0 to 3.
  683. // c. Set millisecond to ! ToIntegerOrInfinity(millisecond).
  684. millisecond = *fraction.substring(0, 3).to_uint<u16>();
  685. // d. Let microsecond be the String value equal to the substring of fraction from 3 to 6.
  686. // e. Set microsecond to ! ToIntegerOrInfinity(microsecond).
  687. microsecond = *fraction.substring(3, 3).to_uint<u16>();
  688. // f. Let nanosecond be the String value equal to the substring of fraction from 6 to 9.
  689. // g. Set nanosecond to ! ToIntegerOrInfinity(nanosecond).
  690. nanosecond = *fraction.substring(6, 3).to_uint<u16>();
  691. }
  692. // 15. Else,
  693. else {
  694. // a. Let millisecond be 0.
  695. millisecond = 0;
  696. // b. Let microsecond be 0.
  697. microsecond = 0;
  698. // c. Let nanosecond be 0.
  699. nanosecond = 0;
  700. }
  701. // 16. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception.
  702. if (!is_valid_iso_date(year, month, day)) {
  703. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidISODate);
  704. return {};
  705. }
  706. // 17. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
  707. if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) {
  708. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTime);
  709. return {};
  710. }
  711. // 18. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }.
  712. 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>() };
  713. }
  714. // 13.35 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring
  715. Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string)
  716. {
  717. auto& vm = global_object.vm();
  718. // 1. Assert: Type(isoString) is String.
  719. // 2. If isoString does not satisfy the syntax of a TemporalInstantString (see 13.33), then
  720. // a. Throw a RangeError exception.
  721. // TODO
  722. // 3. Let result be ! ParseISODateTime(isoString).
  723. auto result = parse_iso_date_time(global_object, iso_string);
  724. // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  725. auto time_zone_result = parse_temporal_time_zone_string(global_object, iso_string);
  726. if (vm.exception())
  727. return {};
  728. // 5. Let offsetString be timeZoneResult.[[OffsetString]].
  729. auto offset_string = time_zone_result->offset;
  730. // 6. If timeZoneResult.[[Z]] is true, then
  731. if (time_zone_result->z) {
  732. // a. Set offsetString to "+00:00".
  733. offset_string = "+00:00"sv;
  734. }
  735. // 7. Assert: offsetString is not undefined.
  736. VERIFY(offset_string.has_value());
  737. // TODO: This is supposed to use `offset_string`, see https://github.com/tc39/proposal-temporal/pull/1799
  738. // 8. 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]] }.
  739. 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) };
  740. }
  741. // 13.37 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
  742. Optional<String> parse_temporal_calendar_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  743. {
  744. auto& vm = global_object.vm();
  745. // 1. Assert: Type(isoString) is String.
  746. // 2. If isoString does not satisfy the syntax of a TemporalCalendarString (see 13.33), then
  747. // a. Throw a RangeError exception.
  748. // 3. Let id be the part of isoString produced by the CalendarName production, or undefined if not present.
  749. Optional<StringView> id_part;
  750. TODO();
  751. // 4. If id is undefined, then
  752. if (!id_part.has_value()) {
  753. // a. Return "iso8601".
  754. return "iso8601";
  755. }
  756. // 5. If ! IsBuiltinCalendar(id) is false, then
  757. if (!is_builtin_calendar(*id_part)) {
  758. // a. Throw a RangeError exception.
  759. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidCalendarIdentifier, *id_part);
  760. return {};
  761. }
  762. // 6. Return id.
  763. return id_part.value();
  764. }
  765. // 13.38 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring
  766. Optional<TemporalDate> parse_temporal_date_string(GlobalObject& global_object, String const& iso_string)
  767. {
  768. auto& vm = global_object.vm();
  769. // 1. Assert: Type(isoString) is String.
  770. // 2. If isoString does not satisfy the syntax of a TemporalDateString (see 13.33), then
  771. // a. Throw a RangeError exception.
  772. // TODO
  773. // 3. Let result be ? ParseISODateTime(isoString).
  774. auto result = parse_iso_date_time(global_object, iso_string);
  775. if (vm.exception())
  776. return {};
  777. // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  778. return TemporalDate { .year = result->year, .month = result->month, .day = result->day, .calendar = move(result->calendar) };
  779. }
  780. // 13.39 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring
  781. Optional<ISODateTime> parse_temporal_date_time_string(GlobalObject& global_object, String const& iso_string)
  782. {
  783. auto& vm = global_object.vm();
  784. // 1. Assert: Type(isoString) is String.
  785. // 2. If isoString does not satisfy the syntax of a TemporalDateTimeString (see 13.33), then
  786. // a. Throw a RangeError exception.
  787. // TODO
  788. // 3. Let result be ? ParseISODateTime(isoString).
  789. auto result = parse_iso_date_time(global_object, iso_string);
  790. if (vm.exception())
  791. return {};
  792. // 4. Return result.
  793. return result;
  794. }
  795. // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
  796. Optional<TemporalDuration> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string)
  797. {
  798. (void)global_object;
  799. (void)iso_string;
  800. TODO();
  801. }
  802. // 13.43 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring
  803. Optional<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  804. {
  805. auto& vm = global_object.vm();
  806. // 1. Assert: Type(isoString) is String.
  807. // 2. If isoString does not satisfy the syntax of a TemporalTimeString (see 13.33), then
  808. // a. Throw a RangeError exception.
  809. // TODO
  810. // 3. Let result be ? ParseISODateTime(isoString).
  811. auto result = parse_iso_date_time(global_object, iso_string);
  812. if (vm.exception())
  813. return {};
  814. // 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]] }.
  815. return TemporalTime { .hour = result->hour, .minute = result->minute, .second = result->second, .millisecond = result->millisecond, .microsecond = result->microsecond, .nanosecond = result->nanosecond, .calendar = move(result->calendar) };
  816. }
  817. // 13.44 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring
  818. Optional<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  819. {
  820. auto& vm = global_object.vm();
  821. // 1. Assert: Type(isoString) is String.
  822. // 2. If isoString does not satisfy the syntax of a TemporalTimeZoneString (see 13.33), then
  823. // a. Throw a RangeError exception.
  824. // 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.
  825. Optional<StringView> z_part;
  826. Optional<StringView> sign_part;
  827. Optional<StringView> hours_part;
  828. Optional<StringView> minutes_part;
  829. Optional<StringView> seconds_part;
  830. Optional<StringView> fraction_part;
  831. Optional<StringView> name_part;
  832. TODO();
  833. // 4. If z is not undefined, then
  834. if (z_part.has_value()) {
  835. // a. Return the Record { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: name }.
  836. return TemporalTimeZone { .z = true, .offset = {}, .name = name_part.has_value() ? String { *name_part } : Optional<String> {} };
  837. }
  838. Optional<String> offset;
  839. // 5. If hours is undefined, then
  840. if (!hours_part.has_value()) {
  841. // a. Let offsetString be undefined.
  842. // NOTE: No-op.
  843. }
  844. // 6. Else,
  845. else {
  846. // a. Assert: sign is not undefined.
  847. VERIFY(sign_part.has_value());
  848. // b. Set hours to ! ToIntegerOrInfinity(hours).
  849. u8 hours = Value(js_string(vm, *hours_part)).to_integer_or_infinity(global_object);
  850. u8 sign;
  851. // c. If sign is the code unit 0x002D (HYPHEN-MINUS) or the code unit 0x2212 (MINUS SIGN), then
  852. if (sign_part->is_one_of("-", "\u2212")) {
  853. // i. Set sign to −1.
  854. sign = -1;
  855. }
  856. // d. Else,
  857. else {
  858. // i. Set sign to 1.
  859. sign = 1;
  860. }
  861. // e. Set minutes to ! ToIntegerOrInfinity(minutes).
  862. u8 minutes = Value(js_string(vm, minutes_part.value_or(""sv))).to_integer_or_infinity(global_object);
  863. // f. Set seconds to ! ToIntegerOrInfinity(seconds).
  864. u8 seconds = Value(js_string(vm, seconds_part.value_or(""sv))).to_integer_or_infinity(global_object);
  865. i32 nanoseconds;
  866. // g. If fraction is not undefined, then
  867. if (fraction_part.has_value()) {
  868. // i. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  869. auto fraction = String::formatted("{}000000000", *fraction_part);
  870. // ii. Let nanoseconds be the String value equal to the substring of fraction from 0 to 9.
  871. // iii. Set nanoseconds to ! ToIntegerOrInfinity(nanoseconds).
  872. nanoseconds = Value(js_string(vm, fraction.substring(0, 9))).to_integer_or_infinity(global_object);
  873. }
  874. // h. Else,
  875. else {
  876. // i. Let nanoseconds be 0.
  877. nanoseconds = 0;
  878. }
  879. // i. Let offsetNanoseconds be sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds).
  880. auto offset_nanoseconds = sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000 + nanoseconds);
  881. // j. Let offsetString be ! FormatTimeZoneOffsetString(offsetNanoseconds).
  882. offset = format_time_zone_offset_string(offset_nanoseconds);
  883. }
  884. Optional<String> name;
  885. // 7. If name is not undefined, then
  886. if (name_part.has_value()) {
  887. // a. If ! IsValidTimeZoneName(name) is false, throw a RangeError exception.
  888. if (!is_valid_time_zone_name(*name_part)) {
  889. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName);
  890. return {};
  891. }
  892. // b. Set name to ! CanonicalizeTimeZoneName(name).
  893. name = canonicalize_time_zone_name(*name_part);
  894. }
  895. // 8. Return the Record { [[Z]]: false, [[OffsetString]]: offsetString, [[Name]]: name }.
  896. return TemporalTimeZone { .z = false, .offset = offset, .name = name };
  897. }
  898. // 13.45 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring
  899. Optional<TemporalYearMonth> parse_temporal_year_month_string(GlobalObject& global_object, String const& iso_string)
  900. {
  901. auto& vm = global_object.vm();
  902. // 1. Assert: Type(isoString) is String.
  903. // 2. If isoString does not satisfy the syntax of a TemporalYearMonthString (see 13.33), then
  904. // a. Throw a RangeError exception.
  905. // TODO
  906. // 3. Let result be ? ParseISODateTime(isoString).
  907. auto result = parse_iso_date_time(global_object, iso_string);
  908. if (vm.exception())
  909. return {};
  910. // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  911. return TemporalYearMonth { .year = result->year, .month = result->month, .day = result->day, .calendar = move(result->calendar) };
  912. }
  913. // 13.46 ToPositiveInteger ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-topositiveinteger
  914. double to_positive_integer(GlobalObject& global_object, Value argument)
  915. {
  916. auto& vm = global_object.vm();
  917. // 1. Let integer be ? ToIntegerThrowOnInfinity(argument).
  918. auto integer = to_integer_throw_on_infinity(global_object, argument, ErrorType::TemporalPropertyMustBePositiveInteger);
  919. if (vm.exception())
  920. return {};
  921. // 2. If integer ≤ 0, then
  922. if (integer <= 0) {
  923. // a. Throw a RangeError exception.
  924. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalPropertyMustBePositiveInteger);
  925. return {};
  926. }
  927. // 3. Return integer.
  928. return integer;
  929. }
  930. // 13.48 PrepareTemporalFields ( fields, fieldNames, requiredFields ), https://tc39.es/proposal-temporal/#sec-temporal-preparetemporalfields
  931. Object* prepare_temporal_fields(GlobalObject& global_object, Object const& fields, Vector<String> const& field_names, Vector<StringView> const& required_fields)
  932. {
  933. auto& vm = global_object.vm();
  934. // 1. Assert: Type(fields) is Object.
  935. // 2. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
  936. auto* result = Object::create(global_object, global_object.object_prototype());
  937. VERIFY(result);
  938. // 3. For each value property of fieldNames, do
  939. for (auto& property : field_names) {
  940. // a. Let value be ? Get(fields, property).
  941. auto value = fields.get(property);
  942. if (vm.exception())
  943. return {};
  944. // b. If value is undefined, then
  945. if (value.is_undefined()) {
  946. // i. If requiredFields contains property, then
  947. if (required_fields.contains_slow(property)) {
  948. // 1. Throw a TypeError exception.
  949. vm.throw_exception<TypeError>(global_object, ErrorType::TemporalMissingRequiredProperty, property);
  950. return {};
  951. }
  952. // ii. If property is in the Property column of Table 13, then
  953. // NOTE: The other properties in the table are automatically handled as their default value is undefined
  954. if (property.is_one_of("hour", "minute", "second", "millisecond", "microsecond", "nanosecond")) {
  955. // 1. Set value to the corresponding Default value of the same row.
  956. value = Value(0);
  957. }
  958. }
  959. // c. Else,
  960. else {
  961. // i. If property is in the Property column of Table 13 and there is a Conversion value in the same row, then
  962. // 1. Let Conversion represent the abstract operation named by the Conversion value of the same row.
  963. // 2. Set value to ? Conversion(value).
  964. if (property.is_one_of("year", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", "eraYear")) {
  965. value = Value(to_integer_throw_on_infinity(global_object, value, ErrorType::TemporalPropertyMustBeFinite));
  966. if (vm.exception())
  967. return {};
  968. } else if (property.is_one_of("month", "day")) {
  969. value = Value(to_positive_integer(global_object, value));
  970. if (vm.exception())
  971. return {};
  972. } else if (property.is_one_of("monthCode", "offset", "era")) {
  973. value = value.to_primitive_string(global_object);
  974. if (vm.exception())
  975. return {};
  976. }
  977. }
  978. // d. Perform ! CreateDataPropertyOrThrow(result, property, value).
  979. result->create_data_property_or_throw(property, value);
  980. }
  981. // 4. Return result.
  982. return result;
  983. }
  984. }