diff options
Diffstat (limited to 'Userland/Libraries')
9 files changed, 499 insertions, 483 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Date.cpp b/Userland/Libraries/LibJS/Runtime/Date.cpp index 14683138a9..b3d59f1c3e 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.cpp +++ b/Userland/Libraries/LibJS/Runtime/Date.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2022, Tim Flynn <trflynn89@pm.me> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -20,6 +21,11 @@ Date* Date::create(GlobalObject& global_object, Core::DateTime datetime, i16 mil return global_object.heap().allocate<Date>(global_object, datetime, milliseconds, is_invalid, *global_object.date_prototype()); } +Date* Date::create(GlobalObject& global_object, double date_value) +{ + return global_object.heap().allocate<Date>(global_object, date_value, *global_object.date_prototype()); +} + Date::Date(Core::DateTime datetime, i16 milliseconds, bool is_invalid, Object& prototype) : Object(prototype) , m_datetime(datetime) @@ -28,6 +34,12 @@ Date::Date(Core::DateTime datetime, i16 milliseconds, bool is_invalid, Object& p { } +Date::Date(double date_value, Object& prototype) + : Object(prototype) + , m_date_value(date_value) +{ +} + Date::~Date() { } @@ -84,9 +96,7 @@ String Date::gmt_date_string() const String Date::iso_date_string() const { - auto tm = to_utc_tm(); - int year = tm.tm_year + 1900; - int month = tm.tm_mon + 1; + int year = year_from_time(m_date_value); StringBuilder builder; if (year < 0) @@ -96,17 +106,17 @@ String Date::iso_date_string() const else builder.appendff("{:04}", year); builder.append('-'); - builder.appendff("{:02}", month); + builder.appendff("{:02}", month_from_time(m_date_value) + 1); builder.append('-'); - builder.appendff("{:02}", tm.tm_mday); + builder.appendff("{:02}", date_from_time(m_date_value)); builder.append('T'); - builder.appendff("{:02}", tm.tm_hour); + builder.appendff("{:02}", hour_from_time(m_date_value)); builder.append(':'); - builder.appendff("{:02}", tm.tm_min); + builder.appendff("{:02}", min_from_time(m_date_value)); builder.append(':'); - builder.appendff("{:02}", tm.tm_sec); + builder.appendff("{:02}", sec_from_time(m_date_value)); builder.append('.'); - builder.appendff("{:03}", m_milliseconds); + builder.appendff("{:03}", ms_from_time(m_date_value)); builder.append('Z'); return builder.build(); diff --git a/Userland/Libraries/LibJS/Runtime/Date.h b/Userland/Libraries/LibJS/Runtime/Date.h index 1829bcbc48..7072ae4e06 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.h +++ b/Userland/Libraries/LibJS/Runtime/Date.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2022, Tim Flynn <trflynn89@pm.me> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -19,11 +20,16 @@ public: static constexpr double time_clip = 8.64e15; static Date* create(GlobalObject&, Core::DateTime, i16 milliseconds, bool is_invalid); + static Date* create(GlobalObject&, double date_value); static Date* now(GlobalObject&); Date(Core::DateTime datetime, i16 milliseconds, bool is_invalid, Object& prototype); + Date(double date_value, Object& prototype); virtual ~Date() override; + double date_value() const { return m_date_value; } + void set_date_value(double value) { m_date_value = value; } + Core::DateTime& datetime() { return m_datetime; } const Core::DateTime& datetime() const { return m_datetime; } @@ -74,20 +80,14 @@ public: String locale_string() const { return m_datetime.to_string(); } String locale_time_string() const { return m_datetime.to_string("%H:%M:%S"); } - // https://tc39.es/ecma262/#sec-properties-of-date-instances - // [[DateValue]] - double date_value() const - { - return m_is_invalid - ? AK::NaN<double> - : static_cast<double>(m_datetime.timestamp() * 1000 + m_milliseconds); - } - private: tm to_utc_tm() const; + double m_date_value { 0 }; // [[DateValue]] + Core::DateTime m_datetime; i16 m_milliseconds; + bool m_is_invalid { false }; }; diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp index a49d74f9b5..7884376ba9 100644 --- a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020, Linus Groh <linusg@serenityos.org> * Copyright (c) 2020, Nico Weber <thakis@chromium.org> * Copyright (c) 2021, Petróczi Zoltán <petroczizoltan@tutanota.com> + * Copyright (c) 2022, Tim Flynn <trflynn89@pm.me> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -13,6 +14,7 @@ #include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/Date.h> #include <LibJS/Runtime/DateConstructor.h> +#include <LibJS/Runtime/DatePrototype.h> #include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/VM.h> #include <sys/time.h> @@ -156,26 +158,15 @@ DateConstructor::~DateConstructor() { } -struct DatetimeAndMilliseconds { - Core::DateTime datetime; - i16 milliseconds { 0 }; -}; - -static DatetimeAndMilliseconds now() -{ - struct timeval tv; - gettimeofday(&tv, nullptr); - auto datetime = Core::DateTime::now(); - auto milliseconds = static_cast<i16>(tv.tv_usec / 1000); - return { datetime, milliseconds }; -} - // 21.4.2.1 Date ( ...values ), https://tc39.es/ecma262/#sec-date ThrowCompletionOr<Value> DateConstructor::call() { - auto [datetime, milliseconds] = JS::now(); - auto* date = Date::create(global_object(), datetime, milliseconds, false); - return js_string(heap(), date->string()); + // 1. If NewTarget is undefined, then + // a. Let now be the time value (UTC) identifying the current time. + auto now = AK::Time::now_realtime().to_milliseconds(); + + // b. Return ToDateString(now). + return js_string(vm(), to_date_string(now)); } // 21.4.2.1 Date ( ...values ), https://tc39.es/ecma262/#sec-date @@ -184,91 +175,94 @@ ThrowCompletionOr<Object*> DateConstructor::construct(FunctionObject& new_target auto& vm = this->vm(); auto& global_object = this->global_object(); + Value date_value; + + // 2. Let numberOfArgs be the number of elements in values. + // 3. If numberOfArgs = 0, then if (vm.argument_count() == 0) { - auto [datetime, milliseconds] = JS::now(); - return TRY(ordinary_create_from_constructor<Date>(global_object, new_target, &GlobalObject::date_prototype, datetime, milliseconds, false)); + // a. Let dv be the time value (UTC) identifying the current time. + auto now = AK::Time::now_realtime().to_milliseconds(); + date_value = Value(static_cast<double>(now)); } + // 4. Else if numberOfArgs = 1, then + else if (vm.argument_count() == 1) { + // a. Let value be values[0]. + auto value = vm.argument(0); + Value time_value; - auto create_invalid_date = [&global_object, &new_target]() -> ThrowCompletionOr<Date*> { - auto datetime = Core::DateTime::create(1970, 1, 1, 0, 0, 0); - auto milliseconds = static_cast<i16>(0); - return ordinary_create_from_constructor<Date>(global_object, new_target, &GlobalObject::date_prototype, datetime, milliseconds, true); - }; + // b. If Type(value) is Object and value has a [[DateValue]] internal slot, then + if (value.is_object() && is<Date>(value.as_object())) { + // i. Let tv be ! thisTimeValue(value). + time_value = MUST(this_time_value(global_object, value)); + } + // c. Else, + else { + // i. Let v be ? ToPrimitive(value). + auto primitive = TRY(value.to_primitive(global_object)); + + // ii. If Type(v) is String, then + if (primitive.is_string()) { + // 1. Assert: The next step never returns an abrupt completion because Type(v) is String. + // 2. Let tv be the result of parsing v as a date, in exactly the same manner as for the parse method (21.4.3.2). + time_value = parse_date_string(global_object, primitive.as_string().string()); + } + // iii. Else, + else { + // 1. Let tv be ? ToNumber(v). + time_value = TRY(primitive.to_number(global_object)); + } + } - if (vm.argument_count() == 1) { - auto value = vm.argument(0); - if (value.is_string()) - value = parse_date_string(global_object, value.as_string().string()); - else - value = TRY(value.to_number(global_object)); - - if (!value.is_finite_number()) - return TRY(create_invalid_date()); - - // A timestamp since the epoch, in UTC. - double value_as_double = value.as_double(); - if (value_as_double > Date::time_clip) - return TRY(create_invalid_date()); - auto datetime = Core::DateTime::from_timestamp(static_cast<time_t>(value_as_double / 1000)); - auto milliseconds = static_cast<i16>(fmod(value_as_double, 1000)); - return TRY(ordinary_create_from_constructor<Date>(global_object, new_target, &GlobalObject::date_prototype, datetime, milliseconds, false)); + // d. Let dv be TimeClip(tv). + date_value = time_clip(global_object, time_value); } + // 5. Else, + else { + // a. Assert: numberOfArgs ≥ 2. + // b. Let y be ? ToNumber(values[0]). + auto year = TRY(vm.argument(0).to_number(global_object)); + // c. Let m be ? ToNumber(values[1]). + auto month = TRY(vm.argument(1).to_number(global_object)); + + auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { + return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); + }; + + // d. If numberOfArgs > 2, let dt be ? ToNumber(values[2]); else let dt be 1𝔽. + auto date = TRY(arg_or(2, 1)); + // e. If numberOfArgs > 3, let h be ? ToNumber(values[3]); else let h be +0𝔽. + auto hours = TRY(arg_or(3, 0)); + // f. If numberOfArgs > 4, let min be ? ToNumber(values[4]); else let min be +0𝔽. + auto minutes = TRY(arg_or(4, 0)); + // g. If numberOfArgs > 5, let s be ? ToNumber(values[5]); else let s be +0𝔽. + auto seconds = TRY(arg_or(5, 0)); + // h. If numberOfArgs > 6, let milli be ? ToNumber(values[6]); else let milli be +0𝔽. + auto milliseconds = TRY(arg_or(6, 0)); + + // i. If y is NaN, let yr be NaN. + // j. Else, + if (!year.is_nan()) { + // i. Let yi be ! ToIntegerOrInfinity(y). + auto year_double = MUST(year.to_integer_or_infinity(global_object)); + + // ii. If 0 ≤ yi ≤ 99, let yr be 1900𝔽 + 𝔽(yi); otherwise, let yr be y. + if (0 <= year_double && year_double <= 99) + year = Value(1900 + year_double); + } - // A date/time in components, in local time. - auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { - return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); - }; + // k. Let finalDate be MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)). + auto day = make_day(global_object, year, month, date); + auto time = make_time(global_object, hours, minutes, seconds, milliseconds); + auto final_date = make_date(day, time); - auto year_value = TRY(vm.argument(0).to_number(global_object)); - if (!year_value.is_finite_number()) - return TRY(create_invalid_date()); - auto year = year_value.as_i32(); - - auto month_index_value = TRY(vm.argument(1).to_number(global_object)); - if (!month_index_value.is_finite_number()) - return TRY(create_invalid_date()); - auto month_index = month_index_value.as_i32(); - - auto day_value = TRY(arg_or(2, 1)); - if (!day_value.is_finite_number()) - return TRY(create_invalid_date()); - auto day = day_value.as_i32(); - - auto hours_value = TRY(arg_or(3, 0)); - if (!hours_value.is_finite_number()) - return TRY(create_invalid_date()); - auto hours = hours_value.as_i32(); - - auto minutes_value = TRY(arg_or(4, 0)); - if (!minutes_value.is_finite_number()) - return TRY(create_invalid_date()); - auto minutes = minutes_value.as_i32(); - - auto seconds_value = TRY(arg_or(5, 0)); - if (!seconds_value.is_finite_number()) - return TRY(create_invalid_date()); - auto seconds = seconds_value.as_i32(); - - auto milliseconds_value = TRY(arg_or(6, 0)); - if (!milliseconds_value.is_finite_number()) - return TRY(create_invalid_date()); - auto milliseconds = milliseconds_value.as_i32(); - - seconds += milliseconds / 1000; - milliseconds %= 1000; - if (milliseconds < 0) { - seconds -= 1; - milliseconds += 1000; + // l. Let dv be TimeClip(UTC(finalDate)). + date_value = time_clip(global_object, Value(utc_time(final_date.as_double()))); } - if (year >= 0 && year <= 99) - year += 1900; - int month = month_index + 1; - auto datetime = Core::DateTime::create(year, month, day, hours, minutes, seconds); - auto time = datetime.timestamp() * 1000.0 + milliseconds; - if (time > Date::time_clip) - return TRY(create_invalid_date()); - return TRY(ordinary_create_from_constructor<Date>(global_object, new_target, &GlobalObject::date_prototype, datetime, milliseconds, false)); + // 6. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Date.prototype%", « [[DateValue]] »). + // 7. Set O.[[DateValue]] to dv. + // 8. Return O. + return TRY(ordinary_create_from_constructor<Date>(global_object, new_target, &GlobalObject::date_prototype, date_value.as_double())); } // 21.4.3.1 Date.now ( ), https://tc39.es/ecma262/#sec-date.now diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp index 5af3da296e..713d2f4243 100644 --- a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021, Petróczi Zoltán <petroczizoltan@tutanota.com> * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * Copyright (c) 2022, Tim Flynn <trflynn89@pm.me> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -125,100 +126,120 @@ ThrowCompletionOr<Value> this_time_value(GlobalObject& global_object, Value valu // 21.4.4.2 Date.prototype.getDate ( ), https://tc39.es/ecma262/#sec-date.prototype.getdate JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_date) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->date()); + // 3. Return DateFromTime(LocalTime(t)). + return Value(date_from_time(local_time(time.as_double()))); } // 21.4.4.3 Date.prototype.getDay ( ), https://tc39.es/ecma262/#sec-date.prototype.getday JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_day) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->day()); + // 3. Return WeekDay(LocalTime(t)). + return Value(week_day(local_time(time.as_double()))); } // 21.4.4.4 Date.prototype.getFullYear ( ), https://tc39.es/ecma262/#sec-date.prototype.getfullyear JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_full_year) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->year()); + // 3. Return YearFromTime(LocalTime(t)). + return Value(year_from_time(local_time(time.as_double()))); } // 21.4.4.5 Date.prototype.getHours ( ), https://tc39.es/ecma262/#sec-date.prototype.gethours JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_hours) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->hours()); + // 3. Return HourFromTime(LocalTime(t)). + return Value(hour_from_time(local_time(time.as_double()))); } // 21.4.4.6 Date.prototype.getMilliseconds ( ), https://tc39.es/ecma262/#sec-date.prototype.getmilliseconds JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_milliseconds) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->milliseconds()); + // 3. Return msFromTime(LocalTime(t)). + return Value(ms_from_time(local_time(time.as_double()))); } // 21.4.4.7 Date.prototype.getMinutes ( ), https://tc39.es/ecma262/#sec-date.prototype.getminutes JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_minutes) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->minutes()); + // 3. Return MinFromTime(LocalTime(t)). + return Value(min_from_time(local_time(time.as_double()))); } // 21.4.4.8 Date.prototype.getMonth ( ), https://tc39.es/ecma262/#sec-date.prototype.getmonth JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_month) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->month()); + // 3. Return MonthFromTime(LocalTime(t)). + return Value(month_from_time(local_time(time.as_double()))); } // 21.4.4.9 Date.prototype.getSeconds ( ), https://tc39.es/ecma262/#sec-date.prototype.getseconds JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_seconds) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->seconds()); + // 3. Return SecFromTime(LocalTime(t)). + return Value(sec_from_time(local_time(time.as_double()))); } // 21.4.4.10 Date.prototype.getTime ( ), https://tc39.es/ecma262/#sec-date.prototype.gettime JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_time) { - auto* this_object = TRY(typed_this_object(global_object)); - - if (this_object->is_invalid()) - return js_nan(); - - return Value(this_object->time()); + // 1. Return ? thisTimeValue(this value). + return this_time_value(global_object, vm.this_value(global_object)); } // 21.4.4.11 Date.prototype.getTimezoneOffset ( ), https://tc39.es/ecma262/#sec-date.prototype.gettimezoneoffset @@ -226,7 +247,7 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_timezone_offset) { auto* this_object = TRY(typed_this_object(global_object)); - if (this_object->is_invalid()) + if (!Value(this_object->date_value()).is_finite_number()) return js_nan(); // FIXME: Make this actually do something once we support timezones instead of just UTC @@ -236,395 +257,380 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_timezone_offset) // 21.4.4.12 Date.prototype.getUTCDate ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcdate JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_date) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_date()); + // 3. Return DateFromTime(t). + return Value(date_from_time(time.as_double())); } // 21.4.4.13 Date.prototype.getUTCDay ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcday JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_day) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_day()); + // 3. Return WeekDay(t). + return Value(week_day(time.as_double())); } // 21.4.4.14 Date.prototype.getUTCFullYear ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcfullyear JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_full_year) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_full_year()); + // 3. Return YearFromTime(t). + return Value(year_from_time(time.as_double())); } // 21.4.4.15 Date.prototype.getUTCHours ( ), https://tc39.es/ecma262/#sec-date.prototype.getutchours JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_hours) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_hours()); + // 3. Return HourFromTime(t). + return Value(hour_from_time(time.as_double())); } // 21.4.4.16 Date.prototype.getUTCMilliseconds ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcmilliseconds JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_milliseconds) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_milliseconds()); + // 3. Return msFromTime(t). + return Value(ms_from_time(time.as_double())); } // 21.4.4.17 Date.prototype.getUTCMinutes ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcminutes JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_minutes) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_minutes()); + // 3. Return MinFromTime(t). + return Value(min_from_time(time.as_double())); } // 21.4.4.18 Date.prototype.getUTCMonth ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcmonth JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_month) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->utc_month()); + // 3. Return MonthFromTime(t). + return Value(month_from_time(time.as_double())); } // 21.4.4.19 Date.prototype.getUTCSeconds ( ), https://tc39.es/ecma262/#sec-date.prototype.getutcseconds JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_seconds) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; + + // 3. Return SecFromTime(t). + return Value(sec_from_time(time.as_double())); +} + +template<typename T> +static ThrowCompletionOr<Value> argument_or_value(GlobalObject& global_object, size_t index, T fallback) +{ + auto& vm = global_object.vm(); + + if (vm.argument_count() > index) + return vm.argument(index).to_number(global_object); - return Value(this_object->utc_seconds()); + return Value(fallback); } // 21.4.4.20 Date.prototype.setDate ( date ), https://tc39.es/ecma262/#sec-date.prototype.setdate JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_date) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be LocalTime(? thisTimeValue(this value)). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); + auto time = local_time(this_time.as_double()); - auto& datetime = this_object->datetime(); + // 2. Let dt be ? ToNumber(date). + auto date = TRY(vm.argument(0).to_number(global_object)); - auto new_date_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_date_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_date = new_date_value.as_i32(); + // 3. Let newDate be MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)). + auto year = Value(year_from_time(time)); + auto month = Value(month_from_time(time)); - datetime.set_time(datetime.year(), datetime.month(), new_date, datetime.hour(), datetime.minute(), datetime.second()); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + auto day = make_day(global_object, year, month, date); + auto new_date = make_date(day, Value(time_within_day(time))); + + // 4. Let u be TimeClip(UTC(newDate)). + new_date = time_clip(global_object, Value(utc_time(new_date.as_double()))); - this_object->set_is_invalid(false); - return Value(this_object->time()); + // 5. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(new_date.as_double()); + + // 6. Return u. + return new_date; } // 21.4.4.21 Date.prototype.setFullYear ( year [ , month [ , date ] ] ), https://tc39.es/ecma262/#sec-date.prototype.setfullyear JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_full_year) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); - auto& datetime = this_object->datetime(); + // 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). + double time = 0; + if (!this_time.is_nan()) + time = local_time(this_time.as_double()); - auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { - return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); - }; + // 3. Let y be ? ToNumber(year). + auto year = TRY(vm.argument(0).to_number(global_object)); - auto new_year_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_year_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_year = new_year_value.as_i32(); + // 4. If month is not present, let m be MonthFromTime(t); otherwise, let m be ? ToNumber(month). + auto month = TRY(argument_or_value(global_object, 1, month_from_time(time))); - auto new_month_value = TRY(arg_or(1, datetime.month() - 1)); - if (!new_month_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_month = new_month_value.as_i32() + 1; // JS Months: 0 - 11, DateTime months: 1-12 + // 5. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date). + auto date = TRY(argument_or_value(global_object, 2, date_from_time(time))); - auto new_day_value = TRY(arg_or(2, datetime.day())); - if (!new_day_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_day = new_day_value.as_i32(); + // 6. Let newDate be MakeDate(MakeDay(y, m, dt), TimeWithinDay(t)). + auto day = make_day(global_object, year, month, date); + auto new_date = make_date(day, Value(time_within_day(time))); - datetime.set_time(new_year, new_month, new_day, datetime.hour(), datetime.minute(), datetime.second()); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 7. Let u be TimeClip(UTC(newDate)). + new_date = time_clip(global_object, Value(utc_time(new_date.as_double()))); - this_object->set_is_invalid(false); + // 8. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(new_date.as_double()); - return Value(this_object->time()); + // 9. Return u. + return new_date; } // 21.4.4.22 Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] ), https://tc39.es/ecma262/#sec-date.prototype.sethours JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_hours) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be LocalTime(? thisTimeValue(this value)). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); + auto time = local_time(this_time.as_double()); - auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { - return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); - }; + // 2. Let h be ? ToNumber(hour). + auto hour = TRY(vm.argument(0).to_number(global_object)); - auto& datetime = this_object->datetime(); + // 3. If min is not present, let m be MinFromTime(t); otherwise, let m be ? ToNumber(min). + auto minute = TRY(argument_or_value(global_object, 1, min_from_time(time))); - auto new_hours_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_hours_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_hours = new_hours_value.as_i32(); + // 4. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec). + auto second = TRY(argument_or_value(global_object, 2, sec_from_time(time))); - auto new_minutes_value = TRY(arg_or(1, datetime.minute())); - if (!new_minutes_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_minutes = new_minutes_value.as_i32(); + // 5. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms). + auto millisecond = TRY(argument_or_value(global_object, 3, ms_from_time(time))); - auto new_seconds_value = TRY(arg_or(2, datetime.second())); - if (!new_seconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_seconds = new_seconds_value.as_i32(); - - auto new_milliseconds_value = TRY(arg_or(3, this_object->milliseconds())); - if (!new_milliseconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_milliseconds = new_milliseconds_value.as_i32(); + // 6. Let date be MakeDate(Day(t), MakeTime(h, m, s, milli)). + auto new_time = make_time(global_object, hour, minute, second, millisecond); + auto date = make_date(Value(day(time)), new_time); - new_seconds += new_milliseconds / 1000; - this_object->set_milliseconds(new_milliseconds % 1000); + // 7. Let u be TimeClip(UTC(date)). + date = time_clip(global_object, Value(utc_time(date.as_double()))); - datetime.set_time(datetime.year(), datetime.month(), datetime.day(), new_hours, new_minutes, new_seconds); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 8. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(date.as_double()); - this_object->set_is_invalid(false); - return Value(this_object->time()); + // 9. Return u. + return date; } // 21.4.4.23 Date.prototype.setMilliseconds ( ms ), https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_milliseconds) { - auto* this_object = TRY(typed_this_object(global_object)); - - auto new_milliseconds_value = TRY(vm.argument(0).to_number(global_object)); - - if (!new_milliseconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 1. Let t be LocalTime(? thisTimeValue(this value)). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); + auto time = local_time(this_time.as_double()); - auto new_milliseconds = new_milliseconds_value.as_i32(); + // 2. Set ms to ? ToNumber(ms). + auto millisecond = TRY(vm.argument(0).to_number(global_object)); - this_object->set_milliseconds(new_milliseconds % 1000); + // 3. Let time be MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms). + auto hour = Value(hour_from_time(time)); + auto minute = Value(min_from_time(time)); + auto second = Value(sec_from_time(time)); - auto added_seconds = new_milliseconds / 1000; - if (added_seconds > 0) { - auto& datetime = this_object->datetime(); - datetime.set_time(datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), datetime.second() + added_seconds); - } + auto new_time = make_time(global_object, hour, minute, second, millisecond); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 4. Let u be TimeClip(UTC(MakeDate(Day(t), time))). + auto date = make_date(Value(day(time)), new_time); + date = time_clip(global_object, Value(utc_time(date.as_double()))); - this_object->set_is_invalid(false); + // 5. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(date.as_double()); - return Value(this_object->time()); + // 6. Return u. + return date; } // 21.4.4.24 Date.prototype.setMinutes ( min [ , sec [ , ms ] ] ), https://tc39.es/ecma262/#sec-date.prototype.setminutes JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_minutes) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be LocalTime(? thisTimeValue(this value)). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); + auto time = local_time(this_time.as_double()); - auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { - return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); - }; + // 2. Let m be ? ToNumber(min). + auto minute = TRY(vm.argument(0).to_number(global_object)); - auto& datetime = this_object->datetime(); + // 3. If sec is not present, let s be SecFromTime(t); otherwise, let s be ? ToNumber(sec). + auto second = TRY(argument_or_value(global_object, 1, sec_from_time(time))); - auto new_minutes_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_minutes_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_minutes = new_minutes_value.as_i32(); + // 4. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms). + auto millisecond = TRY(argument_or_value(global_object, 2, ms_from_time(time))); - auto new_seconds_value = TRY(arg_or(1, datetime.second())); - if (!new_seconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_seconds = new_seconds_value.as_i32(); + // 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)). + auto hour = Value(hour_from_time(time)); - auto new_milliseconds_value = TRY(arg_or(2, this_object->milliseconds())); - if (!new_milliseconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_milliseconds = new_milliseconds_value.as_i32(); + auto new_time = make_time(global_object, hour, minute, second, millisecond); + auto date = make_date(Value(day(time)), new_time); - new_seconds += new_milliseconds / 1000; - this_object->set_milliseconds(new_milliseconds % 1000); + // 6. Let u be TimeClip(UTC(date)). + date = time_clip(global_object, Value(utc_time(date.as_double()))); - datetime.set_time(datetime.year(), datetime.month(), datetime.day(), datetime.hour(), new_minutes, new_seconds); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 7. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(date.as_double()); - this_object->set_is_invalid(false); - return Value(this_object->time()); + // 8. Return u. + return date; } // 21.4.4.25 Date.prototype.setMonth ( month [ , date ] ), https://tc39.es/ecma262/#sec-date.prototype.setmonth JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_month) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be LocalTime(? thisTimeValue(this value)). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); + auto time = local_time(this_time.as_double()); - auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { - return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); - }; + // 2. Let m be ? ToNumber(month). + auto month = TRY(vm.argument(0).to_number(global_object)); - auto& datetime = this_object->datetime(); + // 3. If date is not present, let dt be DateFromTime(t); otherwise, let dt be ? ToNumber(date). + auto date = TRY(argument_or_value(global_object, 1, date_from_time(time))); - auto new_month_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_month_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_month = new_month_value.as_i32() + 1; // JS Months: 0 - 11, DateTime months: 1-12 + // 4. Let newDate be MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t)). + auto year = Value(year_from_time(time)); - auto new_date_value = TRY(arg_or(1, this_object->date())); - if (!new_date_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_date = new_date_value.as_i32(); + auto day = make_day(global_object, year, month, date); + auto new_date = make_date(day, Value(time_within_day(time))); - datetime.set_time(datetime.year(), new_month, new_date, datetime.hour(), datetime.minute(), datetime.second()); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 5. Let u be TimeClip(UTC(newDate)). + new_date = time_clip(global_object, Value(utc_time(new_date.as_double()))); - this_object->set_is_invalid(false); - return Value(this_object->time()); + // 6. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(new_date.as_double()); + + // 7. Return u. + return new_date; } // 21.4.4.26 Date.prototype.setSeconds ( sec [ , ms ] ), https://tc39.es/ecma262/#sec-date.prototype.setseconds JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_seconds) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be LocalTime(? thisTimeValue(this value)). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); + auto time = local_time(this_time.as_double()); - auto arg_or = [&vm, &global_object](size_t i, i32 fallback) -> ThrowCompletionOr<Value> { - return vm.argument_count() > i ? vm.argument(i).to_number(global_object) : Value(fallback); - }; + // 2. Let s be ? ToNumber(sec). + auto second = TRY(vm.argument(0).to_number(global_object)); - auto& datetime = this_object->datetime(); + // 3. If ms is not present, let milli be msFromTime(t); otherwise, let milli be ? ToNumber(ms). + auto millisecond = TRY(argument_or_value(global_object, 1, ms_from_time(time))); - auto new_seconds_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_seconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_seconds = new_seconds_value.as_i32(); + // 4. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)). + auto hour = Value(hour_from_time(time)); + auto minute = Value(min_from_time(time)); - auto new_milliseconds_value = TRY(arg_or(1, this_object->milliseconds())); - if (!new_milliseconds_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_milliseconds = new_milliseconds_value.as_i32(); + auto new_time = make_time(global_object, hour, minute, second, millisecond); + auto new_date = make_date(Value(day(time)), new_time); - new_seconds += new_milliseconds / 1000; - this_object->set_milliseconds(new_milliseconds % 1000); + // 5. Let u be TimeClip(UTC(date)). + new_date = time_clip(global_object, Value(utc_time(new_date.as_double()))); - datetime.set_time(datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), new_seconds); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 6. Set the [[DateValue]] internal slot of this Date object to u. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(new_date.as_double()); - this_object->set_is_invalid(false); - return Value(this_object->time()); + // 7. Return u. + return new_date; } // 21.4.4.27 Date.prototype.setTime ( time ), https://tc39.es/ecma262/#sec-date.prototype.settime JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_time) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Perform ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - auto new_time_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_time_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_time = new_time_value.as_double(); + // 2. Let t be ? ToNumber(time). + time = TRY(vm.argument(0).to_number(global_object)); - if (new_time > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); - } + // 3. Let v be TimeClip(t). + time = time_clip(global_object, time); - auto new_date_time = Core::DateTime::from_timestamp(static_cast<time_t>(new_time / 1000)); - this_object->datetime().set_time(new_date_time.year(), new_date_time.month(), new_date_time.day(), new_date_time.hour(), new_date_time.minute(), new_date_time.second()); - this_object->set_milliseconds(static_cast<i16>(fmod(new_time, 1000))); + // 4. Set the [[DateValue]] internal slot of this Date object to v. + auto* this_object = MUST(typed_this_object(global_object)); + this_object->set_date_value(time.as_double()); - this_object->set_is_invalid(false); - return Value(this_object->time()); + // 5. Return v. + return time; } // 21.4.4.35 Date.prototype.toDateString ( ), https://tc39.es/ecma262/#sec-date.prototype.todatestring JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_date_string) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let O be this Date object. + // 2. Let tv be ? thisTimeValue(O). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_string(vm, "Invalid Date"); + // 3. If tv is NaN, return "Invalid Date". + if (time.is_nan()) + return js_string(vm, "Invalid Date"sv); - auto string = this_object->date_string(); - return js_string(vm, move(string)); + // 4. Let t be LocalTime(tv). + // 5. Return DateString(t). + return js_string(vm, date_string(local_time(time.as_double()))); } // 21.4.4.36 Date.prototype.toISOString ( ), https://tc39.es/ecma262/#sec-date.prototype.toisostring @@ -632,7 +638,7 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_iso_string) { auto* this_object = TRY(typed_this_object(global_object)); - if (this_object->is_invalid()) + if (!Value(this_object->date_value()).is_finite_number()) return vm.throw_completion<RangeError>(global_object, ErrorType::InvalidTimeValue); auto string = this_object->iso_date_string(); @@ -740,13 +746,11 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_time_string) // 21.4.4.41 Date.prototype.toString ( ), https://tc39.es/ecma262/#sec-date.prototype.tostring JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_string) { - auto* this_object = TRY(typed_this_object(global_object)); - - if (this_object->is_invalid()) - return js_string(vm, "Invalid Date"); + // 1. Let tv be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - auto string = this_object->string(); - return js_string(vm, move(string)); + // 2. Return ToDateString(tv). + return js_string(vm, JS::to_date_string(time.as_double())); } // 21.4.4.41.1 TimeString ( tv ), https://tc39.es/ecma262/#sec-timestring @@ -861,25 +865,52 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_temporal_instant) // 21.4.4.42 Date.prototype.toTimeString ( ), https://tc39.es/ecma262/#sec-date.prototype.totimestring JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_time_string) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let O be this Date object. + // 2. Let tv be ? thisTimeValue(O). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_string(vm, "Invalid Date"); + // 3. If tv is NaN, return "Invalid Date". + if (time.is_nan()) + return js_string(vm, "Invalid Date"sv); - auto string = this_object->time_string(); + // 4. Let t be LocalTime(tv). + // 5. Return the string-concatenation of TimeString(t) and TimeZoneString(tv). + auto string = String::formatted("{}{}", time_string(local_time(time.as_double())), time_zone_string(time.as_double())); return js_string(vm, move(string)); } // 21.4.4.43 Date.prototype.toUTCString ( ), https://tc39.es/ecma262/#sec-date.prototype.toutcstring JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_utc_string) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let O be this Date object. + // 2. Let tv be ? thisTimeValue(O). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_string(vm, "Invalid Date"); + // 3. If tv is NaN, return "Invalid Date". + if (time.is_nan()) + return js_string(vm, "Invalid Date"sv); - // HTTP dates are always expressed in GMT. - auto string = this_object->gmt_date_string(); + // 4. Let weekday be the Name of the entry in Table 62 with the Number WeekDay(tv). + auto weekday = day_names[week_day(time.as_double())]; + + // 5. Let month be the Name of the entry in Table 63 with the Number MonthFromTime(tv). + auto month = month_names[month_from_time(time.as_double())]; + + // 6. Let day be the String representation of DateFromTime(tv), formatted as a two-digit decimal number, padded to the left with the code unit 0x0030 (DIGIT ZERO) if necessary. + auto day = date_from_time(time.as_double()); + + // 7. Let yv be YearFromTime(tv). + auto year = year_from_time(time.as_double()); + + // 8. If yv ≥ +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-". + auto year_sign = year >= 0 ? ""sv : "-"sv; + + // 9. Let year be the String representation of abs(ℝ(yv)), formatted as a decimal number. + year = abs(year); + + // 10. Let paddedYear be ! StringPad(year, 4𝔽, "0", start). + // 11. Return the string-concatenation of weekday, ",", the code unit 0x0020 (SPACE), day, the code unit 0x0020 (SPACE), month, the code unit 0x0020 (SPACE), yearSign, paddedYear, the code unit 0x0020 (SPACE), and TimeString(tv). + auto string = String::formatted("{}, {:02} {} {}{:04} {}", weekday, day, month, year_sign, year, time_string(time.as_double())); return js_string(vm, move(string)); } @@ -906,39 +937,62 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::symbol_to_primitive) // B.2.4.1 Date.prototype.getYear ( ), https://tc39.es/ecma262/#sec-date.prototype.getyear JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_year) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto time = TRY(this_time_value(global_object, vm.this_value(global_object))); - if (this_object->is_invalid()) - return js_nan(); + // 2. If t is NaN, return NaN. + if (time.is_nan()) + return time; - return Value(this_object->year() - 1900); + // 3. Return Return YearFromTime(LocalTime(t)) - 1900𝔽. + return Value(year_from_time(local_time(time.as_double())) - 1900); } // B.2.4.2 Date.prototype.setYear ( year ), https://tc39.es/ecma262/#sec-date.prototype.setyear JS_DEFINE_NATIVE_FUNCTION(DatePrototype::set_year) { - auto* this_object = TRY(typed_this_object(global_object)); + // 1. Let t be ? thisTimeValue(this value). + auto this_time = TRY(this_time_value(global_object, vm.this_value(global_object))); - auto& datetime = this_object->datetime(); + // 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). + double time = 0; + if (!this_time.is_nan()) + time = local_time(this_time.as_double()); - auto new_year_value = TRY(vm.argument(0).to_number(global_object)); - if (!new_year_value.is_finite_number()) { - this_object->set_is_invalid(true); - return js_nan(); - } - auto new_year = new_year_value.as_i32(); - if (new_year >= 0 && new_year <= 99) - new_year += 1900; + // 3. Let y be ? ToNumber(year). + auto year = TRY(vm.argument(0).to_number(global_object)); - datetime.set_time(new_year, datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), datetime.second()); - if (this_object->time() > Date::time_clip) { - this_object->set_is_invalid(true); - return js_nan(); + auto* this_object = MUST(typed_this_object(global_object)); + + // 4. If y is NaN, then + if (year.is_nan()) { + // a. Set the [[DateValue]] internal slot of this Date object to NaN. + this_object->set_date_value(js_nan().as_double()); + + // b. Return NaN. + return year; } - this_object->set_is_invalid(false); + // 5. Let yi be ! ToIntegerOrInfinity(y). + auto year_double = MUST(year.to_integer_or_infinity(global_object)); + + // 6. If 0 ≤ yi ≤ 99, let yyyy be 1900𝔽 + 𝔽(yi). + if (0 <= year_double && year_double <= 99) + year = Value(1900 + year_double); + // 7. Else, let yyyy be y. + + // 8. Let d be MakeDay(yyyy, MonthFromTime(t), DateFromTime(t)). + auto day = make_day(global_object, year, Value(month_from_time(time)), Value(date_from_time(time))); + + // 9. Let date be UTC(MakeDate(d, TimeWithinDay(t))). + auto date = utc_time(make_date(day, Value(time_within_day(time))).as_double()); + + // 10. Set the [[DateValue]] internal slot of this Date object to TimeClip(date). + auto new_date = time_clip(global_object, Value(date)); + this_object->set_date_value(new_date.as_double()); - return Value(this_object->time()); + // 11. Return the value of the [[DateValue]] internal slot of this Date object. + return new_date; } // B.2.4.3 Date.prototype.toGMTString ( ), https://tc39.es/ecma262/#sec-date.prototype.togmtstring diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setDate.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setDate.js index b8cd57710f..fc7807eaa4 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setDate.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setDate.js @@ -30,18 +30,3 @@ test("Day as argument", () => { expect(date.getSeconds()).toBe(0); expect(date.getMilliseconds()).toBe(0); }); - -test("Make Invalid Date valid again", () => { - let date = new Date(2021, 0, 1); - date.setDate(NaN); - expect(date.getTime()).toBe(NaN); - - date.setDate(16); - expect(date.getFullYear()).toBe(2021); - expect(date.getMonth()).toBe(0); - expect(date.getDate()).toBe(16); - expect(date.getHours()).toBe(0); - expect(date.getMinutes()).toBe(0); - expect(date.getSeconds()).toBe(0); - expect(date.getMilliseconds()).toBe(0); -}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setMonth.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setMonth.js index a7ffee3c01..1ae040d833 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setMonth.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.setMonth.js @@ -60,28 +60,4 @@ test("NaN or undefined in any arguments", () => { date = new Date(2021, 0, 1); date.setMonth(2021, undefined); expect(date.getTime()).toBe(NaN); - - date.setMonth(3, 16); - expect(date.getFullYear()).toBe(2021); - expect(date.getMonth()).toBe(3); - expect(date.getDate()).toBe(16); - expect(date.getHours()).toBe(0); - expect(date.getMinutes()).toBe(0); - expect(date.getSeconds()).toBe(0); - expect(date.getMilliseconds()).toBe(0); -}); - -test("Make Invalid Date valid again", () => { - let date = new Date(2021, 0, 1); - date.setMonth(NaN, 3, 16); - expect(date.getTime()).toBe(NaN); - - date.setMonth(3, 16); - expect(date.getFullYear()).toBe(2021); - expect(date.getMonth()).toBe(3); - expect(date.getDate()).toBe(16); - expect(date.getHours()).toBe(0); - expect(date.getMinutes()).toBe(0); - expect(date.getSeconds()).toBe(0); - expect(date.getMilliseconds()).toBe(0); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleDateString.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleDateString.js index 8eb8c324b2..387f5b0291 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleDateString.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleDateString.js @@ -15,12 +15,6 @@ describe("errors", () => { }).toThrowWithMessage(TypeError, "Cannot convert BigInt to number"); }); - test("time value cannot be clipped", () => { - expect(() => { - new Date(-8.65e15).toLocaleDateString(); - }).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15"); - }); - test("timeStyle may not be specified", () => { expect(() => { new Date().toLocaleDateString([], { timeStyle: "short" }); @@ -34,6 +28,11 @@ describe("correct behavior", () => { expect(d.toLocaleDateString()).toBe("Invalid Date"); }); + test("time clip", () => { + const d = new Date(-8.65e15); + expect(d.toLocaleDateString()).toBe("Invalid Date"); + }); + const d0 = new Date(Date.UTC(2021, 11, 7, 17, 40, 50, 456)); const d1 = new Date(Date.UTC(1989, 0, 23, 7, 8, 9, 45)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleString.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleString.js index dc76c7f6a9..bb2bc638be 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleString.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleString.js @@ -14,12 +14,6 @@ describe("errors", () => { new Date(1n).toLocaleString(); }).toThrowWithMessage(TypeError, "Cannot convert BigInt to number"); }); - - test("time value cannot be clipped", () => { - expect(() => { - new Date(-8.65e15).toLocaleString(); - }).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15"); - }); }); describe("correct behavior", () => { @@ -28,6 +22,11 @@ describe("correct behavior", () => { expect(d.toLocaleString()).toBe("Invalid Date"); }); + test("time clip", () => { + const d = new Date(-8.65e15); + expect(d.toLocaleString()).toBe("Invalid Date"); + }); + const d0 = new Date(Date.UTC(2021, 11, 7, 17, 40, 50, 456)); const d1 = new Date(Date.UTC(1989, 0, 23, 7, 8, 9, 45)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleTimeString.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleTimeString.js index 6d85632f8e..d188994039 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleTimeString.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.prototype.toLocaleTimeString.js @@ -15,12 +15,6 @@ describe("errors", () => { }).toThrowWithMessage(TypeError, "Cannot convert BigInt to number"); }); - test("time value cannot be clipped", () => { - expect(() => { - new Date(-8.65e15).toLocaleTimeString(); - }).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15"); - }); - test("dateStyle may not be specified", () => { expect(() => { new Date().toLocaleTimeString([], { dateStyle: "short" }); @@ -34,6 +28,11 @@ describe("correct behavior", () => { expect(d.toLocaleTimeString()).toBe("Invalid Date"); }); + test("time clip", () => { + const d = new Date(-8.65e15); + expect(d.toLocaleTimeString()).toBe("Invalid Date"); + }); + const d0 = new Date(Date.UTC(2021, 11, 7, 17, 40, 50, 456)); const d1 = new Date(Date.UTC(1989, 0, 23, 7, 8, 9, 45)); |