PlainDate.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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/Temporal/Calendar.h>
  11. #include <LibJS/Runtime/Temporal/DateEquations.h>
  12. #include <LibJS/Runtime/Temporal/PlainDate.h>
  13. #include <LibJS/Runtime/Temporal/PlainDateTime.h>
  14. #include <LibJS/Runtime/Temporal/PlainTime.h>
  15. namespace JS::Temporal {
  16. // 3.5.2 CreateISODateRecord ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-create-iso-date-record
  17. ISODate create_iso_date_record(double year, double month, double day)
  18. {
  19. // 1. Assert: IsValidISODate(year, month, day) is true.
  20. VERIFY(is_valid_iso_date(year, month, day));
  21. // 2. Return ISO Date Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
  22. return { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
  23. }
  24. // 3.5.5 ISODateSurpasses ( sign, y1, m1, d1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
  25. bool iso_date_surpasses(i8 sign, double year1, double month1, double day1, ISODate iso_date2)
  26. {
  27. // 1. If y1 ≠ isoDate2.[[Year]], then
  28. if (year1 != iso_date2.year) {
  29. // a. If sign × (y1 - isoDate2.[[Year]]) > 0, return true.
  30. if (sign * (year1 - iso_date2.year) > 0)
  31. return true;
  32. }
  33. // 2. Else if m1 ≠ isoDate2.[[Month]], then
  34. else if (month1 != iso_date2.month) {
  35. // a. If sign × (m1 - isoDate2.[[Month]]) > 0, return true.
  36. if (sign * (month1 - iso_date2.month) > 0)
  37. return true;
  38. }
  39. // 3. Else if d1 ≠ isoDate2.[[Day]], then
  40. else if (day1 != iso_date2.day) {
  41. // a. If sign × (d1 - isoDate2.[[Day]]) > 0, return true.
  42. if (sign * (day1 - iso_date2.day) > 0)
  43. return true;
  44. }
  45. // 4. Return false.
  46. return false;
  47. }
  48. // 3.5.6 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
  49. ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow)
  50. {
  51. switch (overflow) {
  52. // 1. If overflow is CONSTRAIN, then
  53. case Overflow::Constrain:
  54. // a. Set month to the result of clamping month between 1 and 12.
  55. month = clamp(month, 1, 12);
  56. // b. Let daysInMonth be ISODaysInMonth(year, month).
  57. // c. Set day to the result of clamping day between 1 and daysInMonth.
  58. day = clamp(day, 1, iso_days_in_month(year, month));
  59. // AD-HOC: We further clamp the year to the range allowed by ISOYearMonthWithinLimits, to ensure we do not
  60. // overflow when we store the year as an integer.
  61. year = clamp(year, -271821, 275760);
  62. break;
  63. // 2. Else,
  64. case Overflow::Reject:
  65. // a. Assert: overflow is REJECT.
  66. // b. If IsValidISODate(year, month, day) is false, throw a RangeError exception.
  67. if (!is_valid_iso_date(year, month, day))
  68. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
  69. break;
  70. }
  71. // 3. Return CreateISODateRecord(year, month, day).
  72. return create_iso_date_record(year, month, day);
  73. }
  74. // 3.5.7 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
  75. bool is_valid_iso_date(double year, double month, double day)
  76. {
  77. // AD-HOC: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
  78. // This does not change the exposed behavior as the call to CreateISODateRecord will immediately check that
  79. // these values are valid ISO values (years: [-271821, 275760], months: [1, 12], days: [1, 31]), all of
  80. // which are subsets of this check.
  81. if (!AK::is_within_range<i32>(year) || !AK::is_within_range<u8>(month) || !AK::is_within_range<u8>(day))
  82. return false;
  83. // 1. If month < 1 or month > 12, then
  84. if (month < 1 || month > 12) {
  85. // a. Return false.
  86. return false;
  87. }
  88. // 2. Let daysInMonth be ISODaysInMonth(year, month).
  89. auto days_in_month = iso_days_in_month(year, month);
  90. // 3. If day < 1 or day > daysInMonth, then
  91. if (day < 1 || day > days_in_month) {
  92. // a. Return false.
  93. return false;
  94. }
  95. // 4. Return true.
  96. return true;
  97. }
  98. // 3.5.8 BalanceISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodate
  99. ISODate balance_iso_date(double year, double month, double day)
  100. {
  101. // 1. Let epochDays be ISODateToEpochDays(year, month - 1, day).
  102. auto epoch_days = iso_date_to_epoch_days(year, month - 1, day);
  103. // 2. Let ms be EpochDaysToEpochMs(epochDays, 0).
  104. auto ms = epoch_days_to_epoch_ms(epoch_days, 0);
  105. // 3. Return CreateISODateRecord(EpochTimeToEpochYear(ms), EpochTimeToMonthInYear(ms) + 1, EpochTimeToDate(ms)).
  106. 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));
  107. }
  108. // 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
  109. String pad_iso_year(i32 year)
  110. {
  111. // 1. If y ≥ 0 and y ≤ 9999, then
  112. if (year >= 0 && year <= 9999) {
  113. // a. Return ToZeroPaddedDecimalString(y, 4).
  114. return MUST(String::formatted("{:04}", year));
  115. }
  116. // 2. If y > 0, let yearSign be "+"; otherwise, let yearSign be "-".
  117. auto year_sign = year > 0 ? '+' : '-';
  118. // 3. Let year be ToZeroPaddedDecimalString(abs(y), 6).
  119. // 4. Return the string-concatenation of yearSign and year.
  120. return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
  121. }
  122. // 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
  123. bool iso_date_within_limits(ISODate iso_date)
  124. {
  125. // 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, NoonTimeRecord()).
  126. auto iso_date_time = combine_iso_date_and_time_record(iso_date, noon_time_record());
  127. // 2. Return ISODateTimeWithinLimits(isoDateTime).
  128. return iso_date_time_within_limits(iso_date_time);
  129. }
  130. // 3.5.12 CompareISODate ( isoDate1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
  131. i8 compare_iso_date(ISODate iso_date1, ISODate iso_date2)
  132. {
  133. // 1. If isoDate1.[[Year]] > isoDate2.[[Year]], return 1.
  134. if (iso_date1.year > iso_date2.year)
  135. return 1;
  136. // 2. If isoDate1.[[Year]] < isoDate2.[[Year]], return -1.
  137. if (iso_date1.year < iso_date2.year)
  138. return -1;
  139. // 3. If isoDate1.[[Month]] > isoDate2.[[Month]], return 1.
  140. if (iso_date1.month > iso_date2.month)
  141. return 1;
  142. // 4. If isoDate1.[[Month]] < isoDate2.[[Month]], return -1.
  143. if (iso_date1.month < iso_date2.month)
  144. return -1;
  145. // 5. If isoDate1.[[Day]] > isoDate2.[[Day]], return 1.
  146. if (iso_date1.day > iso_date2.day)
  147. return 1;
  148. // 6. If isoDate1.[[Day]] < isoDate2.[[Day]], return -1.
  149. if (iso_date1.day < iso_date2.day)
  150. return -1;
  151. // 7. Return 0.
  152. return 0;
  153. }
  154. }