PlainYearMonth.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  3. * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibJS/Runtime/AbstractOperations.h>
  8. #include <LibJS/Runtime/Intrinsics.h>
  9. #include <LibJS/Runtime/Realm.h>
  10. #include <LibJS/Runtime/Temporal/Calendar.h>
  11. #include <LibJS/Runtime/Temporal/Duration.h>
  12. #include <LibJS/Runtime/Temporal/PlainDateTime.h>
  13. #include <LibJS/Runtime/Temporal/PlainTime.h>
  14. #include <LibJS/Runtime/Temporal/PlainYearMonth.h>
  15. #include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
  16. #include <LibJS/Runtime/VM.h>
  17. namespace JS::Temporal {
  18. GC_DEFINE_ALLOCATOR(PlainYearMonth);
  19. // 9 Temporal.PlainYearMonth Objects, https://tc39.es/proposal-temporal/#sec-temporal-plainyearmonth-objects
  20. PlainYearMonth::PlainYearMonth(ISODate iso_date, String calendar, Object& prototype)
  21. : Object(ConstructWithPrototypeTag::Tag, prototype)
  22. , m_iso_date(iso_date)
  23. , m_calendar(move(calendar))
  24. {
  25. }
  26. // 9.5.2 ToTemporalYearMonth ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth
  27. ThrowCompletionOr<GC::Ref<PlainYearMonth>> to_temporal_year_month(VM& vm, Value item, Value options)
  28. {
  29. // 1. If options is not present, set options to undefined.
  30. // 2. If item is an Object, then
  31. if (item.is_object()) {
  32. auto const& object = item.as_object();
  33. // a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
  34. if (is<PlainYearMonth>(object)) {
  35. auto const& plain_year_month = static_cast<PlainYearMonth const&>(object);
  36. // i. Let resolvedOptions be ? GetOptionsObject(options).
  37. auto resolved_options = TRY(get_options_object(vm, options));
  38. // ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
  39. TRY(get_temporal_overflow_option(vm, resolved_options));
  40. // iii. Return ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
  41. return MUST(create_temporal_year_month(vm, plain_year_month.iso_date(), plain_year_month.calendar()));
  42. }
  43. // b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
  44. auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object));
  45. // c. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE », «», «»).
  46. auto fields = TRY(prepare_calendar_fields(vm, calendar, object, { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode } }, {}, CalendarFieldList {}));
  47. // d. Let resolvedOptions be ? GetOptionsObject(options).
  48. auto resolved_options = TRY(get_options_object(vm, options));
  49. // e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
  50. auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
  51. // f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
  52. auto iso_date = TRY(calendar_year_month_from_fields(vm, calendar, move(fields), overflow));
  53. // g. Return ! CreateTemporalYearMonth(isoDate, calendar).
  54. return MUST(create_temporal_year_month(vm, iso_date, move(calendar)));
  55. }
  56. // 3. If item is not a String, throw a TypeError exception.
  57. if (!item.is_string())
  58. return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidPlainYearMonth);
  59. // 4. Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
  60. auto parse_result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalYearMonthString } }));
  61. // 5. Let calendar be result.[[Calendar]].
  62. // 6. If calendar is empty, set calendar to "iso8601".
  63. auto calendar = parse_result.calendar.value_or("iso8601"_string);
  64. // 7. Set calendar to ? CanonicalizeCalendar(calendar).
  65. calendar = TRY(canonicalize_calendar(vm, calendar));
  66. // 8. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
  67. auto iso_date = create_iso_date_record(*parse_result.year, parse_result.month, parse_result.day);
  68. // 9. Set result to ISODateToFields(calendar, isoDate, YEAR-MONTH).
  69. auto result = iso_date_to_fields(calendar, iso_date, DateType::YearMonth);
  70. // 10. Let resolvedOptions be ? GetOptionsObject(options).
  71. auto resolved_options = TRY(get_options_object(vm, options));
  72. // 11. Perform ? GetTemporalOverflowOption(resolvedOptions).
  73. TRY(get_temporal_overflow_option(vm, resolved_options));
  74. // 12. NOTE: The following operation is called with CONSTRAIN regardless of the value of overflow, in order for the
  75. // calendar to store a canonical value in the [[Day]] field of the [[ISODate]] internal slot of the result.
  76. // 13. Set isoDate to ? CalendarYearMonthFromFields(calendar, result, CONSTRAIN).
  77. iso_date = TRY(calendar_year_month_from_fields(vm, calendar, result, Overflow::Constrain));
  78. // 14. Return ! CreateTemporalYearMonth(isoDate, calendar).
  79. return MUST(create_temporal_year_month(vm, iso_date, move(calendar)));
  80. }
  81. // 9.5.3 ISOYearMonthWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthwithinlimits
  82. bool iso_year_month_within_limits(ISODate iso_date)
  83. {
  84. // 1. If isoDate.[[Year]] < -271821 or isoDate.[[Year]] > 275760, then
  85. if (iso_date.year < -271821 || iso_date.year > 275760) {
  86. // a. Return false.
  87. return false;
  88. }
  89. // 2. If isoDate.[[Year]] = -271821 and isoDate.[[Month]] < 4, then
  90. if (iso_date.year == -271821 && iso_date.month < 4) {
  91. // a. Return false.
  92. return false;
  93. }
  94. // 3. If isoDate.[[Year]] = 275760 and isoDate.[[Month]] > 9, then
  95. if (iso_date.year == 275760 && iso_date.month > 9) {
  96. // a. Return false.
  97. return false;
  98. }
  99. // 4. Return true.
  100. return true;
  101. }
  102. // 9.5.4 BalanceISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
  103. ISOYearMonth balance_iso_year_month(double year, double month)
  104. {
  105. // 1. Set year to year + floor((month - 1) / 12).
  106. year += floor((month - 1.0) / 12.0);
  107. // 2. Set month to ((month - 1) modulo 12) + 1.
  108. month = modulo(month - 1, 12.0) + 1.0;
  109. // 3. Return ISO Year-Month Record { [[Year]]: year, [[Month]]: month }.
  110. return { .year = static_cast<i32>(year), .month = static_cast<u8>(month) };
  111. }
  112. // 9.5.5 CreateTemporalYearMonth ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalyearmonth
  113. ThrowCompletionOr<GC::Ref<PlainYearMonth>> create_temporal_year_month(VM& vm, ISODate iso_date, String calendar, GC::Ptr<FunctionObject> new_target)
  114. {
  115. auto& realm = *vm.current_realm();
  116. // 1. If ISOYearMonthWithinLimits(isoDate) is false, throw a RangeError exception.
  117. if (!iso_year_month_within_limits(iso_date))
  118. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainYearMonth);
  119. // 2. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%.
  120. if (!new_target)
  121. new_target = realm.intrinsics().temporal_plain_year_month_constructor();
  122. // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainYearMonth.prototype%", « [[InitializedTemporalYearMonth]], [[ISODate]], [[Calendar]] »).
  123. // 4. Set object.[[ISODate]] to isoDate.
  124. // 5. Set object.[[Calendar]] to calendar.
  125. auto object = TRY(ordinary_create_from_constructor<PlainYearMonth>(vm, *new_target, &Intrinsics::temporal_plain_year_month_prototype, iso_date, move(calendar)));
  126. // 6. Return object.
  127. return object;
  128. }
  129. // 9.5.6 TemporalYearMonthToString ( yearMonth, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporalyearmonthtostring
  130. String temporal_year_month_to_string(PlainYearMonth const& year_month, ShowCalendar show_calendar)
  131. {
  132. // 1. Let year be PadISOYear(yearMonth.[[ISODate]].[[Year]]).
  133. auto year = pad_iso_year(year_month.iso_date().year);
  134. // 2. Let month be ToZeroPaddedDecimalString(yearMonth.[[ISODate]].[[Month]], 2).
  135. // 3. Let result be the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and month.
  136. auto result = MUST(String::formatted("{}-{:02}", year, year_month.iso_date().month));
  137. // 4. If showCalendar is one of always or critical, or if yearMonth.[[Calendar]] is not "iso8601", then
  138. if (show_calendar == ShowCalendar::Always || show_calendar == ShowCalendar::Critical || year_month.calendar() != "iso8601"sv) {
  139. // a. Let day be ToZeroPaddedDecimalString(yearMonth.[[ISODate]].[[Day]], 2).
  140. // b. Set result to the string-concatenation of result, the code unit 0x002D (HYPHEN-MINUS), and day.
  141. result = MUST(String::formatted("{}-{:02}", result, year_month.iso_date().day));
  142. }
  143. // 5. Let calendarString be FormatCalendarAnnotation(yearMonth.[[Calendar]], showCalendar).
  144. auto calendar_string = format_calendar_annotation(year_month.calendar(), show_calendar);
  145. // 6. Set result to the string-concatenation of result and calendarString.
  146. result = MUST(String::formatted("{}{}", result, calendar_string));
  147. // 7. Return result.
  148. return result;
  149. }
  150. // 9.5.7 DifferenceTemporalPlainYearMonth ( operation, yearMonth, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplainyearmonth
  151. ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_year_month(VM& vm, DurationOperation operation, PlainYearMonth const& year_month, Value other_value, Value options)
  152. {
  153. // 1. Set other to ? ToTemporalYearMonth(other).
  154. auto other = TRY(to_temporal_year_month(vm, other_value));
  155. // 2. Let calendar be yearMonth.[[Calendar]].
  156. auto const& calendar = year_month.calendar();
  157. // 3. If CalendarEquals(calendar, other.[[Calendar]]) is false, throw a RangeError exception.
  158. if (!calendar_equals(calendar, other->calendar()))
  159. return vm.throw_completion<RangeError>(ErrorType::TemporalDifferentCalendars);
  160. // 4. Let resolvedOptions be ? GetOptionsObject(options).
  161. auto resolved_options = TRY(get_options_object(vm, options));
  162. // 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « WEEK, DAY », MONTH, YEAR).
  163. auto settings = TRY(get_difference_settings(vm, operation, resolved_options, UnitGroup::Date, { { Unit::Week, Unit::Day } }, Unit::Month, Unit::Year));
  164. // 6. If CompareISODate(yearMonth.[[ISODate]], other.[[ISODate]]) = 0, then
  165. if (compare_iso_date(year_month.iso_date(), other->iso_date()) == 0) {
  166. // a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
  167. return MUST(create_temporal_duration(vm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
  168. }
  169. // 7. Let thisFields be ISODateToFields(calendar, yearMonth.[[ISODate]], YEAR-MONTH).
  170. auto this_fields = iso_date_to_fields(calendar, year_month.iso_date(), DateType::YearMonth);
  171. // 8. Set thisFields.[[Day]] to 1.
  172. this_fields.day = 1;
  173. // 9. Let thisDate be ? CalendarDateFromFields(calendar, thisFields, CONSTRAIN).
  174. auto this_date = TRY(calendar_date_from_fields(vm, calendar, move(this_fields), Overflow::Constrain));
  175. // 10. Let otherFields be ISODateToFields(calendar, other.[[ISODate]], YEAR-MONTH).
  176. auto other_fields = iso_date_to_fields(calendar, other->iso_date(), DateType::YearMonth);
  177. // 11. Set otherFields.[[Day]] to 1.
  178. other_fields.day = 1;
  179. // 12. Let otherDate be ? CalendarDateFromFields(calendar, otherFields, CONSTRAIN).
  180. auto other_date = TRY(calendar_date_from_fields(vm, calendar, move(other_fields), Overflow::Constrain));
  181. // 13. Let dateDifference be CalendarDateUntil(calendar, thisDate, otherDate, settings.[[LargestUnit]]).
  182. auto date_difference = calendar_date_until(vm, calendar, this_date, other_date, settings.largest_unit);
  183. // 14. Let yearsMonthsDifference be ! AdjustDateDurationRecord(dateDifference, 0, 0).
  184. auto years_months_difference = MUST(adjust_date_duration_record(vm, date_difference, 0, 0));
  185. // 15. Let duration be ! CombineDateAndTimeDuration(yearsMonthsDifference, 0).
  186. auto duration = MUST(combine_date_and_time_duration(vm, years_months_difference, TimeDuration { 0 }));
  187. // 16. If settings.[[SmallestUnit]] is not MONTH or settings.[[RoundingIncrement]] ≠ 1, then
  188. if (settings.smallest_unit != Unit::Month || settings.rounding_increment != 1) {
  189. // a. Let isoDateTime be CombineISODateAndTimeRecord(thisDate, MidnightTimeRecord()).
  190. auto iso_date_time = combine_iso_date_and_time_record(this_date, midnight_time_record());
  191. // b. Let isoDateTimeOther be CombineISODateAndTimeRecord(otherDate, MidnightTimeRecord()).
  192. auto iso_date_time_other = combine_iso_date_and_time_record(other_date, midnight_time_record());
  193. // c. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
  194. auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time_other);
  195. // d. Set duration to ? RoundRelativeDuration(duration, destEpochNs, isoDateTime, UNSET, calendar, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
  196. duration = TRY(round_relative_duration(vm, move(duration), dest_epoch_ns, iso_date_time, {}, calendar, settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
  197. }
  198. // 17. Let result be ? TemporalDurationFromInternal(duration, DAY).
  199. auto result = TRY(temporal_duration_from_internal(vm, duration, Unit::Day));
  200. // 18. If operation is SINCE, set result to CreateNegatedTemporalDuration(result).
  201. if (operation == DurationOperation::Since)
  202. result = create_negated_temporal_duration(vm, result);
  203. // 19. Return result.
  204. return result;
  205. }
  206. }