summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2021-08-31 00:15:36 +0100
committerAndreas Kling <kling@serenityos.org>2021-08-31 16:35:51 +0200
commit576be0f8e7a618fbb64a6e8f38583eff06e40ee2 (patch)
tree5ea87e0233de03b38ef834a8834d4e66ce1ac399 /Userland/Libraries/LibJS
parent1c65ee6edfcb3330f6c2abaf55de358725fabbab (diff)
downloadserenity-576be0f8e7a618fbb64a6e8f38583eff06e40ee2.zip
LibJS: Implement Temporal.Instant.prototype.toString()
Diffstat (limited to 'Userland/Libraries/LibJS')
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp171
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h11
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp54
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/Instant.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp55
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp28
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js65
14 files changed, 396 insertions, 6 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
index d2010126e4..e790fac620 100644
--- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
+++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
@@ -164,6 +164,7 @@ namespace JS {
P(fontcolor) \
P(fontsize) \
P(forEach) \
+ P(fractionalSecondDigits) \
P(freeze) \
P(from) \
P(fromCharCode) \
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
index 6e80707357..df040be60c 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
@@ -7,6 +7,7 @@
#include <AK/CharacterTypes.h>
#include <AK/DateTimeLexer.h>
+#include <AK/Variant.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
@@ -168,6 +169,44 @@ Value get_option(GlobalObject& global_object, Object& options, PropertyName cons
return value;
}
+// 13.4 GetStringOrNumberOption ( options, property, stringValues, minimum, maximum, fallback ), https://tc39.es/proposal-temporal/#sec-getstringornumberoption
+template<typename NumberType>
+Optional<Variant<String, NumberType>> get_string_or_number_option(GlobalObject& global_object, Object& options, PropertyName const& property, Vector<StringView> const& string_values, NumberType minimum, NumberType maximum, Value fallback)
+{
+ auto& vm = global_object.vm();
+
+ // 1. Assert: Type(options) is Object.
+
+ // 2. Let value be ? GetOption(options, property, « Number, String », empty, fallback).
+ auto value = get_option(global_object, options, property, { OptionType::Number, OptionType::String }, {}, fallback);
+ if (vm.exception())
+ return {};
+
+ // 3. If Type(value) is Number, then
+ if (value.is_number()) {
+ // a. If value < minimum or value > maximum, throw a RangeError exception.
+ if (value.as_double() < minimum || value.as_double() > maximum) {
+ vm.template throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_double(), property.as_string());
+ return {};
+ }
+
+ // b. Return floor(ℝ(value)).
+ return floor(value.as_double());
+ }
+
+ // 4. Assert: Type(value) is String.
+ VERIFY(value.is_string());
+
+ // 5. If stringValues does not contain value, throw a RangeError exception.
+ if (!string_values.contains_slow(value.as_string().string())) {
+ vm.template throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.as_string().string(), property.as_string());
+ return {};
+ }
+
+ // 6. Return value.
+ return value.as_string().string();
+}
+
// 13.6 ToTemporalOverflow ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
Optional<String> to_temporal_overflow(GlobalObject& global_object, Object& normalized_options)
{
@@ -264,6 +303,88 @@ u64 to_temporal_rounding_increment(GlobalObject& global_object, Object& normaliz
return floored_increment;
}
+// 13.16 ToSecondsStringPrecision ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecision
+Optional<SecondsStringPrecision> to_seconds_string_precision(GlobalObject& global_object, Object& normalized_options)
+{
+ auto& vm = global_object.vm();
+
+ // Let smallestUnit be ? ToSmallestTemporalUnit(normalizedOptions, « "year", "month", "week", "day", "hour" », undefined).
+ auto smallest_unit = to_smallest_temporal_unit(global_object, normalized_options, { "year"sv, "month"sv, "week"sv, "day"sv, "hour"sv }, {});
+ if (vm.exception())
+ return {};
+
+ // 2. If smallestUnit is "minute", then
+ if (smallest_unit == "minute"sv) {
+ // a. Return the Record { [[Precision]]: "minute", [[Unit]]: "minute", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = String { "minute"sv }, .unit = "minute"sv, .increment = 1 };
+ }
+
+ // 3. If smallestUnit is "second", then
+ if (smallest_unit == "second"sv) {
+ // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
+ }
+
+ // 4. If smallestUnit is "millisecond", then
+ if (smallest_unit == "millisecond"sv) {
+ // a. Return the Record { [[Precision]]: 3, [[Unit]]: "millisecond", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = 3, .unit = "millisecond"sv, .increment = 1 };
+ }
+
+ // 5. If smallestUnit is "microsecond", then
+ if (smallest_unit == "microsecond"sv) {
+ // a. Return the Record { [[Precision]]: 6, [[Unit]]: "microsecond", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = 6, .unit = "microsecond"sv, .increment = 1 };
+ }
+
+ // 6. If smallestUnit is "nanosecond", then
+ if (smallest_unit == "nanosecond"sv) {
+ // a. Return the Record { [[Precision]]: 9, [[Unit]]: "nanosecond", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = 9, .unit = "nanosecond"sv, .increment = 1 };
+ }
+
+ // 7. Assert: smallestUnit is undefined.
+ VERIFY(!smallest_unit.has_value());
+
+ // 8. Let digits be ? GetStringOrNumberOption(normalizedOptions, "fractionalSecondDigits", « "auto" », 0, 9, "auto").
+ auto digits_variant = get_string_or_number_option<u8>(global_object, normalized_options, vm.names.fractionalSecondDigits, { "auto"sv }, 0, 9, js_string(vm, "auto"sv));
+ if (vm.exception())
+ return {};
+
+ // 9. If digits is "auto", then
+ if (digits_variant->has<String>()) {
+ VERIFY(digits_variant->get<String>() == "auto"sv);
+ // a. Return the Record { [[Precision]]: "auto", [[Unit]]: "nanosecond", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = String { "auto"sv }, .unit = "nanosecond"sv, .increment = 1 };
+ }
+
+ auto digits = digits_variant->get<u8>();
+
+ // 10. If digits is 0, then
+ if (digits == 0) {
+ // a. Return the Record { [[Precision]]: 0, [[Unit]]: "second", [[Increment]]: 1 }.
+ return SecondsStringPrecision { .precision = 0, .unit = "second"sv, .increment = 1 };
+ }
+
+ // 11. If digits is 1, 2, or 3, then
+ if (digits == 1 || digits == 2 || digits == 3) {
+ // a. Return the Record { [[Precision]]: digits, [[Unit]]: "millisecond", [[Increment]]: 10^(3 − digits) }.
+ return SecondsStringPrecision { .precision = digits, .unit = "millisecond"sv, .increment = (u32)pow(10, 3 - digits) };
+ }
+
+ // 12. If digits is 4, 5, or 6, then
+ if (digits == 4 || digits == 5 || digits == 6) {
+ // a. Return the Record { [[Precision]]: digits, [[Unit]]: "microsecond", [[Increment]]: 10^(6 − digits) }.
+ return SecondsStringPrecision { .precision = digits, .unit = "microsecond"sv, .increment = (u32)pow(10, 6 - digits) };
+ }
+
+ // 13. Assert: digits is 7, 8, or 9.
+ VERIFY(digits == 7 || digits == 8 || digits == 9);
+
+ // 14. Return the Record { [[Precision]]: digits, [[Unit]]: "nanosecond", [[Increment]]: 10^(9 − digits) }.
+ return SecondsStringPrecision { .precision = digits, .unit = "nanosecond"sv, .increment = (u32)pow(10, 9 - digits) };
+}
+
// https://tc39.es/proposal-temporal/#table-temporal-singular-and-plural-units
static HashMap<StringView, StringView> plural_to_singular_units = {
{ "years"sv, "year"sv },
@@ -313,6 +434,56 @@ Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object&
return smallest_unit;
}
+// 13.27 FormatSecondsStringPart ( second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatsecondsstringpart
+String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<String, u8> const& precision)
+{
+ // 1. Assert: second, millisecond, microsecond and nanosecond are integers.
+
+ // Non-standard sanity check
+ if (precision.has<String>())
+ VERIFY(precision.get<String>().is_one_of("minute"sv, "auto"sv));
+
+ // 2. If precision is "minute", return "".
+ if (precision.has<String>() && precision.get<String>() == "minute"sv)
+ return String::empty();
+
+ // 3. Let secondsString be the string-concatenation of the code unit 0x003A (COLON) and second formatted as a two-digit decimal number, padded to the left with zeroes if necessary.
+ auto seconds_string = String::formatted(":{:02}", second);
+
+ // 4. Let fraction be millisecond × 10^6 + microsecond × 10^3 + nanosecond.
+ u32 fraction = millisecond * 1'000'000 + microsecond * 1'000 + nanosecond;
+
+ String fraction_string;
+
+ // 5. If precision is "auto", then
+ if (precision.has<String>() && precision.get<String>() == "auto"sv) {
+ // a. If fraction is 0, return secondsString.
+ if (fraction == 0)
+ return seconds_string;
+
+ // b. Set fraction to fraction formatted as a nine-digit decimal number, padded to the left with zeroes if necessary.
+ fraction_string = String::formatted("{:09}", fraction);
+
+ // c. Set fraction to the longest possible substring of fraction starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO).
+ fraction_string = fraction_string.trim("0"sv, TrimMode::Right);
+ }
+ // 6. Else,
+ else {
+ // a. If precision is 0, return secondsString.
+ if (precision.get<u8>() == 0)
+ return seconds_string;
+
+ // b. Set fraction to fraction formatted as a nine-digit decimal number, padded to the left with zeroes if necessary.
+ fraction_string = String::formatted("{:09}", fraction);
+
+ // c. Set fraction to the substring of fraction from 0 to precision.
+ fraction_string = fraction_string.substring(0, precision.get<u8>());
+ }
+
+ // 7. Return the string-concatenation of secondsString, the code unit 0x002E (FULL STOP), and fraction.
+ return String::formatted("{}.{}", seconds_string, fraction_string);
+}
+
// 13.29 ConstrainToRange ( x, minimum, maximum ), https://tc39.es/proposal-temporal/#sec-temporal-constraintorange
double constrain_to_range(double x, double minimum, double maximum)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h
index 87e73ca31f..1e3fe05fdf 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h
@@ -9,6 +9,7 @@
#include <AK/Forward.h>
#include <AK/String.h>
+#include <AK/Variant.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/GlobalObject.h>
@@ -69,14 +70,24 @@ struct TemporalTimeZone {
Optional<String> name;
};
+struct SecondsStringPrecision {
+ Variant<String, u8> precision;
+ String unit;
+ u32 increment;
+};
+
MarkedValueList iterable_to_list_of_type(GlobalObject&, Value items, Vector<OptionType> const& element_types);
Object* get_options_object(GlobalObject&, Value options);
Value get_option(GlobalObject&, Object& options, PropertyName const& property, Vector<OptionType> const& types, Vector<StringView> const& values, Value fallback);
+template<typename NumberType>
+Optional<Variant<String, NumberType>> get_string_or_number_option(GlobalObject&, Object& options, PropertyName const& property, Vector<StringView> const& string_values, NumberType minimum, NumberType maximum, Value fallback);
Optional<String> to_temporal_overflow(GlobalObject&, Object& normalized_options);
Optional<String> to_temporal_rounding_mode(GlobalObject&, Object& normalized_options, String const& fallback);
Optional<String> to_show_calendar_option(GlobalObject&, Object& normalized_options);
u64 to_temporal_rounding_increment(GlobalObject&, Object& normalized_options, Optional<double> dividend, bool inclusive);
+Optional<SecondsStringPrecision> to_seconds_string_precision(GlobalObject&, Object& normalized_options);
Optional<String> to_smallest_temporal_unit(GlobalObject&, Object& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback);
+String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<String, u8> const& precision);
double constrain_to_range(double x, double minimum, double maximum);
BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, String const& rounding_mode);
Optional<ISODateTime> parse_iso_date_time(GlobalObject&, String const& iso_string);
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp
index 5f98dd6d01..1821fd2195 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp
@@ -5,10 +5,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include <AK/Variant.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
+#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
@@ -233,4 +235,56 @@ BigInt* round_temporal_instant(GlobalObject& global_object, BigInt const& nanose
return round_number_to_increment(global_object, nanoseconds, increment_nanoseconds, rounding_mode);
}
+// 8.5.9 TemporalInstantToString ( instant, timeZone, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring
+Optional<String> temporal_instant_to_string(GlobalObject& global_object, Instant& instant, Value time_zone, Variant<String, u8> const& precision)
+{
+ auto& vm = global_object.vm();
+
+ // 1. Assert: Type(instant) is Object.
+ // 2. Assert: instant has an [[InitializedTemporalInstant]] internal slot.
+
+ // 3. Let outputTimeZone be timeZone.
+ auto output_time_zone = time_zone;
+
+ // 4. If outputTimeZone is undefined, then
+ if (output_time_zone.is_undefined()) {
+ // a. Set outputTimeZone to ? CreateTemporalTimeZone("UTC").
+ output_time_zone = create_temporal_time_zone(global_object, "UTC"sv);
+ // TODO: Can this really throw...?
+ if (vm.exception())
+ return {};
+ }
+
+ // 5. Let isoCalendar be ! GetISO8601Calendar().
+ auto* iso_calendar = get_iso8601_calendar(global_object);
+
+ // 6. Let dateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(outputTimeZone, instant, isoCalendar).
+ auto* date_time = builtin_time_zone_get_plain_date_time_for(global_object, output_time_zone, instant, *iso_calendar);
+ if (vm.exception())
+ return {};
+
+ // 7. Let dateTimeString be ? TemporalDateTimeToString(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], undefined, precision, "never").
+ auto date_time_string = temporal_date_time_to_string(global_object, 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(), js_undefined(), precision, "never"sv);
+ if (vm.exception())
+ return {};
+
+ Optional<String> time_zone_string;
+
+ // 8. If timeZone is undefined, then
+ if (time_zone.is_undefined()) {
+ // a. Let timeZoneString be "Z".
+ time_zone_string = "Z"sv;
+ }
+ // 9. Else,
+ else {
+ // a. Let timeZoneString be ? BuiltinTimeZoneGetOffsetStringFor(timeZone, instant).
+ time_zone_string = builtin_time_zone_get_offset_string_for(global_object, time_zone, instant);
+ if (vm.exception())
+ return {};
+ }
+
+ // 10. Return the string-concatenation of dateTimeString and timeZoneString.
+ return String::formatted("{}{}", *date_time_string, *time_zone_string);
+}
+
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h
index 77caf6acb1..29ff469a57 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h
@@ -8,6 +8,7 @@
#pragma once
#include <AK/Optional.h>
+#include <AK/Variant.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Object.h>
@@ -41,5 +42,6 @@ BigInt* parse_temporal_instant(GlobalObject&, String const& iso_string);
i32 compare_epoch_nanoseconds(BigInt const&, BigInt const&);
BigInt* add_instant(GlobalObject&, BigInt const& epoch_nanoseconds, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
BigInt* round_temporal_instant(GlobalObject&, BigInt const& nanoseconds, u64 increment, String const& unit, String const& rounding_mode);
+Optional<String> temporal_instant_to_string(GlobalObject&, Instant&, Value time_zone, Variant<String, u8> const& precision);
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
index 4df6a3cfba..fa9e6d1825 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
@@ -11,6 +11,7 @@
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
+#include <LibJS/Runtime/Temporal/TimeZone.h>
namespace JS::Temporal {
@@ -39,6 +40,7 @@ void InstantPrototype::initialize(GlobalObject& global_object)
define_native_function(vm.names.subtract, subtract, 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.toString, to_string, 0, attr);
define_native_function(vm.names.valueOf, value_of, 0, attr);
}
@@ -280,6 +282,59 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::equals)
return Value(true);
}
+// 8.3.13 Temporal.Instant.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring
+JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_string)
+{
+ // 1. Let instant be the this value.
+ // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
+ auto* instant = 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 timeZone be ? Get(options, "timeZone").
+ auto time_zone = options->get(vm.names.timeZone);
+ if (vm.exception())
+ return {};
+
+ // 5. If timeZone is not undefined, then
+ if (!time_zone.is_undefined()) {
+ // a. Set timeZone to ? ToTemporalTimeZone(timeZone).
+ time_zone = to_temporal_time_zone(global_object, time_zone);
+ if (vm.exception())
+ return {};
+ }
+
+ // 6. Let precision be ? ToSecondsStringPrecision(options).
+ auto precision = to_seconds_string_precision(global_object, *options);
+ if (vm.exception())
+ return {};
+
+ // 7. Let roundingMode be ? ToTemporalRoundingMode(options, "trunc").
+ auto rounding_mode = to_temporal_rounding_mode(global_object, *options, "trunc"sv);
+ if (vm.exception())
+ return {};
+
+ // 8. Let roundedNs be ? RoundTemporalInstant(instant.[[Nanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode).
+ auto* rounded_ns = round_temporal_instant(global_object, instant->nanoseconds(), precision->increment, precision->unit, *rounding_mode);
+ if (vm.exception())
+ return {};
+
+ // 9. Let roundedInstant be ! CreateTemporalInstant(roundedNs).
+ auto* rounded_instant = create_temporal_instant(global_object, *rounded_ns);
+
+ // 10. Return ? TemporalInstantToString(roundedInstant, timeZone, precision.[[Precision]]).
+ auto string = temporal_instant_to_string(global_object, *rounded_instant, time_zone, precision->precision);
+ if (vm.exception())
+ return {};
+
+ return js_string(vm, *string);
+}
+
// 8.3.16 Temporal.Instant.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::value_of)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
index b7b420c092..49f716a36d 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
@@ -27,6 +27,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(subtract);
JS_DECLARE_NATIVE_FUNCTION(round);
JS_DECLARE_NATIVE_FUNCTION(equals);
+ JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(value_of);
};
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp
index 80ae1be0bb..ef8ff18614 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp
@@ -302,6 +302,34 @@ PlainDateTime* create_temporal_date_time(GlobalObject& global_object, i32 iso_ye
return object;
}
+// 5.5.7 TemporalDateTimeToString ( isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond, calendar, precision, showCalendar ), , https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetimetostring
+Optional<String> temporal_date_time_to_string(GlobalObject& global_object, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Value calendar, Variant<String, u8> const& precision, StringView show_calendar)
+{
+ auto& vm = global_object.vm();
+
+ // 1. Assert: isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, and nanosecond are integers.
+
+ // 2. Let year be ! PadISOYear(isoYear).
+ // 3. Let month be isoMonth formatted as a two-digit decimal number, padded to the left with a zero if necessary.
+ // 4. Let day be isoDay formatted as a two-digit decimal number, padded to the left with a zero if necessary.
+ // 5. Let hour be hour formatted as a two-digit decimal number, padded to the left with a zero if necessary.
+ // 6. Let minute be minute formatted as a two-digit decimal number, padded to the left with a zero if necessary.
+
+ // 7. Let seconds be ! FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision).
+ auto seconds = format_seconds_string_part(second, millisecond, microsecond, nanosecond, precision);
+
+ // 8. Let calendarID be ? ToString(calendar).
+ auto calendar_id = calendar.to_string(global_object);
+ if (vm.exception())
+ return {};
+
+ // 9. Let calendarString be ! FormatCalendarAnnotation(calendarID, showCalendar).
+ auto calendar_string = format_calendar_annotation(calendar_id, show_calendar);
+
+ // 10. Return the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), month, the code unit 0x002D (HYPHEN-MINUS), day, 0x0054 (LATIN CAPITAL LETTER T), hour, the code unit 0x003A (COLON), minute, seconds, and calendarString.
+ return String::formatted("{}-{:02}-{:02}T{:02}:{:02}{}{}", pad_iso_year(iso_year), iso_month, iso_day, hour, minute, seconds, calendar_string);
+}
+
// 5.5.8 CompareISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2 ),https://tc39.es/proposal-temporal/#sec-temporal-compareisodatetime
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)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h
index 4e66d72696..14a7834591 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h
@@ -8,6 +8,7 @@
#pragma once
#include <AK/Optional.h>
+#include <AK/Variant.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
@@ -54,6 +55,7 @@ Optional<ISODateTime> interpret_temporal_date_time_fields(GlobalObject&, Object&
PlainDateTime* to_temporal_date_time(GlobalObject&, Value item, Object* options = nullptr);
ISODateTime balance_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, i64 nanosecond);
PlainDateTime* create_temporal_date_time(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, FunctionObject* new_target = nullptr);
+Optional<String> 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<String, u8> 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);
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp
index 4f29d002ff..33b6149115 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp
@@ -416,12 +416,12 @@ double get_offset_nanoseconds_for(GlobalObject& global_object, Value time_zone,
}
// 11.6.12 BuiltinTimeZoneGetOffsetStringFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetoffsetstringfor
-Optional<String> builtin_time_zone_get_offset_string_for(GlobalObject& global_object, Object& time_zone, Instant& instant)
+Optional<String> builtin_time_zone_get_offset_string_for(GlobalObject& global_object, Value time_zone, Instant& instant)
{
auto& vm = global_object.vm();
// 1. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant).
- auto offset_nanoseconds = get_offset_nanoseconds_for(global_object, &time_zone, instant);
+ auto offset_nanoseconds = get_offset_nanoseconds_for(global_object, time_zone, instant);
if (vm.exception())
return {};
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h
index 3b086c84b3..c27a6b0c0e 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h
@@ -43,7 +43,7 @@ double parse_time_zone_offset_string(GlobalObject&, String const&);
String format_time_zone_offset_string(double offset_nanoseconds);
Object* to_temporal_time_zone(GlobalObject&, Value temporal_time_zone_like);
double get_offset_nanoseconds_for(GlobalObject&, Value time_zone, Instant&);
-Optional<String> builtin_time_zone_get_offset_string_for(GlobalObject&, Object& time_zone, Instant&);
+Optional<String> builtin_time_zone_get_offset_string_for(GlobalObject&, Value time_zone, Instant&);
PlainDateTime* builtin_time_zone_get_plain_date_time_for(GlobalObject&, Value time_zone, Instant&, Object& calendar);
bool is_valid_time_zone_numeric_utc_offset_syntax(String const&);
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp
index 3842b8880c..51b5b1c9b9 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp
@@ -98,7 +98,7 @@ JS_DEFINE_NATIVE_FUNCTION(TimeZonePrototype::get_offset_string_for)
return {};
// 4. Return ? BuiltinTimeZoneGetOffsetStringFor(timeZone, instant).
- auto offset_string = builtin_time_zone_get_offset_string_for(global_object, *time_zone, *instant);
+ auto offset_string = builtin_time_zone_get_offset_string_for(global_object, time_zone, *instant);
if (vm.exception())
return {};
return js_string(vm, move(*offset_string));
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp
index ff25e23d76..4e3e55650d 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp
@@ -700,7 +700,7 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::offset_getter)
auto* instant = create_temporal_instant(global_object, zoned_date_time->nanoseconds());
// 4. Return ? BuiltinTimeZoneGetOffsetStringFor(zonedDateTime.[[TimeZone]], instant).
- auto offset_string = builtin_time_zone_get_offset_string_for(global_object, zoned_date_time->time_zone(), *instant);
+ auto offset_string = builtin_time_zone_get_offset_string_for(global_object, &zoned_date_time->time_zone(), *instant);
if (vm.exception())
return {};
return js_string(vm, move(*offset_string));
@@ -955,7 +955,7 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::get_iso_fields)
return {};
// 8. Let offset be ? BuiltinTimeZoneGetOffsetStringFor(timeZone, instant).
- auto maybe_offset = builtin_time_zone_get_offset_string_for(global_object, time_zone, *instant);
+ auto maybe_offset = builtin_time_zone_get_offset_string_for(global_object, &time_zone, *instant);
if (vm.exception())
return {};
auto offset = move(*maybe_offset);
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js
new file mode 100644
index 0000000000..f39db3c62c
--- /dev/null
+++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js
@@ -0,0 +1,65 @@
+describe("correct behavior", () => {
+ test("length is 0", () => {
+ expect(Temporal.Instant.prototype.toString).toHaveLength(0);
+ });
+
+ test("basic functionality", () => {
+ const instant = new Temporal.Instant(1625614921123456789n);
+ expect(instant.toString()).toBe("2021-07-06T23:42:01.123456789Z");
+ });
+
+ test("timeZone option", () => {
+ const instant = new Temporal.Instant(1625614921123456789n);
+ const options = { timeZone: new Temporal.TimeZone("+01:30") };
+ expect(instant.toString(options)).toBe("2021-07-07T01:12:01.123456789+01:30");
+ });
+
+ test("fractionalSecondDigits option", () => {
+ const instant = new Temporal.Instant(1625614921123456000n);
+ const values = [
+ ["auto", "2021-07-06T23:42:01.123456Z"],
+ [0, "2021-07-06T23:42:01Z"],
+ [1, "2021-07-06T23:42:01.1Z"],
+ [2, "2021-07-06T23:42:01.12Z"],
+ [3, "2021-07-06T23:42:01.123Z"],
+ [4, "2021-07-06T23:42:01.1234Z"],
+ [5, "2021-07-06T23:42:01.12345Z"],
+ [6, "2021-07-06T23:42:01.123456Z"],
+ [7, "2021-07-06T23:42:01.1234560Z"],
+ [8, "2021-07-06T23:42:01.12345600Z"],
+ [9, "2021-07-06T23:42:01.123456000Z"],
+ ];
+ for (const [fractionalSecondDigits, expected] of values) {
+ const options = { fractionalSecondDigits };
+ expect(instant.toString(options)).toBe(expected);
+ }
+
+ // Ignored when smallestUnit is given
+ expect(instant.toString({ smallestUnit: "minute", fractionalSecondDigits: 9 })).toBe(
+ "2021-07-06T23:42Z"
+ );
+ });
+
+ test("smallestUnit option", () => {
+ const instant = new Temporal.Instant(1625614921123456789n);
+ const values = [
+ ["minute", "2021-07-06T23:42Z"],
+ ["second", "2021-07-06T23:42:01Z"],
+ ["millisecond", "2021-07-06T23:42:01.123Z"],
+ ["microsecond", "2021-07-06T23:42:01.123456Z"],
+ ["nanosecond", "2021-07-06T23:42:01.123456789Z"],
+ ];
+ for (const [smallestUnit, expected] of values) {
+ const options = { smallestUnit };
+ expect(instant.toString(options)).toBe(expected);
+ }
+ });
+});
+
+describe("errors", () => {
+ test("this value must be a Temporal.Instant object", () => {
+ expect(() => {
+ Temporal.Instant.prototype.toString.call("foo");
+ }).toThrowWithMessage(TypeError, "Not a Temporal.Instant");
+ });
+});