mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
Merge 91e25ca588
into 001df24935
This commit is contained in:
commit
a9153713ab
43 changed files with 4172 additions and 344 deletions
|
@ -205,12 +205,21 @@ set(SOURCES
|
|||
Runtime/SymbolObject.cpp
|
||||
Runtime/SymbolPrototype.cpp
|
||||
Runtime/Temporal/AbstractOperations.cpp
|
||||
Runtime/Temporal/Calendar.cpp
|
||||
Runtime/Temporal/DateEquations.cpp
|
||||
Runtime/Temporal/Duration.cpp
|
||||
Runtime/Temporal/DurationConstructor.cpp
|
||||
Runtime/Temporal/DurationPrototype.cpp
|
||||
Runtime/Temporal/Instant.cpp
|
||||
Runtime/Temporal/ISO8601.cpp
|
||||
Runtime/Temporal/PlainDate.cpp
|
||||
Runtime/Temporal/PlainDateTime.cpp
|
||||
Runtime/Temporal/PlainMonthDay.cpp
|
||||
Runtime/Temporal/PlainMonthDayConstructor.cpp
|
||||
Runtime/Temporal/PlainMonthDayPrototype.cpp
|
||||
Runtime/Temporal/PlainTime.cpp
|
||||
Runtime/Temporal/Temporal.cpp
|
||||
Runtime/Temporal/TimeZone.cpp
|
||||
Runtime/TypedArray.cpp
|
||||
Runtime/TypedArrayConstructor.cpp
|
||||
Runtime/TypedArrayPrototype.cpp
|
||||
|
|
|
@ -87,8 +87,9 @@
|
|||
__JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \
|
||||
__JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor)
|
||||
|
||||
#define JS_ENUMERATE_TEMPORAL_OBJECTS \
|
||||
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor)
|
||||
#define JS_ENUMERATE_TEMPORAL_OBJECTS \
|
||||
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
|
||||
__JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor)
|
||||
|
||||
#define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \
|
||||
__JS_ENUMERATE(AtomicsObject, atomics) \
|
||||
|
@ -277,6 +278,18 @@ JS_ENUMERATE_TEMPORAL_OBJECTS
|
|||
#undef __JS_ENUMERATE
|
||||
|
||||
class Temporal;
|
||||
|
||||
struct CalendarDate;
|
||||
struct CalendarFields;
|
||||
struct DateDuration;
|
||||
struct InternalDuration;
|
||||
struct ISODate;
|
||||
struct ISODateTime;
|
||||
struct ParseResult;
|
||||
struct PartialDuration;
|
||||
struct Time;
|
||||
struct TimeZone;
|
||||
struct TimeZoneOffset;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include <LibJS/Runtime/StringObject.h>
|
||||
#include <LibJS/Runtime/StringPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibJS/Runtime/WeakMap.h>
|
||||
|
@ -835,6 +836,15 @@ ErrorOr<void> print_temporal_duration(JS::PrintContext& print_context, JS::Tempo
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_temporal_plain_month_day(JS::PrintContext& print_context, JS::Temporal::PlainMonthDay const& plain_month_day, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Temporal.PlainMonthDay"sv));
|
||||
TRY(js_out(print_context, " \033[34;1m{:02}-{:02}\033[0m", plain_month_day.iso_date().month, plain_month_day.iso_date().day));
|
||||
TRY(js_out(print_context, "\n calendar: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(plain_month_day.vm(), plain_month_day.calendar()), seen_objects));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Boolean"sv));
|
||||
|
@ -952,6 +962,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
|
|||
return print_intl_duration_format(print_context, static_cast<JS::Intl::DurationFormat&>(object), seen_objects);
|
||||
if (is<JS::Temporal::Duration>(object))
|
||||
return print_temporal_duration(print_context, static_cast<JS::Temporal::Duration&>(object), seen_objects);
|
||||
if (is<JS::Temporal::PlainMonthDay>(object))
|
||||
return print_temporal_plain_month_day(print_context, static_cast<JS::Temporal::PlainMonthDay&>(object), seen_objects);
|
||||
return print_object(print_context, object, seen_objects);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace JS {
|
||||
|
@ -457,7 +456,7 @@ String system_time_zone_identifier()
|
|||
// time zone identifier or an offset time zone identifier.
|
||||
auto system_time_zone_string = Unicode::current_time_zone();
|
||||
|
||||
if (!is_time_zone_offset_string(system_time_zone_string)) {
|
||||
if (!is_offset_time_zone_identifier(system_time_zone_string)) {
|
||||
auto time_zone_identifier = Intl::get_available_named_time_zone_identifier(system_time_zone_string);
|
||||
if (!time_zone_identifier.has_value())
|
||||
return "UTC"_string;
|
||||
|
@ -476,46 +475,55 @@ void clear_system_time_zone_cache()
|
|||
}
|
||||
|
||||
// 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime
|
||||
// 14.5.6 LocalTime ( t ), https://tc39.es/proposal-temporal/#sec-localtime
|
||||
double local_time(double time)
|
||||
{
|
||||
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
|
||||
auto system_time_zone_identifier = JS::system_time_zone_identifier();
|
||||
|
||||
// 2. Let parseResult be ! ParseTimeZoneIdentifier(systemTimeZoneIdentifier).
|
||||
auto parse_result = Temporal::parse_time_zone_identifier(system_time_zone_identifier);
|
||||
|
||||
double offset_nanoseconds { 0 };
|
||||
|
||||
// 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then
|
||||
if (is_time_zone_offset_string(system_time_zone_identifier)) {
|
||||
// a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier).
|
||||
offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier);
|
||||
// 3. If parseResult.[[OffsetMinutes]] is not EMPTY, then
|
||||
if (parse_result.offset_minutes.has_value()) {
|
||||
// a. Let offsetNs be parseResult.[[OffsetMinutes]] × (60 × 10**9).
|
||||
offset_nanoseconds = static_cast<double>(*parse_result.offset_minutes) * 60'000'000'000;
|
||||
}
|
||||
// 3. Else,
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(t) × 10^6)).
|
||||
auto offset = get_named_time_zone_offset_milliseconds(system_time_zone_identifier, time);
|
||||
offset_nanoseconds = static_cast<double>(offset.offset.to_nanoseconds());
|
||||
}
|
||||
|
||||
// 4. Let offsetMs be truncate(offsetNs / 10^6).
|
||||
// 5. Let offsetMs be truncate(offsetNs / 10^6).
|
||||
auto offset_milliseconds = trunc(offset_nanoseconds / 1e6);
|
||||
|
||||
// 5. Return t + 𝔽(offsetMs).
|
||||
// 6. Return t + 𝔽(offsetMs).
|
||||
return time + offset_milliseconds;
|
||||
}
|
||||
|
||||
// 21.4.1.26 UTC ( t ), https://tc39.es/ecma262/#sec-utc-t
|
||||
// 14.5.7 UTC ( t ), https://tc39.es/proposal-temporal/#sec-localtime
|
||||
// FIXME: Update the rest of this AO for Temporal once we have the required Temporal objects.
|
||||
double utc_time(double time)
|
||||
{
|
||||
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
|
||||
auto system_time_zone_identifier = JS::system_time_zone_identifier();
|
||||
|
||||
// 2. Let parseResult be ! ParseTimeZoneIdentifier(systemTimeZoneIdentifier).
|
||||
auto parse_result = Temporal::parse_time_zone_identifier(system_time_zone_identifier);
|
||||
|
||||
double offset_nanoseconds { 0 };
|
||||
|
||||
// 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then
|
||||
if (is_time_zone_offset_string(system_time_zone_identifier)) {
|
||||
// a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier).
|
||||
offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier);
|
||||
// 3. If parseResult.[[OffsetMinutes]] is not EMPTY, then
|
||||
if (parse_result.offset_minutes.has_value()) {
|
||||
// a. Let offsetNs be parseResult.[[OffsetMinutes]] × (60 × 10**9).
|
||||
offset_nanoseconds = static_cast<double>(*parse_result.offset_minutes) * 60'000'000'000;
|
||||
}
|
||||
// 3. Else,
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Let possibleInstants be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(t)), ℝ(MonthFromTime(t)) + 1, ℝ(DateFromTime(t)), ℝ(HourFromTime(t)), ℝ(MinFromTime(t)), ℝ(SecFromTime(t)), ℝ(msFromTime(t)), 0, 0).
|
||||
auto possible_instants = get_named_time_zone_epoch_nanoseconds(system_time_zone_identifier, year_from_time(time), month_from_time(time) + 1, date_from_time(time), hour_from_time(time), min_from_time(time), sec_from_time(time), ms_from_time(time), 0, 0);
|
||||
|
@ -544,10 +552,10 @@ double utc_time(double time)
|
|||
offset_nanoseconds = static_cast<double>(offset.offset.to_nanoseconds());
|
||||
}
|
||||
|
||||
// 4. Let offsetMs be truncate(offsetNs / 10^6).
|
||||
// 5. Let offsetMs be truncate(offsetNs / 10^6).
|
||||
auto offset_milliseconds = trunc(offset_nanoseconds / 1e6);
|
||||
|
||||
// 5. Return t - 𝔽(offsetMs).
|
||||
// 6. Return t - 𝔽(offsetMs).
|
||||
return time - offset_milliseconds;
|
||||
}
|
||||
|
||||
|
@ -636,171 +644,12 @@ double time_clip(double time)
|
|||
return to_integer_or_infinity(time);
|
||||
}
|
||||
|
||||
// 21.4.1.33 Time Zone Offset String Format, https://tc39.es/ecma262/#sec-time-zone-offset-strings
|
||||
Optional<UTCOffset> parse_utc_offset(StringView offset_string)
|
||||
{
|
||||
GenericLexer lexer { offset_string };
|
||||
UTCOffset parse_result;
|
||||
|
||||
// https://tc39.es/ecma262/#prod-ASCIISign
|
||||
auto parse_ascii_sign = [&]() {
|
||||
// ASCIISign ::: one of
|
||||
// + -
|
||||
if (lexer.next_is(is_any_of("+-"sv))) {
|
||||
parse_result.sign = lexer.consume();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
auto parse_two_digits = [&](size_t max_value) -> Optional<u8> {
|
||||
if (auto digits = lexer.peek_string(2); digits.has_value()) {
|
||||
auto number = digits->to_number<u8>(TrimWhitespace::No);
|
||||
|
||||
if (number.has_value() && *number <= max_value) {
|
||||
lexer.ignore(2);
|
||||
return *number;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-Hour
|
||||
auto parse_hour = [&]() {
|
||||
// Hour :::
|
||||
// 0 DecimalDigit
|
||||
// 1 DecimalDigit
|
||||
// 20
|
||||
// 21
|
||||
// 22
|
||||
// 23
|
||||
parse_result.hour = parse_two_digits(23);
|
||||
return parse_result.hour.has_value();
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-TimeSeparator
|
||||
auto parse_time_separator = [&](auto extended) {
|
||||
// TimeSeparator[Extended] :::
|
||||
// [+Extended] :
|
||||
// [~Extended] [empty]
|
||||
if (extended)
|
||||
return lexer.consume_specific(':');
|
||||
return true;
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-MinuteSecond
|
||||
auto parse_minute_second = [&](auto& result) {
|
||||
// MinuteSecond :::
|
||||
// 0 DecimalDigit
|
||||
// 1 DecimalDigit
|
||||
// 2 DecimalDigit
|
||||
// 3 DecimalDigit
|
||||
// 4 DecimalDigit
|
||||
// 5 DecimalDigit
|
||||
result = parse_two_digits(59);
|
||||
return result.has_value();
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-TemporalDecimalSeparator
|
||||
auto parse_temporal_decimal_separator = [&]() {
|
||||
// TemporalDecimalSeparator ::: one of
|
||||
// . ,
|
||||
return lexer.consume_specific('.') || lexer.consume_specific(',');
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-TemporalDecimalFraction
|
||||
auto parse_temporal_decimal_fraction = [&]() {
|
||||
// TemporalDecimalFraction :::
|
||||
// TemporalDecimalSeparator DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||||
// TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
|
||||
auto position = lexer.tell();
|
||||
|
||||
if (!parse_temporal_decimal_separator())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < 9; ++i) {
|
||||
if (!lexer.next_is(is_ascii_digit))
|
||||
break;
|
||||
lexer.ignore();
|
||||
}
|
||||
|
||||
if (auto fraction = lexer.input().substring_view(position, lexer.tell() - position); fraction.length() > 1) {
|
||||
parse_result.fraction = fraction;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-HourSubcomponents
|
||||
auto parse_hour_subcomponents = [&](auto extended) {
|
||||
// HourSubcomponents[Extended] :::
|
||||
// TimeSeparator[?Extended] MinuteSecond
|
||||
// TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond TemporalDecimalFraction[opt]
|
||||
ArmedScopeGuard guard { [&, position = lexer.tell()]() { lexer.retreat(lexer.tell() - position); } };
|
||||
|
||||
if (!parse_time_separator(extended))
|
||||
return false;
|
||||
if (!parse_minute_second(parse_result.minute))
|
||||
return false;
|
||||
|
||||
if (lexer.is_eof()) {
|
||||
guard.disarm();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!parse_time_separator(extended))
|
||||
return false;
|
||||
if (!parse_minute_second(parse_result.second))
|
||||
return false;
|
||||
|
||||
if (lexer.is_eof()) {
|
||||
guard.disarm();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!parse_temporal_decimal_fraction())
|
||||
return false;
|
||||
|
||||
guard.disarm();
|
||||
return true;
|
||||
};
|
||||
|
||||
// https://tc39.es/ecma262/#prod-UTCOffset
|
||||
// UTCOffset :::
|
||||
// ASCIISign Hour
|
||||
// ASCIISign Hour HourSubcomponents[+Extended]
|
||||
// ASCIISign Hour HourSubcomponents[~Extended]
|
||||
if (!parse_ascii_sign())
|
||||
return {};
|
||||
if (!parse_hour())
|
||||
return {};
|
||||
|
||||
if (lexer.is_eof())
|
||||
return parse_result;
|
||||
|
||||
if (!parse_hour_subcomponents(true) && !parse_hour_subcomponents(false))
|
||||
return {};
|
||||
if (lexer.is_eof())
|
||||
return parse_result;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 21.4.1.33.1 IsTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-istimezoneoffsetstring
|
||||
bool is_time_zone_offset_string(StringView offset_string)
|
||||
// 14.5.10 IsOffsetTimeZoneIdentifier ( offsetString ), https://tc39.es/proposal-temporal/#sec-isoffsettimezoneidentifier
|
||||
bool is_offset_time_zone_identifier(StringView offset_string)
|
||||
{
|
||||
// 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset).
|
||||
auto parse_result = parse_utc_offset(offset_string);
|
||||
// 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[~SubMinutePrecision]).
|
||||
auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::No);
|
||||
|
||||
// 2. If parseResult is a List of errors, return false.
|
||||
// 3. Return true.
|
||||
|
@ -808,66 +657,83 @@ bool is_time_zone_offset_string(StringView offset_string)
|
|||
}
|
||||
|
||||
// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
|
||||
double parse_time_zone_offset_string(StringView offset_string)
|
||||
// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset
|
||||
ThrowCompletionOr<double> parse_date_time_utc_offset(VM& vm, StringView offset_string)
|
||||
{
|
||||
// 1. Let parseResult be ParseText(offsetString, UTCOffset).
|
||||
auto parse_result = parse_utc_offset(offset_string);
|
||||
// 1. Let parseResult be ParseText(offsetString, UTCOffset[+SubMinutePrecision]).
|
||||
auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::Yes);
|
||||
|
||||
// 2. Assert: parseResult is not a List of errors.
|
||||
// 2. If parseResult is a List of errors, throw a RangeError exception.
|
||||
if (!parse_result.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneString, offset_string);
|
||||
|
||||
return parse_date_time_utc_offset(*parse_result);
|
||||
}
|
||||
|
||||
// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
|
||||
// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset
|
||||
double parse_date_time_utc_offset(StringView offset_string)
|
||||
{
|
||||
// OPTIMIZATION: Some callers can assume that parsing will succeed.
|
||||
|
||||
// 1. Let parseResult be ParseText(offsetString, UTCOffset[+SubMinutePrecision]).
|
||||
auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::Yes);
|
||||
VERIFY(parse_result.has_value());
|
||||
|
||||
return parse_date_time_utc_offset(*parse_result);
|
||||
}
|
||||
|
||||
// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
|
||||
// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset
|
||||
double parse_date_time_utc_offset(Temporal::TimeZoneOffset const& parse_result)
|
||||
{
|
||||
// OPTIMIZATION: Some callers will have already parsed and validated the time zone identifier.
|
||||
|
||||
// 3. Assert: parseResult contains a ASCIISign Parse Node.
|
||||
VERIFY(parse_result->sign.has_value());
|
||||
VERIFY(parse_result.sign.has_value());
|
||||
|
||||
// 4. Let parsedSign be the source text matched by the ASCIISign Parse Node contained within parseResult.
|
||||
auto parsed_sign = *parse_result->sign;
|
||||
i8 sign { 0 };
|
||||
|
||||
// 5. If parsedSign is the single code point U+002D (HYPHEN-MINUS), then
|
||||
if (parsed_sign == '-') {
|
||||
// a. Let sign be -1.
|
||||
sign = -1;
|
||||
}
|
||||
// a. Let sign be -1.
|
||||
// 6. Else,
|
||||
else {
|
||||
// a. Let sign be 1.
|
||||
sign = 1;
|
||||
}
|
||||
// a. Let sign be 1.
|
||||
auto sign = parse_result.sign == '-' ? -1 : 1;
|
||||
|
||||
// 7. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed to be a sufficiently short string of decimal digits.
|
||||
// 7. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed
|
||||
// to be a sufficiently short string of decimal digits.
|
||||
|
||||
// 8. Assert: parseResult contains an Hour Parse Node.
|
||||
VERIFY(parse_result->hour.has_value());
|
||||
VERIFY(parse_result.hours.has_value());
|
||||
|
||||
// 9. Let parsedHours be the source text matched by the Hour Parse Node contained within parseResult.
|
||||
// 10. Let hours be ℝ(StringToNumber(CodePointsToString(parsedHours))).
|
||||
auto hours = *parse_result->hour;
|
||||
auto hours = parse_result.hours->to_number<u8>().value();
|
||||
|
||||
// 11. If parseResult does not contain a MinuteSecond Parse Node, then
|
||||
// a. Let minutes be 0.
|
||||
// 12. Else,
|
||||
// a. Let parsedMinutes be the source text matched by the first MinuteSecond Parse Node contained within parseResult.
|
||||
// b. Let minutes be ℝ(StringToNumber(CodePointsToString(parsedMinutes))).
|
||||
double minutes = parse_result->minute.value_or(0);
|
||||
double minutes = parse_result.minutes.has_value() ? parse_result.minutes->to_number<u8>().value() : 0;
|
||||
|
||||
// 13. If parseResult does not contain two MinuteSecond Parse Nodes, then
|
||||
// a. Let seconds be 0.
|
||||
// 14. Else,
|
||||
// a. Let parsedSeconds be the source text matched by the second secondSecond Parse Node contained within parseResult.
|
||||
// b. Let seconds be ℝ(StringToNumber(CodePointsToString(parsedSeconds))).
|
||||
double seconds = parse_result->second.value_or(0);
|
||||
double seconds = parse_result.seconds.has_value() ? parse_result.seconds->to_number<u8>().value() : 0;
|
||||
|
||||
double nanoseconds = 0;
|
||||
|
||||
// 15. If parseResult does not contain a TemporalDecimalFraction Parse Node, then
|
||||
if (!parse_result->fraction.has_value()) {
|
||||
if (!parse_result.fraction.has_value()) {
|
||||
// a. Let nanoseconds be 0.
|
||||
nanoseconds = 0;
|
||||
}
|
||||
// 16. Else,
|
||||
else {
|
||||
// a. Let parsedFraction be the source text matched by the TemporalDecimalFraction Parse Node contained within parseResult.
|
||||
auto parsed_fraction = *parse_result->fraction;
|
||||
auto parsed_fraction = *parse_result.fraction;
|
||||
|
||||
// b. Let fraction be the string-concatenation of CodePointsToString(parsedFraction) and "000000000".
|
||||
auto fraction = ByteString::formatted("{}000000000", parsed_fraction);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -58,14 +58,6 @@ constexpr inline double ms_per_day = 86'400'000;
|
|||
constexpr inline double ns_per_day = 86'400'000'000'000;
|
||||
extern Crypto::SignedBigInteger const ns_per_day_bigint;
|
||||
|
||||
struct UTCOffset {
|
||||
Optional<char> sign;
|
||||
Optional<u8> hour;
|
||||
Optional<u8> minute;
|
||||
Optional<u8> second;
|
||||
Optional<StringView> fraction;
|
||||
};
|
||||
|
||||
double day(double);
|
||||
double time_within_day(double);
|
||||
u16 days_in_year(i32);
|
||||
|
@ -93,8 +85,9 @@ double make_time(double hour, double min, double sec, double ms);
|
|||
double make_day(double year, double month, double date);
|
||||
double make_date(double day, double time);
|
||||
double time_clip(double time);
|
||||
Optional<UTCOffset> parse_utc_offset(StringView);
|
||||
bool is_time_zone_offset_string(StringView offset_string);
|
||||
double parse_time_zone_offset_string(StringView offset_string);
|
||||
bool is_offset_time_zone_identifier(StringView offset_string);
|
||||
ThrowCompletionOr<double> parse_date_time_utc_offset(VM&, StringView offset_string);
|
||||
double parse_date_time_utc_offset(StringView offset_string);
|
||||
double parse_date_time_utc_offset(Temporal::TimeZoneOffset const&);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, Petróczi Zoltán <petroczizoltan@tutanota.com>
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -21,6 +21,8 @@
|
|||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Intl/DateTimeFormat.h>
|
||||
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <LibUnicode/DisplayNames.h>
|
||||
|
@ -1066,19 +1068,14 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_string)
|
|||
}
|
||||
|
||||
// 21.4.4.41.1 TimeString ( tv ), https://tc39.es/ecma262/#sec-timestring
|
||||
// 14.5.8 TimeString ( tv ), https://tc39.es/proposal-temporal/#sec-timestring
|
||||
ByteString time_string(double time)
|
||||
{
|
||||
// 1. Let hour be ToZeroPaddedDecimalString(ℝ(HourFromTime(tv)), 2).
|
||||
auto hour = hour_from_time(time);
|
||||
// 1. Let timeString be FormatTimeString(ℝ(HourFromTime(tv)), ℝ(MinFromTime(tv)), ℝ(SecFromTime(tv)), 0, 0).
|
||||
auto time_string = Temporal::format_time_string(hour_from_time(time), min_from_time(time), sec_from_time(time), 0, 0);
|
||||
|
||||
// 2. Let minute be ToZeroPaddedDecimalString(ℝ(MinFromTime(tv)), 2).
|
||||
auto minute = min_from_time(time);
|
||||
|
||||
// 3. Let second be ToZeroPaddedDecimalString(ℝ(SecFromTime(tv)), 2).
|
||||
auto second = sec_from_time(time);
|
||||
|
||||
// 4. Return the string-concatenation of hour, ":", minute, ":", second, the code unit 0x0020 (SPACE), and "GMT".
|
||||
return ByteString::formatted("{:02}:{:02}:{:02} GMT", hour, minute, second);
|
||||
// 4. Return the string-concatenation of timeString, the code unit 0x0020 (SPACE), and "GMT".
|
||||
return ByteString::formatted("{} GMT", time_string);
|
||||
}
|
||||
|
||||
// 21.4.4.41.2 DateString ( tv ), https://tc39.es/ecma262/#sec-datestring
|
||||
|
@ -1105,62 +1102,40 @@ ByteString date_string(double time)
|
|||
}
|
||||
|
||||
// 21.4.4.41.3 TimeZoneString ( tv ), https://tc39.es/ecma262/#sec-timezoneestring
|
||||
// 14.5.9 TimeZoneString ( tv ), https://tc39.es/proposal-temporal/#sec-timezoneestring
|
||||
ByteString time_zone_string(double time)
|
||||
{
|
||||
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
|
||||
auto system_time_zone_identifier = JS::system_time_zone_identifier();
|
||||
|
||||
double offset_nanoseconds { 0 };
|
||||
// 2. Let offsetMinutes be ! ParseTimeZoneIdentifier(systemTimeZoneIdentifier).[[OffsetMinutes]].
|
||||
auto offset_minutes = Temporal::parse_time_zone_identifier(system_time_zone_identifier).offset_minutes;
|
||||
auto in_dst = Unicode::TimeZoneOffset::InDST::No;
|
||||
|
||||
// 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then
|
||||
if (is_time_zone_offset_string(system_time_zone_identifier)) {
|
||||
// a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier).
|
||||
offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier);
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// 2. If offsetMinutes is EMPTY, then
|
||||
if (!offset_minutes.has_value()) {
|
||||
// a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(tv) × 10^6)).
|
||||
auto offset = get_named_time_zone_offset_milliseconds(system_time_zone_identifier, time);
|
||||
|
||||
offset_nanoseconds = static_cast<double>(offset.offset.to_nanoseconds());
|
||||
in_dst = offset.in_dst;
|
||||
|
||||
// b. Set offsetMinutes to truncate(offsetNs / (60 × 10**9)).
|
||||
offset_minutes = trunc(static_cast<double>(offset.offset.to_nanoseconds()) / 60'000'000'000.0);
|
||||
}
|
||||
|
||||
// 4. Let offset be 𝔽(truncate(offsetNs / 106)).
|
||||
auto offset = trunc(offset_nanoseconds / 1e6);
|
||||
// 3. Let offsetString be FormatOffsetTimeZoneIdentifier(offsetMinutes, UNSEPARATED).
|
||||
auto offset_string = Temporal::format_offset_time_zone_identifier(*offset_minutes, Temporal::TimeStyle::Unseparated);
|
||||
|
||||
StringView offset_sign;
|
||||
|
||||
// 5. If offset is +0𝔽 or offset > +0𝔽, then
|
||||
if (offset >= 0) {
|
||||
// a. Let offsetSign be "+".
|
||||
offset_sign = "+"sv;
|
||||
// b. Let absOffset be offset.
|
||||
}
|
||||
// 6. Else,
|
||||
else {
|
||||
// a. Let offsetSign be "-".
|
||||
offset_sign = "-"sv;
|
||||
// b. Let absOffset be -offset.
|
||||
offset *= -1;
|
||||
}
|
||||
|
||||
// 7. Let offsetMin be ToZeroPaddedDecimalString(ℝ(MinFromTime(absOffset)), 2).
|
||||
auto offset_min = min_from_time(offset);
|
||||
|
||||
// 8. Let offsetHour be ToZeroPaddedDecimalString(ℝ(HourFromTime(absOffset)), 2).
|
||||
auto offset_hour = hour_from_time(offset);
|
||||
|
||||
// 9. Let tzName be an implementation-defined string that is either the empty String or the string-concatenation of the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), an implementation-defined timezone name, and the code unit 0x0029 (RIGHT PARENTHESIS).
|
||||
// 5. Let tzName be an implementation-defined string that is either the empty String or the string-concatenation of
|
||||
// the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), an implementation-defined timezone name,
|
||||
// and the code unit 0x0029 (RIGHT PARENTHESIS).
|
||||
auto tz_name = Unicode::current_time_zone();
|
||||
|
||||
// Most implementations seem to prefer the long-form display name of the time zone. Not super important, but we may as well match that behavior.
|
||||
if (auto name = Unicode::time_zone_display_name(Unicode::default_locale(), tz_name, in_dst, time); name.has_value())
|
||||
tz_name = name.release_value();
|
||||
|
||||
// 10. Return the string-concatenation of offsetSign, offsetHour, offsetMin, and tzName.
|
||||
return ByteString::formatted("{}{:02}{:02} ({})", offset_sign, offset_hour, offset_min, tz_name);
|
||||
// 10. Return the string-concatenation of offsetString and tzName.
|
||||
return ByteString::formatted("{} ({})", offset_string, tz_name);
|
||||
}
|
||||
|
||||
// 21.4.4.41.4 ToDateString ( tv ), https://tc39.es/ecma262/#sec-todatestring
|
||||
|
|
|
@ -249,6 +249,7 @@
|
|||
M(TemporalInvalidCalendarFunctionResult, "Invalid calendar, {}() function returned {}") \
|
||||
M(TemporalInvalidCalendarIdentifier, "Invalid calendar identifier '{}'") \
|
||||
M(TemporalInvalidCalendarString, "Invalid calendar string '{}'") \
|
||||
M(TemporalInvalidCriticalAnnotation, "Invalid critical annotation: '{}={}'") \
|
||||
M(TemporalInvalidDateTimeString, "Invalid date time string '{}'") \
|
||||
M(TemporalInvalidDateTimeStringUTCDesignator, "Invalid date time string '{}': must not contain a UTC designator") \
|
||||
M(TemporalInvalidDuration, "Invalid duration") \
|
||||
|
@ -295,6 +296,7 @@
|
|||
"nanoseconds with the opposite sign") \
|
||||
M(TemporalNanosecondsConvertedToRemainderOfNanosecondsLongerThanDayLength, "Time zone or calendar ended up with a remainder of " \
|
||||
"nanoseconds longer than the day length") \
|
||||
M(TemporalObjectMustBePartialTemporalObject, "Object must be a partial Temporal object") \
|
||||
M(TemporalObjectMustHaveOneOf, "Object must have at least one of the following properties: {}") \
|
||||
M(TemporalObjectMustNotHave, "Object must not have a defined {} property") \
|
||||
M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@
|
|||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Intl/DateTimeFormat.h>
|
||||
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibUnicode/DateTimeFormat.h>
|
||||
#include <LibUnicode/Locale.h>
|
||||
|
||||
|
@ -81,6 +82,8 @@ JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatConstructor::supported_locales_of)
|
|||
}
|
||||
|
||||
// 11.1.2 CreateDateTimeFormat ( newTarget, locales, options, required, defaults ), https://tc39.es/ecma402/#sec-createdatetimeformat
|
||||
// 15.7.1 CreateDateTimeFormat ( newTarget, locales, options, required, defaults [ , toLocaleStringTimeZone ] ), https://tc39.es/proposal-temporal/#sec-createdatetimeformat
|
||||
// FIXME: Update the rest of this AO for Temporal once we have the required Temporal objects.
|
||||
ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, FunctionObject& new_target, Value locales_value, Value options_value, OptionRequired required, OptionDefaults defaults)
|
||||
{
|
||||
// 1. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%Intl.DateTimeFormat.prototype%", « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[HourCycle]], [[DateStyle]], [[TimeStyle]], [[DateTimeFormat]], [[BoundFormat]] »).
|
||||
|
@ -200,29 +203,25 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
|
|||
}
|
||||
|
||||
// 29. If IsTimeZoneOffsetString(timeZone) is true, then
|
||||
bool is_time_zone_offset_string = JS::is_time_zone_offset_string(time_zone);
|
||||
bool is_time_zone_offset_string = JS::is_offset_time_zone_identifier(time_zone);
|
||||
|
||||
if (is_time_zone_offset_string) {
|
||||
// a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset).
|
||||
auto parse_result = parse_utc_offset(time_zone);
|
||||
// a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset[~SubMinutePrecision]).
|
||||
auto parse_result = Temporal::parse_utc_offset(time_zone, Temporal::SubMinutePrecision::No);
|
||||
|
||||
// b. Assert: parseResult is a Parse Node.
|
||||
VERIFY(parse_result.has_value());
|
||||
|
||||
// c. If parseResult contains more than one MinuteSecond Parse Node, throw a RangeError exception.
|
||||
if (parse_result->second.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, time_zone, vm.names.timeZone);
|
||||
// c. Let offsetNanoseconds be ? ParseDateTimeUTCOffset(timeZone).
|
||||
auto offset_nanoseconds = TRY(parse_date_time_utc_offset(vm, time_zone));
|
||||
|
||||
// d. Let offsetNanoseconds be ParseTimeZoneOffsetString(timeZone).
|
||||
auto offset_nanoseconds = parse_time_zone_offset_string(time_zone);
|
||||
|
||||
// e. Let offsetMinutes be offsetNanoseconds / (6 × 10**10).
|
||||
// d. Let offsetMinutes be offsetNanoseconds / (6 × 10**10).
|
||||
auto offset_minutes = offset_nanoseconds / 60'000'000'000;
|
||||
|
||||
// f. Assert: offsetMinutes is an integer.
|
||||
// e. Assert: offsetMinutes is an integer.
|
||||
VERIFY(trunc(offset_minutes) == offset_minutes);
|
||||
|
||||
// g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
||||
// f. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
||||
time_zone = format_offset_time_zone_identifier(offset_minutes);
|
||||
}
|
||||
// 30. Else,
|
||||
|
|
|
@ -101,6 +101,8 @@
|
|||
#include <LibJS/Runtime/SymbolPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/Temporal.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/TypedArrayConstructor.h>
|
||||
|
|
|
@ -9,11 +9,16 @@
|
|||
|
||||
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -43,6 +48,65 @@ StringView temporal_unit_to_string(Unit unit)
|
|||
return temporal_units[to_underlying(unit)].singular_property_name;
|
||||
}
|
||||
|
||||
// 13.1 ISODateToEpochDays ( year, month, date ), https://tc39.es/proposal-temporal/#sec-isodatetoepochdays
|
||||
double iso_date_to_epoch_days(double year, double month, double date)
|
||||
{
|
||||
// 1. Let resolvedYear be year + floor(month / 12).
|
||||
// 2. Let resolvedMonth be month modulo 12.
|
||||
// 3. Find a time t such that EpochTimeToEpochYear(t) = resolvedYear, EpochTimeToMonthInYear(t) = resolvedMonth, and EpochTimeToDate(t) = 1.
|
||||
// 4. Return EpochTimeToDayNumber(t) + date - 1.
|
||||
|
||||
// EDITOR'S NOTE: This operation corresponds to ECMA-262 operation MakeDay(year, month, date). It calculates the
|
||||
// result in mathematical values instead of Number values. These two operations would be unified when
|
||||
// https://github.com/tc39/ecma262/issues/1087 is fixed.
|
||||
|
||||
// Since we don't have a real MV type to work with, let's defer to MakeDay.
|
||||
return JS::make_day(year, month, date);
|
||||
}
|
||||
|
||||
// 13.2 EpochDaysToEpochMs ( day, time ), https://tc39.es/proposal-temporal/#sec-epochdaystoepochms
|
||||
double epoch_days_to_epoch_ms(double day, double time)
|
||||
{
|
||||
// 1. Return day × ℝ(msPerDay) + time.
|
||||
return day * JS::ms_per_day + time;
|
||||
}
|
||||
|
||||
// 13.6 GetTemporalOverflowOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloverflowoption
|
||||
ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM& vm, Object const& options)
|
||||
{
|
||||
// 1. Let stringValue be ? GetOption(options, "overflow", STRING, « "constrain", "reject" », "constrain").
|
||||
auto string_value = TRY(get_option(vm, options, vm.names.overflow, OptionType::String, { "constrain"sv, "reject"sv }, "constrain"sv));
|
||||
|
||||
// 2. If stringValue is "constrain", return CONSTRAIN.
|
||||
if (string_value.as_string().utf8_string() == "constrain"sv)
|
||||
return Overflow::Constrain;
|
||||
|
||||
// 3. Return REJECT.
|
||||
return Overflow::Reject;
|
||||
}
|
||||
|
||||
// 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption
|
||||
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, Object const& options)
|
||||
{
|
||||
// 1. Let stringValue be ? GetOption(options, "calendarName", STRING, « "auto", "always", "never", "critical" », "auto").
|
||||
auto string_value = TRY(get_option(vm, options, vm.names.calendarName, OptionType::String, { "auto"sv, "always"sv, "never"sv, "critical"sv }, "auto"sv));
|
||||
|
||||
// 2. If stringValue is "always", return ALWAYS.
|
||||
if (string_value.as_string().utf8_string_view() == "always"sv)
|
||||
return ShowCalendar::Always;
|
||||
|
||||
// 3. If stringValue is "never", return NEVER.
|
||||
if (string_value.as_string().utf8_string_view() == "never"sv)
|
||||
return ShowCalendar::Never;
|
||||
|
||||
// 4. If stringValue is "critical", return CRITICAL.
|
||||
if (string_value.as_string().utf8_string_view() == "critical"sv)
|
||||
return ShowCalendar::Critical;
|
||||
|
||||
// 5. Return AUTO.
|
||||
return ShowCalendar::Auto;
|
||||
}
|
||||
|
||||
// 13.14 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
|
||||
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM& vm, u64 increment, u64 dividend, bool inclusive)
|
||||
{
|
||||
|
@ -388,6 +452,40 @@ Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit unit)
|
|||
}
|
||||
}
|
||||
|
||||
// 13.23 IsPartialTemporalObject ( value ), https://tc39.es/proposal-temporal/#sec-temporal-ispartialtemporalobject
|
||||
ThrowCompletionOr<bool> is_partial_temporal_object(VM& vm, Value value)
|
||||
{
|
||||
// 1. If value is not an Object, return false.
|
||||
if (!value.is_object())
|
||||
return false;
|
||||
|
||||
auto const& object = value.as_object();
|
||||
|
||||
// 2. If value has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
|
||||
// [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal
|
||||
// slot, return false.
|
||||
// FIXME: Add the other types as we define them.
|
||||
if (is<PlainMonthDay>(object))
|
||||
return false;
|
||||
|
||||
// 3. Let calendarProperty be ? Get(value, "calendar").
|
||||
auto calendar_property = TRY(object.get(vm.names.calendar));
|
||||
|
||||
// 4. If calendarProperty is not undefined, return false.
|
||||
if (!calendar_property.is_undefined())
|
||||
return false;
|
||||
|
||||
// 5. Let timeZoneProperty be ? Get(value, "timeZone").
|
||||
auto time_zone_property = TRY(object.get(vm.names.timeZone));
|
||||
|
||||
// 6. If timeZoneProperty is not undefined, return false.
|
||||
if (!time_zone_property.is_undefined())
|
||||
return false;
|
||||
|
||||
// 7. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 13.24 FormatFractionalSeconds ( subSecondNanoseconds, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatfractionalseconds
|
||||
String format_fractional_seconds(u64 sub_second_nanoseconds, Precision precision)
|
||||
{
|
||||
|
@ -422,6 +520,27 @@ String format_fractional_seconds(u64 sub_second_nanoseconds, Precision precision
|
|||
return MUST(String::formatted(".{}", fraction_string));
|
||||
}
|
||||
|
||||
// 13.25 FormatTimeString ( hour, minute, second, subSecondNanoseconds, precision [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formattimestring
|
||||
String format_time_string(u8 hour, u8 minute, u8 second, u16 sub_second_nanoseconds, SecondsStringPrecision::Precision precision, Optional<TimeStyle> style)
|
||||
{
|
||||
// 1. If style is present and style is UNSEPARATED, let separator be the empty String; otherwise, let separator be ":".
|
||||
auto separator = style == TimeStyle::Unseparated ? ""sv : ":"sv;
|
||||
|
||||
// 2. Let hh be ToZeroPaddedDecimalString(hour, 2).
|
||||
// 3. Let mm be ToZeroPaddedDecimalString(minute, 2).
|
||||
|
||||
// 4. If precision is minute, return the string-concatenation of hh, separator, and mm.
|
||||
if (precision.has<SecondsStringPrecision::Minute>())
|
||||
return MUST(String::formatted("{:02}{}{:02}", hour, separator, minute));
|
||||
|
||||
// 5. Let ss be ToZeroPaddedDecimalString(second, 2).
|
||||
// 6. Let subSecondsPart be FormatFractionalSeconds(subSecondNanoseconds, precision).
|
||||
auto sub_seconds_part = format_fractional_seconds(sub_second_nanoseconds, precision.downcast<Auto, u8>());
|
||||
|
||||
// 7. Return the string-concatenation of hh, separator, mm, separator, ss, and subSecondsPart.
|
||||
return MUST(String::formatted("{:02}{}{:02}{}{:02}{}", hour, separator, minute, separator, second, sub_seconds_part));
|
||||
}
|
||||
|
||||
// 13.26 GetUnsignedRoundingMode ( roundingMode, sign ), https://tc39.es/proposal-temporal/#sec-getunsignedroundingmode
|
||||
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode rounding_mode, Sign sign)
|
||||
{
|
||||
|
@ -664,6 +783,260 @@ Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger cons
|
|||
return rounded.multiplied_by(increment);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 1. Let parseResult be EMPTY.
|
||||
Optional<ParseResult> parse_result;
|
||||
|
||||
// 2. Let calendar be EMPTY.
|
||||
Optional<String> calendar;
|
||||
|
||||
// 3. Let yearAbsent be false.
|
||||
auto year_absent = false;
|
||||
|
||||
// 4. For each nonterminal goal of allowedFormats, do
|
||||
for (auto goal : allowed_formats) {
|
||||
// a. If parseResult is not a Parse Node, then
|
||||
if (parse_result.has_value())
|
||||
break;
|
||||
|
||||
// i. Set parseResult to ParseText(StringToCodePoints(isoString), goal).
|
||||
parse_result = parse_iso8601(goal, iso_string);
|
||||
|
||||
// ii. If parseResult is a Parse Node, then
|
||||
if (parse_result.has_value()) {
|
||||
// 1. Let calendarWasCritical be false.
|
||||
auto calendar_was_critical = false;
|
||||
|
||||
// 2. For each Annotation Parse Node annotation contained within parseResult, do
|
||||
for (auto const& annotation : parse_result->annotations) {
|
||||
// a. Let key be the source text matched by the AnnotationKey Parse Node contained within annotation.
|
||||
auto const& key = annotation.key;
|
||||
|
||||
// b. Let value be the source text matched by the AnnotationValue Parse Node contained within annotation.
|
||||
auto const& value = annotation.value;
|
||||
|
||||
// c. If CodePointsToString(key) is "u-ca", then
|
||||
if (key == "u-ca"sv) {
|
||||
// i. If calendar is EMPTY, then
|
||||
if (!calendar.has_value()) {
|
||||
// i. Set calendar to CodePointsToString(value).
|
||||
calendar = String::from_utf8_without_validation(value.bytes());
|
||||
|
||||
// ii. If annotation contains an AnnotationCriticalFlag Parse Node, set calendarWasCritical to true.
|
||||
if (annotation.critical)
|
||||
calendar_was_critical = true;
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// i. If annotation contains an AnnotationCriticalFlag Parse Node, or calendarWasCritical is true,
|
||||
// throw a RangeError exception.
|
||||
if (annotation.critical || calendar_was_critical)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCriticalAnnotation, key, value);
|
||||
}
|
||||
}
|
||||
// d. Else,
|
||||
else {
|
||||
// i. If annotation contains an AnnotationCriticalFlag Parse Node, throw a RangeError exception.
|
||||
if (annotation.critical)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCriticalAnnotation, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If goal is TemporalMonthDayString or TemporalYearMonthString, calendar is not EMPTY, and the
|
||||
// ASCII-lowercase of calendar is not "iso8601", throw a RangeError exception.
|
||||
if (goal == Production::TemporalMonthDayString || goal == Production::TemporalYearMonthString) {
|
||||
if (calendar.has_value() && !calendar->equals_ignoring_ascii_case("iso8601"sv))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, *calendar);
|
||||
}
|
||||
|
||||
// 4. If goal is TemporalMonthDayString and parseResult does not contain a DateYear Parse Node, then
|
||||
if (goal == Production::TemporalMonthDayString && !parse_result->date_year.has_value()) {
|
||||
// a. Assert: goal is the last element of allowedFormats.
|
||||
VERIFY(goal == allowed_formats.last());
|
||||
|
||||
// b. Set yearAbsent to true.
|
||||
year_absent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If parseResult is not a Parse Node, throw a RangeError exception.
|
||||
if (!parse_result.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODateTime);
|
||||
|
||||
// 6. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed
|
||||
// to be a sufficiently short string of decimal digits.
|
||||
|
||||
// 7. Let each of year, month, day, hour, minute, second, and fSeconds be the source text matched by the respective
|
||||
// DateYear, DateMonth, DateDay, the first Hour, the first MinuteSecond, TimeSecond, and the first
|
||||
// TemporalDecimalFraction Parse Node contained within parseResult, or an empty sequence of code points if not present.
|
||||
auto year = parse_result->date_year.value_or({});
|
||||
auto month = parse_result->date_month.value_or({});
|
||||
auto day = parse_result->date_day.value_or({});
|
||||
auto hour = parse_result->time_hour.value_or({});
|
||||
auto minute = parse_result->time_minute.value_or({});
|
||||
auto second = parse_result->time_second.value_or({});
|
||||
auto fractional_seconds = parse_result->time_fraction.value_or({});
|
||||
|
||||
// 8. Let yearMV be ℝ(StringToNumber(CodePointsToString(year))).
|
||||
auto year_value = string_to_number(year);
|
||||
|
||||
// 9. If month is empty, then
|
||||
// a. Let monthMV be 1.
|
||||
// 10. Else,
|
||||
// a. Let monthMV be ℝ(StringToNumber(CodePointsToString(month))).
|
||||
auto month_value = month.is_empty() ? 1 : string_to_number(month);
|
||||
|
||||
// 11. If day is empty, then
|
||||
// a. Let dayMV be 1.
|
||||
// 12. Else,
|
||||
// a. Let dayMV be ℝ(StringToNumber(CodePointsToString(day))).
|
||||
auto day_value = day.is_empty() ? 1 : string_to_number(day);
|
||||
|
||||
// 13. If hour is empty, then
|
||||
// a. Let hourMV be 0.
|
||||
// 14. Else,
|
||||
// a. Let hourMV be ℝ(StringToNumber(CodePointsToString(hour))).
|
||||
auto hour_value = hour.is_empty() ? 0 : string_to_number(hour);
|
||||
|
||||
// 15. If minute is empty, then
|
||||
// a. Let minuteMV be 0.
|
||||
// 16. Else,
|
||||
// a. Let minuteMV be ℝ(StringToNumber(CodePointsToString(minute))).
|
||||
auto minute_value = minute.is_empty() ? 0 : string_to_number(minute);
|
||||
|
||||
// 17. If second is empty, then
|
||||
// a. Let secondMV be 0.
|
||||
// 18. Else,
|
||||
// a. Let secondMV be ℝ(StringToNumber(CodePointsToString(second))).
|
||||
// b. If secondMV = 60, then
|
||||
// i. Set secondMV to 59.
|
||||
auto second_value = second.is_empty() ? 0 : min(string_to_number(second), 59.0);
|
||||
|
||||
double millisecond_value = 0;
|
||||
double microsecond_value = 0;
|
||||
double nanosecond_value = 0;
|
||||
|
||||
// 19. If fSeconds is not empty, then
|
||||
if (!fractional_seconds.is_empty()) {
|
||||
// a. Let fSecondsDigits be the substring of CodePointsToString(fSeconds) from 1.
|
||||
auto fractional_seconds_digits = fractional_seconds.substring_view(1);
|
||||
|
||||
// b. Let fSecondsDigitsExtended be the string-concatenation of fSecondsDigits and "000000000".
|
||||
auto fractional_seconds_extended = MUST(String::formatted("{}000000000", fractional_seconds_digits));
|
||||
|
||||
// c. Let millisecond be the substring of fSecondsDigitsExtended from 0 to 3.
|
||||
auto millisecond = fractional_seconds_extended.bytes_as_string_view().substring_view(0, 3);
|
||||
|
||||
// d. Let microsecond be the substring of fSecondsDigitsExtended from 3 to 6.
|
||||
auto microsecond = fractional_seconds_extended.bytes_as_string_view().substring_view(3, 3);
|
||||
|
||||
// e. Let nanosecond be the substring of fSecondsDigitsExtended from 6 to 9.
|
||||
auto nanosecond = fractional_seconds_extended.bytes_as_string_view().substring_view(6, 3);
|
||||
|
||||
// f. Let millisecondMV be ℝ(StringToNumber(millisecond)).
|
||||
millisecond_value = string_to_number(millisecond);
|
||||
|
||||
// g. Let microsecondMV be ℝ(StringToNumber(microsecond)).
|
||||
microsecond_value = string_to_number(microsecond);
|
||||
|
||||
// h. Let nanosecondMV be ℝ(StringToNumber(nanosecond)).
|
||||
nanosecond_value = string_to_number(nanosecond);
|
||||
}
|
||||
// 20. Else,
|
||||
else {
|
||||
// a. Let millisecondMV be 0.
|
||||
// b. Let microsecondMV be 0.
|
||||
// c. Let nanosecondMV be 0.
|
||||
}
|
||||
|
||||
// 21. Assert: IsValidISODate(yearMV, monthMV, dayMV) is true.
|
||||
VERIFY(is_valid_iso_date(year_value, month_value, day_value));
|
||||
|
||||
Variant<ParsedISODateTime::StartOfDay, Time> time { ParsedISODateTime::StartOfDay {} };
|
||||
|
||||
// 22. If hour is empty, then
|
||||
if (hour.is_empty()) {
|
||||
// a. Let time be START-OF-DAY.
|
||||
}
|
||||
// 23. Else,
|
||||
else {
|
||||
// a. Let time be CreateTimeRecord(hourMV, minuteMV, secondMV, millisecondMV, microsecondMV, nanosecondMV).
|
||||
time = create_time_record(hour_value, minute_value, second_value, millisecond_value, microsecond_value, nanosecond_value);
|
||||
}
|
||||
|
||||
// 24. Let timeZoneResult be ISO String Time Zone Parse Record { [[Z]]: false, [[OffsetString]]: EMPTY, [[TimeZoneAnnotation]]: EMPTY }.
|
||||
ParsedISOTimeZone time_zone_result;
|
||||
|
||||
// 25. If parseResult contains a TimeZoneIdentifier Parse Node, then
|
||||
if (parse_result->time_zone_identifier.has_value()) {
|
||||
// a. Let identifier be the source text matched by the TimeZoneIdentifier Parse Node contained within parseResult.
|
||||
// b. Set timeZoneResult.[[TimeZoneAnnotation]] to CodePointsToString(identifier).
|
||||
time_zone_result.time_zone_annotation = String::from_utf8_without_validation(parse_result->time_zone_identifier->bytes());
|
||||
}
|
||||
|
||||
// 26. If parseResult contains a UTCDesignator Parse Node, then
|
||||
if (parse_result->utc_designator.has_value()) {
|
||||
// a. Set timeZoneResult.[[Z]] to true.
|
||||
time_zone_result.z_designator = true;
|
||||
}
|
||||
// 27. Else if parseResult contains a UTCOffset[+SubMinutePrecision] Parse Node, then
|
||||
else if (parse_result->date_time_offset.has_value()) {
|
||||
// a. Let offset be the source text matched by the UTCOffset[+SubMinutePrecision] Parse Node contained within parseResult.
|
||||
// b. Set timeZoneResult.[[OffsetString]] to CodePointsToString(offset).
|
||||
time_zone_result.offset_string = String::from_utf8_without_validation(parse_result->date_time_offset->source_text.bytes());
|
||||
}
|
||||
|
||||
// 28. If yearAbsent is true, let yearReturn be EMPTY; else let yearReturn be yearMV.
|
||||
Optional<i32> year_return;
|
||||
if (!year_absent)
|
||||
year_return = static_cast<i32>(year_value);
|
||||
|
||||
// 29. Return ISO Date-Time Parse Record { [[Year]]: yearReturn, [[Month]]: monthMV, [[Day]]: dayMV, [[Time]]: time, [[TimeZone]]: timeZoneResult, [[Calendar]]: calendar }.
|
||||
return ParsedISODateTime { .year = year_return, .month = static_cast<u8>(month_value), .day = static_cast<u8>(day_value), .time = move(time), .time_zone = move(time_zone_result), .calendar = move(calendar) };
|
||||
}
|
||||
|
||||
// 13.34 ParseTemporalCalendarString ( string ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
|
||||
ThrowCompletionOr<String> parse_temporal_calendar_string(VM& vm, String const& string)
|
||||
{
|
||||
// 1. Let parseResult be Completion(ParseISODateTime(string, « TemporalDateTimeString[+Zoned], TemporalDateTimeString[~Zoned],
|
||||
// TemporalInstantString, TemporalTimeString, TemporalMonthDayString, TemporalYearMonthString »)).
|
||||
static constexpr auto productions = to_array<Production>({
|
||||
Production::TemporalZonedDateTimeString,
|
||||
Production::TemporalDateTimeString,
|
||||
Production::TemporalInstantString,
|
||||
Production::TemporalTimeString,
|
||||
Production::TemporalMonthDayString,
|
||||
Production::TemporalYearMonthString,
|
||||
});
|
||||
|
||||
auto parse_result = parse_iso_date_time(vm, string, productions);
|
||||
|
||||
// 2. If parseResult is a normal completion, then
|
||||
if (!parse_result.is_error()) {
|
||||
// a. Let calendar be parseResult.[[Value]].[[Calendar]].
|
||||
auto calendar = parse_result.value().calendar;
|
||||
|
||||
// b. If calendar is empty, return "iso8601".
|
||||
// c. Else, return calendar.
|
||||
return calendar.value_or("iso8601"_string);
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. Set parseResult to ParseText(StringToCodePoints(string), AnnotationValue).
|
||||
auto annotation_parse_result = parse_iso8601(Production::AnnotationValue, string);
|
||||
|
||||
// b. If parseResult is a List of errors, throw a RangeError exception.
|
||||
if (!annotation_parse_result.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarString, string);
|
||||
|
||||
// c. Else, return string.
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
// 13.35 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
|
||||
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM& vm, StringView iso_string)
|
||||
{
|
||||
|
@ -884,6 +1257,149 @@ ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM& vm, Stri
|
|||
return TRY(create_temporal_duration(vm, years_value, months_value, weeks_value, days_value, hours_value, factored_minutes_value, factored_seconds_value, factored_milliseconds_value, factored_microseconds_value, factored_nanoseconds_value));
|
||||
}
|
||||
|
||||
// 13.36 ParseTemporalTimeZoneString ( timeZoneString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring
|
||||
ThrowCompletionOr<TimeZone> parse_temporal_time_zone_string(VM& vm, StringView time_zone_string)
|
||||
{
|
||||
// 1. Let parseResult be ParseText(StringToCodePoints(timeZoneString), TimeZoneIdentifier).
|
||||
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, time_zone_string);
|
||||
|
||||
// 2. If parseResult is a Parse Node, then
|
||||
if (parse_result.has_value()) {
|
||||
// a. Return ! ParseTimeZoneIdentifier(timeZoneString).
|
||||
return parse_time_zone_identifier(parse_result.release_value());
|
||||
}
|
||||
|
||||
// 3. Let result be ? ParseISODateTime(timeZoneString, « TemporalDateTimeString[+Zoned], TemporalDateTimeString[~Zoned],
|
||||
// TemporalInstantString, TemporalTimeString, TemporalMonthDayString, TemporalYearMonthString »).
|
||||
static constexpr auto productions = to_array<Production>({
|
||||
Production::TemporalZonedDateTimeString,
|
||||
Production::TemporalDateTimeString,
|
||||
Production::TemporalInstantString,
|
||||
Production::TemporalTimeString,
|
||||
Production::TemporalMonthDayString,
|
||||
Production::TemporalYearMonthString,
|
||||
});
|
||||
|
||||
auto result = TRY(parse_iso_date_time(vm, time_zone_string, productions));
|
||||
|
||||
// 4. Let timeZoneResult be result.[[TimeZone]].
|
||||
auto time_zone_result = move(result.time_zone);
|
||||
|
||||
// 5. If timeZoneResult.[[TimeZoneAnnotation]] is not empty, then
|
||||
if (time_zone_result.time_zone_annotation.has_value()) {
|
||||
// a. Return ! ParseTimeZoneIdentifier(timeZoneResult.[[TimeZoneAnnotation]]).
|
||||
return MUST(parse_time_zone_identifier(vm, *time_zone_result.time_zone_annotation));
|
||||
}
|
||||
|
||||
// 6. If timeZoneResult.[[Z]] is true, then
|
||||
if (time_zone_result.z_designator) {
|
||||
// a. Return ! ParseTimeZoneIdentifier("UTC").
|
||||
return MUST(parse_time_zone_identifier(vm, "UTC"sv));
|
||||
}
|
||||
|
||||
// 7. If timeZoneResult.[[OffsetString]] is not empty, then
|
||||
if (time_zone_result.offset_string.has_value()) {
|
||||
// a. Return ? ParseTimeZoneIdentifier(timeZoneResult.[[OffsetString]]).
|
||||
return TRY(parse_time_zone_identifier(vm, *time_zone_result.offset_string));
|
||||
}
|
||||
|
||||
// 8. Throw a RangeError exception.
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneString, time_zone_string);
|
||||
}
|
||||
|
||||
// 13.40 ToMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tomonthcode
|
||||
ThrowCompletionOr<String> to_month_code(VM& vm, Value argument)
|
||||
{
|
||||
// 1. Let monthCode be ? ToPrimitive(argument, STRING).
|
||||
auto month_code = TRY(argument.to_primitive(vm, Value::PreferredType::String));
|
||||
|
||||
// 2. If monthCode is not a String, throw a TypeError exception.
|
||||
if (!month_code.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
auto month_code_string = month_code.as_string().utf8_string_view();
|
||||
|
||||
// 3. If the length of monthCode is not 3 or 4, throw a RangeError exception.
|
||||
if (month_code_string.length() != 3 && month_code_string.length() != 4)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
|
||||
// 4. If the first code unit of monthCode is not 0x004D (LATIN CAPITAL LETTER M), throw a RangeError exception.
|
||||
if (month_code_string[0] != 'M')
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
|
||||
// 5. If the second code unit of monthCode is not in the inclusive interval from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE),
|
||||
// throw a RangeError exception.
|
||||
if (!is_ascii_digit(month_code_string[1]) || parse_ascii_digit(month_code_string[1]) > 9)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
|
||||
// 6. If the third code unit of monthCode is not in the inclusive interval from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE),
|
||||
// throw a RangeError exception.
|
||||
if (!is_ascii_digit(month_code_string[2]) || parse_ascii_digit(month_code_string[2]) > 9)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
|
||||
// 7. If the length of monthCode is 4 and the fourth code unit of monthCode is not 0x004C (LATIN CAPITAL LETTER L),
|
||||
// throw a RangeError exception.
|
||||
if (month_code_string.length() == 4 && month_code_string[3] != 'L')
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
|
||||
// 8. Let monthCodeDigits be the substring of monthCode from 1 to 3.
|
||||
auto month_code_digits = month_code_string.substring_view(1, 2);
|
||||
|
||||
// 9. Let monthCodeInteger be ℝ(StringToNumber(monthCodeDigits)).
|
||||
auto month_code_integer = month_code_digits.to_number<u8>().value();
|
||||
|
||||
// 10. If monthCodeInteger is 0 and the length of monthCode is not 4, throw a RangeError exception.
|
||||
if (month_code_integer == 0 && month_code_string.length() != 4)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
|
||||
|
||||
// 11. Return monthCode.
|
||||
return month_code.as_string().utf8_string();
|
||||
}
|
||||
|
||||
// 13.41 ToOffsetString ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tooffsetstring
|
||||
ThrowCompletionOr<String> to_offset_string(VM& vm, Value argument)
|
||||
{
|
||||
// 1. Let offset be ? ToPrimitive(argument, STRING).
|
||||
auto offset = TRY(argument.to_primitive(vm, Value::PreferredType::String));
|
||||
|
||||
// 2. If offset is not a String, throw a TypeError exception.
|
||||
if (!offset.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidTimeZoneString, offset);
|
||||
|
||||
// 3. Perform ? ParseDateTimeUTCOffset(offset).
|
||||
TRY(parse_date_time_utc_offset(vm, offset.as_string().utf8_string_view()));
|
||||
|
||||
// 4. Return offset.
|
||||
return offset.as_string().utf8_string();
|
||||
}
|
||||
|
||||
// 13.42 ISODateToFields ( calendar, isoDate, type ), https://tc39.es/proposal-temporal/#sec-temporal-isodatetofields
|
||||
CalendarFields iso_date_to_fields(StringView calendar, ISODate const& iso_date, DateType type)
|
||||
{
|
||||
// 1. Let fields be an empty Calendar Fields Record with all fields set to unset.
|
||||
auto fields = CalendarFields::unset();
|
||||
|
||||
// 2. Let calendarDate be CalendarISOToDate(calendar, isoDate).
|
||||
auto calendar_date = calendar_iso_to_date(calendar, iso_date);
|
||||
|
||||
// 3. Set fields.[[MonthCode]] to calendarDate.[[MonthCode]].
|
||||
fields.month_code = calendar_date.month_code;
|
||||
|
||||
// 4. If type is MONTH-DAY or DATE, then
|
||||
if (type == DateType::MonthDay || type == DateType::Date) {
|
||||
// a. Set fields.[[Day]] to calendarDate.[[Day]].
|
||||
fields.day = calendar_date.day;
|
||||
}
|
||||
|
||||
// 5. If type is YEAR-MONTH or DATE, then
|
||||
if (type == DateType::YearMonth || type == DateType::Date) {
|
||||
// a. Set fields.[[Year]] to calendarDate.[[Year]].
|
||||
fields.year = calendar_date.year;
|
||||
}
|
||||
|
||||
// 6. Return fields.
|
||||
return fields;
|
||||
}
|
||||
|
||||
// 14.4.1.1 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
|
||||
ThrowCompletionOr<GC::Ref<Object>> get_options_object(VM& vm, Value options)
|
||||
{
|
||||
|
@ -992,4 +1508,19 @@ ThrowCompletionOr<u64> get_rounding_increment_option(VM& vm, Object const& optio
|
|||
return static_cast<u64>(integer_increment);
|
||||
}
|
||||
|
||||
// 14.5.1 GetUTCEpochNanoseconds ( isoDateTime ), https://tc39.es/proposal-temporal/#sec-getutcepochnanoseconds
|
||||
Crypto::SignedBigInteger get_utc_epoch_nanoseconds(ISODateTime const& iso_date_time)
|
||||
{
|
||||
return JS::get_utc_epoch_nanoseconds(
|
||||
iso_date_time.iso_date.year,
|
||||
iso_date_time.iso_date.month,
|
||||
iso_date_time.iso_date.day,
|
||||
iso_date_time.time.hour,
|
||||
iso_date_time.time.minute,
|
||||
iso_date_time.time.second,
|
||||
iso_date_time.time.millisecond,
|
||||
iso_date_time.time.microsecond,
|
||||
iso_date_time.time.nanosecond);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include <LibGC/Ptr.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <math.h>
|
||||
|
@ -25,6 +27,29 @@ enum class ArithmeticOperation {
|
|||
Subtract,
|
||||
};
|
||||
|
||||
enum class DateType {
|
||||
Date,
|
||||
MonthDay,
|
||||
YearMonth,
|
||||
};
|
||||
|
||||
enum class Overflow {
|
||||
Constrain,
|
||||
Reject,
|
||||
};
|
||||
|
||||
enum class ShowCalendar {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
Critical,
|
||||
};
|
||||
|
||||
enum class TimeStyle {
|
||||
Separated,
|
||||
Unseparated,
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-units
|
||||
enum class Unit {
|
||||
Year,
|
||||
|
@ -91,8 +116,9 @@ using UnitValue = Variant<Unset, Auto, Unit>;
|
|||
|
||||
struct SecondsStringPrecision {
|
||||
struct Minute { };
|
||||
using Precision = Variant<Minute, Auto, u8>;
|
||||
|
||||
Variant<Minute, Auto, u8> precision;
|
||||
Precision precision;
|
||||
Unit unit;
|
||||
u8 increment { 0 };
|
||||
};
|
||||
|
@ -103,6 +129,29 @@ struct RelativeTo {
|
|||
GC::Ptr<JS::Object> zoned_relative_to; // [[ZonedRelativeTo]]
|
||||
};
|
||||
|
||||
// 13.31 ISO String Time Zone Parse Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-string-time-zone-parse-records
|
||||
struct ParsedISOTimeZone {
|
||||
bool z_designator { false };
|
||||
Optional<String> offset_string;
|
||||
Optional<String> time_zone_annotation;
|
||||
};
|
||||
|
||||
// 13.32 ISO Date-Time Parse Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-date-time-parse-records
|
||||
struct ParsedISODateTime {
|
||||
struct StartOfDay { };
|
||||
|
||||
Optional<i32> year { 0 };
|
||||
u8 month { 0 };
|
||||
u8 day { 0 };
|
||||
Variant<StartOfDay, Time> time;
|
||||
ParsedISOTimeZone time_zone;
|
||||
Optional<String> calendar;
|
||||
};
|
||||
|
||||
double iso_date_to_epoch_days(double year, double month, double date);
|
||||
double epoch_days_to_epoch_ms(double day, double time);
|
||||
ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& options);
|
||||
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options);
|
||||
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
|
||||
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
|
||||
SecondsStringPrecision to_seconds_string_precision_record(UnitValue, Precision);
|
||||
|
@ -113,13 +162,21 @@ bool is_calendar_unit(Unit);
|
|||
UnitCategory temporal_unit_category(Unit);
|
||||
RoundingIncrement maximum_temporal_duration_rounding_increment(Unit);
|
||||
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit);
|
||||
ThrowCompletionOr<bool> is_partial_temporal_object(VM&, Value);
|
||||
String format_fractional_seconds(u64, Precision);
|
||||
String format_time_string(u8 hour, u8 minute, u8 second, u16 sub_second_nanoseconds, SecondsStringPrecision::Precision, Optional<TimeStyle> = {});
|
||||
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
|
||||
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);
|
||||
double round_number_to_increment(double, u64 increment, RoundingMode);
|
||||
Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
||||
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM&, StringView iso_string, ReadonlySpan<Production> allowed_formats);
|
||||
ThrowCompletionOr<String> parse_temporal_calendar_string(VM&, String const&);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);
|
||||
ThrowCompletionOr<TimeZone> parse_temporal_time_zone_string(VM& vm, StringView time_zone_string);
|
||||
ThrowCompletionOr<String> to_month_code(VM&, Value argument);
|
||||
ThrowCompletionOr<String> to_offset_string(VM&, Value argument);
|
||||
CalendarFields iso_date_to_fields(StringView calendar, ISODate const&, DateType);
|
||||
|
||||
// 13.38 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation
|
||||
template<typename... Args>
|
||||
|
@ -153,6 +210,21 @@ ThrowCompletionOr<double> to_integer_with_truncation(VM& vm, StringView argument
|
|||
return trunc(number);
|
||||
}
|
||||
|
||||
// 13.37 ToPositiveIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-topositiveintegerwithtruncation
|
||||
template<typename... Args>
|
||||
ThrowCompletionOr<double> to_positive_integer_with_truncation(VM& vm, Value argument, ErrorType error_type, Args&&... args)
|
||||
{
|
||||
// 1. Let integer be ? ToIntegerWithTruncation(argument).
|
||||
auto integer = TRY(to_integer_with_truncation(vm, argument, error_type, args...));
|
||||
|
||||
// 2. If integer ≤ 0, throw a RangeError exception.
|
||||
if (integer <= 0)
|
||||
return vm.throw_completion<RangeError>(error_type, args...);
|
||||
|
||||
// 3. Return integer.
|
||||
return integer;
|
||||
}
|
||||
|
||||
// 13.39 ToIntegerIfIntegral ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerifintegral
|
||||
template<typename... Args>
|
||||
ThrowCompletionOr<double> to_integer_if_integral(VM& vm, Value argument, ErrorType error_type, Args&&... args)
|
||||
|
@ -168,6 +240,12 @@ ThrowCompletionOr<double> to_integer_if_integral(VM& vm, Value argument, ErrorTy
|
|||
return number.as_double();
|
||||
}
|
||||
|
||||
// 14.2 The Year-Week Record Specification Type, https://tc39.es/proposal-temporal/#sec-year-week-record-specification-type
|
||||
struct YearWeek {
|
||||
Optional<u8> week;
|
||||
Optional<i32> year;
|
||||
};
|
||||
|
||||
enum class OptionType {
|
||||
Boolean,
|
||||
String,
|
||||
|
@ -186,5 +264,6 @@ ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey c
|
|||
|
||||
ThrowCompletionOr<RoundingMode> get_rounding_mode_option(VM&, Object const& options, RoundingMode fallback);
|
||||
ThrowCompletionOr<u64> get_rounding_increment_option(VM&, Object const& options);
|
||||
Crypto::SignedBigInteger get_utc_epoch_nanoseconds(ISODateTime const&);
|
||||
|
||||
}
|
||||
|
|
713
Libraries/LibJS/Runtime/Temporal/Calendar.cpp
Normal file
713
Libraries/LibJS/Runtime/Temporal/Calendar.cpp
Normal file
|
@ -0,0 +1,713 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/NonnullRawPtr.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/DateEquations.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibUnicode/Locale.h>
|
||||
#include <LibUnicode/UnicodeKeywords.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
enum class CalendarFieldConversion {
|
||||
ToIntegerWithTruncation,
|
||||
ToMonthCode,
|
||||
ToOffsetString,
|
||||
ToPositiveIntegerWithTruncation,
|
||||
ToString,
|
||||
ToTemporalTimeZoneIdentifier,
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields
|
||||
#define JS_ENUMERATE_CALENDAR_FIELDS \
|
||||
__JS_ENUMERATE(CalendarField::Era, era, vm.names.era, CalendarFieldConversion::ToString) \
|
||||
__JS_ENUMERATE(CalendarField::EraYear, era_year, vm.names.eraYear, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Year, year, vm.names.year, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Month, month, vm.names.month, CalendarFieldConversion::ToPositiveIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::MonthCode, month_code, vm.names.monthCode, CalendarFieldConversion::ToMonthCode) \
|
||||
__JS_ENUMERATE(CalendarField::Day, day, vm.names.day, CalendarFieldConversion::ToPositiveIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Hour, hour, vm.names.hour, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Minute, minute, vm.names.minute, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Second, second, vm.names.second, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Millisecond, millisecond, vm.names.millisecond, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Microsecond, microsecond, vm.names.microsecond, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Nanosecond, nanosecond, vm.names.nanosecond, CalendarFieldConversion::ToIntegerWithTruncation) \
|
||||
__JS_ENUMERATE(CalendarField::Offset, offset, vm.names.offset, CalendarFieldConversion::ToOffsetString) \
|
||||
__JS_ENUMERATE(CalendarField::TimeZone, time_zone, vm.names.timeZone, CalendarFieldConversion::ToTemporalTimeZoneIdentifier)
|
||||
|
||||
struct CalendarFieldData {
|
||||
CalendarField key;
|
||||
NonnullRawPtr<PropertyKey> property;
|
||||
CalendarFieldConversion conversion;
|
||||
};
|
||||
static Vector<CalendarFieldData> sorted_calendar_fields(VM& vm, CalendarFieldList fields)
|
||||
{
|
||||
auto data_for_field = [&](auto field) -> CalendarFieldData {
|
||||
switch (field) {
|
||||
#define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
|
||||
case enumeration: \
|
||||
return { enumeration, property_key, conversion };
|
||||
JS_ENUMERATE_CALENDAR_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
};
|
||||
|
||||
Vector<CalendarFieldData> result;
|
||||
result.ensure_capacity(fields.size());
|
||||
|
||||
for (auto field : fields)
|
||||
result.unchecked_append(data_for_field(field));
|
||||
|
||||
quick_sort(result, [](auto const& lhs, auto const& rhs) {
|
||||
return StringView { lhs.property->as_string() } < StringView { rhs.property->as_string() };
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void set_field_value(CalendarField field, CalendarFields& fields, T&& value)
|
||||
{
|
||||
switch (field) {
|
||||
#define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
|
||||
case enumeration: \
|
||||
if constexpr (IsAssignable<decltype(fields.field_name), RemoveCVReference<T>>) \
|
||||
fields.field_name = value; \
|
||||
return;
|
||||
JS_ENUMERATE_CALENDAR_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static void set_default_field_value(CalendarField field, CalendarFields& fields)
|
||||
{
|
||||
CalendarFields default_ {};
|
||||
|
||||
switch (field) {
|
||||
#define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
|
||||
case enumeration: \
|
||||
fields.field_name = default_.field_name; \
|
||||
return;
|
||||
JS_ENUMERATE_CALENDAR_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// 12.1.1 CanonicalizeCalendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar
|
||||
ThrowCompletionOr<String> canonicalize_calendar(VM& vm, StringView id)
|
||||
{
|
||||
// 1. Let calendars be AvailableCalendars().
|
||||
auto const& calendars = available_calendars();
|
||||
|
||||
// 2. If calendars does not contain the ASCII-lowercase of id, throw a RangeError exception.
|
||||
for (auto const& calendar : calendars) {
|
||||
if (calendar.equals_ignoring_ascii_case(id)) {
|
||||
// 3. Return CanonicalizeUValue("ca", id).
|
||||
return Unicode::canonicalize_unicode_extension_values("ca"sv, id);
|
||||
}
|
||||
}
|
||||
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, id);
|
||||
}
|
||||
|
||||
// 12.1.2 AvailableCalendars ( ), https://tc39.es/proposal-temporal/#sec-availablecalendars
|
||||
Vector<String> const& available_calendars()
|
||||
{
|
||||
// The implementation-defined abstract operation AvailableCalendars takes no arguments and returns a List of calendar
|
||||
// types. The returned List is sorted according to lexicographic code unit order, and contains unique calendar types
|
||||
// in canonical form (12.1) identifying the calendars for which the implementation provides the functionality of
|
||||
// Intl.DateTimeFormat objects, including their aliases (e.g., either both or neither of "islamicc" and
|
||||
// "islamic-civil"). The List must include "iso8601".
|
||||
return Unicode::available_calendars();
|
||||
}
|
||||
|
||||
// 12.2.3 PrepareCalendarFields ( calendar, fields, calendarFieldNames, nonCalendarFieldNames, requiredFieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields
|
||||
ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM& vm, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names)
|
||||
{
|
||||
// 1. Assert: If requiredFieldNames is a List, requiredFieldNames contains zero or one of each of the elements of
|
||||
// calendarFieldNames and nonCalendarFieldNames.
|
||||
|
||||
// 2. Let fieldNames be the list-concatenation of calendarFieldNames and nonCalendarFieldNames.
|
||||
Vector<CalendarField> field_names;
|
||||
field_names.append(calendar_field_names.data(), calendar_field_names.size());
|
||||
field_names.append(non_calendar_field_names.data(), non_calendar_field_names.size());
|
||||
|
||||
// 3. Let extraFieldNames be CalendarExtraFields(calendar, calendarFieldNames).
|
||||
auto extra_field_names = calendar_extra_fields(calendar, calendar_field_names);
|
||||
|
||||
// 4. Set fieldNames to the list-concatenation of fieldNames and extraFieldNames.
|
||||
field_names.extend(move(extra_field_names));
|
||||
|
||||
// 5. Assert: fieldNames contains no duplicate elements.
|
||||
|
||||
// 6. Let result be a Calendar Fields Record with all fields equal to UNSET.
|
||||
auto result = CalendarFields::unset();
|
||||
|
||||
// 7. Let any be false.
|
||||
auto any = false;
|
||||
|
||||
// 8. Let sortedPropertyNames be a List whose elements are the values in the Property Key column of Table 19
|
||||
// corresponding to the elements of fieldNames, sorted according to lexicographic code unit order.
|
||||
auto sorted_property_names = sorted_calendar_fields(vm, field_names);
|
||||
|
||||
// 9. For each property name property of sortedPropertyNames, do
|
||||
for (auto const& [key, property, conversion] : sorted_property_names) {
|
||||
// a. Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property.
|
||||
|
||||
// b. Let value be ? Get(fields, property).
|
||||
auto value = TRY(fields.get(property));
|
||||
|
||||
// c. If value is not undefined, then
|
||||
if (!value.is_undefined()) {
|
||||
// i. Set any to true.
|
||||
any = true;
|
||||
|
||||
// ii. Let Conversion be the Conversion value of the same row.
|
||||
switch (conversion) {
|
||||
// iii. If Conversion is TO-INTEGER-WITH-TRUNCATION, then
|
||||
case CalendarFieldConversion::ToIntegerWithTruncation:
|
||||
// 1. Set value to ? ToIntegerWithTruncation(value).
|
||||
// 2. Set value to 𝔽(value).
|
||||
set_field_value(key, result, TRY(to_integer_with_truncation(vm, value, ErrorType::TemporalInvalidCalendarFieldName, *property)));
|
||||
break;
|
||||
// iv. Else if Conversion is TO-POSITIVE-INTEGER-WITH-TRUNCATION, then
|
||||
case CalendarFieldConversion::ToPositiveIntegerWithTruncation:
|
||||
// 1. Set value to ? ToPositiveIntegerWithTruncation(value).
|
||||
// 2. Set value to 𝔽(value).
|
||||
set_field_value(key, result, TRY(to_positive_integer_with_truncation(vm, value, ErrorType::TemporalInvalidCalendarFieldName, *property)));
|
||||
break;
|
||||
// v. Else if Conversion is TO-STRING, then
|
||||
case CalendarFieldConversion::ToString:
|
||||
// 1. Set value to ? ToString(value).
|
||||
set_field_value(key, result, TRY(value.to_string(vm)));
|
||||
break;
|
||||
// vi. Else if Conversion is TO-TEMPORAL-TIME-ZONE-IDENTIFIER, then
|
||||
case CalendarFieldConversion::ToTemporalTimeZoneIdentifier:
|
||||
// 1. Set value to ? ToTemporalTimeZoneIdentifier(value).
|
||||
set_field_value(key, result, TRY(to_temporal_time_zone_identifier(vm, value)));
|
||||
break;
|
||||
// vii. Else if Conversion is TO-MONTH-CODE, then
|
||||
case CalendarFieldConversion::ToMonthCode:
|
||||
// 1. Set value to ? ToMonthCode(value).
|
||||
set_field_value(key, result, TRY(to_month_code(vm, value)));
|
||||
break;
|
||||
// viii. Else,
|
||||
case CalendarFieldConversion::ToOffsetString:
|
||||
// 1. Assert: Conversion is TO-OFFSET-STRING.
|
||||
// 2. Set value to ? ToOffsetString(value).
|
||||
set_field_value(key, result, TRY(to_offset_string(vm, value)));
|
||||
break;
|
||||
}
|
||||
|
||||
// ix. Set result's field whose name is given in the Field Name column of the same row to value.
|
||||
}
|
||||
// d. Else if requiredFieldNames is a List, then
|
||||
else if (auto const* required = required_field_names.get_pointer<CalendarFieldList>()) {
|
||||
// i. If requiredFieldNames contains key, then
|
||||
if (required->contains_slow(key)) {
|
||||
// 1. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, *property);
|
||||
}
|
||||
|
||||
// ii. Set result's field whose name is given in the Field Name column of the same row to the corresponding
|
||||
// Default value of the same row.
|
||||
set_default_field_value(key, result);
|
||||
}
|
||||
}
|
||||
|
||||
// 10. If requiredFieldNames is PARTIAL and any is false, then
|
||||
if (required_field_names.has<Partial>() && !any) {
|
||||
// a. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalObjectMustBePartialTemporalObject);
|
||||
}
|
||||
|
||||
// 11. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 12.2.4 CalendarFieldKeysPresent ( fields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent
|
||||
Vector<CalendarField> calendar_field_keys_present(CalendarFields const& fields)
|
||||
{
|
||||
// 1. Let list be « ».
|
||||
Vector<CalendarField> list;
|
||||
|
||||
auto handle_field = [&](auto enumeration_key, auto const& value) {
|
||||
// a. Let value be fields' field whose name is given in the Field Name column of the row.
|
||||
// b. Let enumerationKey be the value in the Enumeration Key column of the row.
|
||||
// c. If value is not unset, append enumerationKey to list.
|
||||
if (value.has_value())
|
||||
list.append(enumeration_key);
|
||||
};
|
||||
|
||||
// 2. For each row of Table 19, except the header row, do
|
||||
#define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
|
||||
handle_field(enumeration, fields.field_name);
|
||||
JS_ENUMERATE_CALENDAR_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
// 3. Return list.
|
||||
return list;
|
||||
}
|
||||
|
||||
// 12.2.5 CalendarMergeFields ( calendar, fields, additionalFields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields
|
||||
CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields)
|
||||
{
|
||||
// 1. Let additionalKeys be CalendarFieldKeysPresent(additionalFields).
|
||||
auto additional_keys = calendar_field_keys_present(additional_fields);
|
||||
|
||||
// 2. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys).
|
||||
auto overridden_keys = calendar_field_keys_to_ignore(calendar, additional_keys);
|
||||
|
||||
// 3. Let merged be a Calendar Fields Record with all fields set to unset.
|
||||
auto merged = CalendarFields::unset();
|
||||
|
||||
// 4. Let fieldsKeys be CalendarFieldKeysPresent(fields).
|
||||
auto fields_keys = calendar_field_keys_present(fields);
|
||||
|
||||
auto merge_field = [&](auto key, auto& merged_field, auto const& fields_field, auto const& additional_fields_field) {
|
||||
// a. Let key be the value in the Enumeration Key column of the row.
|
||||
|
||||
// b. If fieldsKeys contains key and overriddenKeys does not contain key, then
|
||||
if (fields_keys.contains_slow(key) && !overridden_keys.contains_slow(key)) {
|
||||
// i. Let propValue be fields' field whose name is given in the Field Name column of the row.
|
||||
// ii. Set merged's field whose name is given in the Field Name column of the row to propValue.
|
||||
merged_field = fields_field;
|
||||
}
|
||||
|
||||
// c. If additionalKeys contains key, then
|
||||
if (additional_keys.contains_slow(key)) {
|
||||
// i. Let propValue be additionalFields' field whose name is given in the Field Name column of the row.
|
||||
// ii. Set merged's field whose name is given in the Field Name column of the row to propValue.
|
||||
merged_field = additional_fields_field;
|
||||
}
|
||||
};
|
||||
|
||||
// 5. For each row of Table 19, except the header row, do
|
||||
#define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
|
||||
merge_field(enumeration, merged.field_name, fields.field_name, additional_fields.field_name);
|
||||
JS_ENUMERATE_CALENDAR_FIELDS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
// 6. Return merged.
|
||||
return merged;
|
||||
}
|
||||
|
||||
// 12.2.8 ToTemporalCalendarIdentifier ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier
|
||||
ThrowCompletionOr<String> to_temporal_calendar_identifier(VM& vm, Value temporal_calendar_like)
|
||||
{
|
||||
// 1. If temporalCalendarLike is an Object, then
|
||||
if (temporal_calendar_like.is_object()) {
|
||||
auto const& temporal_calendar_object = temporal_calendar_like.as_object();
|
||||
|
||||
// a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]],
|
||||
// [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
|
||||
// internal slot, then
|
||||
// i. Return temporalCalendarLike.[[Calendar]].
|
||||
// FIXME: Add the other calendar-holding types as we define them.
|
||||
if (is<PlainMonthDay>(temporal_calendar_object))
|
||||
return static_cast<PlainMonthDay const&>(temporal_calendar_object).calendar();
|
||||
}
|
||||
|
||||
// 2. If temporalCalendarLike is not a String, throw a TypeError exception.
|
||||
if (!temporal_calendar_like.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidCalendar);
|
||||
|
||||
// 3. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike).
|
||||
auto identifier = TRY(parse_temporal_calendar_string(vm, temporal_calendar_like.as_string().utf8_string()));
|
||||
|
||||
// 4. Return ? CanonicalizeCalendar(identifier).
|
||||
return TRY(canonicalize_calendar(vm, identifier));
|
||||
}
|
||||
|
||||
// 12.2.9 GetTemporalCalendarIdentifierWithISODefault ( item ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendarslotvaluewithisodefault
|
||||
ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM& vm, Object const& item)
|
||||
{
|
||||
// 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
|
||||
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||
// a. Return item.[[Calendar]].
|
||||
// FIXME: Add the other calendar-holding types as we define them.
|
||||
if (is<PlainMonthDay>(item))
|
||||
return static_cast<PlainMonthDay const&>(item).calendar();
|
||||
|
||||
// 2. Let calendarLike be ? Get(item, "calendar").
|
||||
auto calendar_like = TRY(item.get(vm.names.calendar));
|
||||
|
||||
// 3. If calendarLike is undefined, then
|
||||
if (calendar_like.is_undefined()) {
|
||||
// a. Return "iso8601".
|
||||
return "iso8601"_string;
|
||||
}
|
||||
|
||||
// 4. Return ? ToTemporalCalendarIdentifier(calendarLike).
|
||||
return TRY(to_temporal_calendar_identifier(vm, calendar_like));
|
||||
}
|
||||
|
||||
// 12.2.12 CalendarMonthDayFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields
|
||||
ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM& vm, StringView calendar, CalendarFields fields, Overflow overflow)
|
||||
{
|
||||
// 1. Perform ? CalendarResolveFields(calendar, fields, MONTH-DAY).
|
||||
TRY(calendar_resolve_fields(vm, calendar, fields, DateType::MonthDay));
|
||||
|
||||
// 2. Let result be ? CalendarMonthDayToISOReferenceDate(calendar, fields, overflow).
|
||||
auto result = TRY(calendar_month_day_to_iso_reference_date(vm, calendar, fields, overflow));
|
||||
|
||||
// 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
|
||||
if (!iso_date_within_limits(result))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
|
||||
|
||||
// 4. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 12.2.13 FormatCalendarAnnotation ( id, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation
|
||||
String format_calendar_annotation(StringView id, ShowCalendar show_calendar)
|
||||
{
|
||||
// 1. If showCalendar is NEVER, return the empty String.
|
||||
if (show_calendar == ShowCalendar::Never)
|
||||
return String {};
|
||||
|
||||
// 2. If showCalendar is AUTO and id is "iso8601", return the empty String.
|
||||
if (show_calendar == ShowCalendar::Auto && id == "iso8601"sv)
|
||||
return String {};
|
||||
|
||||
// 3. If showCalendar is CRITICAL, let flag be "!"; else, let flag be the empty String.
|
||||
auto flag = show_calendar == ShowCalendar::Critical ? "!"sv : ""sv;
|
||||
|
||||
// 4. Return the string-concatenation of "[", flag, "u-ca=", id, and "]".
|
||||
return MUST(String::formatted("[{}u-ca={}]", flag, id));
|
||||
}
|
||||
|
||||
// 12.2.14 CalendarEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-calendarequals
|
||||
bool calendar_equals(StringView one, StringView two)
|
||||
{
|
||||
// 1. If CanonicalizeUValue("ca", one) is CanonicalizeUValue("ca", two), return true.
|
||||
// 2. Return false.
|
||||
return Unicode::canonicalize_unicode_extension_values("ca"sv, one)
|
||||
== Unicode::canonicalize_unicode_extension_values("ca"sv, two);
|
||||
}
|
||||
|
||||
// 12.2.15 ISODaysInMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
|
||||
u8 iso_days_in_month(double year, double month)
|
||||
{
|
||||
// 1. If month is 1, 3, 5, 7, 8, 10, or 12, return 31.
|
||||
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)
|
||||
return 31;
|
||||
|
||||
// 2. If month is 4, 6, 9, or 11, return 30.
|
||||
if (month == 4 || month == 6 || month == 9 || month == 11)
|
||||
return 30;
|
||||
|
||||
// 3. Assert: month is 2.
|
||||
VERIFY(month == 2);
|
||||
|
||||
// 4. Return 28 + MathematicalInLeapYear(EpochTimeForYear(year)).
|
||||
return 28 + mathematical_in_leap_year(epoch_time_for_year(year));
|
||||
}
|
||||
|
||||
// 12.2.16 ISOWeekOfYear ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoweekofyear
|
||||
YearWeek iso_week_of_year(ISODate const& iso_date)
|
||||
{
|
||||
// 1. Let year be isoDate.[[Year]].
|
||||
auto year = iso_date.year;
|
||||
|
||||
// 2. Let wednesday be 3.
|
||||
static constexpr auto wednesday = 3;
|
||||
|
||||
// 3. Let thursday be 4.
|
||||
static constexpr auto thursday = 4;
|
||||
|
||||
// 4. Let friday be 5.
|
||||
static constexpr auto friday = 5;
|
||||
|
||||
// 5. Let saturday be 6.
|
||||
static constexpr auto saturday = 6;
|
||||
|
||||
// 6. Let daysInWeek be 7.
|
||||
static constexpr auto days_in_week = 7;
|
||||
|
||||
// 7. Let maxWeekNumber be 53.
|
||||
static constexpr auto max_week_number = 53;
|
||||
|
||||
// 8. Let dayOfYear be ISODayOfYear(isoDate).
|
||||
auto day_of_year = iso_day_of_year(iso_date);
|
||||
|
||||
// 9. Let dayOfWeek be ISODayOfWeek(isoDate).
|
||||
auto day_of_week = iso_day_of_week(iso_date);
|
||||
|
||||
// 10. Let week be floor((dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek).
|
||||
auto week = floor(static_cast<double>(day_of_year + days_in_week - day_of_week + wednesday) / static_cast<double>(days_in_week));
|
||||
|
||||
// 11. If week < 1, then
|
||||
if (week < 1) {
|
||||
// a. NOTE: This is the last week of the previous year.
|
||||
|
||||
// b. Let jan1st be CreateISODateRecord(year, 1, 1).
|
||||
auto jan1st = create_iso_date_record(year, 1, 1);
|
||||
|
||||
// c. Let dayOfJan1st be ISODayOfWeek(jan1st).
|
||||
auto day_of_jan1st = iso_day_of_week(jan1st);
|
||||
|
||||
// d. If dayOfJan1st = friday, then
|
||||
if (day_of_jan1st == friday) {
|
||||
// i. Return Year-Week Record { [[Week]]: maxWeekNumber, [[Year]]: year - 1 }.
|
||||
return { .week = max_week_number, .year = year - 1 };
|
||||
}
|
||||
|
||||
// e. If dayOfJan1st = saturday, and MathematicalInLeapYear(EpochTimeForYear(year - 1)) = 1, then
|
||||
if (day_of_jan1st == saturday && mathematical_in_leap_year(epoch_time_for_year(year - 1)) == 1) {
|
||||
// i. Return Year-Week Record { [[Week]]: maxWeekNumber. [[Year]]: year - 1 }.
|
||||
return { .week = max_week_number, .year = year - 1 };
|
||||
}
|
||||
|
||||
// f. Return Year-Week Record { [[Week]]: maxWeekNumber - 1, [[Year]]: year - 1 }.
|
||||
return { .week = max_week_number - 1, .year = year - 1 };
|
||||
}
|
||||
|
||||
// 12. If week = maxWeekNumber, then
|
||||
if (week == max_week_number) {
|
||||
// a. Let daysInYear be MathematicalDaysInYear(year).
|
||||
auto days_in_year = mathematical_days_in_year(year);
|
||||
|
||||
// b. Let daysLaterInYear be daysInYear - dayOfYear.
|
||||
auto days_later_in_year = days_in_year - day_of_year;
|
||||
|
||||
// c. Let daysAfterThursday be thursday - dayOfWeek.
|
||||
auto days_after_thursday = thursday - day_of_week;
|
||||
|
||||
// d. If daysLaterInYear < daysAfterThursday, then
|
||||
if (days_later_in_year < days_after_thursday) {
|
||||
// i. Return Year-Week Record { [[Week]]: 1, [[Year]]: year + 1 }.
|
||||
return { .week = 1, .year = year + 1 };
|
||||
}
|
||||
}
|
||||
|
||||
// 13. Return Year-Week Record { [[Week]]: week, [[Year]]: year }.
|
||||
return { .week = week, .year = year };
|
||||
}
|
||||
|
||||
// 12.2.17 ISODayOfYear ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodayofyear
|
||||
u16 iso_day_of_year(ISODate const& iso_date)
|
||||
{
|
||||
// 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
|
||||
auto epoch_days = iso_date_to_epoch_days(iso_date.year, iso_date.month - 1, iso_date.day);
|
||||
|
||||
// 2. Return EpochTimeToDayInYear(EpochDaysToEpochMs(epochDays, 0)) + 1.
|
||||
return epoch_time_to_day_in_year(epoch_days_to_epoch_ms(epoch_days, 0)) + 1;
|
||||
}
|
||||
|
||||
// 12.2.18 ISODayOfWeek ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodayofweek
|
||||
u8 iso_day_of_week(ISODate const& iso_date)
|
||||
{
|
||||
// 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
|
||||
auto epoch_days = iso_date_to_epoch_days(iso_date.year, iso_date.month - 1, iso_date.day);
|
||||
|
||||
// 2. Let dayOfWeek be EpochTimeToWeekDay(EpochDaysToEpochMs(epochDays, 0)).
|
||||
auto day_of_week = epoch_time_to_week_day(epoch_days_to_epoch_ms(epoch_days, 0));
|
||||
|
||||
// 3. If dayOfWeek = 0, return 7.
|
||||
if (day_of_week == 0)
|
||||
return 7;
|
||||
|
||||
// 4. Return dayOfWeek.
|
||||
return day_of_week;
|
||||
}
|
||||
|
||||
// 12.2.20 CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate
|
||||
ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM& vm, StringView calendar, CalendarFields const& fields, Overflow overflow)
|
||||
{
|
||||
// 1. If calendar is "iso8601", then
|
||||
if (calendar == "iso8601"sv) {
|
||||
// a. Assert: fields.[[Month]] and fields.[[Day]] are not UNSET.
|
||||
VERIFY(fields.month.has_value());
|
||||
VERIFY(fields.day.has_value());
|
||||
|
||||
// b. Let referenceISOYear be 1972 (the first ISO 8601 leap year after the epoch).
|
||||
static constexpr i32 reference_iso_year = 1972;
|
||||
|
||||
// c. If fields.[[Year]] is UNSET, let year be referenceISOYear; else let year be fields.[[Year]].
|
||||
auto year = !fields.year.has_value() ? reference_iso_year : *fields.year;
|
||||
|
||||
// d. Let result be ? RegulateISODate(year, fields.[[Month]], fields.[[Day]], overflow).
|
||||
auto result = TRY(regulate_iso_date(vm, year, *fields.month, *fields.day, overflow));
|
||||
|
||||
// e. Return CreateISODateRecord(referenceISOYear, result.[[Month]], result.[[Day]]).
|
||||
return create_iso_date_record(reference_iso_year, result.month, result.day);
|
||||
}
|
||||
|
||||
// 2. Return an implementation-defined ISO Date Record, or throw a RangeError exception, as described below.
|
||||
// FIXME: Create an ISODateRecord based on an ISO8601 calendar for now. See also: CalendarResolveFields.
|
||||
return calendar_month_day_to_iso_reference_date(vm, "iso8601"sv, fields, overflow);
|
||||
}
|
||||
|
||||
// 12.2.21 CalendarISOToDate ( calendar, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
|
||||
CalendarDate calendar_iso_to_date(StringView calendar, ISODate const& iso_date)
|
||||
{
|
||||
// 1. If calendar is "iso8601", then
|
||||
if (calendar == "iso8601"sv) {
|
||||
// a. Let monthNumberPart be ToZeroPaddedDecimalString(isoDate.[[Month]], 2).
|
||||
// b. Let monthCode be the string-concatenation of "M" and monthNumberPart.
|
||||
auto month_code = MUST(String::formatted("M{:02}", iso_date.month));
|
||||
|
||||
// c. If MathematicalInLeapYear(EpochTimeForYear(isoDate.[[Year]])) = 1, let inLeapYear be true; else let inLeapYear be false.
|
||||
auto in_leap_year = mathematical_in_leap_year(epoch_time_for_year(iso_date.year)) == 1;
|
||||
|
||||
// d. Return Calendar Date Record { [[Era]]: undefined, [[EraYear]]: undefined, [[Year]]: isoDate.[[Year]],
|
||||
// [[Month]]: isoDate.[[Month]], [[MonthCode]]: monthCode, [[Day]]: isoDate.[[Day]], [[DayOfWeek]]: ISODayOfWeek(isoDate),
|
||||
// [[DayOfYear]]: ISODayOfYear(isoDate), [[WeekOfYear]]: ISOWeekOfYear(isoDate), [[DaysInWeek]]: 7,
|
||||
// [[DaysInMonth]]: ISODaysInMonth(isoDate.[[Year]], isoDate.[[Month]]), [[DaysInYear]]: MathematicalDaysInYear(isoDate.[[Year]]),
|
||||
// [[MonthsInYear]]: 12, [[InLeapYear]]: inLeapYear }.
|
||||
return CalendarDate {
|
||||
.era = {},
|
||||
.era_year = {},
|
||||
.year = iso_date.year,
|
||||
.month = iso_date.month,
|
||||
.month_code = move(month_code),
|
||||
.day = iso_date.day,
|
||||
.day_of_week = iso_day_of_week(iso_date),
|
||||
.day_of_year = iso_day_of_year(iso_date),
|
||||
.week_of_year = iso_week_of_year(iso_date),
|
||||
.days_in_week = 7,
|
||||
.days_in_month = iso_days_in_month(iso_date.year, iso_date.month),
|
||||
.days_in_year = mathematical_days_in_year(iso_date.year),
|
||||
.months_in_year = 12,
|
||||
.in_leap_year = in_leap_year,
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Return an implementation-defined Calendar Date Record with fields as described in Table 18.
|
||||
// FIXME: Return an ISO8601 calendar date for now.
|
||||
return calendar_iso_to_date("iso8601"sv, iso_date);
|
||||
}
|
||||
|
||||
// 12.2.22 CalendarExtraFields ( calendar, fields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarextrafields
|
||||
Vector<CalendarField> calendar_extra_fields(StringView calendar, CalendarFieldList)
|
||||
{
|
||||
// 1. If calendar is "iso8601", return an empty List.
|
||||
if (calendar == "iso8601"sv)
|
||||
return {};
|
||||
|
||||
// FIXME: 2. Return an implementation-defined List as described above.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 12.2.23 CalendarFieldKeysToIgnore ( calendar, keys ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore
|
||||
Vector<CalendarField> calendar_field_keys_to_ignore(StringView calendar, ReadonlySpan<CalendarField> keys)
|
||||
{
|
||||
// 1. If calendar is "iso8601", then
|
||||
if (calendar == "iso8601"sv) {
|
||||
// a. Let ignoredKeys be an empty List.
|
||||
Vector<CalendarField> ignored_keys;
|
||||
|
||||
// b. For each element key of keys, do
|
||||
for (auto key : keys) {
|
||||
// i. Append key to ignoredKeys.
|
||||
ignored_keys.append(key);
|
||||
|
||||
// ii. If key is MONTH, append MONTH-CODE to ignoredKeys.
|
||||
if (key == CalendarField::Month)
|
||||
ignored_keys.append(CalendarField::MonthCode);
|
||||
// iii. Else if key is MONTH-CODE, append MONTH to ignoredKeys.
|
||||
else if (key == CalendarField::MonthCode)
|
||||
ignored_keys.append(CalendarField::Month);
|
||||
}
|
||||
|
||||
// c. NOTE: While ignoredKeys can have duplicate elements, this is not intended to be meaningful. This specification
|
||||
// only checks whether particular keys are or are not members of the list.
|
||||
|
||||
// d. Return ignoredKeys.
|
||||
return ignored_keys;
|
||||
}
|
||||
|
||||
// 2. Return an implementation-defined List as described below.
|
||||
// FIXME: Return keys for an ISO8601 calendar for now.
|
||||
return calendar_field_keys_to_ignore("iso8601"sv, keys);
|
||||
}
|
||||
|
||||
// 12.2.24 CalendarResolveFields ( calendar, fields, type ), https://tc39.es/proposal-temporal/#sec-temporal-calendarresolvefields
|
||||
ThrowCompletionOr<void> calendar_resolve_fields(VM& vm, StringView calendar, CalendarFields& fields, DateType type)
|
||||
{
|
||||
// 1. If calendar is "iso8601", then
|
||||
if (calendar == "iso8601"sv) {
|
||||
// a. If type is DATE or YEAR-MONTH and fields.[[Year]] is UNSET, throw a TypeError exception.
|
||||
if ((type == DateType::Date || type == DateType::YearMonth) && !fields.year.has_value())
|
||||
return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, "year"sv);
|
||||
|
||||
// b. If type is DATE or MONTH-DAY and fields.[[Day]] is UNSET, throw a TypeError exception.
|
||||
if ((type == DateType::Date || type == DateType::MonthDay) && !fields.day.has_value())
|
||||
return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, "day"sv);
|
||||
|
||||
// c. Let month be fields.[[Month]].
|
||||
auto const& month = fields.month;
|
||||
|
||||
// d. Let monthCode be fields.[[MonthCode]].
|
||||
auto const& month_code = fields.month_code;
|
||||
|
||||
// e. If monthCode is UNSET, then
|
||||
if (!month_code.has_value()) {
|
||||
// i. If month is UNSET, throw a TypeError exception.
|
||||
if (!month.has_value())
|
||||
return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, "month"sv);
|
||||
|
||||
// ii. Return UNUSED.
|
||||
return {};
|
||||
}
|
||||
|
||||
// f. Assert: monthCode is a String.
|
||||
VERIFY(month_code.has_value());
|
||||
|
||||
// g. NOTE: The ISO 8601 calendar does not include leap months.
|
||||
// h. If the length of monthCode is not 3, throw a RangeError exception.
|
||||
if (month_code->byte_count() != 3)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "monthCode"sv);
|
||||
|
||||
// i. If the first code unit of monthCode is not 0x004D (LATIN CAPITAL LETTER M), throw a RangeError exception.
|
||||
if (month_code->bytes_as_string_view()[0] != 'M')
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "monthCode"sv);
|
||||
|
||||
// j. Let monthCodeDigits be the substring of monthCode from 1.
|
||||
auto month_code_digits = month_code->bytes_as_string_view().substring_view(1);
|
||||
|
||||
// k. If ParseText(StringToCodePoints(monthCodeDigits), DateMonth) is a List of errors, throw a RangeError exception.
|
||||
if (!parse_iso8601(Production::DateMonth, month_code_digits).has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "monthCode"sv);
|
||||
|
||||
// l. Let monthCodeInteger be ℝ(StringToNumber(monthCodeDigits)).
|
||||
auto month_code_integer = month_code_digits.to_number<u8>().value();
|
||||
|
||||
// m. If month is not UNSET and month ≠ monthCodeInteger, throw a RangeError exception.
|
||||
if (month.has_value() && month != month_code_integer)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "month"sv);
|
||||
|
||||
// n. Set fields.[[Month]] to monthCodeInteger.
|
||||
fields.month = month_code_integer;
|
||||
}
|
||||
// 2. Else,
|
||||
else {
|
||||
// a. Perform implementation-defined processing to mutate fields, or throw a TypeError or RangeError exception, as described below.
|
||||
// FIXME: Resolve fields as an ISO8601 calendar for now. See also: CalendarMonthDayToISOReferenceDate.
|
||||
TRY(calendar_resolve_fields(vm, "iso8601"sv, fields, type));
|
||||
}
|
||||
|
||||
// 3. Return UNUSED.
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
119
Libraries/LibJS/Runtime/Temporal/Calendar.h
Normal file
119
Libraries/LibJS/Runtime/Temporal/Calendar.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 12.2.1 Calendar Date Records, https://tc39.es/proposal-temporal/#sec-temporal-calendar-date-records
|
||||
struct CalendarDate {
|
||||
Optional<String> era;
|
||||
Optional<i32> era_year;
|
||||
i32 year { 0 };
|
||||
u8 month { 0 };
|
||||
String month_code;
|
||||
u8 day { 0 };
|
||||
u8 day_of_week { 0 };
|
||||
u16 day_of_year { 0 };
|
||||
YearWeek week_of_year;
|
||||
u8 days_in_week { 0 };
|
||||
u8 days_in_month { 0 };
|
||||
u16 days_in_year { 0 };
|
||||
u8 months_in_year { 0 };
|
||||
bool in_leap_year { false };
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields
|
||||
enum class CalendarField {
|
||||
Era,
|
||||
EraYear,
|
||||
Year,
|
||||
Month,
|
||||
MonthCode,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
Millisecond,
|
||||
Microsecond,
|
||||
Nanosecond,
|
||||
Offset,
|
||||
TimeZone,
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields
|
||||
struct CalendarFields {
|
||||
static CalendarFields unset()
|
||||
{
|
||||
return {
|
||||
.era = {},
|
||||
.era_year = {},
|
||||
.year = {},
|
||||
.month = {},
|
||||
.month_code = {},
|
||||
.day = {},
|
||||
.hour = {},
|
||||
.minute = {},
|
||||
.second = {},
|
||||
.millisecond = {},
|
||||
.microsecond = {},
|
||||
.nanosecond = {},
|
||||
.offset = {},
|
||||
.time_zone = {},
|
||||
};
|
||||
}
|
||||
|
||||
Optional<String> era;
|
||||
Optional<i32> era_year;
|
||||
Optional<i32> year;
|
||||
Optional<u32> month;
|
||||
Optional<String> month_code;
|
||||
Optional<u32> day;
|
||||
Optional<u8> hour { 0 };
|
||||
Optional<u8> minute { 0 };
|
||||
Optional<u8> second { 0 };
|
||||
Optional<u16> millisecond { 0 };
|
||||
Optional<u16> microsecond { 0 };
|
||||
Optional<u16> nanosecond { 0 };
|
||||
Optional<String> offset;
|
||||
Optional<String> time_zone;
|
||||
};
|
||||
|
||||
struct Partial { };
|
||||
using CalendarFieldList = ReadonlySpan<CalendarField>;
|
||||
using CalendarFieldListOrPartial = Variant<Partial, CalendarFieldList>;
|
||||
|
||||
ThrowCompletionOr<String> canonicalize_calendar(VM&, StringView id);
|
||||
Vector<String> const& available_calendars();
|
||||
ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, StringView calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
|
||||
ThrowCompletionOr<ISODate> calendar_month_day_from_fields(VM&, StringView calendar, CalendarFields, Overflow);
|
||||
String format_calendar_annotation(StringView id, ShowCalendar);
|
||||
bool calendar_equals(StringView one, StringView two);
|
||||
u8 iso_days_in_month(double year, double month);
|
||||
YearWeek iso_week_of_year(ISODate const&);
|
||||
u16 iso_day_of_year(ISODate const&);
|
||||
u8 iso_day_of_week(ISODate const&);
|
||||
Vector<CalendarField> calendar_field_keys_present(CalendarFields const&);
|
||||
CalendarFields calendar_merge_fields(StringView calendar, CalendarFields const& fields, CalendarFields const& additional_fields);
|
||||
ThrowCompletionOr<String> to_temporal_calendar_identifier(VM&, Value temporal_calendar_like);
|
||||
ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&, Object const& item);
|
||||
ThrowCompletionOr<ISODate> calendar_month_day_to_iso_reference_date(VM&, StringView calendar, CalendarFields const&, Overflow);
|
||||
CalendarDate calendar_iso_to_date(StringView calendar, ISODate const&);
|
||||
Vector<CalendarField> calendar_extra_fields(StringView calendar, CalendarFieldList);
|
||||
Vector<CalendarField> calendar_field_keys_to_ignore(StringView calendar, ReadonlySpan<CalendarField>);
|
||||
ThrowCompletionOr<void> calendar_resolve_fields(VM&, StringView calendar, CalendarFields&, DateType);
|
||||
|
||||
}
|
89
Libraries/LibJS/Runtime/Temporal/DateEquations.cpp
Normal file
89
Libraries/LibJS/Runtime/Temporal/DateEquations.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/Temporal/DateEquations.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-mathematicaldaysinyear
|
||||
u16 mathematical_days_in_year(i32 year)
|
||||
{
|
||||
// MathematicalDaysInYear(y)
|
||||
// = 365 if ((y) modulo 4) ≠ 0
|
||||
// = 366 if ((y) modulo 4) = 0 and ((y) modulo 100) ≠ 0
|
||||
// = 365 if ((y) modulo 100) = 0 and ((y) modulo 400) ≠ 0
|
||||
// = 366 if ((y) modulo 400) = 0
|
||||
if (modulo(year, 4) != 0)
|
||||
return 365;
|
||||
if (modulo(year, 4) == 0 && modulo(year, 100) != 0)
|
||||
return 366;
|
||||
if (modulo(year, 100) == 0 && modulo(year, 400) != 0)
|
||||
return 365;
|
||||
if (modulo(year, 400) == 0)
|
||||
return 366;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-mathematicalinleapyear
|
||||
u8 mathematical_in_leap_year(double time)
|
||||
{
|
||||
// MathematicalInLeapYear(t)
|
||||
// = 0 if MathematicalDaysInYear(EpochTimeToEpochYear(t)) = 365
|
||||
// = 1 if MathematicalDaysInYear(EpochTimeToEpochYear(t)) = 366
|
||||
auto days_in_year = mathematical_days_in_year(epoch_time_to_epoch_year(time));
|
||||
|
||||
if (days_in_year == 365)
|
||||
return 0;
|
||||
if (days_in_year == 366)
|
||||
return 1;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-EpochTimeToDayNumber
|
||||
double epoch_time_to_day_number(double time)
|
||||
{
|
||||
// EpochTimeToDayNumber(t) = floor(t / ℝ(msPerDay))
|
||||
return floor(time / JS::ms_per_day);
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochdaynumberforyear
|
||||
double epoch_day_number_for_year(double year)
|
||||
{
|
||||
// EpochDayNumberForYear(y) = 365 × (y - 1970) + floor((y - 1969) / 4) - floor((y - 1901) / 100) + floor((y - 1601) / 400)
|
||||
return 365.0 * (year - 1970.0) + floor((year - 1969.0) / 4.0) - floor((year - 1901.0) / 100.0) + floor((year - 1601.0) / 400.0);
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimeforyear
|
||||
double epoch_time_for_year(double year)
|
||||
{
|
||||
// EpochTimeForYear(y) = ℝ(msPerDay) × EpochDayNumberForYear(y)
|
||||
return ms_per_day * epoch_day_number_for_year(year);
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimetoepochyear
|
||||
i32 epoch_time_to_epoch_year(double time)
|
||||
{
|
||||
// EpochTimeToEpochYear(t) = the largest integral Number y (closest to +∞) such that EpochTimeForYear(y) ≤ t
|
||||
return JS::year_from_time(time);
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimetodayinyear
|
||||
u16 epoch_time_to_day_in_year(double time)
|
||||
{
|
||||
// EpochTimeToDayInYear(t) = EpochTimeToDayNumber(t) - EpochDayNumberForYear(EpochTimeToEpochYear(t))
|
||||
return static_cast<u16>(epoch_time_to_day_number(time) - epoch_day_number_for_year(epoch_time_to_epoch_year(time)));
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-epochtimetoweekday
|
||||
u8 epoch_time_to_week_day(double time)
|
||||
{
|
||||
// EpochTimeToWeekDay(t) = (EpochTimeToDayNumber(t) + 4) modulo 7
|
||||
return static_cast<u8>(modulo(epoch_time_to_day_number(time) + 4, 7.0));
|
||||
}
|
||||
|
||||
}
|
24
Libraries/LibJS/Runtime/Temporal/DateEquations.h
Normal file
24
Libraries/LibJS/Runtime/Temporal/DateEquations.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 13.3 Date Equations, https://tc39.es/proposal-temporal/#sec-date-equations
|
||||
|
||||
u16 mathematical_days_in_year(i32 year);
|
||||
u8 mathematical_in_leap_year(double time);
|
||||
double epoch_time_to_day_number(double time);
|
||||
double epoch_day_number_for_year(double year);
|
||||
double epoch_time_for_year(double year);
|
||||
i32 epoch_time_to_epoch_year(double time);
|
||||
u16 epoch_time_to_day_in_year(double time);
|
||||
u8 epoch_time_to_week_day(double time);
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -9,11 +9,42 @@
|
|||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
struct Annotation {
|
||||
bool critical { false };
|
||||
StringView key;
|
||||
StringView value;
|
||||
};
|
||||
|
||||
struct TimeZoneOffset {
|
||||
Optional<char> sign;
|
||||
Optional<StringView> hours;
|
||||
Optional<StringView> minutes;
|
||||
Optional<StringView> seconds;
|
||||
Optional<StringView> fraction;
|
||||
StringView source_text;
|
||||
};
|
||||
|
||||
struct ParseResult {
|
||||
Optional<char> sign;
|
||||
|
||||
Optional<StringView> date_year;
|
||||
Optional<StringView> date_month;
|
||||
Optional<StringView> date_day;
|
||||
Optional<StringView> time_hour;
|
||||
Optional<StringView> time_minute;
|
||||
Optional<StringView> time_second;
|
||||
Optional<StringView> time_fraction;
|
||||
Optional<TimeZoneOffset> date_time_offset;
|
||||
|
||||
Optional<StringView> utc_designator;
|
||||
Optional<StringView> time_zone_identifier;
|
||||
Optional<StringView> time_zone_iana_name;
|
||||
Optional<TimeZoneOffset> time_zone_offset;
|
||||
|
||||
Optional<StringView> duration_years;
|
||||
Optional<StringView> duration_months;
|
||||
Optional<StringView> duration_weeks;
|
||||
|
@ -24,12 +55,30 @@ struct ParseResult {
|
|||
Optional<StringView> duration_minutes_fraction;
|
||||
Optional<StringView> duration_seconds;
|
||||
Optional<StringView> duration_seconds_fraction;
|
||||
|
||||
Vector<Annotation> annotations;
|
||||
};
|
||||
|
||||
enum class Production {
|
||||
AnnotationValue,
|
||||
DateMonth,
|
||||
TemporalDateTimeString,
|
||||
TemporalDurationString,
|
||||
TemporalInstantString,
|
||||
TemporalMonthDayString,
|
||||
TemporalTimeString,
|
||||
TemporalYearMonthString,
|
||||
TemporalZonedDateTimeString,
|
||||
TimeZoneIdentifier,
|
||||
};
|
||||
|
||||
Optional<ParseResult> parse_iso8601(Production, StringView);
|
||||
|
||||
enum class SubMinutePrecision {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
Optional<TimeZoneOffset> parse_utc_offset(StringView, SubMinutePrecision);
|
||||
|
||||
}
|
||||
|
|
145
Libraries/LibJS/Runtime/Temporal/PlainDate.cpp
Normal file
145
Libraries/LibJS/Runtime/Temporal/PlainDate.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Checked.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 3.5.2 CreateISODateRecord ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-create-iso-date-record
|
||||
ISODate create_iso_date_record(double year, double month, double day)
|
||||
{
|
||||
// 1. Assert: IsValidISODate(year, month, day) is true.
|
||||
VERIFY(is_valid_iso_date(year, month, day));
|
||||
|
||||
// 2. Return ISO Date Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
|
||||
return { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
|
||||
}
|
||||
|
||||
// 3.5.6 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
|
||||
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow)
|
||||
{
|
||||
switch (overflow) {
|
||||
// 1. If overflow is CONSTRAIN, then
|
||||
case Overflow::Constrain:
|
||||
// a. Set month to the result of clamping month between 1 and 12.
|
||||
month = clamp(month, 1, 12);
|
||||
|
||||
// b. Let daysInMonth be ISODaysInMonth(year, month).
|
||||
// c. Set day to the result of clamping day between 1 and daysInMonth.
|
||||
day = clamp(day, 1, iso_days_in_month(year, month));
|
||||
|
||||
// AD-HOC: We perform the IsValidISODate check even for CONSTRAIN to ensure the year may be treated as a normal
|
||||
// integer. See the note in IsValidISODate for more details.
|
||||
[[fallthrough]];
|
||||
|
||||
// 2. Else,
|
||||
case Overflow::Reject:
|
||||
// a. Assert: overflow is REJECT.
|
||||
// b. If IsValidISODate(year, month, day) is false, throw a RangeError exception.
|
||||
if (!is_valid_iso_date(year, month, day))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. Return CreateISODateRecord(year, month, day).
|
||||
return create_iso_date_record(year, month, day);
|
||||
}
|
||||
|
||||
// 3.5.7 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
|
||||
bool is_valid_iso_date(double year, double month, double day)
|
||||
{
|
||||
// AD-HOC: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
|
||||
// This does not change the exposed behavior as the call to CreateISODateRecord will immediately check that
|
||||
// these values are valid ISO values (for years: -273975 - 273975, for months: 1 - 12, for days: 1 - 31),
|
||||
// all of which are subsets of this check.
|
||||
if (!AK::is_within_range<i32>(year) || !AK::is_within_range<u8>(month) || !AK::is_within_range<u8>(day))
|
||||
return false;
|
||||
|
||||
// 1. If month < 1 or month > 12, then
|
||||
if (month < 1 || month > 12) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Let daysInMonth be ISODaysInMonth(year, month).
|
||||
auto days_in_month = iso_days_in_month(year, month);
|
||||
|
||||
// 3. If day < 1 or day > daysInMonth, then
|
||||
if (day < 1 || day > days_in_month) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
|
||||
String pad_iso_year(i32 year)
|
||||
{
|
||||
// 1. If y ≥ 0 and y ≤ 9999, then
|
||||
if (year >= 0 && year <= 9999) {
|
||||
// a. Return ToZeroPaddedDecimalString(y, 4).
|
||||
return MUST(String::formatted("{:04}", year));
|
||||
}
|
||||
|
||||
// 2. If y > 0, let yearSign be "+"; otherwise, let yearSign be "-".
|
||||
auto year_sign = year > 0 ? '+' : '-';
|
||||
|
||||
// 3. Let year be ToZeroPaddedDecimalString(abs(y), 6).
|
||||
// 4. Return the string-concatenation of yearSign and year.
|
||||
return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
|
||||
}
|
||||
|
||||
// 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
|
||||
bool iso_date_within_limits(ISODate iso_date)
|
||||
{
|
||||
// 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, NoonTimeRecord()).
|
||||
auto iso_date_time = combine_iso_date_and_time_record(iso_date, noon_time_record());
|
||||
|
||||
// 2. Return ISODateTimeWithinLimits(isoDateTime).
|
||||
return iso_date_time_within_limits(iso_date_time);
|
||||
}
|
||||
|
||||
// 3.5.12 CompareISODate ( isoDate1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
|
||||
i8 compare_iso_date(ISODate iso_date1, ISODate iso_date2)
|
||||
{
|
||||
// 1. If isoDate1.[[Year]] > isoDate2.[[Year]], return 1.
|
||||
if (iso_date1.year > iso_date2.year)
|
||||
return 1;
|
||||
|
||||
// 2. If isoDate1.[[Year]] < isoDate2.[[Year]], return -1.
|
||||
if (iso_date1.year < iso_date2.year)
|
||||
return -1;
|
||||
|
||||
// 3. If isoDate1.[[Month]] > isoDate2.[[Month]], return 1.
|
||||
if (iso_date1.month > iso_date2.month)
|
||||
return 1;
|
||||
|
||||
// 4. If isoDate1.[[Month]] < isoDate2.[[Month]], return -1.
|
||||
if (iso_date1.month < iso_date2.month)
|
||||
return -1;
|
||||
|
||||
// 5. If isoDate1.[[Day]] > isoDate2.[[Day]], return 1.
|
||||
if (iso_date1.day > iso_date2.day)
|
||||
return 1;
|
||||
|
||||
// 6. If isoDate1.[[Day]] < isoDate2.[[Day]], return -1.
|
||||
if (iso_date1.day < iso_date2.day)
|
||||
return -1;
|
||||
|
||||
// 7. Return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
32
Libraries/LibJS/Runtime/Temporal/PlainDate.h
Normal file
32
Libraries/LibJS/Runtime/Temporal/PlainDate.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 3.5.1 ISO Date Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-date-records
|
||||
struct ISODate {
|
||||
i32 year { 0 };
|
||||
u8 month { 0 };
|
||||
u8 day { 0 };
|
||||
};
|
||||
|
||||
ISODate create_iso_date_record(double year, double month, double day);
|
||||
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow);
|
||||
bool is_valid_iso_date(double year, double month, double day);
|
||||
String pad_iso_year(i32 year);
|
||||
bool iso_date_within_limits(ISODate);
|
||||
i8 compare_iso_date(ISODate, ISODate);
|
||||
|
||||
}
|
55
Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp
Normal file
55
Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 5.5.3 CombineISODateAndTimeRecord ( isoDate, time ), https://tc39.es/proposal-temporal/#sec-temporal-combineisodateandtimerecord
|
||||
ISODateTime combine_iso_date_and_time_record(ISODate iso_date, Time time)
|
||||
{
|
||||
// 1. NOTE: time.[[Days]] is ignored.
|
||||
// 2. Return ISO Date-Time Record { [[ISODate]]: isoDate, [[Time]]: time }.
|
||||
return { .iso_date = iso_date, .time = time };
|
||||
}
|
||||
|
||||
// nsMinInstant - nsPerDay
|
||||
static auto const DATETIME_NANOSECONDS_MIN = "-8640000086400000000000"_sbigint;
|
||||
|
||||
// nsMaxInstant + nsPerDay
|
||||
static auto const DATETIME_NANOSECONDS_MAX = "8640000086400000000000"_sbigint;
|
||||
|
||||
// 5.5.4 ISODateTimeWithinLimits ( isoDateTime ), https://tc39.es/proposal-temporal/#sec-temporal-isodatetimewithinlimits
|
||||
bool iso_date_time_within_limits(ISODateTime iso_date_time)
|
||||
{
|
||||
// 1. If abs(ISODateToEpochDays(isoDateTime.[[ISODate]].[[Year]], isoDateTime.[[ISODate]].[[Month]] - 1, isoDateTime.[[ISODate]].[[Day]])) > 10**8 + 1, return false.
|
||||
if (fabs(iso_date_to_epoch_days(iso_date_time.iso_date.year, iso_date_time.iso_date.month - 1, iso_date_time.iso_date.day)) > 100000001)
|
||||
return false;
|
||||
|
||||
// 2. Let ns be ℝ(GetUTCEpochNanoseconds(isoDateTime)).
|
||||
auto nanoseconds = get_utc_epoch_nanoseconds(iso_date_time);
|
||||
|
||||
// 3. If ns ≤ nsMinInstant - nsPerDay, then
|
||||
if (nanoseconds <= DATETIME_NANOSECONDS_MIN) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. If ns ≥ nsMaxInstant + nsPerDay, then
|
||||
if (nanoseconds >= DATETIME_NANOSECONDS_MAX) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
25
Libraries/LibJS/Runtime/Temporal/PlainDateTime.h
Normal file
25
Libraries/LibJS/Runtime/Temporal/PlainDateTime.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 5.5.1 ISO Date-Time Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-date-time-records
|
||||
struct ISODateTime {
|
||||
ISODate iso_date;
|
||||
Time time;
|
||||
};
|
||||
|
||||
ISODateTime combine_iso_date_and_time_record(ISODate, Time);
|
||||
bool iso_date_time_within_limits(ISODateTime);
|
||||
|
||||
}
|
170
Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp
Normal file
170
Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Intrinsics.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PlainMonthDay);
|
||||
|
||||
// 10 Temporal.PlainMonthDay Objects, https://tc39.es/proposal-temporal/#sec-temporal-plainmonthday-objects
|
||||
PlainMonthDay::PlainMonthDay(ISODate iso_date, String calendar, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_iso_date(iso_date)
|
||||
, m_calendar(move(calendar))
|
||||
{
|
||||
}
|
||||
|
||||
// 10.5.1 ToTemporalMonthDay ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalmonthday
|
||||
ThrowCompletionOr<GC::Ref<PlainMonthDay>> to_temporal_month_day(VM& vm, Value item, Value options)
|
||||
{
|
||||
// 1. If options is not present, set options to undefined.
|
||||
|
||||
// 2. If item is a Object, then
|
||||
if (item.is_object()) {
|
||||
auto const& object = item.as_object();
|
||||
|
||||
// a. If item has an [[InitializedTemporalMonthDay]] internal slot, then
|
||||
if (is<PlainMonthDay>(object)) {
|
||||
auto const& plain_month_day = static_cast<PlainMonthDay const&>(object);
|
||||
|
||||
// i. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||||
TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// iii. Return ! CreateTemporalMonthDay(item.[[ISODate]], item.[[Calendar]]).
|
||||
return MUST(create_temporal_month_day(vm, plain_month_day.iso_date(), plain_month_day.calendar()));
|
||||
}
|
||||
|
||||
// b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
|
||||
auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object));
|
||||
|
||||
// c. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE, DAY », «», «»).
|
||||
auto fields = TRY(prepare_calendar_fields(vm, calendar, object, { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day } }, {}, CalendarFieldList {}));
|
||||
|
||||
// d. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||||
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// f. Let isoDate be ? CalendarMonthDayFromFields(calendar, fields, overflow).
|
||||
auto iso_date = TRY(calendar_month_day_from_fields(vm, calendar, move(fields), overflow));
|
||||
|
||||
// g. Return ! CreateTemporalMonthDay(isoDate, calendar).
|
||||
return MUST(create_temporal_month_day(vm, iso_date, move(calendar)));
|
||||
}
|
||||
|
||||
// 3. If item is not a String, throw a TypeError exception.
|
||||
if (!item.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidPlainMonthDay);
|
||||
|
||||
// 4. Let result be ? ParseISODateTime(item, « TemporalMonthDayString »).
|
||||
auto parse_result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalMonthDayString } }));
|
||||
|
||||
// 5. Let calendar be result.[[Calendar]].
|
||||
// 6. If calendar is empty, set calendar to "iso8601".
|
||||
auto calendar = parse_result.calendar.value_or("iso8601"_string);
|
||||
|
||||
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
|
||||
calendar = TRY(canonicalize_calendar(vm, calendar));
|
||||
|
||||
// 8. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||||
TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// 10. If result.[[Year]] is empty, then
|
||||
if (!parse_result.year.has_value()) {
|
||||
// a. Assert: calendar is "iso8601".
|
||||
VERIFY(calendar == "iso8601"sv);
|
||||
|
||||
// b. Let referenceISOYear be 1972 (the first ISO 8601 leap year after the epoch).
|
||||
static constexpr i32 reference_iso_year = 1972;
|
||||
|
||||
// c. Let isoDate be CreateISODateRecord(referenceISOYear, result.[[Month]], result.[[Day]]).
|
||||
auto iso_date = create_iso_date_record(reference_iso_year, parse_result.month, parse_result.day);
|
||||
|
||||
// d. Return ! CreateTemporalMonthDay(isoDate, calendar).
|
||||
return MUST(create_temporal_month_day(vm, iso_date, move(calendar)));
|
||||
}
|
||||
|
||||
// 11. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
|
||||
auto iso_date = create_iso_date_record(*parse_result.year, parse_result.month, parse_result.day);
|
||||
|
||||
// 12. Set result to ISODateToFields(calendar, isoDate, MONTH-DAY).
|
||||
auto result = iso_date_to_fields(calendar, iso_date, DateType::MonthDay);
|
||||
|
||||
// 13. NOTE: The following operation is called with CONSTRAIN regardless of the value of overflow, in order for the
|
||||
// calendar to store a canonical value in the [[Year]] field of the [[ISODate]] internal slot of the result.
|
||||
// 14. Set isoDate to ? CalendarMonthDayFromFields(calendar, result, CONSTRAIN).
|
||||
iso_date = TRY(calendar_month_day_from_fields(vm, calendar, result, Overflow::Constrain));
|
||||
|
||||
// 15. Return ! CreateTemporalMonthDay(isoDate, calendar).
|
||||
return MUST(create_temporal_month_day(vm, iso_date, move(calendar)));
|
||||
}
|
||||
|
||||
// 10.5.2 CreateTemporalMonthDay ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalmonthday
|
||||
ThrowCompletionOr<GC::Ref<PlainMonthDay>> create_temporal_month_day(VM& vm, ISODate iso_date, String calendar, GC::Ptr<FunctionObject> new_target)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. If ISODateWithinLimits(isoDate) is false, throw a RangeError exception.
|
||||
if (!iso_date_within_limits(iso_date))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainMonthDay);
|
||||
|
||||
// 2. If newTarget is not present, set newTarget to %Temporal.PlainMonthDay%.
|
||||
if (!new_target)
|
||||
new_target = realm.intrinsics().temporal_plain_month_day_constructor();
|
||||
|
||||
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainMonthDay.prototype%", « [[InitializedTemporalMonthDay]], [[ISODate]], [[Calendar]] »).
|
||||
// 4. Set object.[[ISODate]] to isoDate.
|
||||
// 5. Set object.[[Calendar]] to calendar.
|
||||
auto object = TRY(ordinary_create_from_constructor<PlainMonthDay>(vm, *new_target, &Intrinsics::temporal_plain_month_day_prototype, iso_date, move(calendar)));
|
||||
|
||||
// 6. Return object.
|
||||
return object;
|
||||
}
|
||||
|
||||
// 10.5.3 TemporalMonthDayToString ( monthDay, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporalmonthdaytostring
|
||||
String temporal_month_day_to_string(PlainMonthDay const& month_day, ShowCalendar show_calendar)
|
||||
{
|
||||
// 1. Let month be ToZeroPaddedDecimalString(monthDay.[[ISODate]].[[Month]], 2).
|
||||
// 2. Let day be ToZeroPaddedDecimalString(monthDay.[[ISODate]].[[Day]], 2).
|
||||
// 3. Let result be the string-concatenation of month, the code unit 0x002D (HYPHEN-MINUS), and day.
|
||||
auto result = MUST(String::formatted("{:02}-{:02}", month_day.iso_date().month, month_day.iso_date().day));
|
||||
|
||||
// 4. If showCalendar is one of ALWAYS or CRITICAL, or if monthDay.[[Calendar]] is not "iso8601", then
|
||||
if (show_calendar == ShowCalendar::Always || show_calendar == ShowCalendar::Critical || month_day.calendar() != "iso8601"sv) {
|
||||
// a. Let year be PadISOYear(monthDay.[[ISODate]].[[Year]]).
|
||||
auto year = pad_iso_year(month_day.iso_date().year);
|
||||
|
||||
// b. Set result to the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and result.
|
||||
result = MUST(String::formatted("{}-{}", year, result));
|
||||
}
|
||||
|
||||
// 5. Let calendarString be FormatCalendarAnnotation(monthDay.[[Calendar]], showCalendar).
|
||||
auto calendar_string = format_calendar_annotation(month_day.calendar(), show_calendar);
|
||||
|
||||
// 6. Set result to the string-concatenation of result and calendarString.
|
||||
result = MUST(String::formatted("{}{}", result, calendar_string));
|
||||
|
||||
// 7. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
39
Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h
Normal file
39
Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class PlainMonthDay final : public Object {
|
||||
JS_OBJECT(PlainMonthDay, Object);
|
||||
GC_DECLARE_ALLOCATOR(PlainMonthDay);
|
||||
|
||||
public:
|
||||
virtual ~PlainMonthDay() override = default;
|
||||
|
||||
[[nodiscard]] ISODate iso_date() const { return m_iso_date; }
|
||||
[[nodiscard]] String const& calendar() const { return m_calendar; }
|
||||
|
||||
private:
|
||||
PlainMonthDay(ISODate, String calendar, Object& prototype);
|
||||
|
||||
ISODate m_iso_date; // [[ISODate]]
|
||||
String m_calendar; // [[Calendar]]
|
||||
};
|
||||
|
||||
ThrowCompletionOr<GC::Ref<PlainMonthDay>> to_temporal_month_day(VM&, Value item, Value options = js_undefined());
|
||||
ThrowCompletionOr<GC::Ref<PlainMonthDay>> create_temporal_month_day(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
|
||||
String temporal_month_day_to_string(PlainMonthDay const&, ShowCalendar);
|
||||
|
||||
}
|
102
Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.cpp
Normal file
102
Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PlainMonthDayConstructor);
|
||||
|
||||
// 10.1 The Temporal.PlainMonthDay Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plainmonthday-constructor
|
||||
PlainMonthDayConstructor::PlainMonthDayConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.PlainMonthDay.as_string(), realm.intrinsics().function_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void PlainMonthDayConstructor::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 10.2.1 Temporal.PlainMonthDay.prototype, https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype
|
||||
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_plain_month_day_prototype(), 0);
|
||||
|
||||
define_direct_property(vm.names.length, Value(2), Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.from, from, 1, attr);
|
||||
}
|
||||
|
||||
// 10.1.1 Temporal.PlainMonthDay ( isoMonth, isoDay [ , calendar [ , referenceISOYear ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday
|
||||
ThrowCompletionOr<Value> PlainMonthDayConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.PlainMonthDay");
|
||||
}
|
||||
|
||||
// 10.1.1 Temporal.PlainMonthDay ( isoMonth, isoDay [ , calendar [ , referenceISOYear ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday
|
||||
ThrowCompletionOr<GC::Ref<Object>> PlainMonthDayConstructor::construct(FunctionObject& new_target)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto iso_month = vm.argument(0);
|
||||
auto iso_day = vm.argument(1);
|
||||
auto calendar_value = vm.argument(2);
|
||||
auto reference_iso_year = vm.argument(3);
|
||||
|
||||
// 2. If referenceISOYear is undefined, then
|
||||
if (reference_iso_year.is_undefined()) {
|
||||
// a. Set referenceISOYear to 1972𝔽 (the first ISO 8601 leap year after the epoch).
|
||||
reference_iso_year = Value { 1972 };
|
||||
}
|
||||
|
||||
// 3. Let m be ? ToIntegerWithTruncation(isoMonth).
|
||||
auto month = TRY(to_integer_with_truncation(vm, iso_month, ErrorType::TemporalInvalidPlainMonthDay));
|
||||
|
||||
// 4. Let d be ? ToIntegerWithTruncation(isoDay).
|
||||
auto day = TRY(to_integer_with_truncation(vm, iso_day, ErrorType::TemporalInvalidPlainMonthDay));
|
||||
|
||||
// 5. If calendar is undefined, set calendar to "iso8601".
|
||||
if (calendar_value.is_undefined())
|
||||
calendar_value = PrimitiveString::create(vm, "iso8601"_string);
|
||||
|
||||
// 6. If calendar is not a String, throw a TypeError exception.
|
||||
if (!calendar_value.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAString, calendar_value);
|
||||
|
||||
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
|
||||
auto calendar = TRY(canonicalize_calendar(vm, calendar_value.as_string().utf8_string_view()));
|
||||
|
||||
// 8. Let y be ? ToIntegerWithTruncation(referenceISOYear).
|
||||
auto year = TRY(to_integer_with_truncation(vm, reference_iso_year, ErrorType::TemporalInvalidPlainMonthDay));
|
||||
|
||||
// 9. If IsValidISODate(y, m, d) is false, throw a RangeError exception.
|
||||
if (!is_valid_iso_date(year, month, day))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainMonthDay);
|
||||
|
||||
// 10. Let isoDate be CreateISODateRecord(y, m, d).
|
||||
auto iso_date = create_iso_date_record(year, month, day);
|
||||
|
||||
// 11. Return ? CreateTemporalMonthDay(isoDate, calendar, NewTarget).
|
||||
return TRY(create_temporal_month_day(vm, iso_date, move(calendar), &new_target));
|
||||
}
|
||||
|
||||
// 10.2.2 Temporal.PlainMonthDay.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.from
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayConstructor::from)
|
||||
{
|
||||
// 1. Return ? ToTemporalMonthDay(item, options).
|
||||
return TRY(to_temporal_month_day(vm, vm.argument(0), vm.argument(1)));
|
||||
}
|
||||
|
||||
}
|
33
Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.h
Normal file
33
Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class PlainMonthDayConstructor final : public NativeFunction {
|
||||
JS_OBJECT(PlainMonthDayConstructor, NativeFunction);
|
||||
GC_DECLARE_ALLOCATOR(PlainMonthDayConstructor);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~PlainMonthDayConstructor() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target) override;
|
||||
|
||||
private:
|
||||
explicit PlainMonthDayConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(from);
|
||||
};
|
||||
|
||||
}
|
183
Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp
Normal file
183
Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(PlainMonthDayPrototype);
|
||||
|
||||
// 10.3 Properties of the Temporal.PlainMonthDay Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-plainmonthday-prototype-object
|
||||
PlainMonthDayPrototype::PlainMonthDayPrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void PlainMonthDayPrototype::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 10.3.2 Temporal.PlainMonthDay.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype-%symbol.tostringtag%
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.PlainMonthDay"_string), Attribute::Configurable);
|
||||
|
||||
define_native_accessor(realm, vm.names.calendarId, calendar_id_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.monthCode, month_code_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.day, day_getter, {}, Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.with, with, 1, attr);
|
||||
define_native_function(realm, vm.names.equals, equals, 1, attr);
|
||||
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);
|
||||
}
|
||||
|
||||
// 10.3.3 get Temporal.PlainMonthDay.prototype.calendarId, https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.calendarid
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::calendar_id_getter)
|
||||
{
|
||||
// 1. Let monthDay be the this value
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return monthDay.[[Calendar]].
|
||||
return PrimitiveString::create(vm, month_day->calendar());
|
||||
}
|
||||
|
||||
// 10.3.4 get Temporal.PlainMonthDay.prototype.monthCode, https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.monthcode
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::month_code_getter)
|
||||
{
|
||||
// 1. Let monthDay be the this value
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return CalendarISOToDate(monthDay.[[Calendar]], monthDay.[[ISODate]]).[[MonthCode]].
|
||||
return PrimitiveString::create(vm, calendar_iso_to_date(month_day->calendar(), month_day->iso_date()).month_code);
|
||||
}
|
||||
|
||||
// 10.3.5 get Temporal.PlainMonthDay.prototype.day, https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.day
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::day_getter)
|
||||
{
|
||||
// 1. Let monthDay be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return 𝔽(CalendarISOToDate(monthDay.[[Calendar]], monthDay.[[ISODate]]).[[Day]]).
|
||||
return calendar_iso_to_date(month_day->calendar(), month_day->iso_date()).day;
|
||||
}
|
||||
|
||||
// 10.3.6 Temporal.PlainMonthDay.prototype.with ( temporalMonthDayLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.with
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::with)
|
||||
{
|
||||
auto temporal_month_day_like = vm.argument(0);
|
||||
auto options = vm.argument(1);
|
||||
|
||||
// 1. Let monthDay be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If ? IsPartialTemporalObject(temporalMonthDayLike) is false, throw a TypeError exception.
|
||||
if (!TRY(is_partial_temporal_object(vm, temporal_month_day_like)))
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalObjectMustBePartialTemporalObject);
|
||||
|
||||
// 4. Let calendar be monthDay.[[Calendar]].
|
||||
auto const& calendar = month_day->calendar();
|
||||
|
||||
// 5. Let fields be ISODateToFields(calendar, monthDay.[[ISODate]], MONTH-DAY).
|
||||
auto fields = iso_date_to_fields(calendar, month_day->iso_date(), DateType::MonthDay);
|
||||
|
||||
// 6. Let partialMonthDay be ? PrepareCalendarFields(calendar, temporalMonthDayLike, « YEAR, MONTH, MONTH-CODE, DAY », « », PARTIAL).
|
||||
auto partial_month_day = TRY(prepare_calendar_fields(vm, calendar, temporal_month_day_like.as_object(), { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day } }, {}, Partial {}));
|
||||
|
||||
// 7. Set fields to CalendarMergeFields(calendar, fields, partialMonthDay).
|
||||
fields = calendar_merge_fields(calendar, fields, partial_month_day);
|
||||
|
||||
// 8. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, options));
|
||||
|
||||
// 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||||
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
||||
|
||||
// 10. Let isoDate be ? CalendarMonthDayFromFields(calendar, fields, overflow).
|
||||
auto iso_date = TRY(calendar_month_day_from_fields(vm, calendar, fields, overflow));
|
||||
|
||||
// 11. Return ! CreateTemporalMonthDay(isoDate, calendar).
|
||||
return MUST(create_temporal_month_day(vm, iso_date, calendar));
|
||||
}
|
||||
|
||||
// 10.3.7 Temporal.PlainMonthDay.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.equals
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::equals)
|
||||
{
|
||||
// 1. Let monthDay be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Set other to ? ToTemporalMonthDay(other).
|
||||
auto other = TRY(to_temporal_month_day(vm, vm.argument(0)));
|
||||
|
||||
// 4. If CompareISODate(monthDay.[[ISODate]], other.[[ISODate]]) ≠ 0, return false.
|
||||
if (compare_iso_date(month_day->iso_date(), other->iso_date()) != 0)
|
||||
return false;
|
||||
|
||||
// 5. Return CalendarEquals(monthDay.[[Calendar]], other.[[Calendar]]).
|
||||
return calendar_equals(month_day->calendar(), other->calendar());
|
||||
}
|
||||
|
||||
// 10.3.8 Temporal.PlainMonthDay.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tostring
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_string)
|
||||
{
|
||||
// 1. Let monthDay be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
|
||||
|
||||
// 4. Let showCalendar be ? GetTemporalShowCalendarNameOption(resolvedOptions).
|
||||
auto show_calendar = TRY(get_temporal_show_calendar_name_option(vm, resolved_options));
|
||||
|
||||
// 5. Return TemporalMonthDayToString(monthDay, showCalendar).
|
||||
return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, show_calendar));
|
||||
}
|
||||
|
||||
// 10.3.9 Temporal.PlainMonthDay.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
|
||||
// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_locale_string)
|
||||
{
|
||||
// 1. Let monthDay be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return TemporalMonthDayToString(monthDay, auto).
|
||||
return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, ShowCalendar::Auto));
|
||||
}
|
||||
|
||||
// 10.3.10 Temporal.PlainMonthDay.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_json)
|
||||
{
|
||||
// 1. Let monthDay be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(monthDay, [[InitializedTemporalMonthDay]]).
|
||||
auto month_day = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return TemporalMonthDayToString(monthDay, auto).
|
||||
return PrimitiveString::create(vm, temporal_month_day_to_string(month_day, ShowCalendar::Auto));
|
||||
}
|
||||
|
||||
// 10.3.11 Temporal.PlainMonthDay.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.valueof
|
||||
JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::value_of)
|
||||
{
|
||||
// 1. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::Convert, "Temporal.PlainMonthDay", "a primitive value");
|
||||
}
|
||||
|
||||
}
|
37
Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h
Normal file
37
Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class PlainMonthDayPrototype final : public PrototypeObject<PlainMonthDayPrototype, PlainMonthDay> {
|
||||
JS_PROTOTYPE_OBJECT(PlainMonthDayPrototype, PlainMonthDay, Temporal.PlainMonthDay);
|
||||
GC_DECLARE_ALLOCATOR(PlainMonthDayPrototype);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~PlainMonthDayPrototype() override = default;
|
||||
|
||||
private:
|
||||
explicit PlainMonthDayPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(calendar_id_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(month_code_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(day_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(with);
|
||||
JS_DECLARE_NATIVE_FUNCTION(equals);
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
83
Libraries/LibJS/Runtime/Temporal/PlainTime.cpp
Normal file
83
Libraries/LibJS/Runtime/Temporal/PlainTime.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 4.5.2 CreateTimeRecord ( hour, minute, second, millisecond, microsecond, nanosecond [ , deltaDays ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtimerecord
|
||||
Time create_time_record(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, double delta_days)
|
||||
{
|
||||
// 1. If deltaDays is not present, set deltaDays to 0.
|
||||
// 2. Assert: IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond).
|
||||
VERIFY(is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond));
|
||||
|
||||
// 3. Return Time Record { [[Days]]: deltaDays, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond }.
|
||||
return {
|
||||
.days = delta_days,
|
||||
.hour = static_cast<u8>(hour),
|
||||
.minute = static_cast<u8>(minute),
|
||||
.second = static_cast<u8>(second),
|
||||
.millisecond = static_cast<u16>(millisecond),
|
||||
.microsecond = static_cast<u16>(microsecond),
|
||||
.nanosecond = static_cast<u16>(nanosecond),
|
||||
};
|
||||
}
|
||||
|
||||
// 4.5.4 NoonTimeRecord ( ), https://tc39.es/proposal-temporal/#sec-temporal-noontimerecord
|
||||
Time noon_time_record()
|
||||
{
|
||||
// 1. Return Time Record { [[Days]]: 0, [[Hour]]: 12, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }.
|
||||
return { .days = 0, .hour = 12, .minute = 0, .second = 0, .millisecond = 0, .microsecond = 0, .nanosecond = 0 };
|
||||
}
|
||||
|
||||
// 4.5.9 IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidtime
|
||||
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond)
|
||||
{
|
||||
// 1. If hour < 0 or hour > 23, then
|
||||
if (hour < 0 || hour > 23) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. If minute < 0 or minute > 59, then
|
||||
if (minute < 0 || minute > 59) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. If second < 0 or second > 59, then
|
||||
if (second < 0 || second > 59) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. If millisecond < 0 or millisecond > 999, then
|
||||
if (millisecond < 0 || millisecond > 999) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. If microsecond < 0 or microsecond > 999, then
|
||||
if (microsecond < 0 || microsecond > 999) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. If nanosecond < 0 or nanosecond > 999, then
|
||||
if (nanosecond < 0 || nanosecond > 999) {
|
||||
// a. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 7. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
30
Libraries/LibJS/Runtime/Temporal/PlainTime.h
Normal file
30
Libraries/LibJS/Runtime/Temporal/PlainTime.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 4.5.1 Time Records, https://tc39.es/proposal-temporal/#sec-temporal-time-records
|
||||
struct Time {
|
||||
double days { 0 };
|
||||
u8 hour { 0 };
|
||||
u8 minute { 0 };
|
||||
u8 second { 0 };
|
||||
u16 millisecond { 0 };
|
||||
u16 microsecond { 0 };
|
||||
u16 nanosecond { 0 };
|
||||
};
|
||||
|
||||
Time create_time_record(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, double delta_days = 0);
|
||||
Time noon_time_record();
|
||||
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/Temporal.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
@ -30,6 +31,7 @@ void Temporal::initialize(Realm& realm)
|
|||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_intrinsic_accessor(vm.names.Duration, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_duration_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
131
Libraries/LibJS/Runtime/Temporal/TimeZone.cpp
Normal file
131
Libraries/LibJS/Runtime/Temporal/TimeZone.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 1. If offsetMinutes ≥ 0, let sign be the code unit 0x002B (PLUS SIGN); otherwise, let sign be the code unit 0x002D (HYPHEN-MINUS).
|
||||
auto sign = offset_minutes >= 0 ? '+' : '-';
|
||||
|
||||
// 2. Let absoluteMinutes be abs(offsetMinutes).
|
||||
auto absolute_minutes = abs(offset_minutes);
|
||||
|
||||
// 3. Let hour be floor(absoluteMinutes / 60).
|
||||
auto hour = static_cast<u8>(floor(static_cast<double>(absolute_minutes) / 60.0));
|
||||
|
||||
// 4. Let minute be absoluteMinutes modulo 60.
|
||||
auto minute = static_cast<u8>(modulo(static_cast<double>(absolute_minutes), 60.0));
|
||||
|
||||
// 5. Let timeString be FormatTimeString(hour, minute, 0, 0, MINUTE, style).
|
||||
auto time_string = format_time_string(hour, minute, 0, 0, SecondsStringPrecision::Minute {}, style);
|
||||
|
||||
// 6. Return the string-concatenation of sign and timeString.
|
||||
return MUST(String::formatted("{}{}", sign, time_string));
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 1. If temporalTimeZoneLike is an Object, then
|
||||
if (temporal_time_zone_like.is_object()) {
|
||||
// FIXME: a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||
// FIXME: i. Return temporalTimeZoneLike.[[TimeZone]].
|
||||
}
|
||||
|
||||
// 2. If temporalTimeZoneLike is not a String, throw a TypeError exception.
|
||||
if (!temporal_time_zone_like.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidTimeZoneName, temporal_time_zone_like);
|
||||
|
||||
// 3. Let parseResult be ? ParseTemporalTimeZoneString(temporalTimeZoneLike).
|
||||
auto parse_result = TRY(parse_temporal_time_zone_string(vm, temporal_time_zone_like.as_string().utf8_string_view()));
|
||||
|
||||
// 4. Let offsetMinutes be parseResult.[[OffsetMinutes]].
|
||||
// 5. If offsetMinutes is not empty, return FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
||||
if (parse_result.offset_minutes.has_value())
|
||||
return format_offset_time_zone_identifier(*parse_result.offset_minutes);
|
||||
|
||||
// 6. Let name be parseResult.[[Name]].
|
||||
// 7. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(name).
|
||||
auto time_zone_identifier_record = Intl::get_available_named_time_zone_identifier(*parse_result.name);
|
||||
|
||||
// 8. If timeZoneIdentifierRecord is empty, throw a RangeError exception.
|
||||
if (!time_zone_identifier_record.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneName, temporal_time_zone_like);
|
||||
|
||||
// 9. Return timeZoneIdentifierRecord.[[Identifier]].
|
||||
return time_zone_identifier_record->identifier;
|
||||
}
|
||||
|
||||
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
||||
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM& vm, StringView identifier)
|
||||
{
|
||||
// 1. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneIdentifier).
|
||||
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, identifier);
|
||||
|
||||
// 2. If parseResult is a List of errors, throw a RangeError exception.
|
||||
if (!parse_result.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneString, identifier);
|
||||
|
||||
return parse_time_zone_identifier(*parse_result);
|
||||
}
|
||||
|
||||
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
||||
TimeZone parse_time_zone_identifier(StringView identifier)
|
||||
{
|
||||
// OPTIMIZATION: Some callers can assume that parsing will succeed.
|
||||
|
||||
// 1. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneIdentifier).
|
||||
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, identifier);
|
||||
VERIFY(parse_result.has_value());
|
||||
|
||||
return parse_time_zone_identifier(*parse_result);
|
||||
}
|
||||
|
||||
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
||||
TimeZone parse_time_zone_identifier(ParseResult const& parse_result)
|
||||
{
|
||||
// OPTIMIZATION: Some callers will have already parsed and validated the time zone identifier.
|
||||
|
||||
// 3. If parseResult contains a TimeZoneIANAName Parse Node, then
|
||||
if (parse_result.time_zone_iana_name.has_value()) {
|
||||
// a. Let name be the source text matched by the TimeZoneIANAName Parse Node contained within parseResult.
|
||||
// b. NOTE: name is syntactically valid, but does not necessarily conform to IANA Time Zone Database naming
|
||||
// guidelines or correspond with an available named time zone identifier.
|
||||
// c. Return the Record { [[Name]]: CodePointsToString(name), [[OffsetMinutes]]: empty }.
|
||||
return TimeZone { .name = String::from_utf8_without_validation(parse_result.time_zone_iana_name->bytes()), .offset_minutes = {} };
|
||||
}
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Assert: parseResult contains a UTCOffset[~SubMinutePrecision] Parse Node.
|
||||
VERIFY(parse_result.time_zone_offset.has_value());
|
||||
|
||||
// b. Let offsetString be the source text matched by the UTCOffset[~SubMinutePrecision] Parse Node contained within parseResult.
|
||||
// c. Let offsetNanoseconds be ! ParseDateTimeUTCOffset(offsetString).
|
||||
auto offset_nanoseconds = parse_date_time_utc_offset(parse_result.time_zone_offset->source_text);
|
||||
|
||||
// d. Let offsetMinutes be offsetNanoseconds / (60 × 10**9).
|
||||
auto offset_minutes = offset_nanoseconds / 60'000'000'000;
|
||||
|
||||
// e. Assert: offsetMinutes is an integer.
|
||||
VERIFY(trunc(offset_minutes) == offset_minutes);
|
||||
|
||||
// f. Return the Record { [[Name]]: empty, [[OffsetMinutes]]: offsetMinutes }.
|
||||
return TimeZone { .name = {}, .offset_minutes = static_cast<i64>(offset_minutes) };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
28
Libraries/LibJS/Runtime/Temporal/TimeZone.h
Normal file
28
Libraries/LibJS/Runtime/Temporal/TimeZone.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
struct TimeZone {
|
||||
Optional<String> name;
|
||||
Optional<i64> offset_minutes;
|
||||
};
|
||||
|
||||
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> = {});
|
||||
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like);
|
||||
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM&, StringView identifier);
|
||||
TimeZone parse_time_zone_identifier(StringView identifier);
|
||||
TimeZone parse_time_zone_identifier(ParseResult const&);
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainMonthDay.from).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("fields object argument", () => {
|
||||
const object = {
|
||||
month: 7,
|
||||
day: 6,
|
||||
};
|
||||
const plainMonthDay = Temporal.PlainMonthDay.from(object);
|
||||
expect(plainMonthDay.monthCode).toBe("M07");
|
||||
expect(plainMonthDay.day).toBe(6);
|
||||
});
|
||||
|
||||
test("from month day string", () => {
|
||||
const plainMonthDay = Temporal.PlainMonthDay.from("--07-06");
|
||||
expect(plainMonthDay.monthCode).toBe("M07");
|
||||
expect(plainMonthDay.day).toBe(6);
|
||||
});
|
||||
|
||||
test("from date time string", () => {
|
||||
const plainMonthDay = Temporal.PlainMonthDay.from("2021-07-06T23:42:01");
|
||||
expect(plainMonthDay.monthCode).toBe("M07");
|
||||
expect(plainMonthDay.day).toBe(6);
|
||||
});
|
||||
|
||||
test("compares calendar name in month day string in lowercase", () => {
|
||||
const values = [
|
||||
"02-10[u-ca=iso8601]",
|
||||
"02-10[u-ca=isO8601]",
|
||||
"02-10[u-ca=iSo8601]",
|
||||
"02-10[u-ca=iSO8601]",
|
||||
"02-10[u-ca=Iso8601]",
|
||||
"02-10[u-ca=IsO8601]",
|
||||
"02-10[u-ca=ISo8601]",
|
||||
"02-10[u-ca=ISO8601]",
|
||||
];
|
||||
|
||||
for (const value of values) {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from(value);
|
||||
}).not.toThrowWithMessage(
|
||||
RangeError,
|
||||
"MM-DD string format can only be used with the iso8601 calendar"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("missing fields", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from({});
|
||||
}).toThrowWithMessage(TypeError, "Required property day is missing or undefined");
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from({ month: 1 });
|
||||
}).toThrowWithMessage(TypeError, "Required property day is missing or undefined");
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from({ day: 1 });
|
||||
}).toThrowWithMessage(TypeError, "Required property month is missing or undefined");
|
||||
});
|
||||
|
||||
test("invalid month day string", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from("foo");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
|
||||
test("string must not contain a UTC designator", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from("2021-07-06T23:42:01Z");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
|
||||
test("extended year must not be negative zero", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from("-000000-01-01");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
|
||||
test("can only use iso8601 calendar with month day strings", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from("02-10[u-ca=iso8602]");
|
||||
}).toThrowWithMessage(RangeError, "Invalid calendar identifier 'iso8602'");
|
||||
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.from("02-10[u-ca=ladybird]");
|
||||
}).toThrowWithMessage(RangeError, "Invalid calendar identifier 'ladybird'");
|
||||
});
|
||||
|
||||
test("doesn't throw non-iso8601 calendar error when using a superset format string such as DateTime", () => {
|
||||
// NOTE: This will still throw, but only because "ladybird" is not a recognised calendar, not because of the string format restriction.
|
||||
try {
|
||||
Temporal.PlainMonthDay.from("2023-02-10T22:56[u-ca=ladybird]");
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(RangeError);
|
||||
expect(e.message).not.toBe(
|
||||
"MM-DD string format can only be used with the iso8601 calendar"
|
||||
);
|
||||
expect(e.message).toBe("Invalid calendar identifier 'ladybird'");
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay();
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"Temporal.PlainMonthDay constructor must be called with 'new'"
|
||||
);
|
||||
});
|
||||
|
||||
test("cannot pass Infinity", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1, undefined, Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(-Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, -Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1, undefined, -Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
});
|
||||
|
||||
test("cannot pass invalid ISO month/day", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(0, 1);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 0);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
});
|
||||
|
||||
test("not within iso date time limit", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(9, 30, "iso8601", 999_999_999_999_999);
|
||||
}).toThrowWithMessage(RangeError, "Invalid plain month day");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(Temporal.PlainMonthDay).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(typeof plainMonthDay).toBe("object");
|
||||
expect(plainMonthDay).toBeInstanceOf(Temporal.PlainMonthDay);
|
||||
expect(Object.getPrototypeOf(plainMonthDay)).toBe(Temporal.PlainMonthDay.prototype);
|
||||
});
|
||||
|
||||
// FIXME: Re-implement this test with Temporal.PlainMonthDay.prototype.toString({ calendarName: "always" }).
|
||||
// test("default reference year is 1972", () => {
|
||||
// const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
// const fields = plainMonthDay.getISOFields();
|
||||
// expect(fields.isoYear).toBe(1972);
|
||||
// });
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("calendarId basic functionality", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(5, 1, "iso8601");
|
||||
expect(plainMonthDay.calendarId).toBe("iso8601");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainMonthDay.prototype, "calendarId", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(plainMonthDay.day).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainMonthDay.prototype, "day", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainMonthDay.prototype.equals).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const firstPlainMonthDay = new Temporal.PlainMonthDay(2, 1, "iso8601");
|
||||
const secondPlainMonthDay = new Temporal.PlainMonthDay(1, 1, "iso8601");
|
||||
expect(firstPlainMonthDay.equals(firstPlainMonthDay)).toBeTrue();
|
||||
expect(secondPlainMonthDay.equals(secondPlainMonthDay)).toBeTrue();
|
||||
expect(firstPlainMonthDay.equals(secondPlainMonthDay)).toBeFalse();
|
||||
expect(secondPlainMonthDay.equals(firstPlainMonthDay)).toBeFalse();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(plainMonthDay.monthCode).toBe("M07");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.PlainMonthDay.prototype, "monthCode", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainMonthDay.prototype.toJSON).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
let plainMonthDay;
|
||||
|
||||
plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(plainMonthDay.toJSON()).toBe("07-06");
|
||||
|
||||
plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
|
||||
expect(plainMonthDay.toJSON()).toBe("2021-07-06[u-ca=gregory]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.prototype.toJSON.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainMonthDay.prototype.toLocaleString).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
let plainMonthDay;
|
||||
|
||||
plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(plainMonthDay.toLocaleString()).toBe("07-06");
|
||||
|
||||
plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
|
||||
expect(plainMonthDay.toLocaleString()).toBe("2021-07-06[u-ca=gregory]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.prototype.toLocaleString.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.PlainMonthDay.prototype.toString).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
let plainMonthDay;
|
||||
|
||||
plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(plainMonthDay.toString()).toBe("07-06");
|
||||
expect(plainMonthDay.toString({ calendarName: "auto" })).toBe("07-06");
|
||||
expect(plainMonthDay.toString({ calendarName: "always" })).toBe("1972-07-06[u-ca=iso8601]");
|
||||
expect(plainMonthDay.toString({ calendarName: "never" })).toBe("07-06");
|
||||
expect(plainMonthDay.toString({ calendarName: "critical" })).toBe(
|
||||
"1972-07-06[!u-ca=iso8601]"
|
||||
);
|
||||
|
||||
plainMonthDay = new Temporal.PlainMonthDay(7, 6, "gregory", 2021);
|
||||
expect(plainMonthDay.toString()).toBe("2021-07-06[u-ca=gregory]");
|
||||
expect(plainMonthDay.toString({ calendarName: "auto" })).toBe("2021-07-06[u-ca=gregory]");
|
||||
expect(plainMonthDay.toString({ calendarName: "always" })).toBe("2021-07-06[u-ca=gregory]");
|
||||
expect(plainMonthDay.toString({ calendarName: "never" })).toBe("2021-07-06");
|
||||
expect(plainMonthDay.toString({ calendarName: "critical" })).toBe(
|
||||
"2021-07-06[!u-ca=gregory]"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.prototype.toString.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
|
||||
test("calendarName option must be one of 'auto', 'always', 'never', 'critical'", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(() => {
|
||||
plainMonthDay.toString({ calendarName: "foo" });
|
||||
}).toThrowWithMessage(RangeError, "foo is not a valid value for option calendarName");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
describe("errors", () => {
|
||||
test("throws TypeError", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(7, 6);
|
||||
expect(() => {
|
||||
plainMonthDay.valueOf();
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"Cannot convert Temporal.PlainMonthDay to a primitive value"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.PlainMonthDay.prototype.with).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(1, 1);
|
||||
const values = [
|
||||
[{ monthCode: "M07" }, new Temporal.PlainMonthDay(7, 1)],
|
||||
[{ monthCode: "M07", day: 6 }, new Temporal.PlainMonthDay(7, 6)],
|
||||
[{ year: 0, month: 7, day: 6 }, new Temporal.PlainMonthDay(7, 6)],
|
||||
];
|
||||
for (const [arg, expected] of values) {
|
||||
expect(plainMonthDay.with(arg).equals(expected)).toBeTrue();
|
||||
}
|
||||
|
||||
// Supplying the same values doesn't change the month/day, but still creates a new object
|
||||
const plainMonthDayLike = {
|
||||
month: plainMonthDay.month,
|
||||
day: plainMonthDay.day,
|
||||
};
|
||||
expect(plainMonthDay.with(plainMonthDayLike)).not.toBe(plainMonthDay);
|
||||
expect(plainMonthDay.with(plainMonthDayLike).equals(plainMonthDay)).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.PlainMonthDay object", () => {
|
||||
expect(() => {
|
||||
Temporal.PlainMonthDay.prototype.with.call("foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainMonthDay");
|
||||
});
|
||||
|
||||
test("argument must be an object", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1).with("foo");
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1).with(42);
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("argument must have at least one Temporal property", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1).with({});
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
|
||||
test("argument must not have 'calendar' or 'timeZone'", () => {
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1).with({ calendar: {} });
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
expect(() => {
|
||||
new Temporal.PlainMonthDay(1, 1).with({ timeZone: {} });
|
||||
}).toThrowWithMessage(TypeError, "Object must be a partial Temporal object");
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue