AbstractOperations.cpp 84 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830
  1. /*
  2. * Copyright (c) 2021-2022, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <AK/CharacterTypes.h>
  9. #include <AK/DateTimeLexer.h>
  10. #include <AK/TypeCasts.h>
  11. #include <AK/Variant.h>
  12. #include <LibJS/Runtime/AbstractOperations.h>
  13. #include <LibJS/Runtime/Completion.h>
  14. #include <LibJS/Runtime/IteratorOperations.h>
  15. #include <LibJS/Runtime/PropertyKey.h>
  16. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  17. #include <LibJS/Runtime/Temporal/Calendar.h>
  18. #include <LibJS/Runtime/Temporal/Duration.h>
  19. #include <LibJS/Runtime/Temporal/ISO8601.h>
  20. #include <LibJS/Runtime/Temporal/PlainDate.h>
  21. #include <LibJS/Runtime/Temporal/PlainDateTime.h>
  22. #include <LibJS/Runtime/Temporal/PlainTime.h>
  23. #include <LibJS/Runtime/Temporal/TimeZone.h>
  24. #include <LibJS/Runtime/Temporal/ZonedDateTime.h>
  25. #include <stdlib.h>
  26. namespace JS::Temporal {
  27. static Optional<OptionType> to_option_type(Value value)
  28. {
  29. if (value.is_boolean())
  30. return OptionType::Boolean;
  31. if (value.is_string())
  32. return OptionType::String;
  33. if (value.is_number())
  34. return OptionType::Number;
  35. return {};
  36. }
  37. // 13.1 IterableToListOfType ( items, elementTypes ), https://tc39.es/proposal-temporal/#sec-iterabletolistoftype
  38. ThrowCompletionOr<MarkedVector<Value>> iterable_to_list_of_type(GlobalObject& global_object, Value items, Vector<OptionType> const& element_types)
  39. {
  40. auto& vm = global_object.vm();
  41. auto& heap = global_object.heap();
  42. // 1. Let iteratorRecord be ? GetIterator(items, sync).
  43. auto iterator_record = TRY(get_iterator(global_object, items, IteratorHint::Sync));
  44. // 2. Let values be a new empty List.
  45. MarkedVector<Value> values(heap);
  46. // 3. Let next be true.
  47. auto next = true;
  48. // 4. Repeat, while next is not false,
  49. while (next) {
  50. // a. Set next to ? IteratorStep(iteratorRecord).
  51. auto* iterator_result = TRY(iterator_step(global_object, iterator_record));
  52. next = iterator_result;
  53. // b. If next is not false, then
  54. if (next) {
  55. // i. Let nextValue be ? IteratorValue(next).
  56. auto next_value = TRY(iterator_value(global_object, *iterator_result));
  57. // ii. If Type(nextValue) is not an element of elementTypes, then
  58. if (auto type = to_option_type(next_value); !type.has_value() || !element_types.contains_slow(*type)) {
  59. // 1. Let completion be ThrowCompletion(a newly created TypeError object).
  60. auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::IterableToListOfTypeInvalidValue, next_value.to_string_without_side_effects());
  61. // 2. Return ? IteratorClose(iteratorRecord, completion).
  62. return iterator_close(global_object, iterator_record, move(completion));
  63. }
  64. // iii. Append nextValue to the end of the List values.
  65. values.append(next_value);
  66. }
  67. }
  68. // 5. Return values.
  69. return { move(values) };
  70. }
  71. // 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
  72. ThrowCompletionOr<Object*> get_options_object(GlobalObject& global_object, Value options)
  73. {
  74. auto& vm = global_object.vm();
  75. // 1. If options is undefined, then
  76. if (options.is_undefined()) {
  77. // a. Return OrdinaryObjectCreate(null).
  78. return Object::create(global_object, nullptr);
  79. }
  80. // 2. If Type(options) is Object, then
  81. if (options.is_object()) {
  82. // a. Return options.
  83. return &options.as_object();
  84. }
  85. // 3. Throw a TypeError exception.
  86. return vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObject, "Options");
  87. }
  88. // 13.3 GetOption ( options, property, types, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption
  89. ThrowCompletionOr<Value> get_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback)
  90. {
  91. VERIFY(property.is_string());
  92. auto& vm = global_object.vm();
  93. // 1. Assert: Type(options) is Object.
  94. // 2. Assert: Each element of types is Boolean, String, or Number.
  95. // 3. Let value be ? Get(options, property).
  96. auto value = TRY(options.get(property));
  97. // 4. If value is undefined, return fallback.
  98. if (value.is_undefined())
  99. return fallback;
  100. OptionType type;
  101. // 5. If types contains Type(value), then
  102. if (auto value_type = to_option_type(value); value_type.has_value() && types.contains_slow(*value_type)) {
  103. // a. Let type be Type(value).
  104. type = *value_type;
  105. }
  106. // 6. Else,
  107. else {
  108. // a. Let type be the last element of types.
  109. type = types.last();
  110. }
  111. // 7. If type is Boolean, then
  112. if (type == OptionType::Boolean) {
  113. // a. Set value to ToBoolean(value).
  114. value = Value(value.to_boolean());
  115. }
  116. // 8. Else if type is Number, then
  117. else if (type == OptionType::Number) {
  118. // a. Set value to ? ToNumber(value).
  119. value = TRY(value.to_number(global_object));
  120. // b. If value is NaN, throw a RangeError exception.
  121. if (value.is_nan())
  122. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.NaN.as_string(), property.as_string());
  123. }
  124. // 9. Else,
  125. else {
  126. // a. Set value to ? ToString(value).
  127. value = TRY(value.to_primitive_string(global_object));
  128. }
  129. // 10. If values is not empty, then
  130. if (!values.is_empty()) {
  131. VERIFY(value.is_string());
  132. // a. If values does not contain value, throw a RangeError exception.
  133. if (!values.contains_slow(value.as_string().string()))
  134. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  135. }
  136. // 11. Return value.
  137. return value;
  138. }
  139. // 13.4 GetStringOrNumberOption ( options, property, stringValues, minimum, maximum, fallback ), https://tc39.es/proposal-temporal/#sec-getstringornumberoption
  140. template<typename NumberType>
  141. ThrowCompletionOr<Variant<String, NumberType>> get_string_or_number_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, Vector<StringView> const& string_values, NumberType minimum, NumberType maximum, Value fallback)
  142. {
  143. auto& vm = global_object.vm();
  144. // 1. Assert: Type(options) is Object.
  145. // 2. Let value be ? GetOption(options, property, « Number, String », empty, fallback).
  146. auto value = TRY(get_option(global_object, options, property, { OptionType::Number, OptionType::String }, {}, fallback));
  147. // 3. If Type(value) is Number, then
  148. if (value.is_number()) {
  149. // a. If value < minimum or value > maximum, throw a RangeError exception.
  150. if (value.as_double() < minimum || value.as_double() > maximum)
  151. return vm.template throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_double(), property.as_string());
  152. // b. Return floor(ℝ(value)).
  153. return static_cast<NumberType>(floor(value.as_double()));
  154. }
  155. // 4. Assert: Type(value) is String.
  156. VERIFY(value.is_string());
  157. // 5. If stringValues does not contain value, throw a RangeError exception.
  158. if (!string_values.contains_slow(value.as_string().string()))
  159. return vm.template throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  160. // 6. Return value.
  161. return value.as_string().string();
  162. }
  163. // 13.5 ToTemporalOverflow ( options ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
  164. ThrowCompletionOr<String> to_temporal_overflow(GlobalObject& global_object, Object const* options)
  165. {
  166. auto& vm = global_object.vm();
  167. // 1. If options is undefined, return "constrain".
  168. if (options == nullptr)
  169. return "constrain"sv;
  170. // 2. Return ? GetOption(options, "overflow", « String », « "constrain", "reject" », "constrain").
  171. auto option = TRY(get_option(global_object, *options, vm.names.overflow, { OptionType::String }, { "constrain"sv, "reject"sv }, js_string(vm, "constrain")));
  172. VERIFY(option.is_string());
  173. return option.as_string().string();
  174. }
  175. // 13.6 ToTemporalDisambiguation ( options ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldisambiguation
  176. ThrowCompletionOr<String> to_temporal_disambiguation(GlobalObject& global_object, Object const* options)
  177. {
  178. auto& vm = global_object.vm();
  179. // 1. If options is undefined, return "compatible".
  180. if (options == nullptr)
  181. return "compatible"sv;
  182. // 2. Return ? GetOption(options, "disambiguation", « String », « "compatible", "earlier", "later", "reject" », "compatible").
  183. auto option = TRY(get_option(global_object, *options, vm.names.disambiguation, { OptionType::String }, { "compatible"sv, "earlier"sv, "later"sv, "reject"sv }, js_string(vm, "compatible")));
  184. VERIFY(option.is_string());
  185. return option.as_string().string();
  186. }
  187. // 13.7 ToTemporalRoundingMode ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingmode
  188. ThrowCompletionOr<String> to_temporal_rounding_mode(GlobalObject& global_object, Object const& normalized_options, String const& fallback)
  189. {
  190. auto& vm = global_object.vm();
  191. // 1. Return ? GetOption(normalizedOptions, "roundingMode", « String », « "ceil", "floor", "trunc", "halfExpand" », fallback).
  192. auto option = TRY(get_option(global_object, normalized_options, vm.names.roundingMode, { OptionType::String }, { "ceil"sv, "floor"sv, "trunc"sv, "halfExpand"sv }, js_string(vm, fallback)));
  193. VERIFY(option.is_string());
  194. return option.as_string().string();
  195. }
  196. // 13.8 NegateTemporalRoundingMode ( roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-negatetemporalroundingmode
  197. StringView negate_temporal_rounding_mode(String const& rounding_mode)
  198. {
  199. // 1. If roundingMode is "ceil", return "floor".
  200. if (rounding_mode == "ceil"sv)
  201. return "floor"sv;
  202. // 2. If roundingMode is "floor", return "ceil".
  203. if (rounding_mode == "floor"sv)
  204. return "ceil"sv;
  205. // 3. Return roundingMode.
  206. return rounding_mode;
  207. }
  208. // 13.9 ToTemporalOffset ( options, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloffset
  209. ThrowCompletionOr<String> to_temporal_offset(GlobalObject& global_object, Object const* options, String const& fallback)
  210. {
  211. auto& vm = global_object.vm();
  212. // 1. If options is undefined, return fallback.
  213. if (options == nullptr)
  214. return fallback;
  215. // 2. Return ? GetOption(options, "offset", « String », « "prefer", "use", "ignore", "reject" », fallback).
  216. auto option = TRY(get_option(global_object, *options, vm.names.offset, { OptionType::String }, { "prefer"sv, "use"sv, "ignore"sv, "reject"sv }, js_string(vm, fallback)));
  217. VERIFY(option.is_string());
  218. return option.as_string().string();
  219. }
  220. // 13.10 ToShowCalendarOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
  221. ThrowCompletionOr<String> to_show_calendar_option(GlobalObject& global_object, Object const& normalized_options)
  222. {
  223. auto& vm = global_object.vm();
  224. // 1. Return ? GetOption(normalizedOptions, "calendarName", « String », « "auto", "always", "never" », "auto").
  225. auto option = TRY(get_option(global_object, normalized_options, vm.names.calendarName, { OptionType::String }, { "auto"sv, "always"sv, "never"sv }, js_string(vm, "auto"sv)));
  226. VERIFY(option.is_string());
  227. return option.as_string().string();
  228. }
  229. // 13.11 ToShowTimeZoneNameOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowtimezonenameoption
  230. ThrowCompletionOr<String> to_show_time_zone_name_option(GlobalObject& global_object, Object const& normalized_options)
  231. {
  232. auto& vm = global_object.vm();
  233. // 1. Return ? GetOption(normalizedOptions, "timeZoneName", « String », « "auto", "never" », "auto").
  234. auto option = TRY(get_option(global_object, normalized_options, vm.names.timeZoneName, { OptionType::String }, { "auto"sv, "never"sv }, js_string(vm, "auto"sv)));
  235. VERIFY(option.is_string());
  236. return option.as_string().string();
  237. }
  238. // 13.12 ToShowOffsetOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowoffsetoption
  239. ThrowCompletionOr<String> to_show_offset_option(GlobalObject& global_object, Object const& normalized_options)
  240. {
  241. auto& vm = global_object.vm();
  242. // 1. Return ? GetOption(normalizedOptions, "offset", « String », « "auto", "never" », "auto").
  243. auto option = TRY(get_option(global_object, normalized_options, vm.names.offset, { OptionType::String }, { "auto"sv, "never"sv }, js_string(vm, "auto"sv)));
  244. VERIFY(option.is_string());
  245. return option.as_string().string();
  246. }
  247. // 13.13 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
  248. ThrowCompletionOr<u64> to_temporal_rounding_increment(GlobalObject& global_object, Object const& normalized_options, Optional<double> dividend, bool inclusive)
  249. {
  250. auto& vm = global_object.vm();
  251. double maximum;
  252. // 1. If dividend is undefined, then
  253. if (!dividend.has_value()) {
  254. // a. Let maximum be +∞𝔽.
  255. maximum = INFINITY;
  256. }
  257. // 2. Else if inclusive is true, then
  258. else if (inclusive) {
  259. // a. Let maximum be 𝔽(dividend).
  260. maximum = *dividend;
  261. }
  262. // 3. Else if dividend is more than 1, then
  263. else if (*dividend > 1) {
  264. // a. Let maximum be 𝔽(dividend - 1).
  265. maximum = *dividend - 1;
  266. }
  267. // 4. Else,
  268. else {
  269. // a. Let maximum be 1𝔽.
  270. maximum = 1;
  271. }
  272. // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", « Number », empty, 1𝔽).
  273. auto increment_value = TRY(get_option(global_object, normalized_options, vm.names.roundingIncrement, { OptionType::Number }, {}, Value(1)));
  274. VERIFY(increment_value.is_number());
  275. auto increment = increment_value.as_double();
  276. // 6. If increment < 1𝔽 or increment > maximum, throw a RangeError exception.
  277. if (increment < 1 || increment > maximum)
  278. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  279. // 7. Set increment to floor(ℝ(increment)).
  280. auto floored_increment = static_cast<u64>(increment);
  281. // 8. If dividend is not undefined and dividend modulo increment is not zero, then
  282. if (dividend.has_value() && static_cast<u64>(*dividend) % floored_increment != 0)
  283. // a. Throw a RangeError exception.
  284. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  285. // 9. Return increment.
  286. return floored_increment;
  287. }
  288. // 13.14 ToTemporalDateTimeRoundingIncrement ( normalizedOptions, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetimeroundingincrement
  289. ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(GlobalObject& global_object, Object const& normalized_options, StringView smallest_unit)
  290. {
  291. double maximum;
  292. // 1. If smallestUnit is "day", then
  293. if (smallest_unit == "day"sv) {
  294. // a. Let maximum be 1.
  295. maximum = 1;
  296. }
  297. // 2. Else if smallestUnit is "hour", then
  298. else if (smallest_unit == "hour"sv) {
  299. // a. Let maximum be 24.
  300. maximum = 24;
  301. }
  302. // 3. Else if smallestUnit is "minute" or "second", then
  303. else if (smallest_unit.is_one_of("minute"sv, "second"sv)) {
  304. // a. Let maximum be 60.
  305. maximum = 60;
  306. }
  307. // 4. Else,
  308. else {
  309. // a. Assert: smallestUnit is "millisecond", "microsecond", or "nanosecond".
  310. VERIFY(smallest_unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv));
  311. // b. Let maximum be 1000.
  312. maximum = 1000;
  313. }
  314. // 5. Return ? ToTemporalRoundingIncrement(normalizedOptions, maximum, false).
  315. return to_temporal_rounding_increment(global_object, normalized_options, maximum, false);
  316. }
  317. // 13.15 ToSecondsStringPrecision ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecision
  318. ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(GlobalObject& global_object, Object const& normalized_options)
  319. {
  320. auto& vm = global_object.vm();
  321. // 1. Let smallestUnit be ? GetTemporalUnit(normalizedOptions, "smallestUnit", time, undefined).
  322. auto smallest_unit = TRY(get_temporal_unit(global_object, normalized_options, vm.names.smallestUnit, UnitGroup::Time, Optional<StringView> {}));
  323. // 2. If smallestUnit is "hour", throw a RangeError exception.
  324. if (smallest_unit == "hour"sv)
  325. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, *smallest_unit, "smallestUnit"sv);
  326. // 2. If smallestUnit is "minute", then
  327. if (smallest_unit == "minute"sv) {
  328. // a. Return the Record { [[Precision]]: "minute", [[Unit]]: "minute", [[Increment]]: 1 }.
  329. return SecondsStringPrecision { .precision = "minute"sv, .unit = "minute"sv, .increment = 1 };
  330. }
  331. // 3. If smallestUnit is "second", then
  332. if (smallest_unit == "second"sv) {
  333. // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
  334. return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
  335. }
  336. // 4. If smallestUnit is "millisecond", then
  337. if (smallest_unit == "millisecond"sv) {
  338. // a. Return the Record { [[Precision]]: 3, [[Unit]]: "millisecond", [[Increment]]: 1 }.
  339. return SecondsStringPrecision { .precision = 3, .unit = "millisecond"sv, .increment = 1 };
  340. }
  341. // 5. If smallestUnit is "microsecond", then
  342. if (smallest_unit == "microsecond"sv) {
  343. // a. Return the Record { [[Precision]]: 6, [[Unit]]: "microsecond", [[Increment]]: 1 }.
  344. return SecondsStringPrecision { .precision = 6, .unit = "microsecond"sv, .increment = 1 };
  345. }
  346. // 6. If smallestUnit is "nanosecond", then
  347. if (smallest_unit == "nanosecond"sv) {
  348. // a. Return the Record { [[Precision]]: 9, [[Unit]]: "nanosecond", [[Increment]]: 1 }.
  349. return SecondsStringPrecision { .precision = 9, .unit = "nanosecond"sv, .increment = 1 };
  350. }
  351. // 7. Assert: smallestUnit is undefined.
  352. VERIFY(!smallest_unit.has_value());
  353. // 8. Let fractionalDigitCount be ? GetStringOrNumberOption(normalizedOptions, "fractionalSecondDigits", « "auto" », 0, 9, "auto").
  354. auto fractional_digit_count_variant = TRY(get_string_or_number_option<u8>(global_object, normalized_options, vm.names.fractionalSecondDigits, { "auto"sv }, 0, 9, js_string(vm, "auto"sv)));
  355. // 9. If fractionalDigitCount is "auto", then
  356. if (fractional_digit_count_variant.has<String>()) {
  357. VERIFY(fractional_digit_count_variant.get<String>() == "auto"sv);
  358. // a. Return the Record { [[Precision]]: "auto", [[Unit]]: "nanosecond", [[Increment]]: 1 }.
  359. return SecondsStringPrecision { .precision = "auto"sv, .unit = "nanosecond"sv, .increment = 1 };
  360. }
  361. auto fractional_digit_count = fractional_digit_count_variant.get<u8>();
  362. // 10. If fractionalDigitCount is 0, then
  363. if (fractional_digit_count == 0) {
  364. // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
  365. return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
  366. }
  367. // 11. If fractionalDigitCount is 1, 2, or 3, then
  368. if (fractional_digit_count == 1 || fractional_digit_count == 2 || fractional_digit_count == 3) {
  369. // a. Return the Record { [[Precision]]: fractionalDigitCount, [[Unit]]: "millisecond", [[Increment]]: 10^(3 - fractionalDigitCount) }.
  370. return SecondsStringPrecision { .precision = fractional_digit_count, .unit = "millisecond"sv, .increment = (u32)pow(10, 3 - fractional_digit_count) };
  371. }
  372. // 12. If fractionalDigitCount is 4, 5, or 6, then
  373. if (fractional_digit_count == 4 || fractional_digit_count == 5 || fractional_digit_count == 6) {
  374. // a. Return the Record { [[Precision]]: fractionalDigitCount, [[Unit]]: "microsecond", [[Increment]]: 10^(6 - fractionalDigitCount) }.
  375. return SecondsStringPrecision { .precision = fractional_digit_count, .unit = "microsecond"sv, .increment = (u32)pow(10, 6 - fractional_digit_count) };
  376. }
  377. // 13. Assert: fractionalDigitCount is 7, 8, or 9.
  378. VERIFY(fractional_digit_count == 7 || fractional_digit_count == 8 || fractional_digit_count == 9);
  379. // 14. Return the Record { [[Precision]]: fractionalDigitCount, [[Unit]]: "nanosecond", [[Increment]]: 10^(9 - fractionalDigitCount) }.
  380. return SecondsStringPrecision { .precision = fractional_digit_count, .unit = "nanosecond"sv, .increment = (u32)pow(10, 9 - fractional_digit_count) };
  381. }
  382. struct TemporalUnit {
  383. StringView singular;
  384. StringView plural;
  385. UnitGroup category;
  386. };
  387. // https://tc39.es/proposal-temporal/#table-temporal-units
  388. static Vector<TemporalUnit> temporal_units = {
  389. { "year"sv, "years"sv, UnitGroup::Date },
  390. { "month"sv, "months"sv, UnitGroup::Date },
  391. { "week"sv, "weeks"sv, UnitGroup::Date },
  392. { "day"sv, "days"sv, UnitGroup::Date },
  393. { "hour"sv, "hours"sv, UnitGroup::Time },
  394. { "minute"sv, "minutes"sv, UnitGroup::Time },
  395. { "second"sv, "seconds"sv, UnitGroup::Time },
  396. { "millisecond"sv, "milliseconds"sv, UnitGroup::Time },
  397. { "microsecond"sv, "microseconds"sv, UnitGroup::Time },
  398. { "nanosecond"sv, "nanoseconds"sv, UnitGroup::Time }
  399. };
  400. // 13.16 GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues ] ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunit
  401. ThrowCompletionOr<Optional<String>> get_temporal_unit(GlobalObject& global_object, Object const& normalized_options, PropertyKey const& key, UnitGroup unit_group, Variant<TemporalUnitRequired, Optional<StringView>> const& default_, Vector<StringView> const& extra_values)
  402. {
  403. auto& vm = global_object.vm();
  404. // 1. Let singularNames be a new empty List.
  405. Vector<StringView> singular_names;
  406. // 2. For each row of Table 13, except the header row, in table order, do
  407. for (auto const& row : temporal_units) {
  408. // a. Let unit be the value in the Singular column of the row.
  409. auto unit = row.singular;
  410. // b. If the Category column of the row is date and unitGroup is date or datetime, append unit to singularNames.
  411. if (row.category == UnitGroup::Date && (unit_group == UnitGroup::Date || unit_group == UnitGroup::DateTime))
  412. singular_names.append(unit);
  413. // c. Else if the Category column of the row is time and unitGroup is time or datetime, append unit to singularNames.
  414. else if (row.category == UnitGroup::Time && (unit_group == UnitGroup::Time || unit_group == UnitGroup::DateTime))
  415. singular_names.append(unit);
  416. }
  417. // 3. If extraValues is present, then
  418. if (!extra_values.is_empty()) {
  419. // a. Set singularNames to the list-concatenation of singularNames and extraValues.
  420. singular_names.extend(extra_values);
  421. }
  422. Value default_value;
  423. // 4. If default is required, then
  424. if (default_.has<TemporalUnitRequired>()) {
  425. // a. Let defaultValue be undefined.
  426. default_value = js_undefined();
  427. }
  428. // 5. Else,
  429. else {
  430. auto default_string = default_.get<Optional<StringView>>();
  431. // a. Let defaultValue be default.
  432. default_value = default_string.has_value() ? js_string(vm, *default_string) : js_undefined();
  433. // b. If defaultValue is not undefined and singularNames does not contain defaultValue, then
  434. if (default_string.has_value() && !singular_names.contains_slow(*default_string)) {
  435. // i. Append defaultValue to singularNames.
  436. singular_names.append(*default_string);
  437. }
  438. }
  439. // 6. Let allowedValues be a copy of singularNames.
  440. auto allowed_values = singular_names;
  441. // 7. For each element singularName of singularNames, do
  442. for (auto const& singular_name : singular_names) {
  443. for (auto const& row : temporal_units) {
  444. // a. If singularName is listed in the Singular column of Table 13, then
  445. if (singular_name == row.singular) {
  446. // i. Let pluralName be the value in the Plural column of the corresponding row.
  447. auto plural_name = row.plural;
  448. // ii. Append pluralName to allowedValues.
  449. allowed_values.append(plural_name);
  450. }
  451. }
  452. }
  453. // 8. NOTE: For each singular Temporal unit name that is contained within allowedValues, the corresponding plural name is also contained within it.
  454. // 9. Let value be ? GetOption(normalizedOptions, key, « String », allowedValues, defaultValue).
  455. auto option_value = TRY(get_option(global_object, normalized_options, key, { OptionType::String }, allowed_values, default_value));
  456. // 10. If value is undefined and default is required, throw a RangeError exception.
  457. if (option_value.is_undefined() && default_.has<TemporalUnitRequired>())
  458. return vm.throw_completion<RangeError>(global_object, ErrorType::IsUndefined, String::formatted("{} option value", key.as_string()));
  459. Optional<String> value = option_value.is_undefined()
  460. ? Optional<String> {}
  461. : option_value.as_string().string();
  462. // 11. If value is listed in the Plural column of Table 13, then
  463. for (auto const& row : temporal_units) {
  464. if (row.plural == value) {
  465. // a. Set value to the value in the Singular column of the corresponding row.
  466. value = row.singular;
  467. }
  468. }
  469. // 12. Return value.
  470. return value;
  471. }
  472. // 13.17 ToRelativeTemporalObject ( options ), https://tc39.es/proposal-temporal/#sec-temporal-torelativetemporalobject
  473. ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object, Object const& options)
  474. {
  475. auto& vm = global_object.vm();
  476. // 1. Assert: Type(options) is Object.
  477. // 2. Let value be ? Get(options, "relativeTo").
  478. auto value = TRY(options.get(vm.names.relativeTo));
  479. // 3. If value is undefined, then
  480. if (value.is_undefined()) {
  481. // a. Return value.
  482. return value;
  483. }
  484. // 4. Let offsetBehaviour be option.
  485. auto offset_behavior = OffsetBehavior::Option;
  486. // 5. Let matchBehaviour be match exactly.
  487. auto match_behavior = MatchBehavior::MatchExactly;
  488. ISODateTime result;
  489. Value offset_string;
  490. Value time_zone;
  491. Object* calendar = nullptr;
  492. // 6. If Type(value) is Object, then
  493. if (value.is_object()) {
  494. auto& value_object = value.as_object();
  495. // a. If value has either an [[InitializedTemporalDate]] or [[InitializedTemporalZonedDateTime]] internal slot, then
  496. if (is<PlainDate>(value_object) || is<ZonedDateTime>(value_object)) {
  497. // i. Return value.
  498. return value;
  499. }
  500. // b. If value has an [[InitializedTemporalDateTime]] internal slot, then
  501. if (is<PlainDateTime>(value_object)) {
  502. auto& plain_date_time = static_cast<PlainDateTime&>(value_object);
  503. // i. Return ? CreateTemporalDate(value.[[ISOYear]], value.[[ISOMonth]], value.[[ISODay]], 0, 0, 0, 0, 0, 0, value.[[Calendar]]).
  504. return TRY(create_temporal_date(global_object, plain_date_time.iso_year(), plain_date_time.iso_month(), plain_date_time.iso_day(), plain_date_time.calendar()));
  505. }
  506. // c. Let calendar be ? GetTemporalCalendarWithISODefault(value).
  507. calendar = TRY(get_temporal_calendar_with_iso_default(global_object, value_object));
  508. // d. Let fieldNames be ? CalendarFields(calendar, « "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year" »).
  509. auto field_names = TRY(calendar_fields(global_object, *calendar, { "day"sv, "hour"sv, "microsecond"sv, "millisecond"sv, "minute"sv, "month"sv, "monthCode"sv, "nanosecond"sv, "second"sv, "year"sv }));
  510. // e. Let fields be ? PrepareTemporalFields(value, fieldNames, «»).
  511. auto* fields = TRY(prepare_temporal_fields(global_object, value_object, field_names, Vector<StringView> {}));
  512. // f. Let dateOptions be OrdinaryObjectCreate(null).
  513. auto* date_options = Object::create(global_object, nullptr);
  514. // g. Perform ! CreateDataPropertyOrThrow(dateOptions, "overflow", "constrain").
  515. MUST(date_options->create_data_property_or_throw(vm.names.overflow, js_string(vm, "constrain"sv)));
  516. // h. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, dateOptions).
  517. result = TRY(interpret_temporal_date_time_fields(global_object, *calendar, *fields, *date_options));
  518. // i. Let offsetString be ? Get(value, "offset").
  519. offset_string = TRY(value_object.get(vm.names.offset));
  520. // j. Let timeZone be ? Get(value, "timeZone").
  521. time_zone = TRY(value_object.get(vm.names.timeZone));
  522. // k. If timeZone is not undefined, then
  523. if (!time_zone.is_undefined()) {
  524. // i. Set timeZone to ? ToTemporalTimeZone(timeZone).
  525. time_zone = TRY(to_temporal_time_zone(global_object, time_zone));
  526. }
  527. // l. If offsetString is undefined, then
  528. if (offset_string.is_undefined()) {
  529. // i. Set offsetBehaviour to wall.
  530. offset_behavior = OffsetBehavior::Wall;
  531. }
  532. }
  533. // 7. Else,
  534. else {
  535. // a. Let string be ? ToString(value).
  536. auto string = TRY(value.to_string(global_object));
  537. // b. Let result be ? ParseTemporalRelativeToString(string).
  538. auto parsed_result = TRY(parse_temporal_relative_to_string(global_object, string));
  539. // NOTE: The ISODateTime struct inside `parsed_result` will be moved into `result` at the end of this path to avoid mismatching names.
  540. // Thus, all remaining references to `result` in this path actually refer to `parsed_result`.
  541. // c. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]).
  542. calendar = TRY(to_temporal_calendar_with_iso_default(global_object, parsed_result.date_time.calendar.has_value() ? js_string(vm, *parsed_result.date_time.calendar) : js_undefined()));
  543. // d. Let offsetString be result.[[TimeZoneOffsetString]].
  544. offset_string = parsed_result.time_zone.offset_string.has_value() ? js_string(vm, *parsed_result.time_zone.offset_string) : js_undefined();
  545. // e. Let timeZoneName be result.[[TimeZoneIANAName]].
  546. auto time_zone_name = parsed_result.time_zone.name;
  547. // f. If timeZoneName is not undefined, then
  548. if (time_zone_name.has_value()) {
  549. // i. If ParseText(StringToCodePoints(timeZoneName), TimeZoneNumericUTCOffset) is a List of errors, then
  550. if (!is_valid_time_zone_numeric_utc_offset_syntax(*time_zone_name)) {
  551. // 1. If IsValidTimeZoneName(timeZoneName) is false, throw a RangeError exception.
  552. if (!is_valid_time_zone_name(*time_zone_name))
  553. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName, *time_zone_name);
  554. // 2. Set timeZoneName to ! CanonicalizeTimeZoneName(timeZoneName).
  555. time_zone_name = canonicalize_time_zone_name(*time_zone_name);
  556. }
  557. // ii. Let timeZone be ! CreateTemporalTimeZone(timeZoneName).
  558. time_zone = MUST(create_temporal_time_zone(global_object, *time_zone_name));
  559. }
  560. // g. Else,
  561. else {
  562. // i. Let timeZone be undefined.
  563. time_zone = js_undefined();
  564. }
  565. // h. If result.[[TimeZoneZ]] is true, then
  566. if (parsed_result.time_zone.z) {
  567. // i. Set offsetBehaviour to exact.
  568. offset_behavior = OffsetBehavior::Exact;
  569. }
  570. // i. Else if offsetString is undefined, then
  571. else if (offset_string.is_undefined()) {
  572. // i. Set offsetBehaviour to wall.
  573. offset_behavior = OffsetBehavior::Wall;
  574. }
  575. // j. Set matchBehaviour to match minutes.
  576. match_behavior = MatchBehavior::MatchMinutes;
  577. // See NOTE above about why this is done.
  578. result = move(parsed_result.date_time);
  579. }
  580. // 8. If timeZone is not undefined, then
  581. if (!time_zone.is_undefined()) {
  582. double offset_ns;
  583. // a. If offsetBehaviour is option, then
  584. if (offset_behavior == OffsetBehavior::Option) {
  585. // i. Set offsetString to ? ToString(offsetString).
  586. // NOTE: offsetString is not used after this path, so we don't need to put this into the original offset_string which is of type JS::Value.
  587. auto actual_offset_string = TRY(offset_string.to_string(global_object));
  588. // ii. Let offsetNs be ? ParseTimeZoneOffsetString(offsetString).
  589. offset_ns = TRY(parse_time_zone_offset_string(global_object, actual_offset_string));
  590. }
  591. // b. Else,
  592. else {
  593. // i. Let offsetNs be 0.
  594. offset_ns = 0;
  595. }
  596. // c. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNs, timeZone, "compatible", "reject", matchBehaviour).
  597. auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, offset_behavior, offset_ns, time_zone, "compatible"sv, "reject"sv, match_behavior));
  598. // d. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
  599. return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone.as_object(), *calendar));
  600. }
  601. // 9. Return ! CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
  602. return TRY(create_temporal_date(global_object, result.year, result.month, result.day, *calendar));
  603. }
  604. // 13.18 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits
  605. StringView larger_of_two_temporal_units(StringView unit1, StringView unit2)
  606. {
  607. // 1. Assert: Both u1 and u2 are listed in the Singular column of Table 13.
  608. // 2. For each row of Table 13, except the header row, in table order, do
  609. for (auto const& row : temporal_units) {
  610. // a. Let unit be the value in the Singular column of the row.
  611. auto unit = row.singular;
  612. // b. If SameValue(u1, unit) is true, return unit.
  613. if (unit1 == unit)
  614. return unit;
  615. // c. If SameValue(u2, unit) is true, return unit.
  616. if (unit2 == unit)
  617. return unit;
  618. }
  619. VERIFY_NOT_REACHED();
  620. }
  621. // 13.19 MergeLargestUnitOption ( options, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-mergelargestunitoption
  622. ThrowCompletionOr<Object*> merge_largest_unit_option(GlobalObject& global_object, Object const* options, String largest_unit)
  623. {
  624. auto& vm = global_object.vm();
  625. // 1. If options is undefined, set options to OrdinaryObjectCreate(null).
  626. if (options == nullptr)
  627. options = Object::create(global_object, nullptr);
  628. // 2. Let merged be OrdinaryObjectCreate(%Object.prototype%).
  629. auto* merged = Object::create(global_object, global_object.object_prototype());
  630. // 3. Let keys be ? EnumerableOwnPropertyNames(options, key).
  631. auto keys = TRY(options->enumerable_own_property_names(Object::PropertyKind::Key));
  632. // 4. For each element nextKey of keys, do
  633. for (auto& key : keys) {
  634. auto next_key = MUST(PropertyKey::from_value(global_object, key));
  635. // a. Let propValue be ? Get(options, nextKey).
  636. auto prop_value = TRY(options->get(next_key));
  637. // b. Perform ! CreateDataPropertyOrThrow(merged, nextKey, propValue).
  638. MUST(merged->create_data_property_or_throw(next_key, prop_value));
  639. }
  640. // 5. Perform ! CreateDataPropertyOrThrow(merged, "largestUnit", largestUnit).
  641. MUST(merged->create_data_property_or_throw(vm.names.largestUnit, js_string(vm, move(largest_unit))));
  642. // 6. Return merged.
  643. return merged;
  644. }
  645. // 13.20 MaximumTemporalDurationRoundingIncrement ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement
  646. Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit)
  647. {
  648. // 1. If unit is "year", "month", "week", or "day", then
  649. if (unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) {
  650. // a. Return undefined.
  651. return {};
  652. }
  653. // 2. If unit is "hour", then
  654. if (unit == "hour"sv) {
  655. // a. Return 24.
  656. return 24;
  657. }
  658. // 3. If unit is "minute" or "second", then
  659. if (unit.is_one_of("minute"sv, "second"sv)) {
  660. // a. Return 60.
  661. return 60;
  662. }
  663. // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond".
  664. VERIFY(unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv));
  665. // 5. Return 1000.
  666. return 1000;
  667. }
  668. // 13.21 RejectObjectWithCalendarOrTimeZone ( object ), https://tc39.es/proposal-temporal/#sec-temporal-rejectobjectwithcalendarortimezone
  669. ThrowCompletionOr<void> reject_object_with_calendar_or_time_zone(GlobalObject& global_object, Object& object)
  670. {
  671. auto& vm = global_object.vm();
  672. // 1. Assert: Type(object) is Object.
  673. // 2. If object has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
  674. if (is<PlainDate>(object) || is<PlainDateTime>(object) || is<PlainMonthDay>(object) || is<PlainTime>(object) || is<PlainYearMonth>(object) || is<ZonedDateTime>(object)) {
  675. // a. Throw a TypeError exception.
  676. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustNotHave, "calendar or timeZone");
  677. }
  678. // 3. Let calendarProperty be ? Get(object, "calendar").
  679. auto calendar_property = TRY(object.get(vm.names.calendar));
  680. // 4. If calendarProperty is not undefined, then
  681. if (!calendar_property.is_undefined()) {
  682. // a. Throw a TypeError exception.
  683. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustNotHave, "calendar");
  684. }
  685. // 5. Let timeZoneProperty be ? Get(object, "timeZone").
  686. auto time_zone_property = TRY(object.get(vm.names.timeZone));
  687. // 6. If timeZoneProperty is not undefined, then
  688. if (!time_zone_property.is_undefined()) {
  689. // a. Throw a TypeError exception.
  690. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustNotHave, "timeZone");
  691. }
  692. return {};
  693. }
  694. // 13.22 FormatSecondsStringPart ( second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatsecondsstringpart
  695. String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision)
  696. {
  697. // 1. Assert: second, millisecond, microsecond and nanosecond are integers.
  698. // Non-standard sanity check
  699. if (precision.has<StringView>())
  700. VERIFY(precision.get<StringView>().is_one_of("minute"sv, "auto"sv));
  701. // 2. If precision is "minute", return "".
  702. if (precision.has<StringView>() && precision.get<StringView>() == "minute"sv)
  703. return String::empty();
  704. // 3. Let secondsString be the string-concatenation of the code unit 0x003A (COLON) and ToZeroPaddedDecimalString(second, 2).
  705. auto seconds_string = String::formatted(":{:02}", second);
  706. // 4. Let fraction be millisecond × 10^6 + microsecond × 10^3 + nanosecond.
  707. u32 fraction = millisecond * 1'000'000 + microsecond * 1'000 + nanosecond;
  708. String fraction_string;
  709. // 5. If precision is "auto", then
  710. if (precision.has<StringView>() && precision.get<StringView>() == "auto"sv) {
  711. // a. If fraction is 0, return secondsString.
  712. if (fraction == 0)
  713. return seconds_string;
  714. // b. Set fraction to ToZeroPaddedDecimalString(fraction, 9).
  715. fraction_string = String::formatted("{:09}", fraction);
  716. // c. Set fraction to the longest possible substring of fraction starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO).
  717. fraction_string = fraction_string.trim("0"sv, TrimMode::Right);
  718. }
  719. // 6. Else,
  720. else {
  721. // a. If precision is 0, return secondsString.
  722. if (precision.get<u8>() == 0)
  723. return seconds_string;
  724. // b. Set fraction to ToZeroPaddedDecimalString(fraction, 9)
  725. fraction_string = String::formatted("{:09}", fraction);
  726. // c. Set fraction to the substring of fraction from 0 to precision.
  727. fraction_string = fraction_string.substring(0, precision.get<u8>());
  728. }
  729. // 7. Return the string-concatenation of secondsString, the code unit 0x002E (FULL STOP), and fraction.
  730. return String::formatted("{}.{}", seconds_string, fraction_string);
  731. }
  732. // 13.24 GetUnsignedRoundingMode ( roundingMode, isNegative ), https://tc39.es/proposal-temporal/#sec-temporal-getunsignedroundingmode
  733. UnsignedRoundingMode get_unsigned_rounding_mode(StringView rounding_mode, bool is_negative)
  734. {
  735. // 1. If isNegative is true, return the specification type in the third column of Table 14 where the first column is roundingMode and the second column is "negative".
  736. if (is_negative) {
  737. if (rounding_mode == "ceil"sv)
  738. return UnsignedRoundingMode::Zero;
  739. if (rounding_mode == "floor"sv)
  740. return UnsignedRoundingMode::Infinity;
  741. if (rounding_mode == "expand"sv)
  742. return UnsignedRoundingMode::Infinity;
  743. if (rounding_mode == "trunc"sv)
  744. return UnsignedRoundingMode::Zero;
  745. if (rounding_mode == "halfCeil"sv)
  746. return UnsignedRoundingMode::HalfZero;
  747. if (rounding_mode == "halfFloor"sv)
  748. return UnsignedRoundingMode::HalfInfinity;
  749. if (rounding_mode == "halfExpand"sv)
  750. return UnsignedRoundingMode::HalfInfinity;
  751. if (rounding_mode == "halfTrunc"sv)
  752. return UnsignedRoundingMode::HalfZero;
  753. if (rounding_mode == "halfEven"sv)
  754. return UnsignedRoundingMode::HalfEven;
  755. VERIFY_NOT_REACHED();
  756. }
  757. // 2. Else, return the specification type in the third column of Table 14 where the first column is roundingMode and the second column is "positive".
  758. else {
  759. if (rounding_mode == "ceil"sv)
  760. return UnsignedRoundingMode::Infinity;
  761. if (rounding_mode == "floor"sv)
  762. return UnsignedRoundingMode::Zero;
  763. if (rounding_mode == "expand"sv)
  764. return UnsignedRoundingMode::Infinity;
  765. if (rounding_mode == "trunc"sv)
  766. return UnsignedRoundingMode::Zero;
  767. if (rounding_mode == "halfCeil"sv)
  768. return UnsignedRoundingMode::HalfInfinity;
  769. if (rounding_mode == "halfFloor"sv)
  770. return UnsignedRoundingMode::HalfZero;
  771. if (rounding_mode == "halfExpand"sv)
  772. return UnsignedRoundingMode::HalfInfinity;
  773. if (rounding_mode == "halfTrunc"sv)
  774. return UnsignedRoundingMode::HalfZero;
  775. if (rounding_mode == "halfEven"sv)
  776. return UnsignedRoundingMode::HalfEven;
  777. VERIFY_NOT_REACHED();
  778. }
  779. }
  780. // NOTE: We have two variants of these functions, one using doubles and one using BigInts - most of the time
  781. // doubles will be fine, but take care to choose the right one. The spec is not very clear about this, as
  782. // it uses mathematical values which can be arbitrarily (but not infinitely) large.
  783. // Incidentally V8's Temporal implementation does the same :^)
  784. // 13.25 ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-applyunsignedroundingmode
  785. double apply_unsigned_rounding_mode(double x, double r1, double r2, Optional<UnsignedRoundingMode> const& unsigned_rounding_mode)
  786. {
  787. // 1. If x is equal to r1, return r1.
  788. if (x == r1)
  789. return r1;
  790. // 2. Assert: r1 < x < r2.
  791. VERIFY(r1 < x && x < r2);
  792. // 3. Assert: unsignedRoundingMode is not undefined.
  793. VERIFY(unsigned_rounding_mode.has_value());
  794. // 4. If unsignedRoundingMode is zero, return r1.
  795. if (unsigned_rounding_mode == UnsignedRoundingMode::Zero)
  796. return r1;
  797. // 5. If unsignedRoundingMode is infinity, return r2.
  798. if (unsigned_rounding_mode == UnsignedRoundingMode::Infinity)
  799. return r2;
  800. // 6. Let d1 be x – r1.
  801. auto d1 = x - r1;
  802. // 7. Let d2 be r2 – x.
  803. auto d2 = r2 - x;
  804. // 8. If d1 < d2, return r1.
  805. if (d1 < d2)
  806. return r1;
  807. // 9. If d2 < d1, return r2.
  808. if (d2 < d1)
  809. return r2;
  810. // 10. Assert: d1 is equal to d2.
  811. VERIFY(d1 == d2);
  812. // 11. If unsignedRoundingMode is half-zero, return r1.
  813. if (unsigned_rounding_mode == UnsignedRoundingMode::HalfZero)
  814. return r1;
  815. // 12. If unsignedRoundingMode is half-infinity, return r2.
  816. if (unsigned_rounding_mode == UnsignedRoundingMode::HalfInfinity)
  817. return r2;
  818. // 13. Assert: unsignedRoundingMode is half-even.
  819. VERIFY(unsigned_rounding_mode == UnsignedRoundingMode::HalfEven);
  820. // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2.
  821. auto cardinality = modulo((r1 / (r2 - r1)), 2);
  822. // 15. If cardinality is 0, return r1.
  823. if (cardinality == 0)
  824. return r1;
  825. // 16. Return r2.
  826. return r2;
  827. }
  828. // 13.25 ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-applyunsignedroundingmode
  829. Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const& x, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, Optional<UnsignedRoundingMode> const& unsigned_rounding_mode, Crypto::UnsignedBigInteger const& increment)
  830. {
  831. // 1. If x is equal to r1, return r1.
  832. if (x.quotient == r1 && x.remainder.unsigned_value().is_zero())
  833. return r1;
  834. // 2. Assert: r1 < x < r2.
  835. // NOTE: Skipped for the sake of performance
  836. // 3. Assert: unsignedRoundingMode is not undefined.
  837. VERIFY(unsigned_rounding_mode.has_value());
  838. // 4. If unsignedRoundingMode is zero, return r1.
  839. if (unsigned_rounding_mode == UnsignedRoundingMode::Zero)
  840. return r1;
  841. // 5. If unsignedRoundingMode is infinity, return r2.
  842. if (unsigned_rounding_mode == UnsignedRoundingMode::Infinity)
  843. return r2;
  844. // 6. Let d1 be x – r1.
  845. auto d1 = x.remainder.unsigned_value();
  846. // 7. Let d2 be r2 – x.
  847. auto d2 = increment.minus(x.remainder.unsigned_value());
  848. // 8. If d1 < d2, return r1.
  849. if (d1 < d2)
  850. return r1;
  851. // 9. If d2 < d1, return r2.
  852. if (d2 < d1)
  853. return r2;
  854. // 10. Assert: d1 is equal to d2.
  855. // NOTE: Skipped for the sake of performance
  856. // 11. If unsignedRoundingMode is half-zero, return r1.
  857. if (unsigned_rounding_mode == UnsignedRoundingMode::HalfZero)
  858. return r1;
  859. // 12. If unsignedRoundingMode is half-infinity, return r2.
  860. if (unsigned_rounding_mode == UnsignedRoundingMode::HalfInfinity)
  861. return r2;
  862. // 13. Assert: unsignedRoundingMode is half-even.
  863. VERIFY(unsigned_rounding_mode == UnsignedRoundingMode::HalfEven);
  864. // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2.
  865. auto cardinality = modulo(r1.divided_by(r2.minus(r1)).quotient, "2"_bigint);
  866. // 15. If cardinality is 0, return r1.
  867. if (cardinality.unsigned_value().is_zero())
  868. return r1;
  869. // 16. Return r2.
  870. return r2;
  871. }
  872. // 13.26 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
  873. double round_number_to_increment(double x, u64 increment, StringView rounding_mode)
  874. {
  875. VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv);
  876. // 1. Let quotient be x / increment.
  877. auto quotient = x / static_cast<double>(increment);
  878. bool is_negative;
  879. // 2. If quotient < 0, then
  880. if (quotient < 0) {
  881. // a. Let isNegative be true.
  882. is_negative = true;
  883. // b. Set quotient to -quotient.
  884. quotient = -quotient;
  885. }
  886. // 3. Else,
  887. else {
  888. // a. Let isNegative be false.
  889. is_negative = false;
  890. }
  891. // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative).
  892. auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative);
  893. // 5. Let r1 be the largest integer such that r1 ≤ quotient.
  894. auto r1 = floor(quotient);
  895. // 6. Let r2 be the smallest integer such that r2 > quotient.
  896. auto r2 = ceil(quotient);
  897. if (quotient == r2)
  898. r2++;
  899. // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode).
  900. auto rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode);
  901. // 8. If isNegative is true, set rounded to -rounded.
  902. if (is_negative)
  903. rounded = -rounded;
  904. // 9. Return rounded × increment.
  905. return rounded * static_cast<double>(increment);
  906. }
  907. // 13.26 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
  908. Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const& x, u64 increment, StringView rounding_mode)
  909. {
  910. VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv);
  911. // OPTIMIZATION: If the increment is 1 the number is always rounded
  912. if (increment == 1)
  913. return x;
  914. auto increment_big_int = Crypto::UnsignedBigInteger::create_from(increment);
  915. // 1. Let quotient be x / increment.
  916. auto division_result = x.divided_by(increment_big_int);
  917. // OPTIMIZATION: If there's no remainder the number is already rounded
  918. if (division_result.remainder.unsigned_value().is_zero())
  919. return x;
  920. bool is_negative;
  921. // 2. If quotient < 0, then
  922. if (division_result.quotient.is_negative()) {
  923. // a. Let isNegative be true.
  924. is_negative = true;
  925. // b. Set quotient to -quotient.
  926. division_result.quotient.negate();
  927. division_result.remainder.negate();
  928. }
  929. // 3. Else,
  930. else {
  931. // a. Let isNegative be false.
  932. is_negative = false;
  933. }
  934. // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative).
  935. auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative);
  936. // 5. Let r1 be the largest integer such that r1 ≤ quotient.
  937. auto r1 = division_result.quotient;
  938. // 6. Let r2 be the smallest integer such that r2 > quotient.
  939. auto r2 = division_result.quotient.plus("1"_bigint);
  940. // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode).
  941. auto rounded = apply_unsigned_rounding_mode(division_result, r1, r2, unsigned_rounding_mode, increment_big_int);
  942. // 8. If isNegative is true, set rounded to -rounded.
  943. if (is_negative)
  944. rounded.negate();
  945. // 9. Return rounded × increment.
  946. return rounded.multiplied_by(increment_big_int);
  947. }
  948. // 13.28 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
  949. ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object, ParseResult const& parse_result)
  950. {
  951. auto& vm = global_object.vm();
  952. // 1. Let parseResult be empty.
  953. // 2. For each nonterminal goal of « TemporalDateTimeString, TemporalInstantString, TemporalMonthDayString, TemporalTimeString, TemporalYearMonthString, TemporalZonedDateTimeString », do
  954. // a. If parseResult is not a Parse Node, set parseResult to ParseText(StringToCodePoints(isoString), goal).
  955. // 3. Assert: parseResult is a Parse Node.
  956. // NOTE: All of this is done by receiving an already parsed ISO string (ParseResult).
  957. // 4. Let each of year, month, day, fSeconds, and calendar be the source text matched by the respective DateYear, DateMonth, DateDay, TimeFraction, and CalendarName Parse Node contained within parseResult, or an empty sequence of code points if not present.
  958. auto year = parse_result.date_year;
  959. auto month = parse_result.date_month;
  960. auto day = parse_result.date_day;
  961. auto f_seconds = parse_result.time_fraction;
  962. auto calendar = parse_result.calendar_name;
  963. // 5. Let hour be the source text matched by the TimeHour, TimeHourNotValidMonth, TimeHourNotThirtyOneDayMonth, or TimeHourTwoOnly Parse Node contained within parseResult, or an empty sequence of code points if none of those are present.
  964. auto hour = parse_result.time_hour;
  965. if (!hour.has_value())
  966. hour = parse_result.time_hour_not_valid_month;
  967. if (!hour.has_value())
  968. hour = parse_result.time_hour_not_thirty_one_day_month;
  969. if (!hour.has_value())
  970. hour = parse_result.time_hour_two_only;
  971. // 6. Let minute be the source text matched by the TimeMinute, TimeMinuteNotValidDay, TimeMinuteThirtyOnly, or TimeMinuteThirtyOneOnly Parse Node contained within parseResult, or an empty sequence of code points if none of those are present.
  972. auto minute = parse_result.time_minute;
  973. if (!minute.has_value())
  974. minute = parse_result.time_minute_not_valid_day;
  975. if (!minute.has_value())
  976. minute = parse_result.time_minute_thirty_only;
  977. if (!minute.has_value())
  978. minute = parse_result.time_minute_thirty_one_only;
  979. // 7. Let second be the source text matched by the TimeSecond or TimeSecondNotValidMonth Parse Node contained within parseResult, or an empty sequence of code points if neither of those are present.
  980. auto second = parse_result.time_second;
  981. if (!second.has_value())
  982. second = parse_result.time_second_not_valid_month;
  983. // 8. If the first code point of year is U+2212 (MINUS SIGN), replace the first code point with U+002D (HYPHEN-MINUS).
  984. Optional<String> normalized_year;
  985. if (year.has_value()) {
  986. normalized_year = year->starts_with("\xE2\x88\x92"sv)
  987. ? String::formatted("-{}", year->substring_view(3))
  988. : String { *year };
  989. }
  990. // 9. Let yearMV be ! ToIntegerOrInfinity(CodePointsToString(year)).
  991. auto year_mv = *normalized_year.value_or("0"sv).to_int<i32>();
  992. // 10. If month is empty, then
  993. // a. Let monthMV be 1.
  994. // 11. Else,
  995. // a. Let monthMV be ! ToIntegerOrInfinity(CodePointsToString(month)).
  996. auto month_mv = *month.value_or("1"sv).to_uint<u8>();
  997. // 12. If day is empty, then
  998. // a. Let dayMV be 1.
  999. // 13. Else,
  1000. // a. Let dayMV be ! ToIntegerOrInfinity(CodePointsToString(day)).
  1001. auto day_mv = *day.value_or("1"sv).to_uint<u8>();
  1002. // 14. Let hourMV be ! ToIntegerOrInfinity(CodePointsToString(hour)).
  1003. auto hour_mv = *hour.value_or("0"sv).to_uint<u8>();
  1004. // 15. Let minuteMV be ! ToIntegerOrInfinity(CodePointsToString(minute)).
  1005. auto minute_mv = *minute.value_or("0"sv).to_uint<u8>();
  1006. // 16. Let secondMV be ! ToIntegerOrInfinity(CodePointsToString(second)).
  1007. auto second_mv = *second.value_or("0"sv).to_uint<u8>();
  1008. // 17. If secondMV is 60, then
  1009. if (second_mv == 60) {
  1010. // a. Set secondMV to 59.
  1011. second_mv = 59;
  1012. }
  1013. u16 millisecond_mv;
  1014. u16 microsecond_mv;
  1015. u16 nanosecond_mv;
  1016. // 18. If fSeconds is not empty, then
  1017. if (f_seconds.has_value()) {
  1018. // a. Let fSecondsDigits be the substring of CodePointsToString(fSeconds) from 1.
  1019. auto f_seconds_digits = f_seconds->substring_view(1);
  1020. // b. Let fSecondsDigitsExtended be the string-concatenation of fSecondsDigits and "000000000".
  1021. auto f_seconds_digits_extended = String::formatted("{}000000000", f_seconds_digits);
  1022. // c. Let millisecond be the substring of fSecondsDigitsExtended from 0 to 3.
  1023. auto millisecond = f_seconds_digits_extended.substring(0, 3);
  1024. // d. Let microsecond be the substring of fSecondsDigitsExtended from 3 to 6.
  1025. auto microsecond = f_seconds_digits_extended.substring(3, 3);
  1026. // e. Let nanosecond be the substring of fSecondsDigitsExtended from 6 to 9.
  1027. auto nanosecond = f_seconds_digits_extended.substring(6, 3);
  1028. // f. Let millisecondMV be ! ToIntegerOrInfinity(millisecond).
  1029. millisecond_mv = *millisecond.to_uint<u16>();
  1030. // g. Let microsecondMV be ! ToIntegerOrInfinity(microsecond).
  1031. microsecond_mv = *microsecond.to_uint<u16>();
  1032. // h. Let nanosecondMV be ! ToIntegerOrInfinity(nanosecond).
  1033. nanosecond_mv = *nanosecond.to_uint<u16>();
  1034. }
  1035. // 19. Else,
  1036. else {
  1037. // a. Let millisecondMV be 0.
  1038. millisecond_mv = 0;
  1039. // b. Let microsecondMV be 0.
  1040. microsecond_mv = 0;
  1041. // c. Let nanosecondMV be 0.
  1042. nanosecond_mv = 0;
  1043. }
  1044. // 20. If IsValidISODate(yearMV, monthMV, dayMV) is false, throw a RangeError exception.
  1045. if (!is_valid_iso_date(year_mv, month_mv, day_mv))
  1046. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidISODate);
  1047. // 21. If IsValidTime(hourMV, minuteMV, secondMV, millisecondMV, microsecondMV, nanosecondMV) is false, throw a RangeError exception.
  1048. if (!is_valid_time(hour_mv, minute_mv, second_mv, millisecond_mv, microsecond_mv, nanosecond_mv))
  1049. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTime);
  1050. Optional<String> calendar_val;
  1051. // 22. If calendar is empty, then
  1052. if (!calendar.has_value()) {
  1053. // a. Let calendarVal be undefined.
  1054. calendar_val = {};
  1055. }
  1056. // 23. Else,
  1057. else {
  1058. // a. Let calendarVal be CodePointsToString(calendar).
  1059. // NOTE: This turns the StringView into a String.
  1060. calendar_val = *calendar;
  1061. }
  1062. // 24. Return the Record { [[Year]]: yearMV, [[Month]]: monthMV, [[Day]]: dayMV, [[Hour]]: hourMV, [[Minute]]: minuteMV, [[Second]]: secondMV, [[Millisecond]]: millisecondMV, [[Microsecond]]: microsecondMV, [[Nanosecond]]: nanosecondMV, [[Calendar]]: calendarVal, }.
  1063. return ISODateTime { .year = year_mv, .month = month_mv, .day = day_mv, .hour = hour_mv, .minute = minute_mv, .second = second_mv, .millisecond = millisecond_mv, .microsecond = microsecond_mv, .nanosecond = nanosecond_mv, .calendar = move(calendar_val) };
  1064. }
  1065. // 13.29 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring
  1066. ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string)
  1067. {
  1068. auto& vm = global_object.vm();
  1069. // 1. If ParseText(StringToCodePoints(isoString), TemporalInstantString) is a List of errors, throw a RangeError exception.
  1070. auto parse_result = parse_iso8601(Production::TemporalInstantString, iso_string);
  1071. if (!parse_result.has_value())
  1072. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidInstantString, iso_string);
  1073. // 2. Let result be ? ParseISODateTime(isoString).
  1074. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1075. // 3. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  1076. auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string));
  1077. // 4. Let offsetString be timeZoneResult.[[OffsetString]].
  1078. auto offset_string = time_zone_result.offset_string;
  1079. // 5. If timeZoneResult.[[Z]] is true, then
  1080. if (time_zone_result.z) {
  1081. // a. Set offsetString to "+00:00".
  1082. offset_string = "+00:00"sv;
  1083. }
  1084. // 6. Assert: offsetString is not undefined.
  1085. VERIFY(offset_string.has_value());
  1086. // 7. 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]]: offsetString }.
  1087. 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(offset_string) };
  1088. }
  1089. // 13.30 ParseTemporalZonedDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalzoneddatetimestring
  1090. ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(GlobalObject& global_object, String const& iso_string)
  1091. {
  1092. auto& vm = global_object.vm();
  1093. // 1. If ParseText(StringToCodePoints(isoString), TemporalZonedDateTimeString) is a List of errors, throw a RangeError exception.
  1094. auto parse_result = parse_iso8601(Production::TemporalZonedDateTimeString, iso_string);
  1095. if (!parse_result.has_value())
  1096. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidZonedDateTimeString, iso_string);
  1097. // 2. Let result be ? ParseISODateTime(isoString).
  1098. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1099. // 3. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  1100. auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string));
  1101. // 4. 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]], [[Calendar]]: result.[[Calendar]], [[TimeZoneZ]]: timeZoneResult.[[Z]], [[TimeZoneOffsetString]]: timeZoneResult.[[OffsetString]], [[TimeZoneName]]: timeZoneResult.[[Name]] }.
  1102. // NOTE: This returns the two structs together instead of separated to avoid a copy in ToTemporalZonedDateTime, as the spec tries to put the result of InterpretTemporalDateTimeFields and ParseTemporalZonedDateTimeString into the same `result` variable.
  1103. // InterpretTemporalDateTimeFields returns an ISODateTime, so the moved in `result` here is subsequently moved into ParseTemporalZonedDateTimeString's `result` variable.
  1104. return TemporalZonedDateTime { .date_time = move(result), .time_zone = move(time_zone_result) };
  1105. }
  1106. // 13.31 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
  1107. ThrowCompletionOr<String> parse_temporal_calendar_string(GlobalObject& global_object, String const& iso_string)
  1108. {
  1109. auto& vm = global_object.vm();
  1110. // 1. Let parseResult be ParseText(StringToCodePoints(isoString), TemporalCalendarString).
  1111. auto parse_result = parse_iso8601(Production::TemporalCalendarString, iso_string);
  1112. // 2. If parseResult is a List of errors, throw a RangeError exception.
  1113. if (!parse_result.has_value())
  1114. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidCalendarString, iso_string);
  1115. // 3. Let id be the source text matched by the CalendarName Parse Node contained within parseResult, or an empty sequence of code points if not present.
  1116. auto id = parse_result->calendar_name;
  1117. // 4. If id is empty, then
  1118. if (!id.has_value()) {
  1119. // a. Return "iso8601".
  1120. return "iso8601"sv;
  1121. }
  1122. // 5. Return CodePointsToString(id).
  1123. return id.value();
  1124. }
  1125. // 13.32 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring
  1126. ThrowCompletionOr<TemporalDate> parse_temporal_date_string(GlobalObject& global_object, String const& iso_string)
  1127. {
  1128. // 1. Let parts be ? ParseTemporalDateTimeString(isoString).
  1129. auto parts = TRY(parse_temporal_date_time_string(global_object, iso_string));
  1130. // 2. Return the Record { [[Year]]: parts.[[Year]], [[Month]]: parts.[[Month]], [[Day]]: parts.[[Day]], [[Calendar]]: parts.[[Calendar]] }.
  1131. return TemporalDate { .year = parts.year, .month = parts.month, .day = parts.day, .calendar = move(parts.calendar) };
  1132. }
  1133. // 13.33 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring
  1134. ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(GlobalObject& global_object, String const& iso_string)
  1135. {
  1136. auto& vm = global_object.vm();
  1137. // 1. Let parseResult be ParseText(StringToCodePoints(isoString), TemporalDateTimeString).
  1138. auto parse_result = parse_iso8601(Production::TemporalDateTimeString, iso_string);
  1139. // 2. If parseResult is a List of errors, throw a RangeError exception.
  1140. if (!parse_result.has_value())
  1141. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDateTimeString, iso_string);
  1142. // 3. If parseResult contains a UTCDesignator Parse Node, throw a RangeError exception.
  1143. if (parse_result->utc_designator.has_value())
  1144. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDateTimeStringUTCDesignator, iso_string);
  1145. // 4. Return ? ParseISODateTime(isoString).
  1146. return parse_iso_date_time(global_object, *parse_result);
  1147. }
  1148. // 13.34 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
  1149. ThrowCompletionOr<DurationRecord> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string)
  1150. {
  1151. auto& vm = global_object.vm();
  1152. // 1. Let duration be ParseText(StringToCodePoints(isoString), TemporalDurationString).
  1153. auto parse_result = parse_iso8601(Production::TemporalDurationString, iso_string);
  1154. // 2. If duration is a List of errors, throw a RangeError exception.
  1155. if (!parse_result.has_value())
  1156. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDurationString, iso_string);
  1157. // 3. Let each of sign, years, months, weeks, days, hours, fHours, minutes, fMinutes, seconds, and fSeconds be the source text matched by the respective Sign, DurationYears, DurationMonths, DurationWeeks, DurationDays, DurationWholeHours, DurationHoursFraction, DurationWholeMinutes, DurationMinutesFraction, DurationWholeSeconds, and DurationSecondsFraction Parse Node contained within duration, or an empty sequence of code points if not present.
  1158. auto sign_part = parse_result->sign;
  1159. auto years_part = parse_result->duration_years;
  1160. auto months_part = parse_result->duration_months;
  1161. auto weeks_part = parse_result->duration_weeks;
  1162. auto days_part = parse_result->duration_days;
  1163. auto hours_part = parse_result->duration_whole_hours;
  1164. auto f_hours_part = parse_result->duration_hours_fraction;
  1165. auto minutes_part = parse_result->duration_whole_minutes;
  1166. auto f_minutes_part = parse_result->duration_minutes_fraction;
  1167. auto seconds_part = parse_result->duration_whole_seconds;
  1168. auto f_seconds_part = parse_result->duration_seconds_fraction;
  1169. // FIXME: I can has StringView::to<double>()?
  1170. // 4. Let yearsMV be ! ToIntegerOrInfinity(CodePointsToString(years)).
  1171. auto years = strtod(String { years_part.value_or("0"sv) }.characters(), nullptr);
  1172. // 5. Let monthsMV be ! ToIntegerOrInfinity(CodePointsToString(months)).
  1173. auto months = strtod(String { months_part.value_or("0"sv) }.characters(), nullptr);
  1174. // 6. Let weeksMV be ! ToIntegerOrInfinity(CodePointsToString(weeks)).
  1175. auto weeks = strtod(String { weeks_part.value_or("0"sv) }.characters(), nullptr);
  1176. // 7. Let daysMV be ! ToIntegerOrInfinity(CodePointsToString(days)).
  1177. auto days = strtod(String { days_part.value_or("0"sv) }.characters(), nullptr);
  1178. // 8. Let hoursMV be ! ToIntegerOrInfinity(CodePointsToString(hours)).
  1179. auto hours = strtod(String { hours_part.value_or("0"sv) }.characters(), nullptr);
  1180. double minutes;
  1181. // 9. If fHours is not empty, then
  1182. if (f_hours_part.has_value()) {
  1183. // a. If any of minutes, fMinutes, seconds, fSeconds is not empty, throw a RangeError exception.
  1184. if (minutes_part.has_value() || f_minutes_part.has_value() || seconds_part.has_value() || f_seconds_part.has_value())
  1185. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDurationStringFractionNotLast, iso_string, "hours"sv, "minutes or seconds"sv);
  1186. // b. Let fHoursDigits be the substring of CodePointsToString(fHours) from 1.
  1187. auto f_hours_digits = f_hours_part->substring_view(1);
  1188. // c. Let fHoursScale be the length of fHoursDigits.
  1189. auto f_hours_scale = (double)f_hours_digits.length();
  1190. // d. Let minutesMV be ! ToIntegerOrInfinity(fHoursDigits) / 10^fHoursScale × 60.
  1191. minutes = strtod(String { f_hours_digits }.characters(), nullptr) / pow(10, f_hours_scale) * 60;
  1192. }
  1193. // 10. Else,
  1194. else {
  1195. // a. Let minutesMV be ! ToIntegerOrInfinity(CodePointsToString(minutes)).
  1196. minutes = strtod(String { minutes_part.value_or("0"sv) }.characters(), nullptr);
  1197. }
  1198. double seconds;
  1199. // 11. If fMinutes is not empty, then
  1200. if (f_minutes_part.has_value()) {
  1201. // a. If any of seconds, fSeconds is not empty, throw a RangeError exception.
  1202. if (seconds_part.has_value() || f_seconds_part.has_value())
  1203. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDurationStringFractionNotLast, iso_string, "minutes"sv, "seconds"sv);
  1204. // b. Let fMinutesDigits be the substring of CodePointsToString(fMinutes) from 1.
  1205. auto f_minutes_digits = f_minutes_part->substring_view(1);
  1206. // c. Let fMinutesScale be the length of fMinutesDigits.
  1207. auto f_minutes_scale = (double)f_minutes_digits.length();
  1208. // d. Let secondsMV be ! ToIntegerOrInfinity(fMinutesDigits) / 10^fMinutesScale × 60.
  1209. seconds = strtod(String { f_minutes_digits }.characters(), nullptr) / pow(10, f_minutes_scale) * 60;
  1210. }
  1211. // 12. Else if seconds is not empty, then
  1212. else if (seconds_part.has_value()) {
  1213. // a. Let secondsMV be ! ToIntegerOrInfinity(CodePointsToString(seconds)).
  1214. seconds = strtod(String { *seconds_part }.characters(), nullptr);
  1215. }
  1216. // 13. Else,
  1217. else {
  1218. // a. Let secondsMV be remainder(minutesMV, 1) × 60.
  1219. seconds = fmod(minutes, 1) * 60;
  1220. }
  1221. double milliseconds;
  1222. // 14. If fSeconds is not empty, then
  1223. if (f_seconds_part.has_value()) {
  1224. // a. Let fSecondsDigits be the substring of CodePointsToString(fSeconds) from 1.
  1225. auto f_seconds_digits = f_seconds_part->substring_view(1);
  1226. // b. Let fSecondsScale be the length of fSecondsDigits.
  1227. auto f_seconds_scale = (double)f_seconds_digits.length();
  1228. // c. Let millisecondsMV be ! ToIntegerOrInfinity(fSecondsDigits) / 10^fSecondsScale × 1000.
  1229. milliseconds = strtod(String { f_seconds_digits }.characters(), nullptr) / pow(10, f_seconds_scale) * 1000;
  1230. }
  1231. // 15. Else,
  1232. else {
  1233. // a. Let millisecondsMV be remainder(secondsMV, 1) × 1000.
  1234. milliseconds = fmod(seconds, 1) * 1000;
  1235. }
  1236. // FIXME: This suffers from floating point (im)precision issues - e.g. "PT0.0000001S" ends up
  1237. // getting parsed as 99.999999 nanoseconds, which is floor()'d to 99 instead of the
  1238. // expected 100. Oof. This is the reason all of these are suffixed with "MV" in the spec:
  1239. // mathematical values are not supposed to have this issue.
  1240. // 16. Let microsecondsMV be remainder(millisecondsMV, 1) × 1000.
  1241. auto microseconds = fmod(milliseconds, 1) * 1000;
  1242. // 17. Let nanosecondsMV be remainder(microsecondsMV, 1) × 1000.
  1243. auto nanoseconds = fmod(microseconds, 1) * 1000;
  1244. i8 factor;
  1245. // 18. If sign contains the code point U+002D (HYPHEN-MINUS) or U+2212 (MINUS SIGN), then
  1246. if (sign_part.has_value() && sign_part->is_one_of("-", "\u2212")) {
  1247. // a. Let factor be -1.
  1248. factor = -1;
  1249. }
  1250. // 19. Else,
  1251. else {
  1252. // a. Let factor be 1.
  1253. factor = 1;
  1254. }
  1255. // 20. Return ? CreateDurationRecord(yearsMV × factor, monthsMV × factor, weeksMV × factor, daysMV × factor, hoursMV × factor, floor(minutesMV) × factor, floor(secondsMV) × factor, floor(millisecondsMV) × factor, floor(microsecondsMV) × factor, floor(nanosecondsMV) × factor).
  1256. return create_duration_record(global_object, years * factor, months * factor, weeks * factor, days * factor, hours * factor, floor(minutes) * factor, floor(seconds) * factor, floor(milliseconds) * factor, floor(microseconds) * factor, floor(nanoseconds) * factor);
  1257. }
  1258. // 13.35 ParseTemporalMonthDayString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalmonthdaystring
  1259. ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(GlobalObject& global_object, String const& iso_string)
  1260. {
  1261. auto& vm = global_object.vm();
  1262. // 1. Let parseResult be ParseText(StringToCodePoints(isoString), TemporalMonthDayString).
  1263. auto parse_result = parse_iso8601(Production::TemporalMonthDayString, iso_string);
  1264. // 2. If parseResult is a List of errors, throw a RangeError exception.
  1265. if (!parse_result.has_value())
  1266. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidMonthDayString, iso_string);
  1267. // 3. If parseResult contains a UTCDesignator Parse Node, throw a RangeError exception.
  1268. if (parse_result->utc_designator.has_value())
  1269. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidMonthDayStringUTCDesignator, iso_string);
  1270. // 4. Let result be ? ParseISODateTime(isoString).
  1271. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1272. // 5. Let year be result.[[Year]].
  1273. Optional<i32> year = result.year;
  1274. // 6. If parseResult does not contain a DateYear Parse Node, then
  1275. if (!parse_result->date_year.has_value()) {
  1276. // a. Set year to undefined.
  1277. year = {};
  1278. }
  1279. // 7. Return the Record { [[Year]]: year, [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  1280. return TemporalMonthDay { .year = year, .month = result.month, .day = result.day, .calendar = move(result.calendar) };
  1281. }
  1282. // 13.36 ParseTemporalRelativeToString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalrelativetostring
  1283. ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_relative_to_string(GlobalObject& global_object, String const& iso_string)
  1284. {
  1285. auto& vm = global_object.vm();
  1286. // 1. If ParseText(StringToCodePoints(isoString), TemporalDateTimeString) is a List of errors, throw a RangeError exception.
  1287. auto parse_result = parse_iso8601(Production::TemporalDateTimeString, iso_string);
  1288. if (!parse_result.has_value())
  1289. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDateTimeString, iso_string);
  1290. // 2. Let result be ? ParseISODateTime(isoString).
  1291. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1292. bool z;
  1293. Optional<String> offset_string;
  1294. Optional<String> time_zone;
  1295. // 3. If ParseText(StringToCodePoints(isoString), TemporalZonedDateTimeString) is a Parse Node, then
  1296. parse_result = parse_iso8601(Production::TemporalZonedDateTimeString, iso_string);
  1297. if (parse_result.has_value()) {
  1298. // a. Let timeZoneResult be ! ParseTemporalTimeZoneString(isoString).
  1299. auto time_zone_result = MUST(parse_temporal_time_zone_string(global_object, iso_string));
  1300. // b. Let z be timeZoneResult.[[Z]].
  1301. z = time_zone_result.z;
  1302. // c. Let offsetString be timeZoneResult.[[OffsetString]].
  1303. offset_string = time_zone_result.offset_string;
  1304. // d. Let timeZone be timeZoneResult.[[Name]].
  1305. time_zone = time_zone_result.name;
  1306. }
  1307. // 4. Else,
  1308. else {
  1309. // a. Let z be false.
  1310. z = false;
  1311. // b. Let offsetString be undefined.
  1312. // c. Let timeZone be undefined.
  1313. }
  1314. // 5. 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]], [[Calendar]]: result.[[Calendar]], [[TimeZoneZ]]: z, [[TimeZoneOffsetString]]: offsetString, [[TimeZoneIANAName]]: timeZone }.
  1315. return TemporalZonedDateTime { .date_time = move(result), .time_zone = { .z = z, .offset_string = move(offset_string), .name = move(time_zone) } };
  1316. }
  1317. // 13.37 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring
  1318. ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, String const& iso_string)
  1319. {
  1320. auto& vm = global_object.vm();
  1321. // 1. Let parseResult be ParseText(StringToCodePoints(isoString), TemporalTimeString).
  1322. auto parse_result = parse_iso8601(Production::TemporalTimeString, iso_string);
  1323. // 2. If parseResult is a List of errors, throw a RangeError exception.
  1324. if (!parse_result.has_value())
  1325. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeString, iso_string);
  1326. // 3. If parseResult contains a UTCDesignator Parse Node, throw a RangeError exception.
  1327. if (parse_result->utc_designator.has_value())
  1328. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeStringUTCDesignator, iso_string);
  1329. // 4. Let result be ? ParseISODateTime(isoString).
  1330. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1331. // 5. Return the Record { [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]] }.
  1332. return TemporalTime { .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .calendar = move(result.calendar) };
  1333. }
  1334. // 13.38 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring
  1335. ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, String const& iso_string)
  1336. {
  1337. auto& vm = global_object.vm();
  1338. // 1. Let parseResult be ParseText(StringToCodePoints(isoString), TemporalTimeZoneString).
  1339. auto parse_result = parse_iso8601(Production::TemporalTimeZoneString, iso_string);
  1340. // 2. If parseResult is a List of errors, throw a RangeError exception.
  1341. if (!parse_result.has_value())
  1342. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneString, iso_string);
  1343. // 3. Let each of z, offsetString, and name be the source text matched by the respective UTCDesignator, TimeZoneNumericUTCOffset, and TimeZoneIANAName Parse Node contained within parseResult, or an empty sequence of code points if not present.
  1344. auto z = parse_result->utc_designator;
  1345. auto offset_string = parse_result->time_zone_numeric_utc_offset;
  1346. auto name = parse_result->time_zone_iana_name;
  1347. // 4. If name is empty, then
  1348. // a. Set name to undefined.
  1349. // 5. Else,
  1350. // a. Set name to CodePointsToString(name).
  1351. // NOTE: No-op.
  1352. // 6. If z is not empty, then
  1353. if (z.has_value()) {
  1354. // a. Return the Record { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: name }.
  1355. return TemporalTimeZone { .z = true, .offset_string = {}, .name = Optional<String>(move(name)) };
  1356. }
  1357. // 7. If offsetString is empty, then
  1358. // a. Set offsetString to undefined.
  1359. // 8. Else,
  1360. // a. Set offsetString to CodePointsToString(offsetString).
  1361. // NOTE: No-op.
  1362. // 9. Return the Record { [[Z]]: false, [[OffsetString]]: offsetString, [[Name]]: name }.
  1363. return TemporalTimeZone { .z = false, .offset_string = Optional<String>(move(offset_string)), .name = Optional<String>(move(name)) };
  1364. }
  1365. // 13.39 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring
  1366. ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(GlobalObject& global_object, String const& iso_string)
  1367. {
  1368. auto& vm = global_object.vm();
  1369. // 1. Let parseResult be ParseText(StringToCodePoints(isoString), TemporalYearMonthString).
  1370. auto parse_result = parse_iso8601(Production::TemporalYearMonthString, iso_string);
  1371. // 2. If parseResult is a List of errors, throw a RangeError exception.
  1372. if (!parse_result.has_value())
  1373. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidYearMonthString, iso_string);
  1374. // 3. If parseResult contains a UTCDesignator Parse Node, throw a RangeError exception.
  1375. if (parse_result->utc_designator.has_value())
  1376. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidYearMonthStringUTCDesignator, iso_string);
  1377. // 4. Let result be ? ParseISODateTime(isoString).
  1378. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1379. // 5. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  1380. return TemporalYearMonth { .year = result.year, .month = result.month, .day = result.day, .calendar = move(result.calendar) };
  1381. }
  1382. // 13.40 ToPositiveInteger ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-topositiveinteger
  1383. ThrowCompletionOr<double> to_positive_integer(GlobalObject& global_object, Value argument)
  1384. {
  1385. auto& vm = global_object.vm();
  1386. // 1. Let integer be ? ToIntegerThrowOnInfinity(argument).
  1387. auto integer = TRY(to_integer_throw_on_infinity(global_object, argument, ErrorType::TemporalPropertyMustBePositiveInteger));
  1388. // 2. If integer ≤ 0, then
  1389. if (integer <= 0) {
  1390. // a. Throw a RangeError exception.
  1391. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalPropertyMustBePositiveInteger);
  1392. }
  1393. // 3. Return integer.
  1394. return integer;
  1395. }
  1396. // 13.43 PrepareTemporalFields ( fields, fieldNames, requiredFields ), https://tc39.es/proposal-temporal/#sec-temporal-preparetemporalfields
  1397. ThrowCompletionOr<Object*> prepare_temporal_fields(GlobalObject& global_object, Object const& fields, Vector<String> const& field_names, Variant<PrepareTemporalFieldsPartial, Vector<StringView>> const& required_fields)
  1398. {
  1399. auto& vm = global_object.vm();
  1400. // 1. Let result be OrdinaryObjectCreate(%Object.prototype%).
  1401. auto* result = Object::create(global_object, global_object.object_prototype());
  1402. VERIFY(result);
  1403. // 2. Let any be false.
  1404. auto any = false;
  1405. // 3. For each value property of fieldNames, do
  1406. for (auto& property : field_names) {
  1407. // a. Let value be ? Get(fields, property).
  1408. auto value = TRY(fields.get(property));
  1409. // b. If value is not undefined, then
  1410. if (!value.is_undefined()) {
  1411. // i. Set any to true.
  1412. any = true;
  1413. // ii. If property is in the Property column of Table 15 and there is a Conversion value in the same row, then
  1414. // 1. Let Conversion represent the abstract operation named by the Conversion value of the same row.
  1415. // 2. Set value to ? Conversion(value).
  1416. if (property.is_one_of("year"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv, "eraYear"sv))
  1417. value = Value(TRY(to_integer_throw_on_infinity(global_object, value, ErrorType::TemporalPropertyMustBeFinite)));
  1418. else if (property.is_one_of("month"sv, "day"sv))
  1419. value = Value(TRY(to_positive_integer(global_object, value)));
  1420. else if (property.is_one_of("monthCode"sv, "offset"sv, "era"sv))
  1421. value = TRY(value.to_primitive_string(global_object));
  1422. // iii. Perform ! CreateDataPropertyOrThrow(result, property, value).
  1423. MUST(result->create_data_property_or_throw(property, value));
  1424. }
  1425. // c. Else if requiredFields is a List, then
  1426. else if (required_fields.has<Vector<StringView>>()) {
  1427. // i. If requiredFields contains property, then
  1428. if (required_fields.get<Vector<StringView>>().contains_slow(property)) {
  1429. // 1. Throw a TypeError exception.
  1430. return vm.throw_completion<TypeError>(global_object, ErrorType::MissingRequiredProperty, property);
  1431. }
  1432. // ii. If property is in the Property column of Table 13, then
  1433. // NOTE: The other properties in the table are automatically handled as their default value is undefined
  1434. if (property.is_one_of("hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
  1435. // 1. Set value to the corresponding Default value of the same row.
  1436. value = Value(0);
  1437. }
  1438. // iii. Perform ! CreateDataPropertyOrThrow(result, property, value).
  1439. MUST(result->create_data_property_or_throw(property, value));
  1440. }
  1441. }
  1442. // 4. If requiredFields is partial and any is false, then
  1443. if (required_fields.has<PrepareTemporalFieldsPartial>() && !any) {
  1444. // a. Throw a TypeError exception.
  1445. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustHaveOneOf, String::join(", "sv, field_names));
  1446. }
  1447. // 5. Return result.
  1448. return result;
  1449. }
  1450. }