LibJS: Implement Temporal.PlainYearMonth.prototype.until/since
This commit is contained in:
parent
203269fae2
commit
cb5d1b5086
Notes:
github-actions[bot]
2024-11-22 18:56:30 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/cb5d1b5086d Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2496
26 changed files with 1586 additions and 1 deletions
Libraries/LibJS
Forward.h
Runtime
ErrorTypes.h
Temporal
AbstractOperations.cppAbstractOperations.hCalendar.cppCalendar.hDateEquations.cppDateEquations.hDuration.cppDuration.hInstant.cppInstant.hPlainDate.cppPlainDate.hPlainDateTime.cppPlainDateTime.hPlainTime.cppPlainTime.hPlainYearMonth.cppPlainYearMonth.hPlainYearMonthPrototype.cppPlainYearMonthPrototype.hTimeZone.cppTimeZone.h
Tests/builtins/Temporal/PlainYearMonth
|
@ -286,6 +286,7 @@ struct DateDuration;
|
|||
struct InternalDuration;
|
||||
struct ISODate;
|
||||
struct ISODateTime;
|
||||
struct ISOYearMonth;
|
||||
struct ParseResult;
|
||||
struct PartialDuration;
|
||||
struct Time;
|
||||
|
|
|
@ -238,6 +238,8 @@
|
|||
M(TemporalAmbiguousMonthOfPlainMonthDay, "Accessing month of PlainMonthDay is ambiguous, use monthCode instead") \
|
||||
M(TemporalDifferentCalendars, "Cannot compare dates from two different calendars") \
|
||||
M(TemporalDifferentTimeZones, "Cannot compare dates from two different time zones") \
|
||||
M(TemporalDisambiguatePossibleEpochNSRejectMoreThanOne, "Cannot disambiguate two or more possible epoch nanoseconds") \
|
||||
M(TemporalDisambiguatePossibleEpochNSRejectZero, "Cannot disambiguate zero possible epoch nanoseconds") \
|
||||
M(TemporalDisambiguatePossibleInstantsEarlierZero, "Cannot disambiguate zero possible instants with mode \"earlier\"") \
|
||||
M(TemporalDisambiguatePossibleInstantsRejectMoreThanOne, "Cannot disambiguate two or more possible instants with mode \"reject\"") \
|
||||
M(TemporalDisambiguatePossibleInstantsRejectZero, "Cannot disambiguate zero possible instants with mode \"reject\"") \
|
||||
|
|
|
@ -72,6 +72,19 @@ double epoch_days_to_epoch_ms(double day, double time)
|
|||
return day * JS::ms_per_day + time;
|
||||
}
|
||||
|
||||
// 13.4 CheckISODaysRange ( isoDate ), https://tc39.es/proposal-temporal/#sec-checkisodaysrange
|
||||
ThrowCompletionOr<void> check_iso_days_range(VM& vm, ISODate const& iso_date)
|
||||
{
|
||||
// 1. If abs(ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]])) > 10**8, then
|
||||
if (fabs(iso_date_to_epoch_days(iso_date.year, iso_date.month - 1, iso_date.day)) > 100'000'000) {
|
||||
// a. Throw a RangeError exception.
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
|
||||
}
|
||||
|
||||
// 2. Return unused.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 13.6 GetTemporalOverflowOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloverflowoption
|
||||
ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM& vm, Object const& options)
|
||||
{
|
||||
|
@ -86,6 +99,29 @@ ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM& vm, Object const& o
|
|||
return Overflow::Reject;
|
||||
}
|
||||
|
||||
// 13.8 NegateRoundingMode ( roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-negateroundingmode
|
||||
RoundingMode negate_rounding_mode(RoundingMode rounding_mode)
|
||||
{
|
||||
// 1. If roundingMode is CEIL, return FLOOR.
|
||||
if (rounding_mode == RoundingMode::Ceil)
|
||||
return RoundingMode::Floor;
|
||||
|
||||
// 2. If roundingMode is FLOOR, return CEIL.
|
||||
if (rounding_mode == RoundingMode::Floor)
|
||||
return RoundingMode::Ceil;
|
||||
|
||||
// 3. If roundingMode is HALF-CEIL, return HALF-FLOOR.
|
||||
if (rounding_mode == RoundingMode::HalfCeil)
|
||||
return RoundingMode::HalfFloor;
|
||||
|
||||
// 4. If roundingMode is HALF-FLOOR, return HALF-CEIL.
|
||||
if (rounding_mode == RoundingMode::HalfFloor)
|
||||
return RoundingMode::HalfCeil;
|
||||
|
||||
// 5. Return roundingMode.
|
||||
return rounding_mode;
|
||||
}
|
||||
|
||||
// 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption
|
||||
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, Object const& options)
|
||||
{
|
||||
|
@ -1403,6 +1439,61 @@ CalendarFields iso_date_to_fields(StringView calendar, ISODate const& iso_date,
|
|||
return fields;
|
||||
}
|
||||
|
||||
// 13.43 GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit ), https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings
|
||||
ThrowCompletionOr<DifferenceSettings> get_difference_settings(VM& vm, DurationOperation operation, Object const& options, UnitGroup unit_group, ReadonlySpan<Unit> disallowed_units, Unit fallback_smallest_unit, Unit smallest_largest_default_unit)
|
||||
{
|
||||
// 1. NOTE: The following steps read options and perform independent validation in alphabetical order.
|
||||
|
||||
// 2. Let largestUnit be ? GetTemporalUnitValuedOption(options, "largestUnit", unitGroup, AUTO).
|
||||
auto largest_unit = TRY(get_temporal_unit_valued_option(vm, options, vm.names.largestUnit, unit_group, Auto {}));
|
||||
|
||||
// 3. If disallowedUnits contains largestUnit, throw a RangeError exception.
|
||||
if (auto* unit = largest_unit.get_pointer<Unit>(); unit && disallowed_units.contains_slow(*unit))
|
||||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, temporal_unit_to_string(*unit), vm.names.largestUnit);
|
||||
|
||||
// 4. Let roundingIncrement be ? GetRoundingIncrementOption(options).
|
||||
auto rounding_increment = TRY(get_rounding_increment_option(vm, options));
|
||||
|
||||
// 5. Let roundingMode be ? GetRoundingModeOption(options, TRUNC).
|
||||
auto rounding_mode = TRY(get_rounding_mode_option(vm, options, RoundingMode::Trunc));
|
||||
|
||||
// 6. If operation is SINCE, then
|
||||
if (operation == DurationOperation::Since) {
|
||||
// a. Set roundingMode to NegateRoundingMode(roundingMode).
|
||||
rounding_mode = negate_rounding_mode(rounding_mode);
|
||||
}
|
||||
|
||||
// 7. Let smallestUnit be ? GetTemporalUnitValuedOption(options, "smallestUnit", unitGroup, fallbackSmallestUnit).
|
||||
auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, options, vm.names.smallestUnit, unit_group, fallback_smallest_unit));
|
||||
auto smallest_unit_value = smallest_unit.get<Unit>();
|
||||
|
||||
// 8. If disallowedUnits contains smallestUnit, throw a RangeError exception.
|
||||
if (disallowed_units.contains_slow(smallest_unit_value))
|
||||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, temporal_unit_to_string(smallest_unit_value), vm.names.smallestUnit);
|
||||
|
||||
// 9. Let defaultLargestUnit be LargerOfTwoTemporalUnits(smallestLargestDefaultUnit, smallestUnit).
|
||||
auto default_largest_unit = larger_of_two_temporal_units(smallest_largest_default_unit, smallest_unit.get<Unit>());
|
||||
|
||||
// 10. If largestUnit is AUTO, set largestUnit to defaultLargestUnit.
|
||||
if (largest_unit.has<Auto>())
|
||||
largest_unit = default_largest_unit;
|
||||
auto largest_unit_value = largest_unit.get<Unit>();
|
||||
|
||||
// 11. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception.
|
||||
if (larger_of_two_temporal_units(largest_unit_value, smallest_unit_value) != largest_unit_value)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidUnitRange, temporal_unit_to_string(smallest_unit_value), temporal_unit_to_string(largest_unit_value));
|
||||
|
||||
// 12. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit).
|
||||
auto maximum = maximum_temporal_duration_rounding_increment(smallest_unit_value);
|
||||
|
||||
// 13. If maximum is not UNSET, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
|
||||
if (!maximum.has<Unset>())
|
||||
TRY(validate_temporal_rounding_increment(vm, rounding_increment, maximum.get<u64>(), false));
|
||||
|
||||
// 14. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, }.
|
||||
return DifferenceSettings { .smallest_unit = smallest_unit_value, .largest_unit = largest_unit_value, .rounding_mode = rounding_mode, .rounding_increment = rounding_increment };
|
||||
}
|
||||
|
||||
// 14.4.1.1 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
|
||||
ThrowCompletionOr<GC::Ref<Object>> get_options_object(VM& vm, Value options)
|
||||
{
|
||||
|
|
|
@ -33,6 +33,11 @@ enum class DateType {
|
|||
YearMonth,
|
||||
};
|
||||
|
||||
enum class DurationOperation {
|
||||
Since,
|
||||
Until,
|
||||
};
|
||||
|
||||
enum class Overflow {
|
||||
Constrain,
|
||||
Reject,
|
||||
|
@ -148,9 +153,18 @@ struct ParsedISODateTime {
|
|||
Optional<String> calendar;
|
||||
};
|
||||
|
||||
struct DifferenceSettings {
|
||||
Unit smallest_unit;
|
||||
Unit largest_unit;
|
||||
RoundingMode rounding_mode;
|
||||
u64 rounding_increment { 0 };
|
||||
};
|
||||
|
||||
double iso_date_to_epoch_days(double year, double month, double date);
|
||||
double epoch_days_to_epoch_ms(double day, double time);
|
||||
ThrowCompletionOr<void> check_iso_days_range(VM&, ISODate const&);
|
||||
ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& options);
|
||||
RoundingMode negate_rounding_mode(RoundingMode);
|
||||
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options);
|
||||
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
|
||||
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
|
||||
|
@ -177,6 +191,7 @@ ThrowCompletionOr<TimeZone> parse_temporal_time_zone_string(VM& vm, StringView t
|
|||
ThrowCompletionOr<String> to_month_code(VM&, Value argument);
|
||||
ThrowCompletionOr<String> to_offset_string(VM&, Value argument);
|
||||
CalendarFields iso_date_to_fields(StringView calendar, ISODate const&, DateType);
|
||||
ThrowCompletionOr<DifferenceSettings> get_difference_settings(VM&, DurationOperation, Object const& options, UnitGroup, ReadonlySpan<Unit> disallowed_units, Unit fallback_smallest_unit, Unit smallest_largest_default_unit);
|
||||
|
||||
// 13.38 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation
|
||||
template<typename... Args>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/QuickSort.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/DateEquations.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
|
@ -311,6 +312,154 @@ CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const&
|
|||
return merged;
|
||||
}
|
||||
|
||||
// 12.2.6 CalendarDateAdd ( calendar, isoDate, duration, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd
|
||||
ThrowCompletionOr<ISODate> calendar_date_add(VM& vm, StringView calendar, ISODate const& iso_date, DateDuration const& duration, Overflow overflow)
|
||||
{
|
||||
ISODate result;
|
||||
|
||||
// 1. If calendar is "iso8601", then
|
||||
// FIXME: Return an ISODate for an ISO8601 calendar for now.
|
||||
if (true || calendar == "iso8601"sv) {
|
||||
// a. Let intermediate be BalanceISOYearMonth(isoDate.[[Year]] + duration.[[Years]], isoDate.[[Month]] + duration.[[Months]]).
|
||||
auto intermediate = balance_iso_year_month(static_cast<double>(iso_date.year) + duration.years, static_cast<double>(iso_date.month) + duration.months);
|
||||
|
||||
// b. Set intermediate to ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], isoDate.[[Day]], overflow).
|
||||
auto intermediate_date = TRY(regulate_iso_date(vm, intermediate.year, intermediate.month, iso_date.day, overflow));
|
||||
|
||||
// c. Let d be intermediate.[[Day]] + duration.[[Days]] + 7 × duration.[[Weeks]].
|
||||
auto day = intermediate_date.day + duration.days + (7 * duration.weeks);
|
||||
|
||||
// d. Let result be BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d).
|
||||
result = balance_iso_date(intermediate_date.year, intermediate_date.month, day);
|
||||
}
|
||||
// 2. Else,
|
||||
else {
|
||||
// a. Let result be an implementation-defined ISO Date Record, or throw a RangeError exception, as described below.
|
||||
}
|
||||
|
||||
// 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
|
||||
if (!iso_date_within_limits(result))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
|
||||
|
||||
// 4. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 12.2.7 CalendarDateUntil ( calendar, one, two, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil
|
||||
DateDuration calendar_date_until(VM& vm, StringView calendar, ISODate const& one, ISODate const& two, Unit largest_unit)
|
||||
{
|
||||
// 1. If calendar is "iso8601", then
|
||||
if (calendar == "iso8601"sv) {
|
||||
// a. Let sign be -CompareISODate(one, two).
|
||||
auto sign = compare_iso_date(one, two);
|
||||
sign *= -1;
|
||||
|
||||
// b. If sign = 0, return ZeroDateDuration().
|
||||
if (sign == 0)
|
||||
return zero_date_duration(vm);
|
||||
|
||||
// c. Let years be 0.
|
||||
double years = 0;
|
||||
|
||||
// d. If largestUnit is YEAR, then
|
||||
if (largest_unit == Unit::Year) {
|
||||
// i. Let candidateYears be sign.
|
||||
double candidate_years = sign;
|
||||
|
||||
// ii. Repeat, while ISODateSurpasses(sign, one.[[Year]] + candidateYears, one.[[Month]], one.[[Day]], two) is false,
|
||||
while (!iso_date_surpasses(sign, static_cast<double>(one.year) + candidate_years, one.month, one.day, two)) {
|
||||
// 1. Set years to candidateYears.
|
||||
years = candidate_years;
|
||||
|
||||
// 2. Set candidateYears to candidateYears + sign.
|
||||
candidate_years += sign;
|
||||
}
|
||||
}
|
||||
|
||||
// e. Let months be 0.
|
||||
double months = 0;
|
||||
|
||||
// f. If largestUnit is YEAR or largestUnit is MONTH, then
|
||||
if (largest_unit == Unit::Year || largest_unit == Unit::Month) {
|
||||
// i. Let candidateMonths be sign.
|
||||
double candidate_months = sign;
|
||||
|
||||
// ii. Let intermediate be BalanceISOYearMonth(one.[[Year]] + years, one.[[Month]] + candidateMonths).
|
||||
auto intermediate = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + candidate_months);
|
||||
|
||||
// iii. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], one.[[Day]], two) is false,
|
||||
while (!iso_date_surpasses(sign, intermediate.year, intermediate.month, one.day, two)) {
|
||||
// 1. Set months to candidateMonths.
|
||||
months = candidate_months;
|
||||
|
||||
// 2. Set candidateMonths to candidateMonths + sign.
|
||||
candidate_months += sign;
|
||||
|
||||
// 3. Set intermediate to BalanceISOYearMonth(intermediate.[[Year]], intermediate.[[Month]] + sign).
|
||||
intermediate = balance_iso_year_month(intermediate.year, static_cast<double>(intermediate.month) + sign);
|
||||
}
|
||||
}
|
||||
|
||||
// g. Set intermediate to BalanceISOYearMonth(one.[[Year]] + years, one.[[Month]] + months).
|
||||
auto intermediate = balance_iso_year_month(static_cast<double>(one.year) + years, static_cast<double>(one.month) + months);
|
||||
|
||||
// h. Let constrained be ! RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], one.[[Day]], CONSTRAIN).
|
||||
auto constrained = MUST(regulate_iso_date(vm, intermediate.year, intermediate.month, one.day, Overflow::Constrain));
|
||||
|
||||
// i. Let weeks be 0.
|
||||
double weeks = 0;
|
||||
|
||||
// j. If largestUnit is WEEK, then
|
||||
if (largest_unit == Unit::Week) {
|
||||
// i. Let candidateWeeks be sign.
|
||||
double candidate_weeks = sign;
|
||||
|
||||
// ii. Set intermediate to BalanceISODate(constrained.[[Year]], constrained.[[Month]], constrained.[[Day]] + 7 × candidateWeeks).
|
||||
auto intermediate = balance_iso_date(constrained.year, constrained.month, static_cast<double>(constrained.day) + (7.0 * candidate_weeks));
|
||||
|
||||
// iii. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]], two) is false,
|
||||
while (!iso_date_surpasses(sign, intermediate.year, intermediate.month, intermediate.day, two)) {
|
||||
// 1. Set weeks to candidateWeeks.
|
||||
weeks = candidate_weeks;
|
||||
|
||||
// 2. Set candidateWeeks to candidateWeeks + sign.
|
||||
candidate_weeks += sign;
|
||||
|
||||
// 3. Set intermediate to BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]] + 7 × sign).
|
||||
intermediate = balance_iso_date(intermediate.year, intermediate.month, static_cast<double>(intermediate.day) + (7.0 * sign));
|
||||
}
|
||||
}
|
||||
|
||||
// k. Let days be 0.
|
||||
double days = 0;
|
||||
|
||||
// l. Let candidateDays be sign.
|
||||
double candidate_days = sign;
|
||||
|
||||
// m. Set intermediate to BalanceISODate(constrained.[[Year]], constrained.[[Month]], constrained.[[Day]] + 7 × weeks + candidateDays).
|
||||
auto intermediate_date = balance_iso_date(constrained.year, constrained.month, static_cast<double>(constrained.day) + (7.0 * weeks) + candidate_days);
|
||||
|
||||
// n. Repeat, while ISODateSurpasses(sign, intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]], two) is false,
|
||||
while (!iso_date_surpasses(sign, intermediate_date.year, intermediate_date.month, intermediate_date.day, two)) {
|
||||
// i. Set days to candidateDays.
|
||||
days = candidate_days;
|
||||
|
||||
// ii. Set candidateDays to candidateDays + sign.
|
||||
candidate_days += sign;
|
||||
|
||||
// iii. Set intermediate to BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], intermediate.[[Day]] + sign).
|
||||
intermediate_date = balance_iso_date(intermediate_date.year, intermediate_date.month, static_cast<double>(intermediate_date.day) + sign);
|
||||
}
|
||||
|
||||
// o. Return ! CreateDateDurationRecord(years, months, weeks, days).
|
||||
return MUST(create_date_duration_record(vm, years, months, weeks, days));
|
||||
}
|
||||
|
||||
// 2. Return an implementation-defined Date Duration Record as described above.
|
||||
// FIXME: Return a DateDuration for an ISO8601 calendar for now.
|
||||
return calendar_date_until(vm, "iso8601"sv, one, two, largest_unit);
|
||||
}
|
||||
|
||||
// 12.2.8 ToTemporalCalendarIdentifier ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier
|
||||
ThrowCompletionOr<String> to_temporal_calendar_identifier(VM& vm, Value temporal_calendar_like)
|
||||
{
|
||||
|
@ -365,6 +514,23 @@ ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&
|
|||
return TRY(to_temporal_calendar_identifier(vm, calendar_like));
|
||||
}
|
||||
|
||||
// 12.2.10 CalendarDateFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatefromfields
|
||||
ThrowCompletionOr<ISODate> calendar_date_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
|
||||
{
|
||||
// 1. Perform ? CalendarResolveFields(calendar, fields, DATE).
|
||||
TRY(calendar_resolve_fields(vm, calendar, fields, DateType::Date));
|
||||
|
||||
// 2. Let result be ? CalendarDateToISO(calendar, fields, overflow).
|
||||
auto result = TRY(calendar_date_to_iso(vm, calendar, fields, overflow));
|
||||
|
||||
// 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
|
||||
if (!iso_date_within_limits(result))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
|
||||
|
||||
// 4. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 12.2.11 CalendarYearMonthFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields
|
||||
ThrowCompletionOr<ISODate> calendar_year_month_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
|
||||
{
|
||||
|
|
|
@ -99,6 +99,7 @@ using CalendarFieldListOrPartial = Variant<Partial, CalendarFieldList>;
|
|||
ThrowCompletionOr<String> canonicalize_calendar(VM&, StringView id);
|
||||
Vector<String> const& available_calendars();
|
||||
ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
|
||||
ThrowCompletionOr<ISODate> calendar_date_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
|
||||
ThrowCompletionOr<ISODate> calendar_year_month_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
|
||||
ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
|
||||
String format_calendar_annotation(StringView id, ShowCalendar);
|
||||
|
@ -109,6 +110,8 @@ u16 iso_day_of_year(ISODate const&);
|
|||
u8 iso_day_of_week(ISODate const&);
|
||||
Vector<CalendarField> calendar_field_keys_present(CalendarFields const&);
|
||||
CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields);
|
||||
ThrowCompletionOr<ISODate> calendar_date_add(VM&, StringView calendar, ISODate const&, DateDuration const&, Overflow);
|
||||
DateDuration calendar_date_until(VM&, StringView calendar, ISODate const&, ISODate const&, Unit largest_unit);
|
||||
ThrowCompletionOr<String> to_temporal_calendar_identifier(VM&, Value temporal_calendar_like);
|
||||
ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&, Object const& item);
|
||||
ThrowCompletionOr<ISODate> calendar_date_to_iso(VM&, StringView calendar, CalendarFields const&, Overflow);
|
||||
|
|
|
@ -79,6 +79,52 @@ u16 epoch_time_to_day_in_year(double time)
|
|||
return static_cast<u16>(epoch_time_to_day_number(time) - epoch_day_number_for_year(epoch_time_to_epoch_year(time)));
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimetomonthinyear
|
||||
u8 epoch_time_to_month_in_year(double time)
|
||||
{
|
||||
auto day_in_year = epoch_time_to_day_in_year(time);
|
||||
auto in_leap_year = mathematical_in_leap_year(time);
|
||||
|
||||
// EpochTimeToMonthInYear(t)
|
||||
// = 0 if 0 ≤ EpochTimeToDayInYear(t) < 31
|
||||
// = 1 if 31 ≤ EpochTimeToDayInYear(t) < 59 + MathematicalInLeapYear(t)
|
||||
// = 2 if 59 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 90 + MathematicalInLeapYear(t)
|
||||
// = 3 if 90 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 120 + MathematicalInLeapYear(t)
|
||||
// = 4 if 120 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 151 + MathematicalInLeapYear(t)
|
||||
// = 5 if 151 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 181 + MathematicalInLeapYear(t)
|
||||
// = 6 if 181 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 212 + MathematicalInLeapYear(t)
|
||||
// = 7 if 212 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 243 + MathematicalInLeapYear(t)
|
||||
// = 8 if 243 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 273 + MathematicalInLeapYear(t)
|
||||
// = 9 if 273 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 304 + MathematicalInLeapYear(t)
|
||||
// = 10 if 304 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 334 + MathematicalInLeapYear(t)
|
||||
// = 11 if 334 + MathematicalInLeapYear(t) ≤ EpochTimeToDayInYear(t) < 365 + MathematicalInLeapYear(t)
|
||||
if (day_in_year < 31)
|
||||
return 0;
|
||||
if (day_in_year >= 31 && day_in_year < 59 + in_leap_year)
|
||||
return 1;
|
||||
if (day_in_year >= 59 + in_leap_year && day_in_year < 90 + in_leap_year)
|
||||
return 2;
|
||||
if (day_in_year >= 90 + in_leap_year && day_in_year < 120 + in_leap_year)
|
||||
return 3;
|
||||
if (day_in_year >= 120 + in_leap_year && day_in_year < 151 + in_leap_year)
|
||||
return 4;
|
||||
if (day_in_year >= 151 + in_leap_year && day_in_year < 181 + in_leap_year)
|
||||
return 5;
|
||||
if (day_in_year >= 181 + in_leap_year && day_in_year < 212 + in_leap_year)
|
||||
return 6;
|
||||
if (day_in_year >= 212 + in_leap_year && day_in_year < 243 + in_leap_year)
|
||||
return 7;
|
||||
if (day_in_year >= 243 + in_leap_year && day_in_year < 273 + in_leap_year)
|
||||
return 8;
|
||||
if (day_in_year >= 273 + in_leap_year && day_in_year < 304 + in_leap_year)
|
||||
return 9;
|
||||
if (day_in_year >= 304 + in_leap_year && day_in_year < 334 + in_leap_year)
|
||||
return 10;
|
||||
if (day_in_year >= 334 + in_leap_year && day_in_year < 365 + in_leap_year)
|
||||
return 11;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimetoweekday
|
||||
u8 epoch_time_to_week_day(double time)
|
||||
{
|
||||
|
@ -86,4 +132,51 @@ u8 epoch_time_to_week_day(double time)
|
|||
return static_cast<u8>(modulo(epoch_time_to_day_number(time) + 4, 7.0));
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimetodate
|
||||
u8 epoch_time_to_date(double time)
|
||||
{
|
||||
auto day_in_year = epoch_time_to_day_in_year(time);
|
||||
auto month_in_year = epoch_time_to_month_in_year(time);
|
||||
auto in_leap_year = mathematical_in_leap_year(time);
|
||||
|
||||
// EpochTimeToDate(t)
|
||||
// = EpochTimeToDayInYear(t) + 1 if EpochTimeToMonthInYear(t) = 0
|
||||
// = EpochTimeToDayInYear(t) - 30 if EpochTimeToMonthInYear(t) = 1
|
||||
// = EpochTimeToDayInYear(t) - 58 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 2
|
||||
// = EpochTimeToDayInYear(t) - 89 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 3
|
||||
// = EpochTimeToDayInYear(t) - 119 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 4
|
||||
// = EpochTimeToDayInYear(t) - 150 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 5
|
||||
// = EpochTimeToDayInYear(t) - 180 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 6
|
||||
// = EpochTimeToDayInYear(t) - 211 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 7
|
||||
// = EpochTimeToDayInYear(t) - 242 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 8
|
||||
// = EpochTimeToDayInYear(t) - 272 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 9
|
||||
// = EpochTimeToDayInYear(t) - 303 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 10
|
||||
// = EpochTimeToDayInYear(t) - 333 - MathematicalInLeapYear(t) if EpochTimeToMonthInYear(t) = 11
|
||||
if (month_in_year == 0)
|
||||
return day_in_year + 1;
|
||||
if (month_in_year == 1)
|
||||
return day_in_year - 30;
|
||||
if (month_in_year == 2)
|
||||
return day_in_year - 58 - in_leap_year;
|
||||
if (month_in_year == 3)
|
||||
return day_in_year - 89 - in_leap_year;
|
||||
if (month_in_year == 4)
|
||||
return day_in_year - 119 - in_leap_year;
|
||||
if (month_in_year == 5)
|
||||
return day_in_year - 150 - in_leap_year;
|
||||
if (month_in_year == 6)
|
||||
return day_in_year - 180 - in_leap_year;
|
||||
if (month_in_year == 7)
|
||||
return day_in_year - 211 - in_leap_year;
|
||||
if (month_in_year == 8)
|
||||
return day_in_year - 242 - in_leap_year;
|
||||
if (month_in_year == 9)
|
||||
return day_in_year - 272 - in_leap_year;
|
||||
if (month_in_year == 10)
|
||||
return day_in_year - 303 - in_leap_year;
|
||||
if (month_in_year == 11)
|
||||
return day_in_year - 333 - in_leap_year;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ double epoch_day_number_for_year(double year);
|
|||
double epoch_time_for_year(double year);
|
||||
i32 epoch_time_to_epoch_year(double time);
|
||||
u16 epoch_time_to_day_in_year(double time);
|
||||
u8 epoch_time_to_month_in_year(double time);
|
||||
u8 epoch_time_to_week_day(double time);
|
||||
u8 epoch_time_to_date(double time);
|
||||
|
||||
}
|
||||
|
|
|
@ -9,13 +9,16 @@
|
|||
|
||||
#include <AK/Math.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/Intrinsics.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <math.h>
|
||||
|
@ -294,6 +297,21 @@ ThrowCompletionOr<DateDuration> create_date_duration_record(VM& vm, double years
|
|||
return DateDuration { years, months, weeks, days };
|
||||
}
|
||||
|
||||
// 7.5.10 AdjustDateDurationRecord ( dateDuration, days [ , weeks [ , months ] ] ), https://tc39.es/proposal-temporal/#sec-temporal-adjustdatedurationrecord
|
||||
ThrowCompletionOr<DateDuration> adjust_date_duration_record(VM& vm, DateDuration const& date_duration, double days, Optional<double> weeks, Optional<double> months)
|
||||
{
|
||||
// 1. If weeks is not present, set weeks to dateDuration.[[Weeks]].
|
||||
if (!weeks.has_value())
|
||||
weeks = date_duration.weeks;
|
||||
|
||||
// 2. If months is not present, set months to dateDuration.[[Months]].
|
||||
if (!months.has_value())
|
||||
months = date_duration.months;
|
||||
|
||||
// 3. Return ? CreateDateDurationRecord(dateDuration.[[Years]], months, weeks, days).
|
||||
return TRY(create_date_duration_record(vm, date_duration.years, *months, *weeks, days));
|
||||
}
|
||||
|
||||
// 7.5.11 CombineDateAndTimeDuration ( dateDuration, timeDuration ), https://tc39.es/proposal-temporal/#sec-temporal-combinedateandtimeduration
|
||||
ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM& vm, DateDuration date_duration, TimeDuration time_duration)
|
||||
{
|
||||
|
@ -418,6 +436,20 @@ i8 date_duration_sign(DateDuration const& date_duration)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// 7.5.15 InternalDurationSign ( internalDuration ), https://tc39.es/proposal-temporal/#sec-temporal-internaldurationsign
|
||||
i8 internal_duration_sign(InternalDuration const& internal_duration)
|
||||
{
|
||||
// 1. Let dateSign be DateDurationSign(internalDuration.[[Date]]).
|
||||
auto date_sign = date_duration_sign(internal_duration.date);
|
||||
|
||||
// 2. If dateSign ≠ 0, return dateSign.
|
||||
if (date_sign != 0)
|
||||
return date_sign;
|
||||
|
||||
// 3. Return TimeDurationSign(internalDuration.[[Time]]).
|
||||
return time_duration_sign(internal_duration.time);
|
||||
}
|
||||
|
||||
// 7.5.16 IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration
|
||||
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
|
||||
{
|
||||
|
@ -687,6 +719,13 @@ ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM& vm, TimeDu
|
|||
return result;
|
||||
}
|
||||
|
||||
// 7.5.24 AddTimeDurationToEpochNanoseconds ( d, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-addtimedurationtoepochnanoseconds
|
||||
TimeDuration add_time_duration_to_epoch_nanoseconds(TimeDuration const& duration, TimeDuration const& epoch_nanoseconds)
|
||||
{
|
||||
// 1. Return epochNs + ℤ(d).
|
||||
return epoch_nanoseconds.plus(duration);
|
||||
}
|
||||
|
||||
// 7.5.25 CompareTimeDuration ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-comparetimeduration
|
||||
i8 compare_time_duration(TimeDuration const& one, TimeDuration const& two)
|
||||
{
|
||||
|
@ -702,6 +741,19 @@ i8 compare_time_duration(TimeDuration const& one, TimeDuration const& two)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// 7.5.26 TimeDurationFromEpochNanosecondsDifference ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromepochnanosecondsdifference
|
||||
TimeDuration time_duration_from_epoch_nanoseconds_difference(TimeDuration const& one, TimeDuration const& two)
|
||||
{
|
||||
// 1. Let result be ℝ(one) - ℝ(two).
|
||||
auto result = one.minus(two);
|
||||
|
||||
// 2. Assert: abs(result) ≤ maxTimeDuration.
|
||||
VERIFY(result.unsigned_value() <= MAX_TIME_DURATION.unsigned_value());
|
||||
|
||||
// 3. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 7.5.27 RoundTimeDurationToIncrement ( d, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtimedurationtoincrement
|
||||
ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM& vm, TimeDuration const& duration, Crypto::UnsignedBigInteger const& increment, RoundingMode rounding_mode)
|
||||
{
|
||||
|
@ -756,6 +808,532 @@ double total_time_duration(TimeDuration const& time_duration, Unit unit)
|
|||
return result.to_double();
|
||||
}
|
||||
|
||||
// 7.5.33 NudgeToCalendarUnit ( sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetocalendarunit
|
||||
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM& vm, i8 sign, InternalDuration const& duration, TimeDuration const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
|
||||
{
|
||||
DateDuration start_duration;
|
||||
DateDuration end_duration;
|
||||
|
||||
double r1 = 0;
|
||||
double r2 = 0;
|
||||
|
||||
// 1. If unit is YEAR, then
|
||||
if (unit == Unit::Year) {
|
||||
// a. Let years be RoundNumberToIncrement(duration.[[Date]].[[Years]], increment, TRUNC).
|
||||
auto years = round_number_to_increment(duration.date.years, increment, RoundingMode::Trunc);
|
||||
|
||||
// b. Let r1 be years.
|
||||
r1 = years;
|
||||
|
||||
// c. Let r2 be years + increment × sign.
|
||||
r2 = years + static_cast<double>(increment) * sign;
|
||||
|
||||
// d. Let startDuration be ? CreateDateDurationRecord(r1, 0, 0, 0).
|
||||
start_duration = TRY(create_date_duration_record(vm, r1, 0, 0, 0));
|
||||
|
||||
// e. Let endDuration be ? CreateDateDurationRecord(r2, 0, 0, 0).
|
||||
end_duration = TRY(create_date_duration_record(vm, r2, 0, 0, 0));
|
||||
}
|
||||
// 2. Else if unit is MONTH, then
|
||||
else if (unit == Unit::Month) {
|
||||
// a. Let months be RoundNumberToIncrement(duration.[[Date]].[[Months]], increment, TRUNC).
|
||||
auto months = round_number_to_increment(duration.date.months, increment, RoundingMode::Trunc);
|
||||
|
||||
// b. Let r1 be months.
|
||||
r1 = months;
|
||||
|
||||
// c. Let r2 be months + increment × sign.
|
||||
r2 = months + static_cast<double>(increment) * sign;
|
||||
|
||||
// d. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r1).
|
||||
start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r1));
|
||||
|
||||
// e. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, r2).
|
||||
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, r2));
|
||||
}
|
||||
// 3. Else if unit is WEEK, then
|
||||
else if (unit == Unit::Week) {
|
||||
// a. Let yearsMonths be ! AdjustDateDurationRecord(duration.[[Date]], 0, 0).
|
||||
auto years_months = MUST(adjust_date_duration_record(vm, duration.date, 0, 0));
|
||||
|
||||
// b. Let weeksStart be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], yearsMonths, CONSTRAIN).
|
||||
auto weeks_start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, years_months, Overflow::Constrain));
|
||||
|
||||
// c. Let weeksEnd be BalanceISODate(weeksStart.[[Year]], weeksStart.[[Month]], weeksStart.[[Day]] + duration.[[Date]].[[Days]]).
|
||||
auto weeks_end = balance_iso_date(weeks_start.year, weeks_start.month, static_cast<double>(weeks_start.day) + duration.date.days);
|
||||
|
||||
// d. Let untilResult be CalendarDateUntil(calendar, weeksStart, weeksEnd, WEEK).
|
||||
auto until_result = calendar_date_until(vm, calendar, weeks_start, weeks_end, Unit::Week);
|
||||
|
||||
// e. Let weeks be RoundNumberToIncrement(duration.[[Date]].[[Weeks]] + untilResult.[[Weeks]], increment, TRUNC).
|
||||
auto weeks = round_number_to_increment(duration.date.weeks + until_result.weeks, increment, RoundingMode::Trunc);
|
||||
|
||||
// f. Let r1 be weeks.
|
||||
r1 = weeks;
|
||||
|
||||
// g. Let r2 be weeks + increment × sign.
|
||||
r2 = weeks + static_cast<double>(increment) * sign;
|
||||
|
||||
// h. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, r1).
|
||||
start_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, r1));
|
||||
|
||||
// i. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, r2).
|
||||
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, r2));
|
||||
}
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Assert: unit is DAY.
|
||||
VERIFY(unit == Unit::Day);
|
||||
|
||||
// b. Let days be RoundNumberToIncrement(duration.[[Date]].[[Days]], increment, TRUNC).
|
||||
auto days = round_number_to_increment(duration.date.days, increment, RoundingMode::Trunc);
|
||||
|
||||
// c. Let r1 be days.
|
||||
r1 = days;
|
||||
|
||||
// d. Let r2 be days + increment × sign.
|
||||
r2 = days + static_cast<double>(increment) * sign;
|
||||
|
||||
// e. Let startDuration be ? AdjustDateDurationRecord(duration.[[Date]], r1).
|
||||
start_duration = TRY(adjust_date_duration_record(vm, duration.date, r1));
|
||||
|
||||
// f. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], r2).
|
||||
end_duration = TRY(adjust_date_duration_record(vm, duration.date, r2));
|
||||
}
|
||||
|
||||
// 5. Assert: If sign is 1, r1 ≥ 0 and r1 < r2.
|
||||
if (sign == 1)
|
||||
VERIFY(r1 >= 0 && r1 < r2);
|
||||
// 6. Assert: If sign is -1, r1 ≤ 0 and r1 > r2.
|
||||
else if (sign == -1)
|
||||
VERIFY(r1 <= 0 && r1 > r2);
|
||||
|
||||
// 7. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], startDuration, CONSTRAIN).
|
||||
auto start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, start_duration, Overflow::Constrain));
|
||||
|
||||
// 8. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, CONSTRAIN).
|
||||
auto end = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, end_duration, Overflow::Constrain));
|
||||
|
||||
// 9. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]).
|
||||
auto start_date_time = combine_iso_date_and_time_record(start, iso_date_time.time);
|
||||
|
||||
// 10. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]).
|
||||
auto end_date_time = combine_iso_date_and_time_record(end, iso_date_time.time);
|
||||
|
||||
TimeDuration start_epoch_ns;
|
||||
TimeDuration end_epoch_ns;
|
||||
|
||||
// 11. If timeZone is UNSET, then
|
||||
if (!time_zone.has_value()) {
|
||||
// a. Let startEpochNs be GetUTCEpochNanoseconds(startDateTime).
|
||||
start_epoch_ns = get_utc_epoch_nanoseconds(start_date_time);
|
||||
|
||||
// b. Let endEpochNs be GetUTCEpochNanoseconds(endDateTime).
|
||||
end_epoch_ns = get_utc_epoch_nanoseconds(end_date_time);
|
||||
}
|
||||
// 12. Else,
|
||||
else {
|
||||
// a. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, COMPATIBLE).
|
||||
start_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, start_date_time, Disambiguation::Compatible));
|
||||
|
||||
// b. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
|
||||
end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible));
|
||||
}
|
||||
|
||||
// 13. If sign is 1, then
|
||||
if (sign == 1) {
|
||||
// a. Assert: startEpochNs ≤ destEpochNs ≤ endEpochNs.
|
||||
VERIFY(start_epoch_ns <= dest_epoch_ns);
|
||||
VERIFY(dest_epoch_ns <= end_epoch_ns);
|
||||
}
|
||||
// 14. Else,
|
||||
else {
|
||||
// a. Assert: endEpochNs ≤ destEpochNs ≤ startEpochNs.
|
||||
VERIFY(end_epoch_ns <= dest_epoch_ns);
|
||||
VERIFY(dest_epoch_ns <= start_epoch_ns);
|
||||
}
|
||||
|
||||
// 15. Assert: startEpochNs ≠ endEpochNs.
|
||||
VERIFY(start_epoch_ns != end_epoch_ns);
|
||||
|
||||
// 16. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs).
|
||||
auto progress_numerator = dest_epoch_ns.minus(start_epoch_ns);
|
||||
auto progress_denominator = end_epoch_ns.minus(start_epoch_ns);
|
||||
auto progress_equals_one = progress_numerator == progress_denominator;
|
||||
|
||||
// 17. Let total be r1 + progress × increment × sign.
|
||||
auto total_numerator = progress_numerator.multiplied_by(Crypto::UnsignedBigInteger { increment });
|
||||
|
||||
if (sign == -1)
|
||||
total_numerator.negate();
|
||||
if (progress_denominator.is_negative())
|
||||
total_numerator.negate();
|
||||
|
||||
auto total_mv = Crypto::BigFraction { Crypto::SignedBigInteger { r1 } } + Crypto::BigFraction { move(total_numerator), progress_denominator.unsigned_value() };
|
||||
auto total = total_mv.to_double();
|
||||
|
||||
// 18. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic. This division can be
|
||||
// implemented as if expressing the denominator and numerator of total as two time durations, and performing one
|
||||
// division operation with a floating-point result.
|
||||
|
||||
// 19. Assert: 0 ≤ progress ≤ 1.
|
||||
|
||||
// 20. If sign < 0, let isNegative be NEGATIVE; else let isNegative be POSITIVE.
|
||||
auto is_negative = sign < 0 ? Sign::Negative : Sign::Positive;
|
||||
|
||||
// 21. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative).
|
||||
auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative);
|
||||
|
||||
double rounded_unit = 0;
|
||||
|
||||
// 22. If progress = 1, then
|
||||
if (progress_equals_one) {
|
||||
// a. Let roundedUnit be abs(r2).
|
||||
rounded_unit = fabs(r2);
|
||||
}
|
||||
// 23. Else,
|
||||
else {
|
||||
// a. Assert: abs(r1) ≤ abs(total) < abs(r2).
|
||||
VERIFY(fabs(r1) <= fabs(total));
|
||||
VERIFY(fabs(total) <= fabs(r2));
|
||||
|
||||
// b. Let roundedUnit be ApplyUnsignedRoundingMode(abs(total), abs(r1), abs(r2), unsignedRoundingMode).
|
||||
rounded_unit = apply_unsigned_rounding_mode(fabs(total), fabs(r1), fabs(r2), unsigned_rounding_mode);
|
||||
}
|
||||
|
||||
auto did_expand_calendar_unit = false;
|
||||
DateDuration result_duration;
|
||||
TimeDuration nudged_epoch_ns;
|
||||
|
||||
// 24. If roundedUnit is abs(r2), then
|
||||
if (rounded_unit == fabs(r2)) {
|
||||
// a. Let didExpandCalendarUnit be true.
|
||||
did_expand_calendar_unit = true;
|
||||
|
||||
// b. Let resultDuration be endDuration.
|
||||
result_duration = end_duration;
|
||||
|
||||
// c. Let nudgedEpochNs be endEpochNs.
|
||||
nudged_epoch_ns = move(end_epoch_ns);
|
||||
}
|
||||
// 25. Else,
|
||||
else {
|
||||
// a. Let didExpandCalendarUnit be false.
|
||||
did_expand_calendar_unit = false;
|
||||
|
||||
// b. Let resultDuration be startDuration.
|
||||
result_duration = start_duration;
|
||||
|
||||
// c. Let nudgedEpochNs be startEpochNs.
|
||||
nudged_epoch_ns = move(start_epoch_ns);
|
||||
}
|
||||
|
||||
// 26. Set resultDuration to ! CombineDateAndTimeDuration(resultDuration, 0).
|
||||
auto result_date_and_time_duration = MUST(combine_date_and_time_duration(vm, result_duration, TimeDuration { 0 }));
|
||||
|
||||
// 27. Let nudgeResult be Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandCalendarUnit }.
|
||||
DurationNudgeResult nudge_result { .duration = move(result_date_and_time_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_calendar_unit };
|
||||
|
||||
// 28. Return the Record { [[NudgeResult]]: nudgeResult, [[Total]]: total }.
|
||||
return CalendarNudgeResult { .nudge_result = move(nudge_result), .total = move(total_mv) };
|
||||
}
|
||||
|
||||
// 7.5.34 NudgeToZonedTime ( sign, duration, isoDateTime, timeZone, calendar, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetozonedtime
|
||||
ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM& vm, i8 sign, InternalDuration const& duration, ISODateTime const& iso_date_time, StringView time_zone, StringView calendar, u64 increment, Unit unit, RoundingMode rounding_mode)
|
||||
{
|
||||
// 1. Let start be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], CONSTRAIN).
|
||||
auto start = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, duration.date, Overflow::Constrain));
|
||||
|
||||
// 2. Let startDateTime be CombineISODateAndTimeRecord(start, isoDateTime.[[Time]]).
|
||||
auto start_date_time = combine_iso_date_and_time_record(start, iso_date_time.time);
|
||||
|
||||
// 3. Let endDate be BalanceISODate(start.[[Year]], start.[[Month]], start.[[Day]] + sign).
|
||||
auto end_date = balance_iso_date(start.year, start.month, static_cast<double>(start.day) + sign);
|
||||
|
||||
// 4. Let endDateTime be CombineISODateAndTimeRecord(endDate, isoDateTime.[[Time]]).
|
||||
auto end_date_time = combine_iso_date_and_time_record(end_date, iso_date_time.time);
|
||||
|
||||
// 5. Let startEpochNs be ? GetEpochNanosecondsFor(timeZone, startDateTime, COMPATIBLE).
|
||||
auto start_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, time_zone, start_date_time, Disambiguation::Compatible));
|
||||
|
||||
// 6. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
|
||||
auto end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, time_zone, end_date_time, Disambiguation::Compatible));
|
||||
|
||||
// 7. Let daySpan be TimeDurationFromEpochNanosecondsDifference(endEpochNs, startEpochNs).
|
||||
auto day_span = time_duration_from_epoch_nanoseconds_difference(end_epoch_ns, start_epoch_ns);
|
||||
|
||||
// 8. Assert: TimeDurationSign(daySpan) = sign.
|
||||
VERIFY(time_duration_sign(day_span) == sign);
|
||||
|
||||
// 9. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
|
||||
auto const& unit_length = temporal_unit_length_in_nanoseconds(unit);
|
||||
|
||||
// 10. Let roundedTimeDuration be ? RoundTimeDurationToIncrement(duration.[[Time]], increment × unitLength, roundingMode).
|
||||
auto unit_length_multiplied_by_increment = unit_length.multiplied_by(Crypto::UnsignedBigInteger { increment });
|
||||
auto rounded_time_duration = TRY(round_time_duration_to_increment(vm, duration.time, unit_length_multiplied_by_increment, rounding_mode));
|
||||
|
||||
// 11. Let beyondDaySpan be ? AddTimeDuration(roundedTimeDuration, -daySpan).
|
||||
day_span.negate();
|
||||
auto beyond_day_span = TRY(add_time_duration(vm, rounded_time_duration, day_span));
|
||||
|
||||
auto did_round_beyond_day = false;
|
||||
TimeDuration nudged_epoch_ns;
|
||||
i8 day_delta = 0;
|
||||
|
||||
// 12. If TimeDurationSign(beyondDaySpan) ≠ -sign, then
|
||||
if (time_duration_sign(beyond_day_span) != -sign) {
|
||||
// a. Let didRoundBeyondDay be true.
|
||||
did_round_beyond_day = true;
|
||||
|
||||
// b. Let dayDelta be sign.
|
||||
day_delta = sign;
|
||||
|
||||
// c. Set roundedTimeDuration to ? RoundTimeDurationToIncrement(beyondDaySpan, increment × unitLength, roundingMode).
|
||||
rounded_time_duration = TRY(round_time_duration_to_increment(vm, beyond_day_span, unit_length_multiplied_by_increment, rounding_mode));
|
||||
|
||||
// d. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(roundedTimeDuration, endEpochNs).
|
||||
nudged_epoch_ns = add_time_duration_to_epoch_nanoseconds(rounded_time_duration, end_epoch_ns);
|
||||
}
|
||||
// 13. Else,
|
||||
else {
|
||||
// a. Let didRoundBeyondDay be false.
|
||||
did_round_beyond_day = false;
|
||||
|
||||
// b. Let dayDelta be 0.
|
||||
day_delta = 0;
|
||||
|
||||
// c. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(roundedTimeDuration, startEpochNs).
|
||||
nudged_epoch_ns = add_time_duration_to_epoch_nanoseconds(rounded_time_duration, start_epoch_ns);
|
||||
}
|
||||
|
||||
// 14. Let dateDuration be ? AdjustDateDurationRecord(duration.[[Date]], duration.[[Date]].[[Days]] + dayDelta).
|
||||
auto date_duration = TRY(adjust_date_duration_record(vm, duration.date, duration.date.days + day_delta));
|
||||
|
||||
// 15. Let resultDuration be ? CombineDateAndTimeDuration(dateDuration, roundedTimeDuration).
|
||||
auto result_duration = TRY(combine_date_and_time_duration(vm, date_duration, move(rounded_time_duration)));
|
||||
|
||||
// 16. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didRoundBeyondDay }.
|
||||
return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_round_beyond_day };
|
||||
}
|
||||
|
||||
// 7.5.35 NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-nudgetodayortime
|
||||
ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM& vm, InternalDuration const& duration, TimeDuration const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode)
|
||||
{
|
||||
// 1. Let timeDuration be ! Add24HourDaysToTimeDuration(duration.[[Time]], duration.[[Date]].[[Days]]).
|
||||
auto time_duration = MUST(add_24_hour_days_to_time_duration(vm, duration.time, duration.date.days));
|
||||
|
||||
// 2. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains smallestUnit.
|
||||
auto const& unit_length = temporal_unit_length_in_nanoseconds(smallest_unit);
|
||||
|
||||
// 3. Let roundedTime be ? RoundTimeDurationToIncrement(timeDuration, unitLength × increment, roundingMode).
|
||||
auto unit_length_multiplied_by_increment = unit_length.multiplied_by(Crypto::UnsignedBigInteger { increment });
|
||||
auto rounded_time = TRY(round_time_duration_to_increment(vm, time_duration, unit_length_multiplied_by_increment, rounding_mode));
|
||||
|
||||
// 4. Let diffTime be ! AddTimeDuration(roundedTime, -timeDuration).
|
||||
time_duration.negate();
|
||||
auto diff_time = MUST(add_time_duration(vm, rounded_time, time_duration));
|
||||
time_duration.negate();
|
||||
|
||||
// 5. Let wholeDays be truncate(TotalTimeDuration(timeDuration, DAY)).
|
||||
auto whole_days = trunc(total_time_duration(time_duration, Unit::Day));
|
||||
|
||||
// 6. Let roundedWholeDays be truncate(TotalTimeDuration(roundedTime, DAY)).
|
||||
auto rounded_whole_days = trunc(total_time_duration(rounded_time, Unit::Day));
|
||||
|
||||
// 7. Let dayDelta be roundedWholeDays - wholeDays.
|
||||
auto day_delta = rounded_whole_days - whole_days;
|
||||
|
||||
// 8. If dayDelta < 0, let dayDeltaSign be -1; else if dayDelta > 0, let dayDeltaSign be 1; else let dayDeltaSign be 0.
|
||||
auto day_delta_sign = day_delta < 0 ? -1 : (day_delta > 0 ? 1 : 0);
|
||||
|
||||
// 9. If dayDeltaSign = TimeDurationSign(timeDuration), let didExpandDays be true; else let didExpandDays be false.
|
||||
auto did_expand_days = day_delta_sign == time_duration_sign(time_duration);
|
||||
|
||||
// 10. Let nudgedEpochNs be AddTimeDurationToEpochNanoseconds(diffTime, destEpochNs).
|
||||
auto nudged_epoch_ns = add_time_duration_to_epoch_nanoseconds(diff_time, dest_epoch_ns);
|
||||
|
||||
// 11. Let days be 0.
|
||||
double days = 0;
|
||||
|
||||
// 12. Let remainder be roundedTime.
|
||||
TimeDuration remainder;
|
||||
|
||||
// 13. If TemporalUnitCategory(largestUnit) is DATE, then
|
||||
if (temporal_unit_category(largest_unit) == UnitCategory::Date) {
|
||||
// a. Set days to roundedWholeDays.
|
||||
days = rounded_whole_days;
|
||||
|
||||
// b. Set remainder to ! AddTimeDuration(roundedTime, TimeDurationFromComponents(-roundedWholeDays * HoursPerDay, 0, 0, 0, 0, 0)).
|
||||
remainder = MUST(add_time_duration(vm, rounded_time, time_duration_from_components(-rounded_whole_days * JS::hours_per_day, 0, 0, 0, 0, 0)));
|
||||
} else {
|
||||
remainder = move(rounded_time);
|
||||
}
|
||||
|
||||
// 14. Let dateDuration be ? AdjustDateDurationRecord(duration.[[Date]], days).
|
||||
auto date_duration = TRY(adjust_date_duration_record(vm, duration.date, days));
|
||||
|
||||
// 15. Let resultDuration be ? CombineDateAndTimeDuration(dateDuration, remainder).
|
||||
auto result_duration = TRY(combine_date_and_time_duration(vm, date_duration, move(remainder)));
|
||||
|
||||
// 16. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandDays }.
|
||||
return DurationNudgeResult { .duration = move(result_duration), .nudged_epoch_ns = move(nudged_epoch_ns), .did_expand_calendar_unit = did_expand_days };
|
||||
}
|
||||
|
||||
// 7.5.36 BubbleRelativeDuration ( sign, duration, nudgedEpochNs, isoDateTime, timeZone, calendar, largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-bubblerelativeduration
|
||||
ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM& vm, i8 sign, InternalDuration duration, TimeDuration const& nudged_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit)
|
||||
{
|
||||
// 1. If smallestUnit is largestUnit, return duration.
|
||||
if (smallest_unit == largest_unit)
|
||||
return duration;
|
||||
|
||||
// 2. Let largestUnitIndex be the ordinal index of the row of Table 21 whose "Value" column contains largestUnit.
|
||||
auto largest_unit_index = to_underlying(largest_unit);
|
||||
|
||||
// 3. Let smallestUnitIndex be the ordinal index of the row of Table 21 whose "Value" column contains smallestUnit.
|
||||
auto smallest_unit_index = to_underlying(smallest_unit);
|
||||
|
||||
// 4. Let unitIndex be smallestUnitIndex - 1.
|
||||
auto unit_index = smallest_unit_index - 1;
|
||||
|
||||
// 5. Let done be false.
|
||||
auto done = false;
|
||||
|
||||
// 6. Repeat, while unitIndex ≥ largestUnitIndex and done is false,
|
||||
while (unit_index >= largest_unit_index && !done) {
|
||||
// a. Let unit be the value in the "Value" column of Table 21 in the row whose ordinal index is unitIndex.
|
||||
auto unit = static_cast<Unit>(unit_index);
|
||||
|
||||
// b. If unit is not WEEK, or largestUnit is WEEK, then
|
||||
if (unit != Unit::Week || largest_unit == Unit::Week) {
|
||||
DateDuration end_duration;
|
||||
|
||||
// i. If unit is YEAR, then
|
||||
if (unit == Unit::Year) {
|
||||
// 1. Let years be duration.[[Date]].[[Years]] + sign.
|
||||
auto years = duration.date.years + sign;
|
||||
|
||||
// 2. Let endDuration be ? CreateDateDurationRecord(years, 0, 0, 0).
|
||||
end_duration = TRY(create_date_duration_record(vm, years, 0, 0, 0));
|
||||
}
|
||||
// ii. Else if unit is MONTH, then
|
||||
else if (unit == Unit::Month) {
|
||||
// 1. Let months be duration.[[Date]].[[Months]] + sign.
|
||||
auto months = duration.date.months + sign;
|
||||
|
||||
// 2. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, 0, months).
|
||||
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, 0, months));
|
||||
}
|
||||
// iii. Else,
|
||||
else {
|
||||
// 1. Assert: unit is WEEK.
|
||||
VERIFY(unit == Unit::Week);
|
||||
|
||||
// 2. Let weeks be duration.[[Date]].[[Weeks]] + sign.
|
||||
auto weeks = duration.date.weeks + sign;
|
||||
|
||||
// 3. Let endDuration be ? AdjustDateDurationRecord(duration.[[Date]], 0, weeks).
|
||||
end_duration = TRY(adjust_date_duration_record(vm, duration.date, 0, weeks));
|
||||
}
|
||||
|
||||
// iv. Let end be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], endDuration, CONSTRAIN).
|
||||
auto end = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, end_duration, Overflow::Constrain));
|
||||
|
||||
// v. Let endDateTime be CombineISODateAndTimeRecord(end, isoDateTime.[[Time]]).
|
||||
auto end_date_time = combine_iso_date_and_time_record(end, iso_date_time.time);
|
||||
|
||||
TimeDuration end_epoch_ns;
|
||||
|
||||
// vi. If timeZone is UNSET, then
|
||||
if (!time_zone.has_value()) {
|
||||
// 1. Let endEpochNs be GetUTCEpochNanoseconds(endDateTime).
|
||||
end_epoch_ns = get_utc_epoch_nanoseconds(end_date_time);
|
||||
}
|
||||
// vii. Else,
|
||||
else {
|
||||
// 1. Let endEpochNs be ? GetEpochNanosecondsFor(timeZone, endDateTime, COMPATIBLE).
|
||||
end_epoch_ns = TRY(get_epoch_nanoseconds_for(vm, *time_zone, end_date_time, Disambiguation::Compatible));
|
||||
}
|
||||
|
||||
// viii. Let beyondEnd be nudgedEpochNs - endEpochNs.
|
||||
auto beyond_end = nudged_epoch_ns.minus(end_epoch_ns);
|
||||
|
||||
// ix. If beyondEnd < 0, let beyondEndSign be -1; else if beyondEnd > 0, let beyondEndSign be 1; else let beyondEndSign be 0.
|
||||
auto beyond_end_sign = beyond_end.is_negative() ? -1 : (beyond_end.is_positive() ? 1 : 0);
|
||||
|
||||
// x. If beyondEndSign ≠ -sign, then
|
||||
if (beyond_end_sign != -sign) {
|
||||
// 1. Set duration to ! CombineDateAndTimeDuration(endDuration, 0).
|
||||
duration = MUST(combine_date_and_time_duration(vm, end_duration, TimeDuration { 0 }));
|
||||
}
|
||||
// xi. Else,
|
||||
else {
|
||||
// 1. Set done to true.
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// c. Set unitIndex to unitIndex - 1.
|
||||
--unit_index;
|
||||
}
|
||||
|
||||
// 7. Return duration.
|
||||
return duration;
|
||||
}
|
||||
|
||||
// 7.5.37 RoundRelativeDuration ( duration, destEpochNs, isoDateTime, timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundrelativeduration
|
||||
ThrowCompletionOr<InternalDuration> round_relative_duration(VM& vm, InternalDuration duration, TimeDuration const& dest_epoch_ns, ISODateTime const& iso_date_time, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode rounding_mode)
|
||||
{
|
||||
// 1. Let irregularLengthUnit be false.
|
||||
auto irregular_length_unit = false;
|
||||
|
||||
// 2. If IsCalendarUnit(smallestUnit) is true, set irregularLengthUnit to true.
|
||||
if (is_calendar_unit(smallest_unit))
|
||||
irregular_length_unit = true;
|
||||
|
||||
// 3. If timeZone is not UNSET and smallestUnit is DAY, set irregularLengthUnit to true.
|
||||
if (time_zone.has_value() && smallest_unit == Unit::Day)
|
||||
irregular_length_unit = true;
|
||||
|
||||
// 4. If InternalDurationSign(duration) < 0, let sign be -1; else let sign be 1.
|
||||
i8 sign = internal_duration_sign(duration) < 0 ? -1 : 1;
|
||||
|
||||
DurationNudgeResult nudge_result;
|
||||
|
||||
// 5. If irregularLengthUnit is true, then
|
||||
if (irregular_length_unit) {
|
||||
// a. Let record be ? NudgeToCalendarUnit(sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode).
|
||||
auto record = TRY(nudge_to_calendar_unit(vm, sign, duration, dest_epoch_ns, iso_date_time, time_zone, calendar, increment, smallest_unit, rounding_mode));
|
||||
|
||||
// b. Let nudgeResult be record.[[NudgeResult]].
|
||||
nudge_result = move(record.nudge_result);
|
||||
}
|
||||
// 6. Else if timeZone is not UNSET, then
|
||||
else if (time_zone.has_value()) {
|
||||
// a. Let nudgeResult be ? NudgeToZonedTime(sign, duration, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode).
|
||||
nudge_result = TRY(nudge_to_zoned_time(vm, sign, duration, iso_date_time, *time_zone, calendar, increment, smallest_unit, rounding_mode));
|
||||
}
|
||||
// 7. Else,
|
||||
else {
|
||||
// a. Let nudgeResult be ? NudgeToDayOrTime(duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode).
|
||||
nudge_result = TRY(nudge_to_day_or_time(vm, duration, dest_epoch_ns, largest_unit, increment, smallest_unit, rounding_mode));
|
||||
}
|
||||
|
||||
// 8. Set duration to nudgeResult.[[Duration]].
|
||||
duration = move(nudge_result.duration);
|
||||
|
||||
// 9. If nudgeResult.[[DidExpandCalendarUnit]] is true and smallestUnit is not WEEK, then
|
||||
if (nudge_result.did_expand_calendar_unit && smallest_unit != Unit::Week) {
|
||||
// a. Let startUnit be LargerOfTwoTemporalUnits(smallestUnit, DAY).
|
||||
auto start_unit = larger_of_two_temporal_units(smallest_unit, Unit::Day);
|
||||
|
||||
// b. Set duration to ? BubbleRelativeDuration(sign, duration, nudgeResult.[[NudgedEpochNs]], isoDateTime, timeZone, calendar, largestUnit, startUnit).
|
||||
duration = TRY(bubble_relative_duration(vm, sign, move(duration), nudge_result.nudged_epoch_ns, iso_date_time, time_zone, calendar, largest_unit, start_unit));
|
||||
}
|
||||
|
||||
// 10. Return duration.
|
||||
return duration;
|
||||
}
|
||||
|
||||
// 7.5.39 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
|
||||
String temporal_duration_to_string(Duration const& duration, Precision precision)
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
|
@ -100,15 +101,29 @@ struct InternalDuration {
|
|||
TimeDuration time;
|
||||
};
|
||||
|
||||
// 7.5.32 Duration Nudge Result Records, https://tc39.es/proposal-temporal/#sec-temporal-duration-nudge-result-records
|
||||
struct DurationNudgeResult {
|
||||
InternalDuration duration;
|
||||
TimeDuration nudged_epoch_ns;
|
||||
bool did_expand_calendar_unit { false };
|
||||
};
|
||||
|
||||
struct CalendarNudgeResult {
|
||||
DurationNudgeResult nudge_result;
|
||||
Crypto::BigFraction total;
|
||||
};
|
||||
|
||||
DateDuration zero_date_duration(VM&);
|
||||
InternalDuration to_internal_duration_record(VM&, Duration const&);
|
||||
InternalDuration to_internal_duration_record_with_24_hour_days(VM&, Duration const&);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> temporal_duration_from_internal(VM&, InternalDuration const&, Unit largest_unit);
|
||||
ThrowCompletionOr<DateDuration> create_date_duration_record(VM&, double years, double months, double weeks, double days);
|
||||
ThrowCompletionOr<DateDuration> adjust_date_duration_record(VM&, DateDuration const&, double days, Optional<double> weeks = {}, Optional<double> months = {});
|
||||
ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM&, DateDuration, TimeDuration);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM&, Value);
|
||||
i8 duration_sign(Duration const&);
|
||||
i8 date_duration_sign(DateDuration const&);
|
||||
i8 internal_duration_sign(InternalDuration const&);
|
||||
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
|
||||
Unit default_temporal_largest_unit(Duration const&);
|
||||
ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM&, Value temporal_duration_like);
|
||||
|
@ -117,10 +132,17 @@ GC::Ref<Duration> create_negated_temporal_duration(VM&, Duration const&);
|
|||
TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
|
||||
ThrowCompletionOr<TimeDuration> add_time_duration(VM&, TimeDuration const&, TimeDuration const&);
|
||||
ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM&, TimeDuration const&, double days);
|
||||
TimeDuration add_time_duration_to_epoch_nanoseconds(TimeDuration const& duration, TimeDuration const& epoch_nanoseconds);
|
||||
i8 compare_time_duration(TimeDuration const&, TimeDuration const&);
|
||||
TimeDuration time_duration_from_epoch_nanoseconds_difference(TimeDuration const&, TimeDuration const&);
|
||||
ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
||||
i8 time_duration_sign(TimeDuration const&);
|
||||
ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode);
|
||||
ThrowCompletionOr<CalendarNudgeResult> nudge_to_calendar_unit(VM&, i8 sign, InternalDuration const&, TimeDuration const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, u64 increment, Unit, RoundingMode);
|
||||
ThrowCompletionOr<DurationNudgeResult> nudge_to_zoned_time(VM&, i8 sign, InternalDuration const&, ISODateTime const&, StringView time_zone, StringView calendar, u64 increment, Unit, RoundingMode);
|
||||
ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM&, InternalDuration const&, TimeDuration const& dest_epoch_ns, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
|
||||
ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM&, i8 sign, InternalDuration, TimeDuration const& nudged_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, Unit smallest_unit);
|
||||
ThrowCompletionOr<InternalDuration> round_relative_duration(VM&, InternalDuration, TimeDuration const& dest_epoch_ns, ISODateTime const&, Optional<StringView> time_zone, StringView calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
|
||||
double total_time_duration(TimeDuration const&, Unit);
|
||||
String temporal_duration_to_string(Duration const&, Precision);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
|
||||
|
|
|
@ -34,4 +34,17 @@ Crypto::UnsignedBigInteger const SECONDS_PER_MINUTE = 60_bigint;
|
|||
Crypto::UnsignedBigInteger const MINUTES_PER_HOUR = 60_bigint;
|
||||
Crypto::UnsignedBigInteger const HOURS_PER_DAY = 24_bigint;
|
||||
|
||||
// 8.5.1 IsValidEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
|
||||
bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds)
|
||||
{
|
||||
// 1. If ℝ(epochNanoseconds) < nsMinInstant or ℝ(epochNanoseconds) > nsMaxInstant, then
|
||||
if (epoch_nanoseconds < NANOSECONDS_MIN_INSTANT || epoch_nanoseconds > NANOSECONDS_MAX_INSTANT) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,4 +36,6 @@ extern Crypto::UnsignedBigInteger const SECONDS_PER_MINUTE;
|
|||
extern Crypto::UnsignedBigInteger const MINUTES_PER_HOUR;
|
||||
extern Crypto::UnsignedBigInteger const HOURS_PER_DAY;
|
||||
|
||||
bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds);
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <AK/Checked.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/DateEquations.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
@ -25,6 +26,32 @@ ISODate create_iso_date_record(double year, double month, double day)
|
|||
return { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
|
||||
}
|
||||
|
||||
// 3.5.5 ISODateSurpasses ( sign, y1, m1, d1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
|
||||
bool iso_date_surpasses(i8 sign, double year1, double month1, double day1, ISODate iso_date2)
|
||||
{
|
||||
// 1. If y1 ≠ isoDate2.[[Year]], then
|
||||
if (year1 != iso_date2.year) {
|
||||
// a. If sign × (y1 - isoDate2.[[Year]]) > 0, return true.
|
||||
if (sign * (year1 - iso_date2.year) > 0)
|
||||
return true;
|
||||
}
|
||||
// 2. Else if m1 ≠ isoDate2.[[Month]], then
|
||||
else if (month1 != iso_date2.month) {
|
||||
// a. If sign × (m1 - isoDate2.[[Month]]) > 0, return true.
|
||||
if (sign * (month1 - iso_date2.month) > 0)
|
||||
return true;
|
||||
}
|
||||
// 3. Else if d1 ≠ isoDate2.[[Day]], then
|
||||
else if (day1 != iso_date2.day) {
|
||||
// a. If sign × (d1 - isoDate2.[[Day]]) > 0, return true.
|
||||
if (sign * (day1 - iso_date2.day) > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.5.6 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
|
||||
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow)
|
||||
{
|
||||
|
@ -86,6 +113,19 @@ bool is_valid_iso_date(double year, double month, double day)
|
|||
return true;
|
||||
}
|
||||
|
||||
// 3.5.8 BalanceISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodate
|
||||
ISODate balance_iso_date(double year, double month, double day)
|
||||
{
|
||||
// 1. Let epochDays be ISODateToEpochDays(year, month - 1, day).
|
||||
auto epoch_days = iso_date_to_epoch_days(year, month - 1, day);
|
||||
|
||||
// 2. Let ms be EpochDaysToEpochMs(epochDays, 0).
|
||||
auto ms = epoch_days_to_epoch_ms(epoch_days, 0);
|
||||
|
||||
// 3. Return CreateISODateRecord(EpochTimeToEpochYear(ms), EpochTimeToMonthInYear(ms) + 1, EpochTimeToDate(ms)).
|
||||
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));
|
||||
}
|
||||
|
||||
// 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
|
||||
String pad_iso_year(i32 year)
|
||||
{
|
||||
|
|
|
@ -23,8 +23,10 @@ struct ISODate {
|
|||
};
|
||||
|
||||
ISODate create_iso_date_record(double year, double month, double day);
|
||||
bool iso_date_surpasses(i8 sign, double year1, double month1, double day1, ISODate iso_date2);
|
||||
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow);
|
||||
bool is_valid_iso_date(double year, double month, double day);
|
||||
ISODate balance_iso_date(double year, double month, double day);
|
||||
String pad_iso_year(i32 year);
|
||||
bool iso_date_within_limits(ISODate);
|
||||
i8 compare_iso_date(ISODate, ISODate);
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -52,4 +54,17 @@ bool iso_date_time_within_limits(ISODateTime iso_date_time)
|
|||
return true;
|
||||
}
|
||||
|
||||
// 5.5.7 BalanceISODateTime ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodatetime
|
||||
ISODateTime balance_iso_date_time(double year, double month, double day, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond)
|
||||
{
|
||||
// 1. Let balancedTime be BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond).
|
||||
auto balanced_time = balance_time(hour, minute, second, millisecond, microsecond, nanosecond);
|
||||
|
||||
// 2. Let balancedDate be BalanceISODate(year, month, day + balancedTime.[[Days]]).
|
||||
auto balanced_date = balance_iso_date(year, month, day + balanced_time.days);
|
||||
|
||||
// 3. Return CombineISODateAndTimeRecord(balancedDate, balancedTime).
|
||||
return combine_iso_date_and_time_record(balanced_date, balanced_time);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,5 +21,6 @@ struct ISODateTime {
|
|||
|
||||
ISODateTime combine_iso_date_and_time_record(ISODate, Time);
|
||||
bool iso_date_time_within_limits(ISODateTime);
|
||||
ISODateTime balance_iso_date_time(double year, double month, double day, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -30,6 +32,13 @@ Time create_time_record(double hour, double minute, double second, double millis
|
|||
};
|
||||
}
|
||||
|
||||
// 4.5.3 MidnightTimeRecord ( ), https://tc39.es/proposal-temporal/#sec-temporal-midnighttimerecord
|
||||
Time midnight_time_record()
|
||||
{
|
||||
// 1. Return Time Record { [[Days]]: 0, [[Hour]]: 0, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }.
|
||||
return { .days = 0, .hour = 0, .minute = 0, .second = 0, .millisecond = 0, .microsecond = 0, .nanosecond = 0 };
|
||||
}
|
||||
|
||||
// 4.5.4 NoonTimeRecord ( ), https://tc39.es/proposal-temporal/#sec-temporal-noontimerecord
|
||||
Time noon_time_record()
|
||||
{
|
||||
|
@ -80,4 +89,47 @@ bool is_valid_time(double hour, double minute, double second, double millisecond
|
|||
return true;
|
||||
}
|
||||
|
||||
// 4.5.10 BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-balancetime
|
||||
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond)
|
||||
{
|
||||
// 1. Set microsecond to microsecond + floor(nanosecond / 1000).
|
||||
microsecond += floor(nanosecond / 1000.0);
|
||||
|
||||
// 2. Set nanosecond to nanosecond modulo 1000.
|
||||
nanosecond = modulo(nanosecond, 1000.0);
|
||||
|
||||
// 3. Set millisecond to millisecond + floor(microsecond / 1000).
|
||||
millisecond += floor(microsecond / 1000.0);
|
||||
|
||||
// 4. Set microsecond to microsecond modulo 1000.
|
||||
microsecond = modulo(microsecond, 1000.0);
|
||||
|
||||
// 5. Set second to second + floor(millisecond / 1000).
|
||||
second += floor(millisecond / 1000.0);
|
||||
|
||||
// 6. Set millisecond to millisecond modulo 1000.
|
||||
millisecond = modulo(millisecond, 1000.0);
|
||||
|
||||
// 7. Set minute to minute + floor(second / 60).
|
||||
minute += floor(second / 60.0);
|
||||
|
||||
// 8. Set second to second modulo 60.
|
||||
second = modulo(second, 60.0);
|
||||
|
||||
// 9. Set hour to hour + floor(minute / 60).
|
||||
hour += floor(minute / 60.0);
|
||||
|
||||
// 10. Set minute to minute modulo 60.
|
||||
minute = modulo(minute, 60.0);
|
||||
|
||||
// 11. Let deltaDays be floor(hour / 24).
|
||||
auto delta_days = floor(hour / 24.0);
|
||||
|
||||
// 12. Set hour to hour modulo 24.
|
||||
hour = modulo(hour, 24.0);
|
||||
|
||||
// 13. Return CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond, deltaDays).
|
||||
return create_time_record(hour, minute, second, millisecond, microsecond, nanosecond, delta_days);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@ struct Time {
|
|||
};
|
||||
|
||||
Time create_time_record(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, double delta_days = 0);
|
||||
Time midnight_time_record();
|
||||
Time noon_time_record();
|
||||
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#include <LibJS/Runtime/Intrinsics.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
@ -127,6 +130,19 @@ bool iso_year_month_within_limits(ISODate iso_date)
|
|||
return true;
|
||||
}
|
||||
|
||||
// 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) };
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
@ -176,4 +192,82 @@ String temporal_year_month_to_string(PlainYearMonth const& year_month, ShowCalen
|
|||
return result;
|
||||
}
|
||||
|
||||
// 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).
|
||||
auto this_date = TRY(calendar_date_from_fields(vm, calendar, move(this_fields), Overflow::Constrain));
|
||||
|
||||
// 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).
|
||||
auto other_date = TRY(calendar_date_from_fields(vm, calendar, move(other_fields), Overflow::Constrain));
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 17. Let result be ? TemporalDurationFromInternal(duration, DAY).
|
||||
auto result = TRY(temporal_duration_from_internal(vm, duration, Unit::Day));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,9 +32,17 @@ private:
|
|||
String m_calendar; // [[Calendar]]
|
||||
};
|
||||
|
||||
// 9.5.1 ISO Year-Month Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-year-month-records
|
||||
struct ISOYearMonth {
|
||||
i32 year { 0 };
|
||||
u8 month { 0 };
|
||||
};
|
||||
|
||||
ThrowCompletionOr<GC::Ref<PlainYearMonth>> to_temporal_year_month(VM&, Value item, Value options = js_undefined());
|
||||
bool iso_year_month_within_limits(ISODate);
|
||||
ISOYearMonth balance_iso_year_month(double year, double month);
|
||||
ThrowCompletionOr<GC::Ref<PlainYearMonth>> create_temporal_year_month(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
|
||||
String temporal_year_month_to_string(PlainYearMonth const&, ShowCalendar);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_year_month(VM&, DurationOperation, PlainYearMonth const&, Value other, Value options);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
@ -41,6 +42,8 @@ void PlainYearMonthPrototype::initialize(Realm& realm)
|
|||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.with, with, 1, attr);
|
||||
define_native_function(realm, vm.names.until, until, 1, attr);
|
||||
define_native_function(realm, vm.names.since, since, 1, attr);
|
||||
define_native_function(realm, vm.names.equals, equals, 1, attr);
|
||||
define_native_function(realm, vm.names.toString, to_string, 0, attr);
|
||||
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
|
||||
|
@ -170,6 +173,34 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::with)
|
|||
return MUST(create_temporal_year_month(vm, iso_date, calendar));
|
||||
}
|
||||
|
||||
// 9.3.16 Temporal.PlainYearMonth.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.until
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::until)
|
||||
{
|
||||
auto other = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let yearMonth be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
|
||||
auto year_month = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return ? DifferenceTemporalPlainYearMonth(UNTIL, yearMonth, other, options).
|
||||
return TRY(difference_temporal_plain_year_month(vm, DurationOperation::Until, year_month, other, options));
|
||||
}
|
||||
|
||||
// 9.3.17 Temporal.PlainYearMonth.prototype.since ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.since
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::since)
|
||||
{
|
||||
auto other = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let yearMonth be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
|
||||
auto year_month = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return ? DifferenceTemporalPlainYearMonth(SINCE, yearMonth, other, options).
|
||||
return TRY(difference_temporal_plain_year_month(vm, DurationOperation::Since, year_month, other, options));
|
||||
}
|
||||
|
||||
// 9.3.18 Temporal.PlainYearMonth.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.equals
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::equals)
|
||||
{
|
||||
|
|
|
@ -34,6 +34,8 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(months_in_year_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(with);
|
||||
JS_DECLARE_NATIVE_FUNCTION(until);
|
||||
JS_DECLARE_NATIVE_FUNCTION(since);
|
||||
JS_DECLARE_NATIVE_FUNCTION(equals);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
|
@ -70,6 +73,125 @@ ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, Value tempora
|
|||
return time_zone_identifier_record->identifier;
|
||||
}
|
||||
|
||||
// 11.1.11 GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor
|
||||
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM& vm, StringView time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
|
||||
{
|
||||
// 1. Let possibleEpochNs be ? GetPossibleEpochNanoseconds(timeZone, isoDateTime).
|
||||
auto possible_epoch_ns = TRY(get_possible_epoch_nanoseconds(vm, time_zone, iso_date_time));
|
||||
|
||||
// 2. Return ? DisambiguatePossibleEpochNanoseconds(possibleEpochNs, timeZone, isoDateTime, disambiguation).
|
||||
return TRY(disambiguate_possible_epoch_nanoseconds(vm, move(possible_epoch_ns), time_zone, iso_date_time, disambiguation));
|
||||
}
|
||||
|
||||
// 11.1.12 DisambiguatePossibleEpochNanoseconds ( possibleEpochNs, timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleepochnanoseconds
|
||||
ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM& vm, Vector<Crypto::SignedBigInteger> possible_epoch_ns, StringView time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
|
||||
{
|
||||
// 1. Let n be possibleEpochNs's length.
|
||||
auto n = possible_epoch_ns.size();
|
||||
|
||||
// 2. If n = 1, then
|
||||
if (n == 1) {
|
||||
// a. Return possibleEpochNs[0].
|
||||
return move(possible_epoch_ns[0]);
|
||||
}
|
||||
|
||||
// 3. If n ≠ 0, then
|
||||
if (n != 0) {
|
||||
// a. If disambiguation is EARLIER or COMPATIBLE, then
|
||||
if (disambiguation == Disambiguation::Earlier || disambiguation == Disambiguation::Compatible) {
|
||||
// i. Return possibleEpochNs[0].
|
||||
return move(possible_epoch_ns[0]);
|
||||
}
|
||||
|
||||
// b. If disambiguation is LATER, then
|
||||
if (disambiguation == Disambiguation::Later) {
|
||||
// i. Return possibleEpochNs[n - 1].
|
||||
return move(possible_epoch_ns[n - 1]);
|
||||
}
|
||||
|
||||
// c. Assert: disambiguation is REJECT.
|
||||
VERIFY(disambiguation == Disambiguation::Reject);
|
||||
|
||||
// d. Throw a RangeError exception.
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalDisambiguatePossibleEpochNSRejectMoreThanOne);
|
||||
}
|
||||
|
||||
// 4. Assert: n = 0.
|
||||
VERIFY(n == 0);
|
||||
|
||||
// 5. If disambiguation is REJECT, then
|
||||
if (disambiguation == Disambiguation::Reject) {
|
||||
// a. Throw a RangeError exception.
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalDisambiguatePossibleEpochNSRejectZero);
|
||||
}
|
||||
|
||||
// FIXME: GetNamedTimeZoneEpochNanoseconds currently does not produce zero instants.
|
||||
(void)time_zone;
|
||||
(void)iso_date_time;
|
||||
TODO();
|
||||
}
|
||||
|
||||
// 11.1.13 GetPossibleEpochNanoseconds ( timeZone, isoDateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleepochnanoseconds
|
||||
ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanoseconds(VM& vm, StringView time_zone, ISODateTime const& iso_date_time)
|
||||
{
|
||||
Vector<Crypto::SignedBigInteger> possible_epoch_nanoseconds;
|
||||
|
||||
// 1. Let parseResult be ! ParseTimeZoneIdentifier(timeZone).
|
||||
auto parse_result = parse_time_zone_identifier(time_zone);
|
||||
|
||||
// 2. If parseResult.[[OffsetMinutes]] is not empty, then
|
||||
if (parse_result.offset_minutes.has_value()) {
|
||||
// a. Let balanced be BalanceISODateTime(isoDateTime.[[ISODate]].[[Year]], isoDateTime.[[ISODate]].[[Month]], isoDateTime.[[ISODate]].[[Day]], isoDateTime.[[Time]].[[Hour]], isoDateTime.[[Time]].[[Minute]] - parseResult.[[OffsetMinutes]], isoDateTime.[[Time]].[[Second]], isoDateTime.[[Time]].[[Millisecond]], isoDateTime.[[Time]].[[Microsecond]], isoDateTime.[[Time]].[[Nanosecond]]).
|
||||
auto balanced = balance_iso_date_time(
|
||||
iso_date_time.iso_date.year,
|
||||
iso_date_time.iso_date.month,
|
||||
iso_date_time.iso_date.day,
|
||||
iso_date_time.time.hour,
|
||||
static_cast<double>(iso_date_time.time.minute) - static_cast<double>(*parse_result.offset_minutes),
|
||||
iso_date_time.time.second,
|
||||
iso_date_time.time.millisecond,
|
||||
iso_date_time.time.microsecond,
|
||||
iso_date_time.time.nanosecond);
|
||||
|
||||
// b. Perform ? CheckISODaysRange(balanced.[[ISODate]]).
|
||||
TRY(check_iso_days_range(vm, balanced.iso_date));
|
||||
|
||||
// c. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
|
||||
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(balanced);
|
||||
|
||||
// d. Let possibleEpochNanoseconds be « epochNanoseconds ».
|
||||
possible_epoch_nanoseconds.append(move(epoch_nanoseconds));
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. Perform ? CheckISODaysRange(isoDateTime.[[ISODate]]).
|
||||
TRY(check_iso_days_range(vm, iso_date_time.iso_date));
|
||||
|
||||
// b. Let possibleEpochNanoseconds be GetNamedTimeZoneEpochNanoseconds(parseResult.[[Name]], isoDateTime).
|
||||
possible_epoch_nanoseconds = get_named_time_zone_epoch_nanoseconds(
|
||||
*parse_result.name,
|
||||
iso_date_time.iso_date.year,
|
||||
iso_date_time.iso_date.month,
|
||||
iso_date_time.iso_date.day,
|
||||
iso_date_time.time.hour,
|
||||
iso_date_time.time.minute,
|
||||
iso_date_time.time.second,
|
||||
iso_date_time.time.millisecond,
|
||||
iso_date_time.time.microsecond,
|
||||
iso_date_time.time.nanosecond);
|
||||
}
|
||||
|
||||
// 4. For each value epochNanoseconds in possibleEpochNanoseconds, do
|
||||
for (auto const& epoch_nanoseconds : possible_epoch_nanoseconds) {
|
||||
// a. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
|
||||
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
|
||||
}
|
||||
|
||||
// 5. Return possibleEpochNanoseconds.
|
||||
return possible_epoch_nanoseconds;
|
||||
}
|
||||
|
||||
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
||||
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM& vm, StringView identifier)
|
||||
{
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
@ -19,8 +21,18 @@ struct TimeZone {
|
|||
Optional<i64> offset_minutes;
|
||||
};
|
||||
|
||||
enum class Disambiguation {
|
||||
Compatible,
|
||||
Earlier,
|
||||
Later,
|
||||
Reject,
|
||||
};
|
||||
|
||||
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> = {});
|
||||
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like);
|
||||
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM&, StringView time_zone, ISODateTime const&, Disambiguation);
|
||||
ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM&, Vector<Crypto::SignedBigInteger> possible_epoch_ns, StringView time_zone, ISODateTime const&, Disambiguation);
|
||||
ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanoseconds(VM&, StringView time_zone, ISODateTime const&);
|
||||
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM&, StringView identifier);
|
||||
TimeZone parse_time_zone_identifier(StringView identifier);
|
||||
TimeZone parse_time_zone_identifier(ParseResult const&);
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainYearMonth.prototype.since).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const values = [
|
||||
[[0, 1], [0, 1], "PT0S"],
|
||||
[[2, 3], [1, 2], "P1Y1M"],
|
||||
[[1, 2], [0, 1], "P1Y1M"],
|
||||
[[0, 1], [1, 2], "-P1Y1M"],
|
||||
[[0, 12], [0, 1], "P11M"],
|
||||
[[0, 1], [0, 12], "-P11M"],
|
||||
];
|
||||
for (const [args, argsOther, expected] of values) {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(...args);
|
||||
const other = new Temporal.PlainYearMonth(...argsOther);
|
||||
expect(plainYearMonth.since(other).toString()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
test("smallestUnit option", () => {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1, 2);
|
||||
const other = new Temporal.PlainYearMonth(0, 1);
|
||||
const values = [
|
||||
["year", "P1Y"],
|
||||
["month", "P1Y1M"],
|
||||
];
|
||||
for (const [smallestUnit, expected] of values) {
|
||||
expect(plainYearMonth.since(other, { smallestUnit }).toString()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
test("largestUnit option", () => {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1, 2);
|
||||
const other = new Temporal.PlainYearMonth(0, 1);
|
||||
const values = [
|
||||
["year", "P1Y1M"],
|
||||
["month", "P13M"],
|
||||
];
|
||||
for (const [largestUnit, expected] of values) {
|
||||
expect(plainYearMonth.since(other, { largestUnit }).toString()).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainYearMonth object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainYearMonth.prototype.since.call("foo", {});
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
|
||||
});
|
||||
|
||||
test("disallowed smallestUnit option values", () => {
|
||||
const values = [
|
||||
"week",
|
||||
"day",
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"millisecond",
|
||||
"microsecond",
|
||||
"nanosecond",
|
||||
];
|
||||
for (const smallestUnit of values) {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1970, 1);
|
||||
const other = new Temporal.PlainYearMonth(1970, 1);
|
||||
expect(() => {
|
||||
plainYearMonth.since(other, { smallestUnit });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`${smallestUnit} is not a valid value for option smallestUnit`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("disallowed largestUnit option values", () => {
|
||||
const values = [
|
||||
"week",
|
||||
"day",
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"millisecond",
|
||||
"microsecond",
|
||||
"nanosecond",
|
||||
];
|
||||
for (const largestUnit of values) {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1970, 1);
|
||||
const other = new Temporal.PlainYearMonth(1970, 1);
|
||||
expect(() => {
|
||||
plainYearMonth.since(other, { largestUnit });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`${largestUnit} is not a valid value for option largestUnit`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("cannot compare dates from different calendars", () => {
|
||||
const plainYearMonthOne = new Temporal.PlainYearMonth(1970, 1, "iso8601");
|
||||
const plainYearMonthTwo = new Temporal.PlainYearMonth(1970, 1, "gregory");
|
||||
|
||||
expect(() => {
|
||||
plainYearMonthOne.since(plainYearMonthTwo);
|
||||
}).toThrowWithMessage(RangeError, "Cannot compare dates from two different calendars");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainYearMonth.prototype.until).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const values = [
|
||||
[[0, 1], [0, 1], "PT0S"],
|
||||
[[1, 2], [2, 3], "P1Y1M"],
|
||||
[[0, 1], [1, 2], "P1Y1M"],
|
||||
[[1, 2], [0, 1], "-P1Y1M"],
|
||||
[[0, 1], [0, 12], "P11M"],
|
||||
[[0, 12], [0, 1], "-P11M"],
|
||||
];
|
||||
for (const [args, argsOther, expected] of values) {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(...args);
|
||||
const other = new Temporal.PlainYearMonth(...argsOther);
|
||||
expect(plainYearMonth.until(other).toString()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
test("smallestUnit option", () => {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(0, 1);
|
||||
const other = new Temporal.PlainYearMonth(1, 2);
|
||||
const values = [
|
||||
["year", "P1Y"],
|
||||
["month", "P1Y1M"],
|
||||
];
|
||||
for (const [smallestUnit, expected] of values) {
|
||||
expect(plainYearMonth.until(other, { smallestUnit }).toString()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
test("largestUnit option", () => {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(0, 1);
|
||||
const other = new Temporal.PlainYearMonth(1, 2);
|
||||
const values = [
|
||||
["year", "P1Y1M"],
|
||||
["month", "P13M"],
|
||||
];
|
||||
for (const [largestUnit, expected] of values) {
|
||||
expect(plainYearMonth.until(other, { largestUnit }).toString()).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainYearMonth object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainYearMonth.prototype.until.call("foo", {});
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
|
||||
});
|
||||
|
||||
test("disallowed smallestUnit option values", () => {
|
||||
const values = [
|
||||
"week",
|
||||
"day",
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"millisecond",
|
||||
"microsecond",
|
||||
"nanosecond",
|
||||
];
|
||||
for (const smallestUnit of values) {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1970, 1);
|
||||
const other = new Temporal.PlainYearMonth(1970, 1);
|
||||
expect(() => {
|
||||
plainYearMonth.until(other, { smallestUnit });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`${smallestUnit} is not a valid value for option smallestUnit`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("disallowed largestUnit option values", () => {
|
||||
const values = [
|
||||
"week",
|
||||
"day",
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"millisecond",
|
||||
"microsecond",
|
||||
"nanosecond",
|
||||
];
|
||||
for (const largestUnit of values) {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1970, 1);
|
||||
const other = new Temporal.PlainYearMonth(1970, 1);
|
||||
expect(() => {
|
||||
plainYearMonth.until(other, { largestUnit });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`${largestUnit} is not a valid value for option largestUnit`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("cannot compare dates from different calendars", () => {
|
||||
const plainYearMonthOne = new Temporal.PlainYearMonth(1970, 1, "iso8601");
|
||||
const plainYearMonthTwo = new Temporal.PlainYearMonth(1970, 1, "gregory");
|
||||
|
||||
expect(() => {
|
||||
plainYearMonthOne.until(plainYearMonthTwo);
|
||||
}).toThrowWithMessage(RangeError, "Cannot compare dates from two different calendars");
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue