mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:17:36 +00:00
LibJS: Implement Temporal.Instant.prototype.toString()
This commit is contained in:
parent
1c65ee6edf
commit
576be0f8e7
14 changed files with 396 additions and 6 deletions
|
@ -164,6 +164,7 @@ namespace JS {
|
||||||
P(fontcolor) \
|
P(fontcolor) \
|
||||||
P(fontsize) \
|
P(fontsize) \
|
||||||
P(forEach) \
|
P(forEach) \
|
||||||
|
P(fractionalSecondDigits) \
|
||||||
P(freeze) \
|
P(freeze) \
|
||||||
P(from) \
|
P(from) \
|
||||||
P(fromCharCode) \
|
P(fromCharCode) \
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <AK/CharacterTypes.h>
|
#include <AK/CharacterTypes.h>
|
||||||
#include <AK/DateTimeLexer.h>
|
#include <AK/DateTimeLexer.h>
|
||||||
|
#include <AK/Variant.h>
|
||||||
#include <LibJS/Runtime/IteratorOperations.h>
|
#include <LibJS/Runtime/IteratorOperations.h>
|
||||||
#include <LibJS/Runtime/PropertyName.h>
|
#include <LibJS/Runtime/PropertyName.h>
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||||
|
@ -168,6 +169,44 @@ Value get_option(GlobalObject& global_object, Object& options, PropertyName cons
|
||||||
return value;
|
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
|
// 13.6 ToTemporalOverflow ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
|
||||||
Optional<String> to_temporal_overflow(GlobalObject& global_object, Object& normalized_options)
|
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;
|
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
|
// https://tc39.es/proposal-temporal/#table-temporal-singular-and-plural-units
|
||||||
static HashMap<StringView, StringView> plural_to_singular_units = {
|
static HashMap<StringView, StringView> plural_to_singular_units = {
|
||||||
{ "years"sv, "year"sv },
|
{ "years"sv, "year"sv },
|
||||||
|
@ -313,6 +434,56 @@ Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object&
|
||||||
return smallest_unit;
|
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
|
// 13.29 ConstrainToRange ( x, minimum, maximum ), https://tc39.es/proposal-temporal/#sec-temporal-constraintorange
|
||||||
double constrain_to_range(double x, double minimum, double maximum)
|
double constrain_to_range(double x, double minimum, double maximum)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <AK/Forward.h>
|
#include <AK/Forward.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
|
#include <AK/Variant.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
|
||||||
|
@ -69,14 +70,24 @@ struct TemporalTimeZone {
|
||||||
Optional<String> name;
|
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);
|
MarkedValueList iterable_to_list_of_type(GlobalObject&, Value items, Vector<OptionType> const& element_types);
|
||||||
Object* get_options_object(GlobalObject&, Value options);
|
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);
|
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_overflow(GlobalObject&, Object& normalized_options);
|
||||||
Optional<String> to_temporal_rounding_mode(GlobalObject&, Object& normalized_options, String const& fallback);
|
Optional<String> to_temporal_rounding_mode(GlobalObject&, Object& normalized_options, String const& fallback);
|
||||||
Optional<String> to_show_calendar_option(GlobalObject&, Object& normalized_options);
|
Optional<String> to_show_calendar_option(GlobalObject&, Object& normalized_options);
|
||||||
u64 to_temporal_rounding_increment(GlobalObject&, Object& normalized_options, Optional<double> dividend, bool inclusive);
|
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);
|
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);
|
double constrain_to_range(double x, double minimum, double maximum);
|
||||||
BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, String const& rounding_mode);
|
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);
|
Optional<ISODateTime> parse_iso_date_time(GlobalObject&, String const& iso_string);
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Variant.h>
|
||||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||||
#include <LibJS/Runtime/AbstractOperations.h>
|
#include <LibJS/Runtime/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||||
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
|
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
|
||||||
#include <LibJS/Runtime/Temporal/PlainDateTime.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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/Variant.h>
|
||||||
#include <LibJS/Runtime/BigInt.h>
|
#include <LibJS/Runtime/BigInt.h>
|
||||||
#include <LibJS/Runtime/Object.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&);
|
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* 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);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||||
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
|
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||||
|
|
||||||
namespace JS::Temporal {
|
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.subtract, subtract, 1, attr);
|
||||||
define_native_function(vm.names.round, round, 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.toString, to_string, 0, attr);
|
||||||
define_native_function(vm.names.valueOf, value_of, 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);
|
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
|
// 8.3.16 Temporal.Instant.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof
|
||||||
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::value_of)
|
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::value_of)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,7 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(round);
|
JS_DECLARE_NATIVE_FUNCTION(round);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(equals);
|
JS_DECLARE_NATIVE_FUNCTION(equals);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -302,6 +302,34 @@ PlainDateTime* create_temporal_date_time(GlobalObject& global_object, i32 iso_ye
|
||||||
return object;
|
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
|
// 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)
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/Variant.h>
|
||||||
#include <LibJS/Runtime/Object.h>
|
#include <LibJS/Runtime/Object.h>
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.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);
|
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);
|
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);
|
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);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// 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();
|
auto& vm = global_object.vm();
|
||||||
|
|
||||||
// 1. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant).
|
// 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())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ double parse_time_zone_offset_string(GlobalObject&, String const&);
|
||||||
String format_time_zone_offset_string(double offset_nanoseconds);
|
String format_time_zone_offset_string(double offset_nanoseconds);
|
||||||
Object* to_temporal_time_zone(GlobalObject&, Value temporal_time_zone_like);
|
Object* to_temporal_time_zone(GlobalObject&, Value temporal_time_zone_like);
|
||||||
double get_offset_nanoseconds_for(GlobalObject&, Value time_zone, Instant&);
|
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);
|
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&);
|
bool is_valid_time_zone_numeric_utc_offset_syntax(String const&);
|
||||||
|
|
|
@ -98,7 +98,7 @@ JS_DEFINE_NATIVE_FUNCTION(TimeZonePrototype::get_offset_string_for)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// 4. Return ? BuiltinTimeZoneGetOffsetStringFor(timeZone, instant).
|
// 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())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
return js_string(vm, move(*offset_string));
|
return js_string(vm, move(*offset_string));
|
||||||
|
|
|
@ -700,7 +700,7 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::offset_getter)
|
||||||
auto* instant = create_temporal_instant(global_object, zoned_date_time->nanoseconds());
|
auto* instant = create_temporal_instant(global_object, zoned_date_time->nanoseconds());
|
||||||
|
|
||||||
// 4. Return ? BuiltinTimeZoneGetOffsetStringFor(zonedDateTime.[[TimeZone]], instant).
|
// 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())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
return js_string(vm, move(*offset_string));
|
return js_string(vm, move(*offset_string));
|
||||||
|
@ -955,7 +955,7 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::get_iso_fields)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// 8. Let offset be ? BuiltinTimeZoneGetOffsetStringFor(timeZone, instant).
|
// 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())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
auto offset = move(*maybe_offset);
|
auto offset = move(*maybe_offset);
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue