LibJS: Implement Temporal.PlainTime.prototype.toString()

This commit is contained in:
Linus Groh 2021-09-08 22:58:07 +01:00
parent 81aaa9ccd5
commit 9f78a957d5
Notes: sideshowbarker 2024-07-18 04:25:00 +09:00
5 changed files with 214 additions and 0 deletions

View File

@ -412,6 +412,21 @@ Optional<UnregulatedTemporalTime> to_temporal_time_record(GlobalObject& global_o
return result;
}
// 4.5.10 TemporalTimeToString ( hour, minute, second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaltimetostring
String temporal_time_to_string(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision)
{
// 1. Assert: hour, minute, second, millisecond, microsecond and nanosecond are integers.
// 2. Let hour be hour formatted as a two-digit decimal number, padded to the left with a zero if necessary.
// 3. Let minute be minute formatted as a two-digit decimal number, padded to the left with a zero if necessary.
// 4. Let seconds be ! FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision).
auto seconds = format_seconds_string_part(second, millisecond, microsecond, nanosecond, precision);
// 5. Return the string-concatenation of hour, the code unit 0x003A (COLON), minute, and seconds.
return String::formatted("{:02}:{:02}{}", hour, minute, seconds);
}
// 4.5.11 CompareTemporalTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 ), https://tc39.es/proposal-temporal/#sec-temporal-comparetemporaltime
i8 compare_temporal_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2)
{
@ -469,4 +484,107 @@ i8 compare_temporal_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16
return 0;
}
// 4.5.13 RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond, increment, unit, roundingMode [ , dayLengthNs ] ), https://tc39.es/proposal-temporal/#sec-temporal-roundtime
DaysAndTime round_time(GlobalObject& global_object, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional<double> day_length_ns)
{
// 1. Assert: hour, minute, second, millisecond, microsecond, nanosecond, and increment are integers.
// 2. Let fractionalSecond be nanosecond × 109 + microsecond × 106 + millisecond × 103 + second.
double fractional_second = nanosecond * 0.000000001 + microsecond * 0.000001 + millisecond * 0.001 + second;
double quantity;
// 3. If unit is "day", then
if (unit == "day"sv) {
// a. If dayLengthNs is not present, set it to 8.64 × 10^13.
if (!day_length_ns.has_value())
day_length_ns = 86400000000000;
// b. Let quantity be (((((hour × 60 + minute) × 60 + second) × 1000 + millisecond) × 1000 + microsecond) × 1000 + nanosecond) / dayLengthNs.
quantity = (((((hour * 60 + minute) * 60 + second) * 1000 + millisecond) * 1000 + microsecond) * 1000 + nanosecond) / *day_length_ns;
}
// 4. Else if unit is "hour", then
else if (unit == "hour"sv) {
// a. Let quantity be (fractionalSecond / 60 + minute) / 60 + hour.
quantity = (fractional_second / 60 + minute) / 60 + hour;
}
// 5. Else if unit is "minute", then
else if (unit == "minute"sv) {
// a. Let quantity be fractionalSecond / 60 + minute.
quantity = fractional_second / 60 + minute;
}
// 6. Else if unit is "second", then
else if (unit == "second"sv) {
// a. Let quantity be fractionalSecond.
quantity = fractional_second;
}
// 7. Else if unit is "millisecond", then
else if (unit == "millisecond"sv) {
// a. Let quantity be nanosecond × 106 + microsecond × 103 + millisecond.
quantity = nanosecond * 0.000001 + 0.001 * microsecond + millisecond;
}
// 8. Else if unit is "microsecond", then
else if (unit == "microsecond"sv) {
// a. Let quantity be nanosecond × 103 + microsecond.
quantity = nanosecond * 0.001 + microsecond;
}
// 9. Else,
else {
// a. Assert: unit is "nanosecond".
VERIFY(unit == "nanosecond"sv);
// b. Let quantity be nanosecond.
quantity = nanosecond;
}
// FIXME: This doesn't seem right...
auto* quantity_bigint = js_bigint(global_object.vm(), Crypto::SignedBigInteger::create_from((u64)quantity));
// 10. Let result be ! RoundNumberToIncrement(quantity, increment, roundingMode).
auto* result = round_number_to_increment(global_object, *quantity_bigint, increment, rounding_mode);
auto result_i64 = (i64)result->big_integer().to_double();
// If unit is "day", then
if (unit == "day"sv) {
// a. Return the Record { [[Days]]: result, [[Hour]]: 0, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }.
return DaysAndTime { .days = (i32)result_i64, .hour = 0, .minute = 0, .second = 0, .millisecond = 0, .microsecond = 0, .nanosecond = 0 };
}
// 12. If unit is "hour", then
if (unit == "hour"sv) {
// a. Return ! BalanceTime(result, 0, 0, 0, 0, 0).
return balance_time(result_i64, 0, 0, 0, 0, 0);
}
// 13. If unit is "minute", then
if (unit == "minute"sv) {
// a. Return ! BalanceTime(hour, result, 0, 0, 0, 0).
return balance_time(hour, result_i64, 0, 0, 0, 0);
}
// 14. If unit is "second", then
if (unit == "second"sv) {
// a. Return ! BalanceTime(hour, minute, result, 0, 0, 0).
return balance_time(hour, minute, result_i64, 0, 0, 0);
}
// 15. If unit is "millisecond", then
if (unit == "millisecond"sv) {
// a. Return ! BalanceTime(hour, minute, second, result, 0, 0).
return balance_time(hour, minute, second, result_i64, 0, 0);
}
// 16. If unit is "microsecond", then
if (unit == "microsecond"sv) {
// a. Return ! BalanceTime(hour, minute, second, millisecond, result, 0).
return balance_time(hour, minute, second, millisecond, result_i64, 0);
}
// 17. Assert: unit is "nanosecond".
VERIFY(unit == "nanosecond"sv);
// 18. Return ! BalanceTime(hour, minute, second, millisecond, microsecond, result).
return balance_time(hour, minute, second, millisecond, microsecond, result_i64);
}
}

View File

@ -98,6 +98,8 @@ DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64
TemporalTime constrain_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
PlainTime* create_temporal_time(GlobalObject&, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, FunctionObject* new_target = nullptr);
Optional<UnregulatedTemporalTime> to_temporal_time_record(GlobalObject&, Object& temporal_time_like);
String temporal_time_to_string(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision);
i8 compare_temporal_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2);
DaysAndTime round_time(GlobalObject&, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional<double> day_length_ns = {});
}

View File

@ -42,6 +42,7 @@ void PlainTimePrototype::initialize(GlobalObject& global_object)
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.getISOFields, get_iso_fields, 0, attr);
define_native_function(vm.names.toString, to_string, 0, attr);
define_native_function(vm.names.valueOf, value_of, 0, attr);
}
@ -355,6 +356,39 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::get_iso_fields)
return fields;
}
// 4.3.20 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 = typed_this(global_object);
if (vm.exception())
return {};
// 3. Set options to ? GetOptionsObject(options).
auto* options = get_options_object(global_object, vm.argument(0));
if (vm.exception())
return {};
// 4. Let precision be ? ToSecondsStringPrecision(options).
auto precision = to_seconds_string_precision(global_object, *options);
if (vm.exception())
return {};
// 5. Let roundingMode be ? ToTemporalRoundingMode(options, "trunc").
auto rounding_mode = to_temporal_rounding_mode(global_object, *options, "trunc"sv);
if (vm.exception())
return {};
// 6. Let roundResult be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], precision.[[Increment]], precision.[[Unit]], roundingMode).
auto round_result = round_time(global_object, temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), precision->increment, precision->unit, *rounding_mode);
// FIXME: Hey spec, this cannot fail...
// 7. Return ? TemporalTimeToString(roundResult.[[Hour]], roundResult.[[Minute]], roundResult.[[Second]], roundResult.[[Millisecond]], roundResult.[[Microsecond]], roundResult.[[Nanosecond]], precision.[[Precision]]).
auto string = temporal_time_to_string(round_result.hour, round_result.minute, round_result.second, round_result.millisecond, round_result.microsecond, round_result.nanosecond, precision->precision);
return js_string(vm, move(string));
}
// 4.3.23 Temporal.PlainTime.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.valueof
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::value_of)
{

View File

@ -30,6 +30,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(equals);
JS_DECLARE_NATIVE_FUNCTION(to_plain_date_time);
JS_DECLARE_NATIVE_FUNCTION(get_iso_fields);
JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(value_of);
};

View File

@ -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 a Temporal.PlainTime");
});
});