mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-29 11:00:29 +00:00
LibJS: Implement ToTemporalZonedDateTime and the required AOs
This commit is contained in:
parent
3b8853c3cd
commit
2b89d2a360
Notes:
sideshowbarker
2024-07-18 01:26:56 +09:00
Author: https://github.com/Lubrsi Commit: https://github.com/SerenityOS/serenity/commit/2b89d2a3607 Pull-request: https://github.com/SerenityOS/serenity/pull/10835 Reviewed-by: https://github.com/IdanHo ✅
5 changed files with 269 additions and 0 deletions
|
@ -223,6 +223,7 @@
|
|||
M(TemporalInvalidTime, "Invalid time") \
|
||||
M(TemporalInvalidTimeZoneName, "Invalid time zone name") \
|
||||
M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}") \
|
||||
M(TemporalInvalidZonedDateTimeOffset, "Invalid offset for the provided date and time in the current time zone") \
|
||||
M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \
|
||||
M(TemporalObjectMustNotHave, "Object must not have a defined {} property") \
|
||||
M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \
|
||||
|
|
|
@ -224,6 +224,18 @@ ThrowCompletionOr<String> to_temporal_rounding_mode(GlobalObject& global_object,
|
|||
return option.as_string().string();
|
||||
}
|
||||
|
||||
// 13.10 ToTemporalOffset ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaloffset
|
||||
ThrowCompletionOr<String> to_temporal_offset(GlobalObject& global_object, Object const& normalized_options, String const& fallback)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Return ? GetOption(normalizedOptions, "offset", « String », « "prefer", "use", "ignore", "reject" », fallback).
|
||||
auto option = TRY(get_option(global_object, normalized_options, vm.names.offset, { OptionType::String }, { "prefer"sv, "use"sv, "ignore"sv, "reject"sv }, js_string(vm, fallback)));
|
||||
|
||||
VERIFY(option.is_string());
|
||||
return option.as_string().string();
|
||||
}
|
||||
|
||||
// 13.11 ToShowCalendarOption ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
|
||||
ThrowCompletionOr<String> to_show_calendar_option(GlobalObject& global_object, Object const& normalized_options)
|
||||
{
|
||||
|
@ -950,6 +962,27 @@ ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject& g
|
|||
return TemporalInstant { .year = result.year, .month = result.month, .day = result.day, .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .time_zone_offset = move(offset_string) };
|
||||
}
|
||||
|
||||
// 13.36 ParseTemporalZonedDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalzoneddatetimestring
|
||||
ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(GlobalObject& global_object, String const& iso_string)
|
||||
{
|
||||
// 1. Assert: Type(isoString) is String.
|
||||
|
||||
// 2. If isoString does not satisfy the syntax of a TemporalZonedDateTimeString (see 13.33), then
|
||||
// a. Throw a RangeError exception.
|
||||
// TODO
|
||||
|
||||
// 3. Let result be ! ParseISODateTime(isoString).
|
||||
auto result = MUST(parse_iso_date_time(global_object, iso_string));
|
||||
|
||||
// 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString).
|
||||
auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string));
|
||||
|
||||
// 5. Return the Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]], [[TimeZoneZ]]: timeZoneResult.[[Z]], [[TimeZoneOffsetString]]: timeZoneResult.[[OffsetString]], [[TimeZoneName]]: timeZoneResult.[[Name]] }.
|
||||
// NOTE: This returns the two structs together instead of separated to avoid a copy in ToTemporalZonedDateTime, as the spec tries to put the result of InterpretTemporalDateTimeFields and ParseTemporalZonedDateTimeString into the same `result` variable.
|
||||
// InterpretTemporalDateTimeFields returns an ISODateTime, so the moved in `result` here is subsequently moved into ParseTemporalZonedDateTimeString's `result` variable.
|
||||
return TemporalZonedDateTime { .date_time = move(result), .time_zone = move(time_zone_result) };
|
||||
}
|
||||
|
||||
// 13.37 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
|
||||
ThrowCompletionOr<String> parse_temporal_calendar_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
|
||||
{
|
||||
|
|
|
@ -78,6 +78,11 @@ struct TemporalYearMonth {
|
|||
Optional<String> calendar = {};
|
||||
};
|
||||
|
||||
struct TemporalZonedDateTime {
|
||||
ISODateTime date_time;
|
||||
TemporalTimeZone time_zone;
|
||||
};
|
||||
|
||||
struct SecondsStringPrecision {
|
||||
Variant<StringView, u8> precision;
|
||||
String unit;
|
||||
|
@ -92,6 +97,7 @@ ThrowCompletionOr<Variant<String, NumberType>> get_string_or_number_option(Globa
|
|||
ThrowCompletionOr<String> to_temporal_overflow(GlobalObject&, Object const& normalized_options);
|
||||
ThrowCompletionOr<String> to_temporal_disambiguation(GlobalObject&, Object const& normalized_options);
|
||||
ThrowCompletionOr<String> to_temporal_rounding_mode(GlobalObject&, Object const& normalized_options, String const& fallback);
|
||||
ThrowCompletionOr<String> to_temporal_offset(GlobalObject&, Object const& normalized_options, String const& fallback);
|
||||
ThrowCompletionOr<String> to_show_calendar_option(GlobalObject&, Object const& normalized_options);
|
||||
ThrowCompletionOr<u64> to_temporal_rounding_increment(GlobalObject&, Object const& normalized_options, Optional<double> dividend, bool inclusive);
|
||||
ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(GlobalObject&, Object const& normalized_options, StringView smallest_unit);
|
||||
|
@ -109,6 +115,7 @@ i64 round_number_to_increment(double, u64 increment, StringView rounding_mode);
|
|||
BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, StringView rounding_mode);
|
||||
ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject&, String const& iso_string);
|
||||
ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject&, String const& iso_string);
|
||||
ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(GlobalObject&, String const& iso_string);
|
||||
ThrowCompletionOr<String> parse_temporal_calendar_string(GlobalObject&, String const& iso_string);
|
||||
ThrowCompletionOr<TemporalDate> parse_temporal_date_string(GlobalObject&, String const& iso_string);
|
||||
ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(GlobalObject&, String const& iso_string);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -35,6 +36,220 @@ void ZonedDateTime::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(&m_calendar);
|
||||
}
|
||||
|
||||
// 6.5.1 InterpretISODateTimeOffset ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour ), https://tc39.es/proposal-temporal/#sec-temporal-interpretisodatetimeoffset
|
||||
ThrowCompletionOr<BigInt const*> interpret_iso_date_time_offset(GlobalObject& global_object, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, OffsetBehavior offset_behavior, double offset_nanoseconds, Value time_zone, StringView disambiguation, StringView offset_option, MatchBehavior match_behavior)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Assert: offsetNanoseconds is an integer.
|
||||
VERIFY(trunc(offset_nanoseconds) == offset_nanoseconds);
|
||||
|
||||
// 2. Let calendar be ! GetISO8601Calendar().
|
||||
auto* calendar = get_iso8601_calendar(global_object);
|
||||
|
||||
// 3. Let dateTime be ? CreateTemporalDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar).
|
||||
auto* date_time = TRY(create_temporal_date_time(global_object, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, *calendar));
|
||||
|
||||
// 4. If offsetBehaviour is wall, or offsetOption is "ignore", then
|
||||
if (offset_behavior == OffsetBehavior::Wall || offset_option == "ignore"sv) {
|
||||
// a. Let instant be ? BuiltinTimeZoneGetInstantFor(timeZone, dateTime, disambiguation).
|
||||
auto* instant = TRY(builtin_time_zone_get_instant_for(global_object, time_zone, *date_time, disambiguation));
|
||||
|
||||
// b. Return instant.[[Nanoseconds]].
|
||||
return &instant->nanoseconds();
|
||||
}
|
||||
|
||||
// 5. If offsetBehaviour is exact, or offsetOption is "use", then
|
||||
if (offset_behavior == OffsetBehavior::Exact || offset_option == "use"sv) {
|
||||
// a. Let epochNanoseconds be ! GetEpochFromISOParts(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond).
|
||||
auto* epoch_nanoseconds = get_epoch_from_iso_parts(global_object, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
|
||||
|
||||
// b. Return epochNanoseconds − offsetNanoseconds.
|
||||
auto offset_nanoseconds_bigint = Crypto::SignedBigInteger::create_from((i64)offset_nanoseconds);
|
||||
return js_bigint(vm, epoch_nanoseconds->big_integer().minus(offset_nanoseconds_bigint));
|
||||
}
|
||||
|
||||
// 6. Assert: offsetBehaviour is option.
|
||||
VERIFY(offset_behavior == OffsetBehavior::Option);
|
||||
|
||||
// 7. Assert: offsetOption is "prefer" or "reject".
|
||||
VERIFY(offset_option.is_one_of("prefer"sv, "reject"sv));
|
||||
|
||||
// 8. Let possibleInstants be ? GetPossibleInstantsFor(timeZone, dateTime).
|
||||
auto possible_instants = TRY(get_possible_instants_for(global_object, time_zone, *date_time));
|
||||
|
||||
// 9. For each element candidate of possibleInstants, do
|
||||
for (auto& candidate_value : possible_instants) {
|
||||
// TODO: As per the comment in disambiguate_possible_instants, having a MarkedValueList<T> would allow us to remove this cast.
|
||||
auto& candidate = static_cast<Instant&>(candidate_value.as_object());
|
||||
|
||||
// a. Let candidateNanoseconds be ? GetOffsetNanosecondsFor(timeZone, candidate).
|
||||
auto candidate_nanoseconds = TRY(get_offset_nanoseconds_for(global_object, time_zone, candidate));
|
||||
|
||||
// b. If candidateNanoseconds = offsetNanoseconds, then
|
||||
if (candidate_nanoseconds == offset_nanoseconds) {
|
||||
// i. Return candidate.[[Nanoseconds]].
|
||||
return &candidate.nanoseconds();
|
||||
}
|
||||
|
||||
// c. If matchBehaviour is match minutes, then
|
||||
if (match_behavior == MatchBehavior::MatchMinutes) {
|
||||
// i. Let roundedCandidateNanoseconds be ! RoundNumberToIncrement(candidateNanoseconds, 60 × 10^9, "halfExpand").
|
||||
auto rounded_candidate_nanoseconds = round_number_to_increment(candidate_nanoseconds, 60000000000, "halfExpand"sv);
|
||||
|
||||
// ii. If roundedCandidateNanoseconds = offsetNanoseconds, then
|
||||
if (rounded_candidate_nanoseconds == offset_nanoseconds) {
|
||||
// 1. Return candidate.[[Nanoseconds]].
|
||||
return &candidate.nanoseconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10. If offsetOption is "reject", throw a RangeError exception.
|
||||
if (offset_option == "reject"sv)
|
||||
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidZonedDateTimeOffset);
|
||||
|
||||
// 11. Let instant be ? DisambiguatePossibleInstants(possibleInstants, timeZone, dateTime, disambiguation).
|
||||
auto* instant = TRY(disambiguate_possible_instants(global_object, possible_instants, time_zone, *date_time, disambiguation));
|
||||
|
||||
// 12. Return instant.[[Nanoseconds]].
|
||||
return &instant->nanoseconds();
|
||||
}
|
||||
|
||||
// 6.5.2 ToTemporalZonedDateTime ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalzoneddatetime
|
||||
ThrowCompletionOr<ZonedDateTime*> to_temporal_zoned_date_time(GlobalObject& global_object, Value item, Object* options)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. If options is not present, set options to ! OrdinaryObjectCreate(null).
|
||||
if (!options)
|
||||
options = Object::create(global_object, nullptr);
|
||||
|
||||
// 2. Let offsetBehaviour be option.
|
||||
auto offset_behavior = OffsetBehavior::Option;
|
||||
|
||||
// 3. Let matchBehaviour be match exactly.
|
||||
auto match_behavior = MatchBehavior::MatchExactly;
|
||||
|
||||
Object* calendar = nullptr;
|
||||
Object* time_zone = nullptr;
|
||||
Optional<String> offset_string;
|
||||
ISODateTime result;
|
||||
|
||||
// 4. If Type(item) is Object, then
|
||||
if (item.is_object()) {
|
||||
auto& item_object = item.as_object();
|
||||
|
||||
// a. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||
if (is<ZonedDateTime>(item_object)) {
|
||||
// i. Return item.
|
||||
return &static_cast<ZonedDateTime&>(item_object);
|
||||
}
|
||||
|
||||
// b. Let calendar be ? GetTemporalCalendarWithISODefault(item).
|
||||
calendar = TRY(get_temporal_calendar_with_iso_default(global_object, item_object));
|
||||
|
||||
// c. Let fieldNames be ? CalendarFields(calendar, « "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year" »).
|
||||
auto field_names = TRY(calendar_fields(global_object, *calendar, { "day"sv, "hour"sv, "microsecond"sv, "millisecond"sv, "minute"sv, "month"sv, "monthCode"sv, "nanosecond"sv, "second"sv, "year"sv }));
|
||||
|
||||
// d. Append "timeZone" to fieldNames.
|
||||
field_names.append("timeZone");
|
||||
|
||||
// e. Append "offset" to fieldNames.
|
||||
field_names.append("offset");
|
||||
|
||||
// f. Let fields be ? PrepareTemporalFields(item, fieldNames, « "timeZone" »).
|
||||
auto* fields = TRY(prepare_temporal_fields(global_object, item_object, field_names, { "timeZone"sv }));
|
||||
|
||||
// g. Let timeZone be ? Get(fields, "timeZone").
|
||||
auto time_zone_value = TRY(fields->get(vm.names.timeZone));
|
||||
|
||||
// h. Set timeZone to ? ToTemporalTimeZone(timeZone).
|
||||
time_zone = TRY(to_temporal_time_zone(global_object, time_zone_value));
|
||||
|
||||
// i. Let offsetString be ? Get(fields, "offset").
|
||||
auto offset_string_value = TRY(fields->get(vm.names.offset));
|
||||
|
||||
// j. If offsetString is undefined, then
|
||||
if (offset_string_value.is_undefined()) {
|
||||
// i. Set offsetBehaviour to wall.
|
||||
offset_behavior = OffsetBehavior::Wall;
|
||||
}
|
||||
// k. Else,
|
||||
else {
|
||||
// i. Set offsetString to ? ToString(offsetString).
|
||||
offset_string = TRY(offset_string_value.to_string(global_object));
|
||||
}
|
||||
|
||||
// l. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, options).
|
||||
result = TRY(interpret_temporal_date_time_fields(global_object, *calendar, *fields, *options));
|
||||
}
|
||||
// 5. Else,
|
||||
else {
|
||||
// a. Perform ? ToTemporalOverflow(options).
|
||||
(void)TRY(to_temporal_overflow(global_object, *options));
|
||||
|
||||
// b. Let string be ? ToString(item).
|
||||
auto string = TRY(item.to_string(global_object));
|
||||
|
||||
// c. Let result be ? ParseTemporalZonedDateTimeString(string).
|
||||
auto parsed_result = TRY(parse_temporal_zoned_date_time_string(global_object, string));
|
||||
|
||||
// NOTE: The ISODateTime struct inside parsed_result will be moved into `result` at the end of this path to avoid mismatching names.
|
||||
// Thus, all remaining references to `result` in this path actually refers to `parsed_result`.
|
||||
|
||||
// d. Assert: result.[[TimeZoneName]] is not undefined.
|
||||
VERIFY(parsed_result.time_zone.name.has_value());
|
||||
|
||||
// e. Let offsetString be result.[[TimeZoneOffsetString]].
|
||||
offset_string = move(parsed_result.time_zone.offset);
|
||||
|
||||
// f. If result.[[TimeZoneZ]] is true, then
|
||||
if (parsed_result.time_zone.z) {
|
||||
// i. Set offsetBehaviour to exact.
|
||||
offset_behavior = OffsetBehavior::Exact;
|
||||
}
|
||||
// g. Else if offsetString is undefined, then
|
||||
else if (!offset_string.has_value()) {
|
||||
// i. Set offsetBehaviour to wall.
|
||||
offset_behavior = OffsetBehavior::Wall;
|
||||
}
|
||||
|
||||
// h. Let timeZone be ? CreateTemporalTimeZone(result.[[TimeZoneName]]).
|
||||
time_zone = TRY(create_temporal_time_zone(global_object, *parsed_result.time_zone.name));
|
||||
|
||||
// i. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]).
|
||||
calendar = TRY(to_temporal_calendar_with_iso_default(global_object, js_string(vm, parsed_result.date_time.calendar.value())));
|
||||
|
||||
// j. Set matchBehaviour to match minutes.
|
||||
match_behavior = MatchBehavior::MatchMinutes;
|
||||
|
||||
// See NOTE above about why this is done.
|
||||
result = move(parsed_result.date_time);
|
||||
}
|
||||
|
||||
// 6. Let offsetNanoseconds be 0.
|
||||
double offset_nanoseconds = 0;
|
||||
|
||||
// 7. If offsetBehaviour is option, then
|
||||
if (offset_behavior == OffsetBehavior::Option) {
|
||||
// a. Set offsetNanoseconds to ? ParseTimeZoneOffsetString(offsetString).
|
||||
offset_nanoseconds = TRY(parse_time_zone_offset_string(global_object, *offset_string));
|
||||
}
|
||||
|
||||
// 8. Let disambiguation be ? ToTemporalDisambiguation(options).
|
||||
auto disambiguation = TRY(to_temporal_disambiguation(global_object, *options));
|
||||
|
||||
// 9. Let offsetOption be ? ToTemporalOffset(options, "reject").
|
||||
auto offset_option = TRY(to_temporal_offset(global_object, *options, "reject"));
|
||||
|
||||
// 10. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour).
|
||||
auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, offset_behavior, offset_nanoseconds, time_zone, disambiguation, offset_option, match_behavior));
|
||||
|
||||
// 11. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
|
||||
return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, *time_zone, *calendar));
|
||||
}
|
||||
|
||||
// 6.5.3 CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalzoneddatetime
|
||||
ThrowCompletionOr<ZonedDateTime*> create_temporal_zoned_date_time(GlobalObject& global_object, BigInt const& epoch_nanoseconds, Object& time_zone, Object& calendar, FunctionObject const* new_target)
|
||||
{
|
||||
|
|
|
@ -40,6 +40,19 @@ struct NanosecondsToDaysResult {
|
|||
double day_length;
|
||||
};
|
||||
|
||||
enum class OffsetBehavior {
|
||||
Option,
|
||||
Exact,
|
||||
Wall,
|
||||
};
|
||||
|
||||
enum class MatchBehavior {
|
||||
MatchExactly,
|
||||
MatchMinutes,
|
||||
};
|
||||
|
||||
ThrowCompletionOr<BigInt const*> interpret_iso_date_time_offset(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, OffsetBehavior offset_behavior, double offset_nanoseconds, Value time_zone, StringView disambiguation, StringView offset_option, MatchBehavior match_behavior);
|
||||
ThrowCompletionOr<ZonedDateTime*> to_temporal_zoned_date_time(GlobalObject&, Value item, Object* options = nullptr);
|
||||
ThrowCompletionOr<ZonedDateTime*> create_temporal_zoned_date_time(GlobalObject&, BigInt const& epoch_nanoseconds, Object& time_zone, Object& calendar, FunctionObject const* new_target = nullptr);
|
||||
ThrowCompletionOr<BigInt*> add_zoned_date_time(GlobalObject&, BigInt const& epoch_nanoseconds, Value time_zone, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options = nullptr);
|
||||
ThrowCompletionOr<NanosecondsToDaysResult> nanoseconds_to_days(GlobalObject&, BigInt const& nanoseconds, Value relative_to);
|
||||
|
|
Loading…
Reference in a new issue