From d49bbb1da3fcae89d5801535f4287beedfc4535d Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Tue, 2 Nov 2021 00:14:47 +0100 Subject: [PATCH] LibJS: Implement TODO()'d parts of balance_duration() Massive :yakstack:, so I understand why it was skipped in the first place :^) --- .../Runtime/Temporal/AbstractOperations.cpp | 29 ++++ .../Runtime/Temporal/AbstractOperations.h | 1 + .../LibJS/Runtime/Temporal/Calendar.cpp | 23 +++ .../LibJS/Runtime/Temporal/Calendar.h | 1 + .../LibJS/Runtime/Temporal/Duration.cpp | 15 +- .../LibJS/Runtime/Temporal/PlainDateTime.cpp | 54 ++++++ .../LibJS/Runtime/Temporal/PlainDateTime.h | 2 + .../LibJS/Runtime/Temporal/PlainTime.cpp | 34 ++++ .../LibJS/Runtime/Temporal/PlainTime.h | 2 + .../LibJS/Runtime/Temporal/ZonedDateTime.cpp | 156 ++++++++++++++++++ .../LibJS/Runtime/Temporal/ZonedDateTime.h | 9 + 11 files changed, 320 insertions(+), 6 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 01054b3e5b..c41634c2f7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -533,6 +533,35 @@ String larger_of_two_temporal_units(StringView unit1, StringView unit2) return "nanosecond"sv; } +// 13.24 MergeLargestUnitOption ( options, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-mergelargestunitoption +ThrowCompletionOr merge_largest_unit_option(GlobalObject& global_object, Object& options, String largest_unit) +{ + auto& vm = global_object.vm(); + + // 1. Let merged be ! OrdinaryObjectCreate(%Object.prototype%). + auto* merged = Object::create(global_object, global_object.object_prototype()); + + // 2. Let keys be ? EnumerableOwnPropertyNames(options, key). + auto keys = TRY(options.enumerable_own_property_names(Object::PropertyKind::Key)); + + // 3. For each element nextKey of keys, do + for (auto& key : keys) { + auto next_key = PropertyKey::from_value(global_object, key); + + // a. Let propValue be ? Get(options, nextKey). + auto prop_value = TRY(options.get(next_key)); + + // b. Perform ! CreateDataPropertyOrThrow(merged, nextKey, propValue). + MUST(merged->create_data_property_or_throw(next_key, prop_value)); + } + + // 4. Perform ! CreateDataPropertyOrThrow(merged, "largestUnit", largestUnit). + MUST(merged->create_data_property_or_throw(vm.names.largestUnit, js_string(vm, move(largest_unit)))); + + // 5. Return merged. + return merged; +} + // 13.25 MaximumTemporalDurationRoundingIncrement ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement Optional maximum_temporal_duration_rounding_increment(StringView unit) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 702a86730a..bf6f9939fd 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -99,6 +99,7 @@ ThrowCompletionOr to_largest_temporal_unit(GlobalObject&, Object const& ThrowCompletionOr> to_smallest_temporal_unit(GlobalObject&, Object const& normalized_options, Vector const& disallowed_units, Optional fallback); ThrowCompletionOr validate_temporal_unit_range(GlobalObject&, StringView largest_unit, StringView smallest_unit); String larger_of_two_temporal_units(StringView, StringView); +ThrowCompletionOr merge_largest_unit_option(GlobalObject&, Object& options, String largest_unit); Optional maximum_temporal_duration_rounding_increment(StringView unit); ThrowCompletionOr reject_temporal_calendar_type(GlobalObject&, Object&); String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant const& precision); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 73031d8398..6e2e3bbcd2 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -159,6 +159,29 @@ ThrowCompletionOr calendar_date_add(GlobalObject& global_object, Obj return static_cast(added_date_object); } +// 12.1.8 CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil +ThrowCompletionOr calendar_date_until(GlobalObject& global_object, Object& calendar, PlainDate& one, PlainDate& two, Object& options, FunctionObject* date_until) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(calendar) is Object. + + // 2. If dateUntil is not present, set dateUntil to ? GetMethod(calendar, "dateUntil"). + if (!date_until) + date_until = TRY(Value(&calendar).get_method(global_object, vm.names.dateUntil)); + + // 3. Let duration be ? Call(dateUntil, calendar, « one, two, options »). + auto duration = TRY(call(global_object, date_until ?: js_undefined(), &calendar, &one, &two, &options)); + + // 4. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + auto* duration_object = TRY(duration.to_object(global_object)); + if (!is(duration_object)) + return vm.throw_completion(global_object, ErrorType::NotAnObjectOfType, "Temporal.Duration"); + + // 5. Return duration. + return static_cast(duration_object); +} + // 12.1.9 CalendarYear ( calendar, dateLike ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryear ThrowCompletionOr calendar_year(GlobalObject& global_object, Object& calendar, Object& date_like) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index 387e2e6da8..a330fb807b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -37,6 +37,7 @@ Calendar* get_iso8601_calendar(GlobalObject&); ThrowCompletionOr> calendar_fields(GlobalObject&, Object& calendar, Vector const& field_names); ThrowCompletionOr calendar_merge_fields(GlobalObject&, Object& calendar, Object& fields, Object& additional_fields); ThrowCompletionOr calendar_date_add(GlobalObject&, Object& calendar, PlainDate&, Duration&, Object* options, FunctionObject* date_add = nullptr); +ThrowCompletionOr calendar_date_until(GlobalObject&, Object& calendar, PlainDate& one, PlainDate& two, Object& options, FunctionObject* date_until = nullptr); ThrowCompletionOr calendar_year(GlobalObject&, Object& calendar, Object& date_like); ThrowCompletionOr calendar_month(GlobalObject&, Object& calendar, Object& date_like); ThrowCompletionOr calendar_month_code(GlobalObject&, Object& calendar, Object& date_like); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp index 6f3975438c..2fc870e612 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -294,16 +294,19 @@ BigInt* total_duration_nanoseconds(GlobalObject& global_object, double days, dou ThrowCompletionOr balance_duration(GlobalObject& global_object, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, String const& largest_unit, Object* relative_to) { auto& vm = global_object.vm(); + // 1. If relativeTo is not present, set relativeTo to undefined. Crypto::SignedBigInteger total_nanoseconds; // 2. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then if (relative_to && is(*relative_to)) { + auto& relative_to_zoned_date_time = static_cast(*relative_to); + // a. Let endNs be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). - TODO(); - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + auto* end_ns = TRY(add_zoned_date_time(global_object, relative_to_zoned_date_time.nanoseconds(), &relative_to_zoned_date_time.time_zone(), relative_to_zoned_date_time.calendar(), 0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds.big_integer().to_double())); + // b. Set nanoseconds to endNs − relativeTo.[[Nanoseconds]]. + total_nanoseconds = end_ns->big_integer().minus(relative_to_zoned_date_time.nanoseconds().big_integer()); } // 3. Else, else { @@ -314,13 +317,13 @@ ThrowCompletionOr balance_duration(GlobalObject& global_object // 4. If largestUnit is one of "year", "month", "week", or "day", then if (largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) { // a. Let result be ? NanosecondsToDays(nanoseconds, relativeTo). - TODO(); - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + auto result = TRY(nanoseconds_to_days(global_object, *js_bigint(vm, total_nanoseconds), relative_to ?: js_undefined())); // b. Set days to result.[[Days]]. + days = result.days; // c. Set nanoseconds to result.[[Nanoseconds]]. + total_nanoseconds = result.nanoseconds.cell()->big_integer(); } // 5. Else, else { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index 2a9455db01..7468c4da66 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -333,4 +333,58 @@ ThrowCompletionOr add_date_time(GlobalObject& global_obje return TemporalPlainDateTime { .year = added_date->iso_year(), .month = added_date->iso_month(), .day = added_date->iso_day(), .hour = time_result.hour, .minute = time_result.minute, .second = time_result.second, .millisecond = time_result.millisecond, .microsecond = time_result.microsecond, .nanosecond = time_result.nanosecond }; } +// 5.5.11 DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2, calendar, largestUnit [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-differenceisodatetime +ThrowCompletionOr difference_iso_date_time(GlobalObject& global_object, i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2, Object& calendar, StringView largest_unit, Object* options) +{ + auto& vm = global_object.vm(); + + // 1. Assert: y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, and ns2 are integers. + + // 2. If options is not present, set options to ! OrdinaryObjectCreate(null). + if (!options) + options = Object::create(global_object, nullptr); + + // 3. Let timeDifference be ! DifferenceTime(h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2). + auto time_difference = difference_time(hour1, minute1, second1, millisecond1, microsecond1, nanosecond1, hour2, minute2, second2, millisecond2, microsecond2, nanosecond2); + + // 4. Let timeSign be ! DurationSign(0, 0, 0, timeDifference.[[Days]], timeDifference.[[Hours]], timeDifference.[[Minutes]], timeDifference.[[Seconds]], timeDifference.[[Milliseconds]], timeDifference.[[Microseconds]], timeDifference.[[Nanoseconds]]). + auto time_sign = duration_sign(0, 0, 0, time_difference.days, time_difference.hours, time_difference.minutes, time_difference.seconds, time_difference.milliseconds, time_difference.microseconds, time_difference.nanoseconds); + + // 5. Let dateSign be ! CompareISODate(y2, mon2, d2, y1, mon1, d1). + auto date_sign = compare_iso_date(year2, month2, day2, year1, month1, day1); + + // 6. Let balanceResult be ! BalanceISODate(y1, mon1, d1 + timeDifference.[[Days]]). + auto balance_result = balance_iso_date(year1, month1, day1 + time_difference.days); + + // 7. If timeSign is -dateSign, then + if (time_sign == -date_sign) { + // a. Set balanceResult to ! BalanceISODate(balanceResult.[[Year]], balanceResult.[[Month]], balanceResult.[[Day]] - timeSign). + balance_result = balance_iso_date(balance_result.year, balance_result.month, balance_result.day - time_sign); + + // b. Set timeDifference to ? BalanceDuration(-timeSign, timeDifference.[[Hours]], timeDifference.[[Minutes]], timeDifference.[[Seconds]], timeDifference.[[Milliseconds]], timeDifference.[[Microseconds]], timeDifference.[[Nanoseconds]], largestUnit). + time_difference = TRY(balance_duration(global_object, -time_sign, time_difference.hours, time_difference.minutes, time_difference.seconds, time_difference.milliseconds, time_difference.microseconds, *js_bigint(vm, { (i32)time_difference.nanoseconds }), largest_unit)); + } + + // 8. Let date1 be ? CreateTemporalDate(balanceResult.[[Year]], balanceResult.[[Month]], balanceResult.[[Day]], calendar). + auto* date1 = TRY(create_temporal_date(global_object, balance_result.year, balance_result.month, balance_result.day, calendar)); + + // 9. Let date2 be ? CreateTemporalDate(y2, mon2, d2, calendar). + auto* date2 = TRY(create_temporal_date(global_object, year2, month2, day2, calendar)); + + // 10. Let dateLargestUnit be ! LargerOfTwoTemporalUnits("day", largestUnit). + auto date_largest_unit = larger_of_two_temporal_units("day"sv, largest_unit); + + // 11. Let untilOptions be ? MergeLargestUnitOption(options, dateLargestUnit). + auto* until_options = TRY(merge_largest_unit_option(global_object, *options, move(date_largest_unit))); + + // 12. Let dateDifference be ? CalendarDateUntil(calendar, date1, date2, untilOptions). + auto* date_difference = TRY(calendar_date_until(global_object, calendar, *date1, *date2, *until_options)); + + // 13. Let balanceResult be ? BalanceDuration(dateDifference.[[Days]], timeDifference.[[Hours]], timeDifference.[[Minutes]], timeDifference.[[Seconds]], timeDifference.[[Milliseconds]], timeDifference.[[Microseconds]], timeDifference.[[Nanoseconds]], largestUnit). + auto balance_result_ = TRY(balance_duration(global_object, date_difference->days(), time_difference.hours, time_difference.minutes, time_difference.seconds, time_difference.milliseconds, time_difference.microseconds, *js_bigint(vm, { (i32)time_difference.nanoseconds }), largest_unit)); + + // 14. Return the Record { [[Years]]: dateDifference.[[Years]], [[Months]]: dateDifference.[[Months]], [[Weeks]]: dateDifference.[[Weeks]], [[Days]]: balanceResult.[[Days]], [[Hours]]: balanceResult.[[Hours]], [[Minutes]]: balanceResult.[[Minutes]], [[Seconds]]: balanceResult.[[Seconds]], [[Milliseconds]]: balanceResult.[[Milliseconds]], [[Microseconds]]: balanceResult.[[Microseconds]], [[Nanoseconds]]: balanceResult.[[Nanoseconds]] }. + return TemporalDuration { .years = date_difference->years(), .months = date_difference->months(), .weeks = date_difference->weeks(), .days = balance_result_.days, .hours = balance_result_.hours, .minutes = balance_result_.minutes, .seconds = balance_result_.seconds, .milliseconds = balance_result_.milliseconds, .microseconds = balance_result_.microseconds, .nanoseconds = balance_result_.nanoseconds }; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h index 51ea00aca6..601b9efa8b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace JS::Temporal { @@ -71,5 +72,6 @@ ThrowCompletionOr create_temporal_date_time(GlobalObject&, i32 i ThrowCompletionOr temporal_date_time_to_string(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Value calendar, Variant const& precision, StringView show_calendar); i8 compare_iso_date_time(i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2); ThrowCompletionOr add_date_time(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options); +ThrowCompletionOr difference_iso_date_time(GlobalObject&, i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2, Object& calendar, StringView largest_unit, Object* options = nullptr); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp index 4304150311..ec5db05d7b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,39 @@ void PlainTime::visit_edges(Visitor& visitor) visitor.visit(&m_calendar); } +// 4.5.1 DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 ), https://tc39.es/proposal-temporal/#sec-temporal-differencetime +BalancedDuration difference_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2) +{ + // Assert: h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, and ns2 are integers. + + // 2. Let hours be h2 − h1. + auto hours = hour2 - hour1; + + // 3. Let minutes be min2 − min1. + auto minutes = minute2 - minute1; + + // 4. Let seconds be s2 − s1. + auto seconds = second2 - second1; + + // 5. Let milliseconds be ms2 − ms1. + auto milliseconds = millisecond2 - millisecond1; + + // 6. Let microseconds be mus2 − mus1. + auto microseconds = microsecond2 - microsecond1; + + // 7. Let nanoseconds be ns2 − ns1. + auto nanoseconds = nanosecond2 - nanosecond1; + + // 8. Let sign be ! DurationSign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + auto sign = duration_sign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + + // 9. Let bt be ! BalanceTime(hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). + auto bt = balance_time(hours * sign, minutes * sign, seconds * sign, milliseconds * sign, microseconds * sign, nanoseconds * sign); + + // 10. Return the Record { [[Days]]: bt.[[Days]] × sign, [[Hours]]: bt.[[Hour]] × sign, [[Minutes]]: bt.[[Minute]] × sign, [[Seconds]]: bt.[[Second]] × sign, [[Milliseconds]]: bt.[[Millisecond]] × sign, [[Microseconds]]: bt.[[Microsecond]] × sign, [[Nanoseconds]]: bt.[[Nanosecond]] × sign }. + return BalancedDuration { .days = static_cast(bt.days * sign), .hours = static_cast(bt.hour * sign), .minutes = static_cast(bt.minute * sign), .seconds = static_cast(bt.second * sign), .milliseconds = static_cast(bt.millisecond * sign), .microseconds = static_cast(bt.microsecond * sign), .nanoseconds = static_cast(bt.nanosecond * sign) }; +} + // 4.5.2 ToTemporalTime ( item [ , overflow ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime ThrowCompletionOr to_temporal_time(GlobalObject& global_object, Value item, Optional overflow) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h index 3e394bdbef..267bf59c87 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace JS::Temporal { @@ -92,6 +93,7 @@ auto temporal_time_like_properties = [](VM& vm) { }; }; +BalancedDuration difference_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2); ThrowCompletionOr to_temporal_time(GlobalObject&, Value item, Optional overflow = {}); ThrowCompletionOr to_partial_time(GlobalObject&, Object& temporal_time_like); ThrowCompletionOr regulate_time(GlobalObject&, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, StringView overflow); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index 8714bbf099..74b0c733a8 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -6,7 +6,12 @@ #include #include +#include +#include #include +#include +#include +#include #include #include @@ -54,4 +59,155 @@ ThrowCompletionOr create_temporal_zoned_date_time(GlobalObject& return object; } +// 6.5.5 AddZonedDateTime ( epochNanoseconds, timeZone, calendar, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-addzoneddatetime +ThrowCompletionOr add_zoned_date_time(GlobalObject& global_object, BigInt const& epoch_nanoseconds, Value time_zone, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options) +{ + // 1. If options is not present, set options to ! OrdinaryObjectCreate(null). + if (!options) + options = Object::create(global_object, nullptr); + + // 2. If all of years, months, weeks, and days are 0, then + if (years == 0 && months == 0 && weeks == 0 && days == 0) { + // a. Return ! AddInstant(epochNanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + return MUST(add_instant(global_object, epoch_nanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); + } + + // 3. Let instant be ! CreateTemporalInstant(epochNanoseconds). + auto* instant = MUST(create_temporal_instant(global_object, epoch_nanoseconds)); + + // 4. Let temporalDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar). + auto* temporal_date_time = TRY(builtin_time_zone_get_plain_date_time_for(global_object, time_zone, *instant, calendar)); + + // 5. Let datePart be ? CreateTemporalDate(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], calendar). + auto* date_part = TRY(create_temporal_date(global_object, temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), calendar)); + + // 6. Let dateDuration be ? CreateTemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0). + auto* date_duration = TRY(create_temporal_duration(global_object, years, months, weeks, days, 0, 0, 0, 0, 0, 0)); + + // 7. Let addedDate be ? CalendarDateAdd(calendar, datePart, dateDuration, options). + auto* added_date = TRY(calendar_date_add(global_object, calendar, *date_part, *date_duration, options)); + + // 8. Let intermediateDateTime be ? CreateTemporalDateTime(addedDate.[[ISOYear]], addedDate.[[ISOMonth]], addedDate.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], calendar). + auto* intermediate_date_time = TRY(create_temporal_date_time(global_object, added_date->iso_year(), added_date->iso_month(), added_date->iso_day(), temporal_date_time->iso_hour(), temporal_date_time->iso_minute(), temporal_date_time->iso_second(), temporal_date_time->iso_millisecond(), temporal_date_time->iso_microsecond(), temporal_date_time->iso_nanosecond(), calendar)); + + // 9. Let intermediateInstant be ? BuiltinTimeZoneGetInstantFor(timeZone, intermediateDateTime, "compatible"). + auto* intermediate_instant = TRY(builtin_time_zone_get_instant_for(global_object, time_zone, *intermediate_date_time, "compatible"sv)); + + // 10. Return ! AddInstant(intermediateInstant.[[Nanoseconds]], hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + return MUST(add_instant(global_object, intermediate_instant->nanoseconds(), hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); +} + +// 6.5.7 NanosecondsToDays ( nanoseconds, relativeTo ), https://tc39.es/proposal-temporal/#sec-temporal-nanosecondstodays +ThrowCompletionOr nanoseconds_to_days(GlobalObject& global_object, BigInt const& nanoseconds_bigint, Value relative_to_value) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(nanoseconds) is BigInt. + + // 2. Set nanoseconds to ℝ(nanoseconds). + auto nanoseconds = nanoseconds_bigint.big_integer(); + + // 3. Let sign be ! ℝ(Sign(𝔽(nanoseconds))). + i8 sign; + if (nanoseconds == Crypto::UnsignedBigInteger { 0 }) + sign = 0; + else if (nanoseconds.is_negative()) + sign = -1; + else + sign = 1; + + // 4. Let dayLengthNs be 8.64 × 10^13. + auto day_length_ns = "86400000000000"_sbigint; + + // 5. If sign is 0, then + if (sign == 0) { + // a. Return the Record { [[Days]]: 0, [[Nanoseconds]]: 0, [[DayLength]]: dayLengthNs }. + return NanosecondsToDaysResult { .days = 0, .nanoseconds = make_handle(js_bigint(vm, { 0 })), .day_length = day_length_ns.to_double() }; + } + + // 6. If Type(relativeTo) is not Object or relativeTo does not have an [[InitializedTemporalZonedDateTime]] internal slot, then + if (!relative_to_value.is_object() || !is(relative_to_value.as_object())) { + // a. Return the Record { [[Days]]: the integral part of nanoseconds / dayLengthNs, [[Nanoseconds]]: (abs(nanoseconds) modulo dayLengthNs) × sign, [[DayLength]]: dayLengthNs }. + return NanosecondsToDaysResult { + .days = nanoseconds.divided_by(day_length_ns).quotient.to_double(), + .nanoseconds = make_handle(js_bigint(vm, Crypto::SignedBigInteger { nanoseconds.unsigned_value() }.divided_by(day_length_ns).remainder.multiplied_by(Crypto::SignedBigInteger { sign }))), + .day_length = day_length_ns.to_double() + }; + } + + auto& relative_to = static_cast(relative_to_value.as_object()); + + // 7. Let startNs be ℝ(relativeTo.[[Nanoseconds]]). + auto& start_ns = relative_to.nanoseconds().big_integer(); + + // 8. Let startInstant be ! CreateTemporalInstant(ℤ(startNs)). + auto* start_instant = MUST(create_temporal_instant(global_object, *js_bigint(vm, start_ns))); + + // 9. Let startDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(relativeTo.[[TimeZone]], startInstant, relativeTo.[[Calendar]]). + auto* start_date_time = TRY(builtin_time_zone_get_plain_date_time_for(global_object, &relative_to.time_zone(), *start_instant, relative_to.calendar())); + + // 10. Let endNs be startNs + nanoseconds. + auto end_ns = start_ns.plus(nanoseconds); + + // 11. Let endInstant be ! CreateTemporalInstant(ℤ(endNs)). + auto* end_instant = MUST(create_temporal_instant(global_object, *js_bigint(vm, end_ns))); + + // 12. Let endDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(relativeTo.[[TimeZone]], endInstant, relativeTo.[[Calendar]]). + auto* end_date_time = TRY(builtin_time_zone_get_plain_date_time_for(global_object, &relative_to.time_zone(), *end_instant, relative_to.calendar())); + + // 13. Let dateDifference be ? DifferenceISODateTime(startDateTime.[[ISOYear]], startDateTime.[[ISOMonth]], startDateTime.[[ISODay]], startDateTime.[[ISOHour]], startDateTime.[[ISOMinute]], startDateTime.[[ISOSecond]], startDateTime.[[ISOMillisecond]], startDateTime.[[ISOMicrosecond]], startDateTime.[[ISONanosecond]], endDateTime.[[ISOYear]], endDateTime.[[ISOMonth]], endDateTime.[[ISODay]], endDateTime.[[ISOHour]], endDateTime.[[ISOMinute]], endDateTime.[[ISOSecond]], endDateTime.[[ISOMillisecond]], endDateTime.[[ISOMicrosecond]], endDateTime.[[ISONanosecond]], relativeTo.[[Calendar]], "day"). + auto date_difference = TRY(difference_iso_date_time(global_object, start_date_time->iso_year(), start_date_time->iso_month(), start_date_time->iso_day(), start_date_time->iso_hour(), start_date_time->iso_minute(), start_date_time->iso_second(), start_date_time->iso_millisecond(), start_date_time->iso_microsecond(), start_date_time->iso_nanosecond(), end_date_time->iso_year(), end_date_time->iso_month(), end_date_time->iso_day(), end_date_time->iso_hour(), end_date_time->iso_minute(), end_date_time->iso_second(), end_date_time->iso_millisecond(), end_date_time->iso_microsecond(), end_date_time->iso_nanosecond(), relative_to.calendar(), "day"sv)); + + // 14. Let days be dateDifference.[[Days]]. + auto days = date_difference.days; + + // 15. Let intermediateNs be ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)). + auto intermediate_ns = TRY(add_zoned_date_time(global_object, *js_bigint(vm, start_ns), &relative_to.time_zone(), relative_to.calendar(), 0, 0, 0, days, 0, 0, 0, 0, 0, 0))->big_integer(); + + // 16. If sign is 1, then + if (sign == 1) { + // a. Repeat, while days > 0 and intermediateNs > endNs, + while (days > 0 && intermediate_ns > end_ns) { + // i. Set days to days − 1. + days--; + + // ii. Set intermediateNs to ℝ(? AddZonedDateTime(ℤ(startNs), relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, 0, 0, 0, 0, 0, 0)). + intermediate_ns = TRY(add_zoned_date_time(global_object, *js_bigint(vm, start_ns), &relative_to.time_zone(), relative_to.calendar(), 0, 0, 0, days, 0, 0, 0, 0, 0, 0))->big_integer(); + } + } + + // 17. Set nanoseconds to endNs − intermediateNs. + nanoseconds = end_ns.minus(intermediate_ns); + + // 18. Let done be false. + // 19. Repeat, while done is false, + while (true) { + // a. Let oneDayFartherNs be ℝ(? AddZonedDateTime(ℤ(intermediateNs), relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, sign, 0, 0, 0, 0, 0, 0)). + auto one_day_farther_ns = TRY(add_zoned_date_time(global_object, *js_bigint(vm, intermediate_ns), &relative_to.time_zone(), relative_to.calendar(), 0, 0, 0, sign, 0, 0, 0, 0, 0, 0))->big_integer(); + + // b. Set dayLengthNs to oneDayFartherNs − intermediateNs. + day_length_ns = one_day_farther_ns.minus(intermediate_ns); + + // c. If (nanoseconds − dayLengthNs) × sign ≥ 0, then + if (nanoseconds.minus(day_length_ns).multiplied_by(Crypto::SignedBigInteger { sign }) >= "0"_sbigint) { + // i. Set nanoseconds to nanoseconds − dayLengthNs. + nanoseconds = nanoseconds.minus(day_length_ns); + + // ii. Set intermediateNs to oneDayFartherNs. + intermediate_ns = move(one_day_farther_ns); + + // iii. Set days to days + sign. + days += sign; + } + // d. Else, + else { + // i. Set done to true. + break; + } + } + + // 20. Return the Record { [[Days]]: days, [[Nanoseconds]]: nanoseconds, [[DayLength]]: abs(dayLengthNs) }. + return NanosecondsToDaysResult { .days = days, .nanoseconds = make_handle(js_bigint(vm, move(nanoseconds))), .day_length = fabs(day_length_ns.to_double()) }; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h index 8bd512ad9f..855793723d 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -33,6 +34,14 @@ private: Object& m_calendar; // [[Calendar]] }; +struct NanosecondsToDaysResult { + double days; + Handle nanoseconds; + double day_length; +}; + ThrowCompletionOr create_temporal_zoned_date_time(GlobalObject&, BigInt const& epoch_nanoseconds, Object& time_zone, Object& calendar, FunctionObject const* new_target = nullptr); +ThrowCompletionOr add_zoned_date_time(GlobalObject&, BigInt const& epoch_nanoseconds, Value time_zone, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options = nullptr); +ThrowCompletionOr nanoseconds_to_days(GlobalObject&, BigInt const& nanoseconds, Value relative_to); }