mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
LibJS: Implement the Temporal.Duration constructor
This also includes a stubbed Temporal.Duration.prototype. Until we have re-implemented Temporal.PlainDate/ZonedDateTime, some of Temporal.Duration.compare (and its invoked AOs) are left unimplemented.
This commit is contained in:
parent
eca378a7a3
commit
5fe0d3352d
Notes:
github-actions[bot]
2024-11-21 00:06:22 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/5fe0d3352da Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2431 Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/shannonbooth ✅
30 changed files with 2143 additions and 26 deletions
|
@ -206,7 +206,10 @@ set(SOURCES
|
|||
Runtime/SymbolPrototype.cpp
|
||||
Runtime/Temporal/AbstractOperations.cpp
|
||||
Runtime/Temporal/Duration.cpp
|
||||
Runtime/Temporal/DurationConstructor.cpp
|
||||
Runtime/Temporal/DurationPrototype.cpp
|
||||
Runtime/Temporal/Instant.cpp
|
||||
Runtime/Temporal/ISO8601.cpp
|
||||
Runtime/Temporal/Temporal.cpp
|
||||
Runtime/TypedArray.cpp
|
||||
Runtime/TypedArrayConstructor.cpp
|
||||
|
|
|
@ -87,7 +87,8 @@
|
|||
__JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \
|
||||
__JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor)
|
||||
|
||||
#define JS_ENUMERATE_TEMPORAL_OBJECTS
|
||||
#define JS_ENUMERATE_TEMPORAL_OBJECTS \
|
||||
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor)
|
||||
|
||||
#define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \
|
||||
__JS_ENUMERATE(AtomicsObject, atomics) \
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include <LibJS/Runtime/Shape.h>
|
||||
#include <LibJS/Runtime/StringObject.h>
|
||||
#include <LibJS/Runtime/StringPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibJS/Runtime/WeakMap.h>
|
||||
|
@ -827,6 +828,13 @@ ErrorOr<void> print_intl_duration_format(JS::PrintContext& print_context, JS::In
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_temporal_duration(JS::PrintContext& print_context, JS::Temporal::Duration const& duration, HashTable<JS::Object*>&)
|
||||
{
|
||||
TRY(print_type(print_context, "Temporal.Duration"sv));
|
||||
TRY(js_out(print_context, " \033[34;1m{} y, {} M, {} w, {} d, {} h, {} m, {} s, {} ms, {} us, {} ns\033[0m", duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()));
|
||||
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));
|
||||
|
@ -942,6 +950,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
|
|||
return print_intl_segments(print_context, static_cast<JS::Intl::Segments&>(object), seen_objects);
|
||||
if (is<JS::Intl::DurationFormat>(object))
|
||||
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);
|
||||
return print_object(print_context, object, seen_objects);
|
||||
}
|
||||
|
||||
|
|
|
@ -286,7 +286,7 @@
|
|||
M(TemporalOnlyISO8601WithMonthDayString, "MM-DD string format can only be used with the iso8601 calendar") \
|
||||
M(TemporalOnlyISO8601WithYearMonthString, "YYYY-MM string format can only be used with the iso8601 calendar") \
|
||||
M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \
|
||||
M(TemporalMissingStartingPoint, "A starting point is required for balancing {}") \
|
||||
M(TemporalMissingStartingPoint, "A starting point is required for comparing {}") \
|
||||
M(TemporalMissingUnits, "One or both of smallestUnit or largestUnit is required") \
|
||||
M(TemporalNanosecondsConvertedToDaysWithOppositeSign, "Time zone or calendar converted nanoseconds into a number of days with " \
|
||||
"the opposite sign") \
|
||||
|
|
|
@ -99,6 +99,8 @@
|
|||
#include <LibJS/Runtime/SuppressedErrorPrototype.h>
|
||||
#include <LibJS/Runtime/SymbolConstructor.h>
|
||||
#include <LibJS/Runtime/SymbolPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/Temporal.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/TypedArrayConstructor.h>
|
||||
|
|
|
@ -7,11 +7,300 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-units
|
||||
struct TemporalUnit {
|
||||
Unit value;
|
||||
StringView singular_property_name;
|
||||
StringView plural_property_name;
|
||||
UnitCategory category;
|
||||
RoundingIncrement maximum_duration_rounding_increment;
|
||||
};
|
||||
static auto temporal_units = to_array<TemporalUnit>({
|
||||
{ Unit::Year, "year"sv, "years"sv, UnitCategory::Date, Unset {} },
|
||||
{ Unit::Month, "month"sv, "months"sv, UnitCategory::Date, Unset {} },
|
||||
{ Unit::Week, "week"sv, "weeks"sv, UnitCategory::Date, Unset {} },
|
||||
{ Unit::Day, "day"sv, "days"sv, UnitCategory::Date, Unset {} },
|
||||
{ Unit::Hour, "hour"sv, "hours"sv, UnitCategory::Time, 24 },
|
||||
{ Unit::Minute, "minute"sv, "minutes"sv, UnitCategory::Time, 60 },
|
||||
{ Unit::Second, "second"sv, "seconds"sv, UnitCategory::Time, 60 },
|
||||
{ Unit::Millisecond, "millisecond"sv, "milliseconds"sv, UnitCategory::Time, 1000 },
|
||||
{ Unit::Microsecond, "microsecond"sv, "microseconds"sv, UnitCategory::Time, 1000 },
|
||||
{ Unit::Nanosecond, "nanosecond"sv, "nanoseconds"sv, UnitCategory::Time, 1000 },
|
||||
});
|
||||
|
||||
StringView temporal_unit_to_string(Unit unit)
|
||||
{
|
||||
return temporal_units[to_underlying(unit)].singular_property_name;
|
||||
}
|
||||
|
||||
// 13.18 GetTemporalRelativeToOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalrelativetooption
|
||||
ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object const& options)
|
||||
{
|
||||
// 1. Let value be ? Get(options, "relativeTo").
|
||||
auto value = TRY(options.get(vm.names.relativeTo));
|
||||
|
||||
// 2. If value is undefined, return the Record { [[PlainRelativeTo]]: undefined, [[ZonedRelativeTo]]: undefined }.
|
||||
if (value.is_undefined())
|
||||
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} };
|
||||
|
||||
// FIXME: Implement the remaining steps of this AO when we have implemented PlainRelativeTo and ZonedRelativeTo.
|
||||
return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} };
|
||||
}
|
||||
|
||||
// 13.20 IsCalendarUnit ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-iscalendarunit
|
||||
bool is_calendar_unit(Unit unit)
|
||||
{
|
||||
// 1. If unit is year, return true.
|
||||
if (unit == Unit::Year)
|
||||
return true;
|
||||
|
||||
// 2. If unit is month, return true.
|
||||
if (unit == Unit::Month)
|
||||
return true;
|
||||
|
||||
// 3. If unit is week, return true.
|
||||
if (unit == Unit::Week)
|
||||
return true;
|
||||
|
||||
// 4. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 13.21 TemporalUnitCategory ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-temporalunitcategory
|
||||
UnitCategory temporal_unit_category(Unit unit)
|
||||
{
|
||||
// 1. Return the value from the "Category" column of the row of Table 21 in which unit is in the "Value" column.
|
||||
return temporal_units[to_underlying(unit)].category;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 1. Let duration be ParseText(StringToCodePoints(isoString), TemporalDurationString).
|
||||
auto parse_result = parse_iso8601(Production::TemporalDurationString, iso_string);
|
||||
|
||||
// 2. If duration is a List of errors, throw a RangeError exception.
|
||||
if (!parse_result.has_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDurationString, iso_string);
|
||||
|
||||
// 3. Let sign be the source text matched by the ASCIISign Parse Node contained within duration, or an empty sequence
|
||||
// of code points if not present.
|
||||
auto sign = parse_result->sign;
|
||||
|
||||
// 4. If duration contains a DurationYearsPart Parse Node, then
|
||||
// a. Let yearsNode be that DurationYearsPart Parse Node contained within duration.
|
||||
// b. Let years be the source text matched by the DecimalDigits Parse Node contained within yearsNode.
|
||||
// 5. Else,
|
||||
// a. Let years be an empty sequence of code points.
|
||||
auto years = parse_result->duration_years.value_or({});
|
||||
|
||||
// 6. If duration contains a DurationMonthsPart Parse Node, then
|
||||
// a. Let monthsNode be the DurationMonthsPart Parse Node contained within duration.
|
||||
// b. Let months be the source text matched by the DecimalDigits Parse Node contained within monthsNode.
|
||||
// 7. Else,
|
||||
// a. Let months be an empty sequence of code points.
|
||||
auto months = parse_result->duration_months.value_or({});
|
||||
|
||||
// 8. If duration contains a DurationWeeksPart Parse Node, then
|
||||
// a. Let weeksNode be the DurationWeeksPart Parse Node contained within duration.
|
||||
// b. Let weeks be the source text matched by the DecimalDigits Parse Node contained within weeksNode.
|
||||
// 9. Else,
|
||||
// a. Let weeks be an empty sequence of code points.
|
||||
auto weeks = parse_result->duration_weeks.value_or({});
|
||||
|
||||
// 10. If duration contains a DurationDaysPart Parse Node, then
|
||||
// a. Let daysNode be the DurationDaysPart Parse Node contained within duration.
|
||||
// b. Let days be the source text matched by the DecimalDigits Parse Node contained within daysNode.
|
||||
// 11. Else,
|
||||
// a. Let days be an empty sequence of code points.
|
||||
auto days = parse_result->duration_days.value_or({});
|
||||
|
||||
// 12. If duration contains a DurationHoursPart Parse Node, then
|
||||
// a. Let hoursNode be the DurationHoursPart Parse Node contained within duration.
|
||||
// b. Let hours be the source text matched by the DecimalDigits Parse Node contained within hoursNode.
|
||||
// c. Let fHours be the source text matched by the TemporalDecimalFraction Parse Node contained within
|
||||
// hoursNode, or an empty sequence of code points if not present.
|
||||
// 13. Else,
|
||||
// a. Let hours be an empty sequence of code points.
|
||||
// b. Let fHours be an empty sequence of code points.
|
||||
auto hours = parse_result->duration_hours.value_or({});
|
||||
auto fractional_hours = parse_result->duration_hours_fraction.value_or({});
|
||||
|
||||
// 14. If duration contains a DurationMinutesPart Parse Node, then
|
||||
// a. Let minutesNode be the DurationMinutesPart Parse Node contained within duration.
|
||||
// b. Let minutes be the source text matched by the DecimalDigits Parse Node contained within minutesNode.
|
||||
// c. Let fMinutes be the source text matched by the TemporalDecimalFraction Parse Node contained within
|
||||
// minutesNode, or an empty sequence of code points if not present.
|
||||
// 15. Else,
|
||||
// a. Let minutes be an empty sequence of code points.
|
||||
// b. Let fMinutes be an empty sequence of code points.
|
||||
auto minutes = parse_result->duration_minutes.value_or({});
|
||||
auto fractional_minutes = parse_result->duration_minutes_fraction.value_or({});
|
||||
|
||||
// 16. If duration contains a DurationSecondsPart Parse Node, then
|
||||
// a. Let secondsNode be the DurationSecondsPart Parse Node contained within duration.
|
||||
// b. Let seconds be the source text matched by the DecimalDigits Parse Node contained within secondsNode.
|
||||
// c. Let fSeconds be the source text matched by the TemporalDecimalFraction Parse Node contained within
|
||||
// secondsNode, or an empty sequence of code points if not present.
|
||||
// 17. Else,
|
||||
// a. Let seconds be an empty sequence of code points.
|
||||
// b. Let fSeconds be an empty sequence of code points.
|
||||
auto seconds = parse_result->duration_seconds.value_or({});
|
||||
auto fractional_seconds = parse_result->duration_seconds_fraction.value_or({});
|
||||
|
||||
// 18. Let yearsMV be ? ToIntegerWithTruncation(CodePointsToString(years)).
|
||||
auto years_value = TRY(to_integer_with_truncation(vm, years, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
|
||||
// 19. Let monthsMV be ? ToIntegerWithTruncation(CodePointsToString(months)).
|
||||
auto months_value = TRY(to_integer_with_truncation(vm, months, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
|
||||
// 20. Let weeksMV be ? ToIntegerWithTruncation(CodePointsToString(weeks)).
|
||||
auto weeks_value = TRY(to_integer_with_truncation(vm, weeks, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
|
||||
// 21. Let daysMV be ? ToIntegerWithTruncation(CodePointsToString(days)).
|
||||
auto days_value = TRY(to_integer_with_truncation(vm, days, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
|
||||
// 22. Let hoursMV be ? ToIntegerWithTruncation(CodePointsToString(hours)).
|
||||
auto hours_value = TRY(to_integer_with_truncation(vm, hours, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
|
||||
Crypto::BigFraction minutes_value;
|
||||
Crypto::BigFraction seconds_value;
|
||||
Crypto::BigFraction milliseconds_value;
|
||||
|
||||
auto remainder_one = [](Crypto::BigFraction const& value) {
|
||||
// FIXME: We should add a generic remainder() method to BigFraction, or a method equivalent to modf(). But for
|
||||
// now, since we know we are only dividing by powers of 10, we can implement a very situationally specific
|
||||
// method to extract the fractional part of the BigFraction.
|
||||
auto res = value.numerator().divided_by(value.denominator());
|
||||
return Crypto::BigFraction { move(res.remainder), value.denominator() };
|
||||
};
|
||||
|
||||
// 23. If fHours is not empty, then
|
||||
if (!fractional_hours.is_empty()) {
|
||||
// a. Assert: minutes, fMinutes, seconds, and fSeconds are empty.
|
||||
VERIFY(minutes.is_empty());
|
||||
VERIFY(fractional_minutes.is_empty());
|
||||
VERIFY(seconds.is_empty());
|
||||
VERIFY(fractional_seconds.is_empty());
|
||||
|
||||
// b. Let fHoursDigits be the substring of CodePointsToString(fHours) from 1.
|
||||
auto fractional_hours_digits = fractional_hours.substring_view(1);
|
||||
|
||||
// c. Let fHoursScale be the length of fHoursDigits.
|
||||
auto fractional_hours_scale = fractional_hours_digits.length();
|
||||
|
||||
// d. Let minutesMV be ? ToIntegerWithTruncation(fHoursDigits) / 10**fHoursScale × 60.
|
||||
auto minutes_integer = TRY(to_integer_with_truncation(vm, fractional_hours_digits, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
minutes_value = Crypto::BigFraction { minutes_integer } / Crypto::BigFraction { pow(10.0, fractional_hours_scale) } * Crypto::BigFraction { 60.0 };
|
||||
}
|
||||
// 24. Else,
|
||||
else {
|
||||
// a. Let minutesMV be ? ToIntegerWithTruncation(CodePointsToString(minutes)).
|
||||
auto minutes_integer = TRY(to_integer_with_truncation(vm, minutes, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
minutes_value = Crypto::BigFraction { minutes_integer };
|
||||
}
|
||||
|
||||
// 25. If fMinutes is not empty, then
|
||||
if (!fractional_minutes.is_empty()) {
|
||||
// a. Assert: seconds and fSeconds are empty.
|
||||
VERIFY(seconds.is_empty());
|
||||
VERIFY(fractional_seconds.is_empty());
|
||||
|
||||
// b. Let fMinutesDigits be the substring of CodePointsToString(fMinutes) from 1.
|
||||
auto fractional_minutes_digits = fractional_minutes.substring_view(1);
|
||||
|
||||
// c. Let fMinutesScale be the length of fMinutesDigits.
|
||||
auto fractional_minutes_scale = fractional_minutes_digits.length();
|
||||
|
||||
// d. Let secondsMV be ? ToIntegerWithTruncation(fMinutesDigits) / 10**fMinutesScale × 60.
|
||||
auto seconds_integer = TRY(to_integer_with_truncation(vm, fractional_minutes_digits, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
seconds_value = Crypto::BigFraction { seconds_integer } / Crypto::BigFraction { pow(10.0, fractional_minutes_scale) } * Crypto::BigFraction { 60.0 };
|
||||
}
|
||||
// 26. Else if seconds is not empty, then
|
||||
else if (!seconds.is_empty()) {
|
||||
// a. Let secondsMV be ? ToIntegerWithTruncation(CodePointsToString(seconds)).
|
||||
auto seconds_integer = TRY(to_integer_with_truncation(vm, seconds, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
seconds_value = Crypto::BigFraction { seconds_integer };
|
||||
}
|
||||
// 27. Else,
|
||||
else {
|
||||
// a. Let secondsMV be remainder(minutesMV, 1) × 60.
|
||||
seconds_value = remainder_one(minutes_value) * Crypto::BigFraction { 60.0 };
|
||||
}
|
||||
|
||||
// 28. 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 fSecondsScale be the length of fSecondsDigits.
|
||||
auto fractional_seconds_scale = fractional_seconds_digits.length();
|
||||
|
||||
// c. Let millisecondsMV be ? ToIntegerWithTruncation(fSecondsDigits) / 10**fSecondsScale × 1000.
|
||||
auto milliseconds_integer = TRY(to_integer_with_truncation(vm, fractional_seconds_digits, ErrorType::TemporalInvalidDurationString, iso_string));
|
||||
milliseconds_value = Crypto::BigFraction { milliseconds_integer } / Crypto::BigFraction { pow(10.0, fractional_seconds_scale) } * Crypto::BigFraction { 1000.0 };
|
||||
|
||||
}
|
||||
// 29. Else,
|
||||
else {
|
||||
// a. Let millisecondsMV be remainder(secondsMV, 1) × 1000.
|
||||
milliseconds_value = remainder_one(seconds_value) * Crypto::BigFraction { 1000.0 };
|
||||
}
|
||||
|
||||
// 30. Let microsecondsMV be remainder(millisecondsMV, 1) × 1000.
|
||||
auto microseconds_value = remainder_one(milliseconds_value) * Crypto::BigFraction { 1000.0 };
|
||||
|
||||
// 31. Let nanosecondsMV be remainder(microsecondsMV, 1) × 1000.
|
||||
auto nanoseconds_value = remainder_one(microseconds_value) * Crypto::BigFraction { 1000.0 };
|
||||
|
||||
// 32. If sign contains the code point U+002D (HYPHEN-MINUS), then
|
||||
// a. Let factor be -1.
|
||||
// 33. Else,
|
||||
// a. Let factor be 1.
|
||||
i8 factor = sign == '-' ? -1 : 1;
|
||||
|
||||
// 34. Set yearsMV to yearsMV × factor.
|
||||
years_value *= factor;
|
||||
|
||||
// 35. Set monthsMV to monthsMV × factor.
|
||||
months_value *= factor;
|
||||
|
||||
// 36. Set weeksMV to weeksMV × factor.
|
||||
weeks_value *= factor;
|
||||
|
||||
// 37. Set daysMV to daysMV × factor.
|
||||
days_value *= factor;
|
||||
|
||||
// 38. Set hoursMV to hoursMV × factor.
|
||||
hours_value *= factor;
|
||||
|
||||
// 39. Set minutesMV to floor(minutesMV) × factor.
|
||||
auto factored_minutes_value = floor(minutes_value.to_double()) * factor;
|
||||
|
||||
// 40. Set secondsMV to floor(secondsMV) × factor.
|
||||
auto factored_seconds_value = floor(seconds_value.to_double()) * factor;
|
||||
|
||||
// 41. Set millisecondsMV to floor(millisecondsMV) × factor.
|
||||
auto factored_milliseconds_value = floor(milliseconds_value.to_double()) * factor;
|
||||
|
||||
// 42. Set microsecondsMV to floor(microsecondsMV) × factor.
|
||||
auto factored_microseconds_value = floor(microseconds_value.to_double()) * factor;
|
||||
|
||||
// 43. Set nanosecondsMV to floor(nanosecondsMV) × factor.
|
||||
auto factored_nanoseconds_value = floor(nanoseconds_value.to_double()) * factor;
|
||||
|
||||
// 44. Return ? CreateTemporalDuration(yearsMV, monthsMV, weeksMV, daysMV, hoursMV, minutesMV, secondsMV, millisecondsMV, microsecondsMV, nanosecondsMV).
|
||||
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));
|
||||
}
|
||||
|
||||
// 14.4.1.1 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
|
||||
ThrowCompletionOr<GC::Ref<Object>> get_options_object(VM& vm, Value options)
|
||||
{
|
||||
|
|
|
@ -14,9 +14,84 @@
|
|||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-units
|
||||
enum class Unit {
|
||||
Year,
|
||||
Month,
|
||||
Week,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
Millisecond,
|
||||
Microsecond,
|
||||
Nanosecond,
|
||||
};
|
||||
StringView temporal_unit_to_string(Unit);
|
||||
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-units
|
||||
enum class UnitCategory {
|
||||
Date,
|
||||
Time,
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-units
|
||||
enum class UnitGroup {
|
||||
Date,
|
||||
Time,
|
||||
DateTime,
|
||||
};
|
||||
|
||||
struct Unset { };
|
||||
using RoundingIncrement = Variant<Unset, u64>;
|
||||
|
||||
struct RelativeTo {
|
||||
// FIXME: Make these objects represent their actual types when we re-implement them.
|
||||
GC::Ptr<JS::Object> plain_relative_to; // [[PlainRelativeTo]]
|
||||
GC::Ptr<JS::Object> zoned_relative_to; // [[ZonedRelativeTo]]
|
||||
};
|
||||
|
||||
ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM&, Object const& options);
|
||||
bool is_calendar_unit(Unit);
|
||||
UnitCategory temporal_unit_category(Unit);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);
|
||||
|
||||
// 13.38 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation
|
||||
template<typename... Args>
|
||||
ThrowCompletionOr<double> to_integer_with_truncation(VM& vm, Value argument, ErrorType error_type, Args&&... args)
|
||||
{
|
||||
// 1. Let number be ? ToNumber(argument).
|
||||
auto number = TRY(argument.to_number(vm));
|
||||
|
||||
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception.
|
||||
if (number.is_nan() || number.is_infinity())
|
||||
return vm.throw_completion<RangeError>(error_type, forward<Args>(args)...);
|
||||
|
||||
// 3. Return truncate(ℝ(number)).
|
||||
return trunc(number.as_double());
|
||||
}
|
||||
|
||||
// 13.38 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation
|
||||
// AD-HOC: We often need to use this AO when we have a parsed StringView. This overload allows callers to avoid creating
|
||||
// a PrimitiveString for the primary definition.
|
||||
template<typename... Args>
|
||||
ThrowCompletionOr<double> to_integer_with_truncation(VM& vm, StringView argument, ErrorType error_type, Args&&... args)
|
||||
{
|
||||
// 1. Let number be ? ToNumber(argument).
|
||||
auto number = string_to_number(argument);
|
||||
|
||||
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception.
|
||||
if (isnan(number) || isinf(number))
|
||||
return vm.throw_completion<RangeError>(error_type, forward<Args>(args)...);
|
||||
|
||||
// 3. Return truncate(ℝ(number)).
|
||||
return trunc(number);
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
|
|
@ -9,13 +9,220 @@
|
|||
|
||||
#include <AK/Math.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Intrinsics.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(Duration);
|
||||
|
||||
// 7 Temporal.Duration Objects, https://tc39.es/proposal-temporal/#sec-temporal-duration-objects
|
||||
Duration::Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_years(years)
|
||||
, m_months(months)
|
||||
, m_weeks(weeks)
|
||||
, m_days(days)
|
||||
, m_hours(hours)
|
||||
, m_minutes(minutes)
|
||||
, m_seconds(seconds)
|
||||
, m_milliseconds(milliseconds)
|
||||
, m_microseconds(microseconds)
|
||||
, m_nanoseconds(nanoseconds)
|
||||
{
|
||||
auto fields = AK::Array {
|
||||
&Duration::m_years,
|
||||
&Duration::m_months,
|
||||
&Duration::m_weeks,
|
||||
&Duration::m_days,
|
||||
&Duration::m_hours,
|
||||
&Duration::m_minutes,
|
||||
&Duration::m_seconds,
|
||||
&Duration::m_milliseconds,
|
||||
&Duration::m_microseconds,
|
||||
&Duration::m_nanoseconds,
|
||||
};
|
||||
|
||||
// NOTE: The spec stores these fields as mathematical values. VERIFY() that we have finite, integral values in them,
|
||||
// and normalize any negative zeros caused by floating point math. This is usually done using ℝ(𝔽(value)) at
|
||||
// the call site.
|
||||
for (auto const& field : fields) {
|
||||
auto& value = this->*field;
|
||||
VERIFY(isfinite(value));
|
||||
// FIXME: test-js contains a small number of cases where a Temporal.Duration is constructed with a non-integral
|
||||
// double. Eliminate these and VERIFY(trunc(value) == value) instead.
|
||||
if (trunc(value) != value)
|
||||
value = trunc(value);
|
||||
else if (bit_cast<u64>(value) == NEGATIVE_ZERO_BITS)
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// maxTimeDuration = 2**53 × 10**9 - 1 = 9,007,199,254,740,991,999,999,999
|
||||
TimeDuration const MAX_TIME_DURATION = "9007199254740991999999999"_sbigint;
|
||||
|
||||
// 7.5.4 ZeroDateDuration ( ), https://tc39.es/proposal-temporal/#sec-temporal-zerodateduration
|
||||
DateDuration zero_date_duration(VM& vm)
|
||||
{
|
||||
// 1. Return ! CreateDateDurationRecord(0, 0, 0, 0).
|
||||
return MUST(create_date_duration_record(vm, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
// 7.5.5 ToInternalDurationRecord ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecord
|
||||
InternalDuration to_internal_duration_record(VM& vm, Duration const& duration)
|
||||
{
|
||||
// 1. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]]).
|
||||
auto date_duration = MUST(create_date_duration_record(vm, duration.years(), duration.months(), duration.weeks(), duration.days()));
|
||||
|
||||
// 2. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]).
|
||||
auto time_duration = time_duration_from_components(duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds());
|
||||
|
||||
// 3. Return ! CombineDateAndTimeDuration(dateDuration, timeDuration).
|
||||
return MUST(combine_date_and_time_duration(vm, date_duration, move(time_duration)));
|
||||
}
|
||||
|
||||
// 7.5.9 CreateDateDurationRecord ( years, months, weeks, days ), https://tc39.es/proposal-temporal/#sec-temporal-createdatedurationrecord
|
||||
ThrowCompletionOr<DateDuration> create_date_duration_record(VM& vm, double years, double months, double weeks, double days)
|
||||
{
|
||||
// 1. If IsValidDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
|
||||
if (!is_valid_duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
|
||||
|
||||
// 2. Return Date Duration Record { [[Years]]: ℝ(𝔽(years)), [[Months]]: ℝ(𝔽(months)), [[Weeks]]: ℝ(𝔽(weeks)), [[Days]]: ℝ(𝔽(days)) }.
|
||||
return DateDuration { years, months, weeks, days };
|
||||
}
|
||||
|
||||
// 7.5.11 CombineDateAndTimeDuration ( dateDuration, timeDuration ), https://tc39.es/proposal-temporal/#sec-temporal-combinedateandtimeduration
|
||||
ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM& vm, DateDuration date_duration, TimeDuration time_duration)
|
||||
{
|
||||
// 1. Let dateSign be DateDurationSign(dateDuration).
|
||||
auto date_sign = date_duration_sign(date_duration);
|
||||
|
||||
// 2. Let timeSign be TimeDurationSign(timeDuration).
|
||||
auto time_sign = time_duration_sign(time_duration);
|
||||
|
||||
// 3. If dateSign ≠ 0 and timeSign ≠ 0 and dateSign ≠ timeSign, throw a RangeError exception.
|
||||
if (date_sign != 0 && time_sign != 0 && date_sign != time_sign)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
|
||||
|
||||
// 4. Return Internal Duration Record { [[Date]]: dateDuration, [[Time]]: timeDuration }.
|
||||
return InternalDuration { date_duration, move(time_duration) };
|
||||
}
|
||||
|
||||
// 7.5.12 ToTemporalDuration ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration
|
||||
ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM& vm, Value item)
|
||||
{
|
||||
// 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then
|
||||
if (item.is_object() && is<Duration>(item.as_object())) {
|
||||
auto const& duration = static_cast<Duration const&>(item.as_object());
|
||||
|
||||
// a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]).
|
||||
return MUST(create_temporal_duration(vm, duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()));
|
||||
}
|
||||
|
||||
// 2. If item is not an Object, then
|
||||
if (!item.is_object()) {
|
||||
// a. If item is not a String, throw a TypeError exception.
|
||||
if (!item.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAString, item);
|
||||
|
||||
// b. Return ? ParseTemporalDurationString(item).
|
||||
return TRY(parse_temporal_duration_string(vm, item.as_string().utf8_string_view()));
|
||||
}
|
||||
|
||||
// 3. Let result be a new Partial Duration Record with each field set to 0.
|
||||
auto result = PartialDuration::zero();
|
||||
|
||||
// 4. Let partial be ? ToTemporalPartialDurationRecord(item).
|
||||
auto partial = TRY(to_temporal_partial_duration_record(vm, item));
|
||||
|
||||
// 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]].
|
||||
if (partial.years.has_value())
|
||||
result.years = *partial.years;
|
||||
|
||||
// 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]].
|
||||
if (partial.months.has_value())
|
||||
result.months = *partial.months;
|
||||
|
||||
// 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]].
|
||||
if (partial.weeks.has_value())
|
||||
result.weeks = *partial.weeks;
|
||||
|
||||
// 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]].
|
||||
if (partial.days.has_value())
|
||||
result.days = *partial.days;
|
||||
|
||||
// 9. If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]].
|
||||
if (partial.hours.has_value())
|
||||
result.hours = *partial.hours;
|
||||
|
||||
// 10. If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]].
|
||||
if (partial.minutes.has_value())
|
||||
result.minutes = *partial.minutes;
|
||||
|
||||
// 11. If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]].
|
||||
if (partial.seconds.has_value())
|
||||
result.seconds = *partial.seconds;
|
||||
|
||||
// 12. If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]].
|
||||
if (partial.milliseconds.has_value())
|
||||
result.milliseconds = *partial.milliseconds;
|
||||
|
||||
// 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]].
|
||||
if (partial.microseconds.has_value())
|
||||
result.microseconds = *partial.microseconds;
|
||||
|
||||
// 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]].
|
||||
if (partial.nanoseconds.has_value())
|
||||
result.nanoseconds = *partial.nanoseconds;
|
||||
|
||||
// 15. Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
|
||||
return TRY(create_temporal_duration(vm, *result.years, *result.months, *result.weeks, *result.days, *result.hours, *result.minutes, *result.seconds, *result.milliseconds, *result.microseconds, *result.nanoseconds));
|
||||
}
|
||||
|
||||
// 7.5.13 DurationSign ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-durationsign
|
||||
i8 duration_sign(Duration const& duration)
|
||||
{
|
||||
// 1. For each value v of « duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]] », do
|
||||
for (auto value : { duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds() }) {
|
||||
// a. If v < 0, return -1.
|
||||
if (value < 0)
|
||||
return -1;
|
||||
|
||||
// b. If v > 0, return 1.
|
||||
if (value > 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. Return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 7.5.14 DateDurationSign ( dateDuration ), https://tc39.es/proposal-temporal/#sec-temporal-datedurationsign
|
||||
i8 date_duration_sign(DateDuration const& date_duration)
|
||||
{
|
||||
// 1. For each value v of « dateDuration.[[Years]], dateDuration.[[Months]], dateDuration.[[Weeks]], dateDuration.[[Days]] », do
|
||||
for (auto value : { date_duration.years, date_duration.months, date_duration.weeks, date_duration.days }) {
|
||||
// a. If v < 0, return -1.
|
||||
if (value < 0)
|
||||
return -1;
|
||||
|
||||
// b. If v > 0, return 1.
|
||||
if (value > 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. Return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 7.5.16 IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration
|
||||
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
|
||||
{
|
||||
|
@ -66,32 +273,232 @@ bool is_valid_duration(double years, double months, double weeks, double days, d
|
|||
// unsafe integer. This multiplication can be implemented in C++ with an implementation of std::remquo()
|
||||
// with sufficient bits in the quotient. String manipulation will also give an exact result, since the
|
||||
// multiplication is by a power of 10.
|
||||
static Crypto::SignedBigInteger days_to_nanoseconds { 8.64e13 };
|
||||
static Crypto::SignedBigInteger hours_to_nanoseconds { 3.6e12 };
|
||||
static Crypto::SignedBigInteger minutes_to_nanoseconds { 6e10 };
|
||||
static Crypto::SignedBigInteger seconds_to_nanoseconds { 1e9 };
|
||||
static Crypto::SignedBigInteger milliseconds_to_nanoseconds { 1e6 };
|
||||
static Crypto::SignedBigInteger microseconds_to_nanoseconds { 1e3 };
|
||||
auto total_fractional_seconds = TimeDuration { days }.multiplied_by(NANOSECONDS_PER_DAY);
|
||||
total_fractional_seconds = total_fractional_seconds.plus(TimeDuration { hours }.multiplied_by(NANOSECONDS_PER_HOUR));
|
||||
total_fractional_seconds = total_fractional_seconds.plus(TimeDuration { minutes }.multiplied_by(NANOSECONDS_PER_MINUTE));
|
||||
total_fractional_seconds = total_fractional_seconds.plus(TimeDuration { seconds }.multiplied_by(NANOSECONDS_PER_SECOND));
|
||||
total_fractional_seconds = total_fractional_seconds.plus(TimeDuration { milliseconds }.multiplied_by(NANOSECONDS_PER_MILLISECOND));
|
||||
total_fractional_seconds = total_fractional_seconds.plus(TimeDuration { microseconds }.multiplied_by(NANOSECONDS_PER_MICROSECOND));
|
||||
total_fractional_seconds = total_fractional_seconds.plus(TimeDuration { nanoseconds });
|
||||
|
||||
auto normalized_nanoseconds = Crypto::SignedBigInteger { days }.multiplied_by(days_to_nanoseconds);
|
||||
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { hours }.multiplied_by(hours_to_nanoseconds));
|
||||
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { minutes }.multiplied_by(minutes_to_nanoseconds));
|
||||
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { seconds }.multiplied_by(seconds_to_nanoseconds));
|
||||
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { milliseconds }.multiplied_by(milliseconds_to_nanoseconds));
|
||||
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { microseconds }.multiplied_by(microseconds_to_nanoseconds));
|
||||
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { nanoseconds });
|
||||
|
||||
// 8. If abs(normalizedSeconds) ≥ 2**53, return false.
|
||||
static auto maximum_time = Crypto::SignedBigInteger { MAX_ARRAY_LIKE_INDEX }.plus(1_bigint).multiplied_by(seconds_to_nanoseconds);
|
||||
|
||||
if (normalized_nanoseconds.is_negative())
|
||||
normalized_nanoseconds.negate();
|
||||
|
||||
if (normalized_nanoseconds >= maximum_time)
|
||||
// 8. If abs(totalFractionalSeconds) ≥ 2**53, return false.
|
||||
if (total_fractional_seconds.unsigned_value() > MAX_TIME_DURATION.unsigned_value())
|
||||
return false;
|
||||
|
||||
// 9. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 7.5.17 DefaultTemporalLargestUnit ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-defaulttemporallargestunit
|
||||
Unit default_temporal_largest_unit(Duration const& duration)
|
||||
{
|
||||
// 1. If duration.[[Years]] ≠ 0, return YEAR.
|
||||
if (duration.years() != 0)
|
||||
return Unit::Year;
|
||||
|
||||
// 2. If duration.[[Months]] ≠ 0, return MONTH.
|
||||
if (duration.months() != 0)
|
||||
return Unit::Month;
|
||||
|
||||
// 3. If duration.[[Weeks]] ≠ 0, return WEEK.
|
||||
if (duration.weeks() != 0)
|
||||
return Unit::Week;
|
||||
|
||||
// 4. If duration.[[Days]] ≠ 0, return DAY.
|
||||
if (duration.days() != 0)
|
||||
return Unit::Day;
|
||||
|
||||
// 5. If duration.[[Hours]] ≠ 0, return HOUR.
|
||||
if (duration.hours() != 0)
|
||||
return Unit::Hour;
|
||||
|
||||
// 6. If duration.[[Minutes]] ≠ 0, return MINUTE.
|
||||
if (duration.minutes() != 0)
|
||||
return Unit::Minute;
|
||||
|
||||
// 7. If duration.[[Seconds]] ≠ 0, return SECOND.
|
||||
if (duration.seconds() != 0)
|
||||
return Unit::Second;
|
||||
|
||||
// 8. If duration.[[Milliseconds]] ≠ 0, return MILLISECOND.
|
||||
if (duration.milliseconds() != 0)
|
||||
return Unit::Millisecond;
|
||||
|
||||
// 9. If duration.[[Microseconds]] ≠ 0, return MICROSECOND.
|
||||
if (duration.microseconds() != 0)
|
||||
return Unit::Microsecond;
|
||||
|
||||
// 10. Return NANOSECOND.
|
||||
return Unit::Nanosecond;
|
||||
}
|
||||
|
||||
// 7.5.18 ToTemporalPartialDurationRecord ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalpartialdurationrecord
|
||||
ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM& vm, Value temporal_duration_like)
|
||||
{
|
||||
// 1. If temporalDurationLike is not an Object, then
|
||||
if (!temporal_duration_like.is_object()) {
|
||||
// a. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, temporal_duration_like);
|
||||
}
|
||||
|
||||
// 2. Let result be a new partial Duration Record with each field set to undefined.
|
||||
PartialDuration result {};
|
||||
|
||||
// 3. NOTE: The following steps read properties and perform independent validation in alphabetical order.
|
||||
|
||||
auto to_integral_if_defined = [&vm, &temporal_duration = temporal_duration_like.as_object()](auto const& property, auto& field) -> ThrowCompletionOr<void> {
|
||||
if (auto value = TRY(temporal_duration.get(property)); !value.is_undefined())
|
||||
field = TRY(to_integer_if_integral(vm, value, ErrorType::TemporalInvalidDurationPropertyValueNonIntegral, property, value));
|
||||
return {};
|
||||
};
|
||||
|
||||
// 4. Let days be ? Get(temporalDurationLike, "days").
|
||||
// 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days).
|
||||
TRY(to_integral_if_defined(vm.names.days, result.days));
|
||||
|
||||
// 6. Let hours be ? Get(temporalDurationLike, "hours").
|
||||
// 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours).
|
||||
TRY(to_integral_if_defined(vm.names.hours, result.hours));
|
||||
|
||||
// 8. Let microseconds be ? Get(temporalDurationLike, "microseconds").
|
||||
// 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds).
|
||||
TRY(to_integral_if_defined(vm.names.microseconds, result.microseconds));
|
||||
|
||||
// 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds").
|
||||
// 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds).
|
||||
TRY(to_integral_if_defined(vm.names.milliseconds, result.milliseconds));
|
||||
|
||||
// 12. Let minutes be ? Get(temporalDurationLike, "minutes").
|
||||
// 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes).
|
||||
TRY(to_integral_if_defined(vm.names.minutes, result.minutes));
|
||||
|
||||
// 14. Let months be ? Get(temporalDurationLike, "months").
|
||||
// 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months).
|
||||
TRY(to_integral_if_defined(vm.names.months, result.months));
|
||||
|
||||
// 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds").
|
||||
// 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds).
|
||||
TRY(to_integral_if_defined(vm.names.nanoseconds, result.nanoseconds));
|
||||
|
||||
// 18. Let seconds be ? Get(temporalDurationLike, "seconds").
|
||||
// 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds).
|
||||
TRY(to_integral_if_defined(vm.names.seconds, result.seconds));
|
||||
|
||||
// 20. Let weeks be ? Get(temporalDurationLike, "weeks").
|
||||
// 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks).
|
||||
TRY(to_integral_if_defined(vm.names.weeks, result.weeks));
|
||||
|
||||
// 22. Let years be ? Get(temporalDurationLike, "years").
|
||||
// 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years).
|
||||
TRY(to_integral_if_defined(vm.names.years, result.years));
|
||||
|
||||
// 24. If years is undefined, and months is undefined, and weeks is undefined, and days is undefined, and hours is
|
||||
// undefined, and minutes is undefined, and seconds is undefined, and milliseconds is undefined, and microseconds
|
||||
// is undefined, and nanoseconds is undefined, throw a TypeError exception.
|
||||
if (!result.any_field_defined())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidDurationLikeObject);
|
||||
|
||||
// 25. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 7.5.19 CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalduration
|
||||
ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(VM& vm, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, GC::Ptr<FunctionObject> new_target)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. If IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception.
|
||||
if (!is_valid_duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
|
||||
|
||||
// 2. If newTarget is not present, set newTarget to %Temporal.Duration%.
|
||||
if (!new_target)
|
||||
new_target = realm.intrinsics().temporal_duration_constructor();
|
||||
|
||||
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »).
|
||||
// 4. Set object.[[Years]] to ℝ(𝔽(years)).
|
||||
// 5. Set object.[[Months]] to ℝ(𝔽(months)).
|
||||
// 6. Set object.[[Weeks]] to ℝ(𝔽(weeks)).
|
||||
// 7. Set object.[[Days]] to ℝ(𝔽(days)).
|
||||
// 8. Set object.[[Hours]] to ℝ(𝔽(hours)).
|
||||
// 9. Set object.[[Minutes]] to ℝ(𝔽(minutes)).
|
||||
// 10. Set object.[[Seconds]] to ℝ(𝔽(seconds)).
|
||||
// 11. Set object.[[Milliseconds]] to ℝ(𝔽(milliseconds)).
|
||||
// 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)).
|
||||
// 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)).
|
||||
auto object = TRY(ordinary_create_from_constructor<Duration>(vm, *new_target, &Intrinsics::temporal_duration_prototype, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
|
||||
|
||||
// 14. Return object.
|
||||
return object;
|
||||
}
|
||||
|
||||
// 7.5.21 TimeDurationFromComponents ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromcomponents
|
||||
TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
|
||||
{
|
||||
// 1. Set minutes to minutes + hours × 60.
|
||||
auto total_minutes = TimeDuration { minutes }.plus(TimeDuration { hours }.multiplied_by(60_bigint));
|
||||
|
||||
// 2. Set seconds to seconds + minutes × 60.
|
||||
auto total_seconds = TimeDuration { seconds }.plus(total_minutes.multiplied_by(60_bigint));
|
||||
|
||||
// 3. Set milliseconds to milliseconds + seconds × 1000.
|
||||
auto total_milliseconds = TimeDuration { milliseconds }.plus(total_seconds.multiplied_by(1000_bigint));
|
||||
|
||||
// 4. Set microseconds to microseconds + milliseconds × 1000.
|
||||
auto total_microseconds = TimeDuration { microseconds }.plus(total_milliseconds.multiplied_by(1000_bigint));
|
||||
|
||||
// 5. Set nanoseconds to nanoseconds + microseconds × 1000.
|
||||
auto total_nanoseconds = TimeDuration { nanoseconds }.plus(total_microseconds.multiplied_by(1000_bigint));
|
||||
|
||||
// 6. Assert: abs(nanoseconds) ≤ maxTimeDuration.
|
||||
VERIFY(total_nanoseconds.unsigned_value() <= MAX_TIME_DURATION.unsigned_value());
|
||||
|
||||
// 7. Return nanoseconds.
|
||||
return total_nanoseconds;
|
||||
}
|
||||
|
||||
// 7.5.23 Add24HourDaysToTimeDuration ( d, days ), https://tc39.es/proposal-temporal/#sec-temporal-add24hourdaystonormalizedtimeduration
|
||||
ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM& vm, TimeDuration const& time_duration, double days)
|
||||
{
|
||||
// 1. Let result be d + days × nsPerDay.
|
||||
auto result = time_duration.plus(TimeDuration { days }.multiplied_by(NANOSECONDS_PER_DAY));
|
||||
|
||||
// 2. If abs(result) > maxTimeDuration, throw a RangeError exception.
|
||||
if (result.unsigned_value() > MAX_TIME_DURATION.unsigned_value())
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
|
||||
|
||||
// 3. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// 7.5.25 CompareTimeDuration ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-comparetimeduration
|
||||
i8 compare_time_duration(TimeDuration const& one, TimeDuration const& two)
|
||||
{
|
||||
// 1. If one > two, return 1.
|
||||
if (one > two)
|
||||
return 1;
|
||||
|
||||
// 2. If one < two, return -1.
|
||||
if (one < two)
|
||||
return -1;
|
||||
|
||||
// 3. Return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 7.5.28 TimeDurationSign ( d ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationsign
|
||||
i8 time_duration_sign(TimeDuration const& time_duration)
|
||||
{
|
||||
// 1. If d < 0, return -1.
|
||||
if (time_duration.is_negative())
|
||||
return -1;
|
||||
|
||||
// 2. If d > 0, return 1.
|
||||
if (time_duration.is_positive())
|
||||
return 1;
|
||||
|
||||
// 3. Return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,10 +8,112 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
#define JS_ENUMERATE_DURATION_UNITS \
|
||||
__JS_ENUMERATE(years) \
|
||||
__JS_ENUMERATE(months) \
|
||||
__JS_ENUMERATE(weeks) \
|
||||
__JS_ENUMERATE(days) \
|
||||
__JS_ENUMERATE(hours) \
|
||||
__JS_ENUMERATE(minutes) \
|
||||
__JS_ENUMERATE(seconds) \
|
||||
__JS_ENUMERATE(milliseconds) \
|
||||
__JS_ENUMERATE(microseconds) \
|
||||
__JS_ENUMERATE(nanoseconds)
|
||||
|
||||
class Duration final : public Object {
|
||||
JS_OBJECT(Duration, Object);
|
||||
GC_DECLARE_ALLOCATOR(Duration);
|
||||
|
||||
public:
|
||||
virtual ~Duration() override = default;
|
||||
|
||||
#define __JS_ENUMERATE(unit) \
|
||||
[[nodiscard]] double unit() const { return m_##unit; }
|
||||
JS_ENUMERATE_DURATION_UNITS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
private:
|
||||
Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype);
|
||||
|
||||
double m_years { 0 }; // [[Years]]
|
||||
double m_months { 0 }; // [[Months]]
|
||||
double m_weeks { 0 }; // [[Weeks]]
|
||||
double m_days { 0 }; // [[Days]]
|
||||
double m_hours { 0 }; // [[Hours]]
|
||||
double m_minutes { 0 }; // [[Minutes]]
|
||||
double m_seconds { 0 }; // [[Seconds]]
|
||||
double m_milliseconds { 0 }; // [[Milliseconds]]
|
||||
double m_microseconds { 0 }; // [[Microseconds]]
|
||||
double m_nanoseconds { 0 }; // [[Nanoseconds]]
|
||||
};
|
||||
|
||||
// 7.5.1 Date Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records
|
||||
struct DateDuration {
|
||||
double years { 0 };
|
||||
double months { 0 };
|
||||
double weeks { 0 };
|
||||
double days { 0 };
|
||||
};
|
||||
|
||||
// 7.5.2 Partial Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-partial-duration-records
|
||||
struct PartialDuration {
|
||||
static PartialDuration zero()
|
||||
{
|
||||
return { .years = 0, .months = 0, .weeks = 0, .days = 0, .hours = 0, .minutes = 0, .seconds = 0, .milliseconds = 0, .microseconds = 0, .nanoseconds = 0 };
|
||||
}
|
||||
|
||||
bool any_field_defined() const
|
||||
{
|
||||
return years.has_value() || months.has_value() || weeks.has_value() || days.has_value() || hours.has_value() || minutes.has_value() || seconds.has_value() || milliseconds.has_value() || microseconds.has_value() || nanoseconds.has_value();
|
||||
}
|
||||
|
||||
Optional<double> years;
|
||||
Optional<double> months;
|
||||
Optional<double> weeks;
|
||||
Optional<double> days;
|
||||
Optional<double> hours;
|
||||
Optional<double> minutes;
|
||||
Optional<double> seconds;
|
||||
Optional<double> milliseconds;
|
||||
Optional<double> microseconds;
|
||||
Optional<double> nanoseconds;
|
||||
};
|
||||
|
||||
// A time duration is an integer in the inclusive interval from -maxTimeDuration to maxTimeDuration, where
|
||||
// maxTimeDuration = 2**53 × 10**9 - 1 = 9,007,199,254,740,991,999,999,999. It represents the portion of a
|
||||
// Temporal.Duration object that deals with time units, but as a combined value of total nanoseconds.
|
||||
using TimeDuration = Crypto::SignedBigInteger;
|
||||
extern TimeDuration const MAX_TIME_DURATION;
|
||||
|
||||
// 7.5.3 Internal Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-internal-duration-records
|
||||
struct InternalDuration {
|
||||
DateDuration date;
|
||||
TimeDuration time;
|
||||
};
|
||||
|
||||
DateDuration zero_date_duration(VM&);
|
||||
InternalDuration to_internal_duration_record(VM&, Duration const&);
|
||||
ThrowCompletionOr<DateDuration> create_date_duration_record(VM&, double years, double months, double weeks, double days);
|
||||
ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM&, DateDuration, TimeDuration);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM&, Value);
|
||||
i8 duration_sign(Duration const&);
|
||||
i8 date_duration_sign(DateDuration const&);
|
||||
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
|
||||
Unit default_temporal_largest_unit(Duration const&);
|
||||
ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM&, Value temporal_duration_like);
|
||||
ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, GC::Ptr<FunctionObject> new_target = {});
|
||||
TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
|
||||
ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM&, TimeDuration const&, double days);
|
||||
i8 compare_time_duration(TimeDuration const&, TimeDuration const&);
|
||||
i8 time_duration_sign(TimeDuration const&);
|
||||
|
||||
}
|
||||
|
|
194
Libraries/LibJS/Runtime/Temporal/DurationConstructor.cpp
Normal file
194
Libraries/LibJS/Runtime/Temporal/DurationConstructor.cpp
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(DurationConstructor);
|
||||
|
||||
// 7.1 The Temporal.Duration Constructor, https://tc39.es/proposal-temporal/#sec-temporal-duration-constructor
|
||||
DurationConstructor::DurationConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.Duration.as_string(), realm.intrinsics().function_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void DurationConstructor::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 7.2.1 Temporal.Duration.prototype, https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype
|
||||
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_duration_prototype(), 0);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.from, from, 1, attr);
|
||||
define_native_function(realm, vm.names.compare, compare, 2, attr);
|
||||
|
||||
define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 7.1.1 Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ , minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ] ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration
|
||||
ThrowCompletionOr<Value> DurationConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If NewTarget is undefined, then
|
||||
// a. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.Duration");
|
||||
}
|
||||
|
||||
// 7.1.1 Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ , minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ] ] ] ] ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration
|
||||
ThrowCompletionOr<GC::Ref<Object>> DurationConstructor::construct(FunctionObject& new_target)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto next_integer_argument = [&, index = 0]() mutable -> ThrowCompletionOr<double> {
|
||||
if (auto value = vm.argument(index++); !value.is_undefined())
|
||||
return to_integer_if_integral(vm, value, ErrorType::TemporalInvalidDuration);
|
||||
return 0;
|
||||
};
|
||||
|
||||
// 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years).
|
||||
auto years = TRY(next_integer_argument());
|
||||
|
||||
// 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months).
|
||||
auto months = TRY(next_integer_argument());
|
||||
|
||||
// 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks).
|
||||
auto weeks = TRY(next_integer_argument());
|
||||
|
||||
// 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days).
|
||||
auto days = TRY(next_integer_argument());
|
||||
|
||||
// 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours).
|
||||
auto hours = TRY(next_integer_argument());
|
||||
|
||||
// 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes).
|
||||
auto minutes = TRY(next_integer_argument());
|
||||
|
||||
// 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds).
|
||||
auto seconds = TRY(next_integer_argument());
|
||||
|
||||
// 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds).
|
||||
auto milliseconds = TRY(next_integer_argument());
|
||||
|
||||
// 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds).
|
||||
auto microseconds = TRY(next_integer_argument());
|
||||
|
||||
// 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds).
|
||||
auto nanoseconds = TRY(next_integer_argument());
|
||||
|
||||
// 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget).
|
||||
return TRY(create_temporal_duration(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, new_target));
|
||||
}
|
||||
|
||||
// 7.2.2 Temporal.Duration.from ( item ), https://tc39.es/proposal-temporal/#sec-temporal.duration.from
|
||||
JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::from)
|
||||
{
|
||||
// 1. Return ? ToTemporalDuration(item).
|
||||
return TRY(to_temporal_duration(vm, vm.argument(0)));
|
||||
}
|
||||
|
||||
// 7.2.3 Temporal.Duration.compare ( one, two [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.compare
|
||||
JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::compare)
|
||||
{
|
||||
// 1. Set one to ? ToTemporalDuration(one).
|
||||
auto one = TRY(to_temporal_duration(vm, vm.argument(0)));
|
||||
|
||||
// 2. Set two to ? ToTemporalDuration(two).
|
||||
auto two = TRY(to_temporal_duration(vm, vm.argument(1)));
|
||||
|
||||
// 3. Let resolvedOptions be ? GetOptionsObject(options).
|
||||
auto resolved_options = TRY(get_options_object(vm, vm.argument(2)));
|
||||
|
||||
// 4. Let relativeToRecord be ? GetTemporalRelativeToOption(resolvedOptions).
|
||||
auto relative_to_record = TRY(get_temporal_relative_to_option(vm, resolved_options));
|
||||
|
||||
// 5. If one.[[Years]] = two.[[Years]], and one.[[Months]] = two.[[Months]], and one.[[Weeks]] = two.[[Weeks]], and
|
||||
// one.[[Days]] = two.[[Days]], and one.[[Hours]] = two.[[Hours]], and one.[[Minutes]] = two.[[Minutes]], and
|
||||
// one.[[Seconds]] = two.[[Seconds]], and one.[[Milliseconds]] = two.[[Milliseconds]], and
|
||||
// one.[[Microseconds]] = two.[[Microseconds]], and one.[[Nanoseconds]] = two.[[Nanoseconds]], then
|
||||
if (one->years() == two->years()
|
||||
&& one->months() == two->months()
|
||||
&& one->weeks() == two->weeks()
|
||||
&& one->days() == two->days()
|
||||
&& one->hours() == two->hours()
|
||||
&& one->minutes() == two->minutes()
|
||||
&& one->seconds() == two->seconds()
|
||||
&& one->milliseconds() == two->milliseconds()
|
||||
&& one->microseconds() == two->microseconds()
|
||||
&& one->nanoseconds() == two->nanoseconds()) {
|
||||
// a. Return +0𝔽.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 6. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
|
||||
// 7. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
|
||||
auto [zoned_relative_to, plain_relative_to] = relative_to_record;
|
||||
|
||||
// 8. Let largestUnit1 be DefaultTemporalLargestUnit(one).
|
||||
auto largest_unit1 = default_temporal_largest_unit(one);
|
||||
|
||||
// 9. Let largestUnit2 be DefaultTemporalLargestUnit(two).
|
||||
auto largest_unit2 = default_temporal_largest_unit(two);
|
||||
|
||||
// 10. Let duration1 be ToInternalDurationRecord(one).
|
||||
auto duration1 = to_internal_duration_record(vm, one);
|
||||
|
||||
// 11. Let duration2 be ToInternalDurationRecord(two).
|
||||
auto duration2 = to_internal_duration_record(vm, two);
|
||||
|
||||
// 12. If zonedRelativeTo is not undefined, and either TemporalUnitCategory(largestUnit1) or TemporalUnitCategory(largestUnit2) is date, then
|
||||
if (zoned_relative_to && (temporal_unit_category(largest_unit1) == UnitCategory::Date || temporal_unit_category(largest_unit2) == UnitCategory::Date)) {
|
||||
// FIXME: a. Let timeZone be zonedRelativeTo.[[TimeZone]].
|
||||
// FIXME: b. Let calendar be zonedRelativeTo.[[Calendar]].
|
||||
// FIXME: c. Let after1 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration1, constrain).
|
||||
// FIXME: d. Let after2 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration2, constrain).
|
||||
// FIXME: e. If after1 > after2, return 1𝔽.
|
||||
// FIXME: f. If after1 < after2, return -1𝔽.
|
||||
|
||||
// g. Return +0𝔽.
|
||||
return 0;
|
||||
}
|
||||
|
||||
double days1 = 0;
|
||||
double days2 = 0;
|
||||
|
||||
// 13. If IsCalendarUnit(largestUnit1) is true or IsCalendarUnit(largestUnit2) is true, then
|
||||
if (is_calendar_unit(largest_unit1) || is_calendar_unit(largest_unit2)) {
|
||||
// a. If plainRelativeTo is undefined, throw a RangeError exception.
|
||||
if (!plain_relative_to)
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalMissingStartingPoint, "calendar units");
|
||||
|
||||
// FIXME: b. Let days1 be ? DateDurationDays(duration1.[[Date]], plainRelativeTo).
|
||||
// FIXME: c. Let days2 be ? DateDurationDays(duration2.[[Date]], plainRelativeTo).
|
||||
}
|
||||
// 14. Else,
|
||||
else {
|
||||
// a. Let days1 be one.[[Days]].
|
||||
days1 = one->days();
|
||||
|
||||
// b. Let days2 be two.[[Days]].
|
||||
days2 = two->days();
|
||||
}
|
||||
|
||||
// 15. Let timeDuration1 be ? Add24HourDaysToTimeDuration(duration1.[[Time]], days1).
|
||||
auto time_duration1 = TRY(add_24_hour_days_to_time_duration(vm, duration1.time, days1));
|
||||
|
||||
// 16. Let timeDuration2 be ? Add24HourDaysToTimeDuration(duration2.[[Time]], days2).
|
||||
auto time_duration2 = TRY(add_24_hour_days_to_time_duration(vm, duration2.time, days2));
|
||||
|
||||
// 17. Return 𝔽(CompareTimeDuration(timeDuration1, timeDuration2)).
|
||||
return compare_time_duration(time_duration1, time_duration2);
|
||||
}
|
||||
|
||||
}
|
34
Libraries/LibJS/Runtime/Temporal/DurationConstructor.h
Normal file
34
Libraries/LibJS/Runtime/Temporal/DurationConstructor.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 DurationConstructor final : public NativeFunction {
|
||||
JS_OBJECT(DurationConstructor, NativeFunction);
|
||||
GC_DECLARE_ALLOCATOR(DurationConstructor);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~DurationConstructor() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target) override;
|
||||
|
||||
private:
|
||||
explicit DurationConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(from);
|
||||
JS_DECLARE_NATIVE_FUNCTION(compare);
|
||||
};
|
||||
|
||||
}
|
59
Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp
Normal file
59
Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(DurationPrototype);
|
||||
|
||||
// 7.3 Properties of the Temporal.Duration Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-duration-prototype-object
|
||||
DurationPrototype::DurationPrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void DurationPrototype::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 7.3.2 Temporal.Duration.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype-%symbol.tostringtag%
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Duration"_string), Attribute::Configurable);
|
||||
|
||||
#define __JS_ENUMERATE(unit) \
|
||||
define_native_accessor(realm, vm.names.unit, unit##_getter, {}, Attribute::Configurable);
|
||||
JS_ENUMERATE_DURATION_UNITS
|
||||
#undef __JS_ENUMERATE
|
||||
}
|
||||
|
||||
// 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years
|
||||
// 7.3.4 get Temporal.Duration.prototype.months, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.months
|
||||
// 7.3.5 get Temporal.Duration.prototype.weeks, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.weeks
|
||||
// 7.3.6 get Temporal.Duration.prototype.days, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.days
|
||||
// 7.3.7 get Temporal.Duration.prototype.hours, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.hours
|
||||
// 7.3.8 get Temporal.Duration.prototype.minutes, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.minutes
|
||||
// 7.3.9 get Temporal.Duration.prototype.seconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.seconds
|
||||
// 7.3.10 get Temporal.Duration.prototype.milliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.milliseconds
|
||||
// 7.3.11 get Temporal.Duration.prototype.microseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.microseconds
|
||||
// 7.3.12 get Temporal.Duration.prototype.nanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds
|
||||
#define __JS_ENUMERATE(unit) \
|
||||
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::unit##_getter) \
|
||||
{ \
|
||||
/* 1. Let duration be the this value. */ \
|
||||
/* 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). */ \
|
||||
auto duration = TRY(typed_this_object(vm)); \
|
||||
\
|
||||
/* 3. Return 𝔽(duration.[[<unit>]]). */ \
|
||||
return duration->unit(); \
|
||||
}
|
||||
JS_ENUMERATE_DURATION_UNITS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
}
|
32
Libraries/LibJS/Runtime/Temporal/DurationPrototype.h
Normal file
32
Libraries/LibJS/Runtime/Temporal/DurationPrototype.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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/Duration.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class DurationPrototype final : public PrototypeObject<DurationPrototype, Duration> {
|
||||
JS_PROTOTYPE_OBJECT(DurationPrototype, Duration, Temporal.Duration);
|
||||
GC_DECLARE_ALLOCATOR(DurationPrototype);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~DurationPrototype() override = default;
|
||||
|
||||
private:
|
||||
explicit DurationPrototype(Realm&);
|
||||
|
||||
#define __JS_ENUMERATE(unit) \
|
||||
JS_DECLARE_NATIVE_FUNCTION(unit##_getter);
|
||||
JS_ENUMERATE_DURATION_UNITS
|
||||
#undef __JS_ENUMERATE
|
||||
};
|
||||
|
||||
}
|
465
Libraries/LibJS/Runtime/Temporal/ISO8601.cpp
Normal file
465
Libraries/LibJS/Runtime/Temporal/ISO8601.cpp
Normal file
|
@ -0,0 +1,465 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 13.30 ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar
|
||||
class ISO8601Parser {
|
||||
public:
|
||||
explicit ISO8601Parser(StringView input)
|
||||
: m_input(input)
|
||||
, m_state({
|
||||
.lexer = GenericLexer { input },
|
||||
.parse_result = {},
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] GenericLexer const& lexer() const { return m_state.lexer; }
|
||||
[[nodiscard]] ParseResult const& parse_result() const { return m_state.parse_result; }
|
||||
|
||||
enum class Separator {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-TemporalDurationString
|
||||
[[nodiscard]] bool parse_temporal_duration_string()
|
||||
{
|
||||
// TemporalDurationString :
|
||||
// Duration
|
||||
return parse_duration();
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationDate
|
||||
[[nodiscard]] bool parse_duration_date()
|
||||
{
|
||||
// DurationDate :
|
||||
// DurationYearsPart DurationTime[opt]
|
||||
// DurationMonthsPart DurationTime[opt]
|
||||
// DurationWeeksPart DurationTime[opt]
|
||||
// DurationDaysPart DurationTime[opt]
|
||||
auto success = parse_duration_years_part() || parse_duration_months_part() || parse_duration_weeks_part() || parse_duration_days_part();
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
(void)parse_duration_time();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-Duration
|
||||
[[nodiscard]] bool parse_duration()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// Duration :::
|
||||
// ASCIISign[opt] DurationDesignator DurationDate
|
||||
// ASCIISign[opt] DurationDesignator DurationTime
|
||||
(void)parse_ascii_sign();
|
||||
|
||||
if (!parse_duration_designator())
|
||||
return false;
|
||||
|
||||
auto success = parse_duration_date() || parse_duration_time();
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationYearsPart
|
||||
[[nodiscard]] bool parse_duration_years_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationYearsPart :
|
||||
// DecimalDigits[~Sep] YearsDesignator DurationMonthsPart
|
||||
// DecimalDigits[~Sep] YearsDesignator DurationWeeksPart
|
||||
// DecimalDigits[~Sep] YearsDesignator DurationDaysPart[opt]
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_years))
|
||||
return false;
|
||||
|
||||
if (!parse_years_designator())
|
||||
return false;
|
||||
|
||||
(void)(parse_duration_months_part() || parse_duration_weeks_part() || parse_duration_days_part());
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationMonthsPart
|
||||
[[nodiscard]] bool parse_duration_months_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationMonthsPart :
|
||||
// DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart
|
||||
// DecimalDigits[~Sep] MonthsDesignator DurationDaysPart[opt]
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_months))
|
||||
return false;
|
||||
|
||||
if (!parse_months_designator())
|
||||
return false;
|
||||
|
||||
(void)(parse_duration_weeks_part() || parse_duration_days_part());
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationWeeksPart
|
||||
[[nodiscard]] bool parse_duration_weeks_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationWeeksPart :
|
||||
// DecimalDigits[~Sep] WeeksDesignator DurationDaysPart[opt]
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_weeks))
|
||||
return false;
|
||||
|
||||
if (!parse_weeks_designator())
|
||||
return false;
|
||||
|
||||
(void)parse_duration_days_part();
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationDaysPart
|
||||
[[nodiscard]] bool parse_duration_days_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationDaysPart :
|
||||
// DecimalDigits[~Sep] DaysDesignator
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_days))
|
||||
return false;
|
||||
|
||||
if (!parse_days_designator())
|
||||
return false;
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationTime
|
||||
[[nodiscard]] bool parse_duration_time()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationTime :
|
||||
// TimeDesignator DurationHoursPart
|
||||
// TimeDesignator DurationMinutesPart
|
||||
// TimeDesignator DurationSecondsPart
|
||||
if (!parse_time_designator())
|
||||
return false;
|
||||
|
||||
auto success = parse_duration_hours_part() || parse_duration_minutes_part() || parse_duration_seconds_part();
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationHoursPart
|
||||
[[nodiscard]] bool parse_duration_hours_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationHoursPart :
|
||||
// DecimalDigits[~Sep] TemporalDecimalFraction HoursDesignator
|
||||
// DecimalDigits[~Sep] HoursDesignator DurationMinutesPart
|
||||
// DecimalDigits[~Sep] HoursDesignator DurationSecondsPart[opt]
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_hours))
|
||||
return false;
|
||||
|
||||
auto is_fractional = parse_temporal_decimal_fraction(m_state.parse_result.duration_hours_fraction);
|
||||
|
||||
if (!parse_hours_designator())
|
||||
return false;
|
||||
if (!is_fractional)
|
||||
(void)(parse_duration_minutes_part() || parse_duration_seconds_part());
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationMinutesPart
|
||||
[[nodiscard]] bool parse_duration_minutes_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationMinutesPart :
|
||||
// DecimalDigits[~Sep] TemporalDecimalFraction MinutesDesignator
|
||||
// DecimalDigits[~Sep] MinutesDesignator DurationSecondsPart[opt]
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_minutes))
|
||||
return false;
|
||||
|
||||
auto is_fractional = parse_temporal_decimal_fraction(m_state.parse_result.duration_minutes_fraction);
|
||||
|
||||
if (!parse_minutes_designator())
|
||||
return false;
|
||||
if (!is_fractional)
|
||||
(void)parse_duration_seconds_part();
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationSecondsPart
|
||||
[[nodiscard]] bool parse_duration_seconds_part()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// DurationSecondsPart :
|
||||
// DecimalDigits[~Sep] TemporalDecimalFraction[opt] SecondsDesignator
|
||||
if (!parse_decimal_digits(Separator::No, m_state.parse_result.duration_seconds))
|
||||
return false;
|
||||
|
||||
(void)parse_temporal_decimal_fraction(m_state.parse_result.duration_seconds_fraction);
|
||||
|
||||
if (!parse_seconds_designator())
|
||||
return false;
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#prod-DecimalDigit
|
||||
[[nodiscard]] bool parse_decimal_digit()
|
||||
{
|
||||
// DecimalDigit : one of
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
if (m_state.lexer.next_is(is_ascii_digit)) {
|
||||
m_state.lexer.consume();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#prod-DecimalDigits
|
||||
[[nodiscard]] bool parse_decimal_digits(Separator separator, Optional<StringView>& result)
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// FIXME: Implement [+Sep] if it's ever needed.
|
||||
VERIFY(separator == Separator::No);
|
||||
|
||||
// DecimalDigits[Sep] ::
|
||||
// DecimalDigit
|
||||
// DecimalDigits[?Sep] DecimalDigit
|
||||
// [+Sep] DecimalDigits[+Sep] NumericLiteralSeparator DecimalDigit
|
||||
if (!parse_decimal_digit())
|
||||
return {};
|
||||
while (parse_decimal_digit())
|
||||
;
|
||||
|
||||
result = transaction.parsed_string_view();
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#prod-TemporalDecimalFraction
|
||||
[[nodiscard]] bool parse_temporal_decimal_fraction(Optional<StringView>& result)
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// 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
|
||||
if (!parse_temporal_decimal_separator())
|
||||
return false;
|
||||
if (!parse_decimal_digit())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
if (!parse_decimal_digit())
|
||||
break;
|
||||
}
|
||||
|
||||
result = transaction.parsed_string_view();
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#prod-ASCIISign
|
||||
[[nodiscard]] bool parse_ascii_sign()
|
||||
{
|
||||
StateTransaction transaction { *this };
|
||||
|
||||
// ASCIISign : one of
|
||||
// + -
|
||||
if (!m_state.lexer.next_is(is_any_of("+-"sv)))
|
||||
return false;
|
||||
|
||||
m_state.parse_result.sign = m_state.lexer.consume();
|
||||
|
||||
transaction.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#prod-TemporalDecimalSeparator
|
||||
[[nodiscard]] bool parse_temporal_decimal_separator()
|
||||
{
|
||||
// TemporalDecimalSeparator ::: one of
|
||||
// . ,
|
||||
return m_state.lexer.consume_specific('.') || m_state.lexer.consume_specific(',');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DurationDesignator
|
||||
[[nodiscard]] bool parse_duration_designator()
|
||||
{
|
||||
// DurationDesignator : one of
|
||||
// P p
|
||||
return m_state.lexer.consume_specific('P') || m_state.lexer.consume_specific('p');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-TimeDesignator
|
||||
[[nodiscard]] bool parse_time_designator()
|
||||
{
|
||||
// TimeDesignator : one of
|
||||
// T t
|
||||
return m_state.lexer.consume_specific('T') || m_state.lexer.consume_specific('t');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-YearsDesignator
|
||||
[[nodiscard]] bool parse_years_designator()
|
||||
{
|
||||
// YearsDesignator : one of
|
||||
// Y y
|
||||
return m_state.lexer.consume_specific('Y') || m_state.lexer.consume_specific('y');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-MonthsDesignator
|
||||
[[nodiscard]] bool parse_months_designator()
|
||||
{
|
||||
// MonthsDesignator : one of
|
||||
// M m
|
||||
return m_state.lexer.consume_specific('M') || m_state.lexer.consume_specific('m');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-WeeksDesignator
|
||||
[[nodiscard]] bool parse_weeks_designator()
|
||||
{
|
||||
// WeeksDesignator : one of
|
||||
// W w
|
||||
return m_state.lexer.consume_specific('W') || m_state.lexer.consume_specific('w');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-DaysDesignator
|
||||
[[nodiscard]] bool parse_days_designator()
|
||||
{
|
||||
// DaysDesignator : one of
|
||||
// D d
|
||||
return m_state.lexer.consume_specific('D') || m_state.lexer.consume_specific('d');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-HoursDesignator
|
||||
[[nodiscard]] bool parse_hours_designator()
|
||||
{
|
||||
// HoursDesignator : one of
|
||||
// H h
|
||||
return m_state.lexer.consume_specific('H') || m_state.lexer.consume_specific('h');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-MinutesDesignator
|
||||
[[nodiscard]] bool parse_minutes_designator()
|
||||
{
|
||||
// MinutesDesignator : one of
|
||||
// M m
|
||||
return m_state.lexer.consume_specific('M') || m_state.lexer.consume_specific('m');
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-temporal/#prod-SecondsDesignator
|
||||
[[nodiscard]] bool parse_seconds_designator()
|
||||
{
|
||||
// SecondsDesignator : one of
|
||||
// S s
|
||||
return m_state.lexer.consume_specific('S') || m_state.lexer.consume_specific('s');
|
||||
}
|
||||
|
||||
private:
|
||||
struct State {
|
||||
GenericLexer lexer;
|
||||
ParseResult parse_result;
|
||||
};
|
||||
|
||||
struct StateTransaction {
|
||||
explicit StateTransaction(ISO8601Parser& parser)
|
||||
: m_parser(parser)
|
||||
, m_saved_state(parser.m_state)
|
||||
, m_start_index(parser.m_state.lexer.tell())
|
||||
{
|
||||
}
|
||||
|
||||
~StateTransaction()
|
||||
{
|
||||
if (!m_commit)
|
||||
m_parser.m_state = move(m_saved_state);
|
||||
}
|
||||
|
||||
void commit() { m_commit = true; }
|
||||
StringView parsed_string_view() const
|
||||
{
|
||||
return m_parser.m_input.substring_view(m_start_index, m_parser.m_state.lexer.tell() - m_start_index);
|
||||
}
|
||||
|
||||
private:
|
||||
ISO8601Parser& m_parser;
|
||||
State m_saved_state;
|
||||
size_t m_start_index { 0 };
|
||||
bool m_commit { false };
|
||||
};
|
||||
|
||||
StringView m_input;
|
||||
State m_state;
|
||||
};
|
||||
|
||||
#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \
|
||||
__JS_ENUMERATE(TemporalDurationString, parse_temporal_duration_string)
|
||||
|
||||
Optional<ParseResult> parse_iso8601(Production production, StringView input)
|
||||
{
|
||||
ISO8601Parser parser { input };
|
||||
|
||||
switch (production) {
|
||||
#define __JS_ENUMERATE(ProductionName, parse_production) \
|
||||
case Production::ProductionName: \
|
||||
if (!parser.parse_production()) \
|
||||
return {}; \
|
||||
break;
|
||||
JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS
|
||||
#undef __JS_ENUMERATE
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// If we parsed successfully but didn't reach the end, the string doesn't match the given production.
|
||||
if (!parser.lexer().is_eof())
|
||||
return {};
|
||||
|
||||
return parser.parse_result();
|
||||
}
|
||||
|
||||
}
|
35
Libraries/LibJS/Runtime/Temporal/ISO8601.h
Normal file
35
Libraries/LibJS/Runtime/Temporal/ISO8601.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
struct ParseResult {
|
||||
Optional<char> sign;
|
||||
Optional<StringView> duration_years;
|
||||
Optional<StringView> duration_months;
|
||||
Optional<StringView> duration_weeks;
|
||||
Optional<StringView> duration_days;
|
||||
Optional<StringView> duration_hours;
|
||||
Optional<StringView> duration_hours_fraction;
|
||||
Optional<StringView> duration_minutes;
|
||||
Optional<StringView> duration_minutes_fraction;
|
||||
Optional<StringView> duration_seconds;
|
||||
Optional<StringView> duration_seconds_fraction;
|
||||
};
|
||||
|
||||
enum class Production {
|
||||
TemporalDurationString,
|
||||
};
|
||||
|
||||
Optional<ParseResult> parse_iso8601(Production, StringView);
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/Temporal.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
@ -26,6 +27,9 @@ void Temporal::initialize(Realm& realm)
|
|||
|
||||
// 1.1.1 Temporal [ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal-%symbol.tostringtag%
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal"_string), Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_intrinsic_accessor(vm.names.Duration, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_duration_constructor(); });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(Temporal.Duration.compare).toHaveLength(2);
|
||||
});
|
||||
|
||||
function checkCommonResults(duration1, duration2) {
|
||||
expect(Temporal.Duration.compare(duration1, duration1)).toBe(0);
|
||||
expect(Temporal.Duration.compare(duration2, duration2)).toBe(0);
|
||||
expect(Temporal.Duration.compare(duration1, duration2)).toBe(-1);
|
||||
expect(Temporal.Duration.compare(duration2, duration1)).toBe(1);
|
||||
}
|
||||
|
||||
test("basic functionality", () => {
|
||||
const duration1 = new Temporal.Duration(0, 0, 0, 1);
|
||||
const duration2 = new Temporal.Duration(0, 0, 0, 2);
|
||||
checkCommonResults(duration1, duration2);
|
||||
});
|
||||
|
||||
test("duration-like objects", () => {
|
||||
const duration1 = { years: 0, months: 0, weeks: 0, days: 1 };
|
||||
const duration2 = { years: 0, months: 0, weeks: 0, days: 2 };
|
||||
checkCommonResults(duration1, duration2);
|
||||
});
|
||||
|
||||
test("duration strings", () => {
|
||||
const duration1 = "P1D";
|
||||
const duration2 = "P2D";
|
||||
checkCommonResults(duration1, duration2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("invalid duration-like object", () => {
|
||||
expect(() => {
|
||||
Temporal.Duration.compare({});
|
||||
}).toThrowWithMessage(TypeError, "Invalid duration-like object");
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.compare({ years: 0, months: 0, weeks: 0, days: 1 }, {});
|
||||
}).toThrowWithMessage(TypeError, "Invalid duration-like object");
|
||||
});
|
||||
|
||||
test("relativeTo is required for comparing calendar units (year, month, week)", () => {
|
||||
const duration1 = new Temporal.Duration(1);
|
||||
const duration2 = new Temporal.Duration(2);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.compare(duration1, duration2);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"A starting point is required for comparing calendar units"
|
||||
);
|
||||
|
||||
const duration3 = new Temporal.Duration(0, 3);
|
||||
const duration4 = new Temporal.Duration(0, 4);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.compare(duration3, duration4);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"A starting point is required for comparing calendar units"
|
||||
);
|
||||
|
||||
const duration5 = new Temporal.Duration(0, 0, 5);
|
||||
const duration6 = new Temporal.Duration(0, 0, 6);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.compare(duration5, duration6);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"A starting point is required for comparing calendar units"
|
||||
);
|
||||
|
||||
// Still throws if year/month/week of one the duration objects is non-zero.
|
||||
const duration7 = new Temporal.Duration(0, 0, 0, 7);
|
||||
const duration8 = new Temporal.Duration(0, 0, 8);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.compare(duration7, duration8);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"A starting point is required for comparing calendar units"
|
||||
);
|
||||
|
||||
const duration9 = new Temporal.Duration(0, 0, 9);
|
||||
const duration10 = new Temporal.Duration(0, 0, 0, 10);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.compare(duration9, duration10);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"A starting point is required for comparing calendar units"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,132 @@
|
|||
const expectDurationOneToTen = duration => {
|
||||
expect(duration.years).toBe(1);
|
||||
expect(duration.months).toBe(2);
|
||||
expect(duration.weeks).toBe(3);
|
||||
expect(duration.days).toBe(4);
|
||||
expect(duration.hours).toBe(5);
|
||||
expect(duration.minutes).toBe(6);
|
||||
expect(duration.seconds).toBe(7);
|
||||
expect(duration.milliseconds).toBe(8);
|
||||
expect(duration.microseconds).toBe(9);
|
||||
expect(duration.nanoseconds).toBe(10);
|
||||
};
|
||||
|
||||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Duration.from).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("Duration instance argument", () => {
|
||||
const duration = Temporal.Duration.from(
|
||||
new Temporal.Duration(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
);
|
||||
expectDurationOneToTen(duration);
|
||||
});
|
||||
|
||||
test("Duration-like object argument", () => {
|
||||
const duration = Temporal.Duration.from({
|
||||
years: 1,
|
||||
months: 2,
|
||||
weeks: 3,
|
||||
days: 4,
|
||||
hours: 5,
|
||||
minutes: 6,
|
||||
seconds: 7,
|
||||
milliseconds: 8,
|
||||
microseconds: 9,
|
||||
nanoseconds: 10,
|
||||
});
|
||||
expectDurationOneToTen(duration);
|
||||
});
|
||||
|
||||
test("Duration string argument", () => {
|
||||
const duration = Temporal.Duration.from("P1Y2M3W4DT5H6M7.008009010S");
|
||||
expectDurationOneToTen(duration);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("Invalid duration-like object", () => {
|
||||
expect(() => {
|
||||
Temporal.Duration.from({});
|
||||
}).toThrowWithMessage(TypeError, "Invalid duration-like object");
|
||||
});
|
||||
|
||||
test("Invalid duration property value", () => {
|
||||
expect(() => {
|
||||
Temporal.Duration.from({ years: 1.23 });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid value for duration property 'years': must be an integer, got 1.2" // ...29999999999999 - let's not include that in the test :^)
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.from({ years: "foo" });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid value for duration property 'years': must be an integer, got foo"
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.from({ years: NaN });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid value for duration property 'years': must be an integer, got NaN"
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid duration string", () => {
|
||||
expect(() => {
|
||||
Temporal.Duration.from("foo");
|
||||
}).toThrowWithMessage(RangeError, "Invalid duration string 'foo'");
|
||||
});
|
||||
|
||||
test("invalid duration string: fractional hours proceeded by minutes or seconds", () => {
|
||||
const values = [
|
||||
"PT1.23H1M",
|
||||
"PT1.23H1.23M",
|
||||
"PT1.23H1S",
|
||||
"PT1.23H1.23S",
|
||||
"PT1.23H1M1S",
|
||||
"PT1.23H1M1.23S",
|
||||
"PT1.23H1.23M1S",
|
||||
"PT1.23H1.23M1.23S",
|
||||
];
|
||||
for (const value of values) {
|
||||
expect(() => {
|
||||
Temporal.Duration.from(value);
|
||||
}).toThrowWithMessage(RangeError, `Invalid duration string '${value}'`);
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid duration string: fractional minutes proceeded by seconds", () => {
|
||||
const values = ["PT1.23M1S", "PT1.23M1.23S"];
|
||||
for (const value of values) {
|
||||
expect(() => {
|
||||
Temporal.Duration.from(value);
|
||||
}).toThrowWithMessage(RangeError, `Invalid duration string '${value}'`);
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid duration string: exceed duration limits", () => {
|
||||
const values = [
|
||||
"P4294967296Y", // abs(years) >= 2**32
|
||||
"P4294967296M", // abs(months) >= 2**32
|
||||
"P4294967296W", // abs(weeks) >= 2**32
|
||||
"P104249991375D", // days >= 2*53 seconds
|
||||
"PT2501999792984H", // hours >= 2*53 seconds
|
||||
"PT150119987579017M", // minutes >= 2*53 seconds
|
||||
"PT9007199254740992S", // seconds >= 2*53 seconds
|
||||
];
|
||||
|
||||
for (const value of values) {
|
||||
expect(() => {
|
||||
Temporal.Duration.from(value);
|
||||
}).toThrowWithMessage(RangeError, `Invalid duration`);
|
||||
|
||||
expect(() => {
|
||||
Temporal.Duration.from("-" + value);
|
||||
}).toThrowWithMessage(RangeError, `Invalid duration`);
|
||||
}
|
||||
});
|
||||
});
|
35
Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.js
Normal file
35
Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
Temporal.Duration();
|
||||
}).toThrowWithMessage(TypeError, "Temporal.Duration constructor must be called with 'new'");
|
||||
});
|
||||
|
||||
test("cannot mix arguments with different signs", () => {
|
||||
expect(() => {
|
||||
new Temporal.Duration(-1, 1);
|
||||
}).toThrowWithMessage(RangeError, "Invalid duration");
|
||||
expect(() => {
|
||||
new Temporal.Duration(1, -1);
|
||||
}).toThrowWithMessage(RangeError, "Invalid duration");
|
||||
});
|
||||
|
||||
test("cannot pass Infinity", () => {
|
||||
expect(() => {
|
||||
new Temporal.Duration(Infinity);
|
||||
}).toThrowWithMessage(RangeError, "Invalid duration");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("length is 0", () => {
|
||||
expect(Temporal.Duration).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration();
|
||||
expect(typeof duration).toBe("object");
|
||||
expect(duration).toBeInstanceOf(Temporal.Duration);
|
||||
expect(Object.getPrototypeOf(duration)).toBe(Temporal.Duration.prototype);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
test("basic functionality", () => {
|
||||
expect(Temporal.Duration.prototype[Symbol.toStringTag]).toBe("Temporal.Duration");
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 123);
|
||||
expect(duration.days).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "days", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 123);
|
||||
expect(duration.hours).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "hours", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 123);
|
||||
expect(duration.microseconds).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "microseconds", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 123);
|
||||
expect(duration.milliseconds).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "milliseconds", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 123);
|
||||
expect(duration.minutes).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "minutes", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 123);
|
||||
expect(duration.months).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "months", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 0, 0, 0, 0, 123);
|
||||
expect(duration.nanoseconds).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "nanoseconds", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 0, 0, 0, 0, 123);
|
||||
expect(duration.seconds).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "seconds", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(0, 0, 123);
|
||||
expect(duration.weeks).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "weeks", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
const duration = new Temporal.Duration(123);
|
||||
expect(duration.years).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Duration object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Duration.prototype, "years", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue