Calendar.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
  5. * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include <AK/NonnullRawPtr.h>
  10. #include <AK/QuickSort.h>
  11. #include <LibJS/Runtime/Temporal/Calendar.h>
  12. #include <LibJS/Runtime/Temporal/DateEquations.h>
  13. #include <LibJS/Runtime/Temporal/Duration.h>
  14. #include <LibJS/Runtime/Temporal/ISO8601.h>
  15. #include <LibJS/Runtime/Temporal/PlainDate.h>
  16. #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
  17. #include <LibJS/Runtime/Temporal/PlainYearMonth.h>
  18. #include <LibJS/Runtime/Temporal/TimeZone.h>
  19. #include <LibJS/Runtime/VM.h>
  20. #include <LibUnicode/Locale.h>
  21. #include <LibUnicode/UnicodeKeywords.h>
  22. namespace JS::Temporal {
  23. enum class CalendarFieldConversion {
  24. ToIntegerWithTruncation,
  25. ToMonthCode,
  26. ToOffsetString,
  27. ToPositiveIntegerWithTruncation,
  28. ToString,
  29. ToTemporalTimeZoneIdentifier,
  30. };
  31. // https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields
  32. #define JS_ENUMERATE_CALENDAR_FIELDS \
  33. __JS_ENUMERATE(CalendarField::Era, era, vm.names.era, CalendarFieldConversion::ToString) \
  34. __JS_ENUMERATE(CalendarField::EraYear, era_year, vm.names.eraYear, CalendarFieldConversion::ToIntegerWithTruncation) \
  35. __JS_ENUMERATE(CalendarField::Year, year, vm.names.year, CalendarFieldConversion::ToIntegerWithTruncation) \
  36. __JS_ENUMERATE(CalendarField::Month, month, vm.names.month, CalendarFieldConversion::ToPositiveIntegerWithTruncation) \
  37. __JS_ENUMERATE(CalendarField::MonthCode, month_code, vm.names.monthCode, CalendarFieldConversion::ToMonthCode) \
  38. __JS_ENUMERATE(CalendarField::Day, day, vm.names.day, CalendarFieldConversion::ToPositiveIntegerWithTruncation) \
  39. __JS_ENUMERATE(CalendarField::Hour, hour, vm.names.hour, CalendarFieldConversion::ToIntegerWithTruncation) \
  40. __JS_ENUMERATE(CalendarField::Minute, minute, vm.names.minute, CalendarFieldConversion::ToIntegerWithTruncation) \
  41. __JS_ENUMERATE(CalendarField::Second, second, vm.names.second, CalendarFieldConversion::ToIntegerWithTruncation) \
  42. __JS_ENUMERATE(CalendarField::Millisecond, millisecond, vm.names.millisecond, CalendarFieldConversion::ToIntegerWithTruncation) \
  43. __JS_ENUMERATE(CalendarField::Microsecond, microsecond, vm.names.microsecond, CalendarFieldConversion::ToIntegerWithTruncation) \
  44. __JS_ENUMERATE(CalendarField::Nanosecond, nanosecond, vm.names.nanosecond, CalendarFieldConversion::ToIntegerWithTruncation) \
  45. __JS_ENUMERATE(CalendarField::Offset, offset, vm.names.offset, CalendarFieldConversion::ToOffsetString) \
  46. __JS_ENUMERATE(CalendarField::TimeZone, time_zone, vm.names.timeZone, CalendarFieldConversion::ToTemporalTimeZoneIdentifier)
  47. struct CalendarFieldData {
  48. CalendarField key;
  49. NonnullRawPtr<PropertyKey> property;
  50. CalendarFieldConversion conversion;
  51. };
  52. static Vector<CalendarFieldData> sorted_calendar_fields(VM& vm, CalendarFieldList fields)
  53. {
  54. auto data_for_field = [&](auto field) -> CalendarFieldData {
  55. switch (field) {
  56. #define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
  57. case enumeration: \
  58. return { enumeration, property_key, conversion };
  59. JS_ENUMERATE_CALENDAR_FIELDS
  60. #undef __JS_ENUMERATE
  61. }
  62. VERIFY_NOT_REACHED();
  63. };
  64. Vector<CalendarFieldData> result;
  65. result.ensure_capacity(fields.size());
  66. for (auto field : fields)
  67. result.unchecked_append(data_for_field(field));
  68. quick_sort(result, [](auto const& lhs, auto const& rhs) {
  69. return StringView { lhs.property->as_string() } < StringView { rhs.property->as_string() };
  70. });
  71. return result;
  72. }
  73. template<typename T>
  74. static void set_field_value(CalendarField field, CalendarFields& fields, T&& value)
  75. {
  76. switch (field) {
  77. #define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
  78. case enumeration: \
  79. if constexpr (IsAssignable<decltype(fields.field_name), RemoveCVReference<T>>) \
  80. fields.field_name = value; \
  81. return;
  82. JS_ENUMERATE_CALENDAR_FIELDS
  83. #undef __JS_ENUMERATE
  84. }
  85. VERIFY_NOT_REACHED();
  86. }
  87. static void set_default_field_value(CalendarField field, CalendarFields& fields)
  88. {
  89. CalendarFields default_ {};
  90. switch (field) {
  91. #define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
  92. case enumeration: \
  93. fields.field_name = default_.field_name; \
  94. return;
  95. JS_ENUMERATE_CALENDAR_FIELDS
  96. #undef __JS_ENUMERATE
  97. }
  98. VERIFY_NOT_REACHED();
  99. }
  100. // 12.1.1 CanonicalizeCalendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar
  101. ThrowCompletionOr<String> canonicalize_calendar(VM& vm, StringView id)
  102. {
  103. // 1. Let calendars be AvailableCalendars().
  104. auto const& calendars = available_calendars();
  105. // 2. If calendars does not contain the ASCII-lowercase of id, throw a RangeError exception.
  106. for (auto const& calendar : calendars) {
  107. if (calendar.equals_ignoring_ascii_case(id)) {
  108. // 3. Return CanonicalizeUValue("ca", id).
  109. return Unicode::canonicalize_unicode_extension_values("ca"sv, id);
  110. }
  111. }
  112. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, id);
  113. }
  114. // 12.1.2 AvailableCalendars ( ), https://tc39.es/proposal-temporal/#sec-availablecalendars
  115. Vector<String> const& available_calendars()
  116. {
  117. // The implementation-defined abstract operation AvailableCalendars takes no arguments and returns a List of calendar
  118. // types. The returned List is sorted according to lexicographic code unit order, and contains unique calendar types
  119. // in canonical form (12.1) identifying the calendars for which the implementation provides the functionality of
  120. // Intl.DateTimeFormat objects, including their aliases (e.g., either both or neither of "islamicc" and
  121. // "islamic-civil"). The List must include "iso8601".
  122. return Unicode::available_calendars();
  123. }
  124. // 12.2.3 PrepareCalendarFields ( calendar, fields, calendarFieldNames, nonCalendarFieldNames, requiredFieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields
  125. ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM& vm, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names)
  126. {
  127. // 1. Assert: If requiredFieldNames is a List, requiredFieldNames contains zero or one of each of the elements of
  128. // calendarFieldNames and nonCalendarFieldNames.
  129. // 2. Let fieldNames be the list-concatenation of calendarFieldNames and nonCalendarFieldNames.
  130. Vector<CalendarField> field_names;
  131. field_names.append(calendar_field_names.data(), calendar_field_names.size());
  132. field_names.append(non_calendar_field_names.data(), non_calendar_field_names.size());
  133. // 3. Let extraFieldNames be CalendarExtraFields(calendar, calendarFieldNames).
  134. auto extra_field_names = calendar_extra_fields(calendar, calendar_field_names);
  135. // 4. Set fieldNames to the list-concatenation of fieldNames and extraFieldNames.
  136. field_names.extend(move(extra_field_names));
  137. // 5. Assert: fieldNames contains no duplicate elements.
  138. // 6. Let result be a Calendar Fields Record with all fields equal to UNSET.
  139. auto result = CalendarFields::unset();
  140. // 7. Let any be false.
  141. auto any = false;
  142. // 8. Let sortedPropertyNames be a List whose elements are the values in the Property Key column of Table 19
  143. // corresponding to the elements of fieldNames, sorted according to lexicographic code unit order.
  144. auto sorted_property_names = sorted_calendar_fields(vm, field_names);
  145. // 9. For each property name property of sortedPropertyNames, do
  146. for (auto const& [key, property, conversion] : sorted_property_names) {
  147. // a. Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property.
  148. // b. Let value be ? Get(fields, property).
  149. auto value = TRY(fields.get(property));
  150. // c. If value is not undefined, then
  151. if (!value.is_undefined()) {
  152. // i. Set any to true.
  153. any = true;
  154. // ii. Let Conversion be the Conversion value of the same row.
  155. switch (conversion) {
  156. // iii. If Conversion is TO-INTEGER-WITH-TRUNCATION, then
  157. case CalendarFieldConversion::ToIntegerWithTruncation:
  158. // 1. Set value to ? ToIntegerWithTruncation(value).
  159. // 2. Set value to 𝔽(value).
  160. set_field_value(key, result, TRY(to_integer_with_truncation(vm, value, ErrorType::TemporalInvalidCalendarFieldName, *property)));
  161. break;
  162. // iv. Else if Conversion is TO-POSITIVE-INTEGER-WITH-TRUNCATION, then
  163. case CalendarFieldConversion::ToPositiveIntegerWithTruncation:
  164. // 1. Set value to ? ToPositiveIntegerWithTruncation(value).
  165. // 2. Set value to 𝔽(value).
  166. set_field_value(key, result, TRY(to_positive_integer_with_truncation(vm, value, ErrorType::TemporalInvalidCalendarFieldName, *property)));
  167. break;
  168. // v. Else if Conversion is TO-STRING, then
  169. case CalendarFieldConversion::ToString:
  170. // 1. Set value to ? ToString(value).
  171. set_field_value(key, result, TRY(value.to_string(vm)));
  172. break;
  173. // vi. Else if Conversion is TO-TEMPORAL-TIME-ZONE-IDENTIFIER, then
  174. case CalendarFieldConversion::ToTemporalTimeZoneIdentifier:
  175. // 1. Set value to ? ToTemporalTimeZoneIdentifier(value).
  176. set_field_value(key, result, TRY(to_temporal_time_zone_identifier(vm, value)));
  177. break;
  178. // vii. Else if Conversion is TO-MONTH-CODE, then
  179. case CalendarFieldConversion::ToMonthCode:
  180. // 1. Set value to ? ToMonthCode(value).
  181. set_field_value(key, result, TRY(to_month_code(vm, value)));
  182. break;
  183. // viii. Else,
  184. case CalendarFieldConversion::ToOffsetString:
  185. // 1. Assert: Conversion is TO-OFFSET-STRING.
  186. // 2. Set value to ? ToOffsetString(value).
  187. set_field_value(key, result, TRY(to_offset_string(vm, value)));
  188. break;
  189. }
  190. // ix. Set result's field whose name is given in the Field Name column of the same row to value.
  191. }
  192. // d. Else if requiredFieldNames is a List, then
  193. else if (auto const* required = required_field_names.get_pointer<CalendarFieldList>()) {
  194. // i. If requiredFieldNames contains key, then
  195. if (required->contains_slow(key)) {
  196. // 1. Throw a TypeError exception.
  197. return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, *property);
  198. }
  199. // ii. Set result's field whose name is given in the Field Name column of the same row to the corresponding
  200. // Default value of the same row.
  201. set_default_field_value(key, result);
  202. }
  203. }
  204. // 10. If requiredFieldNames is PARTIAL and any is false, then
  205. if (required_field_names.has<Partial>() && !any) {
  206. // a. Throw a TypeError exception.
  207. return vm.throw_completion<TypeError>(ErrorType::TemporalObjectMustBePartialTemporalObject);
  208. }
  209. // 11. Return result.
  210. return result;
  211. }
  212. // 12.2.4 CalendarFieldKeysPresent ( fields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent
  213. Vector<CalendarField> calendar_field_keys_present(CalendarFields const& fields)
  214. {
  215. // 1. Let list be « ».
  216. Vector<CalendarField> list;
  217. auto handle_field = [&](auto enumeration_key, auto const& value) {
  218. // a. Let value be fields' field whose name is given in the Field Name column of the row.
  219. // b. Let enumerationKey be the value in the Enumeration Key column of the row.
  220. // c. If value is not unset, append enumerationKey to list.
  221. if (value.has_value())
  222. list.append(enumeration_key);
  223. };
  224. // 2. For each row of Table 19, except the header row, do
  225. #define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
  226. handle_field(enumeration, fields.field_name);
  227. JS_ENUMERATE_CALENDAR_FIELDS
  228. #undef __JS_ENUMERATE
  229. // 3. Return list.
  230. return list;
  231. }
  232. // 12.2.5 CalendarMergeFields ( calendar, fields, additionalFields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields
  233. CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields)
  234. {
  235. // 1. Let additionalKeys be CalendarFieldKeysPresent(additionalFields).
  236. auto additional_keys = calendar_field_keys_present(additional_fields);
  237. // 2. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys).
  238. auto overridden_keys = calendar_field_keys_to_ignore(calendar, additional_keys);
  239. // 3. Let merged be a Calendar Fields Record with all fields set to unset.
  240. auto merged = CalendarFields::unset();
  241. // 4. Let fieldsKeys be CalendarFieldKeysPresent(fields).
  242. auto fields_keys = calendar_field_keys_present(fields);
  243. auto merge_field = [&](auto key, auto& merged_field, auto const& fields_field, auto const& additional_fields_field) {
  244. // a. Let key be the value in the Enumeration Key column of the row.
  245. // b. If fieldsKeys contains key and overriddenKeys does not contain key, then
  246. if (fields_keys.contains_slow(key) && !overridden_keys.contains_slow(key)) {
  247. // i. Let propValue be fields' field whose name is given in the Field Name column of the row.
  248. // ii. Set merged's field whose name is given in the Field Name column of the row to propValue.
  249. merged_field = fields_field;
  250. }
  251. // c. If additionalKeys contains key, then
  252. if (additional_keys.contains_slow(key)) {
  253. // i. Let propValue be additionalFields' field whose name is given in the Field Name column of the row.
  254. // ii. Set merged's field whose name is given in the Field Name column of the row to propValue.
  255. merged_field = additional_fields_field;
  256. }
  257. };
  258. // 5. For each row of Table 19, except the header row, do
  259. #define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
  260. merge_field(enumeration, merged.field_name, fields.field_name, additional_fields.field_name);
  261. JS_ENUMERATE_CALENDAR_FIELDS
  262. #undef __JS_ENUMERATE
  263. // 6. Return merged.
  264. return merged;
  265. }
  266. // 12.2.6 CalendarDateAdd ( calendar, isoDate, duration, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd
  267. ThrowCompletionOr<ISODate> calendar_date_add(VM& vm, StringView calendar, ISODate const& iso_date, DateDuration const& duration, Overflow overflow)
  268. {
  269. ISODate result;
  270. // 1. If calendar is "iso8601", then
  271. // FIXME: Return an ISODate for an ISO8601 calendar for now.
  272. if (true || calendar == "iso8601"sv) {
  273. // a. Let intermediate be BalanceISOYearMonth(isoDate.[[Year]] + duration.[[Years]], isoDate.[[Month]] + duration.[[Months]]).
  274. auto intermediate = balance_iso_year_month(static_cast<double>(iso_date.year) + duration.years, static_cast<double>(iso_date.month) + duration.months);
  275. // b. Set intermediate to ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], isoDate.[[Day]], overflow).
  276. auto intermediate_date = TRY(regulate_iso_date(vm, intermediate.year, intermediate.month, iso_date.day, overflow));
  277. // c. Let d be intermediate.[[Day]] + duration.[[Days]] + 7 × duration.[[Weeks]].
  278. auto day = intermediate_date.day + duration.days + (7 * duration.weeks);
  279. // d. Let result be BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d).
  280. result = balance_iso_date(intermediate_date.year, intermediate_date.month, day);
  281. }
  282. // 2. Else,
  283. else {
  284. // a. Let result be an implementation-defined ISO Date Record, or throw a RangeError exception, as described below.
  285. }
  286. // 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
  287. if (!iso_date_within_limits(result))
  288. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
  289. // 4. Return result.
  290. return result;
  291. }
  292. // 12.2.7 CalendarDateUntil ( calendar, one, two, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil
  293. DateDuration calendar_date_until(VM& vm, StringView calendar, ISODate const& one, ISODate const& two, Unit largest_unit)
  294. {
  295. // 1. If calendar is "iso8601", then
  296. if (calendar == "iso8601"sv) {
  297. // a. Let sign be -CompareISODate(one, two).
  298. auto sign = compare_iso_date(one, two);
  299. sign *= -1;
  300. // b. If sign = 0, return ZeroDateDuration().
  301. if (sign == 0)
  302. return zero_date_duration(vm);
  303. // c. Let years be 0.
  304. double years = 0;
  305. // d. If largestUnit is YEAR, then
  306. if (largest_unit == Unit::Year) {
  307. // i. Let candidateYears be sign.
  308. double candidate_years = sign;
  309. // ii. Repeat, while ISODateSurpasses(sign, one.[[Year]] + candidateYears, one.[[Month]], one.[[Day]], two) is false,
  310. while (!iso_date_surpasses(sign, static_cast<double>(one.year) + candidate_years, one.month, one.day, two)) {
  311. // 1. Set years to candidateYears.
  312. years = candidate_years;
  313. // 2. Set candidateYears to candidateYears + sign.
  314. candidate_years += sign;
  315. }
  316. }
  317. // e. Let months be 0.
  318. double months = 0;
  319. // f. If largestUnit is YEAR or largestUnit is MONTH, then
  320. if (largest_unit == Unit::Year || largest_unit == Unit::Month) {
  321. // i. Let candidateMonths be sign.
  322. double candidate_months = sign;
  323. // ii. Let intermediate be BalanceISOYearMonth(one.[[Year]] + years, one.[[Month]] + candidateMonths).
  324. auto intermediate = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + candidate_months);
  325. // iii. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], one.[[Day]], two) is false,
  326. while (!iso_date_surpasses(sign, intermediate.year, intermediate.month, one.day, two)) {
  327. // 1. Set months to candidateMonths.
  328. months = candidate_months;
  329. // 2. Set candidateMonths to candidateMonths + sign.
  330. candidate_months += sign;
  331. // 3. Set intermediate to BalanceISOYearMonth(intermediate.[[Year]], intermediate.[[Month]] + sign).
  332. intermediate = balance_iso_year_month(intermediate.year, static_cast<double>(intermediate.month) + sign);
  333. }
  334. }
  335. // g. Set intermediate to BalanceISOYearMonth(one.[[Year]] + years, one.[[Month]] + months).
  336. auto intermediate = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + months);
  337. // h. Let constrained be ! RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], one.[[Day]], CONSTRAIN).
  338. auto constrained = MUST(regulate_iso_date(vm, intermediate.year, intermediate.month, one.day, Overflow::Constrain));
  339. // i. Let weeks be 0.
  340. double weeks = 0;
  341. // j. If largestUnit is WEEK, then
  342. if (largest_unit == Unit::Week) {
  343. // i. Let candidateWeeks be sign.
  344. double candidate_weeks = sign;
  345. // ii. Set intermediate to BalanceISODate(constrained.[[Year]], constrained.[[Month]], constrained.[[Day]] + 7 × candidateWeeks).
  346. auto intermediate = balance_iso_date(constrained.year, constrained.month, static_cast<double>(constrained.day) + (7.0 * candidate_weeks));
  347. // iii. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]], two) is false,
  348. while (!iso_date_surpasses(sign, intermediate.year, intermediate.month, intermediate.day, two)) {
  349. // 1. Set weeks to candidateWeeks.
  350. weeks = candidate_weeks;
  351. // 2. Set candidateWeeks to candidateWeeks + sign.
  352. candidate_weeks += sign;
  353. // 3. Set intermediate to BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]] + 7 × sign).
  354. intermediate = balance_iso_date(intermediate.year, intermediate.month, static_cast<double>(intermediate.day) + (7.0 * sign));
  355. }
  356. }
  357. // k. Let days be 0.
  358. double days = 0;
  359. // l. Let candidateDays be sign.
  360. double candidate_days = sign;
  361. // m. Set intermediate to BalanceISODate(constrained.[[Year]], constrained.[[Month]], constrained.[[Day]] + 7 × weeks + candidateDays).
  362. auto intermediate_date = balance_iso_date(constrained.year, constrained.month, static_cast<double>(constrained.day) + (7.0 * weeks) + candidate_days);
  363. // n. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]], two) is false,
  364. while (!iso_date_surpasses(sign, intermediate_date.year, intermediate_date.month, intermediate_date.day, two)) {
  365. // i. Set days to candidateDays.
  366. days = candidate_days;
  367. // ii. Set candidateDays to candidateDays + sign.
  368. candidate_days += sign;
  369. // iii. Set intermediate to BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]] + sign).
  370. intermediate_date = balance_iso_date(intermediate_date.year, intermediate_date.month, static_cast<double>(intermediate_date.day) + sign);
  371. }
  372. // o. Return ! CreateDateDurationRecord(years, months, weeks, days).
  373. return MUST(create_date_duration_record(vm, years, months, weeks, days));
  374. }
  375. // 2. Return an implementation-defined Date Duration Record as described above.
  376. // FIXME: Return a DateDuration for an ISO8601 calendar for now.
  377. return calendar_date_until(vm, "iso8601"sv, one, two, largest_unit);
  378. }
  379. // 12.2.8 ToTemporalCalendarIdentifier ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier
  380. ThrowCompletionOr<String> to_temporal_calendar_identifier(VM& vm, Value temporal_calendar_like)
  381. {
  382. // 1. If temporalCalendarLike is an Object, then
  383. if (temporal_calendar_like.is_object()) {
  384. auto const& temporal_calendar_object = temporal_calendar_like.as_object();
  385. // a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]],
  386. // [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
  387. // internal slot, then
  388. // i. Return temporalCalendarLike.[[Calendar]].
  389. // FIXME: Add the other calendar-holding types as we define them.
  390. if (is<PlainMonthDay>(temporal_calendar_object))
  391. return static_cast<PlainMonthDay const&>(temporal_calendar_object).calendar();
  392. if (is<PlainYearMonth>(temporal_calendar_object))
  393. return static_cast<PlainYearMonth const&>(temporal_calendar_object).calendar();
  394. }
  395. // 2. If temporalCalendarLike is not a String, throw a TypeError exception.
  396. if (!temporal_calendar_like.is_string())
  397. return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidCalendar);
  398. // 3. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike).
  399. auto identifier = TRY(parse_temporal_calendar_string(vm, temporal_calendar_like.as_string().utf8_string()));
  400. // 4. Return ? CanonicalizeCalendar(identifier).
  401. return TRY(canonicalize_calendar(vm, identifier));
  402. }
  403. // 12.2.9 GetTemporalCalendarIdentifierWithISODefault ( item ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendarslotvaluewithisodefault
  404. ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM& vm, Object const& item)
  405. {
  406. // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
  407. // [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
  408. // a. Return item.[[Calendar]].
  409. // FIXME: Add the other calendar-holding types as we define them.
  410. if (is<PlainMonthDay>(item))
  411. return static_cast<PlainMonthDay const&>(item).calendar();
  412. if (is<PlainYearMonth>(item))
  413. return static_cast<PlainYearMonth const&>(item).calendar();
  414. // 2. Let calendarLike be ? Get(item, "calendar").
  415. auto calendar_like = TRY(item.get(vm.names.calendar));
  416. // 3. If calendarLike is undefined, then
  417. if (calendar_like.is_undefined()) {
  418. // a. Return "iso8601".
  419. return "iso8601"_string;
  420. }
  421. // 4. Return ? ToTemporalCalendarIdentifier(calendarLike).
  422. return TRY(to_temporal_calendar_identifier(vm, calendar_like));
  423. }
  424. // 12.2.10 CalendarDateFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatefromfields
  425. ThrowCompletionOr<ISODate> calendar_date_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
  426. {
  427. // 1. Perform ? CalendarResolveFields(calendar, fields, DATE).
  428. TRY(calendar_resolve_fields(vm, calendar, fields, DateType::Date));
  429. // 2. Let result be ? CalendarDateToISO(calendar, fields, overflow).
  430. auto result = TRY(calendar_date_to_iso(vm, calendar, fields, overflow));
  431. // 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
  432. if (!iso_date_within_limits(result))
  433. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
  434. // 4. Return result.
  435. return result;
  436. }
  437. // 12.2.11 CalendarYearMonthFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields
  438. ThrowCompletionOr<ISODate> calendar_year_month_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
  439. {
  440. // 1. Perform ? CalendarResolveFields(calendar, fields, YEAR-MONTH).
  441. TRY(calendar_resolve_fields(vm, calendar, fields, DateType::YearMonth));
  442. // FIXME: 2. Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the
  443. // month's first day is skipped by this calendar.)
  444. static auto constexpr first_day_index = 1;
  445. // 3. Set fields.[[Day]] to firstDayIndex.
  446. fields.day = first_day_index;
  447. // 4. Let result be ? CalendarDateToISO(calendar, fields, overflow).
  448. auto result = TRY(calendar_date_to_iso(vm, calendar, fields, overflow));
  449. // 5. If ISOYearMonthWithinLimits(result) is false, throw a RangeError exception.
  450. if (!iso_year_month_within_limits(result))
  451. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
  452. // 6. Return result.
  453. return result;
  454. }
  455. // 12.2.12 CalendarMonthDayFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields
  456. ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
  457. {
  458. // 1. Perform ? CalendarResolveFields(calendar, fields, MONTH-DAY).
  459. TRY(calendar_resolve_fields(vm, calendar, fields, DateType::MonthDay));
  460. // 2. Let result be ? CalendarMonthDayToISOReferenceDate(calendar, fields, overflow).
  461. auto result = TRY(calendar_month_day_to_iso_reference_date(vm, calendar, fields, overflow));
  462. // 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
  463. if (!iso_date_within_limits(result))
  464. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
  465. // 4. Return result.
  466. return result;
  467. }
  468. // 12.2.13 FormatCalendarAnnotation ( id, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation
  469. String format_calendar_annotation(StringView id, ShowCalendar show_calendar)
  470. {
  471. // 1. If showCalendar is NEVER, return the empty String.
  472. if (show_calendar == ShowCalendar::Never)
  473. return String {};
  474. // 2. If showCalendar is AUTO and id is "iso8601", return the empty String.
  475. if (show_calendar == ShowCalendar::Auto && id == "iso8601"sv)
  476. return String {};
  477. // 3. If showCalendar is CRITICAL, let flag be "!"; else, let flag be the empty String.
  478. auto flag = show_calendar == ShowCalendar::Critical ? "!"sv : ""sv;
  479. // 4. Return the string-concatenation of "[", flag, "u-ca=", id, and "]".
  480. return MUST(String::formatted("[{}u-ca={}]", flag, id));
  481. }
  482. // 12.2.14 CalendarEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-calendarequals
  483. bool calendar_equals(StringView one, StringView two)
  484. {
  485. // 1. If CanonicalizeUValue("ca", one) is CanonicalizeUValue("ca", two), return true.
  486. // 2. Return false.
  487. return Unicode::canonicalize_unicode_extension_values("ca"sv, one)
  488. == Unicode::canonicalize_unicode_extension_values("ca"sv, two);
  489. }
  490. // 12.2.15 ISODaysInMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
  491. u8 iso_days_in_month(double year, double month)
  492. {
  493. // 1. If month is 1, 3, 5, 7, 8, 10, or 12, return 31.
  494. if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)
  495. return 31;
  496. // 2. If month is 4, 6, 9, or 11, return 30.
  497. if (month == 4 || month == 6 || month == 9 || month == 11)
  498. return 30;
  499. // 3. Assert: month is 2.
  500. VERIFY(month == 2);
  501. // 4. Return 28 + MathematicalInLeapYear(EpochTimeForYear(year)).
  502. return 28 + mathematical_in_leap_year(epoch_time_for_year(year));
  503. }
  504. // 12.2.16 ISOWeekOfYear ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoweekofyear
  505. YearWeek iso_week_of_year(ISODate const& iso_date)
  506. {
  507. // 1. Let year be isoDate.[[Year]].
  508. auto year = iso_date.year;
  509. // 2. Let wednesday be 3.
  510. static constexpr auto wednesday = 3;
  511. // 3. Let thursday be 4.
  512. static constexpr auto thursday = 4;
  513. // 4. Let friday be 5.
  514. static constexpr auto friday = 5;
  515. // 5. Let saturday be 6.
  516. static constexpr auto saturday = 6;
  517. // 6. Let daysInWeek be 7.
  518. static constexpr auto days_in_week = 7;
  519. // 7. Let maxWeekNumber be 53.
  520. static constexpr auto max_week_number = 53;
  521. // 8. Let dayOfYear be ISODayOfYear(isoDate).
  522. auto day_of_year = iso_day_of_year(iso_date);
  523. // 9. Let dayOfWeek be ISODayOfWeek(isoDate).
  524. auto day_of_week = iso_day_of_week(iso_date);
  525. // 10. Let week be floor((dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek).
  526. auto week = floor(static_cast<double>(day_of_year + days_in_week - day_of_week + wednesday) / static_cast<double>(days_in_week));
  527. // 11. If week < 1, then
  528. if (week < 1) {
  529. // a. NOTE: This is the last week of the previous year.
  530. // b. Let jan1st be CreateISODateRecord(year, 1, 1).
  531. auto jan1st = create_iso_date_record(year, 1, 1);
  532. // c. Let dayOfJan1st be ISODayOfWeek(jan1st).
  533. auto day_of_jan1st = iso_day_of_week(jan1st);
  534. // d. If dayOfJan1st = friday, then
  535. if (day_of_jan1st == friday) {
  536. // i. Return Year-Week Record { [[Week]]: maxWeekNumber, [[Year]]: year - 1 }.
  537. return { .week = max_week_number, .year = year - 1 };
  538. }
  539. // e. If dayOfJan1st = saturday, and MathematicalInLeapYear(EpochTimeForYear(year - 1)) = 1, then
  540. if (day_of_jan1st == saturday && mathematical_in_leap_year(epoch_time_for_year(year - 1)) == 1) {
  541. // i. Return Year-Week Record { [[Week]]: maxWeekNumber. [[Year]]: year - 1 }.
  542. return { .week = max_week_number, .year = year - 1 };
  543. }
  544. // f. Return Year-Week Record { [[Week]]: maxWeekNumber - 1, [[Year]]: year - 1 }.
  545. return { .week = max_week_number - 1, .year = year - 1 };
  546. }
  547. // 12. If week = maxWeekNumber, then
  548. if (week == max_week_number) {
  549. // a. Let daysInYear be MathematicalDaysInYear(year).
  550. auto days_in_year = mathematical_days_in_year(year);
  551. // b. Let daysLaterInYear be daysInYear - dayOfYear.
  552. auto days_later_in_year = days_in_year - day_of_year;
  553. // c. Let daysAfterThursday be thursday - dayOfWeek.
  554. auto days_after_thursday = thursday - day_of_week;
  555. // d. If daysLaterInYear < daysAfterThursday, then
  556. if (days_later_in_year < days_after_thursday) {
  557. // i. Return Year-Week Record { [[Week]]: 1, [[Year]]: year + 1 }.
  558. return { .week = 1, .year = year + 1 };
  559. }
  560. }
  561. // 13. Return Year-Week Record { [[Week]]: week, [[Year]]: year }.
  562. return { .week = week, .year = year };
  563. }
  564. // 12.2.17 ISODayOfYear ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodayofyear
  565. u16 iso_day_of_year(ISODate const& iso_date)
  566. {
  567. // 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
  568. auto epoch_days = iso_date_to_epoch_days(iso_date.year, iso_date.month - 1, iso_date.day);
  569. // 2. Return EpochTimeToDayInYear(EpochDaysToEpochMs(epochDays, 0)) + 1.
  570. return epoch_time_to_day_in_year(epoch_days_to_epoch_ms(epoch_days, 0)) + 1;
  571. }
  572. // 12.2.18 ISODayOfWeek ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodayofweek
  573. u8 iso_day_of_week(ISODate const& iso_date)
  574. {
  575. // 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
  576. auto epoch_days = iso_date_to_epoch_days(iso_date.year, iso_date.month - 1, iso_date.day);
  577. // 2. Let dayOfWeek be EpochTimeToWeekDay(EpochDaysToEpochMs(epochDays, 0)).
  578. auto day_of_week = epoch_time_to_week_day(epoch_days_to_epoch_ms(epoch_days, 0));
  579. // 3. If dayOfWeek = 0, return 7.
  580. if (day_of_week == 0)
  581. return 7;
  582. // 4. Return dayOfWeek.
  583. return day_of_week;
  584. }
  585. // 12.2.19 CalendarDateToISO ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso
  586. ThrowCompletionOr<ISODate> calendar_date_to_iso(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow)
  587. {
  588. // 1. If calendar is "iso8601", then
  589. if (calendar == "iso8601"sv) {
  590. // a. Assert: fields.[[Year]], fields.[[Month]], and fields.[[Day]] are not UNSET.
  591. VERIFY(fields.year.has_value());
  592. VERIFY(fields.month.has_value());
  593. VERIFY(fields.day.has_value());
  594. // b. Return ? RegulateISODate(fields.[[Year]], fields.[[Month]], fields.[[Day]], overflow).
  595. return TRY(regulate_iso_date(vm, *fields.year, *fields.month, *fields.day, overflow));
  596. }
  597. // 2. Return an implementation-defined ISO Date Record, or throw a RangeError exception, as described below.
  598. // FIXME: Create an ISODateRecord based on an ISO8601 calendar for now. See also: CalendarResolveFields.
  599. return calendar_month_day_to_iso_reference_date(vm, "iso8601"sv, fields, overflow);
  600. }
  601. // 12.2.20 CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate
  602. ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow)
  603. {
  604. // 1. If calendar is "iso8601", then
  605. if (calendar == "iso8601"sv) {
  606. // a. Assert: fields.[[Month]] and fields.[[Day]] are not UNSET.
  607. VERIFY(fields.month.has_value());
  608. VERIFY(fields.day.has_value());
  609. // b. Let referenceISOYear be 1972 (the first ISO 8601 leap year after the epoch).
  610. static constexpr i32 reference_iso_year = 1972;
  611. // c. If fields.[[Year]] is UNSET, let year be referenceISOYear; else let year be fields.[[Year]].
  612. auto year = !fields.year.has_value() ? reference_iso_year : *fields.year;
  613. // d. Let result be ? RegulateISODate(year, fields.[[Month]], fields.[[Day]], overflow).
  614. auto result = TRY(regulate_iso_date(vm, year, *fields.month, *fields.day, overflow));
  615. // e. Return CreateISODateRecord(referenceISOYear, result.[[Month]], result.[[Day]]).
  616. return create_iso_date_record(reference_iso_year, result.month, result.day);
  617. }
  618. // 2. Return an implementation-defined ISO Date Record, or throw a RangeError exception, as described below.
  619. // FIXME: Create an ISODateRecord based on an ISO8601 calendar for now. See also: CalendarResolveFields.
  620. return calendar_month_day_to_iso_reference_date(vm, "iso8601"sv, fields, overflow);
  621. }
  622. // 12.2.21 CalendarISOToDate ( calendar, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
  623. CalendarDate calendar_iso_to_date(StringView calendar, ISODate const& iso_date)
  624. {
  625. // 1. If calendar is "iso8601", then
  626. if (calendar == "iso8601"sv) {
  627. // a. Let monthNumberPart be ToZeroPaddedDecimalString(isoDate.[[Month]], 2).
  628. // b. Let monthCode be the string-concatenation of "M" and monthNumberPart.
  629. auto month_code = MUST(String::formatted("M{:02}", iso_date.month));
  630. // c. If MathematicalInLeapYear(EpochTimeForYear(isoDate.[[Year]])) = 1, let inLeapYear be true; else let inLeapYear be false.
  631. auto in_leap_year = mathematical_in_leap_year(epoch_time_for_year(iso_date.year)) == 1;
  632. // d. Return Calendar Date Record { [[Era]]: undefined, [[EraYear]]: undefined, [[Year]]: isoDate.[[Year]],
  633. // [[Month]]: isoDate.[[Month]], [[MonthCode]]: monthCode, [[Day]]: isoDate.[[Day]], [[DayOfWeek]]: ISODayOfWeek(isoDate),
  634. // [[DayOfYear]]: ISODayOfYear(isoDate), [[WeekOfYear]]: ISOWeekOfYear(isoDate), [[DaysInWeek]]: 7,
  635. // [[DaysInMonth]]: ISODaysInMonth(isoDate.[[Year]], isoDate.[[Month]]), [[DaysInYear]]: MathematicalDaysInYear(isoDate.[[Year]]),
  636. // [[MonthsInYear]]: 12, [[InLeapYear]]: inLeapYear }.
  637. return CalendarDate {
  638. .era = {},
  639. .era_year = {},
  640. .year = iso_date.year,
  641. .month = iso_date.month,
  642. .month_code = move(month_code),
  643. .day = iso_date.day,
  644. .day_of_week = iso_day_of_week(iso_date),
  645. .day_of_year = iso_day_of_year(iso_date),
  646. .week_of_year = iso_week_of_year(iso_date),
  647. .days_in_week = 7,
  648. .days_in_month = iso_days_in_month(iso_date.year, iso_date.month),
  649. .days_in_year = mathematical_days_in_year(iso_date.year),
  650. .months_in_year = 12,
  651. .in_leap_year = in_leap_year,
  652. };
  653. }
  654. // 2. Return an implementation-defined Calendar Date Record with fields as described in Table 18.
  655. // FIXME: Return an ISO8601 calendar date for now.
  656. return calendar_iso_to_date("iso8601"sv, iso_date);
  657. }
  658. // 12.2.22 CalendarExtraFields ( calendar, fields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarextrafields
  659. Vector<CalendarField> calendar_extra_fields(StringView calendar, CalendarFieldList)
  660. {
  661. // 1. If calendar is "iso8601", return an empty List.
  662. if (calendar == "iso8601"sv)
  663. return {};
  664. // FIXME: 2. Return an implementation-defined List as described above.
  665. return {};
  666. }
  667. // 12.2.23 CalendarFieldKeysToIgnore ( calendar, keys ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore
  668. Vector<CalendarField> calendar_field_keys_to_ignore(StringView calendar, ReadonlySpan<CalendarField> keys)
  669. {
  670. // 1. If calendar is "iso8601", then
  671. if (calendar == "iso8601"sv) {
  672. // a. Let ignoredKeys be an empty List.
  673. Vector<CalendarField> ignored_keys;
  674. // b. For each element key of keys, do
  675. for (auto key : keys) {
  676. // i. Append key to ignoredKeys.
  677. ignored_keys.append(key);
  678. // ii. If key is MONTH, append MONTH-CODE to ignoredKeys.
  679. if (key == CalendarField::Month)
  680. ignored_keys.append(CalendarField::MonthCode);
  681. // iii. Else if key is MONTH-CODE, append MONTH to ignoredKeys.
  682. else if (key == CalendarField::MonthCode)
  683. ignored_keys.append(CalendarField::Month);
  684. }
  685. // c. NOTE: While ignoredKeys can have duplicate elements, this is not intended to be meaningful. This specification
  686. // only checks whether particular keys are or are not members of the list.
  687. // d. Return ignoredKeys.
  688. return ignored_keys;
  689. }
  690. // 2. Return an implementation-defined List as described below.
  691. // FIXME: Return keys for an ISO8601 calendar for now.
  692. return calendar_field_keys_to_ignore("iso8601"sv, keys);
  693. }
  694. // 12.2.24 CalendarResolveFields ( calendar, fields, type ), https://tc39.es/proposal-temporal/#sec-temporal-calendarresolvefields
  695. ThrowCompletionOr<void> calendar_resolve_fields(VM& vm, StringView calendar, CalendarFields& fields, DateType type)
  696. {
  697. // 1. If calendar is "iso8601", then
  698. if (calendar == "iso8601"sv) {
  699. // a. If type is DATE or YEAR-MONTH and fields.[[Year]] is UNSET, throw a TypeError exception.
  700. if ((type == DateType::Date || type == DateType::YearMonth) && !fields.year.has_value())
  701. return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, "year"sv);
  702. // b. If type is DATE or MONTH-DAY and fields.[[Day]] is UNSET, throw a TypeError exception.
  703. if ((type == DateType::Date || type == DateType::MonthDay) && !fields.day.has_value())
  704. return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, "day"sv);
  705. // c. Let month be fields.[[Month]].
  706. auto const& month = fields.month;
  707. // d. Let monthCode be fields.[[MonthCode]].
  708. auto const& month_code = fields.month_code;
  709. // e. If monthCode is UNSET, then
  710. if (!month_code.has_value()) {
  711. // i. If month is UNSET, throw a TypeError exception.
  712. if (!month.has_value())
  713. return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, "month"sv);
  714. // ii. Return UNUSED.
  715. return {};
  716. }
  717. // f. Assert: monthCode is a String.
  718. VERIFY(month_code.has_value());
  719. // g. NOTE: The ISO 8601 calendar does not include leap months.
  720. // h. If the length of monthCode is not 3, throw a RangeError exception.
  721. if (month_code->byte_count() != 3)
  722. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "monthCode"sv);
  723. // i. If the first code unit of monthCode is not 0x004D (LATIN CAPITAL LETTER M), throw a RangeError exception.
  724. if (month_code->bytes_as_string_view()[0] != 'M')
  725. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "monthCode"sv);
  726. // j. Let monthCodeDigits be the substring of monthCode from 1.
  727. auto month_code_digits = month_code->bytes_as_string_view().substring_view(1);
  728. // k. If ParseText(StringToCodePoints(monthCodeDigits), DateMonth) is a List of errors, throw a RangeError exception.
  729. if (!parse_iso8601(Production::DateMonth, month_code_digits).has_value())
  730. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "monthCode"sv);
  731. // l. Let monthCodeInteger be ℝ(StringToNumber(monthCodeDigits)).
  732. auto month_code_integer = month_code_digits.to_number<u8>().value();
  733. // m. If month is not UNSET and month ≠ monthCodeInteger, throw a RangeError exception.
  734. if (month.has_value() && month != month_code_integer)
  735. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "month"sv);
  736. // n. Set fields.[[Month]] to monthCodeInteger.
  737. fields.month = month_code_integer;
  738. }
  739. // 2. Else,
  740. else {
  741. // a. Perform implementation-defined processing to mutate fields, or throw a TypeError or RangeError exception, as described below.
  742. // FIXME: Resolve fields as an ISO8601 calendar for now. See also: CalendarMonthDayToISOReferenceDate.
  743. TRY(calendar_resolve_fields(vm, "iso8601"sv, fields, type));
  744. }
  745. // 3. Return UNUSED.
  746. return {};
  747. }
  748. }