mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 03:32:45 +00:00 
			
		
		
		
	LibJS: Implement Temporal.PlainTime.prototype.round
This commit is contained in:
		
							parent
							
								
									853fab352d
								
							
						
					
					
						commit
						b83e3fd01d
					
				
					 3 changed files with 178 additions and 0 deletions
				
			
		|  | @ -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) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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"); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Luke Wilde
						Luke Wilde