mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibJS: Implement Temporal.ZonedDateTime.prototype.add/subtract/equals
This commit is contained in:
parent
4ef21614e9
commit
336efa5e3f
Notes:
github-actions[bot]
2024-11-26 10:03:47 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/336efa5e3f4 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2577 Reviewed-by: https://github.com/shannonbooth ✅
9 changed files with 271 additions and 0 deletions
|
@ -353,6 +353,48 @@ ThrowCompletionOr<Crypto::SignedBigInteger> get_start_of_day(VM& vm, StringView
|
|||
TODO();
|
||||
}
|
||||
|
||||
// 11.1.15 TimeZoneEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals
|
||||
bool time_zone_equals(StringView one, StringView two)
|
||||
{
|
||||
// 1. If one is two, return true.
|
||||
if (one == two)
|
||||
return true;
|
||||
|
||||
// 2. Let offsetMinutesOne be ! ParseTimeZoneIdentifier(one).[[OffsetMinutes]].
|
||||
auto offset_minutes_one = parse_time_zone_identifier(one).offset_minutes;
|
||||
|
||||
// 3. Let offsetMinutesTwo be ! ParseTimeZoneIdentifier(two).[[OffsetMinutes]].
|
||||
auto offset_minutes_two = parse_time_zone_identifier(two).offset_minutes;
|
||||
|
||||
// 4. If offsetMinutesOne is EMPTY and offsetMinutesTwo is EMPTY, then
|
||||
if (!offset_minutes_one.has_value() && !offset_minutes_two.has_value()) {
|
||||
// a. Let recordOne be GetAvailableNamedTimeZoneIdentifier(one).
|
||||
auto record_one = Intl::get_available_named_time_zone_identifier(one);
|
||||
|
||||
// b. Let recordTwo be GetAvailableNamedTimeZoneIdentifier(two).
|
||||
auto record_two = Intl::get_available_named_time_zone_identifier(two);
|
||||
|
||||
// c. If recordOne is not EMPTY and recordTwo is not EMPTY and recordOne.[[PrimaryIdentifier]] is
|
||||
// recordTwo.[[PrimaryIdentifier]], return true.
|
||||
if (record_one.has_value() && record_two.has_value()) {
|
||||
if (record_one->primary_identifier == record_two->primary_identifier)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 5. Else,
|
||||
else {
|
||||
// a. If offsetMinutesOne is not EMPTY and offsetMinutesTwo is not EMPTY and offsetMinutesOne = offsetMinutesTwo,
|
||||
// return true.
|
||||
if (offset_minutes_one.has_value() && offset_minutes_two.has_value()) {
|
||||
if (offset_minutes_one == offset_minutes_two)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
||||
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM& vm, StringView identifier)
|
||||
{
|
||||
|
|
|
@ -33,6 +33,7 @@ ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM&, Strin
|
|||
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<Crypto::SignedBigInteger> get_start_of_day(VM&, StringView time_zone, ISODate);
|
||||
bool time_zone_equals(StringView one, StringView two);
|
||||
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM&, StringView identifier);
|
||||
TimeZone parse_time_zone_identifier(StringView identifier);
|
||||
TimeZone parse_time_zone_identifier(ParseResult const&);
|
||||
|
|
|
@ -524,4 +524,36 @@ ThrowCompletionOr<Crypto::BigFraction> difference_zoned_date_time_with_total(VM&
|
|||
return TRY(total_relative_duration(vm, difference, nanoseconds2, date_time, time_zone, calendar, unit));
|
||||
}
|
||||
|
||||
// 6.5.10 AddDurationToZonedDateTime ( operation, zonedDateTime, temporalDurationLike, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtozoneddatetime
|
||||
ThrowCompletionOr<GC::Ref<ZonedDateTime>> add_duration_to_zoned_date_time(VM& vm, ArithmeticOperation operation, ZonedDateTime const& zoned_date_time, Value temporal_duration_like, Value options)
|
||||
{
|
||||
// 1. Let duration be ? ToTemporalDuration(temporalDurationLike).
|
||||
auto duration = TRY(to_temporal_duration(vm, temporal_duration_like));
|
||||
|
||||
// 2. If operation is SUBTRACT, set duration to CreateNegatedTemporalDuration(duration).
|
||||
if (operation == ArithmeticOperation::Subtract)
|
||||
duration = create_negated_temporal_duration(vm, duration);
|
||||
|
||||
// 3. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// 4. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||||
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// 5. Let calendar be zonedDateTime.[[Calendar]].
|
||||
auto const& calendar = zoned_date_time.calendar();
|
||||
|
||||
// 6. Let timeZone be zonedDateTime.[[TimeZone]].
|
||||
auto const& time_zone = zoned_date_time.time_zone();
|
||||
|
||||
// 7. Let internalDuration be ToInternalDurationRecord(duration).
|
||||
auto internal_duration = to_internal_duration_record(vm, duration);
|
||||
|
||||
// 8. Let epochNanoseconds be ? AddZonedDateTime(zonedDateTime.[[EpochNanoseconds]], timeZone, calendar, internalDuration, overflow).
|
||||
auto epoch_nanoseconds = TRY(add_zoned_date_time(vm, zoned_date_time.epoch_nanoseconds()->big_integer(), time_zone, calendar, internal_duration, overflow));
|
||||
|
||||
// 9. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
|
||||
return MUST(create_temporal_zoned_date_time(vm, BigInt::create(vm, move(epoch_nanoseconds)), time_zone, calendar));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,5 +56,6 @@ ThrowCompletionOr<Crypto::SignedBigInteger> add_zoned_date_time(VM&, Crypto::Sig
|
|||
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);
|
||||
ThrowCompletionOr<GC::Ref<ZonedDateTime>> add_duration_to_zoned_date_time(VM&, ArithmeticOperation, ZonedDateTime const&, Value temporal_duration_like, Value options);
|
||||
|
||||
}
|
||||
|
|
|
@ -62,6 +62,9 @@ void ZonedDateTimePrototype::initialize(Realm& realm)
|
|||
define_native_accessor(realm, vm.names.offset, offset_getter, {}, Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.add, add, 1, attr);
|
||||
define_native_function(realm, vm.names.subtract, subtract, 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);
|
||||
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
|
||||
|
@ -342,6 +345,56 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::offset_getter)
|
|||
return PrimitiveString::create(vm, format_utc_offset_nanoseconds(offset_nanoseconds));
|
||||
}
|
||||
|
||||
// 6.3.35 Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.add
|
||||
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::add)
|
||||
{
|
||||
auto temporal_duration_like = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let zonedDateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
|
||||
auto zoned_date_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return ? AddDurationToZonedDateTime(ADD, zonedDateTime, temporalDurationLike, options).
|
||||
return TRY(add_duration_to_zoned_date_time(vm, ArithmeticOperation::Add, zoned_date_time, temporal_duration_like, options));
|
||||
}
|
||||
|
||||
// 6.3.36 Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.subtract
|
||||
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::subtract)
|
||||
{
|
||||
auto temporal_duration_like = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let zonedDateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
|
||||
auto zoned_date_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return ? AddDurationToZonedDateTime(SUBTRACT, zonedDateTime, temporalDurationLike, options).
|
||||
return TRY(add_duration_to_zoned_date_time(vm, ArithmeticOperation::Subtract, zoned_date_time, temporal_duration_like, options));
|
||||
}
|
||||
|
||||
// 6.3.40 Temporal.ZonedDateTime.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.equals
|
||||
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::equals)
|
||||
{
|
||||
// 1. Let zonedDateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
|
||||
auto zoned_date_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Set other to ? ToTemporalZonedDateTime(other).
|
||||
auto other = TRY(to_temporal_zoned_date_time(vm, vm.argument(0)));
|
||||
|
||||
// 4. If zonedDateTime.[[EpochNanoseconds]] ≠ other.[[EpochNanoseconds]], return false.
|
||||
if (zoned_date_time->epoch_nanoseconds()->big_integer() != other->epoch_nanoseconds()->big_integer())
|
||||
return false;
|
||||
|
||||
// 5. If TimeZoneEquals(zonedDateTime.[[TimeZone]], other.[[TimeZone]]) is false, return false.
|
||||
if (!time_zone_equals(zoned_date_time->time_zone(), other->time_zone()))
|
||||
return false;
|
||||
|
||||
// 6. Return CalendarEquals(zonedDateTime.[[Calendar]], other.[[Calendar]]).
|
||||
return calendar_equals(zoned_date_time->calendar(), other->calendar());
|
||||
}
|
||||
|
||||
// 6.3.41 Temporal.ZonedDateTime.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tostring
|
||||
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::to_string)
|
||||
{
|
||||
|
|
|
@ -51,6 +51,9 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(offset_nanoseconds_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(offset_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(add);
|
||||
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
||||
JS_DECLARE_NATIVE_FUNCTION(equals);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.ZonedDateTime.prototype.add).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 12, 0, 22, 30, 100, 200, 300);
|
||||
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
|
||||
const dayDuration = new Temporal.Duration(0, 0, 0, 1);
|
||||
const result = zonedDateTime.add(dayDuration);
|
||||
|
||||
expect(zonedDateTime.day).toBe(12);
|
||||
expect(zonedDateTime.epochNanoseconds).toBe(1636676550100200300n);
|
||||
|
||||
expect(result.day).toBe(13);
|
||||
expect(result.epochNanoseconds).toBe(1636762950100200300n);
|
||||
});
|
||||
|
||||
test("duration-like object", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 12, 0, 22, 30, 100, 200, 300);
|
||||
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
|
||||
const dayDuration = { days: 1 };
|
||||
const result = zonedDateTime.add(dayDuration);
|
||||
|
||||
expect(zonedDateTime.day).toBe(12);
|
||||
expect(zonedDateTime.epochNanoseconds).toBe(1636676550100200300n);
|
||||
|
||||
expect(result.day).toBe(13);
|
||||
expect(result.epochNanoseconds).toBe(1636762950100200300n);
|
||||
});
|
||||
|
||||
test("duration string", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 12, 0, 22, 30, 100, 200, 300);
|
||||
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
|
||||
const dayDuration = "P1D";
|
||||
const result = zonedDateTime.add(dayDuration);
|
||||
|
||||
expect(zonedDateTime.day).toBe(12);
|
||||
expect(zonedDateTime.epochNanoseconds).toBe(1636676550100200300n);
|
||||
|
||||
expect(result.day).toBe(13);
|
||||
expect(result.epochNanoseconds).toBe(1636762950100200300n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.ZonedDateTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.ZonedDateTime.prototype.add.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
|
||||
});
|
||||
|
||||
test("invalid duration-like object", () => {
|
||||
expect(() => {
|
||||
new Temporal.ZonedDateTime(1n, "UTC").add({});
|
||||
}).toThrowWithMessage(TypeError, "Invalid duration-like object");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.ZonedDateTime.prototype.equals).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const zonedDateTimeOne = new Temporal.ZonedDateTime(1n, "UTC");
|
||||
const zonedDateTimeTwo = new Temporal.ZonedDateTime(2n, "UTC");
|
||||
|
||||
expect(zonedDateTimeOne.equals(zonedDateTimeOne)).toBeTrue();
|
||||
expect(zonedDateTimeTwo.equals(zonedDateTimeTwo)).toBeTrue();
|
||||
expect(zonedDateTimeOne.equals(zonedDateTimeTwo)).toBeFalse();
|
||||
expect(zonedDateTimeTwo.equals(zonedDateTimeOne)).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.ZonedDateTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.ZonedDateTime.prototype.equals.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.ZonedDateTime.prototype.subtract).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 12, 0, 22, 30, 100, 200, 300);
|
||||
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
|
||||
const dayDuration = new Temporal.Duration(0, 0, 0, 1);
|
||||
const result = zonedDateTime.subtract(dayDuration);
|
||||
|
||||
expect(zonedDateTime.day).toBe(12);
|
||||
expect(zonedDateTime.epochNanoseconds).toBe(1636676550100200300n);
|
||||
|
||||
expect(result.day).toBe(11);
|
||||
expect(result.epochNanoseconds).toBe(1636590150100200300n);
|
||||
});
|
||||
|
||||
test("duration-like object", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 12, 0, 22, 30, 100, 200, 300);
|
||||
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
|
||||
const dayDuration = { days: 1 };
|
||||
const result = zonedDateTime.subtract(dayDuration);
|
||||
|
||||
expect(zonedDateTime.day).toBe(12);
|
||||
expect(zonedDateTime.epochNanoseconds).toBe(1636676550100200300n);
|
||||
|
||||
expect(result.day).toBe(11);
|
||||
expect(result.epochNanoseconds).toBe(1636590150100200300n);
|
||||
});
|
||||
|
||||
test("duration string", () => {
|
||||
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 12, 0, 22, 30, 100, 200, 300);
|
||||
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
|
||||
const dayDuration = "P1D";
|
||||
const result = zonedDateTime.subtract(dayDuration);
|
||||
|
||||
expect(zonedDateTime.day).toBe(12);
|
||||
expect(zonedDateTime.epochNanoseconds).toBe(1636676550100200300n);
|
||||
|
||||
expect(result.day).toBe(11);
|
||||
expect(result.epochNanoseconds).toBe(1636590150100200300n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.ZonedDateTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.ZonedDateTime.prototype.subtract.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
|
||||
});
|
||||
|
||||
test("invalid duration-like object", () => {
|
||||
expect(() => {
|
||||
new Temporal.ZonedDateTime(1n, "UTC").subtract({});
|
||||
}).toThrowWithMessage(TypeError, "Invalid duration-like object");
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue