PlainDate.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 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/Checked.h>
  10. #include <LibJS/Runtime/AbstractOperations.h>
  11. #include <LibJS/Runtime/Temporal/Calendar.h>
  12. #include <LibJS/Runtime/Temporal/DateEquations.h>
  13. #include <LibJS/Runtime/Temporal/PlainDate.h>
  14. #include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
  15. #include <LibJS/Runtime/Temporal/PlainDateTime.h>
  16. #include <LibJS/Runtime/Temporal/PlainTime.h>
  17. namespace JS::Temporal {
  18. GC_DEFINE_ALLOCATOR(PlainDate);
  19. // 3 Temporal.PlainDate Objects, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-objects
  20. PlainDate::PlainDate(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. // 3.5.2 CreateISODateRecord ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-create-iso-date-record
  27. ISODate create_iso_date_record(double year, double month, double day)
  28. {
  29. // 1. Assert: IsValidISODate(year, month, day) is true.
  30. VERIFY(is_valid_iso_date(year, month, day));
  31. // 2. Return ISO Date Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
  32. return { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
  33. }
  34. // 3.5.3 CreateTemporalDate ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldate
  35. ThrowCompletionOr<GC::Ref<PlainDate>> create_temporal_date(VM& vm, ISODate iso_date, String calendar, GC::Ptr<FunctionObject> new_target)
  36. {
  37. auto& realm = *vm.current_realm();
  38. // 1. If ISODateWithinLimits(isoDate) is false, throw a RangeError exception.
  39. if (!iso_date_within_limits(iso_date))
  40. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
  41. // 2. If newTarget is not present, set newTarget to %Temporal.PlainDate%.
  42. if (!new_target)
  43. new_target = realm.intrinsics().temporal_plain_date_constructor();
  44. // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDate.prototype%", « [[InitializedTemporalDate]], [[ISODate]], [[Calendar]] »).
  45. // 4. Set object.[[ISODate]] to isoDate.
  46. // 5. Set object.[[Calendar]] to calendar.
  47. auto object = TRY(ordinary_create_from_constructor<PlainDate>(vm, *new_target, &Intrinsics::temporal_plain_date_prototype, iso_date, move(calendar)));
  48. // 6. Return object.
  49. return object;
  50. }
  51. // 3.5.4 ToTemporalDate ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldate
  52. ThrowCompletionOr<GC::Ref<PlainDate>> to_temporal_date(VM& vm, Value item, Value options)
  53. {
  54. // 1. If options is not present, set options to undefined.
  55. // 2. If item is an Object, then
  56. if (item.is_object()) {
  57. auto const& object = item.as_object();
  58. // a. If item has an [[InitializedTemporalDate]] internal slot, then
  59. if (is<PlainDate>(object)) {
  60. auto const& plain_date = static_cast<PlainDate const&>(object);
  61. // i. Let resolvedOptions be ? GetOptionsObject(options).
  62. auto resolved_options = TRY(get_options_object(vm, options));
  63. // ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
  64. TRY(get_temporal_overflow_option(vm, resolved_options));
  65. // iii. Return ! CreateTemporalDate(item.[[ISODate]], item.[[Calendar]]).
  66. return MUST(create_temporal_date(vm, plain_date.iso_date(), plain_date.calendar()));
  67. }
  68. // FIXME: b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
  69. // FIXME: i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]).
  70. // FIXME: ii. Let resolvedOptions be ? GetOptionsObject(options).
  71. // FIXME: iii. Perform ? GetTemporalOverflowOption(resolvedOptions).
  72. // FIXME: iv. Return ! CreateTemporalDate(isoDateTime.[[ISODate]], item.[[Calendar]]).
  73. // FIXME: c. If item has an [[InitializedTemporalDateTime]] internal slot, then
  74. // FIXME: i. Let resolvedOptions be ? GetOptionsObject(options).
  75. // FIXME: ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
  76. // FIXME: iii. Return ! CreateTemporalDate(item.[[ISODateTime]].[[ISODate]], item.[[Calendar]]).
  77. // d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
  78. auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object));
  79. // e. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE, DAY », «», «»).
  80. auto fields = TRY(prepare_calendar_fields(vm, calendar, object, { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day } }, {}, CalendarFieldList {}));
  81. // f. Let resolvedOptions be ? GetOptionsObject(options).
  82. auto resolved_options = TRY(get_options_object(vm, options));
  83. // g. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
  84. auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
  85. // h. Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow).
  86. auto iso_date = TRY(calendar_date_from_fields(vm, calendar, move(fields), overflow));
  87. // i. Return ! CreateTemporalDate(isoDate, calendar).
  88. return MUST(create_temporal_date(vm, iso_date, move(calendar)));
  89. }
  90. // 3. If item is not a String, throw a TypeError exception.
  91. if (!item.is_string())
  92. return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidPlainDate);
  93. // 4. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »).
  94. auto result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalDateTimeString } }));
  95. // 5. Let calendar be result.[[Calendar]].
  96. // 6. If calendar is empty, set calendar to "iso8601".
  97. auto calendar = result.calendar.value_or("iso8601"_string);
  98. // 7. Set calendar to ? CanonicalizeCalendar(calendar).
  99. calendar = TRY(canonicalize_calendar(vm, calendar));
  100. // 8. Let resolvedOptions be ? GetOptionsObject(options).
  101. auto resolved_options = TRY(get_options_object(vm, options));
  102. // 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
  103. TRY(get_temporal_overflow_option(vm, resolved_options));
  104. // 10. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
  105. auto iso_date = create_iso_date_record(*result.year, result.month, result.day);
  106. // 11. Return ? CreateTemporalDate(isoDate, calendar).
  107. return TRY(create_temporal_date(vm, iso_date, move(calendar)));
  108. }
  109. // 3.5.5 ISODateSurpasses ( sign, y1, m1, d1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
  110. bool iso_date_surpasses(i8 sign, double year1, double month1, double day1, ISODate iso_date2)
  111. {
  112. // 1. If y1 ≠ isoDate2.[[Year]], then
  113. if (year1 != iso_date2.year) {
  114. // a. If sign × (y1 - isoDate2.[[Year]]) > 0, return true.
  115. if (sign * (year1 - iso_date2.year) > 0)
  116. return true;
  117. }
  118. // 2. Else if m1 ≠ isoDate2.[[Month]], then
  119. else if (month1 != iso_date2.month) {
  120. // a. If sign × (m1 - isoDate2.[[Month]]) > 0, return true.
  121. if (sign * (month1 - iso_date2.month) > 0)
  122. return true;
  123. }
  124. // 3. Else if d1 ≠ isoDate2.[[Day]], then
  125. else if (day1 != iso_date2.day) {
  126. // a. If sign × (d1 - isoDate2.[[Day]]) > 0, return true.
  127. if (sign * (day1 - iso_date2.day) > 0)
  128. return true;
  129. }
  130. // 4. Return false.
  131. return false;
  132. }
  133. // 3.5.6 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
  134. ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow)
  135. {
  136. switch (overflow) {
  137. // 1. If overflow is CONSTRAIN, then
  138. case Overflow::Constrain:
  139. // a. Set month to the result of clamping month between 1 and 12.
  140. month = clamp(month, 1, 12);
  141. // b. Let daysInMonth be ISODaysInMonth(year, month).
  142. // c. Set day to the result of clamping day between 1 and daysInMonth.
  143. day = clamp(day, 1, iso_days_in_month(year, month));
  144. // AD-HOC: We further clamp the year to the range allowed by ISOYearMonthWithinLimits, to ensure we do not
  145. // overflow when we store the year as an integer.
  146. year = clamp(year, -271821, 275760);
  147. break;
  148. // 2. Else,
  149. case Overflow::Reject:
  150. // a. Assert: overflow is REJECT.
  151. // b. If IsValidISODate(year, month, day) is false, throw a RangeError exception.
  152. if (!is_valid_iso_date(year, month, day))
  153. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
  154. break;
  155. }
  156. // 3. Return CreateISODateRecord(year, month, day).
  157. return create_iso_date_record(year, month, day);
  158. }
  159. // 3.5.7 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
  160. bool is_valid_iso_date(double year, double month, double day)
  161. {
  162. // AD-HOC: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
  163. // This does not change the exposed behavior as the call to CreateISODateRecord will immediately check that
  164. // these values are valid ISO values (years: [-271821, 275760], months: [1, 12], days: [1, 31]), all of
  165. // which are subsets of this check.
  166. if (!AK::is_within_range<i32>(year) || !AK::is_within_range<u8>(month) || !AK::is_within_range<u8>(day))
  167. return false;
  168. // 1. If month < 1 or month > 12, then
  169. if (month < 1 || month > 12) {
  170. // a. Return false.
  171. return false;
  172. }
  173. // 2. Let daysInMonth be ISODaysInMonth(year, month).
  174. auto days_in_month = iso_days_in_month(year, month);
  175. // 3. If day < 1 or day > daysInMonth, then
  176. if (day < 1 || day > days_in_month) {
  177. // a. Return false.
  178. return false;
  179. }
  180. // 4. Return true.
  181. return true;
  182. }
  183. // 3.5.8 BalanceISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodate
  184. ISODate balance_iso_date(double year, double month, double day)
  185. {
  186. // 1. Let epochDays be ISODateToEpochDays(year, month - 1, day).
  187. auto epoch_days = iso_date_to_epoch_days(year, month - 1, day);
  188. // 2. Let ms be EpochDaysToEpochMs(epochDays, 0).
  189. auto ms = epoch_days_to_epoch_ms(epoch_days, 0);
  190. // 3. Return CreateISODateRecord(EpochTimeToEpochYear(ms), EpochTimeToMonthInYear(ms) + 1, EpochTimeToDate(ms)).
  191. return create_iso_date_record(epoch_time_to_epoch_year(ms), epoch_time_to_month_in_year(ms) + 1.0, epoch_time_to_date(ms));
  192. }
  193. // 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
  194. String pad_iso_year(i32 year)
  195. {
  196. // 1. If y ≥ 0 and y ≤ 9999, then
  197. if (year >= 0 && year <= 9999) {
  198. // a. Return ToZeroPaddedDecimalString(y, 4).
  199. return MUST(String::formatted("{:04}", year));
  200. }
  201. // 2. If y > 0, let yearSign be "+"; otherwise, let yearSign be "-".
  202. auto year_sign = year > 0 ? '+' : '-';
  203. // 3. Let year be ToZeroPaddedDecimalString(abs(y), 6).
  204. // 4. Return the string-concatenation of yearSign and year.
  205. return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
  206. }
  207. // 3.5.10 TemporalDateToString ( temporalDate, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetostring
  208. String temporal_date_to_string(PlainDate const& temporal_date, ShowCalendar show_calendar)
  209. {
  210. // 1. Let year be PadISOYear(temporalDate.[[ISODate]].[[Year]]).
  211. auto year = pad_iso_year(temporal_date.iso_date().year);
  212. // 2. Let month be ToZeroPaddedDecimalString(temporalDate.[[ISODate]].[[Month]], 2).
  213. auto month = temporal_date.iso_date().month;
  214. // 3. Let day be ToZeroPaddedDecimalString(temporalDate.[[ISODate]].[[Day]], 2).
  215. auto day = temporal_date.iso_date().day;
  216. // 4. Let calendar be FormatCalendarAnnotation(temporalDate.[[Calendar]], showCalendar).
  217. auto calendar = format_calendar_annotation(temporal_date.calendar(), show_calendar);
  218. // 5. Return the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), month, the code unit 0x002D (HYPHEN-MINUS), day, and calendar.
  219. return MUST(String::formatted("{}-{:02}-{:02}{}", year, month, day, calendar));
  220. }
  221. // 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
  222. bool iso_date_within_limits(ISODate iso_date)
  223. {
  224. // 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, NoonTimeRecord()).
  225. auto iso_date_time = combine_iso_date_and_time_record(iso_date, noon_time_record());
  226. // 2. Return ISODateTimeWithinLimits(isoDateTime).
  227. return iso_date_time_within_limits(iso_date_time);
  228. }
  229. // 3.5.12 CompareISODate ( isoDate1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
  230. i8 compare_iso_date(ISODate iso_date1, ISODate iso_date2)
  231. {
  232. // 1. If isoDate1.[[Year]] > isoDate2.[[Year]], return 1.
  233. if (iso_date1.year > iso_date2.year)
  234. return 1;
  235. // 2. If isoDate1.[[Year]] < isoDate2.[[Year]], return -1.
  236. if (iso_date1.year < iso_date2.year)
  237. return -1;
  238. // 3. If isoDate1.[[Month]] > isoDate2.[[Month]], return 1.
  239. if (iso_date1.month > iso_date2.month)
  240. return 1;
  241. // 4. If isoDate1.[[Month]] < isoDate2.[[Month]], return -1.
  242. if (iso_date1.month < iso_date2.month)
  243. return -1;
  244. // 5. If isoDate1.[[Day]] > isoDate2.[[Day]], return 1.
  245. if (iso_date1.day > iso_date2.day)
  246. return 1;
  247. // 6. If isoDate1.[[Day]] < isoDate2.[[Day]], return -1.
  248. if (iso_date1.day < iso_date2.day)
  249. return -1;
  250. // 7. Return 0.
  251. return 0;
  252. }
  253. }