diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-07-11 21:04:11 +0300 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-07-12 19:05:17 +0100 |
commit | b816037739e323f331f43c33a8eb808cd933c465 (patch) | |
tree | 4e18c383da4bdc24a3f7b20b587d83b95e002e31 /Userland | |
parent | 141c46feda1c91f3b50948749463ecf7c380aa1a (diff) | |
download | serenity-b816037739e323f331f43c33a8eb808cd933c465.zip |
LibJS: Add the ToTemporalInstant Abstract Operation & its requirements
This is Abstract Operation is required for the majority of
InstantConstructor's and InstantPrototype's methods.
The implementation is not entirely complete, (specifically 2 of the
underlying required abstract operations, ParseTemporalTimeZoneString
and ParseISODateTime are missing the required lexing, and as such are
TODO()-ed) but the majority of it is done.
Diffstat (limited to 'Userland')
19 files changed, 888 insertions, 13 deletions
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 3d127f8921..952839753f 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -120,11 +120,15 @@ set(SOURCES Runtime/SymbolConstructor.cpp Runtime/SymbolObject.cpp Runtime/SymbolPrototype.cpp + Runtime/Temporal/AbstractOperations.cpp + Runtime/Temporal/Calendar.cpp Runtime/Temporal/Instant.cpp Runtime/Temporal/InstantConstructor.cpp Runtime/Temporal/InstantPrototype.cpp - Runtime/Temporal/ISO8601.cpp Runtime/Temporal/Now.cpp + Runtime/Temporal/PlainDate.cpp + Runtime/Temporal/PlainDateTime.cpp + Runtime/Temporal/PlainTime.cpp Runtime/Temporal/Temporal.cpp Runtime/Temporal/TimeZone.cpp Runtime/Temporal/TimeZoneConstructor.cpp diff --git a/Userland/Libraries/LibJS/Runtime/Date.cpp b/Userland/Libraries/LibJS/Runtime/Date.cpp index 3bb5d09da9..7c94ee81cb 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.cpp +++ b/Userland/Libraries/LibJS/Runtime/Date.cpp @@ -111,4 +111,86 @@ String Date::iso_date_string() const return builder.build(); } +// https://tc39.es/ecma262/#eqn-msPerSecond +static constexpr double MS_PER_SECOND = 1000; +// https://tc39.es/ecma262/#eqn-msPerMinute +static constexpr double MS_PER_MINUTE = 60000; +// https://tc39.es/ecma262/#eqn-msPerHour +static constexpr double MS_PER_HOUR = 3600000; +// https://tc39.es/ecma262/#eqn-msPerDay +static constexpr double MS_PER_DAY = 86400000; + +// 21.4.1.11 MakeTime ( hour, min, sec, ms ), https://tc39.es/ecma262/#sec-maketime +Value make_time(GlobalObject& global_object, Value hour, Value min, Value sec, Value ms) +{ + // 1. If hour is not finite or min is not finite or sec is not finite or ms is not finite, return NaN. + if (!hour.is_finite_number() || !min.is_finite_number() || !sec.is_finite_number() || !ms.is_finite_number()) + return js_nan(); + + // 2. Let h be 𝔽(! ToIntegerOrInfinity(hour)). + auto h = hour.to_integer_or_infinity(global_object); + // 3. Let m be 𝔽(! ToIntegerOrInfinity(min)). + auto m = min.to_integer_or_infinity(global_object); + // 4. Let s be 𝔽(! ToIntegerOrInfinity(sec)). + auto s = sec.to_integer_or_infinity(global_object); + // 5. Let milli be 𝔽(! ToIntegerOrInfinity(ms)). + auto milli = ms.to_integer_or_infinity(global_object); + // 6. Let t be ((h * msPerHour + m * msPerMinute) + s * msPerSecond) + milli, performing the arithmetic according to IEEE 754-2019 rules (that is, as if using the ECMAScript operators * and +). + // NOTE: C++ arithmetic abides by IEEE 754 rules + auto t = ((h * MS_PER_HOUR + m * MS_PER_MINUTE) + s * MS_PER_SECOND) + milli; + // 7. Return t. + return Value(t); +} + +// https://tc39.es/ecma262/#eqn-Day +static inline double day(double time_value) +{ + return floor(time_value / MS_PER_DAY); +} + +// 21.4.1.12 MakeDay ( year, month, date ), https://tc39.es/ecma262/#sec-makeday +Value make_day(GlobalObject& global_object, Value year, Value month, Value date) +{ + // 1. If year is not finite or month is not finite or date is not finite, return NaN. + if (!year.is_finite_number() || !month.is_finite_number() || !date.is_finite_number()) + return js_nan(); + + // 2. Let y be 𝔽(! ToIntegerOrInfinity(year)). + auto y = year.to_integer_or_infinity(global_object); + // 3. Let m be 𝔽(! ToIntegerOrInfinity(month)). + auto m = month.to_integer_or_infinity(global_object); + // 4. Let dt be 𝔽(! ToIntegerOrInfinity(date)). + auto dt = date.to_integer_or_infinity(global_object); + // 5. Let ym be y + 𝔽(floor(ℝ(m) / 12)). + auto ym = Value(y + floor(m / 12)); + // 6. If ym is not finite, return NaN. + if (!ym.is_finite_number()) + return js_nan(); + // 7. Let mn be 𝔽(ℝ(m) modulo 12). + // NOTE: This calculation has no side-effects and is unused, so we omit it + + // 8. Find a finite time value t such that YearFromTime(t) is ym and MonthFromTime(t) is mn and DateFromTime(t) is 1𝔽; but if this is not possible (because some argument is out of range), return NaN. + auto t = Core::DateTime::create(y, m + 1, 0).timestamp() * 1000; + // 9. Return Day(t) + dt - 1𝔽. + return Value(day(t) + dt - 1); +} + +// 21.4.1.13 MakeDate ( day, time ), https://tc39.es/ecma262/#sec-makedate +Value make_date(Value day, Value time) +{ + // 1. If day is not finite or time is not finite, return NaN. + if (!day.is_finite_number() || !time.is_finite_number()) + return js_nan(); + + // 2. Let tv be day × msPerDay + time. + auto tv = Value(day.as_double() * MS_PER_DAY + time.as_double()); + + // 3. If tv is not finite, return NaN. + if (!tv.is_finite_number()) + return js_nan(); + + // 4. Return tv. + return tv; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Date.h b/Userland/Libraries/LibJS/Runtime/Date.h index ad60b9a44e..c2c4be40dd 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.h +++ b/Userland/Libraries/LibJS/Runtime/Date.h @@ -86,4 +86,8 @@ private: bool m_is_invalid { false }; }; +Value make_time(GlobalObject& global_object, Value hour, Value min, Value sec, Value ms); +Value make_day(GlobalObject& global_object, Value year, Value month, Value date); +Value make_date(Value day, Value time); + } diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 56378ab499..f8fae12fdc 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -31,6 +31,7 @@ M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object") \ M(InvalidAssignToConst, "Invalid assignment to const variable") \ M(InvalidCodePoint, "Invalid code point {}, must be an integer no less than 0 and no greater than 0x10FFFF") \ + M(InvalidFormat, "Invalid {} format") \ M(InvalidFractionDigits, "Fraction Digits must be an integer no less than 0, and no greater than 100") \ M(InvalidHint, "Invalid hint: \"{}\"") \ M(InvalidIndex, "Index must be a positive integer") \ @@ -163,7 +164,9 @@ M(StringNonGlobalRegExp, "RegExp argument is non-global") \ M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \ M(StringRepeatCountMustBe, "repeat count must be a {} number") \ + M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \ + M(TemporalInvalidTime, "Invalid time") \ M(TemporalInvalidTimeZoneName, "Invalid time zone name") \ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ M(ThisIsAlreadyInitialized, "|this| is already initialized") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp new file mode 100644 index 0000000000..ad2352edda --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/CharacterTypes.h> +#include <AK/DateTimeLexer.h> +#include <LibJS/Runtime/Temporal/AbstractOperations.h> +#include <LibJS/Runtime/Temporal/PlainDate.h> +#include <LibJS/Runtime/Temporal/PlainTime.h> +#include <LibJS/Runtime/Temporal/TimeZone.h> + +namespace JS::Temporal { + +// 13.34 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime +Optional<ISODateTime> parse_iso_date_time(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(isoString) is String. + + // 2. Let year, month, day, hour, minute, second, fraction, and calendar be the parts of isoString produced respectively by the DateYear, DateMonth, DateDay, TimeHour, TimeMinute, TimeSecond, TimeFractionalPart, and CalendarName productions, or undefined if not present. + Optional<StringView> year_part; + Optional<StringView> month_part; + Optional<StringView> day_part; + Optional<StringView> hour_part; + Optional<StringView> minute_part; + Optional<StringView> second_part; + Optional<StringView> fraction_part; + Optional<StringView> calendar_part; + TODO(); + // 3. Let year be the part of isoString produced by the DateYear production. + // 4. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS). + String normalized_year; + if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv)) + normalized_year = String::formatted("-{}", year_part->substring_view(3)); + else + normalized_year = year_part.value_or(""); + + // 5. Set year to ! ToIntegerOrInfinity(year). + i32 year = Value(js_string(vm, normalized_year)).to_integer_or_infinity(global_object); + + i32 month; + // 6. If month is undefined, then + if (!month_part.has_value()) { + // a. Set month to 1. + month = 1; + } + // 7. Else, + else { + // a. Set month to ! ToIntegerOrInfinity(month). + month = Value(js_string(vm, *month_part)).to_integer_or_infinity(global_object); + } + + i32 day; + // 8. If day is undefined, then + if (!day_part.has_value()) { + // a. Set day to 1. + day = 1; + } + // 9. Else, + else { + // a. Set day to ! ToIntegerOrInfinity(day). + day = Value(js_string(vm, *day_part)).to_integer_or_infinity(global_object); + } + + // 10. Set hour to ! ToIntegerOrInfinity(hour). + i32 hour = Value(js_string(vm, hour_part.value_or(""sv))).to_integer_or_infinity(global_object); + + // 11. Set minute to ! ToIntegerOrInfinity(minute). + i32 minute = Value(js_string(vm, minute_part.value_or(""sv))).to_integer_or_infinity(global_object); + + // 12. Set second to ! ToIntegerOrInfinity(second). + i32 second = Value(js_string(vm, second_part.value_or(""sv))).to_integer_or_infinity(global_object); + + // 13. If second is 60, then + if (second == 60) { + // a. Set second to 59. + second = 59; + } + + i32 millisecond; + i32 microsecond; + i32 nanosecond; + // 14. If fraction is not undefined, then + if (fraction_part.has_value()) { + // a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000". + auto fraction = String::formatted("{}000000000", *fraction_part); + // b. Let millisecond be the String value equal to the substring of fraction from 0 to 3. + // c. Set millisecond to ! ToIntegerOrInfinity(millisecond). + millisecond = Value(js_string(vm, fraction.substring(0, 3))).to_integer_or_infinity(global_object); + // d. Let microsecond be the String value equal to the substring of fraction from 3 to 6. + // e. Set microsecond to ! ToIntegerOrInfinity(microsecond). + microsecond = Value(js_string(vm, fraction.substring(3, 3))).to_integer_or_infinity(global_object); + // f. Let nanosecond be the String value equal to the substring of fraction from 6 to 9. + // g. Set nanosecond to ! ToIntegerOrInfinity(nanosecond). + nanosecond = Value(js_string(vm, fraction.substring(6, 3))).to_integer_or_infinity(global_object); + } + // 15. Else, + else { + // a. Let millisecond be 0. + millisecond = 0; + // b. Let microsecond be 0. + microsecond = 0; + // c. Let nanosecond be 0. + nanosecond = 0; + } + + // 16. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception. + if (!is_valid_iso_date(year, month, day)) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidISODate); + return {}; + } + + // 17. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception. + if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTime); + return {}; + } + + // 18. Return the new Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }. + return ISODateTime { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond, .calendar = calendar_part.has_value() ? *calendar_part : Optional<String>() }; +} + +// 13.35 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring +Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(isoString) is String. + + // 2. If isoString does not satisfy the syntax of a TemporalInstantString (see 13.33), then + // a. Throw a RangeError exception. + // TODO + + // 3. Let result be ! ParseISODateTime(isoString). + auto result = parse_iso_date_time(global_object, iso_string); + + // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString). + auto time_zone_result = parse_temporal_time_zone_string(global_object, iso_string); + if (vm.exception()) + return {}; + + // 5. Assert: timeZoneResult.[[OffsetString]] is not undefined. + VERIFY(time_zone_result->offset.has_value()); + + // 6. Return the new Record { [[Year]]: result.[[Year]], [[Month]]: result.[[Month]], [[Day]]: result.[[Day]], [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[TimeZoneOffsetString]]: timeZoneResult.[[OffsetString]] }. + return TemporalInstant { .year = result->year, .month = result->month, .day = result->day, .hour = result->hour, .minute = result->minute, .second = result->second, .millisecond = result->millisecond, .microsecond = result->microsecond, .nanosecond = result->nanosecond, .time_zone_offset = move(time_zone_result->offset) }; +} + +// 13.43 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring +Optional<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) +{ + auto& vm = global_object.vm(); + // 1. Assert: Type(isoString) is String. + + // 2. If isoString does not satisfy the syntax of a TemporalTimeZoneString (see 13.33), then + // a. Throw a RangeError exception. + // 3. Let z, sign, hours, minutes, seconds, fraction and name be the parts of isoString produced respectively by the UTCDesignator, TimeZoneUTCOffsetSign, TimeZoneUTCOffsetHour, TimeZoneUTCOffsetMinute, TimeZoneUTCOffsetSecond, TimeZoneUTCOffsetFraction, and TimeZoneIANAName productions, or undefined if not present. + Optional<StringView> z_part; + Optional<StringView> sign_part; + Optional<StringView> hours_part; + Optional<StringView> minutes_part; + Optional<StringView> seconds_part; + Optional<StringView> fraction_part; + Optional<StringView> name_part; + TODO(); + + // 4. If z is not undefined, then + if (z_part.has_value()) { + // a. Return the new Record: { [[Z]]: "Z", [[OffsetString]]: "+00:00", [[Name]]: undefined }. + return TemporalTimeZone { .z = true, .offset = "+00:00", .name = {} }; + } + + Optional<String> offset; + // 5. If hours is undefined, then + if (!hours_part.has_value()) { + // a. Let offsetString be undefined. + // NOTE: No-op. + } + // 6. Else, + else { + // a. Assert: sign is not undefined. + VERIFY(sign_part.has_value()); + + // b. Set hours to ! ToIntegerOrInfinity(hours). + i32 hours = Value(js_string(vm, *hours_part)).to_integer_or_infinity(global_object); + + i32 sign; + // c. If sign is the code unit 0x002D (HYPHEN-MINUS) or the code unit 0x2212 (MINUS SIGN), then + if (sign_part->is_one_of("-", "\u2212")) { + // i. Set sign to −1. + sign = -1; + } + // d. Else, + else { + // i. Set sign to 1. + sign = 1; + } + + // e. Set minutes to ! ToIntegerOrInfinity(minutes). + i32 minutes = Value(js_string(vm, minutes_part.value_or(""sv))).to_integer_or_infinity(global_object); + + // f. Set seconds to ! ToIntegerOrInfinity(seconds). + i32 seconds = Value(js_string(vm, seconds_part.value_or(""sv))).to_integer_or_infinity(global_object); + + i32 nanoseconds; + // g. If fraction is not undefined, then + if (fraction_part.has_value()) { + // i. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000". + auto fraction = String::formatted("{}000000000", *fraction_part); + // ii. Let nanoseconds be the String value equal to the substring of fraction from 0 to 9. + // iii. Set nanoseconds to ! ToIntegerOrInfinity(nanoseconds). + nanoseconds = Value(js_string(vm, fraction.substring(0, 9))).to_integer_or_infinity(global_object); + } + // h. Else, + else { + // i. Let nanoseconds be 0. + nanoseconds = 0; + } + // i. Let offsetNanoseconds be sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds). + auto offset_nanoseconds = sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000 + nanoseconds); + // j. Let offsetString be ! FormatTimeZoneOffsetString(offsetNanoseconds). + offset = format_time_zone_offset_string(offset_nanoseconds); + } + + Optional<String> name; + // 7. If name is not undefined, then + if (name_part.has_value()) { + // a. If ! IsValidTimeZoneName(name) is false, throw a RangeError exception. + if (!is_valid_time_zone_name(*name_part)) { + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName); + return {}; + } + // b. Set name to ! CanonicalizeTimeZoneName(name). + name = canonicalize_time_zone_name(*name_part); + } + + // 8. Return the new Record: { [[Z]]: undefined, [[OffsetString]]: offsetString, [[Name]]: name }. + return TemporalTimeZone { .z = false, .offset = offset, .name = name }; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h new file mode 100644 index 0000000000..36e2fc3c49 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Forward.h> +#include <AK/String.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS::Temporal { + +struct ISODateTime { + i32 year; + i32 month; + i32 day; + i32 hour; + i32 minute; + i32 second; + i32 millisecond; + i32 microsecond; + i32 nanosecond; + Optional<String> calendar; +}; +Optional<ISODateTime> parse_iso_date_time(GlobalObject&, String const& iso_string); +struct TemporalInstant { + i32 year; + i32 month; + i32 day; + i32 hour; + i32 minute; + i32 second; + i32 millisecond; + i32 microsecond; + i32 nanosecond; + Optional<String> time_zone_offset; +}; +Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject&, String const& iso_string); +struct TemporalTimeZone { + bool z; + Optional<String> offset; + Optional<String> name; +}; +Optional<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject&, String const& iso_string); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp new file mode 100644 index 0000000000..f9948eea02 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/Temporal/Calendar.h> +#include <LibJS/Runtime/Value.h> + +namespace JS::Temporal { + +// 12.1.30 IsISOLeapYear ( year ), https://tc39.es/proposal-temporal/#sec-temporal-isisoleapyear +bool is_iso_leap_year(i32 year) +{ + // 1. Assert: year is an integer. + + // 2. If year modulo 4 ≠ 0, return false. + if (year % 4 != 0) + return false; + + // 3. If year modulo 400 = 0, return true. + if (year % 400 == 0) + return true; + + // 4. If year modulo 100 = 0, return false. + if (year % 100 == 0) + return false; + + // 5. Return true. + return true; +} + +// 12.1.32 ISODaysInMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth +i32 iso_days_in_month(i32 year, i32 month) +{ + // 1. Assert: year is an integer. + + // 2. Assert: month is an integer, month ≥ 1, and month ≤ 12. + VERIFY(month >= 1 && month <= 12); + + // 3. If month is 1, 3, 5, 7, 8, 10, or 12, return 31. + if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) + return 31; + + // 4. If month is 4, 6, 9, or 11, return 30. + if (month == 4 || month == 6 || month == 9 || month == 11) + return 30; + + // 5. If ! IsISOLeapYear(year) is true, return 29. + if (is_iso_leap_year(year)) + return 29; + + // 6. Return 28. + return 28; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h new file mode 100644 index 0000000000..edb742c566 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/Value.h> + +namespace JS::Temporal { + +bool is_iso_leap_year(i32 year); +i32 iso_days_in_month(i32 year, i32 month); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp index df10f993c2..409992417a 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,8 +8,11 @@ #include <LibCrypto/BigInt/SignedBigInteger.h> #include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Temporal/AbstractOperations.h> #include <LibJS/Runtime/Temporal/Instant.h> #include <LibJS/Runtime/Temporal/InstantConstructor.h> +#include <LibJS/Runtime/Temporal/PlainDateTime.h> +#include <LibJS/Runtime/Temporal/TimeZone.h> namespace JS::Temporal { @@ -42,7 +46,7 @@ bool is_valid_epoch_nanoseconds(BigInt const& epoch_nanoseconds) } // 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalinstant -Object* create_temporal_instant(GlobalObject& global_object, BigInt& epoch_nanoseconds, FunctionObject* new_target) +Instant* create_temporal_instant(GlobalObject& global_object, BigInt& epoch_nanoseconds, FunctionObject* new_target) { auto& vm = global_object.vm(); @@ -65,4 +69,73 @@ Object* create_temporal_instant(GlobalObject& global_object, BigInt& epoch_nanos return object; } +// 8.5.3 ToTemporalInstant ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant +Instant* to_temporal_instant(GlobalObject& global_object, Value item) +{ + auto& vm = global_object.vm(); + + // 1. If Type(item) is Object, then + if (item.is_object()) { + // a. If item has an [[InitializedTemporalInstant]] internal slot, then + if (is<Instant>(item.as_object())) { + // i. Return item. + return &static_cast<Instant&>(item.as_object()); + } + // TODO: + // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + // i. Return ! CreateTemporalInstant(item.[[Nanoseconds]]). + } + + // 2. Let string be ? ToString(item). + auto string = item.to_string(global_object); + if (vm.exception()) + return {}; + + // 3. Let epochNanoseconds be ? ParseTemporalInstant(string). + auto* epoch_nanoseconds = parse_temporal_instant(global_object, string); + if (vm.exception()) + return {}; + + return create_temporal_instant(global_object, *epoch_nanoseconds); +} + +// 8.5.4 ParseTemporalInstant ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstant +BigInt* parse_temporal_instant(GlobalObject& global_object, String const& iso_string) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(isoString) is String. + + // 2. Let result be ? ParseTemporalInstantString(isoString). + auto result = parse_temporal_instant_string(global_object, iso_string); + if (vm.exception()) + return {}; + + // 3. Let offsetString be result.[[TimeZoneOffsetString]]. + auto& offset_string = result->time_zone_offset; + + // 4. Assert: offsetString is not undefined. + VERIFY(offset_string.has_value()); + + // 5. Let utc be ? GetEpochFromISOParts(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). + auto* utc = get_epoch_from_iso_parts(global_object, result->year, result->month, result->day, result->hour, result->minute, result->second, result->millisecond, result->microsecond, result->nanosecond); + if (vm.exception()) + return {}; + + // 6. If utc < −8.64 × 10^21 or utc > 8.64 × 10^21, then + if (utc->big_integer() < INSTANT_NANOSECONDS_MIN || utc->big_integer() > INSTANT_NANOSECONDS_MAX) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidEpochNanoseconds); + return {}; + } + + // 7. Let offsetNanoseconds be ? ParseTimeZoneOffsetString(offsetString). + auto offset_nanoseconds = parse_time_zone_offset_string(global_object, *offset_string); + if (vm.exception()) + return {}; + + // 8. Return utc − offsetNanoseconds. + return js_bigint(vm.heap(), utc->big_integer().minus(Crypto::SignedBigInteger::create_from(offset_nanoseconds))); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h index 7700295d93..2c8a0e7871 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -31,11 +32,13 @@ private: }; // -86400 * 10^17 -const auto INSTANT_NANOSECONDS_MIN = Crypto::SignedBigInteger::from_base(10, "-8640000000000000000000"); +const auto INSTANT_NANOSECONDS_MIN = "-8640000000000000000000"_sbigint; // +86400 * 10^17 -const auto INSTANT_NANOSECONDS_MAX = Crypto::SignedBigInteger::from_base(10, "8640000000000000000000"); +const auto INSTANT_NANOSECONDS_MAX = "8640000000000000000000"_sbigint; bool is_valid_epoch_nanoseconds(BigInt const& epoch_nanoseconds); -Object* create_temporal_instant(GlobalObject&, BigInt& nanoseconds, FunctionObject* new_target = nullptr); +Instant* create_temporal_instant(GlobalObject&, BigInt& nanoseconds, FunctionObject* new_target = nullptr); +Instant* to_temporal_instant(GlobalObject&, Value item); +BigInt* parse_temporal_instant(GlobalObject&, String const& iso_string); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp new file mode 100644 index 0000000000..bf18c9a5c2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/Temporal/Calendar.h> +#include <LibJS/Runtime/Temporal/PlainDate.h> +#include <LibJS/Runtime/Value.h> + +namespace JS::Temporal { + +// 3.5.5 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate +bool is_valid_iso_date(i32 year, i32 month, i32 day) +{ + // 1. Assert: year, month, and day are integers. + + // 2. If month < 1 or month > 12, then + if (month < 1 || month > 12) { + // a. Return false. + return false; + } + + // 3. Let daysInMonth be ! ISODaysInMonth(year, month). + auto days_in_month = iso_days_in_month(year, month); + + // 4. If day < 1 or day > daysInMonth, then + if (day < 1 || day > days_in_month) { + // a. Return false. + return false; + } + + // 5. Return true. + return true; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h new file mode 100644 index 0000000000..8471958743 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/Value.h> + +namespace JS::Temporal { + +bool is_valid_iso_date(i32 year, i32 month, i32 day); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp new file mode 100644 index 0000000000..3bb99eded6 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/Date.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Temporal/PlainDate.h> +#include <LibJS/Runtime/Temporal/PlainDateTime.h> +#include <LibJS/Runtime/Temporal/PlainTime.h> + +namespace JS::Temporal { + +// 5.5.1 GetEpochFromISOParts ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-getepochfromisoparts +BigInt* get_epoch_from_iso_parts(GlobalObject& global_object, i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond) +{ + auto& vm = global_object.vm(); + + // 1. Assert: year, month, day, hour, minute, second, millisecond, microsecond, and nanosecond are integers. + + // 2. Assert: ! IsValidISODate(year, month, day) is true. + VERIFY(is_valid_iso_date(year, month, day)); + + // 3. Assert: ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is true. + VERIFY(is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)); + + // 4. Let date be ! MakeDay(𝔽(year), 𝔽(month − 1), 𝔽(day)). + auto date = make_day(global_object, Value(year), Value(month - 1), Value(day)); + + // 5. Let time be ! MakeTime(𝔽(hour), 𝔽(minute), 𝔽(second), 𝔽(millisecond)). + auto time = make_time(global_object, Value(hour), Value(minute), Value(second), Value(millisecond)); + + // 6. Let ms be ! MakeDate(date, time). + auto ms = make_date(date, time); + + // 7. Assert: ms is finite. + VERIFY(ms.is_finite_number()); + + // 8. Return ℝ(ms) × 10^6 + microsecond × 10^3 + nanosecond. + return js_bigint(vm.heap(), Crypto::SignedBigInteger::create_from(static_cast<i64>(ms.as_double())).multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 }).plus(Crypto::SignedBigInteger::create_from((i64)microsecond * 1000)).plus(Crypto::SignedBigInteger(nanosecond))); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h new file mode 100644 index 0000000000..77f4b0a46c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/BigInt.h> + +namespace JS::Temporal { + +BigInt* get_epoch_from_iso_parts(GlobalObject&, i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp new file mode 100644 index 0000000000..993cd462b1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/Temporal/PlainTime.h> +#include <LibJS/Runtime/Value.h> + +namespace JS::Temporal { + +// 4.5.5 IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidtime +bool is_valid_time(i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond) +{ + // 1. Assert: hour, minute, second, millisecond, microsecond, and nanosecond are integers. + + // 2. If hour < 0 or hour > 23, then + if (hour < 0 || hour > 23) { + // a. Return false. + return false; + } + + // 3. If minute < 0 or minute > 59, then + if (minute < 0 || minute > 59) { + // a. Return false. + return false; + } + + // 4. If second < 0 or second > 59, then + if (second < 0 || second > 59) { + // a. Return false. + return false; + } + + // 5. If millisecond < 0 or millisecond > 999, then + if (millisecond < 0 || millisecond > 999) { + // a. Return false. + return false; + } + + // 6. If microsecond < 0 or microsecond > 999, then + if (microsecond < 0 || microsecond > 999) { + // a. Return false. + return false; + } + + // 7. If nanosecond < 0 or nanosecond > 999, then + if (nanosecond < 0 || nanosecond > 999) { + // a. Return false. + return false; + } + + // 8. Return true. + return true; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h new file mode 100644 index 0000000000..cbc842d383 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/Value.h> + +namespace JS::Temporal { + +bool is_valid_time(i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index f1ef6755af..f509f1cfde 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -4,9 +4,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <AK/DateTimeLexer.h> #include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/GlobalObject.h> -#include <LibJS/Runtime/Temporal/ISO8601.h> #include <LibJS/Runtime/Temporal/TimeZone.h> #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h> @@ -75,9 +75,9 @@ Object* create_temporal_time_zone(GlobalObject& global_object, String const& ide return {}; // 4. If identifier satisfies the syntax of a TimeZoneNumericUTCOffset (see 13.33), then - if (is_valid_time_zone_numeric_utc_offset(identifier)) { - // TODO: + if (is_valid_time_zone_numeric_utc_offset_syntax(identifier)) { // a. Set object.[[OffsetNanoseconds]] to ! ParseTimeZoneOffsetString(identifier). + object->set_offset_nanoseconds(parse_time_zone_offset_string(global_object, identifier)); } // 5. Else, else { @@ -92,4 +92,155 @@ Object* create_temporal_time_zone(GlobalObject& global_object, String const& ide return object; } +// https://tc39.es/proposal-temporal/#prod-TimeZoneNumericUTCOffset +static bool parse_time_zone_numeric_utc_offset_syntax(String const& offset_string, StringView& sign, StringView& hours, Optional<StringView>& minutes, Optional<StringView>& seconds, Optional<StringView>& fraction) +{ + DateTimeLexer lexer(offset_string); + auto sign_part = lexer.consume_sign(); + if (!sign_part.has_value()) + return false; + sign = *sign_part; + auto hours_part = lexer.consume_hours(); + if (!hours_part.has_value()) + return false; + hours = *hours_part; + if (!lexer.tell_remaining()) + return true; + auto uses_separator = lexer.consume_specific(':'); + minutes = lexer.consume_minutes_or_seconds(); + if (!minutes.has_value()) + return false; + if (!lexer.tell_remaining()) + return true; + if (lexer.consume_specific(':') != uses_separator) + return false; + seconds = lexer.consume_minutes_or_seconds(); + if (!seconds.has_value()) + return false; + if (!lexer.tell_remaining()) + return true; + if (!lexer.consume_specific('.') && !lexer.consume_specific(',')) + return false; + fraction = lexer.consume_fractional_seconds(); + return fraction.has_value(); +} + +bool is_valid_time_zone_numeric_utc_offset_syntax(String const& offset_string) +{ + StringView discarded; + Optional<StringView> optionally_discarded; + // FIXME: This is very wasteful + return parse_time_zone_numeric_utc_offset_syntax(offset_string, discarded, discarded, optionally_discarded, optionally_discarded, optionally_discarded); +} + +// 11.6.8 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetimezoneoffsetstring +double parse_time_zone_offset_string(GlobalObject& global_object, String const& offset_string) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(offsetString) is String. + + // 2. If offsetString does not satisfy the syntax of a TimeZoneNumericUTCOffset (see 13.33), then + // a. Throw a RangeError exception. + // 3. Let sign, hours, minutes, seconds, and fraction be the parts of offsetString produced respectively by the TimeZoneUTCOffsetSign, TimeZoneUTCOffsetHour, TimeZoneUTCOffsetMinute, TimeZoneUTCOffsetSecond, and TimeZoneUTCOffsetFraction productions, or undefined if not present. + StringView sign_part; + StringView hours_part; + Optional<StringView> minutes_part; + Optional<StringView> seconds_part; + Optional<StringView> fraction_part; + auto success = parse_time_zone_numeric_utc_offset_syntax(offset_string, sign_part, hours_part, minutes_part, seconds_part, fraction_part); + if (!success) { + vm.throw_exception<RangeError>(global_object, ErrorType::InvalidFormat, "TimeZone offset"); + return {}; + } + + // 4. If either hours or sign are undefined, throw a RangeError exception. + // NOTE: Both of these checks are always false, due to the handling of Step 2 + + double sign; + // 5. If sign is the code unit 0x002D (HYPHEN-MINUS) or 0x2212 (MINUS SIGN), then + if (sign_part.is_one_of("-", "\xE2\x88\x92")) { + // a. Set sign to −1. + sign = -1; + } + // 6. Else, + else { + // a. Set sign to 1. + sign = 1; + } + + // 7. Set hours to ! ToIntegerOrInfinity(hours). + auto hours = Value(js_string(vm, hours_part)).to_integer_or_infinity(global_object); + // 8. Set minutes to ! ToIntegerOrInfinity(minutes). + auto minutes = Value(js_string(vm, minutes_part.value_or(""sv))).to_integer_or_infinity(global_object); + // 9. Set seconds to ! ToIntegerOrInfinity(seconds). + auto seconds = Value(js_string(vm, seconds_part.value_or(""sv))).to_integer_or_infinity(global_object); + + double nanoseconds; + // 10. If fraction is not undefined, then + if (fraction_part.has_value()) { + // a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000". + auto fraction = String::formatted("{}000000000", *fraction_part); + // b. Let nanoseconds be the String value equal to the substring of fraction consisting of the code units with indices 0 (inclusive) through 9 (exclusive). + // c. Set nanoseconds to ! ToIntegerOrInfinity(nanoseconds). + nanoseconds = Value(js_string(vm, fraction_part->substring_view(0, 9))).to_integer_or_infinity(global_object); + } + // 11. Else, + else { + // a. Let nanoseconds be 0. + nanoseconds = 0; + } + // 12. Return sign × (((hours × 60 + minutes) × 60 + seconds) × 10^9 + nanoseconds). + return sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000 + nanoseconds); +} + +// 11.6.9 FormatTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formattimezoneoffsetstring +String format_time_zone_offset_string(double offset_nanoseconds) +{ + auto offset = static_cast<i64>(offset_nanoseconds); + + // 1. Assert: offsetNanoseconds is an integer. + VERIFY(offset == offset_nanoseconds); + + StringBuilder builder; + // 2. If offsetNanoseconds ≥ 0, let sign be "+"; otherwise, let sign be "-". + if (offset >= 0) + builder.append('+'); + else + builder.append('-'); + + // 3. Let nanoseconds be abs(offsetNanoseconds) modulo 10^9. + auto nanoseconds = abs(offset) % 1000000000; + + // 4. Let seconds be floor(offsetNanoseconds / 10^9) modulo 60. + auto seconds = (offset / 1000000000) % 60; + // 5. Let minutes be floor(offsetNanoseconds / (6 × 10^10)) modulo 60. + auto minutes = (offset / 60000000000) % 60; + // 6. Let hours be floor(offsetNanoseconds / (3.6 × 10^12)). + auto hours = offset / 3600000000000; + + // 7. Let h be hours, formatted as a two-digit decimal number, padded to the left with a zero if necessary. + builder.appendff("{:02}", hours); + // 8. Let m be minutes, formatted as a two-digit decimal number, padded to the left with a zero if necessary. + builder.appendff(":{:02}", minutes); + // 9. Let s be seconds, formatted as a two-digit decimal number, padded to the left with a zero if necessary. + // Handled by steps 10 & 11 + + // 10. If nanoseconds ≠ 0, then + if (nanoseconds != 0) { + // a. Let fraction be nanoseconds, formatted as a nine-digit decimal number, padded to the left with zeroes if necessary. + // b. Set fraction to the longest possible substring of fraction starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO). + // c. Let post be the string-concatenation of the code unit 0x003A (COLON), s, the code unit 0x002E (FULL STOP), and fraction. + builder.appendff(":{:02}.{:9}", seconds, nanoseconds); + } + // 11. Else if seconds ≠ 0, then + else if (seconds != 0) { + // a. Let post be the string-concatenation of the code unit 0x003A (COLON) and s. + builder.appendff(":{:02}", seconds); + } + + // 12. Return the string-concatenation of sign, h, the code unit 0x003A (COLON), m, and post. + return builder.to_string(); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h index 072a7ab456..d9c84ba516 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -14,7 +14,7 @@ namespace JS::Temporal { class TimeZone final : public Object { JS_OBJECT(TimeZone, Object); - // Needs to store values in the range -8.64 * 10^21 to 8.64 * 10^21 + // Needs to store values in the range -8.64 * 10^13 to 8.64 * 10^13 using OffsetType = double; public: @@ -23,7 +23,7 @@ public: String const& identifier() const { return m_identifier; } Optional<OffsetType> const& offset_nanoseconds() const { return m_offset_nanoseconds; } - void set_offset_nanoseconds(u32 offset_nanoseconds) { m_offset_nanoseconds = offset_nanoseconds; }; + void set_offset_nanoseconds(OffsetType offset_nanoseconds) { m_offset_nanoseconds = offset_nanoseconds; }; private: // 11.5 Properties of Temporal.TimeZone Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-timezone-instances @@ -39,5 +39,9 @@ bool is_valid_time_zone_name(String const& time_zone); String canonicalize_time_zone_name(String const& time_zone); String default_time_zone(); Object* create_temporal_time_zone(GlobalObject&, String const& identifier, FunctionObject* new_target = nullptr); +double parse_time_zone_offset_string(GlobalObject&, String const&); +String format_time_zone_offset_string(double offset_nanoseconds); + +bool is_valid_time_zone_numeric_utc_offset_syntax(String const&); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp index 62731fec27..3ffd8c2f29 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp @@ -5,7 +5,6 @@ */ #include <LibJS/Runtime/GlobalObject.h> -#include <LibJS/Runtime/Temporal/ISO8601.h> #include <LibJS/Runtime/Temporal/TimeZone.h> #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h> @@ -54,10 +53,14 @@ Value TimeZoneConstructor::construct(FunctionObject& new_target) String canonical; // 3. If identifier satisfies the syntax of a TimeZoneNumericUTCOffset (see 13.33), then - if (is_valid_time_zone_numeric_utc_offset(identifier)) { - // TODO: + if (is_valid_time_zone_numeric_utc_offset_syntax(identifier)) { // a. Let offsetNanoseconds be ? ParseTimeZoneOffsetString(identifier). + auto offset_nanoseconds = parse_time_zone_offset_string(global_object, identifier); + if (vm.exception()) + return {}; + // b. Let canonical be ! FormatTimeZoneOffsetString(offsetNanoseconds). + canonical = format_time_zone_offset_string(offset_nanoseconds); } // 4. Else, else { |