Browse Source

LibJS: Implement Temporal.PlainTime.prototype.round

Luke Wilde 3 years ago
parent
commit
b83e3fd01d

+ 58 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp

@@ -46,6 +46,7 @@ void PlainTimePrototype::initialize(GlobalObject& global_object)
     define_native_function(vm.names.add, add, 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.subtract, subtract, 1, attr);
     define_native_function(vm.names.with, with, 1, attr);
     define_native_function(vm.names.with, with, 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.equals, equals, 1, attr);
     define_native_function(vm.names.toPlainDateTime, to_plain_date_time, 1, attr);
     define_native_function(vm.names.toPlainDateTime, to_plain_date_time, 1, attr);
     define_native_function(vm.names.toZonedDateTime, to_zoned_date_time, 1, attr);
     define_native_function(vm.names.toZonedDateTime, to_zoned_date_time, 1, attr);
@@ -267,6 +268,63 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::with)
     return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond));
     return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond));
 }
 }
 
 
+// 4.3.15 Temporal.PlainTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round
+JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::round)
+{
+    // 1. Let temporalTime be the this value.
+    // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
+    auto* temporal_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", "day" », undefined).
+    auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv, "day"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"));
+
+    double maximum;
+
+    // 8. If smallestUnit is "hour", then
+    if (smallest_unit == "hour"sv) {
+        // a. Let maximum be 24.
+        maximum = 24;
+    }
+    // 9. Else if smallestUnit is "minute" or "second", then
+    else if (smallest_unit == "minute"sv || smallest_unit == "second"sv) {
+        // a. Let maximum be 60.
+        maximum = 60;
+    }
+    // 10. Else,
+    else {
+        // a. Let maximum be 1000.
+        maximum = 1000;
+    }
+
+    // 11. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, false).
+    auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *options, maximum, false));
+
+    // 12. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode).
+    auto result = round_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode);
+
+    // 13. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
+    return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond));
+}
+
 // 4.3.16 Temporal.PlainTime.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals
 // 4.3.16 Temporal.PlainTime.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals
 JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::equals)
 JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::equals)
 {
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.h

@@ -30,6 +30,7 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(add);
     JS_DECLARE_NATIVE_FUNCTION(add);
     JS_DECLARE_NATIVE_FUNCTION(subtract);
     JS_DECLARE_NATIVE_FUNCTION(subtract);
     JS_DECLARE_NATIVE_FUNCTION(with);
     JS_DECLARE_NATIVE_FUNCTION(with);
+    JS_DECLARE_NATIVE_FUNCTION(round);
     JS_DECLARE_NATIVE_FUNCTION(equals);
     JS_DECLARE_NATIVE_FUNCTION(equals);
     JS_DECLARE_NATIVE_FUNCTION(to_plain_date_time);
     JS_DECLARE_NATIVE_FUNCTION(to_plain_date_time);
     JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time);
     JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time);

+ 119 - 0
Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.prototype.round.js

@@ -0,0 +1,119 @@
+describe("correct behavior", () => {
+    test("length is 1", () => {
+        expect(Temporal.PlainTime.prototype.round).toHaveLength(1);
+    });
+
+    test("basic functionality", () => {
+        const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
+
+        const firstRoundedPlainTime = plainTime.round({ smallestUnit: "minute" });
+        expect(firstRoundedPlainTime.hour).toBe(18);
+        expect(firstRoundedPlainTime.minute).toBe(15);
+        expect(firstRoundedPlainTime.second).toBe(0);
+        expect(firstRoundedPlainTime.millisecond).toBe(0);
+        expect(firstRoundedPlainTime.microsecond).toBe(0);
+        expect(firstRoundedPlainTime.nanosecond).toBe(0);
+
+        const secondRoundedPlainTime = plainTime.round({
+            smallestUnit: "minute",
+            roundingMode: "ceil",
+        });
+        expect(secondRoundedPlainTime.hour).toBe(18);
+        expect(secondRoundedPlainTime.minute).toBe(16);
+        expect(secondRoundedPlainTime.second).toBe(0);
+        expect(secondRoundedPlainTime.millisecond).toBe(0);
+        expect(secondRoundedPlainTime.microsecond).toBe(0);
+        expect(secondRoundedPlainTime.nanosecond).toBe(0);
+
+        const thirdRoundedPlainTime = plainTime.round({
+            smallestUnit: "minute",
+            roundingMode: "ceil",
+            roundingIncrement: 30,
+        });
+        expect(thirdRoundedPlainTime.hour).toBe(18);
+        expect(thirdRoundedPlainTime.minute).toBe(30);
+        expect(thirdRoundedPlainTime.second).toBe(0);
+        expect(thirdRoundedPlainTime.millisecond).toBe(0);
+        expect(thirdRoundedPlainTime.microsecond).toBe(0);
+        expect(thirdRoundedPlainTime.nanosecond).toBe(0);
+
+        const fourthRoundedPlainTime = plainTime.round({
+            smallestUnit: "minute",
+            roundingMode: "floor",
+            roundingIncrement: 30,
+        });
+        expect(fourthRoundedPlainTime.hour).toBe(18);
+        expect(fourthRoundedPlainTime.minute).toBe(0);
+        expect(fourthRoundedPlainTime.second).toBe(0);
+        expect(fourthRoundedPlainTime.millisecond).toBe(0);
+        expect(fourthRoundedPlainTime.microsecond).toBe(0);
+        expect(fourthRoundedPlainTime.nanosecond).toBe(0);
+
+        const fifthRoundedPlainTime = plainTime.round({
+            smallestUnit: "hour",
+            roundingMode: "halfExpand",
+            roundingIncrement: 4,
+        });
+        expect(fifthRoundedPlainTime.hour).toBe(20);
+        expect(fifthRoundedPlainTime.minute).toBe(0);
+        expect(fifthRoundedPlainTime.second).toBe(0);
+        expect(fifthRoundedPlainTime.millisecond).toBe(0);
+        expect(fifthRoundedPlainTime.microsecond).toBe(0);
+        expect(fifthRoundedPlainTime.nanosecond).toBe(0);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.PlainTime object", () => {
+        expect(() => {
+            Temporal.PlainTime.prototype.round.call("foo", {});
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
+    });
+
+    test("missing options object", () => {
+        expect(() => {
+            const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
+            plainTime.round();
+        }).toThrowWithMessage(TypeError, "Required options object is missing or undefined");
+    });
+
+    test("invalid rounding mode", () => {
+        expect(() => {
+            const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
+            plainTime.round({ smallestUnit: "second", roundingMode: "serenityOS" });
+        }).toThrowWithMessage(
+            RangeError,
+            "serenityOS is not a valid value for option roundingMode"
+        );
+    });
+
+    test("invalid smallest unit", () => {
+        expect(() => {
+            const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
+            plainTime.round({ smallestUnit: "serenityOS" });
+        }).toThrowWithMessage(
+            RangeError,
+            "serenityOS is not a valid value for option smallestUnit"
+        );
+    });
+
+    test("increment may not be NaN", () => {
+        expect(() => {
+            const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
+            plainTime.round({ smallestUnit: "second", roundingIncrement: NaN });
+        }).toThrowWithMessage(RangeError, "NaN is not a valid value for option roundingIncrement");
+    });
+
+    test("increment may not be smaller than 1 or larger than maximum", () => {
+        const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300);
+        expect(() => {
+            plainTime.round({ smallestUnit: "second", roundingIncrement: -1 });
+        }).toThrowWithMessage(RangeError, "-1 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "second", roundingIncrement: 0 });
+        }).toThrowWithMessage(RangeError, "0 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "second", roundingIncrement: Infinity });
+        }).toThrowWithMessage(RangeError, "inf is not a valid value for option roundingIncrement");
+    });
+});