LibJS: Implement stringification Temporal.PlainTime prototypes
This commit is contained in:
parent
66365fef57
commit
ab3c825fa8
Notes:
github-actions[bot]
2024-11-24 00:37:56 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/ab3c825fa8f Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2535
7 changed files with 270 additions and 0 deletions
Libraries/LibJS
Runtime/Temporal
Tests/builtins/Temporal/PlainTime
|
@ -441,6 +441,16 @@ ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM& vm, Object const
|
|||
return result;
|
||||
}
|
||||
|
||||
// 4.5.13 TimeRecordToString ( time, precision ), https://tc39.es/proposal-temporal/#sec-temporal-timerecordtostring
|
||||
String time_record_to_string(Time const& time, SecondsStringPrecision::Precision precision)
|
||||
{
|
||||
// 1. Let subSecondNanoseconds be time.[[Millisecond]] × 10**6 + time.[[Microsecond]] × 10**3 + time.[[Nanosecond]].
|
||||
auto sub_second_nanoseconds = (static_cast<u64>(time.millisecond) * 1'000'000) + (static_cast<u64>(time.microsecond) * 1000) + static_cast<u64>(time.nanosecond);
|
||||
|
||||
// 2. Return FormatTimeString(time.[[Hour]], time.[[Minute]], time.[[Second]], subSecondNanoseconds, precision).
|
||||
return format_time_string(time.hour, time.minute, time.second, sub_second_nanoseconds, precision);
|
||||
}
|
||||
|
||||
// 4.5.14 CompareTimeRecord ( time1, time2 ), https://tc39.es/proposal-temporal/#sec-temporal-comparetimerecord
|
||||
i8 compare_time_record(Time const& time1, Time const& time2)
|
||||
{
|
||||
|
@ -499,4 +509,101 @@ Time add_time(Time const& time, TimeDuration const& time_duration)
|
|||
return balance_time(time.hour, time.minute, time.second, time.millisecond, time.microsecond, nanoseconds);
|
||||
}
|
||||
|
||||
// 4.5.16 RoundTime ( time, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtime
|
||||
Time round_time(Time const& time, u64 increment, Unit unit, RoundingMode rounding_mode)
|
||||
{
|
||||
double quantity = 0;
|
||||
|
||||
switch (unit) {
|
||||
// 1. If unit is DAY or HOUR, then
|
||||
case Unit::Day:
|
||||
case Unit::Hour:
|
||||
// a. Let quantity be ((((time.[[Hour]] × 60 + time.[[Minute]]) × 60 + time.[[Second]]) × 1000 + time.[[Millisecond]]) × 1000 + time.[[Microsecond]]) × 1000 + time.[[Nanosecond]].
|
||||
quantity = ((((time.hour * 60.0 + time.minute) * 60.0 + time.second) * 1000.0 + time.millisecond) * 1000.0 + time.microsecond) * 1000.0 + time.nanosecond;
|
||||
break;
|
||||
|
||||
// 2. Else if unit is MINUTE, then
|
||||
case Unit::Minute:
|
||||
// a. Let quantity be (((time.[[Minute]] × 60 + time.[[Second]]) × 1000 + time.[[Millisecond]]) × 1000 + time.[[Microsecond]]) × 1000 + time.[[Nanosecond]].
|
||||
quantity = (((time.minute * 60.0 + time.second) * 1000.0 + time.millisecond) * 1000.0 + time.microsecond) * 1000.0 + time.nanosecond;
|
||||
break;
|
||||
|
||||
// 3. Else if unit is SECOND, then
|
||||
case Unit::Second:
|
||||
// a. Let quantity be ((time.[[Second]] × 1000 + time.[[Millisecond]]) × 1000 + time.[[Microsecond]]) × 1000 + time.[[Nanosecond]].
|
||||
quantity = ((time.second * 1000.0 + time.millisecond) * 1000.0 + time.microsecond) * 1000.0 + time.nanosecond;
|
||||
break;
|
||||
|
||||
// 4. Else if unit is MILLISECOND, then
|
||||
case Unit::Millisecond:
|
||||
// a. Let quantity be (time.[[Millisecond]] × 1000 + time.[[Microsecond]]) × 1000 + time.[[Nanosecond]].
|
||||
quantity = (time.millisecond * 1000.0 + time.microsecond) * 1000.0 + time.nanosecond;
|
||||
break;
|
||||
|
||||
// 5. Else if unit is MICROSECOND, then
|
||||
case Unit::Microsecond:
|
||||
// a. Let quantity be time.[[Microsecond]] × 1000 + time.[[Nanosecond]].
|
||||
quantity = time.microsecond * 1000.0 + time.nanosecond;
|
||||
break;
|
||||
|
||||
// 6. Else,
|
||||
case Unit::Nanosecond:
|
||||
// a. Assert: unit is NANOSECOND.
|
||||
// b. Let quantity be time.[[Nanosecond]].
|
||||
quantity = time.nanosecond;
|
||||
break;
|
||||
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// 7. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
|
||||
auto unit_length = temporal_unit_length_in_nanoseconds(unit).to_u64();
|
||||
|
||||
// 8. Let result be RoundNumberToIncrement(quantity, increment × unitLength, roundingMode) / unitLength.
|
||||
auto result = round_number_to_increment(quantity, increment * unit_length, rounding_mode) / static_cast<double>(unit_length);
|
||||
|
||||
switch (unit) {
|
||||
// 9. If unit is DAY, then
|
||||
case Unit::Day:
|
||||
// a. Return CreateTimeRecord(0, 0, 0, 0, 0, 0, result).
|
||||
return create_time_record(0, 0, 0, 0, 0, 0, result);
|
||||
|
||||
// 10. If unit is HOUR, then
|
||||
case Unit::Hour:
|
||||
// a. Return BalanceTime(result, 0, 0, 0, 0, 0).
|
||||
return balance_time(result, 0, 0, 0, 0, 0);
|
||||
|
||||
// 11. If unit is MINUTE, then
|
||||
case Unit::Minute:
|
||||
// a. Return BalanceTime(time.[[Hour]], result, 0, 0, 0, 0).
|
||||
return balance_time(time.hour, result, 0, 0, 0, 0);
|
||||
|
||||
// 12. If unit is SECOND, then
|
||||
case Unit::Second:
|
||||
// a. Return BalanceTime(time.[[Hour]], time.[[Minute]], result, 0, 0, 0).
|
||||
return balance_time(time.hour, time.minute, result, 0, 0, 0);
|
||||
|
||||
// 13. If unit is MILLISECOND, then
|
||||
case Unit::Millisecond:
|
||||
// a. Return BalanceTime(time.[[Hour]], time.[[Minute]], time.[[Second]], result, 0, 0).
|
||||
return balance_time(time.hour, time.minute, time.second, result, 0, 0);
|
||||
|
||||
// 14. If unit is MICROSECOND, then
|
||||
case Unit::Microsecond:
|
||||
// a. Return BalanceTime(time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], result, 0).
|
||||
return balance_time(time.hour, time.minute, time.second, time.millisecond, result, 0);
|
||||
|
||||
// 15. Assert: unit is NANOSECOND.
|
||||
case Unit::Nanosecond:
|
||||
// 16. Return BalanceTime(time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], time.[[Microsecond]], result).
|
||||
return balance_time(time.hour, time.minute, time.second, time.millisecond, time.microsecond, result);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -59,7 +59,9 @@ Time balance_time(double hour, double minute, double second, double millisecond,
|
|||
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, TimeDuration const& nanosecond);
|
||||
ThrowCompletionOr<GC::Ref<PlainTime>> create_temporal_time(VM&, Time const&, GC::Ptr<FunctionObject> new_target = {});
|
||||
ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM&, Object const& temporal_time_like, Completeness = Completeness::Complete);
|
||||
String time_record_to_string(Time const&, SecondsStringPrecision::Precision);
|
||||
i8 compare_time_record(Time const&, Time const&);
|
||||
Time add_time(Time const&, TimeDuration const& time_duration);
|
||||
Time round_time(Time const&, u64 increment, Unit, RoundingMode);
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ void PlainTimePrototype::initialize(Realm& realm)
|
|||
define_native_accessor(realm, vm.names.millisecond, millisecond_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.microsecond, microsecond_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.nanosecond, nanosecond_getter, {}, Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
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);
|
||||
}
|
||||
|
||||
// 4.3.3 get Temporal.PlainTime.prototype.hour, https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.hour
|
||||
|
@ -62,4 +67,62 @@ void PlainTimePrototype::initialize(Realm& realm)
|
|||
JS_ENUMERATE_PLAIN_TIME_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
// 4.3.16 Temporal.PlainTime.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tostring
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::to_string)
|
||||
{
|
||||
// 1. Let temporalTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
|
||||
auto temporal_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
|
||||
|
||||
// 4. NOTE: The following steps read options and perform independent validation in alphabetical order
|
||||
// (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode").
|
||||
|
||||
// 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions).
|
||||
auto digits = TRY(get_temporal_fractional_second_digits_option(vm, resolved_options));
|
||||
|
||||
// 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, TRUNC).
|
||||
auto rounding_mode = TRY(get_rounding_mode_option(vm, resolved_options, RoundingMode::Trunc));
|
||||
|
||||
// 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", TIME, UNSET).
|
||||
auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, resolved_options, vm.names.smallestUnit, UnitGroup::Time, Unset {}));
|
||||
|
||||
// 8. If smallestUnit is HOUR, throw a RangeError exception.
|
||||
if (auto const* unit = smallest_unit.get_pointer<Unit>(); unit && *unit == Unit::Hour)
|
||||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, temporal_unit_to_string(*unit), vm.names.smallestUnit);
|
||||
|
||||
// 9. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits).
|
||||
auto precision = to_seconds_string_precision_record(smallest_unit, digits);
|
||||
|
||||
// 10. Let roundResult be RoundTime(temporalTime.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode).
|
||||
auto round_result = round_time(temporal_time->time(), precision.increment, precision.unit, rounding_mode);
|
||||
|
||||
// 11. Return TimeRecordToString(roundResult, precision.[[Precision]]).
|
||||
return PrimitiveString::create(vm, time_record_to_string(round_result, precision.precision));
|
||||
}
|
||||
|
||||
// 4.3.17 Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tolocalestring
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::to_locale_string)
|
||||
{
|
||||
// 1. Let temporalTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
|
||||
auto temporal_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return TimeRecordToString(temporalTime.[[Time]], AUTO).
|
||||
return PrimitiveString::create(vm, time_record_to_string(temporal_time->time(), Auto {}));
|
||||
}
|
||||
|
||||
// 4.3.18 Temporal.PlainTime.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tojson
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::to_json)
|
||||
{
|
||||
// 1. Let temporalTime be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
|
||||
auto temporal_time = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return TimeRecordToString(temporalTime.[[Time]], AUTO).
|
||||
return PrimitiveString::create(vm, time_record_to_string(temporal_time->time(), Auto {}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(millisecond_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(microsecond_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(nanosecond_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainTime.prototype.toJSON).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(18, 14, 47, 123, 456, 789);
|
||||
expect(plainTime.toJSON()).toBe("18:14:47.123456789");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.prototype.toJSON.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainTime.prototype.toLocaleString).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(18, 14, 47, 123, 456, 789);
|
||||
expect(plainTime.toLocaleString()).toBe("18:14:47.123456789");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.prototype.toLocaleString.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainTime.prototype.toString).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainTime = new Temporal.PlainTime(18, 14, 47, 123, 456, 789);
|
||||
expect(plainTime.toString()).toBe("18:14:47.123456789");
|
||||
});
|
||||
|
||||
test("fractionalSecondDigits option", () => {
|
||||
const plainTime = new Temporal.PlainTime(18, 14, 47, 123, 456);
|
||||
const values = [
|
||||
["auto", "18:14:47.123456"],
|
||||
[0, "18:14:47"],
|
||||
[1, "18:14:47.1"],
|
||||
[2, "18:14:47.12"],
|
||||
[3, "18:14:47.123"],
|
||||
[4, "18:14:47.1234"],
|
||||
[5, "18:14:47.12345"],
|
||||
[6, "18:14:47.123456"],
|
||||
[7, "18:14:47.1234560"],
|
||||
[8, "18:14:47.12345600"],
|
||||
[9, "18:14:47.123456000"],
|
||||
];
|
||||
for (const [fractionalSecondDigits, expected] of values) {
|
||||
const options = { fractionalSecondDigits };
|
||||
expect(plainTime.toString(options)).toBe(expected);
|
||||
}
|
||||
|
||||
// Ignored when smallestUnit is given
|
||||
expect(plainTime.toString({ smallestUnit: "minute", fractionalSecondDigits: 9 })).toBe(
|
||||
"18:14"
|
||||
);
|
||||
});
|
||||
|
||||
test("smallestUnit option", () => {
|
||||
const plainTime = new Temporal.PlainTime(18, 14, 47, 123, 456, 789);
|
||||
const values = [
|
||||
["minute", "18:14"],
|
||||
["second", "18:14:47"],
|
||||
["millisecond", "18:14:47.123"],
|
||||
["microsecond", "18:14:47.123456"],
|
||||
["nanosecond", "18:14:47.123456789"],
|
||||
];
|
||||
for (const [smallestUnit, expected] of values) {
|
||||
const options = { smallestUnit };
|
||||
expect(plainTime.toString(options)).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainTime object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainTime.prototype.toString.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainTime");
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue