mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-24 02:12:09 -05:00
LibJS: Implement Temporal.Now.plainDate()
...and ten required AOs we didn't have yet: - BalanceISODate - BalanceISODateTime - BalanceISOYearMonth - BalanceTime - BuiltinTimeZoneGetPlainDateTimeFor - GetISOPartsFromEpoch - GetOffsetNanosecondsFor - ParseTemporalTimeZone - SystemDateTime - ToTemporalTimeZone
This commit is contained in:
parent
5512ff79f0
commit
c303bbde54
18 changed files with 507 additions and 1 deletions
|
@ -141,6 +141,7 @@ set(SOURCES
|
|||
Runtime/Temporal/PlainDateTimeConstructor.cpp
|
||||
Runtime/Temporal/PlainDateTimePrototype.cpp
|
||||
Runtime/Temporal/PlainTime.cpp
|
||||
Runtime/Temporal/PlainYearMonth.cpp
|
||||
Runtime/Temporal/Temporal.cpp
|
||||
Runtime/Temporal/TimeZone.cpp
|
||||
Runtime/Temporal/TimeZoneConstructor.cpp
|
||||
|
|
|
@ -282,6 +282,7 @@ namespace JS {
|
|||
P(parse) \
|
||||
P(parseFloat) \
|
||||
P(parseInt) \
|
||||
P(plainDate) \
|
||||
P(pop) \
|
||||
P(pow) \
|
||||
P(preventExtensions) \
|
||||
|
|
|
@ -174,6 +174,7 @@
|
|||
M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \
|
||||
M(TemporalInvalidISODate, "Invalid ISO date") \
|
||||
M(TemporalInvalidMonthCode, "Invalid month code") \
|
||||
M(TemporalInvalidOffsetNanosecondsValue, "Invalid offset nanoseconds value, must be in range -86400 * 10^9 to 86400 * 10^9") \
|
||||
M(TemporalInvalidPlainDate, "Invalid plain date") \
|
||||
M(TemporalInvalidPlainDateTime, "Invalid plain date time") \
|
||||
M(TemporalInvalidTime, "Invalid time") \
|
||||
|
|
|
@ -240,6 +240,7 @@ void GlobalObject::initialize_global_object()
|
|||
|
||||
m_array_prototype_values_function = &m_array_prototype->get_without_side_effects(vm.names.values).as_function();
|
||||
m_eval_function = &get_without_side_effects(vm.names.eval).as_function();
|
||||
m_temporal_time_zone_prototype_get_offset_nanoseconds_for_function = &m_temporal_time_zone_prototype->get_without_side_effects(vm.names.getOffsetNanosecondsFor).as_function();
|
||||
}
|
||||
|
||||
GlobalObject::~GlobalObject()
|
||||
|
@ -258,6 +259,7 @@ void GlobalObject::visit_edges(Visitor& visitor)
|
|||
visitor.visit(m_environment);
|
||||
visitor.visit(m_array_prototype_values_function);
|
||||
visitor.visit(m_eval_function);
|
||||
visitor.visit(m_temporal_time_zone_prototype_get_offset_nanoseconds_for_function);
|
||||
visitor.visit(m_throw_type_error_function);
|
||||
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
|
||||
FunctionObject* array_prototype_values_function() const { return m_array_prototype_values_function; }
|
||||
FunctionObject* eval_function() const { return m_eval_function; }
|
||||
FunctionObject* temporal_time_zone_prototype_get_offset_nanoseconds_for_function() const { return m_temporal_time_zone_prototype_get_offset_nanoseconds_for_function; }
|
||||
FunctionObject* throw_type_error_function() const { return m_throw_type_error_function; }
|
||||
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
|
@ -97,6 +98,7 @@ private:
|
|||
|
||||
FunctionObject* m_array_prototype_values_function { nullptr };
|
||||
FunctionObject* m_eval_function { nullptr };
|
||||
FunctionObject* m_temporal_time_zone_prototype_get_offset_nanoseconds_for_function { nullptr };
|
||||
FunctionObject* m_throw_type_error_function { nullptr };
|
||||
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/Calendar.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/Now.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <time.h>
|
||||
|
||||
|
@ -31,6 +34,7 @@ void Now::initialize(GlobalObject& global_object)
|
|||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(vm.names.timeZone, time_zone, 0, attr);
|
||||
define_native_function(vm.names.instant, instant, 0, attr);
|
||||
define_native_function(vm.names.plainDate, plain_date, 1, attr);
|
||||
}
|
||||
|
||||
// 2.1.1 Temporal.Now.timeZone ( ), https://tc39.es/proposal-temporal/#sec-temporal.now.timezone
|
||||
|
@ -47,6 +51,21 @@ JS_DEFINE_NATIVE_FUNCTION(Now::instant)
|
|||
return system_instant(global_object);
|
||||
}
|
||||
|
||||
// 2.1.7 Temporal.Now.plainDate ( calendar [ , temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindate
|
||||
JS_DEFINE_NATIVE_FUNCTION(Now::plain_date)
|
||||
{
|
||||
auto calendar = vm.argument(0);
|
||||
auto temporal_time_zone_like = vm.argument(1);
|
||||
|
||||
// 1. Let dateTime be ? SystemDateTime(temporalTimeZoneLike, calendar).
|
||||
auto* date_time = system_date_time(global_object, temporal_time_zone_like, calendar);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 2. Return ? CreateTemporalDate(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[Calendar]]).
|
||||
return create_temporal_date(global_object, date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->calendar());
|
||||
}
|
||||
|
||||
// 2.2.1 SystemTimeZone ( ), https://tc39.es/proposal-temporal/#sec-temporal-systemtimezone
|
||||
TimeZone* system_time_zone(GlobalObject& global_object)
|
||||
{
|
||||
|
@ -90,4 +109,33 @@ Instant* system_instant(GlobalObject& global_object)
|
|||
return create_temporal_instant(global_object, *ns);
|
||||
}
|
||||
|
||||
// 2.2.4 SystemDateTime ( temporalTimeZoneLike, calendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-systemdatetime
|
||||
PlainDateTime* system_date_time(GlobalObject& global_object, Value temporal_time_zone_like, Value calendar_like)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
Object* time_zone;
|
||||
|
||||
// 1. If temporalTimeZoneLike is undefined, then
|
||||
if (temporal_time_zone_like.is_undefined()) {
|
||||
// a. Let timeZone be ! SystemTimeZone().
|
||||
time_zone = system_time_zone(global_object);
|
||||
} else {
|
||||
// a. Let timeZone be ? ToTemporalTimeZone(temporalTimeZoneLike).
|
||||
time_zone = to_temporal_time_zone(global_object, temporal_time_zone_like);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
}
|
||||
|
||||
// 3. Let calendar be ? ToTemporalCalendar(calendarLike).
|
||||
auto* calendar = to_temporal_calendar(global_object, calendar_like);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 4. Let instant be ! SystemInstant().
|
||||
auto* instant = system_instant(global_object);
|
||||
|
||||
// 5. Return ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar).
|
||||
return builtin_time_zone_get_plain_date_time_for(global_object, *time_zone, *instant, *calendar);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,10 +21,12 @@ public:
|
|||
private:
|
||||
JS_DECLARE_NATIVE_FUNCTION(time_zone);
|
||||
JS_DECLARE_NATIVE_FUNCTION(instant);
|
||||
JS_DECLARE_NATIVE_FUNCTION(plain_date);
|
||||
};
|
||||
|
||||
TimeZone* system_time_zone(GlobalObject&);
|
||||
BigInt* system_utc_epoch_nanoseconds(GlobalObject&);
|
||||
Instant* system_instant(GlobalObject&);
|
||||
PlainDateTime* system_date_time(GlobalObject&, Value temporal_time_zone_like, Value calendar_like);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -10,6 +11,7 @@
|
|||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -220,6 +222,102 @@ bool is_valid_iso_date(i32 year, u8 month, u8 day)
|
|||
return true;
|
||||
}
|
||||
|
||||
// 3.5.6 BalanceISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodate
|
||||
ISODate balance_iso_date(i32 year, i32 month, i32 day)
|
||||
{
|
||||
// 1. Assert: year, month, and day are integers.
|
||||
|
||||
// 2. Let balancedYearMonth be ! BalanceISOYearMonth(year, month).
|
||||
auto balanced_year_month = balance_iso_year_month(year, month);
|
||||
|
||||
// 3. Set month to balancedYearMonth.[[Month]].
|
||||
month = balanced_year_month.month;
|
||||
|
||||
// 4. Set year to balancedYearMonth.[[Year]].
|
||||
year = balanced_year_month.year;
|
||||
|
||||
// 5. NOTE: To deal with negative numbers of days whose absolute value is greater than the number of days in a year, the following section subtracts years and adds days until the number of days is greater than −366 or −365.
|
||||
|
||||
i32 test_year;
|
||||
|
||||
// 6. If month > 2, then
|
||||
if (month > 2) {
|
||||
// a. Let testYear be year.
|
||||
test_year = year;
|
||||
}
|
||||
// 7. Else,
|
||||
else {
|
||||
// a. Let testYear be year − 1.
|
||||
test_year = year - 1;
|
||||
}
|
||||
|
||||
// 8. Repeat, while day < −1 × ! ISODaysInYear(testYear),
|
||||
while (day < -1 * iso_days_in_year(test_year)) {
|
||||
// a.Set day to day + !ISODaysInYear(testYear).
|
||||
day += iso_days_in_year(test_year);
|
||||
|
||||
// b.Set year to year − 1.
|
||||
year--;
|
||||
|
||||
// c.Set testYear to testYear − 1.
|
||||
test_year--;
|
||||
}
|
||||
|
||||
// 9. NOTE: To deal with numbers of days greater than the number of days in a year, the following section adds years and subtracts days until the number of days is less than 366 or 365.
|
||||
|
||||
// 10. Let testYear be year + 1.
|
||||
test_year = year + 1;
|
||||
|
||||
// 11. Repeat, while day > ! ISODaysInYear(testYear),
|
||||
while (day > iso_days_in_year(test_year)) {
|
||||
// a. Set day to day − ! ISODaysInYear(testYear).
|
||||
day -= iso_days_in_year(test_year);
|
||||
|
||||
// b. Set year to year + 1.
|
||||
year++;
|
||||
|
||||
// c. Set testYear to testYear + 1.
|
||||
test_year++;
|
||||
}
|
||||
|
||||
// 12. NOTE: To deal with negative numbers of days whose absolute value is greater than the number of days in the current month, the following section subtracts months and adds days until the number of days is greater than 0.
|
||||
|
||||
// 13. Repeat, while day < 1,
|
||||
while (day < 1) {
|
||||
// a. Set balancedYearMonth to ! BalanceISOYearMonth(year, month − 1).
|
||||
balanced_year_month = balance_iso_year_month(year, month - 1);
|
||||
|
||||
// b. Set year to balancedYearMonth.[[Year]].
|
||||
year = balanced_year_month.year;
|
||||
|
||||
// c. Set month to balancedYearMonth.[[Month]].
|
||||
month = balanced_year_month.month;
|
||||
|
||||
// d. Set day to day + ! ISODaysInMonth(year, month).
|
||||
day += iso_days_in_month(year, month);
|
||||
}
|
||||
|
||||
// 14. NOTE: To deal with numbers of days greater than the number of days in the current month, the following section adds months and subtracts days until the number of days is less than the number of days in the month.
|
||||
|
||||
// 15. Repeat, while day > ! ISODaysInMonth(year, month),
|
||||
while (day > iso_days_in_month(year, month)) {
|
||||
// a. Set day to day − ! ISODaysInMonth(year, month).
|
||||
day -= iso_days_in_month(year, month);
|
||||
|
||||
// b. Set balancedYearMonth to ! BalanceISOYearMonth(year, month + 1).
|
||||
balanced_year_month = balance_iso_year_month(year, month + 1);
|
||||
|
||||
// c. Set year to balancedYearMonth.[[Year]].
|
||||
year = balanced_year_month.year;
|
||||
|
||||
// d. Set month to balancedYearMonth.[[Month]].
|
||||
month = balanced_year_month.month;
|
||||
}
|
||||
|
||||
// 16. Return the new Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
|
||||
return ISODate { .year = year, .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
|
||||
}
|
||||
|
||||
// 3.5.10 CompareISODate ( y1, m1, d1, y2, m2, d2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
|
||||
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -34,10 +35,17 @@ private:
|
|||
Object& m_calendar; // [[Calendar]]
|
||||
};
|
||||
|
||||
struct ISODate {
|
||||
i32 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
};
|
||||
|
||||
PlainDate* create_temporal_date(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, Object& calendar, FunctionObject* new_target = nullptr);
|
||||
PlainDate* to_temporal_date(GlobalObject&, Value item, Object* options = nullptr);
|
||||
Optional<TemporalDate> regulate_iso_date(GlobalObject&, double year, double month, double day, String const& overflow);
|
||||
bool is_valid_iso_date(i32 year, u8 month, u8 day);
|
||||
ISODate balance_iso_date(i32 year, i32 month, i32 day);
|
||||
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2);
|
||||
|
||||
}
|
||||
|
|
|
@ -92,6 +92,25 @@ bool iso_date_time_within_limits(GlobalObject& global_object, i32 year, u8 month
|
|||
return true;
|
||||
}
|
||||
|
||||
// 5.5.5 BalanceISODateTime ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodatetime
|
||||
ISODateTime balance_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, i64 nanosecond)
|
||||
{
|
||||
// NOTE: The only use of this AO is in BuiltinTimeZoneGetPlainDateTimeFor, where we know that all values
|
||||
// but `nanosecond` are in their usual range, hence why that's the only outlier here. The range for that
|
||||
// is -86400000000000 to 86400000000999, so an i32 is not enough.
|
||||
|
||||
// 1. Assert: year, month, day, hour, minute, second, millisecond, microsecond, and nanosecond are integers.
|
||||
|
||||
// 2. Let balancedTime be ! BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond).
|
||||
auto balanced_time = balance_time(hour, minute, second, millisecond, microsecond, nanosecond);
|
||||
|
||||
// 3. Let balancedDate be ! BalanceISODate(year, month, day + balancedTime.[[Days]]).
|
||||
auto balanced_date = balance_iso_date(year, month, day + balanced_time.days);
|
||||
|
||||
// 4. Return the Record { [[Year]]: balancedDate.[[Year]], [[Month]]: balancedDate.[[Month]], [[Day]]: balancedDate.[[Day]], [[Hour]]: balancedTime.[[Hour]], [[Minute]]: balancedTime.[[Minute]], [[Second]]: balancedTime.[[Second]], [[Millisecond]]: balancedTime.[[Millisecond]], [[Microsecond]]: balancedTime.[[Microsecond]], [[Nanosecond]]: balancedTime.[[Nanosecond]] }.
|
||||
return ISODateTime { .year = balanced_date.year, .month = balanced_date.month, .day = balanced_date.day, .hour = balanced_time.hour, .minute = balanced_time.minute, .second = balanced_time.second, .millisecond = balanced_time.millisecond, .microsecond = balanced_time.microsecond, .nanosecond = balanced_time.nanosecond };
|
||||
}
|
||||
|
||||
// 5.5.6 CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldatetime
|
||||
PlainDateTime* create_temporal_date_time(GlobalObject& global_object, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, FunctionObject* new_target)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -48,6 +49,7 @@ private:
|
|||
|
||||
BigInt* get_epoch_from_iso_parts(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond);
|
||||
bool iso_date_time_within_limits(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond);
|
||||
ISODateTime balance_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, i64 nanosecond);
|
||||
PlainDateTime* create_temporal_date_time(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, FunctionObject* new_target = nullptr);
|
||||
|
||||
}
|
||||
|
|
|
@ -54,4 +54,57 @@ bool is_valid_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microseco
|
|||
return true;
|
||||
}
|
||||
|
||||
// 4.5.6 BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-balancetime
|
||||
DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 microsecond, i64 nanosecond)
|
||||
{
|
||||
// 1. Assert: hour, minute, second, millisecond, microsecond, and nanosecond are integers.
|
||||
|
||||
// 2. Set microsecond to microsecond + floor(nanosecond / 1000).
|
||||
microsecond += nanosecond / 1000;
|
||||
|
||||
// 3. Set nanosecond to nanosecond modulo 1000.
|
||||
nanosecond %= 1000;
|
||||
|
||||
// 4. Set millisecond to millisecond + floor(microsecond / 1000).
|
||||
millisecond += microsecond / 1000;
|
||||
|
||||
// 5. Set microsecond to microsecond modulo 1000.
|
||||
microsecond %= 1000;
|
||||
|
||||
// 6. Set second to second + floor(millisecond / 1000).
|
||||
second += millisecond / 1000;
|
||||
|
||||
// 7. Set millisecond to millisecond modulo 1000.
|
||||
millisecond %= 1000;
|
||||
|
||||
// 8. Set minute to minute + floor(second / 60).
|
||||
minute += second / 60;
|
||||
|
||||
// 9. Set second to second modulo 60.
|
||||
second %= 60;
|
||||
|
||||
// 10. Set hour to hour + floor(minute / 60).
|
||||
hour += minute / 60;
|
||||
|
||||
// 11. Set minute to minute modulo 60.
|
||||
minute %= 60;
|
||||
|
||||
// 12. Let days be floor(hour / 24).
|
||||
u8 days = hour / 24;
|
||||
|
||||
// 13. Set hour to hour modulo 24.
|
||||
hour %= 24;
|
||||
|
||||
// 14. Return the new Record { [[Days]]: days, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond }.
|
||||
return DaysAndTime {
|
||||
.days = static_cast<i32>(days),
|
||||
.hour = static_cast<u8>(hour),
|
||||
.minute = static_cast<u8>(minute),
|
||||
.second = static_cast<u8>(second),
|
||||
.millisecond = static_cast<u16>(millisecond),
|
||||
.microsecond = static_cast<u16>(microsecond),
|
||||
.nanosecond = static_cast<u16>(nanosecond),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,17 @@
|
|||
|
||||
namespace JS::Temporal {
|
||||
|
||||
struct DaysAndTime {
|
||||
i32 days;
|
||||
u8 hour;
|
||||
u8 minute;
|
||||
u8 second;
|
||||
u16 millisecond;
|
||||
u16 microsecond;
|
||||
u16 nanosecond;
|
||||
};
|
||||
|
||||
bool is_valid_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond);
|
||||
DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 microsecond, i64 nanosecond);
|
||||
|
||||
}
|
||||
|
|
26
Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp
Normal file
26
Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
// 9.5.5 BalanceISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
|
||||
ISOYearMonth balance_iso_year_month(i32 year, i32 month)
|
||||
{
|
||||
// 1. Assert: year and month are integers.
|
||||
|
||||
// 2. Set year to year + floor((month - 1) / 12).
|
||||
year += (month - 1) / 12;
|
||||
|
||||
// 3. Set month to (month − 1) modulo 12 + 1.
|
||||
month = (month - 1) % 12 + 1;
|
||||
|
||||
// 4. Return the new Record { [[Year]]: year, [[Month]]: month }.
|
||||
return ISOYearMonth { .year = year, .month = static_cast<u8>(month) };
|
||||
}
|
||||
|
||||
}
|
21
Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h
Normal file
21
Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
struct ISOYearMonth {
|
||||
i32 year;
|
||||
u8 month;
|
||||
};
|
||||
|
||||
ISOYearMonth balance_iso_year_month(i32 year, i32 month);
|
||||
|
||||
}
|
|
@ -5,8 +5,13 @@
|
|||
*/
|
||||
|
||||
#include <AK/DateTimeLexer.h>
|
||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||
#include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
|
||||
|
||||
|
@ -59,6 +64,26 @@ String default_time_zone()
|
|||
return "UTC";
|
||||
}
|
||||
|
||||
// 11.6.1 ParseTemporalTimeZone ( string ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezone
|
||||
String parse_temporal_time_zone(GlobalObject& global_object, String const& string)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Assert: Type(string) is String.
|
||||
|
||||
// 2. Let result be ? ParseTemporalTimeZoneString(string).
|
||||
auto result = parse_temporal_time_zone_string(global_object, string);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 3. If result.[[Z]] is not undefined, return "UTC".
|
||||
if (result->z)
|
||||
return "UTC";
|
||||
|
||||
// 4. Return result.[[Name]].
|
||||
return *result->name;
|
||||
}
|
||||
|
||||
// 11.6.2 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone
|
||||
TimeZone* create_temporal_time_zone(GlobalObject& global_object, String const& identifier, FunctionObject* new_target)
|
||||
{
|
||||
|
@ -92,6 +117,48 @@ TimeZone* create_temporal_time_zone(GlobalObject& global_object, String const& i
|
|||
return object;
|
||||
}
|
||||
|
||||
// 11.6.3 GetISOPartsFromEpoch ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getisopartsfromepoch
|
||||
Optional<ISODateTime> get_iso_parts_from_epoch(BigInt const& epoch_nanoseconds)
|
||||
{
|
||||
// 1. Let remainderNs be epochNanoseconds modulo 10^6.
|
||||
auto remainder_ns_bigint = epoch_nanoseconds.big_integer().divided_by(Crypto::UnsignedBigInteger { 1'000'000 }).remainder;
|
||||
auto remainder_ns = remainder_ns_bigint.to_base(10).to_int<i64>().value();
|
||||
|
||||
// 2. Let epochMilliseconds be (epochNanoseconds − remainderNs) / 10^6.
|
||||
auto epoch_milliseconds_bigint = epoch_nanoseconds.big_integer().minus(remainder_ns_bigint).divided_by(Crypto::UnsignedBigInteger { 1'000'000 }).quotient;
|
||||
auto epoch_milliseconds = (double)epoch_milliseconds_bigint.to_base(10).to_int<i64>().value();
|
||||
|
||||
// 3. Let year be ! YearFromTime(epochMilliseconds).
|
||||
auto year = year_from_time(epoch_milliseconds);
|
||||
|
||||
// 4. Let month be ! MonthFromTime(epochMilliseconds) + 1.
|
||||
auto month = static_cast<u8>(month_from_time(epoch_milliseconds) + 1);
|
||||
|
||||
// 5. Let day be ! DateFromTime(epochMilliseconds).
|
||||
auto day = date_from_time(epoch_milliseconds);
|
||||
|
||||
// 6. Let hour be ! HourFromTime(epochMilliseconds).
|
||||
auto hour = hour_from_time(epoch_milliseconds);
|
||||
|
||||
// 7. Let minute be ! MinFromTime(epochMilliseconds).
|
||||
auto minute = min_from_time(epoch_milliseconds);
|
||||
|
||||
// 8. Let second be ! SecFromTime(epochMilliseconds).
|
||||
auto second = sec_from_time(epoch_milliseconds);
|
||||
|
||||
// 9. Let millisecond be ! msFromTime(epochMilliseconds).
|
||||
auto millisecond = ms_from_time(epoch_milliseconds);
|
||||
|
||||
// 10. Let microsecond be floor(remainderNs / 1000) modulo 1000.
|
||||
auto microsecond = static_cast<u16>((remainder_ns / 1000) % 1000);
|
||||
|
||||
// 11. Let nanosecond be remainderNs modulo 1000.
|
||||
auto nanosecond = static_cast<u16>(remainder_ns % 1000);
|
||||
|
||||
// 12. Return the new Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond }.
|
||||
return ISODateTime { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond };
|
||||
}
|
||||
|
||||
// 11.6.5 GetIANATimeZoneOffsetNanoseconds ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneoffsetnanoseconds
|
||||
i64 get_iana_time_zone_offset_nanoseconds([[maybe_unused]] BigInt const& epoch_nanoseconds, [[maybe_unused]] String const& time_zone_identifier)
|
||||
{
|
||||
|
@ -252,4 +319,115 @@ String format_time_zone_offset_string(double offset_nanoseconds)
|
|||
return builder.to_string();
|
||||
}
|
||||
|
||||
// 11.6.10 ToTemporalTimeZone ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezone
|
||||
Object* to_temporal_time_zone(GlobalObject& global_object, Value temporal_time_zone_like)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. If Type(temporalTimeZoneLike) is Object, then
|
||||
if (temporal_time_zone_like.is_object()) {
|
||||
// TODO:
|
||||
// a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||
// i. Return temporalTimeZoneLike.[[TimeZone]].
|
||||
|
||||
// b. If ? HasProperty(temporalTimeZoneLike, "timeZone") is false, return temporalTimeZoneLike.
|
||||
auto has_property = temporal_time_zone_like.as_object().has_property(vm.names.timeZone);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
if (!has_property)
|
||||
return &temporal_time_zone_like.as_object();
|
||||
|
||||
// c. Set temporalTimeZoneLike to ? Get(temporalTimeZoneLike, "timeZone").
|
||||
temporal_time_zone_like = temporal_time_zone_like.as_object().get(vm.names.timeZone);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// d. If Type(temporalTimeZoneLike) is Object and ? HasProperty(temporalTimeZoneLike, "timeZone") is false, return temporalTimeZoneLike.
|
||||
if (temporal_time_zone_like.is_object()) {
|
||||
has_property = temporal_time_zone_like.as_object().has_property(vm.names.timeZone);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
if (!has_property)
|
||||
return &temporal_time_zone_like.as_object();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Let identifier be ? ToString(temporalTimeZoneLike).
|
||||
auto identifier = temporal_time_zone_like.to_string(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 3. Let result be ? ParseTemporalTimeZone(identifier).
|
||||
auto result = parse_temporal_time_zone(global_object, identifier);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 4. Return ? CreateTemporalTimeZone(result).
|
||||
return create_temporal_time_zone(global_object, result);
|
||||
}
|
||||
|
||||
// 11.6.11 GetOffsetNanosecondsFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor
|
||||
double get_offset_nanoseconds_for(GlobalObject& global_object, Object& time_zone, Instant& instant)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Let getOffsetNanosecondsFor be ? GetMethod(timeZone, "getOffsetNanosecondsFor").
|
||||
auto* get_offset_nanoseconds_for = Value(&time_zone).get_method(global_object, vm.names.getOffsetNanosecondsFor);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 2. If getOffsetNanosecondsFor is undefined, set getOffsetNanosecondsFor to %Temporal.TimeZone.prototype.getOffsetNanosecondsFor%.
|
||||
if (!get_offset_nanoseconds_for)
|
||||
get_offset_nanoseconds_for = global_object.temporal_time_zone_prototype_get_offset_nanoseconds_for_function();
|
||||
|
||||
// 3. Let offsetNanoseconds be ? Call(getOffsetNanosecondsFor, timeZone, « instant »).
|
||||
auto offset_nanoseconds_value = vm.call(*get_offset_nanoseconds_for, &time_zone, &instant);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 4. If Type(offsetNanoseconds) is not Number, throw a TypeError exception.
|
||||
if (!offset_nanoseconds_value.is_number()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::IsNotA, "Offset nanoseconds value", "number");
|
||||
return {};
|
||||
}
|
||||
|
||||
// 5. If ! IsIntegralNumber(offsetNanoseconds) is false, throw a RangeError exception.
|
||||
if (!offset_nanoseconds_value.is_integral_number()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::IsNotA, "Offset nanoseconds value", "integral number");
|
||||
return {};
|
||||
}
|
||||
|
||||
// 6. Set offsetNanoseconds to ℝ(offsetNanoseconds).
|
||||
auto offset_nanoseconds = offset_nanoseconds_value.as_double();
|
||||
|
||||
// 7. If abs(offsetNanoseconds) > 86400 × 10^9, throw a RangeError exception.
|
||||
if (fabs(offset_nanoseconds) > 86400000000000.0) {
|
||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidOffsetNanosecondsValue);
|
||||
return {};
|
||||
}
|
||||
|
||||
// 8. Return offsetNanoseconds.
|
||||
return offset_nanoseconds;
|
||||
}
|
||||
|
||||
// 11.6.13 BuiltinTimeZoneGetPlainDateTimeFor ( timeZone, instant, calendar ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetplaindatetimefor
|
||||
PlainDateTime* builtin_time_zone_get_plain_date_time_for(GlobalObject& global_object, Object& time_zone, Instant& instant, Object& calendar)
|
||||
{
|
||||
auto& vm = global_object.vm();
|
||||
|
||||
// 1. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant).
|
||||
auto offset_nanoseconds = get_offset_nanoseconds_for(global_object, time_zone, instant);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 2. Let result be ! GetISOPartsFromEpoch(instant.[[Nanoseconds]]).
|
||||
auto result = get_iso_parts_from_epoch(instant.nanoseconds());
|
||||
|
||||
// 3. Set result to ! BalanceISODateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]] + offsetNanoseconds).
|
||||
result = balance_iso_date_time(result->year, result->month, result->day, result->hour, result->minute, result->second, result->millisecond, result->microsecond, result->nanosecond + offset_nanoseconds);
|
||||
|
||||
// 4. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], calendar).
|
||||
return create_temporal_date_time(global_object, result->year, result->month, result->day, result->hour, result->minute, result->second, result->millisecond, result->microsecond, result->nanosecond, calendar);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/Optional.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
|
@ -38,10 +39,15 @@ private:
|
|||
bool is_valid_time_zone_name(String const& time_zone);
|
||||
String canonicalize_time_zone_name(String const& time_zone);
|
||||
String default_time_zone();
|
||||
String parse_temporal_time_zone(GlobalObject&, String const&);
|
||||
TimeZone* create_temporal_time_zone(GlobalObject&, String const& identifier, FunctionObject* new_target = nullptr);
|
||||
Optional<ISODateTime> get_iso_parts_from_epoch(BigInt const& epoch_nanoseconds);
|
||||
i64 get_iana_time_zone_offset_nanoseconds(BigInt const& epoch_nanoseconds, String const& time_zone_identifier);
|
||||
double parse_time_zone_offset_string(GlobalObject&, String const&);
|
||||
String format_time_zone_offset_string(double offset_nanoseconds);
|
||||
Object* to_temporal_time_zone(GlobalObject&, Value temporal_time_zone_like);
|
||||
double get_offset_nanoseconds_for(GlobalObject&, Object& time_zone, Instant&);
|
||||
PlainDateTime* builtin_time_zone_get_plain_date_time_for(GlobalObject&, Object& time_zone, Instant&, Object& calendar);
|
||||
|
||||
bool is_valid_time_zone_numeric_utc_offset_syntax(String const&);
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Now.plainDate).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const plainDate = Temporal.Now.plainDate(calendar);
|
||||
expect(plainDate).toBeInstanceOf(Temporal.PlainDate);
|
||||
expect(plainDate.calendar).toBe(calendar);
|
||||
});
|
||||
|
||||
test("custom time zone", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const timeZone = {
|
||||
getOffsetNanosecondsFor() {
|
||||
return 86400000000000;
|
||||
},
|
||||
};
|
||||
const plainDate = Temporal.Now.plainDate(calendar);
|
||||
const plainDateWithOffset = Temporal.Now.plainDate(calendar, timeZone);
|
||||
// Yes, this will fail if a day, month, or year change happens between the above two lines :^)
|
||||
expect(plainDateWithOffset.year).toBe(plainDate.year);
|
||||
expect(plainDateWithOffset.month).toBe(plainDate.month);
|
||||
expect(plainDateWithOffset.day).toBe(plainDate.day + 1);
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue