diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index 7468c4da66..11f62fdbca 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -333,6 +333,25 @@ ThrowCompletionOr add_date_time(GlobalObject& global_obje return TemporalPlainDateTime { .year = added_date->iso_year(), .month = added_date->iso_month(), .day = added_date->iso_day(), .hour = time_result.hour, .minute = time_result.minute, .second = time_result.second, .millisecond = time_result.millisecond, .microsecond = time_result.microsecond, .nanosecond = time_result.nanosecond }; } +// 5.5.10 RoundISODateTime ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] ), https://tc39.es/proposal-temporal/#sec-temporal-roundisodatetime +ISODateTime round_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional day_length) +{ + // 1. Assert: year, month, day, hour, minute, second, millisecond, microsecond, and nanosecond are integers. + + // 2. If dayLength is not present, set dayLength to 8.64 × 10^13. + if (!day_length.has_value()) + day_length = 86400000000000; + + // 3. Let roundedTime be ! RoundTime(hour, minute, second, millisecond, microsecond, nanosecond, increment, unit, roundingMode, dayLength). + auto rounded_time = round_time(hour, minute, second, millisecond, microsecond, nanosecond, increment, unit, rounding_mode, day_length); + + // 4. Let balanceResult be ! BalanceISODate(year, month, day + roundedTime.[[Days]]). + auto balance_result = balance_iso_date(year, month, day + rounded_time.days); + + // 5. Return the Record { [[Year]]: balanceResult.[[Year]], [[Month]]: balanceResult.[[Month]], [[Day]]: balanceResult.[[Day]], [[Hour]]: roundedTime.[[Hour]], [[Minute]]: roundedTime.[[Minute]], [[Second]]: roundedTime.[[Second]], [[Millisecond]]: roundedTime.[[Millisecond]], [[Microsecond]]: roundedTime.[[Microsecond]], [[Nanosecond]]: roundedTime.[[Nanosecond]] }. + return ISODateTime { .year = balance_result.year, .month = balance_result.month, .day = balance_result.day, .hour = rounded_time.hour, .minute = rounded_time.minute, .second = rounded_time.second, .millisecond = rounded_time.millisecond, .microsecond = rounded_time.microsecond, .nanosecond = rounded_time.nanosecond }; +} + // 5.5.11 DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2, calendar, largestUnit [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-differenceisodatetime ThrowCompletionOr difference_iso_date_time(GlobalObject& global_object, i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2, Object& calendar, StringView largest_unit, Object* options) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h index 601b9efa8b..720de4dc56 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h @@ -72,6 +72,7 @@ ThrowCompletionOr create_temporal_date_time(GlobalObject&, i32 i ThrowCompletionOr temporal_date_time_to_string(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Value calendar, Variant const& precision, StringView show_calendar); i8 compare_iso_date_time(i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2); ThrowCompletionOr add_date_time(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options); +ISODateTime round_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional day_length = {}); ThrowCompletionOr difference_iso_date_time(GlobalObject&, i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2, Object& calendar, StringView largest_unit, Object* options = nullptr); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp index 2dedbf52f4..3a68713a6e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp @@ -63,6 +63,7 @@ void PlainDateTimePrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.add, add, 1, attr); define_native_function(vm.names.subtract, subtract, 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.valueOf, value_of, 0, attr); define_native_function(vm.names.toZonedDateTime, to_zoned_date_time, 1, attr); define_native_function(vm.names.toPlainDate, to_plain_date, 0, attr); @@ -469,6 +470,32 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::equals) return Value(TRY(calendar_equals(global_object, date_time->calendar(), other->calendar()))); } +// 5.3.32 Temporal.PlainDateTime.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tostring +JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::to_string) +{ + // 1. Let dateTime be the this value. + // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). + auto* date_time = TRY(typed_this_object(global_object)); + + // 3. Set options to ? GetOptionsObject(options). + auto* options = TRY(get_options_object(global_object, vm.argument(0))); + + // 4. Let precision be ? ToSecondsStringPrecision(options). + auto precision = TRY(to_seconds_string_precision(global_object, *options)); + + // 5. Let roundingMode be ? ToTemporalRoundingMode(options, "trunc"). + auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "trunc"sv)); + + // 6. Let showCalendar be ? ToShowCalendarOption(options). + auto show_calendar = TRY(to_show_calendar_option(global_object, *options)); + + // 7. Let result be ! RoundISODateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], precision.[[Increment]], precision.[[Unit]], roundingMode). + auto result = round_iso_date_time(date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond(), precision.increment, precision.unit, rounding_mode); + + // 8. Return ? TemporalDateTimeToString(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]], precision.[[Precision]], showCalendar). + return js_string(vm, TRY(temporal_date_time_to_string(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, &date_time->calendar(), precision.precision, show_calendar))); +} + // 5.3.35 Temporal.PlainDateTime.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.valueof JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::value_of) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.h index 238b1ba911..cdc032d731 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.h @@ -47,6 +47,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(add); JS_DECLARE_NATIVE_FUNCTION(subtract); JS_DECLARE_NATIVE_FUNCTION(equals); + JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(value_of); JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time); JS_DECLARE_NATIVE_FUNCTION(to_plain_date); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.toString.js new file mode 100644 index 0000000000..29461d4022 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.toString.js @@ -0,0 +1,63 @@ +describe("correct behavior", () => { + test("length is 0", () => { + expect(Temporal.PlainDateTime.prototype.toString).toHaveLength(0); + }); + + test("basic functionality", () => { + const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 1, 33, 5, 100, 200, 300); + expect(plainDateTime.toString()).toBe("2021-11-03T01:33:05.1002003"); + }); + + test("fractionalSecondDigits option", () => { + const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 1, 33, 5, 100, 200, 300); + const values = [ + ["auto", "2021-11-03T01:33:05.1002003"], + [0, "2021-11-03T01:33:05"], + [1, "2021-11-03T01:33:05.1"], + [2, "2021-11-03T01:33:05.10"], + [3, "2021-11-03T01:33:05.100"], + [4, "2021-11-03T01:33:05.1002"], + [5, "2021-11-03T01:33:05.10020"], + [6, "2021-11-03T01:33:05.100200"], + [7, "2021-11-03T01:33:05.1002003"], + [8, "2021-11-03T01:33:05.10020030"], + [9, "2021-11-03T01:33:05.100200300"], + ]; + + for (const [fractionalSecondDigits, expected] of values) { + const options = { fractionalSecondDigits }; + expect(plainDateTime.toString(options)).toBe(expected); + } + + // Ignored when smallestUnit is given + expect(plainDateTime.toString({ smallestUnit: "minute", fractionalSecondDigits: 9 })).toBe( + "2021-11-03T01:33" + ); + }); + + test("smallestUnit option", () => { + const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 1, 33, 5, 100, 200, 300); + const values = [ + ["minute", "2021-11-03T01:33"], + ["second", "2021-11-03T01:33:05"], + ["millisecond", "2021-11-03T01:33:05.100"], + ["microsecond", "2021-11-03T01:33:05.100200"], + ["nanosecond", "2021-11-03T01:33:05.100200300"], + ]; + + for (const [smallestUnit, expected] of values) { + const singularOptions = { smallestUnit }; + const pluralOptions = { smallestUnit: `${smallestUnit}s` }; + expect(plainDateTime.toString(singularOptions)).toBe(expected); + expect(plainDateTime.toString(pluralOptions)).toBe(expected); + } + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.PlainDateTime object", () => { + expect(() => { + Temporal.PlainDateTime.prototype.toString.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainDateTime"); + }); +});