2024-11-21 11:22:32 -05:00
/*
* Copyright ( c ) 2021 - 2023 , Linus Groh < linusg @ serenityos . org >
* Copyright ( c ) 2024 , Tim Flynn < trflynn89 @ ladybird . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibJS/Runtime/AbstractOperations.h>
# include <LibJS/Runtime/Intrinsics.h>
# include <LibJS/Runtime/Realm.h>
# include <LibJS/Runtime/Temporal/Calendar.h>
2024-11-21 13:21:29 -05:00
# include <LibJS/Runtime/Temporal/Duration.h>
# include <LibJS/Runtime/Temporal/PlainDateTime.h>
# include <LibJS/Runtime/Temporal/PlainTime.h>
2024-11-21 11:22:32 -05:00
# include <LibJS/Runtime/Temporal/PlainYearMonth.h>
# include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
# include <LibJS/Runtime/VM.h>
namespace JS : : Temporal {
GC_DEFINE_ALLOCATOR ( PlainYearMonth ) ;
// 9 Temporal.PlainYearMonth Objects, https://tc39.es/proposal-temporal/#sec-temporal-plainyearmonth-objects
PlainYearMonth : : PlainYearMonth ( ISODate iso_date , String calendar , Object & prototype )
: Object ( ConstructWithPrototypeTag : : Tag , prototype )
, m_iso_date ( iso_date )
, m_calendar ( move ( calendar ) )
{
}
// 9.5.2 ToTemporalYearMonth ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth
ThrowCompletionOr < GC : : Ref < PlainYearMonth > > to_temporal_year_month ( VM & vm , Value item , Value options )
{
// 1. If options is not present, set options to undefined.
// 2. If item is an Object, then
if ( item . is_object ( ) ) {
auto const & object = item . as_object ( ) ;
// a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
if ( is < PlainYearMonth > ( object ) ) {
auto const & plain_year_month = static_cast < PlainYearMonth const & > ( object ) ;
// i. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY ( get_options_object ( vm , options ) ) ;
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY ( get_temporal_overflow_option ( vm , resolved_options ) ) ;
// iii. Return ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
return MUST ( create_temporal_year_month ( vm , plain_year_month . iso_date ( ) , plain_year_month . calendar ( ) ) ) ;
}
// b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
auto calendar = TRY ( get_temporal_calendar_identifier_with_iso_default ( vm , object ) ) ;
// c. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE », «», «»).
auto fields = TRY ( prepare_calendar_fields ( vm , calendar , object , { { CalendarField : : Year , CalendarField : : Month , CalendarField : : MonthCode } } , { } , CalendarFieldList { } ) ) ;
// d. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY ( get_options_object ( vm , options ) ) ;
// e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
auto overflow = TRY ( get_temporal_overflow_option ( vm , resolved_options ) ) ;
// f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
2024-11-22 16:55:18 -05:00
auto iso_date = TRY ( calendar_year_month_from_fields ( vm , calendar , fields , overflow ) ) ;
2024-11-21 11:22:32 -05:00
// g. Return ! CreateTemporalYearMonth(isoDate, calendar).
return MUST ( create_temporal_year_month ( vm , iso_date , move ( calendar ) ) ) ;
}
// 3. If item is not a String, throw a TypeError exception.
if ( ! item . is_string ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : TemporalInvalidPlainYearMonth ) ;
// 4. Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
auto parse_result = TRY ( parse_iso_date_time ( vm , item . as_string ( ) . utf8_string_view ( ) , { { Production : : TemporalYearMonthString } } ) ) ;
// 5. Let calendar be result.[[Calendar]].
// 6. If calendar is empty, set calendar to "iso8601".
auto calendar = parse_result . calendar . value_or ( " iso8601 " _string ) ;
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY ( canonicalize_calendar ( vm , calendar ) ) ;
// 8. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
auto iso_date = create_iso_date_record ( * parse_result . year , parse_result . month , parse_result . day ) ;
// 9. Set result to ISODateToFields(calendar, isoDate, YEAR-MONTH).
auto result = iso_date_to_fields ( calendar , iso_date , DateType : : YearMonth ) ;
// 10. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY ( get_options_object ( vm , options ) ) ;
// 11. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY ( get_temporal_overflow_option ( vm , resolved_options ) ) ;
// 12. NOTE: The following operation is called with CONSTRAIN regardless of the value of overflow, in order for the
// calendar to store a canonical value in the [[Day]] field of the [[ISODate]] internal slot of the result.
// 13. Set isoDate to ? CalendarYearMonthFromFields(calendar, result, CONSTRAIN).
iso_date = TRY ( calendar_year_month_from_fields ( vm , calendar , result , Overflow : : Constrain ) ) ;
// 14. Return ! CreateTemporalYearMonth(isoDate, calendar).
return MUST ( create_temporal_year_month ( vm , iso_date , move ( calendar ) ) ) ;
}
// 9.5.3 ISOYearMonthWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthwithinlimits
bool iso_year_month_within_limits ( ISODate iso_date )
{
// 1. If isoDate.[[Year]] < -271821 or isoDate.[[Year]] > 275760, then
if ( iso_date . year < - 271821 | | iso_date . year > 275760 ) {
// a. Return false.
return false ;
}
// 2. If isoDate.[[Year]] = -271821 and isoDate.[[Month]] < 4, then
if ( iso_date . year = = - 271821 & & iso_date . month < 4 ) {
// a. Return false.
return false ;
}
// 3. If isoDate.[[Year]] = 275760 and isoDate.[[Month]] > 9, then
if ( iso_date . year = = 275760 & & iso_date . month > 9 ) {
// a. Return false.
return false ;
}
// 4. Return true.
return true ;
}
2024-11-21 13:21:29 -05:00
// 9.5.4 BalanceISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
ISOYearMonth balance_iso_year_month ( double year , double month )
{
// 1. Set year to year + floor((month - 1) / 12).
year + = floor ( ( month - 1.0 ) / 12.0 ) ;
// 2. Set month to ((month - 1) modulo 12) + 1.
month = modulo ( month - 1 , 12.0 ) + 1.0 ;
// 3. Return ISO Year-Month Record { [[Year]]: year, [[Month]]: month }.
return { . year = static_cast < i32 > ( year ) , . month = static_cast < u8 > ( month ) } ;
}
2024-11-21 11:22:32 -05:00
// 9.5.5 CreateTemporalYearMonth ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalyearmonth
ThrowCompletionOr < GC : : Ref < PlainYearMonth > > create_temporal_year_month ( VM & vm , ISODate iso_date , String calendar , GC : : Ptr < FunctionObject > new_target )
{
auto & realm = * vm . current_realm ( ) ;
// 1. If ISOYearMonthWithinLimits(isoDate) is false, throw a RangeError exception.
if ( ! iso_year_month_within_limits ( iso_date ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidPlainYearMonth ) ;
// 2. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%.
if ( ! new_target )
new_target = realm . intrinsics ( ) . temporal_plain_year_month_constructor ( ) ;
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainYearMonth.prototype%", « [[InitializedTemporalYearMonth]], [[ISODate]], [[Calendar]] »).
// 4. Set object.[[ISODate]] to isoDate.
// 5. Set object.[[Calendar]] to calendar.
auto object = TRY ( ordinary_create_from_constructor < PlainYearMonth > ( vm , * new_target , & Intrinsics : : temporal_plain_year_month_prototype , iso_date , move ( calendar ) ) ) ;
// 6. Return object.
return object ;
}
2024-11-21 11:39:26 -05:00
// 9.5.6 TemporalYearMonthToString ( yearMonth, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporalyearmonthtostring
String temporal_year_month_to_string ( PlainYearMonth const & year_month , ShowCalendar show_calendar )
{
// 1. Let year be PadISOYear(yearMonth.[[ISODate]].[[Year]]).
auto year = pad_iso_year ( year_month . iso_date ( ) . year ) ;
// 2. Let month be ToZeroPaddedDecimalString(yearMonth.[[ISODate]].[[Month]], 2).
// 3. Let result be the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and month.
auto result = MUST ( String : : formatted ( " {}-{:02} " , year , year_month . iso_date ( ) . month ) ) ;
// 4. If showCalendar is one of always or critical, or if yearMonth.[[Calendar]] is not "iso8601", then
if ( show_calendar = = ShowCalendar : : Always | | show_calendar = = ShowCalendar : : Critical | | year_month . calendar ( ) ! = " iso8601 " sv ) {
// a. Let day be ToZeroPaddedDecimalString(yearMonth.[[ISODate]].[[Day]], 2).
// b. Set result to the string-concatenation of result, the code unit 0x002D (HYPHEN-MINUS), and day.
result = MUST ( String : : formatted ( " {}-{:02} " , result , year_month . iso_date ( ) . day ) ) ;
}
// 5. Let calendarString be FormatCalendarAnnotation(yearMonth.[[Calendar]], showCalendar).
auto calendar_string = format_calendar_annotation ( year_month . calendar ( ) , show_calendar ) ;
// 6. Set result to the string-concatenation of result and calendarString.
result = MUST ( String : : formatted ( " {}{} " , result , calendar_string ) ) ;
// 7. Return result.
return result ;
}
2024-11-21 13:21:29 -05:00
// 9.5.7 DifferenceTemporalPlainYearMonth ( operation, yearMonth, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplainyearmonth
ThrowCompletionOr < GC : : Ref < Duration > > difference_temporal_plain_year_month ( VM & vm , DurationOperation operation , PlainYearMonth const & year_month , Value other_value , Value options )
{
// 1. Set other to ? ToTemporalYearMonth(other).
auto other = TRY ( to_temporal_year_month ( vm , other_value ) ) ;
// 2. Let calendar be yearMonth.[[Calendar]].
auto const & calendar = year_month . calendar ( ) ;
// 3. If CalendarEquals(calendar, other.[[Calendar]]) is false, throw a RangeError exception.
if ( ! calendar_equals ( calendar , other - > calendar ( ) ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalDifferentCalendars ) ;
// 4. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY ( get_options_object ( vm , options ) ) ;
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « WEEK, DAY », MONTH, YEAR).
auto settings = TRY ( get_difference_settings ( vm , operation , resolved_options , UnitGroup : : Date , { { Unit : : Week , Unit : : Day } } , Unit : : Month , Unit : : Year ) ) ;
// 6. If CompareISODate(yearMonth.[[ISODate]], other.[[ISODate]]) = 0, then
if ( compare_iso_date ( year_month . iso_date ( ) , other - > iso_date ( ) ) = = 0 ) {
// a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
return MUST ( create_temporal_duration ( vm , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ) ;
}
// 7. Let thisFields be ISODateToFields(calendar, yearMonth.[[ISODate]], YEAR-MONTH).
auto this_fields = iso_date_to_fields ( calendar , year_month . iso_date ( ) , DateType : : YearMonth ) ;
// 8. Set thisFields.[[Day]] to 1.
this_fields . day = 1 ;
// 9. Let thisDate be ? CalendarDateFromFields(calendar, thisFields, CONSTRAIN).
2024-11-22 16:55:18 -05:00
auto this_date = TRY ( calendar_date_from_fields ( vm , calendar , this_fields , Overflow : : Constrain ) ) ;
2024-11-21 13:21:29 -05:00
// 10. Let otherFields be ISODateToFields(calendar, other.[[ISODate]], YEAR-MONTH).
auto other_fields = iso_date_to_fields ( calendar , other - > iso_date ( ) , DateType : : YearMonth ) ;
// 11. Set otherFields.[[Day]] to 1.
other_fields . day = 1 ;
// 12. Let otherDate be ? CalendarDateFromFields(calendar, otherFields, CONSTRAIN).
2024-11-22 16:55:18 -05:00
auto other_date = TRY ( calendar_date_from_fields ( vm , calendar , other_fields , Overflow : : Constrain ) ) ;
2024-11-21 13:21:29 -05:00
// 13. Let dateDifference be CalendarDateUntil(calendar, thisDate, otherDate, settings.[[LargestUnit]]).
auto date_difference = calendar_date_until ( vm , calendar , this_date , other_date , settings . largest_unit ) ;
// 14. Let yearsMonthsDifference be ! AdjustDateDurationRecord(dateDifference, 0, 0).
auto years_months_difference = MUST ( adjust_date_duration_record ( vm , date_difference , 0 , 0 ) ) ;
// 15. Let duration be ! CombineDateAndTimeDuration(yearsMonthsDifference, 0).
auto duration = MUST ( combine_date_and_time_duration ( vm , years_months_difference , TimeDuration { 0 } ) ) ;
// 16. If settings.[[SmallestUnit]] is not MONTH or settings.[[RoundingIncrement]] ≠1, then
if ( settings . smallest_unit ! = Unit : : Month | | settings . rounding_increment ! = 1 ) {
// a. Let isoDateTime be CombineISODateAndTimeRecord(thisDate, MidnightTimeRecord()).
auto iso_date_time = combine_iso_date_and_time_record ( this_date , midnight_time_record ( ) ) ;
// b. Let isoDateTimeOther be CombineISODateAndTimeRecord(otherDate, MidnightTimeRecord()).
auto iso_date_time_other = combine_iso_date_and_time_record ( other_date , midnight_time_record ( ) ) ;
// c. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
auto dest_epoch_ns = get_utc_epoch_nanoseconds ( iso_date_time_other ) ;
// d. Set duration to ? RoundRelativeDuration(duration, destEpochNs, isoDateTime, UNSET, calendar, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
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 ) ) ;
}
2024-12-05 09:51:55 -05:00
// 17. Let result be ! TemporalDurationFromInternal(duration, DAY).
auto result = MUST ( temporal_duration_from_internal ( vm , duration , Unit : : Day ) ) ;
2024-11-21 13:21:29 -05:00
// 18. If operation is SINCE, set result to CreateNegatedTemporalDuration(result).
if ( operation = = DurationOperation : : Since )
result = create_negated_temporal_duration ( vm , result ) ;
// 19. Return result.
return result ;
}
2024-11-21 19:09:21 -05:00
// 9.5.8 AddDurationToYearMonth ( operation, yearMonth, temporalDurationLike, options )
ThrowCompletionOr < GC : : Ref < PlainYearMonth > > add_duration_to_year_month ( VM & vm , ArithmeticOperation operation , PlainYearMonth const & year_month , Value temporal_duration_like , Value options )
{
// 1. Let duration be ? ToTemporalDuration(temporalDurationLike).
auto duration = TRY ( to_temporal_duration ( vm , temporal_duration_like ) ) ;
// 2. If operation is SUBTRACT, set duration to CreateNegatedTemporalDuration(duration).
if ( operation = = ArithmeticOperation : : Subtract )
duration = create_negated_temporal_duration ( vm , duration ) ;
// 3. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY ( get_options_object ( vm , options ) ) ;
// 4. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
auto overflow = TRY ( get_temporal_overflow_option ( vm , resolved_options ) ) ;
// 5. Let sign be DurationSign(duration).
auto sign = duration_sign ( duration ) ;
// 6. Let calendar be yearMonth.[[Calendar]].
auto const & calendar = year_month . calendar ( ) ;
// 7. Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]], YEAR-MONTH).
auto fields = iso_date_to_fields ( calendar , year_month . iso_date ( ) , DateType : : YearMonth ) ;
// 8. Set fields.[[Day]] to 1.
fields . day = 1 ;
// 9. Let intermediateDate be ? CalendarDateFromFields(calendar, fields, CONSTRAIN).
2024-11-22 16:55:18 -05:00
auto intermediate_date = TRY ( calendar_date_from_fields ( vm , calendar , fields , Overflow : : Constrain ) ) ;
2024-11-21 19:09:21 -05:00
ISODate date ;
// 10. If sign < 0, then
if ( sign < 0 ) {
// a. Let oneMonthDuration be ! CreateDateDurationRecord(0, 1, 0, 0).
auto one_month_duration = MUST ( create_date_duration_record ( vm , 0 , 1 , 0 , 0 ) ) ;
// b. Let nextMonth be ? CalendarDateAdd(calendar, intermediateDate, oneMonthDuration, CONSTRAIN).
auto next_month = TRY ( calendar_date_add ( vm , calendar , intermediate_date , one_month_duration , Overflow : : Constrain ) ) ;
// c. Let date be BalanceISODate(nextMonth.[[Year]], nextMonth.[[Month]], nextMonth.[[Day]] - 1).
date = balance_iso_date ( next_month . year , next_month . month , next_month . day - 1 ) ;
// d. Assert: ISODateWithinLimits(date) is true.
VERIFY ( iso_date_within_limits ( date ) ) ;
}
// 11. Else,
else {
// a. Let date be intermediateDate.
date = intermediate_date ;
}
2024-12-05 09:53:52 -05:00
// 12. Let durationToAdd be ToDateDurationRecordWithoutTime(duration).
auto duration_to_add = to_date_duration_record_without_time ( vm , duration ) ;
2024-11-21 19:09:21 -05:00
// 13. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, overflow).
auto added_date = TRY ( calendar_date_add ( vm , calendar , date , duration_to_add , overflow ) ) ;
// 14. Let addedDateFields be ISODateToFields(calendar, addedDate, YEAR-MONTH).
auto added_date_fields = iso_date_to_fields ( calendar , added_date , DateType : : YearMonth ) ;
// 15. Let isoDate be ? CalendarYearMonthFromFields(calendar, addedDateFields, overflow).
2024-11-22 16:55:18 -05:00
auto iso_date = TRY ( calendar_year_month_from_fields ( vm , calendar , added_date_fields , overflow ) ) ;
2024-11-21 19:09:21 -05:00
// 16. Return ! CreateTemporalYearMonth(isoDate, calendar).
return MUST ( create_temporal_year_month ( vm , iso_date , calendar ) ) ;
}
2024-11-21 11:22:32 -05:00
}