mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-05 15:49:11 +00:00
LibJS: Implement stringification Temporal.Instant prototypes
This commit is contained in:
parent
90820873a2
commit
615ad70030
Notes:
github-actions[bot]
2024-11-25 12:34:21 +00:00
Author: https://github.com/trflynn89
Commit: 615ad70030
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2557
Reviewed-by: https://github.com/shannonbooth ✅
11 changed files with 387 additions and 4 deletions
|
@ -804,7 +804,7 @@ double apply_unsigned_rounding_mode(double x, double r1, double r2, UnsignedRoun
|
||||||
}
|
}
|
||||||
|
|
||||||
// 13.27 ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode ), https://tc39.es/proposal-temporal/#sec-applyunsignedroundingmode
|
// 13.27 ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode ), https://tc39.es/proposal-temporal/#sec-applyunsignedroundingmode
|
||||||
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const& x, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, UnsignedRoundingMode unsigned_rounding_mode, Crypto::UnsignedBigInteger const& increment)
|
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const& x, Crypto::SignedBigInteger r1, Crypto::SignedBigInteger r2, UnsignedRoundingMode unsigned_rounding_mode, Crypto::UnsignedBigInteger const& increment)
|
||||||
{
|
{
|
||||||
// 1. If x = r1, return r1.
|
// 1. If x = r1, return r1.
|
||||||
if (x.quotient == r1 && x.remainder.unsigned_value().is_zero())
|
if (x.quotient == r1 && x.remainder.unsigned_value().is_zero())
|
||||||
|
@ -947,7 +947,7 @@ Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger cons
|
||||||
auto r2 = division_result.quotient.plus(1_bigint);
|
auto r2 = division_result.quotient.plus(1_bigint);
|
||||||
|
|
||||||
// 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode).
|
// 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode).
|
||||||
auto rounded = apply_unsigned_rounding_mode(division_result, r1, r2, unsigned_rounding_mode, increment);
|
auto rounded = apply_unsigned_rounding_mode(division_result, move(r1), move(r2), unsigned_rounding_mode, increment);
|
||||||
|
|
||||||
// 8. If isNegative is NEGATIVE, set rounded to -rounded.
|
// 8. If isNegative is NEGATIVE, set rounded to -rounded.
|
||||||
if (is_negative == Sign::Negative)
|
if (is_negative == Sign::Negative)
|
||||||
|
@ -957,6 +957,43 @@ Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger cons
|
||||||
return rounded.multiplied_by(increment);
|
return rounded.multiplied_by(increment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 13.29 RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrementasifpositive
|
||||||
|
Crypto::SignedBigInteger round_number_to_increment_as_if_positive(Crypto::SignedBigInteger const& x, Crypto::UnsignedBigInteger const& increment, RoundingMode rounding_mode)
|
||||||
|
{
|
||||||
|
// OPTIMIZATION: If the increment is 1 the number is always rounded.
|
||||||
|
if (increment == 1)
|
||||||
|
return x;
|
||||||
|
|
||||||
|
// 1. Let quotient be x / increment.
|
||||||
|
auto division_result = x.divided_by(increment);
|
||||||
|
|
||||||
|
// OPTIMIZATION: If there's no remainder the number is already rounded.
|
||||||
|
if (division_result.remainder.unsigned_value().is_zero())
|
||||||
|
return x;
|
||||||
|
|
||||||
|
// 2. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, POSITIVE).
|
||||||
|
auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, Sign::Positive);
|
||||||
|
|
||||||
|
// 3. Let r1 be the largest integer such that r1 ≤ quotient.
|
||||||
|
// 4. Let r2 be the smallest integer such that r2 > quotient.
|
||||||
|
Crypto::SignedBigInteger r1;
|
||||||
|
Crypto::SignedBigInteger r2;
|
||||||
|
|
||||||
|
if (x.is_negative()) {
|
||||||
|
r1 = division_result.quotient.minus("1"_sbigint);
|
||||||
|
r2 = division_result.quotient;
|
||||||
|
} else {
|
||||||
|
r1 = division_result.quotient;
|
||||||
|
r2 = division_result.quotient.plus("1"_sbigint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode).
|
||||||
|
auto rounded = apply_unsigned_rounding_mode(division_result, move(r1), move(r2), unsigned_rounding_mode, increment);
|
||||||
|
|
||||||
|
// 6. Return rounded × increment.
|
||||||
|
return rounded.multiplied_by(increment);
|
||||||
|
}
|
||||||
|
|
||||||
// 13.33 ParseISODateTime ( isoString, allowedFormats ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
|
// 13.33 ParseISODateTime ( isoString, allowedFormats ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime
|
||||||
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, StringView iso_string, ReadonlySpan<Production> allowed_formats)
|
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, StringView iso_string, ReadonlySpan<Production> allowed_formats)
|
||||||
{
|
{
|
||||||
|
|
|
@ -161,9 +161,10 @@ String format_fractional_seconds(u64, Precision);
|
||||||
String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseconds, SecondsStringPrecision::Precision, Optional<TimeStyle> = {});
|
String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseconds, SecondsStringPrecision::Precision, Optional<TimeStyle> = {});
|
||||||
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
|
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
|
||||||
double apply_unsigned_rounding_mode(double, double r1, double r2, UnsignedRoundingMode);
|
double apply_unsigned_rounding_mode(double, double r1, double r2, UnsignedRoundingMode);
|
||||||
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, UnsignedRoundingMode, Crypto::UnsignedBigInteger const& increment);
|
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger r1, Crypto::SignedBigInteger r2, UnsignedRoundingMode, Crypto::UnsignedBigInteger const& increment);
|
||||||
double round_number_to_increment(double, u64 increment, RoundingMode);
|
double round_number_to_increment(double, u64 increment, RoundingMode);
|
||||||
Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
||||||
|
Crypto::SignedBigInteger round_number_to_increment_as_if_positive(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
||||||
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM&, StringView iso_string, ReadonlySpan<Production> allowed_formats);
|
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM&, StringView iso_string, ReadonlySpan<Production> allowed_formats);
|
||||||
ThrowCompletionOr<String> parse_temporal_calendar_string(VM&, String const&);
|
ThrowCompletionOr<String> parse_temporal_calendar_string(VM&, String const&);
|
||||||
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);
|
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
#include <LibJS/Runtime/BigInt.h>
|
#include <LibJS/Runtime/BigInt.h>
|
||||||
#include <LibJS/Runtime/Date.h>
|
#include <LibJS/Runtime/Date.h>
|
||||||
#include <LibJS/Runtime/Realm.h>
|
#include <LibJS/Runtime/Realm.h>
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.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>
|
||||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||||
#include <LibJS/Runtime/VM.h>
|
#include <LibJS/Runtime/VM.h>
|
||||||
#include <LibJS/Runtime/ValueInlines.h>
|
#include <LibJS/Runtime/ValueInlines.h>
|
||||||
|
|
||||||
|
@ -164,4 +164,53 @@ i8 compare_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds_o
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 8.5.7 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant
|
||||||
|
Crypto::SignedBigInteger round_temporal_instant(Crypto::SignedBigInteger const& nanoseconds, u64 increment, Unit unit, RoundingMode rounding_mode)
|
||||||
|
{
|
||||||
|
// 1. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
|
||||||
|
auto unit_length = temporal_unit_length_in_nanoseconds(unit);
|
||||||
|
|
||||||
|
// 2. Let incrementNs be increment × unitLength.
|
||||||
|
auto increment_nanoseconds = Crypto::UnsignedBigInteger { increment }.multiplied_by(unit_length);
|
||||||
|
|
||||||
|
// 3. Return ℤ(RoundNumberToIncrementAsIfPositive(ℝ(ns), incrementNs, roundingMode)).
|
||||||
|
return round_number_to_increment_as_if_positive(nanoseconds, increment_nanoseconds, rounding_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.5.8 TemporalInstantToString ( instant, timeZone, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring
|
||||||
|
String temporal_instant_to_string(Instant const& instant, Optional<StringView> time_zone, SecondsStringPrecision::Precision precision)
|
||||||
|
{
|
||||||
|
// 1. Let outputTimeZone be timeZone.
|
||||||
|
// 2. If outputTimeZone is undefined, set outputTimeZone to "UTC".
|
||||||
|
auto output_time_zone = time_zone.value_or("UTC"sv);
|
||||||
|
|
||||||
|
// 3. Let epochNs be instant.[[EpochNanoseconds]].
|
||||||
|
auto const& epoch_nanoseconds = instant.epoch_nanoseconds()->big_integer();
|
||||||
|
|
||||||
|
// 4. Let isoDateTime be GetISODateTimeFor(outputTimeZone, epochNs).
|
||||||
|
auto iso_date_time = get_iso_date_time_for(output_time_zone, epoch_nanoseconds);
|
||||||
|
|
||||||
|
// 5. Let dateTimeString be ISODateTimeToString(isoDateTime, "iso8601", precision, NEVER).
|
||||||
|
auto date_time_string = iso_date_time_to_string(iso_date_time, "iso8601"sv, precision, ShowCalendar::Never);
|
||||||
|
|
||||||
|
String time_zone_string;
|
||||||
|
|
||||||
|
// 6. If timeZone is undefined, then
|
||||||
|
if (!time_zone.has_value()) {
|
||||||
|
// a. Let timeZoneString be "Z".
|
||||||
|
time_zone_string = "Z"_string;
|
||||||
|
}
|
||||||
|
// 7. Else,
|
||||||
|
else {
|
||||||
|
// a. Let offsetNanoseconds be GetOffsetNanosecondsFor(outputTimeZone, epochNs).
|
||||||
|
auto offset_nanoseconds = get_offset_nanoseconds_for(output_time_zone, epoch_nanoseconds);
|
||||||
|
|
||||||
|
// b. Let timeZoneString be FormatDateTimeUTCOffsetRounded(offsetNanoseconds).
|
||||||
|
time_zone_string = format_date_time_utc_offset_rounded(offset_nanoseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Return the string-concatenation of dateTimeString and timeZoneString.
|
||||||
|
return MUST(String::formatted("{}{}", date_time_string, time_zone_string));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||||
#include <LibJS/Runtime/BigInt.h>
|
#include <LibJS/Runtime/BigInt.h>
|
||||||
#include <LibJS/Runtime/Object.h>
|
#include <LibJS/Runtime/Object.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||||
|
|
||||||
namespace JS::Temporal {
|
namespace JS::Temporal {
|
||||||
|
|
||||||
|
@ -59,5 +60,7 @@ bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanosecond
|
||||||
ThrowCompletionOr<GC::Ref<Instant>> create_temporal_instant(VM&, BigInt const& epoch_nanoseconds, GC::Ptr<FunctionObject> new_target = {});
|
ThrowCompletionOr<GC::Ref<Instant>> create_temporal_instant(VM&, BigInt const& epoch_nanoseconds, GC::Ptr<FunctionObject> new_target = {});
|
||||||
ThrowCompletionOr<GC::Ref<Instant>> to_temporal_instant(VM&, Value item);
|
ThrowCompletionOr<GC::Ref<Instant>> to_temporal_instant(VM&, Value item);
|
||||||
i8 compare_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds_one, Crypto::SignedBigInteger const& epoch_nanoseconds_two);
|
i8 compare_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds_one, Crypto::SignedBigInteger const& epoch_nanoseconds_two);
|
||||||
|
Crypto::SignedBigInteger round_temporal_instant(Crypto::SignedBigInteger const& nanoseconds, u64 increment, Unit, RoundingMode);
|
||||||
|
String temporal_instant_to_string(Instant const&, Optional<StringView> time_zone, SecondsStringPrecision::Precision);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
#include <LibJS/Runtime/Temporal/AbstractOperations.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 {
|
||||||
|
|
||||||
|
@ -31,6 +32,9 @@ void InstantPrototype::initialize(Realm& realm)
|
||||||
define_native_accessor(realm, vm.names.epochNanoseconds, epoch_nanoseconds_getter, {}, Attribute::Configurable);
|
define_native_accessor(realm, vm.names.epochNanoseconds, epoch_nanoseconds_getter, {}, Attribute::Configurable);
|
||||||
|
|
||||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||||
|
define_native_function(realm, vm.names.toString, to_string, 0, attr);
|
||||||
|
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
|
||||||
|
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
|
||||||
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
|
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +66,81 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_nanoseconds_getter)
|
||||||
return instant->epoch_nanoseconds();
|
return instant->epoch_nanoseconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 8.3.11 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 = TRY(typed_this_object(vm));
|
||||||
|
|
||||||
|
// 3. Let resolvedOptions be ? GetOptionsObject(options).
|
||||||
|
auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
|
||||||
|
|
||||||
|
// 4. NOTE: The following steps read options and perform independent validation in alphabetical order
|
||||||
|
// (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode").
|
||||||
|
|
||||||
|
// 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions).
|
||||||
|
auto digits = TRY(get_temporal_fractional_second_digits_option(vm, resolved_options));
|
||||||
|
|
||||||
|
// 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
|
||||||
|
auto rounding_mode = TRY(get_rounding_mode_option(vm, resolved_options, RoundingMode::Trunc));
|
||||||
|
|
||||||
|
// 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", time, unset).
|
||||||
|
auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, resolved_options, vm.names.smallestUnit, UnitGroup::Time, Unset {}));
|
||||||
|
|
||||||
|
// 8. If smallestUnit is HOUR, throw a RangeError exception.
|
||||||
|
if (auto const* unit = smallest_unit.get_pointer<Unit>(); unit && *unit == Unit::Hour)
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, temporal_unit_to_string(*unit), vm.names.smallestUnit);
|
||||||
|
|
||||||
|
// 9. Let timeZone be ? Get(resolvedOptions, "timeZone").
|
||||||
|
auto time_zone_value = TRY(resolved_options->get(vm.names.timeZone));
|
||||||
|
|
||||||
|
String time_zone_buffer;
|
||||||
|
Optional<StringView> time_zone;
|
||||||
|
|
||||||
|
// 10. If timeZone is not undefined, then
|
||||||
|
if (!time_zone_value.is_undefined()) {
|
||||||
|
// a. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone).
|
||||||
|
time_zone_buffer = TRY(to_temporal_time_zone_identifier(vm, time_zone_value));
|
||||||
|
time_zone = time_zone_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits).
|
||||||
|
auto precision = to_seconds_string_precision_record(smallest_unit, digits);
|
||||||
|
|
||||||
|
// 12. Let roundedNs be RoundTemporalInstant(instant.[[EpochNanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode).
|
||||||
|
auto rounded_nanoseconds = round_temporal_instant(instant->epoch_nanoseconds()->big_integer(), precision.increment, precision.unit, rounding_mode);
|
||||||
|
|
||||||
|
// 13. Let roundedInstant be ! CreateTemporalInstant(roundedNs).
|
||||||
|
auto rounded_instant = MUST(create_temporal_instant(vm, BigInt::create(vm, move(rounded_nanoseconds))));
|
||||||
|
|
||||||
|
// 14. Return TemporalInstantToString(roundedInstant, timeZone, precision.[[Precision]]).
|
||||||
|
return PrimitiveString::create(vm, temporal_instant_to_string(rounded_instant, time_zone, precision.precision));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.3.12 Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring
|
||||||
|
// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_locale_string)
|
||||||
|
{
|
||||||
|
// 1. Let instant be the this value.
|
||||||
|
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
|
||||||
|
auto instant = TRY(typed_this_object(vm));
|
||||||
|
|
||||||
|
// 3. Return TemporalInstantToString(instant, undefined, AUTO).
|
||||||
|
return PrimitiveString::create(vm, temporal_instant_to_string(instant, {}, Auto {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.3.13 Temporal.Instant.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tojson
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_json)
|
||||||
|
{
|
||||||
|
// 1. Let instant be the this value.
|
||||||
|
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
|
||||||
|
auto instant = TRY(typed_this_object(vm));
|
||||||
|
|
||||||
|
// 3. Return TemporalInstantToString(instant, undefined, AUTO).
|
||||||
|
return PrimitiveString::create(vm, temporal_instant_to_string(instant, {}, Auto {}));
|
||||||
|
}
|
||||||
|
|
||||||
// 8.3.14 Temporal.Instant.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof
|
// 8.3.14 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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,9 @@ private:
|
||||||
|
|
||||||
JS_DECLARE_NATIVE_FUNCTION(epoch_milliseconds_getter);
|
JS_DECLARE_NATIVE_FUNCTION(epoch_milliseconds_getter);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(epoch_nanoseconds_getter);
|
JS_DECLARE_NATIVE_FUNCTION(epoch_nanoseconds_getter);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,70 @@
|
||||||
#include <LibJS/Runtime/Date.h>
|
#include <LibJS/Runtime/Date.h>
|
||||||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/DateEquations.h>
|
||||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||||
|
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||||
#include <LibJS/Runtime/VM.h>
|
#include <LibJS/Runtime/VM.h>
|
||||||
|
|
||||||
namespace JS::Temporal {
|
namespace JS::Temporal {
|
||||||
|
|
||||||
|
// 11.1.2 GetISOPartsFromEpoch ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getisopartsfromepoch
|
||||||
|
ISODateTime get_iso_parts_from_epoch(Crypto::SignedBigInteger const& epoch_nanoseconds)
|
||||||
|
{
|
||||||
|
// 1. Assert: IsValidEpochNanoseconds(ℤ(epochNanoseconds)) is true.
|
||||||
|
VERIFY(is_valid_epoch_nanoseconds(epoch_nanoseconds));
|
||||||
|
|
||||||
|
// 2. Let remainderNs be epochNanoseconds modulo 10**6.
|
||||||
|
auto remainder_nanoseconds = modulo(epoch_nanoseconds, NANOSECONDS_PER_MILLISECOND);
|
||||||
|
auto remainder_nanoseconds_value = remainder_nanoseconds.to_double();
|
||||||
|
|
||||||
|
// 3. Let epochMilliseconds be 𝔽((epochNanoseconds - remainderNs) / 10**6).
|
||||||
|
auto epoch_milliseconds = epoch_nanoseconds.minus(remainder_nanoseconds).divided_by(NANOSECONDS_PER_MILLISECOND).quotient.to_double();
|
||||||
|
|
||||||
|
// 4. Let year be EpochTimeToEpochYear(epochMilliseconds).
|
||||||
|
auto year = epoch_time_to_epoch_year(epoch_milliseconds);
|
||||||
|
|
||||||
|
// 5. Let month be EpochTimeToMonthInYear(epochMilliseconds) + 1.
|
||||||
|
auto month = epoch_time_to_month_in_year(epoch_milliseconds) + 1;
|
||||||
|
|
||||||
|
// 6. Let day be EpochTimeToDate(epochMilliseconds).
|
||||||
|
auto day = epoch_time_to_date(epoch_milliseconds);
|
||||||
|
|
||||||
|
// 7. Let hour be ℝ(HourFromTime(epochMilliseconds)).
|
||||||
|
auto hour = hour_from_time(epoch_milliseconds);
|
||||||
|
|
||||||
|
// 8. Let minute be ℝ(MinFromTime(epochMilliseconds)).
|
||||||
|
auto minute = min_from_time(epoch_milliseconds);
|
||||||
|
|
||||||
|
// 9. Let second be ℝ(SecFromTime(epochMilliseconds)).
|
||||||
|
auto second = sec_from_time(epoch_milliseconds);
|
||||||
|
|
||||||
|
// 10. Let millisecond be ℝ(msFromTime(epochMilliseconds)).
|
||||||
|
auto millisecond = ms_from_time(epoch_milliseconds);
|
||||||
|
|
||||||
|
// 11. Let microsecond be floor(remainderNs / 1000).
|
||||||
|
auto microsecond = floor(remainder_nanoseconds_value / 1000.0);
|
||||||
|
|
||||||
|
// 12. Assert: microsecond < 1000.
|
||||||
|
VERIFY(microsecond < 1000.0);
|
||||||
|
|
||||||
|
// 13. Let nanosecond be remainderNs modulo 1000.
|
||||||
|
auto nanosecond = modulo(remainder_nanoseconds_value, 1000.0);
|
||||||
|
|
||||||
|
// 14. Let isoDate be CreateISODateRecord(year, month, day).
|
||||||
|
auto iso_date = create_iso_date_record(year, month, day);
|
||||||
|
|
||||||
|
// 15. Let time be CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond).
|
||||||
|
auto time = create_time_record(hour, minute, second, millisecond, microsecond, nanosecond);
|
||||||
|
|
||||||
|
// 16. Return CombineISODateAndTimeRecord(isoDate, time).
|
||||||
|
return combine_iso_date_and_time_record(iso_date, time);
|
||||||
|
}
|
||||||
|
|
||||||
// 11.1.5 FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formatoffsettimezoneidentifier
|
// 11.1.5 FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formatoffsettimezoneidentifier
|
||||||
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> style)
|
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> style)
|
||||||
{
|
{
|
||||||
|
@ -40,6 +96,22 @@ String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle
|
||||||
return MUST(String::formatted("{}{}", sign, time_string));
|
return MUST(String::formatted("{}{}", sign, time_string));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 11.1.7 FormatDateTimeUTCOffsetRounded ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatdatetimeutcoffsetrounded
|
||||||
|
String format_date_time_utc_offset_rounded(i64 offset_nanoseconds)
|
||||||
|
{
|
||||||
|
// 1. Set offsetNanoseconds to RoundNumberToIncrement(offsetNanoseconds, 60 × 10**9, HALF-EXPAND).
|
||||||
|
auto offset_nanoseconds_value = round_number_to_increment(static_cast<double>(offset_nanoseconds), 60'000'000'000, RoundingMode::HalfExpand);
|
||||||
|
|
||||||
|
// 2. Let offsetMinutes be offsetNanoseconds / (60 × 10**9).
|
||||||
|
auto offset_minutes = offset_nanoseconds_value / 60'000'000'000;
|
||||||
|
|
||||||
|
// 3. Assert: offsetMinutes is an integer.
|
||||||
|
VERIFY(trunc(offset_minutes) == offset_minutes);
|
||||||
|
|
||||||
|
// 4. Return FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
||||||
|
return format_offset_time_zone_identifier(static_cast<i64>(offset_minutes));
|
||||||
|
}
|
||||||
|
|
||||||
// 11.1.8 ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezoneidentifier
|
// 11.1.8 ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezoneidentifier
|
||||||
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, Value temporal_time_zone_like)
|
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, Value temporal_time_zone_like)
|
||||||
{
|
{
|
||||||
|
@ -79,6 +151,33 @@ ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, StringView te
|
||||||
return time_zone_identifier_record->identifier;
|
return time_zone_identifier_record->identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 11.1.9 GetOffsetNanosecondsFor ( timeZone, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor
|
||||||
|
i64 get_offset_nanoseconds_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
|
||||||
|
{
|
||||||
|
// 1. Let parseResult be ! ParseTimeZoneIdentifier(timeZone).
|
||||||
|
auto parse_result = parse_time_zone_identifier(time_zone);
|
||||||
|
|
||||||
|
// 2. If parseResult.[[OffsetMinutes]] is not empty, return parseResult.[[OffsetMinutes]] × (60 × 10**9).
|
||||||
|
if (parse_result.offset_minutes.has_value())
|
||||||
|
return *parse_result.offset_minutes * 60'000'000'000;
|
||||||
|
|
||||||
|
// 3. Return GetNamedTimeZoneOffsetNanoseconds(parseResult.[[Name]], epochNs).
|
||||||
|
return get_named_time_zone_offset_nanoseconds(*parse_result.name, epoch_nanoseconds).offset.to_nanoseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11.1.10 GetISODateTimeFor ( timeZone, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-getisodatetimefor
|
||||||
|
ISODateTime get_iso_date_time_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
|
||||||
|
{
|
||||||
|
// 1. Let offsetNanoseconds be GetOffsetNanosecondsFor(timeZone, epochNs).
|
||||||
|
auto offset_nanoseconds = get_offset_nanoseconds_for(time_zone, epoch_nanoseconds);
|
||||||
|
|
||||||
|
// 2. Let result be GetISOPartsFromEpoch(ℝ(epochNs)).
|
||||||
|
auto result = get_iso_parts_from_epoch(epoch_nanoseconds);
|
||||||
|
|
||||||
|
// 3. Return BalanceISODateTime(result.[[ISODate]].[[Year]], result.[[ISODate]].[[Month]], result.[[ISODate]].[[Day]], result.[[Time]].[[Hour]], result.[[Time]].[[Minute]], result.[[Time]].[[Second]], result.[[Time]].[[Millisecond]], result.[[Time]].[[Microsecond]], result.[[Time]].[[Nanosecond]] + offsetNanoseconds).
|
||||||
|
return balance_iso_date_time(result.iso_date.year, result.iso_date.month, result.iso_date.day, result.time.hour, result.time.minute, result.time.second, result.time.millisecond, result.time.microsecond, static_cast<double>(result.time.nanosecond) + static_cast<double>(offset_nanoseconds));
|
||||||
|
}
|
||||||
|
|
||||||
// 11.1.11 GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor
|
// 11.1.11 GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor
|
||||||
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM& vm, StringView time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
|
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM& vm, StringView time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,8 +28,12 @@ enum class Disambiguation {
|
||||||
Reject,
|
Reject,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ISODateTime get_iso_parts_from_epoch(Crypto::SignedBigInteger const& epoch_nanoseconds);
|
||||||
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> = {});
|
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> = {});
|
||||||
|
String format_date_time_utc_offset_rounded(i64 offset_nanoseconds);
|
||||||
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like);
|
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like);
|
||||||
|
i64 get_offset_nanoseconds_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds);
|
||||||
|
ISODateTime get_iso_date_time_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds);
|
||||||
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, StringView temporal_time_zone_like);
|
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, StringView temporal_time_zone_like);
|
||||||
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM&, StringView time_zone, ISODateTime const&, Disambiguation);
|
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM&, StringView time_zone, ISODateTime const&, Disambiguation);
|
||||||
ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM&, Vector<Crypto::SignedBigInteger> possible_epoch_ns, StringView time_zone, ISODateTime const&, Disambiguation);
|
ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM&, Vector<Crypto::SignedBigInteger> possible_epoch_ns, StringView time_zone, ISODateTime const&, Disambiguation);
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
describe("correct behavior", () => {
|
||||||
|
test("length is 0", () => {
|
||||||
|
expect(Temporal.Instant.prototype.toJSON).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("basic functionality", () => {
|
||||||
|
const instant = new Temporal.Instant(1625614921123456789n);
|
||||||
|
expect(instant.toJSON()).toBe("2021-07-06T23:42:01.123456789Z");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
test("this value must be a Temporal.Instant object", () => {
|
||||||
|
expect(() => {
|
||||||
|
Temporal.Instant.prototype.toJSON.call("foo");
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Instant");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
describe("correct behavior", () => {
|
||||||
|
test("length is 0", () => {
|
||||||
|
expect(Temporal.Instant.prototype.toLocaleString).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("basic functionality", () => {
|
||||||
|
const instant = new Temporal.Instant(1625614921123456789n);
|
||||||
|
expect(instant.toLocaleString()).toBe("2021-07-06T23:42:01.123456789Z");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
test("this value must be a Temporal.Instant object", () => {
|
||||||
|
expect(() => {
|
||||||
|
Temporal.Instant.prototype.toLocaleString.call("foo");
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Instant");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,72 @@
|
||||||
|
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: "+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 an object of type Temporal.Instant");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom time zone doesn't have a getOffsetNanosecondsFor function", () => {
|
||||||
|
const instant = new Temporal.Instant(0n);
|
||||||
|
expect(() => {
|
||||||
|
instant.toString({ timeZone: null });
|
||||||
|
}).toThrowWithMessage(TypeError, "Invalid time zone name 'null");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue