mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 19:38:12 +00:00
LibJS: Implement Temporal.ZonedDateTime.prototype.round
This commit is contained in:
parent
3450def494
commit
f6ab63993a
4 changed files with 193 additions and 0 deletions
|
@ -230,6 +230,7 @@
|
|||
M(TemporalObjectMustNotHave, "Object must not have a defined {} property") \
|
||||
M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \
|
||||
M(TemporalPropertyMustBePositiveInteger, "Property must be a positive integer") \
|
||||
M(TemporalZonedDateTimeRoundZeroLengthDay, "Cannot round a ZonedDateTime in a calendar that has zero-length days") \
|
||||
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
||||
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
|
||||
M(ToObjectNullOrUndefined, "ToObject on null or undefined") \
|
||||
|
|
|
@ -72,6 +72,7 @@ void ZonedDateTimePrototype::initialize(GlobalObject& global_object)
|
|||
define_native_function(vm.names.withCalendar, with_calendar, 1, attr);
|
||||
define_native_function(vm.names.add, add, 1, attr);
|
||||
define_native_function(vm.names.subtract, subtract, 1, attr);
|
||||
define_native_function(vm.names.round, round, 1, attr);
|
||||
define_native_function(vm.names.equals, equals, 1, attr);
|
||||
define_native_function(vm.names.toString, to_string, 0, attr);
|
||||
define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
|
||||
|
@ -941,6 +942,88 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::subtract)
|
|||
return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar));
|
||||
}
|
||||
|
||||
// 6.3.39 Temporal.ZonedDateTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round
|
||||
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::round)
|
||||
{
|
||||
// 1. Let zonedDateTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
|
||||
auto* zoned_date_time = TRY(typed_this_object(global_object));
|
||||
|
||||
// 3. If options is undefined, then
|
||||
if (vm.argument(0).is_undefined()) {
|
||||
// a. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(global_object, ErrorType::TemporalMissingOptionsObject);
|
||||
}
|
||||
|
||||
// 4. Set options to ? GetOptionsObject(options).
|
||||
auto* options = TRY(get_options_object(global_object, vm.argument(0)));
|
||||
|
||||
// 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week" », undefined).
|
||||
auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv }, {}));
|
||||
|
||||
// 6. If smallestUnit is undefined, throw a RangeError exception.
|
||||
if (!smallest_unit_value.has_value())
|
||||
return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit");
|
||||
|
||||
// NOTE: At this point smallest_unit_value can only be a string
|
||||
auto& smallest_unit = *smallest_unit_value;
|
||||
|
||||
// 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand").
|
||||
auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand"sv));
|
||||
|
||||
// 8. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit).
|
||||
auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *options, smallest_unit));
|
||||
|
||||
// 9. Let timeZone be zonedDateTime.[[TimeZone]].
|
||||
auto& time_zone = zoned_date_time->time_zone();
|
||||
|
||||
// 10. Let instant be ! CreateTemporalInstant(zonedDateTime.[[Nanoseconds]]).
|
||||
auto* instant = MUST(create_temporal_instant(global_object, zoned_date_time->nanoseconds()));
|
||||
|
||||
// 11. Let calendar be zonedDateTime.[[Calendar]].
|
||||
auto& calendar = zoned_date_time->calendar();
|
||||
|
||||
// 12. 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));
|
||||
|
||||
// 13. Let isoCalendar be ! GetISO8601Calendar().
|
||||
auto* iso_calendar = get_iso8601_calendar(global_object);
|
||||
|
||||
// 14. Let dtStart be ? CreateTemporalDateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], 0, 0, 0, 0, 0, 0, isoCalendar).
|
||||
auto* dt_start = TRY(create_temporal_date_time(global_object, temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), 0, 0, 0, 0, 0, 0, *iso_calendar));
|
||||
|
||||
// 15. Let instantStart be ? BuiltinTimeZoneGetInstantFor(timeZone, dtStart, "compatible").
|
||||
auto* instant_start = TRY(builtin_time_zone_get_instant_for(global_object, &time_zone, *dt_start, "compatible"sv));
|
||||
|
||||
// 16. Let startNs be instantStart.[[Nanoseconds]].
|
||||
auto& start_ns = instant_start->nanoseconds();
|
||||
|
||||
// 17. Let endNs be ? AddZonedDateTime(startNs, timeZone, zonedDateTime.[[Calendar]], 0, 0, 0, 1, 0, 0, 0, 0, 0, 0).
|
||||
// TODO: Shouldn't `zonedDateTime.[[Calendar]]` be `calendar` for consistency?
|
||||
auto* end_ns = TRY(add_zoned_date_time(global_object, start_ns, &time_zone, zoned_date_time->calendar(), 0, 0, 0, 1, 0, 0, 0, 0, 0, 0));
|
||||
|
||||
// 18. Let dayLengthNs be ℝ(endNs − startNs).
|
||||
auto day_length_ns = end_ns->big_integer().minus(start_ns.big_integer()).to_double();
|
||||
|
||||
// 19. If dayLengthNs is 0, then
|
||||
if (day_length_ns == 0) {
|
||||
// a. Throw a RangeError exception.
|
||||
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalZonedDateTimeRoundZeroLengthDay);
|
||||
}
|
||||
|
||||
// 20. Let roundResult be ! RoundISODateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode, dayLengthNs).
|
||||
auto round_result = round_iso_date_time(temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->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(), rounding_increment, smallest_unit, rounding_mode, day_length_ns);
|
||||
|
||||
// 21. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant).
|
||||
auto offset_nanoseconds = TRY(get_offset_nanoseconds_for(global_object, &time_zone, *instant));
|
||||
|
||||
// 22. Let epochNanoseconds be ? InterpretISODateTimeOffset(roundResult.[[Year]], roundResult.[[Month]], roundResult.[[Day]], roundResult.[[Hour]], roundResult.[[Minute]], roundResult.[[Second]], roundResult.[[Millisecond]], roundResult.[[Microsecond]], roundResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, "compatible", "prefer", match exactly).
|
||||
auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, round_result.year, round_result.month, round_result.day, round_result.hour, round_result.minute, round_result.second, round_result.millisecond, round_result.microsecond, round_result.nanosecond, OffsetBehavior::Option, offset_nanoseconds, &time_zone, "compatible"sv, "prefer"sv, MatchBehavior::MatchExactly));
|
||||
|
||||
// 23. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
|
||||
return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar));
|
||||
}
|
||||
|
||||
// 6.3.40 Temporal.ZonedDateTime.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.equals
|
||||
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::equals)
|
||||
{
|
||||
|
|
|
@ -56,6 +56,7 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(with_calendar);
|
||||
JS_DECLARE_NATIVE_FUNCTION(add);
|
||||
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
||||
JS_DECLARE_NATIVE_FUNCTION(round);
|
||||
JS_DECLARE_NATIVE_FUNCTION(equals);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.ZonedDateTime.prototype.round).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(
|
||||
1111111111111n,
|
||||
new Temporal.TimeZone("UTC")
|
||||
);
|
||||
expect(zonedDateTime.round({ smallestUnit: "second" }).epochNanoseconds).toBe(
|
||||
1111000000000n
|
||||
);
|
||||
expect(
|
||||
zonedDateTime.round({ smallestUnit: "second", roundingMode: "ceil" }).epochNanoseconds
|
||||
).toBe(1112000000000n);
|
||||
expect(
|
||||
zonedDateTime.round({
|
||||
smallestUnit: "minute",
|
||||
roundingIncrement: 30,
|
||||
roundingMode: "floor",
|
||||
}).epochNanoseconds
|
||||
).toBe(0n);
|
||||
expect(
|
||||
zonedDateTime.round({
|
||||
smallestUnit: "minute",
|
||||
roundingIncrement: 30,
|
||||
roundingMode: "halfExpand",
|
||||
}).epochNanoseconds
|
||||
).toBe(1800000000000n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.ZonedDateTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.ZonedDateTime.prototype.round.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
|
||||
});
|
||||
|
||||
test("missing options object", () => {
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(1n, new Temporal.TimeZone("UTC"));
|
||||
expect(() => {
|
||||
zonedDateTime.round();
|
||||
}).toThrowWithMessage(TypeError, "Required options object is missing or undefined");
|
||||
});
|
||||
|
||||
test("invalid rounding mode", () => {
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(1n, new Temporal.TimeZone("UTC"));
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "second", roundingMode: "serenityOS" });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"serenityOS is not a valid value for option roundingMode"
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid smallest unit", () => {
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(1n, new Temporal.TimeZone("UTC"));
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "serenityOS" });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"serenityOS is not a valid value for option smallestUnit"
|
||||
);
|
||||
});
|
||||
|
||||
test("increment may not be NaN", () => {
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(1n, new Temporal.TimeZone("UTC"));
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "second", roundingIncrement: NaN });
|
||||
}).toThrowWithMessage(RangeError, "NaN is not a valid value for option roundingIncrement");
|
||||
});
|
||||
|
||||
test("increment may smaller than 1 or larger than maximum", () => {
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(1n, new Temporal.TimeZone("UTC"));
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "second", roundingIncrement: -1 });
|
||||
}).toThrowWithMessage(RangeError, "-1 is not a valid value for option roundingIncrement");
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "second", roundingIncrement: 0 });
|
||||
}).toThrowWithMessage(RangeError, "0 is not a valid value for option roundingIncrement");
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "second", roundingIncrement: Infinity });
|
||||
}).toThrowWithMessage(RangeError, "inf is not a valid value for option roundingIncrement");
|
||||
});
|
||||
|
||||
test("calendar with zero-length days", () => {
|
||||
const calendar = {
|
||||
dateAdd(date) {
|
||||
return date;
|
||||
},
|
||||
};
|
||||
|
||||
const zonedDateTime = new Temporal.ZonedDateTime(
|
||||
1n,
|
||||
new Temporal.TimeZone("UTC"),
|
||||
calendar
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
zonedDateTime.round({ smallestUnit: "second" });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Cannot round a ZonedDateTime in a calendar that has zero-length days"
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue