AbstractOperations.cpp 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021, 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/Completion.h>
  13. #include <LibJS/Runtime/IteratorOperations.h>
  14. #include <LibJS/Runtime/PropertyKey.h>
  15. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  16. #include <LibJS/Runtime/Temporal/Calendar.h>
  17. #include <LibJS/Runtime/Temporal/Duration.h>
  18. #include <LibJS/Runtime/Temporal/ISO8601.h>
  19. #include <LibJS/Runtime/Temporal/PlainDate.h>
  20. #include <LibJS/Runtime/Temporal/PlainDateTime.h>
  21. #include <LibJS/Runtime/Temporal/PlainTime.h>
  22. #include <LibJS/Runtime/Temporal/TimeZone.h>
  23. #include <LibJS/Runtime/Temporal/ZonedDateTime.h>
  24. namespace JS::Temporal {
  25. static Optional<OptionType> to_option_type(Value value)
  26. {
  27. if (value.is_boolean())
  28. return OptionType::Boolean;
  29. if (value.is_string())
  30. return OptionType::String;
  31. if (value.is_number())
  32. return OptionType::Number;
  33. return {};
  34. }
  35. // 13.1 IterableToListOfType ( items, elementTypes ), https://tc39.es/proposal-temporal/#sec-iterabletolistoftype
  36. ThrowCompletionOr<MarkedValueList> iterable_to_list_of_type(GlobalObject& global_object, Value items, Vector<OptionType> const& element_types)
  37. {
  38. auto& vm = global_object.vm();
  39. auto& heap = global_object.heap();
  40. // 1. Let iteratorRecord be ? GetIterator(items, sync).
  41. auto iterator_record = TRY(get_iterator(global_object, items, IteratorHint::Sync));
  42. // 2. Let values be a new empty List.
  43. MarkedValueList values(heap);
  44. // 3. Let next be true.
  45. auto next = true;
  46. // 4. Repeat, while next is not false,
  47. while (next) {
  48. // a. Set next to ? IteratorStep(iteratorRecord).
  49. auto* iterator_result = TRY(iterator_step(global_object, *iterator_record));
  50. next = iterator_result;
  51. // b. If next is not false, then
  52. if (next) {
  53. // i. Let nextValue be ? IteratorValue(next).
  54. auto next_value = TRY(iterator_value(global_object, *iterator_result));
  55. // ii. If Type(nextValue) is not an element of elementTypes, then
  56. if (auto type = to_option_type(next_value); !type.has_value() || !element_types.contains_slow(*type)) {
  57. // 1. Let completion be ThrowCompletion(a newly created TypeError object).
  58. auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::IterableToListOfTypeInvalidValue, next_value.to_string_without_side_effects());
  59. // 2. Return ? IteratorClose(iteratorRecord, completion).
  60. return iterator_close(*iterator_record, move(completion));
  61. }
  62. // iii. Append nextValue to the end of the List values.
  63. values.append(next_value);
  64. }
  65. }
  66. // 5. Return values.
  67. return { move(values) };
  68. }
  69. // 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
  70. ThrowCompletionOr<Object*> get_options_object(GlobalObject& global_object, Value options)
  71. {
  72. auto& vm = global_object.vm();
  73. // 1. If options is undefined, then
  74. if (options.is_undefined()) {
  75. // a. Return ! OrdinaryObjectCreate(null).
  76. return Object::create(global_object, nullptr);
  77. }
  78. // 2. If Type(options) is Object, then
  79. if (options.is_object()) {
  80. // a. Return options.
  81. return &options.as_object();
  82. }
  83. // 3. Throw a TypeError exception.
  84. return vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObject, "Options");
  85. }
  86. // 13.3 GetOption ( options, property, types, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption
  87. ThrowCompletionOr<Value> get_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback)
  88. {
  89. VERIFY(property.is_string());
  90. auto& vm = global_object.vm();
  91. // 1. Assert: Type(options) is Object.
  92. // 2. Assert: Each element of types is Boolean, String, or Number.
  93. // 3. Let value be ? Get(options, property).
  94. auto value = TRY(options.get(property));
  95. // 4. If value is undefined, return fallback.
  96. if (value.is_undefined())
  97. return fallback;
  98. OptionType type;
  99. // 5. If types contains Type(value), then
  100. if (auto value_type = to_option_type(value); value_type.has_value() && types.contains_slow(*value_type)) {
  101. // a. Let type be Type(value).
  102. type = *value_type;
  103. }
  104. // 6. Else,
  105. else {
  106. // a. Let type be the last element of types.
  107. type = types.last();
  108. }
  109. // 7. If type is Boolean, then
  110. if (type == OptionType::Boolean) {
  111. // a. Set value to ! ToBoolean(value).
  112. value = Value(value.to_boolean());
  113. }
  114. // 8. Else if type is Number, then
  115. else if (type == OptionType::Number) {
  116. // a. Set value to ? ToNumber(value).
  117. value = TRY(value.to_number(global_object));
  118. // b. If value is NaN, throw a RangeError exception.
  119. if (value.is_nan())
  120. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.NaN.as_string(), property.as_string());
  121. }
  122. // 9. Else,
  123. else {
  124. // a. Set value to ? ToString(value).
  125. value = TRY(value.to_primitive_string(global_object));
  126. }
  127. // 10. If values is not empty, then
  128. if (!values.is_empty()) {
  129. VERIFY(value.is_string());
  130. // a. If values does not contain value, throw a RangeError exception.
  131. if (!values.contains_slow(value.as_string().string()))
  132. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  133. }
  134. // 11. Return value.
  135. return value;
  136. }
  137. // 13.4 GetStringOrNumberOption ( options, property, stringValues, minimum, maximum, fallback ), https://tc39.es/proposal-temporal/#sec-getstringornumberoption
  138. template<typename NumberType>
  139. 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)
  140. {
  141. auto& vm = global_object.vm();
  142. // 1. Assert: Type(options) is Object.
  143. // 2. Let value be ? GetOption(options, property, « Number, String », empty, fallback).
  144. auto value = TRY(get_option(global_object, options, property, { OptionType::Number, OptionType::String }, {}, fallback));
  145. // 3. If Type(value) is Number, then
  146. if (value.is_number()) {
  147. // a. If value < minimum or value > maximum, throw a RangeError exception.
  148. if (value.as_double() < minimum || value.as_double() > maximum)
  149. return vm.template throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_double(), property.as_string());
  150. // b. Return floor(ℝ(value)).
  151. return static_cast<NumberType>(floor(value.as_double()));
  152. }
  153. // 4. Assert: Type(value) is String.
  154. VERIFY(value.is_string());
  155. // 5. If stringValues does not contain value, throw a RangeError exception.
  156. if (!string_values.contains_slow(value.as_string().string()))
  157. return vm.template throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
  158. // 6. Return value.
  159. return value.as_string().string();
  160. }
  161. // 13.6 ToTemporalOverflow ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
  162. ThrowCompletionOr<String> to_temporal_overflow(GlobalObject& global_object, Object const& normalized_options)
  163. {
  164. auto& vm = global_object.vm();
  165. // 1. Return ? GetOption(normalizedOptions, "overflow", « String », « "constrain", "reject" », "constrain").
  166. auto option = TRY(get_option(global_object, normalized_options, vm.names.overflow, { OptionType::String }, { "constrain"sv, "reject"sv }, js_string(vm, "constrain")));
  167. VERIFY(option.is_string());
  168. return option.as_string().string();
  169. }
  170. // 13.7 ToTemporalDisambiguation ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldisambiguation
  171. ThrowCompletionOr<String> to_temporal_disambiguation(GlobalObject& global_object, Object const& normalized_options)
  172. {
  173. auto& vm = global_object.vm();
  174. // 1. Return ? GetOption(normalizedOptions, "disambiguation", « String », « "compatible", "earlier", "later", "reject" », "compatible").
  175. auto option = TRY(get_option(global_object, normalized_options, vm.names.disambiguation, { OptionType::String }, { "compatible"sv, "earlier"sv, "later"sv, "reject"sv }, js_string(vm, "compatible")));
  176. VERIFY(option.is_string());
  177. return option.as_string().string();
  178. }
  179. // 13.8 ToTemporalRoundingMode ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingmode
  180. ThrowCompletionOr<String> to_temporal_rounding_mode(GlobalObject& global_object, Object const& normalized_options, String const& fallback)
  181. {
  182. auto& vm = global_object.vm();
  183. // 1. Return ? GetOption(normalizedOptions, "roundingMode", « String », « "ceil", "floor", "trunc", "halfExpand" », fallback).
  184. 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)));
  185. VERIFY(option.is_string());
  186. return option.as_string().string();
  187. }
  188. // 13.9 NegateTemporalRoundingMode ( roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-negatetemporalroundingmode
  189. StringView negate_temporal_rounding_mode(String const& rounding_mode)
  190. {
  191. // 1. If roundingMode is "ceil", return "floor".
  192. if (rounding_mode == "ceil"sv)
  193. return "floor"sv;
  194. // 2. If roundingMode is "floor", return "ceil".
  195. if (rounding_mode == "floor"sv)
  196. return "ceil"sv;
  197. // 3. Return roundingMode.
  198. return rounding_mode;
  199. }
  200. // 13.10 ToTemporalOffset ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloffset
  201. ThrowCompletionOr<String> to_temporal_offset(GlobalObject& global_object, Object const& normalized_options, String const& fallback)
  202. {
  203. auto& vm = global_object.vm();
  204. // 1. Return ? GetOption(normalizedOptions, "offset", « String », « "prefer", "use", "ignore", "reject" », fallback).
  205. auto option = TRY(get_option(global_object, normalized_options, vm.names.offset, { OptionType::String }, { "prefer"sv, "use"sv, "ignore"sv, "reject"sv }, js_string(vm, fallback)));
  206. VERIFY(option.is_string());
  207. return option.as_string().string();
  208. }
  209. // 13.11 ToShowCalendarOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
  210. ThrowCompletionOr<String> to_show_calendar_option(GlobalObject& global_object, Object const& normalized_options)
  211. {
  212. auto& vm = global_object.vm();
  213. // 1. Return ? GetOption(normalizedOptions, "calendarName", « String », « "auto", "always", "never" », "auto").
  214. 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)));
  215. VERIFY(option.is_string());
  216. return option.as_string().string();
  217. }
  218. // 13.12 ToShowTimeZoneNameOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowtimezonenameoption
  219. ThrowCompletionOr<String> to_show_time_zone_name_option(GlobalObject& global_object, Object const& normalized_options)
  220. {
  221. auto& vm = global_object.vm();
  222. // 1. Return ? GetOption(normalizedOptions, "timeZoneName", « String », « "auto", "never" », "auto").
  223. auto option = TRY(get_option(global_object, normalized_options, vm.names.timeZoneName, { OptionType::String }, { "auto"sv, "never"sv }, js_string(vm, "auto"sv)));
  224. VERIFY(option.is_string());
  225. return option.as_string().string();
  226. }
  227. // 13.13 ToShowOffsetOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowoffsetoption
  228. ThrowCompletionOr<String> to_show_offset_option(GlobalObject& global_object, Object const& normalized_options)
  229. {
  230. auto& vm = global_object.vm();
  231. // 1. Return ? GetOption(normalizedOptions, "offset", « String », « "auto", "never" », "auto").
  232. auto option = TRY(get_option(global_object, normalized_options, vm.names.offset, { OptionType::String }, { "auto"sv, "never"sv }, js_string(vm, "auto"sv)));
  233. VERIFY(option.is_string());
  234. return option.as_string().string();
  235. }
  236. // 13.14 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
  237. ThrowCompletionOr<u64> to_temporal_rounding_increment(GlobalObject& global_object, Object const& normalized_options, Optional<double> dividend, bool inclusive)
  238. {
  239. auto& vm = global_object.vm();
  240. double maximum;
  241. // 1. If dividend is undefined, then
  242. if (!dividend.has_value()) {
  243. // a. Let maximum be +∞.
  244. maximum = INFINITY;
  245. }
  246. // 2. Else if inclusive is true, then
  247. else if (inclusive) {
  248. // a. Let maximum be dividend.
  249. maximum = *dividend;
  250. }
  251. // 3. Else if dividend is more than 1, then
  252. else if (*dividend > 1) {
  253. // a. Let maximum be dividend − 1.
  254. maximum = *dividend - 1;
  255. }
  256. // 4. Else,
  257. else {
  258. // a. Let maximum be 1.
  259. maximum = 1;
  260. }
  261. // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", « Number », empty, 1).
  262. auto increment_value = TRY(get_option(global_object, normalized_options, vm.names.roundingIncrement, { OptionType::Number }, {}, Value(1)));
  263. VERIFY(increment_value.is_number());
  264. auto increment = increment_value.as_double();
  265. // 6. If increment < 1 or increment > maximum, throw a RangeError exception.
  266. if (increment < 1 || increment > maximum)
  267. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  268. // 7. Set increment to floor(ℝ(increment)).
  269. auto floored_increment = static_cast<u64>(increment);
  270. // 8. If dividend is not undefined and dividend modulo increment is not zero, then
  271. if (dividend.has_value() && static_cast<u64>(*dividend) % floored_increment != 0)
  272. // a. Throw a RangeError exception.
  273. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  274. // 9. Return increment.
  275. return floored_increment;
  276. }
  277. // 13.15 ToTemporalDateTimeRoundingIncrement ( normalizedOptions, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetimeroundingincrement
  278. ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(GlobalObject& global_object, Object const& normalized_options, StringView smallest_unit)
  279. {
  280. double maximum;
  281. // 1. If smallestUnit is "day", then
  282. if (smallest_unit == "day"sv) {
  283. // a. Let maximum be 1.
  284. maximum = 1;
  285. }
  286. // 2. Else if smallestUnit is "hour", then
  287. else if (smallest_unit == "hour"sv) {
  288. // a. Let maximum be 24.
  289. maximum = 24;
  290. }
  291. // 3. Else if smallestUnit is "minute" or "second", then
  292. else if (smallest_unit.is_one_of("minute"sv, "second"sv)) {
  293. // a. Let maximum be 60.
  294. maximum = 60;
  295. }
  296. // 4. Else,
  297. else {
  298. // a. Assert: smallestUnit is "millisecond", "microsecond", or "nanosecond".
  299. VERIFY(smallest_unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv));
  300. // b. Let maximum be 1000.
  301. maximum = 1000;
  302. }
  303. // 5. Return ? ToTemporalRoundingIncrement(normalizedOptions, maximum, false).
  304. return to_temporal_rounding_increment(global_object, normalized_options, maximum, false);
  305. }
  306. // 13.16 ToSecondsStringPrecision ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecision
  307. ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(GlobalObject& global_object, Object const& normalized_options)
  308. {
  309. auto& vm = global_object.vm();
  310. // Let smallestUnit be ? ToSmallestTemporalUnit(normalizedOptions, « "year", "month", "week", "day", "hour" », undefined).
  311. auto smallest_unit = TRY(to_smallest_temporal_unit(global_object, normalized_options, { "year"sv, "month"sv, "week"sv, "day"sv, "hour"sv }, {}));
  312. // 2. If smallestUnit is "minute", then
  313. if (smallest_unit == "minute"sv) {
  314. // a. Return the Record { [[Precision]]: "minute", [[Unit]]: "minute", [[Increment]]: 1 }.
  315. return SecondsStringPrecision { .precision = "minute"sv, .unit = "minute"sv, .increment = 1 };
  316. }
  317. // 3. If smallestUnit is "second", then
  318. if (smallest_unit == "second"sv) {
  319. // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
  320. return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
  321. }
  322. // 4. If smallestUnit is "millisecond", then
  323. if (smallest_unit == "millisecond"sv) {
  324. // a. Return the Record { [[Precision]]: 3, [[Unit]]: "millisecond", [[Increment]]: 1 }.
  325. return SecondsStringPrecision { .precision = 3, .unit = "millisecond"sv, .increment = 1 };
  326. }
  327. // 5. If smallestUnit is "microsecond", then
  328. if (smallest_unit == "microsecond"sv) {
  329. // a. Return the Record { [[Precision]]: 6, [[Unit]]: "microsecond", [[Increment]]: 1 }.
  330. return SecondsStringPrecision { .precision = 6, .unit = "microsecond"sv, .increment = 1 };
  331. }
  332. // 6. If smallestUnit is "nanosecond", then
  333. if (smallest_unit == "nanosecond"sv) {
  334. // a. Return the Record { [[Precision]]: 9, [[Unit]]: "nanosecond", [[Increment]]: 1 }.
  335. return SecondsStringPrecision { .precision = 9, .unit = "nanosecond"sv, .increment = 1 };
  336. }
  337. // 7. Assert: smallestUnit is undefined.
  338. VERIFY(!smallest_unit.has_value());
  339. // 8. Let digits be ? GetStringOrNumberOption(normalizedOptions, "fractionalSecondDigits", « "auto" », 0, 9, "auto").
  340. auto digits_variant = TRY(get_string_or_number_option<u8>(global_object, normalized_options, vm.names.fractionalSecondDigits, { "auto"sv }, 0, 9, js_string(vm, "auto"sv)));
  341. // 9. If digits is "auto", then
  342. if (digits_variant.has<String>()) {
  343. VERIFY(digits_variant.get<String>() == "auto"sv);
  344. // a. Return the Record { [[Precision]]: "auto", [[Unit]]: "nanosecond", [[Increment]]: 1 }.
  345. return SecondsStringPrecision { .precision = "auto"sv, .unit = "nanosecond"sv, .increment = 1 };
  346. }
  347. auto digits = digits_variant.get<u8>();
  348. // 10. If digits is 0, then
  349. if (digits == 0) {
  350. // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
  351. return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
  352. }
  353. // 11. If digits is 1, 2, or 3, then
  354. if (digits == 1 || digits == 2 || digits == 3) {
  355. // a. Return the Record { [[Precision]]: digits, [[Unit]]: "millisecond", [[Increment]]: 10^(3 − digits) }.
  356. return SecondsStringPrecision { .precision = digits, .unit = "millisecond"sv, .increment = (u32)pow(10, 3 - digits) };
  357. }
  358. // 12. If digits is 4, 5, or 6, then
  359. if (digits == 4 || digits == 5 || digits == 6) {
  360. // a. Return the Record { [[Precision]]: digits, [[Unit]]: "microsecond", [[Increment]]: 10^(6 − digits) }.
  361. return SecondsStringPrecision { .precision = digits, .unit = "microsecond"sv, .increment = (u32)pow(10, 6 - digits) };
  362. }
  363. // 13. Assert: digits is 7, 8, or 9.
  364. VERIFY(digits == 7 || digits == 8 || digits == 9);
  365. // 14. Return the Record { [[Precision]]: digits, [[Unit]]: "nanosecond", [[Increment]]: 10^(9 − digits) }.
  366. return SecondsStringPrecision { .precision = digits, .unit = "nanosecond"sv, .increment = (u32)pow(10, 9 - digits) };
  367. }
  368. // https://tc39.es/proposal-temporal/#table-temporal-singular-and-plural-units
  369. static HashMap<StringView, StringView> plural_to_singular_units = {
  370. { "years"sv, "year"sv },
  371. { "months"sv, "month"sv },
  372. { "weeks"sv, "week"sv },
  373. { "days"sv, "day"sv },
  374. { "hours"sv, "hour"sv },
  375. { "minutes"sv, "minute"sv },
  376. { "seconds"sv, "second"sv },
  377. { "milliseconds"sv, "millisecond"sv },
  378. { "microseconds"sv, "microsecond"sv },
  379. { "nanoseconds"sv, "nanosecond"sv }
  380. };
  381. // 13.17 ToLargestTemporalUnit ( normalizedOptions, disallowedUnits, fallback [ , autoValue ] ), https://tc39.es/proposal-temporal/#sec-temporal-tolargesttemporalunit
  382. ThrowCompletionOr<String> to_largest_temporal_unit(GlobalObject& global_object, Object const& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value)
  383. {
  384. auto& vm = global_object.vm();
  385. // 1. Assert: disallowedUnits does not contain fallback.
  386. // 2. Assert: disallowedUnits does not contain "auto".
  387. // 3. Assert: autoValue is not present or fallback is "auto".
  388. VERIFY(!auto_value.has_value() || fallback == "auto"sv);
  389. // 4. Assert: autoValue is not present or disallowedUnits does not contain autoValue.
  390. // 5. Let largestUnit be ? GetOption(normalizedOptions, "largestUnit", « String », « "auto", "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" », fallback).
  391. auto largest_unit_value = TRY(get_option(global_object, normalized_options, vm.names.largestUnit, { OptionType::String }, { "auto"sv, "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv, "minutes"sv, "second"sv, "seconds"sv, "millisecond"sv, "milliseconds"sv, "microsecond"sv, "microseconds"sv, "nanosecond"sv, "nanoseconds"sv }, js_string(vm, fallback)));
  392. auto largest_unit = largest_unit_value.as_string().string();
  393. // 6. If largestUnit is "auto" and autoValue is present, then
  394. if (largest_unit == "auto"sv && auto_value.has_value()) {
  395. // a. Return autoValue.
  396. return *auto_value;
  397. }
  398. // 7. If largestUnit is in the Plural column of Table 12, then
  399. if (auto singular_unit = plural_to_singular_units.get(largest_unit); singular_unit.has_value()) {
  400. // a. Set largestUnit to the corresponding Singular value of the same row.
  401. largest_unit = singular_unit.value();
  402. }
  403. // 8. If disallowedUnits contains largestUnit, then
  404. if (disallowed_units.contains_slow(largest_unit)) {
  405. // a. Throw a RangeError exception.
  406. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, largest_unit, vm.names.largestUnit.as_string());
  407. }
  408. // 9. Return largestUnit.
  409. return largest_unit;
  410. }
  411. // 13.18 ToSmallestTemporalUnit ( normalizedOptions, disallowedUnits, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-tosmallesttemporalunit
  412. ThrowCompletionOr<Optional<String>> to_smallest_temporal_unit(GlobalObject& global_object, Object const& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback)
  413. {
  414. auto& vm = global_object.vm();
  415. // 1. Assert: disallowedUnits does not contain fallback.
  416. // 2. Let smallestUnit be ? GetOption(normalizedOptions, "smallestUnit", « String », « "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" », fallback).
  417. auto smallest_unit_value = TRY(get_option(global_object, normalized_options, vm.names.smallestUnit, { OptionType::String }, { "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv, "minutes"sv, "second"sv, "seconds"sv, "millisecond"sv, "milliseconds"sv, "microsecond"sv, "microseconds"sv, "nanosecond"sv, "nanoseconds"sv }, fallback.has_value() ? js_string(vm, *fallback) : js_undefined()));
  418. // OPTIMIZATION: We skip the following string-only checks for the fallback to tidy up the code a bit
  419. if (smallest_unit_value.is_undefined())
  420. return Optional<String> {};
  421. VERIFY(smallest_unit_value.is_string());
  422. auto smallest_unit = smallest_unit_value.as_string().string();
  423. // 3. If smallestUnit is in the Plural column of Table 12, then
  424. if (auto singular_unit = plural_to_singular_units.get(smallest_unit); singular_unit.has_value()) {
  425. // a. Set smallestUnit to the corresponding Singular value of the same row.
  426. smallest_unit = singular_unit.value();
  427. }
  428. // 4. If disallowedUnits contains smallestUnit, then
  429. if (disallowed_units.contains_slow(smallest_unit)) {
  430. // a. Throw a RangeError exception.
  431. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, smallest_unit, vm.names.smallestUnit.as_string());
  432. }
  433. // 5. Return smallestUnit.
  434. return smallest_unit;
  435. }
  436. // 13.19 ToTemporalDurationTotalUnit ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldurationtotalunit
  437. ThrowCompletionOr<String> to_temporal_duration_total_unit(GlobalObject& global_object, Object const& normalized_options)
  438. {
  439. auto& vm = global_object.vm();
  440. // 1. Let unit be ? GetOption(normalizedOptions, "unit", « String », « "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" », undefined).
  441. auto unit_value = TRY(get_option(global_object, normalized_options, vm.names.unit, { OptionType::String }, { "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv, "minutes"sv, "second"sv, "seconds"sv, "millisecond"sv, "milliseconds"sv, "microsecond"sv, "microseconds"sv, "nanosecond"sv, "nanoseconds"sv }, js_undefined()));
  442. // 2. If unit is undefined, then
  443. if (unit_value.is_undefined()) {
  444. // a. Throw a RangeError exception.
  445. return vm.throw_completion<RangeError>(global_object, ErrorType::IsUndefined, "unit option value"sv);
  446. }
  447. auto unit = unit_value.as_string().string();
  448. // 3. If unit is in the Plural column of Table 12, then
  449. if (auto singular_unit = plural_to_singular_units.get(unit); singular_unit.has_value()) {
  450. // a. Set unit to the corresponding Singular value of the same row.
  451. unit = *singular_unit;
  452. }
  453. // 4. Return unit.
  454. return unit;
  455. }
  456. // 13.21 ToRelativeTemporalObject ( options ), https://tc39.es/proposal-temporal/#sec-temporal-torelativetemporalobject
  457. ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object, Object const& options)
  458. {
  459. auto& vm = global_object.vm();
  460. // 1. Assert: Type(options) is Object.
  461. // 2. Let value be ? Get(options, "relativeTo").
  462. auto value = TRY(options.get(vm.names.relativeTo));
  463. // 3. If value is undefined, then
  464. if (value.is_undefined()) {
  465. // a. Return value.
  466. return value;
  467. }
  468. // 4. Let offsetBehaviour be option.
  469. auto offset_behavior = OffsetBehavior::Option;
  470. // 5. Let matchBehaviour be match exactly.
  471. auto match_behavior = MatchBehavior::MatchExactly;
  472. ISODateTime result;
  473. Value offset_string;
  474. Value time_zone;
  475. Object* calendar = nullptr;
  476. // 6. If Type(value) is Object, then
  477. if (value.is_object()) {
  478. auto& value_object = value.as_object();
  479. // a. If value has either an [[InitializedTemporalDate]] or [[InitializedTemporalZonedDateTime]] internal slot, then
  480. if (is<PlainDate>(value_object) || is<ZonedDateTime>(value_object)) {
  481. // i. Return value.
  482. return value;
  483. }
  484. // b. If value has an [[InitializedTemporalDateTime]] internal slot, then
  485. if (is<PlainDateTime>(value_object)) {
  486. auto& plain_date_time = static_cast<PlainDateTime&>(value_object);
  487. // i. Return ? CreateTemporalDate(value.[[ISOYear]], value.[[ISOMonth]], value.[[ISODay]], 0, 0, 0, 0, 0, 0, value.[[Calendar]]).
  488. 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()));
  489. }
  490. // c. Let calendar be ? GetTemporalCalendarWithISODefault(value).
  491. calendar = TRY(get_temporal_calendar_with_iso_default(global_object, value_object));
  492. // d. Let fieldNames be ? CalendarFields(calendar, « "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year" »).
  493. 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 }));
  494. // e. Let fields be ? PrepareTemporalFields(value, fieldNames, «»).
  495. auto* fields = TRY(prepare_temporal_fields(global_object, value_object, field_names, {}));
  496. // f. Let dateOptions be ! OrdinaryObjectCreate(null).
  497. auto* date_options = Object::create(global_object, nullptr);
  498. // g. Perform ! CreateDataPropertyOrThrow(dateOptions, "overflow", "constrain").
  499. MUST(date_options->create_data_property_or_throw(vm.names.overflow, js_string(vm, "constrain"sv)));
  500. // h. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, dateOptions).
  501. result = TRY(interpret_temporal_date_time_fields(global_object, *calendar, *fields, *date_options));
  502. // i. Let offsetString be ? Get(value, "offset").
  503. offset_string = TRY(value_object.get(vm.names.offset));
  504. // j. Let timeZone be ? Get(value, "timeZone").
  505. time_zone = TRY(value_object.get(vm.names.timeZone));
  506. // k. If offsetString is undefined, then
  507. if (offset_string.is_undefined()) {
  508. // i. Set offsetBehaviour to wall.
  509. offset_behavior = OffsetBehavior::Wall;
  510. }
  511. }
  512. // 7. Else,
  513. else {
  514. // a. Let string be ? ToString(value).
  515. auto string = TRY(value.to_string(global_object));
  516. // b. Let result be ? ParseTemporalRelativeToString(string).
  517. auto parsed_result = TRY(parse_temporal_relative_to_string(global_object, string));
  518. // NOTE: The ISODateTime struct inside `parsed_result` will be moved into `result` at the end of this path to avoid mismatching names.
  519. // Thus, all remaining references to `result` in this path actually refer to `parsed_result`.
  520. // c. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]).
  521. 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()));
  522. // d. Let offsetString be result.[[TimeZoneOffset]].
  523. offset_string = parsed_result.time_zone.offset.has_value() ? js_string(vm, *parsed_result.time_zone.offset) : js_undefined();
  524. // e. Let timeZone be result.[[TimeZoneIANAName]].
  525. time_zone = parsed_result.time_zone.name.has_value() ? js_string(vm, *parsed_result.time_zone.name) : js_undefined();
  526. // f. If result.[[TimeZoneZ]] is true, then
  527. if (parsed_result.time_zone.z) {
  528. // i. Set offsetBehaviour to exact.
  529. offset_behavior = OffsetBehavior::Exact;
  530. }
  531. // g. Else if offsetString is undefined, then
  532. else if (offset_string.is_undefined()) {
  533. // i. Set offsetBehaviour to wall.
  534. offset_behavior = OffsetBehavior::Wall;
  535. }
  536. // h. Set matchBehaviour to match minutes.
  537. match_behavior = MatchBehavior::MatchMinutes;
  538. // See NOTE above about why this is done.
  539. result = move(parsed_result.date_time);
  540. }
  541. // 8. If timeZone is not undefined, then
  542. if (!time_zone.is_undefined()) {
  543. // a. Set timeZone to ? ToTemporalTimeZone(timeZone).
  544. time_zone = TRY(to_temporal_time_zone(global_object, time_zone));
  545. double offset_ns;
  546. // b. If offsetBehaviour is option, then
  547. if (offset_behavior == OffsetBehavior::Option) {
  548. // i. Set offsetString to ? ToString(offsetString).
  549. // 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.
  550. auto actual_offset_string = TRY(offset_string.to_string(global_object));
  551. // ii. Let offsetNs be ? ParseTimeZoneOffsetString(offsetString).
  552. offset_ns = TRY(parse_time_zone_offset_string(global_object, actual_offset_string));
  553. }
  554. // c. Else,
  555. else {
  556. // i. Let offsetNs be 0.
  557. offset_ns = 0;
  558. }
  559. // d. 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).
  560. 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));
  561. // e. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
  562. return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone.as_object(), *calendar));
  563. }
  564. // 9. Return ! CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
  565. return TRY(create_temporal_date(global_object, result.year, result.month, result.day, *calendar));
  566. }
  567. // 13.22 ValidateTemporalUnitRange ( largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-validatetemporalunitrange
  568. ThrowCompletionOr<void> validate_temporal_unit_range(GlobalObject& global_object, StringView largest_unit, StringView smallest_unit)
  569. {
  570. auto& vm = global_object.vm();
  571. // 1. If smallestUnit is "year" and largestUnit is not "year", then
  572. if (smallest_unit == "year"sv && largest_unit != "year"sv) {
  573. // a. Throw a RangeError exception.
  574. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  575. }
  576. // 2. If smallestUnit is "month" and largestUnit is not "year" or "month", then
  577. if (smallest_unit == "month"sv && !largest_unit.is_one_of("year"sv, "month"sv)) {
  578. // a. Throw a RangeError exception.
  579. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  580. }
  581. // 3. If smallestUnit is "week" and largestUnit is not one of "year", "month", or "week", then
  582. if (smallest_unit == "week"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv)) {
  583. // a. Throw a RangeError exception.
  584. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  585. }
  586. // 4. If smallestUnit is "day" and largestUnit is not one of "year", "month", "week", or "day", then
  587. if (smallest_unit == "day"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) {
  588. // a. Throw a RangeError exception.
  589. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  590. }
  591. // 5. If smallestUnit is "hour" and largestUnit is not one of "year", "month", "week", "day", or "hour", then
  592. if (smallest_unit == "hour"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv, "hour"sv)) {
  593. // a. Throw a RangeError exception.
  594. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  595. }
  596. // 6. If smallestUnit is "minute" and largestUnit is "second", "millisecond", "microsecond", or "nanosecond", then
  597. if (smallest_unit == "minute"sv && largest_unit.is_one_of("second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
  598. // a. Throw a RangeError exception.
  599. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  600. }
  601. // 7. If smallestUnit is "second" and largestUnit is "millisecond", "microsecond", or "nanosecond", then
  602. if (smallest_unit == "second"sv && largest_unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
  603. // a. Throw a RangeError exception.
  604. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  605. }
  606. // 8. If smallestUnit is "millisecond" and largestUnit is "microsecond" or "nanosecond", then
  607. if (smallest_unit == "millisecond"sv && largest_unit.is_one_of("microsecond"sv, "nanosecond"sv)) {
  608. // a. Throw a RangeError exception.
  609. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  610. }
  611. // 9. If smallestUnit is "microsecond" and largestUnit is "nanosecond", then
  612. if (smallest_unit == "microsecond"sv && largest_unit == "nanosecond"sv) {
  613. // a. Throw a RangeError exception.
  614. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit);
  615. }
  616. return {};
  617. }
  618. // 13.23 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits
  619. String larger_of_two_temporal_units(StringView unit1, StringView unit2)
  620. {
  621. // 1. If either u1 or u2 is "year", return "year".
  622. if (unit1 == "year"sv || unit2 == "year"sv)
  623. return "year"sv;
  624. // 2. If either u1 or u2 is "month", return "month".
  625. if (unit1 == "month"sv || unit2 == "month"sv)
  626. return "month"sv;
  627. // 3. If either u1 or u2 is "week", return "week".
  628. if (unit1 == "week"sv || unit2 == "week"sv)
  629. return "week"sv;
  630. // 4. If either u1 or u2 is "day", return "day".
  631. if (unit1 == "day"sv || unit2 == "day"sv)
  632. return "day"sv;
  633. // 5. If either u1 or u2 is "hour", return "hour".
  634. if (unit1 == "hour"sv || unit2 == "hour"sv)
  635. return "hour"sv;
  636. // 6. If either u1 or u2 is "minute", return "minute".
  637. if (unit1 == "minute"sv || unit2 == "minute"sv)
  638. return "minute"sv;
  639. // 7. If either u1 or u2 is "second", return "second".
  640. if (unit1 == "second"sv || unit2 == "second"sv)
  641. return "second"sv;
  642. // 8. If either u1 or u2 is "millisecond", return "millisecond".
  643. if (unit1 == "millisecond"sv || unit2 == "millisecond"sv)
  644. return "millisecond"sv;
  645. // 9. If either u1 or u2 is "microsecond", return "microsecond".
  646. if (unit1 == "microsecond"sv || unit2 == "microsecond"sv)
  647. return "microsecond"sv;
  648. // 10. Return "nanosecond".
  649. return "nanosecond"sv;
  650. }
  651. // 13.24 MergeLargestUnitOption ( options, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-mergelargestunitoption
  652. ThrowCompletionOr<Object*> merge_largest_unit_option(GlobalObject& global_object, Object& options, String largest_unit)
  653. {
  654. auto& vm = global_object.vm();
  655. // 1. Let merged be ! OrdinaryObjectCreate(%Object.prototype%).
  656. auto* merged = Object::create(global_object, global_object.object_prototype());
  657. // 2. Let keys be ? EnumerableOwnPropertyNames(options, key).
  658. auto keys = TRY(options.enumerable_own_property_names(Object::PropertyKind::Key));
  659. // 3. For each element nextKey of keys, do
  660. for (auto& key : keys) {
  661. auto next_key = PropertyKey::from_value(global_object, key);
  662. // a. Let propValue be ? Get(options, nextKey).
  663. auto prop_value = TRY(options.get(next_key));
  664. // b. Perform ! CreateDataPropertyOrThrow(merged, nextKey, propValue).
  665. MUST(merged->create_data_property_or_throw(next_key, prop_value));
  666. }
  667. // 4. Perform ! CreateDataPropertyOrThrow(merged, "largestUnit", largestUnit).
  668. MUST(merged->create_data_property_or_throw(vm.names.largestUnit, js_string(vm, move(largest_unit))));
  669. // 5. Return merged.
  670. return merged;
  671. }
  672. // 13.25 MaximumTemporalDurationRoundingIncrement ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement
  673. Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit)
  674. {
  675. // 1. If unit is "year", "month", "week", or "day", then
  676. if (unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) {
  677. // a. Return undefined.
  678. return {};
  679. }
  680. // 2. If unit is "hour", then
  681. if (unit == "hour"sv) {
  682. // a. Return 24.
  683. return 24;
  684. }
  685. // 3. If unit is "minute" or "second", then
  686. if (unit.is_one_of("minute"sv, "second"sv)) {
  687. // a. Return 60.
  688. return 60;
  689. }
  690. // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond".
  691. VERIFY(unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv));
  692. // 5. Return 1000.
  693. return 1000;
  694. }
  695. // 13.26 RejectObjectWithCalendarOrTimeZone ( object ), https://tc39.es/proposal-temporal/#sec-temporal-rejectobjectwithcalendarortimezone
  696. ThrowCompletionOr<void> reject_object_with_calendar_or_time_zone(GlobalObject& global_object, Object& object)
  697. {
  698. auto& vm = global_object.vm();
  699. // 1. Assert: Type(object) is Object.
  700. // 2. If object has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
  701. if (is<PlainDate>(object) || is<PlainDateTime>(object) || is<PlainMonthDay>(object) || is<PlainTime>(object) || is<PlainYearMonth>(object) || is<ZonedDateTime>(object)) {
  702. // a. Throw a TypeError exception.
  703. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustNotHave, "calendar or timeZone");
  704. }
  705. // 3. Let calendarProperty be ? Get(object, "calendar").
  706. auto calendar_property = TRY(object.get(vm.names.calendar));
  707. // 4. If calendarProperty is not undefined, then
  708. if (!calendar_property.is_undefined()) {
  709. // a. Throw a TypeError exception.
  710. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustNotHave, "calendar");
  711. }
  712. // 5. Let timeZoneProperty be ? Get(object, "timeZone").
  713. auto time_zone_property = TRY(object.get(vm.names.timeZone));
  714. // 6. If timeZoneProperty is not undefined, then
  715. if (!time_zone_property.is_undefined()) {
  716. // a. Throw a TypeError exception.
  717. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustNotHave, "timeZone");
  718. }
  719. return {};
  720. }
  721. // 13.27 FormatSecondsStringPart ( second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatsecondsstringpart
  722. String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision)
  723. {
  724. // 1. Assert: second, millisecond, microsecond and nanosecond are integers.
  725. // Non-standard sanity check
  726. if (precision.has<StringView>())
  727. VERIFY(precision.get<StringView>().is_one_of("minute"sv, "auto"sv));
  728. // 2. If precision is "minute", return "".
  729. if (precision.has<StringView>() && precision.get<StringView>() == "minute"sv)
  730. return String::empty();
  731. // 3. Let secondsString be the string-concatenation of the code unit 0x003A (COLON) and second formatted as a two-digit decimal number, padded to the left with zeroes if necessary.
  732. auto seconds_string = String::formatted(":{:02}", second);
  733. // 4. Let fraction be millisecond × 10^6 + microsecond × 10^3 + nanosecond.
  734. u32 fraction = millisecond * 1'000'000 + microsecond * 1'000 + nanosecond;
  735. String fraction_string;
  736. // 5. If precision is "auto", then
  737. if (precision.has<StringView>() && precision.get<StringView>() == "auto"sv) {
  738. // a. If fraction is 0, return secondsString.
  739. if (fraction == 0)
  740. return seconds_string;
  741. // b. Set fraction to fraction formatted as a nine-digit decimal number, padded to the left with zeroes if necessary.
  742. fraction_string = String::formatted("{:09}", fraction);
  743. // c. Set fraction to the longest possible substring of fraction starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO).
  744. fraction_string = fraction_string.trim("0"sv, TrimMode::Right);
  745. }
  746. // 6. Else,
  747. else {
  748. // a. If precision is 0, return secondsString.
  749. if (precision.get<u8>() == 0)
  750. return seconds_string;
  751. // b. Set fraction to fraction formatted as a nine-digit decimal number, padded to the left with zeroes if necessary.
  752. fraction_string = String::formatted("{:09}", fraction);
  753. // c. Set fraction to the substring of fraction from 0 to precision.
  754. fraction_string = fraction_string.substring(0, precision.get<u8>());
  755. }
  756. // 7. Return the string-concatenation of secondsString, the code unit 0x002E (FULL STOP), and fraction.
  757. return String::formatted("{}.{}", seconds_string, fraction_string);
  758. }
  759. // 13.28 Sign ( n ), https://tc39.es/proposal-temporal/#sec-temporal-sign
  760. double sign(double n)
  761. {
  762. // 1. If n is NaN, n is +0𝔽, or n is −0𝔽, return n.
  763. if (isnan(n) || n == 0)
  764. return n;
  765. // 2. If n < +0𝔽, return −1𝔽.
  766. if (n < 0)
  767. return -1;
  768. // 3. Return 1𝔽.
  769. return 1;
  770. }
  771. // 13.29 ConstrainToRange ( x, minimum, maximum ), https://tc39.es/proposal-temporal/#sec-temporal-constraintorange
  772. double constrain_to_range(double x, double minimum, double maximum)
  773. {
  774. // 1. Assert: x, minimum and maximum are mathematical values.
  775. // 2. Return min(max(x, minimum), maximum).
  776. return min(max(x, minimum), maximum);
  777. }
  778. // NOTE: We have two variants of this function, one using doubles and one using BigInts - most of the time
  779. // doubles will be fine, but take care to choose the right one. The spec is not very clear about this, as
  780. // it uses mathematical values which can be arbitrarily (but not infinitely) large.
  781. // Incidentally V8's Temporal implementation does the same :^)
  782. // 13.32 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
  783. i64 round_number_to_increment(double x, u64 increment, StringView rounding_mode)
  784. {
  785. // 1. Assert: x and increment are mathematical values.
  786. // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand".
  787. VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv);
  788. // 3. Let quotient be x / increment.
  789. auto quotient = x / (double)increment;
  790. double rounded;
  791. // 4. If roundingMode is "ceil", then
  792. if (rounding_mode == "ceil"sv) {
  793. // a. Let rounded be −floor(−quotient).
  794. rounded = -floor(-quotient);
  795. }
  796. // 5. Else if roundingMode is "floor", then
  797. else if (rounding_mode == "floor"sv) {
  798. // a. Let rounded be floor(quotient).
  799. rounded = floor(quotient);
  800. }
  801. // 6. Else if roundingMode is "trunc", then
  802. else if (rounding_mode == "trunc"sv) {
  803. // a. Let rounded be the integral part of quotient, removing any fractional digits.
  804. rounded = trunc(quotient);
  805. }
  806. // 7. Else,
  807. else {
  808. // a. Let rounded be ! RoundHalfAwayFromZero(quotient).
  809. rounded = round(quotient);
  810. }
  811. // 8. Return rounded × increment.
  812. return (i64)rounded * (i64)increment;
  813. }
  814. // 13.32 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
  815. BigInt* round_number_to_increment(GlobalObject& global_object, BigInt const& x, u64 increment, StringView rounding_mode)
  816. {
  817. auto& heap = global_object.heap();
  818. // 1. Assert: x and increment are mathematical values.
  819. // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand".
  820. VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv);
  821. // OPTIMIZATION: If the increment is 1 the number is always rounded
  822. if (increment == 1)
  823. return js_bigint(heap, x.big_integer());
  824. auto increment_big_int = Crypto::UnsignedBigInteger::create_from(increment);
  825. // 3. Let quotient be x / increment.
  826. auto division_result = x.big_integer().divided_by(increment_big_int);
  827. // OPTIMIZATION: If there's no remainder the number is already rounded
  828. if (division_result.remainder == Crypto::UnsignedBigInteger { 0 })
  829. return js_bigint(heap, x.big_integer());
  830. Crypto::SignedBigInteger rounded = move(division_result.quotient);
  831. // 4. If roundingMode is "ceil", then
  832. if (rounding_mode == "ceil"sv) {
  833. // a. Let rounded be −floor(−quotient).
  834. if (!division_result.remainder.is_negative())
  835. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  836. }
  837. // 5. Else if roundingMode is "floor", then
  838. else if (rounding_mode == "floor"sv) {
  839. // a. Let rounded be floor(quotient).
  840. if (division_result.remainder.is_negative())
  841. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  842. }
  843. // 6. Else if roundingMode is "trunc", then
  844. else if (rounding_mode == "trunc"sv) {
  845. // a. Let rounded be the integral part of quotient, removing any fractional digits.
  846. // NOTE: This is a no-op
  847. }
  848. // 7. Else,
  849. else {
  850. // a. Let rounded be ! RoundHalfAwayFromZero(quotient).
  851. if (division_result.remainder.multiplied_by(Crypto::UnsignedBigInteger { 2 }).unsigned_value() >= increment_big_int) {
  852. if (division_result.remainder.is_negative())
  853. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  854. else
  855. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  856. }
  857. }
  858. // 8. Return rounded × increment.
  859. return js_bigint(heap, rounded.multiplied_by(increment_big_int));
  860. }
  861. // 13.34 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
  862. ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object, ParseResult const& parse_result)
  863. {
  864. auto& vm = global_object.vm();
  865. // 1. Assert: Type(isoString) is String.
  866. // 2. Let year, month, day, hour, minute, second, fraction, and calendar be the parts of isoString produced respectively by the DateYear, DateMonth, DateDay, TimeHour, TimeMinute, TimeSecond, TimeFractionalPart, and CalendarName productions, or undefined if not present.
  867. auto year_part = parse_result.date_year;
  868. auto month_part = parse_result.date_month;
  869. auto day_part = parse_result.date_day;
  870. auto hour_part = parse_result.time_hour;
  871. auto minute_part = parse_result.time_minute;
  872. auto second_part = parse_result.time_second;
  873. auto fraction_part = parse_result.time_fractional_part;
  874. auto calendar_part = parse_result.calendar_name;
  875. // 3. Let year be the part of isoString produced by the DateYear production.
  876. // 4. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS).
  877. String normalized_year;
  878. if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv))
  879. normalized_year = String::formatted("-{}", year_part->substring_view(3));
  880. else
  881. normalized_year = year_part.value_or("0");
  882. // 5. Set year to ! ToIntegerOrInfinity(year).
  883. auto year = *normalized_year.to_int<i32>();
  884. u8 month;
  885. // 6. If month is undefined, then
  886. if (!month_part.has_value()) {
  887. // a. Set month to 1.
  888. month = 1;
  889. }
  890. // 7. Else,
  891. else {
  892. // a. Set month to ! ToIntegerOrInfinity(month).
  893. month = *month_part->to_uint<u8>();
  894. }
  895. u8 day;
  896. // 8. If day is undefined, then
  897. if (!day_part.has_value()) {
  898. // a. Set day to 1.
  899. day = 1;
  900. }
  901. // 9. Else,
  902. else {
  903. // a. Set day to ! ToIntegerOrInfinity(day).
  904. day = *day_part->to_uint<u8>();
  905. }
  906. // 10. Set hour to ! ToIntegerOrInfinity(hour).
  907. u8 hour = *hour_part.value_or("0"sv).to_uint<u8>();
  908. // 11. Set minute to ! ToIntegerOrInfinity(minute).
  909. u8 minute = *minute_part.value_or("0"sv).to_uint<u8>();
  910. // 12. Set second to ! ToIntegerOrInfinity(second).
  911. u8 second = *second_part.value_or("0"sv).to_uint<u8>();
  912. // 13. If second is 60, then
  913. if (second == 60) {
  914. // a. Set second to 59.
  915. second = 59;
  916. }
  917. u16 millisecond;
  918. u16 microsecond;
  919. u16 nanosecond;
  920. // 14. If fraction is not undefined, then
  921. if (fraction_part.has_value()) {
  922. // a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  923. auto fraction = String::formatted("{}000000000", *fraction_part);
  924. // b. Let millisecond be the String value equal to the substring of fraction from 0 to 3.
  925. // c. Set millisecond to ! ToIntegerOrInfinity(millisecond).
  926. millisecond = *fraction.substring(0, 3).to_uint<u16>();
  927. // d. Let microsecond be the String value equal to the substring of fraction from 3 to 6.
  928. // e. Set microsecond to ! ToIntegerOrInfinity(microsecond).
  929. microsecond = *fraction.substring(3, 3).to_uint<u16>();
  930. // f. Let nanosecond be the String value equal to the substring of fraction from 6 to 9.
  931. // g. Set nanosecond to ! ToIntegerOrInfinity(nanosecond).
  932. nanosecond = *fraction.substring(6, 3).to_uint<u16>();
  933. }
  934. // 15. Else,
  935. else {
  936. // a. Let millisecond be 0.
  937. millisecond = 0;
  938. // b. Let microsecond be 0.
  939. microsecond = 0;
  940. // c. Let nanosecond be 0.
  941. nanosecond = 0;
  942. }
  943. // 16. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception.
  944. if (!is_valid_iso_date(year, month, day))
  945. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidISODate);
  946. // 17. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
  947. if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond))
  948. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTime);
  949. // 18. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }.
  950. return ISODateTime { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond, .calendar = calendar_part.has_value() ? *calendar_part : Optional<String>() };
  951. }
  952. // 13.35 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring
  953. ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string)
  954. {
  955. // 1. Assert: Type(isoString) is String.
  956. // 2. If isoString does not satisfy the syntax of a TemporalInstantString (see 13.33), then
  957. // a. Throw a RangeError exception.
  958. // TODO
  959. // 3. Let result be ! ParseISODateTime(isoString).
  960. auto result = MUST(parse_iso_date_time(global_object, {}));
  961. // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  962. auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string));
  963. // 5. Let offsetString be timeZoneResult.[[OffsetString]].
  964. auto offset_string = time_zone_result.offset;
  965. // 6. If timeZoneResult.[[Z]] is true, then
  966. if (time_zone_result.z) {
  967. // a. Set offsetString to "+00:00".
  968. offset_string = "+00:00"sv;
  969. }
  970. // 7. Assert: offsetString is not undefined.
  971. VERIFY(offset_string.has_value());
  972. // 8. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[TimeZoneOffsetString]]: offsetString }.
  973. 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) };
  974. }
  975. // 13.36 ParseTemporalZonedDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalzoneddatetimestring
  976. ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(GlobalObject& global_object, String const& iso_string)
  977. {
  978. // 1. Assert: Type(isoString) is String.
  979. // 2. If isoString does not satisfy the syntax of a TemporalZonedDateTimeString (see 13.33), then
  980. // a. Throw a RangeError exception.
  981. // TODO
  982. // 3. Let result be ! ParseISODateTime(isoString).
  983. auto result = MUST(parse_iso_date_time(global_object, {}));
  984. // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  985. auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string));
  986. // 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]]: timeZoneResult.[[Z]], [[TimeZoneOffsetString]]: timeZoneResult.[[OffsetString]], [[TimeZoneName]]: timeZoneResult.[[Name]] }.
  987. // 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.
  988. // InterpretTemporalDateTimeFields returns an ISODateTime, so the moved in `result` here is subsequently moved into ParseTemporalZonedDateTimeString's `result` variable.
  989. return TemporalZonedDateTime { .date_time = move(result), .time_zone = move(time_zone_result) };
  990. }
  991. // 13.37 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
  992. ThrowCompletionOr<String> parse_temporal_calendar_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  993. {
  994. auto& vm = global_object.vm();
  995. // 1. Assert: Type(isoString) is String.
  996. // 2. If isoString does not satisfy the syntax of a TemporalCalendarString (see 13.33), then
  997. // a. Throw a RangeError exception.
  998. // 3. Let id be the part of isoString produced by the CalendarName production, or undefined if not present.
  999. Optional<StringView> id_part;
  1000. return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "ParseTemporalCalendarString");
  1001. // 4. If id is undefined, then
  1002. if (!id_part.has_value()) {
  1003. // a. Return "iso8601".
  1004. return "iso8601"sv;
  1005. }
  1006. // 5. If ! IsBuiltinCalendar(id) is false, then
  1007. if (!is_builtin_calendar(*id_part)) {
  1008. // a. Throw a RangeError exception.
  1009. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidCalendarIdentifier, *id_part);
  1010. }
  1011. // 6. Return id.
  1012. return id_part.value();
  1013. }
  1014. // 13.38 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring
  1015. ThrowCompletionOr<TemporalDate> parse_temporal_date_string(GlobalObject& global_object, String const& iso_string)
  1016. {
  1017. auto& vm = global_object.vm();
  1018. // 1. Assert: Type(isoString) is String.
  1019. // 2. If isoString does not satisfy the syntax of a TemporalDateString (see 13.33), then
  1020. auto parse_result = parse_iso8601(Production::TemporalDateString, iso_string);
  1021. if (!parse_result.has_value()) {
  1022. // a. Throw a RangeError exception.
  1023. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidDateTimeString, iso_string);
  1024. }
  1025. // 3. Let result be ? ParseISODateTime(isoString).
  1026. auto result = TRY(parse_iso_date_time(global_object, *parse_result));
  1027. // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  1028. return TemporalDate { .year = result.year, .month = result.month, .day = result.day, .calendar = move(result.calendar) };
  1029. }
  1030. // 13.39 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring
  1031. ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  1032. {
  1033. // 1. Assert: Type(isoString) is String.
  1034. // 2. If isoString does not satisfy the syntax of a TemporalDateTimeString (see 13.33), then
  1035. // a. Throw a RangeError exception.
  1036. // TODO
  1037. // 3. Let result be ? ParseISODateTime(isoString).
  1038. auto result = TRY(parse_iso_date_time(global_object, {}));
  1039. // 4. Return result.
  1040. return result;
  1041. }
  1042. // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
  1043. ThrowCompletionOr<TemporalDuration> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string)
  1044. {
  1045. (void)iso_string;
  1046. auto& vm = global_object.vm();
  1047. return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "ParseTemporalDurationString");
  1048. }
  1049. // 13.41 ParseTemporalMonthDayString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalmonthdaystring
  1050. ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  1051. {
  1052. // 1. Assert: Type(isoString) is String.
  1053. // 2. If isoString does not satisfy the syntax of a TemporalMonthDayString (see 13.33), then
  1054. // a. Throw a RangeError exception.
  1055. // TODO
  1056. // 3. Let result be ? ParseISODateTime(isoString).
  1057. auto result = TRY(parse_iso_date_time(global_object, {}));
  1058. // 4. Let year be result.[[Year]].
  1059. Optional<i32> year = result.year;
  1060. // 5. If no part of isoString is produced by the DateYear production, then
  1061. // a. Set year to undefined.
  1062. // TODO (this is the case when TemporalMonthDayString is a DateSpecMonthDay)
  1063. // 6. Return the Record { [[Year]]: year, [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  1064. return TemporalMonthDay { .year = year, .month = result.month, .day = result.day, .calendar = move(result.calendar) };
  1065. }
  1066. // 13.42 ParseTemporalRelativeToString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalrelativetostring
  1067. ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_relative_to_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  1068. {
  1069. // 1. Assert: Type(isoString) is String.
  1070. // 2. If isoString does not satisfy the syntax of a TemporalRelativeToString (see 13.33), then
  1071. // a. Throw a RangeError exception.
  1072. // TODO
  1073. // 3. Let result be ! ParseISODateTime(isoString).
  1074. auto result = MUST(parse_iso_date_time(global_object, {}));
  1075. bool z;
  1076. Optional<String> offset;
  1077. Optional<String> time_zone;
  1078. // TODO: 4. If isoString satisfies the syntax of a TemporalZonedDateTimeString (see 13.33), then
  1079. // a. Let timeZoneResult be ! ParseTemporalTimeZoneString(isoString).
  1080. // b. Let z be timeZoneResult.[[Z]].
  1081. // c. Let offset be timeZoneResult.[[Offset]].
  1082. // d. Let timeZone be timeZoneResult.[[Name]].
  1083. // TODO: 5. Else,
  1084. // a. Let z be false.
  1085. z = false;
  1086. // b. Let offset be undefined. (NOTE: It already is)
  1087. // c. Let timeZone be undefined. (NOTE: It already is)
  1088. // 6. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]], [[TimeZoneZ]]: z, [[TimeZoneOffset]]: offset, [[TimeZoneIANAName]]: timeZone }.
  1089. return TemporalZonedDateTime { .date_time = move(result), .time_zone = { .z = z, .offset = move(offset), .name = move(time_zone) } };
  1090. }
  1091. // 13.43 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring
  1092. ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  1093. {
  1094. // 1. Assert: Type(isoString) is String.
  1095. // 2. If isoString does not satisfy the syntax of a TemporalTimeString (see 13.33), then
  1096. // a. Throw a RangeError exception.
  1097. // TODO
  1098. // 3. Let result be ? ParseISODateTime(isoString).
  1099. auto result = TRY(parse_iso_date_time(global_object, {}));
  1100. // 4. Return the Record { [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]] }.
  1101. return TemporalTime { .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .calendar = move(result.calendar) };
  1102. }
  1103. // 13.44 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring
  1104. ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  1105. {
  1106. auto& vm = global_object.vm();
  1107. // 1. Assert: Type(isoString) is String.
  1108. // 2. If isoString does not satisfy the syntax of a TemporalTimeZoneString (see 13.33), then
  1109. // a. Throw a RangeError exception.
  1110. // 3. Let z, sign, hours, minutes, seconds, fraction and name be the parts of isoString produced respectively by the UTCDesignator, TimeZoneUTCOffsetSign, TimeZoneUTCOffsetHour, TimeZoneUTCOffsetMinute, TimeZoneUTCOffsetSecond, TimeZoneUTCOffsetFractionalPart, and TimeZoneIANAName productions, or undefined if not present.
  1111. Optional<StringView> z_part;
  1112. Optional<StringView> sign_part;
  1113. Optional<StringView> hours_part;
  1114. Optional<StringView> minutes_part;
  1115. Optional<StringView> seconds_part;
  1116. Optional<StringView> fraction_part;
  1117. Optional<StringView> name_part;
  1118. return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "ParseTemporalTimeZoneString");
  1119. // 4. If z is not undefined, then
  1120. if (z_part.has_value()) {
  1121. // a. Return the Record { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: name }.
  1122. return TemporalTimeZone { .z = true, .offset = {}, .name = name_part.has_value() ? String { *name_part } : Optional<String> {} };
  1123. }
  1124. Optional<String> offset;
  1125. // 5. If hours is undefined, then
  1126. if (!hours_part.has_value()) {
  1127. // a. Let offsetString be undefined.
  1128. // NOTE: No-op.
  1129. }
  1130. // 6. Else,
  1131. else {
  1132. // a. Assert: sign is not undefined.
  1133. VERIFY(sign_part.has_value());
  1134. // b. Set hours to ! ToIntegerOrInfinity(hours).
  1135. u8 hours = MUST(Value(js_string(vm, *hours_part)).to_integer_or_infinity(global_object));
  1136. u8 sign;
  1137. // c. If sign is the code unit 0x002D (HYPHEN-MINUS) or the code unit 0x2212 (MINUS SIGN), then
  1138. if (sign_part->is_one_of("-", "\u2212")) {
  1139. // i. Set sign to −1.
  1140. sign = -1;
  1141. }
  1142. // d. Else,
  1143. else {
  1144. // i. Set sign to 1.
  1145. sign = 1;
  1146. }
  1147. // e. Set minutes to ! ToIntegerOrInfinity(minutes).
  1148. u8 minutes = MUST(Value(js_string(vm, minutes_part.value_or(""sv))).to_integer_or_infinity(global_object));
  1149. // f. Set seconds to ! ToIntegerOrInfinity(seconds).
  1150. u8 seconds = MUST(Value(js_string(vm, seconds_part.value_or(""sv))).to_integer_or_infinity(global_object));
  1151. i32 nanoseconds;
  1152. // g. If fraction is not undefined, then
  1153. if (fraction_part.has_value()) {
  1154. // i. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  1155. auto fraction = String::formatted("{}000000000", *fraction_part);
  1156. // ii. Let nanoseconds be the String value equal to the substring of fraction from 1 to 10.
  1157. // iii. Set nanoseconds to ! ToIntegerOrInfinity(nanoseconds).
  1158. nanoseconds = MUST(Value(js_string(vm, fraction.substring(1, 10))).to_integer_or_infinity(global_object));
  1159. }
  1160. // h. Else,
  1161. else {
  1162. // i. Let nanoseconds be 0.
  1163. nanoseconds = 0;
  1164. }
  1165. // i. Let offsetNanoseconds be sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds).
  1166. auto offset_nanoseconds = sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000 + nanoseconds);
  1167. // j. Let offsetString be ! FormatTimeZoneOffsetString(offsetNanoseconds).
  1168. offset = format_time_zone_offset_string(offset_nanoseconds);
  1169. }
  1170. Optional<String> name;
  1171. // 7. If name is not undefined, then
  1172. if (name_part.has_value()) {
  1173. // a. If ! IsValidTimeZoneName(name) is false, throw a RangeError exception.
  1174. if (!is_valid_time_zone_name(*name_part))
  1175. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName);
  1176. // b. Set name to ! CanonicalizeTimeZoneName(name).
  1177. name = canonicalize_time_zone_name(*name_part);
  1178. }
  1179. // 8. Return the Record { [[Z]]: false, [[OffsetString]]: offsetString, [[Name]]: name }.
  1180. return TemporalTimeZone { .z = false, .offset = offset, .name = name };
  1181. }
  1182. // 13.45 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring
  1183. ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  1184. {
  1185. // 1. Assert: Type(isoString) is String.
  1186. // 2. If isoString does not satisfy the syntax of a TemporalYearMonthString (see 13.33), then
  1187. // a. Throw a RangeError exception.
  1188. // TODO
  1189. // 3. Let result be ? ParseISODateTime(isoString).
  1190. auto result = TRY(parse_iso_date_time(global_object, {}));
  1191. // 4. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Calendar]]: result.[[Calendar]] }.
  1192. return TemporalYearMonth { .year = result.year, .month = result.month, .day = result.day, .calendar = move(result.calendar) };
  1193. }
  1194. // 13.46 ToPositiveInteger ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-topositiveinteger
  1195. ThrowCompletionOr<double> to_positive_integer(GlobalObject& global_object, Value argument)
  1196. {
  1197. auto& vm = global_object.vm();
  1198. // 1. Let integer be ? ToIntegerThrowOnInfinity(argument).
  1199. auto integer = TRY(to_integer_throw_on_infinity(global_object, argument, ErrorType::TemporalPropertyMustBePositiveInteger));
  1200. // 2. If integer ≤ 0, then
  1201. if (integer <= 0) {
  1202. // a. Throw a RangeError exception.
  1203. return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalPropertyMustBePositiveInteger);
  1204. }
  1205. // 3. Return integer.
  1206. return integer;
  1207. }
  1208. // 13.49 PrepareTemporalFields ( fields, fieldNames, requiredFields ), https://tc39.es/proposal-temporal/#sec-temporal-preparetemporalfields
  1209. ThrowCompletionOr<Object*> prepare_temporal_fields(GlobalObject& global_object, Object const& fields, Vector<String> const& field_names, Vector<StringView> const& required_fields)
  1210. {
  1211. auto& vm = global_object.vm();
  1212. // 1. Assert: Type(fields) is Object.
  1213. // 2. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
  1214. auto* result = Object::create(global_object, global_object.object_prototype());
  1215. VERIFY(result);
  1216. // 3. For each value property of fieldNames, do
  1217. for (auto& property : field_names) {
  1218. // a. Let value be ? Get(fields, property).
  1219. auto value = TRY(fields.get(property));
  1220. // b. If value is undefined, then
  1221. if (value.is_undefined()) {
  1222. // i. If requiredFields contains property, then
  1223. if (required_fields.contains_slow(property)) {
  1224. // 1. Throw a TypeError exception.
  1225. return vm.throw_completion<TypeError>(global_object, ErrorType::MissingRequiredProperty, property);
  1226. }
  1227. // ii. If property is in the Property column of Table 13, then
  1228. // NOTE: The other properties in the table are automatically handled as their default value is undefined
  1229. if (property.is_one_of("hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
  1230. // 1. Set value to the corresponding Default value of the same row.
  1231. value = Value(0);
  1232. }
  1233. }
  1234. // c. Else,
  1235. else {
  1236. // i. If property is in the Property column of Table 13 and there is a Conversion value in the same row, then
  1237. // 1. Let Conversion represent the abstract operation named by the Conversion value of the same row.
  1238. // 2. Set value to ? Conversion(value).
  1239. if (property.is_one_of("year"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv, "eraYear"sv))
  1240. value = Value(TRY(to_integer_throw_on_infinity(global_object, value, ErrorType::TemporalPropertyMustBeFinite)));
  1241. else if (property.is_one_of("month"sv, "day"sv))
  1242. value = Value(TRY(to_positive_integer(global_object, value)));
  1243. else if (property.is_one_of("monthCode"sv, "offset"sv, "era"sv))
  1244. value = TRY(value.to_primitive_string(global_object));
  1245. }
  1246. // d. Perform ! CreateDataPropertyOrThrow(result, property, value).
  1247. MUST(result->create_data_property_or_throw(property, value));
  1248. }
  1249. // 4. Return result.
  1250. return result;
  1251. }
  1252. // 13.50 PreparePartialTemporalFields ( fields, fieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparepartialtemporalfields
  1253. ThrowCompletionOr<Object*> prepare_partial_temporal_fields(GlobalObject& global_object, Object const& fields, Vector<String> const& field_names)
  1254. {
  1255. auto& vm = global_object.vm();
  1256. // 1. Assert: Type(fields) is Object.
  1257. // 2. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
  1258. auto* result = Object::create(global_object, global_object.object_prototype());
  1259. // 3. Let any be false.
  1260. auto any = false;
  1261. // 4. For each value property of fieldNames, do
  1262. for (auto& property : field_names) {
  1263. // a. Let value be ? Get(fields, property).
  1264. auto value = TRY(fields.get(property));
  1265. // b. If value is not undefined, then
  1266. if (!value.is_undefined()) {
  1267. // i. Set any to true.
  1268. any = true;
  1269. // ii. If property is in the Property column of Table 13, then
  1270. // 1. Let Conversion represent the abstract operation named by the Conversion value of the same row.
  1271. // 2. Set value to ? Conversion(value).
  1272. if (property.is_one_of("year"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv, "eraYear"sv))
  1273. value = Value(TRY(to_integer_throw_on_infinity(global_object, value, ErrorType::TemporalPropertyMustBeFinite)));
  1274. else if (property.is_one_of("month"sv, "day"sv))
  1275. value = Value(TRY(to_positive_integer(global_object, value)));
  1276. else if (property.is_one_of("monthCode"sv, "offset"sv, "era"sv))
  1277. value = TRY(value.to_primitive_string(global_object));
  1278. // NOTE: According to the spec this is step 4c, but I believe that's incorrect. See https://github.com/tc39/proposal-temporal/issues/1910.
  1279. // iii. Perform ! CreateDataPropertyOrThrow(result, property, value).
  1280. MUST(result->create_data_property_or_throw(property, value));
  1281. }
  1282. }
  1283. // 5. If any is false, then
  1284. if (!any) {
  1285. // a. Throw a TypeError exception.
  1286. return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalObjectMustHaveOneOf, String::join(", "sv, field_names));
  1287. }
  1288. // 6. Return result.
  1289. return result;
  1290. }
  1291. }