LibJS: Implement the Temporal.Duration relative-to ZonedDateTime options

This commit is contained in:
Timothy Flynn 2024-11-25 08:40:25 -05:00 committed by Andreas Kling
commit 18f95434bc
Notes: github-actions[bot] 2024-11-26 10:04:10 +00:00
9 changed files with 296 additions and 35 deletions

View file

@ -475,12 +475,16 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
if (value.is_undefined()) if (value.is_undefined())
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} }; return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} };
// FIXME: 3. Let offsetBehaviour be OPTION. // 3. Let offsetBehaviour be OPTION.
// FIXME: 4. Let matchBehaviour be MATCH-EXACTLY. auto offset_behavior = OffsetBehavior::Option;
// 4. Let matchBehaviour be MATCH-EXACTLY.
auto match_behavior = MatchBehavior::MatchExactly;
String calendar; String calendar;
Optional<String> time_zone; Optional<String> time_zone;
Optional<String> offset_string; Optional<String> offset_string;
ISODate iso_date; ISODate iso_date;
Variant<ParsedISODateTime::StartOfDay, Time> time { Time {} }; Variant<ParsedISODateTime::StartOfDay, Time> time { Time {} };
@ -488,8 +492,11 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
if (value.is_object()) { if (value.is_object()) {
auto& object = value.as_object(); auto& object = value.as_object();
// FIXME: a. If value has an [[InitializedTemporalZonedDateTime]] internal slot, then // a. If value has an [[InitializedTemporalZonedDateTime]] internal slot, then
// FIXME: i. Return the Record { [[PlainRelativeTo]]: undefined, [[ZonedRelativeTo]]: value }. if (is<ZonedDateTime>(object)) {
// i. Return the Record { [[PlainRelativeTo]]: undefined, [[ZonedRelativeTo]]: value }.
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = static_cast<ZonedDateTime&>(object) };
}
// b. If value has an [[InitializedTemporalDate]] internal slot, then // b. If value has an [[InitializedTemporalDate]] internal slot, then
if (is<PlainDate>(object)) { if (is<PlainDate>(object)) {
@ -527,7 +534,8 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
// i. If offsetString is UNSET, then // i. If offsetString is UNSET, then
if (!offset_string.has_value()) { if (!offset_string.has_value()) {
// FIXME: i. Set offsetBehaviour to WALL. // i. Set offsetBehaviour to WALL.
offset_behavior = OffsetBehavior::Wall;
} }
// j. Let isoDate be result.[[ISODate]]. // j. Let isoDate be result.[[ISODate]].
@ -563,14 +571,17 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
// ii. If result.[[TimeZone]].[[Z]] is true, then // ii. If result.[[TimeZone]].[[Z]] is true, then
if (result.time_zone.z_designator) { if (result.time_zone.z_designator) {
// FIXME: 1. Set offsetBehaviour to EXACT. // 1. Set offsetBehaviour to EXACT.
offset_behavior = OffsetBehavior::Exact;
} }
// iii. Else if offsetString is EMPTY, then // iii. Else if offsetString is EMPTY, then
else if (!offset_string.has_value()) { else if (!offset_string.has_value()) {
// FIXME: 1. Set offsetBehaviour to WALL. // 1. Set offsetBehaviour to WALL.
offset_behavior = OffsetBehavior::Wall;
} }
// FIXME: iv. Set matchBehaviour to MATCH-MINUTES. // iv. Set matchBehaviour to MATCH-MINUTES.
match_behavior = MatchBehavior::MatchMinutes;
} }
// g. Let calendar be result.[[Calendar]]. // g. Let calendar be result.[[Calendar]].
@ -596,15 +607,27 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
return RelativeTo { .plain_relative_to = plain_date, .zoned_relative_to = {} }; return RelativeTo { .plain_relative_to = plain_date, .zoned_relative_to = {} };
} }
// FIXME: 8. If offsetBehaviour is OPTION, then double offset_nanoseconds = 0;
// FIXME: a. Let offsetNs be ! ParseDateTimeUTCOffset(offsetString).
// FIXME: 9. Else,
// FIXME: a. Let offsetNs be 0.
// FIXME: 10. Let epochNanoseconds be ? InterpretISODateTimeOffset(isoDate, time, offsetBehaviour, offsetNs, timeZone, compatible, reject, matchBehaviour).
// FIXME: 11. Let zonedRelativeTo be ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
// FIXME: 12. Return the Record { [[PlainRelativeTo]]: undefined, [[ZonedRelativeTo]]: zonedRelativeTo }.
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} }; // 8. If offsetBehaviour is OPTION, then
if (offset_behavior == OffsetBehavior::Option) {
// a. Let offsetNs be ! ParseDateTimeUTCOffset(offsetString).
offset_nanoseconds = parse_date_time_utc_offset(*offset_string);
}
// 9. Else,
else {
// a. Let offsetNs be 0.
offset_nanoseconds = 0;
}
// 10. Let epochNanoseconds be ? InterpretISODateTimeOffset(isoDate, time, offsetBehaviour, offsetNs, timeZone, COMPATIBLE, REJECT, matchBehaviour).
auto epoch_nanoseconds = TRY(interpret_iso_date_time_offset(vm, iso_date, time, offset_behavior, offset_nanoseconds, *time_zone, Disambiguation::Compatible, OffsetOption::Reject, match_behavior));
// 11. Let zonedRelativeTo be ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
auto zoned_relative_to = MUST(create_temporal_zoned_date_time(vm, BigInt::create(vm, move(epoch_nanoseconds)), time_zone.release_value(), move(calendar)));
// 12. Return the Record { [[PlainRelativeTo]]: undefined, [[ZonedRelativeTo]]: zonedRelativeTo }.
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = zoned_relative_to };
} }
// 13.19 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits // 13.19 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits

View file

@ -143,8 +143,8 @@ struct SecondsStringPrecision {
}; };
struct RelativeTo { struct RelativeTo {
GC::Ptr<PlainDate> plain_relative_to; // [[PlainRelativeTo]] GC::Ptr<PlainDate> plain_relative_to; // [[PlainRelativeTo]]
GC::Ptr<Object> zoned_relative_to; // FIXME: [[ZonedRelativeTo]] GC::Ptr<ZonedDateTime> zoned_relative_to; // [[ZonedRelativeTo]]
}; };
struct DifferenceSettings { struct DifferenceSettings {

View file

@ -9,6 +9,7 @@
#include <LibJS/Runtime/Temporal/AbstractOperations.h> #include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Duration.h> #include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h> #include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -149,12 +150,25 @@ JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::compare)
// 12. If zonedRelativeTo is not undefined, and either TemporalUnitCategory(largestUnit1) or TemporalUnitCategory(largestUnit2) is date, then // 12. If zonedRelativeTo is not undefined, and either TemporalUnitCategory(largestUnit1) or TemporalUnitCategory(largestUnit2) is date, then
if (zoned_relative_to && (temporal_unit_category(largest_unit1) == UnitCategory::Date || temporal_unit_category(largest_unit2) == UnitCategory::Date)) { if (zoned_relative_to && (temporal_unit_category(largest_unit1) == UnitCategory::Date || temporal_unit_category(largest_unit2) == UnitCategory::Date)) {
// FIXME: a. Let timeZone be zonedRelativeTo.[[TimeZone]]. // a. Let timeZone be zonedRelativeTo.[[TimeZone]].
// FIXME: b. Let calendar be zonedRelativeTo.[[Calendar]]. auto const time_zone = zoned_relative_to->time_zone();
// FIXME: c. Let after1 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration1, constrain).
// FIXME: d. Let after2 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration2, constrain). // b. Let calendar be zonedRelativeTo.[[Calendar]].
// FIXME: e. If after1 > after2, return 1𝔽. auto const& calendar = zoned_relative_to->calendar();
// FIXME: f. If after1 < after2, return -1𝔽.
// c. Let after1 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration1, CONSTRAIN).
auto after1 = TRY(add_zoned_date_time(vm, zoned_relative_to->epoch_nanoseconds()->big_integer(), time_zone, calendar, duration1, Overflow::Constrain));
// d. Let after2 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration2, CONSTRAIN).
auto after2 = TRY(add_zoned_date_time(vm, zoned_relative_to->epoch_nanoseconds()->big_integer(), time_zone, calendar, duration2, Overflow::Constrain));
// e. If after1 > after2, return 1𝔽.
if (after1 > after2)
return 1;
// f. If after1 < after2, return -1𝔽.
if (after1 < after2)
return -1;
// g. Return +0𝔽. // g. Return +0𝔽.
return 0; return 0;

View file

@ -11,6 +11,7 @@
#include <LibJS/Runtime/Temporal/PlainDate.h> #include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h> #include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h> #include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -347,11 +348,20 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
// a. Let internalDuration be ToInternalDurationRecord(duration). // a. Let internalDuration be ToInternalDurationRecord(duration).
auto internal_duration = to_internal_duration_record(vm, duration); auto internal_duration = to_internal_duration_record(vm, duration);
// FIXME: b. Let timeZone be zonedRelativeTo.[[TimeZone]]. // b. Let timeZone be zonedRelativeTo.[[TimeZone]].
// FIXME: c. Let calendar be zonedRelativeTo.[[Calendar]]. auto const& time_zone = zoned_relative_to->time_zone();
// FIXME: d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]].
// FIXME: e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain). // c. Let calendar be zonedRelativeTo.[[Calendar]].
// FIXME: f. Set internalDuration to ? DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode). auto const& calendar = zoned_relative_to->calendar();
// d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]].
auto const& relative_epoch_nanoseconds = zoned_relative_to->epoch_nanoseconds()->big_integer();
// e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, CONSTRAIN).
auto target_epoch_nanoseconds = TRY(add_zoned_date_time(vm, relative_epoch_nanoseconds, time_zone, calendar, internal_duration, Overflow::Constrain));
// f. Set internalDuration to ? DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
internal_duration = TRY(difference_zoned_date_time_with_rounding(vm, relative_epoch_nanoseconds, target_epoch_nanoseconds, time_zone, calendar, largest_unit_value, rounding_increment, smallest_unit_value, rounding_mode));
// g. If TemporalUnitCategory(largestUnit) is date, set largestUnit to hour. // g. If TemporalUnitCategory(largestUnit) is date, set largestUnit to hour.
if (temporal_unit_category(largest_unit_value) == UnitCategory::Date) if (temporal_unit_category(largest_unit_value) == UnitCategory::Date)
@ -479,12 +489,23 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
// 11. If zonedRelativeTo is not undefined, then // 11. If zonedRelativeTo is not undefined, then
if (zoned_relative_to) { if (zoned_relative_to) {
// FIXME: a. Let internalDuration be ToInternalDurationRecord(duration). // a. Let internalDuration be ToInternalDurationRecord(duration).
// FIXME: b. Let timeZone be zonedRelativeTo.[[TimeZone]]. auto internal_duration = to_internal_duration_record(vm, duration);
// FIXME: c. Let calendar be zonedRelativeTo.[[Calendar]].
// FIXME: d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]]. // b. Let timeZone be zonedRelativeTo.[[TimeZone]].
// FIXME: e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain). auto const time_zone = zoned_relative_to->time_zone();
// FIXME: f. Let total be ? DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit).
// c. Let calendar be zonedRelativeTo.[[Calendar]].
auto const& calendar = zoned_relative_to->calendar();
// d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]].
auto const& relative_epoch_nanoseconds = zoned_relative_to->epoch_nanoseconds()->big_integer();
// e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, CONSTRAIN).
auto target_epoch_nanoseconds = TRY(add_zoned_date_time(vm, relative_epoch_nanoseconds, time_zone, calendar, internal_duration, Overflow::Constrain));
// f. Let total be ? DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit).
total = TRY(difference_zoned_date_time_with_total(vm, relative_epoch_nanoseconds, target_epoch_nanoseconds, time_zone, calendar, unit));
} }
// 12. Else if plainRelativeTo is not undefined, then // 12. Else if plainRelativeTo is not undefined, then
else if (plain_relative_to) { else if (plain_relative_to) {

View file

@ -9,9 +9,11 @@
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h> #include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Temporal/Calendar.h> #include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h> #include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h> #include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h> #include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h> #include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h> #include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h> #include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
@ -317,4 +319,152 @@ ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM& vm
return object; return object;
} }
// 6.5.5 AddZonedDateTime ( epochNanoseconds, timeZone, calendar, duration, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-addzoneddatetime
ThrowCompletionOr<Crypto::SignedBigInteger> add_zoned_date_time(VM& vm, Crypto::SignedBigInteger const& epoch_nanoseconds, StringView time_zone, StringView calendar, InternalDuration const& duration, Overflow overflow)
{
// 1. If DateDurationSign(duration.[[Date]]) = 0, then
if (date_duration_sign(duration.date) == 0) {
// a. Return ? AddInstant(epochNanoseconds, duration.[[Time]]).
return TRY(add_instant(vm, epoch_nanoseconds, duration.time));
}
// 2. Let isoDateTime be GetISODateTimeFor(timeZone, epochNanoseconds).
auto iso_date_time = get_iso_date_time_for(time_zone, epoch_nanoseconds);
// 3. Let addedDate be ? CalendarDateAdd(calendar, isoDateTime.[[ISODate]], duration.[[Date]], overflow).
auto added_date = TRY(calendar_date_add(vm, calendar, iso_date_time.iso_date, duration.date, overflow));
// 4. Let intermediateDateTime be CombineISODateAndTimeRecord(addedDate, isoDateTime.[[Time]]).
auto intermediate_date_time = combine_iso_date_and_time_record(added_date, iso_date_time.time);
// 5. If ISODateTimeWithinLimits(intermediateDateTime) is false, throw a RangeError exception.
if (!iso_date_time_within_limits(intermediate_date_time))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODateTime);
// 6. Let intermediateNs be ! GetEpochNanosecondsFor(timeZone, intermediateDateTime, COMPATIBLE).
auto intermediate_nanoseconds = MUST(get_epoch_nanoseconds_for(vm, time_zone, intermediate_date_time, Disambiguation::Compatible));
// 7. Return ? AddInstant(intermediateNs, duration.[[Time]]).
return TRY(add_instant(vm, intermediate_nanoseconds, duration.time));
}
// 6.5.6 DifferenceZonedDateTime ( ns1, ns2, timeZone, calendar, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetime
ThrowCompletionOr<InternalDuration> difference_zoned_date_time(VM& vm, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit largest_unit)
{
// 1. If ns1 = ns2, return ! CombineDateAndTimeDuration(ZeroDateDuration(), 0).
if (nanoseconds1 == nanoseconds2)
return MUST(combine_date_and_time_duration(vm, zero_date_duration(vm), TimeDuration { 0 }));
// 2. Let startDateTime be GetISODateTimeFor(timeZone, ns1).
auto start_date_time = get_iso_date_time_for(time_zone, nanoseconds1);
// 3. Let endDateTime be GetISODateTimeFor(timeZone, ns2).
auto end_date_time = get_iso_date_time_for(time_zone, nanoseconds2);
// 4. If ns2 - ns1 < 0, let sign be -1; else let sign be 1.
double sign = nanoseconds2 < nanoseconds1 ? -1 : 1;
// 5. If sign = 1, let maxDayCorrection be 2; else let maxDayCorrection be 1.
double max_day_correction = sign == 1 ? 2 : 1;
// 6. Let dayCorrection be 0.
double day_correction = 0;
// 7. Let timeDuration be DifferenceTime(startDateTime.[[Time]], endDateTime.[[Time]]).
auto time_duration = difference_time(start_date_time.time, end_date_time.time);
// 8. If TimeDurationSign(timeDuration) = -sign, set dayCorrection to dayCorrection + 1.
if (time_duration_sign(time_duration) == -sign)
++day_correction;
// 9. Let success be false.
auto success = false;
ISODateTime intermediate_date_time;
// 10. Repeat, while dayCorrection ≤ maxDayCorrection and success is false,
while (day_correction <= max_day_correction && !success) {
// a. Let intermediateDate be BalanceISODate(endDateTime.[[ISODate]].[[Year]], endDateTime.[[ISODate]].[[Month]], endDateTime.[[ISODate]].[[Day]] - dayCorrection × sign).
auto intermediate_date = balance_iso_date(end_date_time.iso_date.year, end_date_time.iso_date.month, static_cast<double>(end_date_time.iso_date.day) - (day_correction * sign));
// b. Let intermediateDateTime be CombineISODateAndTimeRecord(intermediateDate, startDateTime.[[Time]]).
intermediate_date_time = combine_iso_date_and_time_record(intermediate_date, start_date_time.time);
// c. Let intermediateNs be ? GetEpochNanosecondsFor(timeZone, intermediateDateTime, COMPATIBLE).
auto intermediate_nanoseconds = TRY(get_epoch_nanoseconds_for(vm, time_zone, intermediate_date_time, Disambiguation::Compatible));
// d. Set timeDuration to TimeDurationFromEpochNanosecondsDifference(ns2, intermediateNs).
time_duration = time_duration_from_epoch_nanoseconds_difference(nanoseconds2, intermediate_nanoseconds);
// e. Let timeSign be TimeDurationSign(timeDuration).
auto time_sign = time_duration_sign(time_duration);
// f. If sign ≠ -timeSign, then
if (sign != -time_sign) {
// i. Set success to true.
success = true;
}
// g. Set dayCorrection to dayCorrection + 1.
++day_correction;
}
// 11. Assert: success is true.
VERIFY(success);
// 12. Let dateLargestUnit be LargerOfTwoTemporalUnits(largestUnit, DAY).
auto date_largest_unit = larger_of_two_temporal_units(largest_unit, Unit::Day);
// 13. Let dateDifference be CalendarDateUntil(calendar, startDateTime.[[ISODate]], intermediateDateTime.[[ISODate]], dateLargestUnit).
auto date_difference = calendar_date_until(vm, calendar, start_date_time.iso_date, intermediate_date_time.iso_date, date_largest_unit);
// 14. Return ? CombineDateAndTimeDuration(dateDifference, timeDuration).
return TRY(combine_date_and_time_duration(vm, date_difference, move(time_duration)));
}
// 6.5.7 DifferenceZonedDateTimeWithRounding ( ns1, ns2, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetimewithrounding
ThrowCompletionOr<InternalDuration> difference_zoned_date_time_with_rounding(VM& vm, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit largest_unit, u64 rounding_increment, Unit smallest_unit, RoundingMode rounding_mode)
{
// 1. If TemporalUnitCategory(largestUnit) is TIME, then
if (temporal_unit_category(largest_unit) == UnitCategory::Time) {
// a. Return DifferenceInstant(ns1, ns2, roundingIncrement, smallestUnit, roundingMode).
return difference_instant(vm, nanoseconds1, nanoseconds2, rounding_increment, smallest_unit, rounding_mode);
}
// 2. Let difference be ? DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUnit).
auto difference = TRY(difference_zoned_date_time(vm, nanoseconds1, nanoseconds2, time_zone, calendar, largest_unit));
// 3. If smallestUnit is NANOSECOND and roundingIncrement = 1, return difference.
if (smallest_unit == Unit::Nanosecond && rounding_increment == 1)
return difference;
// 4. Let dateTime be GetISODateTimeFor(timeZone, ns1).
auto date_time = get_iso_date_time_for(time_zone, nanoseconds1);
// 5. Return ? RoundRelativeDuration(difference, ns2, dateTime, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
return TRY(round_relative_duration(vm, difference, nanoseconds2, date_time, time_zone, calendar, largest_unit, rounding_increment, smallest_unit, rounding_mode));
}
// 6.5.8 DifferenceZonedDateTimeWithTotal ( ns1, ns2, timeZone, calendar, unit ), https://tc39.es/proposal-temporal/#sec-temporal-differencezoneddatetimewithtotal
ThrowCompletionOr<Crypto::BigFraction> difference_zoned_date_time_with_total(VM& vm, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit unit)
{
// 1. If TemporalUnitCategory(unit) is TIME, then
if (temporal_unit_category(unit) == UnitCategory::Time) {
// a. Let difference be TimeDurationFromEpochNanosecondsDifference(ns2, ns1).
auto difference = time_duration_from_epoch_nanoseconds_difference(nanoseconds2, nanoseconds1);
// b. Return TotalTimeDuration(difference, unit).
return total_time_duration(difference, unit);
}
// 2. Let difference be ? DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, unit).
auto difference = TRY(difference_zoned_date_time(vm, nanoseconds1, nanoseconds2, time_zone, calendar, unit));
// 3. Let dateTime be GetISODateTimeFor(timeZone, ns1).
auto date_time = get_iso_date_time_for(time_zone, nanoseconds1);
// 4. Return ? TotalRelativeDuration(difference, ns2, dateTime, timeZone, calendar, unit).
return TRY(total_relative_duration(vm, difference, nanoseconds2, date_time, time_zone, calendar, unit));
}
} }

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibJS/Runtime/BigInt.h> #include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
@ -50,5 +51,9 @@ enum class MatchBehavior {
ThrowCompletionOr<Crypto::SignedBigInteger> interpret_iso_date_time_offset(VM&, ISODate, Variant<ParsedISODateTime::StartOfDay, Time> const&, OffsetBehavior, double offset_nanoseconds, StringView time_zone, Disambiguation, OffsetOption, MatchBehavior); ThrowCompletionOr<Crypto::SignedBigInteger> interpret_iso_date_time_offset(VM&, ISODate, Variant<ParsedISODateTime::StartOfDay, Time> const&, OffsetBehavior, double offset_nanoseconds, StringView time_zone, Disambiguation, OffsetOption, MatchBehavior);
ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM&, Value item, Value options = js_undefined()); ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM&, Value item, Value options = js_undefined());
ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM&, BigInt const& epoch_nanoseconds, String time_zone, String calendar, GC::Ptr<FunctionObject> new_target = {}); ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM&, BigInt const& epoch_nanoseconds, String time_zone, String calendar, GC::Ptr<FunctionObject> new_target = {});
ThrowCompletionOr<Crypto::SignedBigInteger> add_zoned_date_time(VM&, Crypto::SignedBigInteger const& epoch_nanoseconds, StringView time_zone, StringView calendar, InternalDuration const&, Overflow);
ThrowCompletionOr<InternalDuration> difference_zoned_date_time(VM&, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit largest_unit);
ThrowCompletionOr<InternalDuration> difference_zoned_date_time_with_rounding(VM&, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit largest_unit, u64 rounding_increment, Unit smallest_unit, RoundingMode);
ThrowCompletionOr<Crypto::BigFraction> difference_zoned_date_time_with_total(VM&, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit);
} }

View file

@ -47,6 +47,26 @@ describe("correct behavior", () => {
}); });
expect(result).toBe(-1); expect(result).toBe(-1);
}); });
test("relative to zoned date time", () => {
const oneMonth = new Temporal.Duration(0, 1);
const thirtyDays = new Temporal.Duration(0, 0, 0, 30);
let result = Temporal.Duration.compare(oneMonth, thirtyDays, {
relativeTo: Temporal.ZonedDateTime.from("2018-04-01[UTC]"),
});
expect(result).toBe(0);
result = Temporal.Duration.compare(oneMonth, thirtyDays, {
relativeTo: Temporal.ZonedDateTime.from("2018-03-01[UTC]"),
});
expect(result).toBe(1);
result = Temporal.Duration.compare(oneMonth, thirtyDays, {
relativeTo: Temporal.ZonedDateTime.from("2018-02-01[UTC]"),
});
expect(result).toBe(-1);
});
}); });
describe("errors", () => { describe("errors", () => {

View file

@ -98,6 +98,20 @@ describe("correct behavior", () => {
expect(result.months).toBe(1); expect(result.months).toBe(1);
}); });
}); });
test("relative to zoned date time", () => {
const duration = new Temporal.Duration(0, 0, 0, 31);
[
"2000-01-01[UTC]",
"2000-01-01T00:00[UTC]",
"2000-01-01T00:00+00:00[UTC]",
"2000-01-01T00:00+00:00[UTC][u-ca=iso8601]",
].forEach(relativeTo => {
const result = duration.round({ largestUnit: "months", relativeTo });
expect(result.months).toBe(1);
});
});
}); });
describe("errors", () => { describe("errors", () => {

View file

@ -25,6 +25,20 @@ describe("correct behavior", () => {
expect(result).toBe(1); expect(result).toBe(1);
}); });
}); });
test("relative to zoned date time", () => {
const duration = new Temporal.Duration(0, 0, 0, 31);
[
"2000-01-01[UTC]",
"2000-01-01T00:00[UTC]",
"2000-01-01T00:00+00:00[UTC]",
"2000-01-01T00:00+00:00[UTC][u-ca=iso8601]",
].forEach(relativeTo => {
const result = duration.total({ unit: "months", relativeTo });
expect(result).toBe(1);
});
});
}); });
describe("errors", () => { describe("errors", () => {