AbstractOperations.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/CharacterTypes.h>
  8. #include <AK/DateTimeLexer.h>
  9. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  10. #include <LibJS/Runtime/Temporal/Duration.h>
  11. #include <LibJS/Runtime/Temporal/PlainDate.h>
  12. #include <LibJS/Runtime/Temporal/PlainTime.h>
  13. #include <LibJS/Runtime/Temporal/TimeZone.h>
  14. namespace JS::Temporal {
  15. // 13.2 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
  16. Object* get_options_object(GlobalObject& global_object, Value options)
  17. {
  18. auto& vm = global_object.vm();
  19. // 1. If options is undefined, then
  20. if (options.is_undefined()) {
  21. // a. Return ! OrdinaryObjectCreate(null).
  22. return Object::create(global_object, nullptr);
  23. }
  24. // 2. If Type(options) is Object, then
  25. if (options.is_object()) {
  26. // a. Return options.
  27. return &options.as_object();
  28. }
  29. // 3. Throw a TypeError exception.
  30. vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Options");
  31. return {};
  32. }
  33. static Optional<OptionType> to_option_type(Value value)
  34. {
  35. if (value.is_boolean())
  36. return OptionType::Boolean;
  37. if (value.is_string())
  38. return OptionType::String;
  39. if (value.is_number())
  40. return OptionType::Number;
  41. return {};
  42. }
  43. // 13.3 GetOption ( options, property, types, values, fallback ), https://tc39.es/proposal-temporal/#sec-getoption
  44. Value get_option(GlobalObject& global_object, Object& options, String const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback)
  45. {
  46. auto& vm = global_object.vm();
  47. // 1. Assert: Type(options) is Object.
  48. // 2. Assert: Each element of types is Boolean, String, or Number.
  49. // 3. Let value be ? Get(options, property).
  50. auto value = options.get(property);
  51. if (vm.exception())
  52. return {};
  53. // 4. If value is undefined, return fallback.
  54. if (value.is_undefined())
  55. return fallback;
  56. OptionType type;
  57. // 5. If types contains Type(value), then
  58. if (auto value_type = to_option_type(value); value_type.has_value() && types.contains_slow(*value_type)) {
  59. // a. Let type be Type(value).
  60. type = *value_type;
  61. }
  62. // 6. Else,
  63. else {
  64. // a. Let type be the last element of types.
  65. type = types.last();
  66. }
  67. // 7. If type is Boolean, then
  68. if (type == OptionType::Boolean) {
  69. // a. Set value to ! ToBoolean(value).
  70. value = Value(value.to_boolean());
  71. }
  72. // 8. Else if type is Number, then
  73. else if (type == OptionType::Number) {
  74. // a. Set value to ? ToNumber(value).
  75. value = value.to_number(global_object);
  76. if (vm.exception())
  77. return {};
  78. // b. If value is NaN, throw a RangeError exception.
  79. if (value.is_nan()) {
  80. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.NaN.as_string(), property);
  81. return {};
  82. }
  83. }
  84. // 9. Else,
  85. else {
  86. // a. Set value to ? ToString(value).
  87. value = value.to_primitive_string(global_object);
  88. if (vm.exception())
  89. return {};
  90. }
  91. // 10. If values is not empty, then
  92. if (!values.is_empty()) {
  93. VERIFY(value.is_string());
  94. // a. If values does not contain value, throw a RangeError exception.
  95. if (!values.contains_slow(value.as_string().string())) {
  96. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property);
  97. return {};
  98. }
  99. }
  100. // 11. Return value.
  101. return value;
  102. }
  103. // 13.8 ToTemporalRoundingMode ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingmode
  104. String to_temporal_rounding_mode(GlobalObject& global_object, Object& normalized_options, String const& fallback)
  105. {
  106. auto& vm = global_object.vm();
  107. auto option = get_option(global_object, normalized_options, "roundingMode", { OptionType::String }, { "ceil"sv, "floor"sv, "trunc"sv, "halfExpand"sv }, js_string(vm, fallback));
  108. if (vm.exception())
  109. return {};
  110. VERIFY(option.is_string());
  111. return option.as_string().string();
  112. }
  113. // 13.14 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
  114. u64 to_temporal_rounding_increment(GlobalObject& global_object, Object& normalized_options, Optional<double> dividend, bool inclusive)
  115. {
  116. auto& vm = global_object.vm();
  117. double maximum;
  118. // 1. If dividend is undefined, then
  119. if (!dividend.has_value()) {
  120. // a. Let maximum be +∞.
  121. maximum = INFINITY;
  122. }
  123. // 2. Else if inclusive is true, then
  124. else if (inclusive) {
  125. // a. Let maximum be dividend.
  126. maximum = *dividend;
  127. }
  128. // 3. Else if dividend is more than 1, then
  129. else if (*dividend > 1) {
  130. // a. Let maximum be dividend − 1.
  131. maximum = *dividend - 1;
  132. }
  133. // 4. Else,
  134. else {
  135. // a. Let maximum be 1.
  136. maximum = 1;
  137. }
  138. // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", « Number », empty, 1).
  139. auto increment_value = get_option(global_object, normalized_options, "roundingIncrement", { OptionType::Number }, {}, Value(1));
  140. if (vm.exception())
  141. return {};
  142. VERIFY(increment_value.is_number());
  143. auto increment = increment_value.as_double();
  144. // 6. If increment < 1 or increment > maximum, throw a RangeError exception.
  145. if (increment < 1 || increment > maximum) {
  146. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  147. return {};
  148. }
  149. // 7. Set increment to floor(ℝ(increment)).
  150. auto floored_increment = static_cast<u64>(increment);
  151. // 8. If dividend is not undefined and dividend modulo increment is not zero, then
  152. if (dividend.has_value() && static_cast<u64>(*dividend) % floored_increment != 0) {
  153. // a. Throw a RangeError exception.
  154. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
  155. return {};
  156. }
  157. // 9. Return increment.
  158. return floored_increment;
  159. }
  160. // https://tc39.es/proposal-temporal/#table-temporal-singular-and-plural-units
  161. static HashMap<StringView, StringView> plural_to_singular_units = {
  162. { "years"sv, "year"sv },
  163. { "months"sv, "month"sv },
  164. { "weeks"sv, "week"sv },
  165. { "days"sv, "day"sv },
  166. { "hours"sv, "hour"sv },
  167. { "minutes"sv, "minute"sv },
  168. { "seconds"sv, "second"sv },
  169. { "milliseconds"sv, "millisecond"sv },
  170. { "microseconds"sv, "microsecond"sv },
  171. { "nanoseconds"sv, "nanosecond"sv }
  172. };
  173. // 13.18 ToSmallestTemporalUnit ( normalizedOptions, disallowedUnits, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-tosmallesttemporalunit
  174. Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback)
  175. {
  176. auto& vm = global_object.vm();
  177. // 1. Assert: disallowedUnits does not contain fallback.
  178. // 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).
  179. auto smallest_unit_value = get_option(global_object, normalized_options, "smallestUnit"sv, { 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());
  180. if (vm.exception())
  181. return {};
  182. // OPTIMIZATION: We skip the following string-only checks for the fallback to tidy up the code a bit
  183. if (smallest_unit_value.is_undefined())
  184. return {};
  185. VERIFY(smallest_unit_value.is_string());
  186. auto smallest_unit = smallest_unit_value.as_string().string();
  187. // 3. If smallestUnit is in the Plural column of Table 12, then
  188. if (auto singular_unit = plural_to_singular_units.get(smallest_unit); singular_unit.has_value()) {
  189. // a. Set smallestUnit to the corresponding Singular value of the same row.
  190. smallest_unit = singular_unit.value();
  191. }
  192. // 4. If disallowedUnits contains smallestUnit, then
  193. if (disallowed_units.contains_slow(smallest_unit)) {
  194. // a. Throw a RangeError exception.
  195. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, smallest_unit, "smallestUnit");
  196. return {};
  197. }
  198. // 5. Return smallestUnit.
  199. return smallest_unit;
  200. }
  201. // 13.32 RoundNumberToIncrement ( x, increment, roundingMode )
  202. BigInt* round_number_to_increment(GlobalObject& global_object, BigInt const& x, u64 increment, String const& rounding_mode)
  203. {
  204. auto& heap = global_object.heap();
  205. // 1. Assert: x and increment are mathematical values.
  206. // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand".
  207. VERIFY(rounding_mode == "ceil" || rounding_mode == "floor" || rounding_mode == "trunc" || rounding_mode == "halfExpand");
  208. // OPTIMIZATION: If the increment is 1 the number is always rounded
  209. if (increment == 1)
  210. return js_bigint(heap, x.big_integer());
  211. auto increment_big_int = Crypto::UnsignedBigInteger::create_from(increment);
  212. // 3. Let quotient be x / increment.
  213. auto division_result = x.big_integer().divided_by(increment_big_int);
  214. // OPTIMIZATION: If theres no remainder there number is already rounded
  215. if (division_result.remainder == Crypto::UnsignedBigInteger { 0 })
  216. return js_bigint(heap, x.big_integer());
  217. Crypto::SignedBigInteger rounded = move(division_result.quotient);
  218. // 4. If roundingMode is "ceil", then
  219. if (rounding_mode == "ceil") {
  220. // a. Let rounded be −floor(−quotient).
  221. if (!division_result.remainder.is_negative())
  222. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  223. }
  224. // 5. Else if roundingMode is "floor", then
  225. else if (rounding_mode == "floor") {
  226. // a. Let rounded be floor(quotient).
  227. if (division_result.remainder.is_negative())
  228. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  229. }
  230. // 6. Else if roundingMode is "trunc", then
  231. else if (rounding_mode == "trunc") {
  232. // a. Let rounded be the integral part of quotient, removing any fractional digits.
  233. // NOTE: This is a no-op
  234. }
  235. // 7. Else,
  236. else {
  237. // a. Let rounded be ! RoundHalfAwayFromZero(quotient).
  238. if (division_result.remainder.multiplied_by(Crypto::UnsignedBigInteger { 2 }).unsigned_value() >= increment_big_int) {
  239. if (division_result.remainder.is_negative())
  240. rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 });
  241. else
  242. rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 });
  243. }
  244. }
  245. // 8. Return rounded × increment.
  246. return js_bigint(heap, rounded.multiplied_by(increment_big_int));
  247. }
  248. // 13.34 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
  249. Optional<ISODateTime> parse_iso_date_time(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  250. {
  251. auto& vm = global_object.vm();
  252. // 1. Assert: Type(isoString) is String.
  253. // 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.
  254. Optional<StringView> year_part;
  255. Optional<StringView> month_part;
  256. Optional<StringView> day_part;
  257. Optional<StringView> hour_part;
  258. Optional<StringView> minute_part;
  259. Optional<StringView> second_part;
  260. Optional<StringView> fraction_part;
  261. Optional<StringView> calendar_part;
  262. TODO();
  263. // 3. Let year be the part of isoString produced by the DateYear production.
  264. // 4. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS).
  265. String normalized_year;
  266. if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv))
  267. normalized_year = String::formatted("-{}", year_part->substring_view(3));
  268. else
  269. normalized_year = year_part.value_or("");
  270. // 5. Set year to ! ToIntegerOrInfinity(year).
  271. i32 year = Value(js_string(vm, normalized_year)).to_integer_or_infinity(global_object);
  272. i32 month;
  273. // 6. If month is undefined, then
  274. if (!month_part.has_value()) {
  275. // a. Set month to 1.
  276. month = 1;
  277. }
  278. // 7. Else,
  279. else {
  280. // a. Set month to ! ToIntegerOrInfinity(month).
  281. month = Value(js_string(vm, *month_part)).to_integer_or_infinity(global_object);
  282. }
  283. i32 day;
  284. // 8. If day is undefined, then
  285. if (!day_part.has_value()) {
  286. // a. Set day to 1.
  287. day = 1;
  288. }
  289. // 9. Else,
  290. else {
  291. // a. Set day to ! ToIntegerOrInfinity(day).
  292. day = Value(js_string(vm, *day_part)).to_integer_or_infinity(global_object);
  293. }
  294. // 10. Set hour to ! ToIntegerOrInfinity(hour).
  295. i32 hour = Value(js_string(vm, hour_part.value_or(""sv))).to_integer_or_infinity(global_object);
  296. // 11. Set minute to ! ToIntegerOrInfinity(minute).
  297. i32 minute = Value(js_string(vm, minute_part.value_or(""sv))).to_integer_or_infinity(global_object);
  298. // 12. Set second to ! ToIntegerOrInfinity(second).
  299. i32 second = Value(js_string(vm, second_part.value_or(""sv))).to_integer_or_infinity(global_object);
  300. // 13. If second is 60, then
  301. if (second == 60) {
  302. // a. Set second to 59.
  303. second = 59;
  304. }
  305. i32 millisecond;
  306. i32 microsecond;
  307. i32 nanosecond;
  308. // 14. If fraction is not undefined, then
  309. if (fraction_part.has_value()) {
  310. // a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  311. auto fraction = String::formatted("{}000000000", *fraction_part);
  312. // b. Let millisecond be the String value equal to the substring of fraction from 0 to 3.
  313. // c. Set millisecond to ! ToIntegerOrInfinity(millisecond).
  314. millisecond = Value(js_string(vm, fraction.substring(0, 3))).to_integer_or_infinity(global_object);
  315. // d. Let microsecond be the String value equal to the substring of fraction from 3 to 6.
  316. // e. Set microsecond to ! ToIntegerOrInfinity(microsecond).
  317. microsecond = Value(js_string(vm, fraction.substring(3, 3))).to_integer_or_infinity(global_object);
  318. // f. Let nanosecond be the String value equal to the substring of fraction from 6 to 9.
  319. // g. Set nanosecond to ! ToIntegerOrInfinity(nanosecond).
  320. nanosecond = Value(js_string(vm, fraction.substring(6, 3))).to_integer_or_infinity(global_object);
  321. }
  322. // 15. Else,
  323. else {
  324. // a. Let millisecond be 0.
  325. millisecond = 0;
  326. // b. Let microsecond be 0.
  327. microsecond = 0;
  328. // c. Let nanosecond be 0.
  329. nanosecond = 0;
  330. }
  331. // 16. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception.
  332. if (!is_valid_iso_date(year, month, day)) {
  333. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidISODate);
  334. return {};
  335. }
  336. // 17. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
  337. if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) {
  338. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTime);
  339. return {};
  340. }
  341. // 18. Return the new Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }.
  342. 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>() };
  343. }
  344. // 13.35 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring
  345. Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string)
  346. {
  347. auto& vm = global_object.vm();
  348. // 1. Assert: Type(isoString) is String.
  349. // 2. If isoString does not satisfy the syntax of a TemporalInstantString (see 13.33), then
  350. // a. Throw a RangeError exception.
  351. // TODO
  352. // 3. Let result be ! ParseISODateTime(isoString).
  353. auto result = parse_iso_date_time(global_object, iso_string);
  354. // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
  355. auto time_zone_result = parse_temporal_time_zone_string(global_object, iso_string);
  356. if (vm.exception())
  357. return {};
  358. // 5. Assert: timeZoneResult.[[OffsetString]] is not undefined.
  359. VERIFY(time_zone_result->offset.has_value());
  360. // 6. Return the new Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[TimeZoneOffsetString]]: timeZoneResult.[[OffsetString]] }.
  361. return TemporalInstant { .year = result->year, .month = result->month, .day = result->day, .hour = result->hour, .minute = result->minute, .second = result->second, .millisecond = result->millisecond, .microsecond = result->microsecond, .nanosecond = result->nanosecond, .time_zone_offset = move(time_zone_result->offset) };
  362. }
  363. // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
  364. Optional<TemporalDuration> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string)
  365. {
  366. (void)global_object;
  367. (void)iso_string;
  368. TODO();
  369. }
  370. // 13.43 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring
  371. Optional<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
  372. {
  373. auto& vm = global_object.vm();
  374. // 1. Assert: Type(isoString) is String.
  375. // 2. If isoString does not satisfy the syntax of a TemporalTimeZoneString (see 13.33), then
  376. // a. Throw a RangeError exception.
  377. // 3. Let z, sign, hours, minutes, seconds, fraction and name be the parts of isoString produced respectively by the UTCDesignator, TimeZoneUTCOffsetSign, TimeZoneUTCOffsetHour, TimeZoneUTCOffsetMinute, TimeZoneUTCOffsetSecond, TimeZoneUTCOffsetFraction, and TimeZoneIANAName productions, or undefined if not present.
  378. Optional<StringView> z_part;
  379. Optional<StringView> sign_part;
  380. Optional<StringView> hours_part;
  381. Optional<StringView> minutes_part;
  382. Optional<StringView> seconds_part;
  383. Optional<StringView> fraction_part;
  384. Optional<StringView> name_part;
  385. TODO();
  386. // 4. If z is not undefined, then
  387. if (z_part.has_value()) {
  388. // a. Return the new Record: { [[Z]]: "Z", [[OffsetString]]: "+00:00", [[Name]]: undefined }.
  389. return TemporalTimeZone { .z = true, .offset = "+00:00", .name = {} };
  390. }
  391. Optional<String> offset;
  392. // 5. If hours is undefined, then
  393. if (!hours_part.has_value()) {
  394. // a. Let offsetString be undefined.
  395. // NOTE: No-op.
  396. }
  397. // 6. Else,
  398. else {
  399. // a. Assert: sign is not undefined.
  400. VERIFY(sign_part.has_value());
  401. // b. Set hours to ! ToIntegerOrInfinity(hours).
  402. i32 hours = Value(js_string(vm, *hours_part)).to_integer_or_infinity(global_object);
  403. i32 sign;
  404. // c. If sign is the code unit 0x002D (HYPHEN-MINUS) or the code unit 0x2212 (MINUS SIGN), then
  405. if (sign_part->is_one_of("-", "\u2212")) {
  406. // i. Set sign to −1.
  407. sign = -1;
  408. }
  409. // d. Else,
  410. else {
  411. // i. Set sign to 1.
  412. sign = 1;
  413. }
  414. // e. Set minutes to ! ToIntegerOrInfinity(minutes).
  415. i32 minutes = Value(js_string(vm, minutes_part.value_or(""sv))).to_integer_or_infinity(global_object);
  416. // f. Set seconds to ! ToIntegerOrInfinity(seconds).
  417. i32 seconds = Value(js_string(vm, seconds_part.value_or(""sv))).to_integer_or_infinity(global_object);
  418. i32 nanoseconds;
  419. // g. If fraction is not undefined, then
  420. if (fraction_part.has_value()) {
  421. // i. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
  422. auto fraction = String::formatted("{}000000000", *fraction_part);
  423. // ii. Let nanoseconds be the String value equal to the substring of fraction from 0 to 9.
  424. // iii. Set nanoseconds to ! ToIntegerOrInfinity(nanoseconds).
  425. nanoseconds = Value(js_string(vm, fraction.substring(0, 9))).to_integer_or_infinity(global_object);
  426. }
  427. // h. Else,
  428. else {
  429. // i. Let nanoseconds be 0.
  430. nanoseconds = 0;
  431. }
  432. // i. Let offsetNanoseconds be sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds).
  433. auto offset_nanoseconds = sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000 + nanoseconds);
  434. // j. Let offsetString be ! FormatTimeZoneOffsetString(offsetNanoseconds).
  435. offset = format_time_zone_offset_string(offset_nanoseconds);
  436. }
  437. Optional<String> name;
  438. // 7. If name is not undefined, then
  439. if (name_part.has_value()) {
  440. // a. If ! IsValidTimeZoneName(name) is false, throw a RangeError exception.
  441. if (!is_valid_time_zone_name(*name_part)) {
  442. vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName);
  443. return {};
  444. }
  445. // b. Set name to ! CanonicalizeTimeZoneName(name).
  446. name = canonicalize_time_zone_name(*name_part);
  447. }
  448. // 8. Return the new Record: { [[Z]]: undefined, [[OffsetString]]: offsetString, [[Name]]: name }.
  449. return TemporalTimeZone { .z = false, .offset = offset, .name = name };
  450. }
  451. }