lib/VM/JSLib/Date.cpp (984 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
//===----------------------------------------------------------------------===//
/// \file
/// ES5.1 15.9 Initialize the Date constructor.
//===----------------------------------------------------------------------===//
#include "JSLibInternal.h"
#include "hermes/Support/OSCompat.h"
#include "hermes/VM/HermesValue.h"
#include "hermes/VM/JSLib/DateUtil.h"
#include "hermes/VM/JSLib/RuntimeCommonStorage.h"
#include "hermes/VM/Operations.h"
namespace hermes {
namespace vm {
//===----------------------------------------------------------------------===//
/// Date.
namespace {
struct ToStringOptions {
/// Converts a time \p t that has been adjusted by \p tza from UTC,
/// and appends the resultant string into \p buf.
/// \param t the local time since Jan 1 1970 UTC.
/// \param tza the offset from UTC that \p t has been adjusted by.
/// \param buf[out] the buffer into which to output the result string.
void (*toStringFn)(double t, double tza, llvh::SmallVectorImpl<char> &buf);
bool isUTC;
/// Throw if the internal value of this Date is not finite.
bool throwOnError;
};
struct ToLocaleStringOptions {
/// Converts a time \p t that has been adjusted by \p tza from UTC,
/// and appends the resultant string into \p buf.
/// \param t the local time since Jan 1 1970 UTC.
/// \param locale the locale to convert in (the current locale).
/// \param buf[out] the buffer into which to output the result string.
void (*toStringFn)(double t, llvh::SmallVectorImpl<char16_t> &buf);
};
struct GetterOptions {
enum Field {
FULL_YEAR,
YEAR,
MONTH,
DATE,
DAY,
HOURS,
MINUTES,
SECONDS,
MILLISECONDS,
TIMEZONE_OFFSET,
};
Field field;
bool isUTC;
};
} // namespace
enum class ToStringKind {
DatetimeToString,
DateToString,
TimeToString,
ISOToString,
UTCToString,
NumKinds
};
enum class ToLocaleStringKind {
DatetimeToLocaleString,
DateToLocaleString,
TimeToLocaleString,
NumKinds
};
enum class GetterKind {
GetFullYear,
GetYear,
GetMonth,
GetDate,
GetDay,
GetHours,
GetMinutes,
GetSeconds,
GetMilliseconds,
GetUTCFullYear,
GetUTCMonth,
GetUTCDate,
GetUTCDay,
GetUTCHours,
GetUTCMinutes,
GetUTCSeconds,
GetUTCMilliseconds,
GetTimezoneOffset,
NumKinds
};
Handle<JSObject> createDateConstructor(Runtime &runtime) {
auto datePrototype = Handle<JSObject>::vmcast(&runtime.datePrototype);
auto cons = defineSystemConstructor<JSDate>(
runtime,
Predefined::getSymbolID(Predefined::Date),
dateConstructor,
datePrototype,
7,
CellKind::JSDateKind);
// Date.prototype.xxx() methods.
defineMethod(
runtime,
datePrototype,
Predefined::getSymbolID(Predefined::valueOf),
nullptr,
datePrototypeGetTime,
0);
defineMethod(
runtime,
datePrototype,
Predefined::getSymbolID(Predefined::getTime),
nullptr,
datePrototypeGetTime,
0);
auto defineToStringMethod = [&](SymbolID name, ToStringKind kind) {
defineMethod(
runtime,
datePrototype,
name,
(void *)kind,
datePrototypeToStringHelper,
0);
};
defineToStringMethod(
Predefined::getSymbolID(Predefined::toString),
ToStringKind::DatetimeToString);
defineToStringMethod(
Predefined::getSymbolID(Predefined::toDateString),
ToStringKind::DateToString);
defineToStringMethod(
Predefined::getSymbolID(Predefined::toTimeString),
ToStringKind::TimeToString);
defineToStringMethod(
Predefined::getSymbolID(Predefined::toISOString),
ToStringKind::ISOToString);
defineToStringMethod(
Predefined::getSymbolID(Predefined::toUTCString),
ToStringKind::UTCToString);
defineToStringMethod(
Predefined::getSymbolID(Predefined::toGMTString),
ToStringKind::UTCToString);
auto defineToLocaleStringMethod = [&](SymbolID name,
ToLocaleStringKind kind) {
defineMethod(
runtime,
datePrototype,
name,
(void *)kind,
datePrototypeToLocaleStringHelper,
0);
};
defineToLocaleStringMethod(
Predefined::getSymbolID(Predefined::toLocaleString),
ToLocaleStringKind::DatetimeToLocaleString);
defineToLocaleStringMethod(
Predefined::getSymbolID(Predefined::toLocaleDateString),
ToLocaleStringKind::DateToLocaleString);
defineToLocaleStringMethod(
Predefined::getSymbolID(Predefined::toLocaleTimeString),
ToLocaleStringKind::TimeToLocaleString);
auto defineGetterMethod = [&](SymbolID name, GetterKind kind) {
defineMethod(
runtime,
datePrototype,
name,
(void *)kind,
datePrototypeGetterHelper,
0);
};
defineGetterMethod(
Predefined::getSymbolID(Predefined::getFullYear),
GetterKind::GetFullYear);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getYear), GetterKind::GetYear);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getMonth), GetterKind::GetMonth);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getDate), GetterKind::GetDate);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getDay), GetterKind::GetDay);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getHours), GetterKind::GetHours);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getMinutes), GetterKind::GetMinutes);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getSeconds), GetterKind::GetSeconds);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getMilliseconds),
GetterKind::GetMilliseconds);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCFullYear),
GetterKind::GetUTCFullYear);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCMonth),
GetterKind::GetUTCMonth);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCDate), GetterKind::GetUTCDate);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCDay), GetterKind::GetUTCDay);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCHours),
GetterKind::GetUTCHours);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCMinutes),
GetterKind::GetUTCMinutes);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCSeconds),
GetterKind::GetUTCSeconds);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getUTCMilliseconds),
GetterKind::GetUTCMilliseconds);
defineGetterMethod(
Predefined::getSymbolID(Predefined::getTimezoneOffset),
GetterKind::GetTimezoneOffset);
defineMethod(
runtime,
datePrototype,
Predefined::getSymbolID(Predefined::setTime),
nullptr,
datePrototypeSetTime,
1);
auto defineSetterMethod =
[&](SymbolID name, uint32_t length, bool isUTC, NativeFunctionPtr func) {
defineMethod(
runtime,
datePrototype,
name,
reinterpret_cast<void *>(isUTC),
func,
length);
};
defineSetterMethod(
Predefined::getSymbolID(Predefined::setMilliseconds),
1,
false,
datePrototypeSetMilliseconds);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCMilliseconds),
1,
true,
datePrototypeSetMilliseconds);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setSeconds),
2,
false,
datePrototypeSetSeconds);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCSeconds),
2,
true,
datePrototypeSetSeconds);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setMinutes),
3,
false,
datePrototypeSetMinutes);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCMinutes),
3,
true,
datePrototypeSetMinutes);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setHours),
4,
false,
datePrototypeSetHours);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCHours),
4,
true,
datePrototypeSetHours);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setDate),
1,
false,
datePrototypeSetDate);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCDate),
1,
true,
datePrototypeSetDate);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setMonth),
2,
false,
datePrototypeSetMonth);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCMonth),
2,
true,
datePrototypeSetMonth);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setFullYear),
3,
false,
datePrototypeSetFullYear);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setUTCFullYear),
3,
true,
datePrototypeSetFullYear);
defineSetterMethod(
Predefined::getSymbolID(Predefined::setYear),
1,
false,
datePrototypeSetYear);
defineMethod(
runtime,
datePrototype,
Predefined::getSymbolID(Predefined::toJSON),
nullptr,
datePrototypeToJSON,
1);
DefinePropertyFlags dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
dpf.writable = 0;
dpf.enumerable = 0;
(void)defineMethod(
runtime,
datePrototype,
Predefined::getSymbolID(Predefined::SymbolToPrimitive),
Predefined::getSymbolID(Predefined::squareSymbolToPrimitive),
nullptr,
datePrototypeSymbolToPrimitive,
1,
dpf);
// Date.xxx() methods.
defineMethod(
runtime,
cons,
Predefined::getSymbolID(Predefined::parse),
nullptr,
dateParse,
1);
defineMethod(
runtime,
cons,
Predefined::getSymbolID(Predefined::UTC),
nullptr,
dateUTC,
7);
defineMethod(
runtime,
cons,
Predefined::getSymbolID(Predefined::now),
nullptr,
dateNow,
0);
return cons;
}
/// Takes \p args in UTC time of the form:
/// (year, month, [, date [, hours [, minutes [, seconds [, ms]]]]])
/// and returns the unclipped time in milliseconds since Jan 1 1970 UTC.
static CallResult<double> makeTimeFromArgs(Runtime &runtime, NativeArgs args) {
const double nan = std::numeric_limits<double>::quiet_NaN();
auto argCount = args.getArgCount();
// General case: read all fields in and compute timestamp.
enum fieldname { y, m, dt, h, min, s, milli, yr, FIELD_COUNT };
// Initialize to default fields and update them in loop if necessary.
double fields[FIELD_COUNT] = {nan, nan, 1, 0, 0, 0, 0};
for (size_t i = 0; i < std::min(7u, argCount); ++i) {
GCScopeMarkerRAII marker{runtime};
auto res = toNumber_RJS(runtime, args.getArgHandle(i));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
fields[i] = res->getNumber();
}
// Years between 0 and 99 are treated as offsets from 1900.
double yint = std::trunc(fields[y]);
if (!std::isnan(fields[y]) && 0 <= yint && yint <= 99) {
fields[yr] = 1900 + yint;
} else {
fields[yr] = fields[y];
}
return makeDate(
makeDay(fields[yr], fields[m], fields[dt]),
makeTime(fields[h], fields[min], fields[s], fields[milli]));
}
CallResult<HermesValue>
dateConstructor(void *, Runtime &runtime, NativeArgs args) {
auto *const storage = runtime.getCommonStorage();
if (args.isConstructorCall()) {
auto self = args.vmcastThis<JSDate>();
uint32_t argCount = args.getArgCount();
double finalDate;
if (argCount == 0) {
if (storage->env) {
if (storage->env->callsToNewDate.empty()) {
return runtime.raiseTypeError(
"Replay of new Date() ran out of traced values");
}
finalDate = storage->env->callsToNewDate.front();
storage->env->callsToNewDate.pop_front();
} else {
// No arguments, just set it to the current time.
finalDate = curTime();
}
if (LLVM_UNLIKELY(storage->shouldTrace)) {
storage->tracedEnv.callsToNewDate.push_back(finalDate);
}
} else if (argCount == 1) {
if (auto *dateArg = dyn_vmcast<JSDate>(args.getArg(0))) {
// No handle needed here because we just retrieve a double.
NoAllocScope noAlloc(runtime);
finalDate = dateArg->getPrimitiveValue();
} else {
// Parse the argument if it's a string, else just convert to number.
auto res =
toPrimitive_RJS(runtime, args.getArgHandle(0), PreferredType::NONE);
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
auto v = runtime.makeHandle(res.getValue());
if (v->isString()) {
// Call the String -> Date parsing function.
finalDate = timeClip(parseDate(StringPrimitive::createStringView(
runtime, Handle<StringPrimitive>::vmcast(v))));
} else {
auto numRes = toNumber_RJS(runtime, v);
if (numRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
finalDate = timeClip(numRes->getNumber());
}
}
} else {
// General case: read all fields in and compute timestamp.
CallResult<double> cr{0};
cr = makeTimeFromArgs(runtime, args);
if (cr == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
// makeTimeFromArgs interprets arguments as UTC.
// We want them as local time, so pretend that they are,
// and call utcTime to get the final UTC value we want to store.
finalDate = timeClip(utcTime(*cr));
}
self->setPrimitiveValue(finalDate);
return self.getHermesValue();
}
llvh::SmallString<32> str{};
if (storage->env) {
if (storage->env->callsToDateAsFunction.empty()) {
return runtime.raiseTypeError(
"Replay of Date() ran out of traced values");
}
str = storage->env->callsToDateAsFunction.front();
storage->env->callsToDateAsFunction.pop_front();
} else {
double t = curTime();
double local = localTime(t);
dateTimeString(local, local - t, str);
}
if (LLVM_UNLIKELY(storage->shouldTrace)) {
storage->tracedEnv.callsToDateAsFunction.push_back(
std::string(str.c_str()));
}
return runtime.ignoreAllocationFailure(StringPrimitive::create(runtime, str));
}
CallResult<HermesValue> dateParse(void *, Runtime &runtime, NativeArgs args) {
auto res = toString_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
return HermesValue::encodeDoubleValue(
parseDate(StringPrimitive::createStringView(
runtime, runtime.makeHandle(std::move(*res)))));
}
CallResult<HermesValue> dateUTC(void *, Runtime &runtime, NativeArgs args) {
// With less than 2 arguments, this is implementation-dependent behavior.
// We define the behavior that test262 expects here.
if (args.getArgCount() == 0) {
return HermesValue::encodeNaNValue();
}
if (args.getArgCount() == 1) {
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double y = res->getNumber();
return HermesValue::encodeNumberValue(
timeClip(makeDate(makeDay(y, 0, 1), makeTime(0, 0, 0, 0))));
}
CallResult<double> cr{0};
cr = makeTimeFromArgs(runtime, args);
if (cr == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
return HermesValue::encodeDoubleValue(timeClip(*cr));
}
CallResult<HermesValue> dateNow(void *, Runtime &runtime, NativeArgs args) {
double t = curTime();
auto *const storage = runtime.getCommonStorage();
if (storage->env) {
if (storage->env->callsToDateNow.empty()) {
return runtime.raiseTypeError(
"Replay of Date.now() ran out of traced values");
}
t = storage->env->callsToDateNow.front();
storage->env->callsToDateNow.pop_front();
}
if (LLVM_UNLIKELY(storage->shouldTrace)) {
storage->tracedEnv.callsToDateNow.push_back(t);
}
return HermesValue::encodeDoubleValue(t);
}
CallResult<HermesValue>
datePrototypeToStringHelper(void *ctx, Runtime &runtime, NativeArgs args) {
static ToStringOptions toStringOptions[] = {
{dateTimeString, false, false},
{dateString, false, false},
{timeTZString, false, false},
{datetimeToISOString, true, true},
{dateTimeUTCString, true, false},
};
assert(
(uint64_t)ctx < (uint64_t)ToStringKind::NumKinds &&
"dataPrototypeToString with wrong kind as context");
ToStringOptions *opts = &toStringOptions[(uint64_t)ctx];
auto *date = dyn_vmcast<JSDate>(args.getThisArg());
if (!date) {
return runtime.raiseTypeError(
"Date.prototype.toString() called on non-Date object");
}
double t = date->getPrimitiveValue();
if (!std::isfinite(t)) {
if (opts->throwOnError) {
return runtime.raiseRangeError("Date value out of bounds");
}
// "Invalid Date" in non-finite or NaN cases.
return HermesValue::encodeStringValue(
runtime.getPredefinedString(Predefined::InvalidDate));
}
llvh::SmallString<32> str{};
if (!opts->isUTC) {
double local = localTime(t);
opts->toStringFn(local, local - t, str);
} else {
opts->toStringFn(t, 0, str);
}
return runtime.ignoreAllocationFailure(StringPrimitive::create(runtime, str));
}
CallResult<HermesValue> datePrototypeToLocaleStringHelper(
void *ctx,
Runtime &runtime,
NativeArgs args) {
assert(
(uint64_t)ctx < (uint64_t)ToLocaleStringKind::NumKinds &&
"dataPrototypeToLocaleString with wrong kind as context");
#ifdef HERMES_ENABLE_INTL
static NativeFunctionPtr toLocaleStringFunctions[] = {
intlDatePrototypeToLocaleString,
intlDatePrototypeToLocaleDateString,
intlDatePrototypeToLocaleTimeString,
};
assert(
sizeof(toLocaleStringFunctions) / sizeof(toLocaleStringFunctions[0]) ==
(size_t)ToLocaleStringKind::NumKinds &&
"toLocaleStringFunctions has wrong number of elements");
return toLocaleStringFunctions[(uint64_t)ctx](
/* unused */ ctx, runtime, args);
#else
static ToLocaleStringOptions toLocaleStringOptions[] = {
{datetimeToLocaleString},
{dateToLocaleString},
{timeToLocaleString},
};
assert(
sizeof(toLocaleStringOptions) / sizeof(toLocaleStringOptions[0]) ==
(size_t)ToLocaleStringKind::NumKinds &&
"toLocaleStringOptions has wrong number of elements");
ToLocaleStringOptions *opts = &toLocaleStringOptions[(uint64_t)ctx];
auto *date = dyn_vmcast<JSDate>(args.getThisArg());
if (!date) {
return runtime.raiseTypeError(
"Date.prototype.toString() called on non-Date object");
}
double t = date->getPrimitiveValue();
if (!std::isfinite(t)) {
// "Invalid Date" in non-finite or NaN cases.
return HermesValue::encodeStringValue(
runtime.getPredefinedString(Predefined::InvalidDate));
}
SmallU16String<128> str{};
opts->toStringFn(t, str);
return StringPrimitive::create(runtime, str);
#endif
}
CallResult<HermesValue>
datePrototypeGetTime(void *, Runtime &runtime, NativeArgs args) {
auto *date = dyn_vmcast<JSDate>(args.getThisArg());
if (!date) {
return runtime.raiseTypeError(
"Date.prototype.getTime() called on non-Date object");
}
return HermesValue::encodeDoubleValue(date->getPrimitiveValue());
}
CallResult<HermesValue>
datePrototypeGetterHelper(void *ctx, Runtime &runtime, NativeArgs args) {
static GetterOptions getterOptions[] = {
{GetterOptions::Field::FULL_YEAR, false},
{GetterOptions::Field::YEAR, false},
{GetterOptions::Field::MONTH, false},
{GetterOptions::Field::DATE, false},
{GetterOptions::Field::DAY, false},
{GetterOptions::Field::HOURS, false},
{GetterOptions::Field::MINUTES, false},
{GetterOptions::Field::SECONDS, false},
{GetterOptions::Field::MILLISECONDS, false},
{GetterOptions::Field::FULL_YEAR, true},
{GetterOptions::Field::MONTH, true},
{GetterOptions::Field::DATE, true},
{GetterOptions::Field::DAY, true},
{GetterOptions::Field::HOURS, true},
{GetterOptions::Field::MINUTES, true},
{GetterOptions::Field::SECONDS, true},
{GetterOptions::Field::MILLISECONDS, true},
{GetterOptions::Field::TIMEZONE_OFFSET, false},
};
assert(
(uint64_t)ctx < (uint64_t)GetterKind::NumKinds &&
"datePropertyGetterHelper with wrong kind as context");
GetterOptions *opts = &getterOptions[(uint64_t)ctx];
auto *date = dyn_vmcast<JSDate>(args.getThisArg());
if (!date) {
return runtime.raiseTypeError(
"Date.prototype.toString() called on non-Date object");
}
double t = date->getPrimitiveValue();
if (std::isnan(t)) {
return HermesValue::encodeNaNValue();
}
// Store the original value of t to be used in offset calculations.
double utc = t;
if (!opts->isUTC) {
t = localTime(t);
}
double result{std::numeric_limits<double>::quiet_NaN()};
switch (opts->field) {
case GetterOptions::Field::FULL_YEAR:
result = yearFromTime(t);
break;
case GetterOptions::Field::YEAR:
result = yearFromTime(t) - 1900;
break;
case GetterOptions::Field::MONTH:
result = monthFromTime(t);
break;
case GetterOptions::Field::DATE:
result = dateFromTime(t);
break;
case GetterOptions::Field::DAY:
result = weekDay(t);
break;
case GetterOptions::Field::HOURS:
result = hourFromTime(t);
break;
case GetterOptions::Field::MINUTES:
result = minFromTime(t);
break;
case GetterOptions::Field::SECONDS:
result = secFromTime(t);
break;
case GetterOptions::Field::MILLISECONDS:
result = msFromTime(t);
break;
case GetterOptions::Field::TIMEZONE_OFFSET:
result = (utc - t) / MS_PER_MINUTE;
break;
}
return HermesValue::encodeDoubleValue(result);
}
/// Set the [[PrimitiveValue]] to the given time.
CallResult<HermesValue>
datePrototypeSetTime(void *ctx, Runtime &runtime, NativeArgs args) {
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setTime() called on non-Date object");
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double t = timeClip(res->getNumber());
self->setPrimitiveValue(t);
return HermesValue::encodeDoubleValue(t);
}
/// Set the milliseconds as provided and return the new time value.
CallResult<HermesValue>
datePrototypeSetMilliseconds(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setMilliseconds() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double ms = res->getNumber();
double date = makeDate(
day(t), makeTime(hourFromTime(t), minFromTime(t), secFromTime(t), ms));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Takes 2 arguments: seconds, milliseconds.
/// Set the seconds, optionally milliseconds, and return the new time.
CallResult<HermesValue>
datePrototypeSetSeconds(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setSeconds() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double s = res->getNumber();
double milli;
if (args.getArgCount() >= 2) {
res = toNumber_RJS(runtime, args.getArgHandle(1));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
milli = res->getNumber();
} else {
milli = msFromTime(t);
}
double date =
makeDate(day(t), makeTime(hourFromTime(t), minFromTime(t), s, milli));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Takes 3 arguments: minutes, seconds, milliseconds.
/// Set the minutes, optionally seconds and milliseconds, return time.
CallResult<HermesValue>
datePrototypeSetMinutes(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setMinutes() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double m = res->getNumber();
double s;
if (args.getArgCount() >= 2) {
res = toNumber_RJS(runtime, args.getArgHandle(1));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
s = res->getNumber();
} else {
s = secFromTime(t);
}
double milli;
if (args.getArgCount() >= 3) {
res = toNumber_RJS(runtime, args.getArgHandle(2));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
milli = res->getNumber();
} else {
milli = msFromTime(t);
}
double date = makeDate(day(t), makeTime(hourFromTime(t), m, s, milli));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Takes 4 arguments: hours, minutes, seconds, milliseconds.
/// Set the hours, optionally minutes, seconds, and milliseconds, return time.
CallResult<HermesValue>
datePrototypeSetHours(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setHours() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double h = res->getNumber();
double m;
if (args.getArgCount() >= 2) {
res = toNumber_RJS(runtime, args.getArgHandle(1));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
m = res->getNumber();
} else {
m = minFromTime(t);
}
double s;
if (args.getArgCount() >= 3) {
res = toNumber_RJS(runtime, args.getArgHandle(2));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
s = res->getNumber();
} else {
s = secFromTime(t);
}
double milli;
if (args.getArgCount() >= 4) {
res = toNumber_RJS(runtime, args.getArgHandle(3));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
milli = res->getNumber();
} else {
milli = msFromTime(t);
}
double date = makeDate(day(t), makeTime(h, m, s, milli));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Set the date of the month and return the new time.
CallResult<HermesValue>
datePrototypeSetDate(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setDate() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double dt = res->getNumber();
double newDate = makeDate(
makeDay(yearFromTime(t), monthFromTime(t), dt), timeWithinDay(t));
double utcT = !isUTC ? timeClip(utcTime(newDate)) : timeClip(newDate);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Takes 2 arguments: month and date.
/// Set the month, optionally the date of the month, return the time.
CallResult<HermesValue>
datePrototypeSetMonth(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setMonth() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double m = res->getNumber();
double dt;
if (args.getArgCount() >= 2) {
res = toNumber_RJS(runtime, args.getArgHandle(1));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
dt = res->getNumber();
} else {
dt = dateFromTime(t);
}
double newDate = makeDate(makeDay(yearFromTime(t), m, dt), timeWithinDay(t));
double utcT = !isUTC ? timeClip(utcTime(newDate)) : timeClip(newDate);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Takes 3 arguments: full year, month and date.
/// Set the full year, optionally the month and date, return the time.
CallResult<HermesValue>
datePrototypeSetFullYear(void *ctx, Runtime &runtime, NativeArgs args) {
bool isUTC = static_cast<bool>(ctx);
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setFullYear() called on non-Date object");
}
double t = self->getPrimitiveValue();
if (!isUTC) {
t = localTime(t);
}
if (std::isnan(t)) {
t = 0;
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double y = res->getNumber();
double m;
if (args.getArgCount() >= 2) {
res = toNumber_RJS(runtime, args.getArgHandle(1));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
m = res->getNumber();
} else {
m = monthFromTime(t);
}
double dt;
if (args.getArgCount() >= 3) {
res = toNumber_RJS(runtime, args.getArgHandle(2));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
dt = res->getNumber();
} else {
dt = dateFromTime(t);
}
double newDate = makeDate(makeDay(y, m, dt), timeWithinDay(t));
double utcT = !isUTC ? timeClip(utcTime(newDate)) : timeClip(newDate);
self->setPrimitiveValue(utcT);
return HermesValue::encodeDoubleValue(utcT);
}
/// Takes one argument: the partial (or full) year.
/// Per spec, adds 1900 if the year is between 0 and 99.
/// Sets the year to the new year and returns the time.
CallResult<HermesValue>
datePrototypeSetYear(void *ctx, Runtime &runtime, NativeArgs args) {
auto self = args.dyncastThis<JSDate>();
if (!self) {
return runtime.raiseTypeError(
"Date.prototype.setYear() called on non-Date object");
}
double t = self->getPrimitiveValue();
t = localTime(t);
if (std::isnan(t)) {
t = 0;
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
double y = res->getNumber();
if (std::isnan(y)) {
self->setPrimitiveValue(std::numeric_limits<double>::quiet_NaN());
return HermesValue::encodeNaNValue();
}
double yint = std::trunc(y);
double yr = 0 <= yint && yint <= 99 ? yint + 1900 : y;
double date = utcTime(makeDate(
makeDay(yr, monthFromTime(t), dateFromTime(t)), timeWithinDay(t)));
double d = timeClip(date);
self->setPrimitiveValue(d);
return HermesValue::encodeDoubleValue(d);
}
CallResult<HermesValue>
datePrototypeToJSON(void *ctx, Runtime &runtime, NativeArgs args) {
auto selfHandle = args.getThisHandle();
auto objRes = toObject(runtime, selfHandle);
if (objRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto tvRes = toPrimitive_RJS(runtime, O, PreferredType::NUMBER);
if (tvRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
auto tv = *tvRes;
if (tv.isNumber() && !std::isfinite(tv.getNumber())) {
return HermesValue::encodeNullValue();
}
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::toISOString));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
Handle<Callable> toISO =
Handle<Callable>::dyn_vmcast(runtime.makeHandle(std::move(*propRes)));
if (!toISO.get()) {
return runtime.raiseTypeError(
"toISOString is not callable in Date.prototype.toJSON()");
}
return Callable::executeCall0(toISO, runtime, O).toCallResultHermesValue();
}
CallResult<HermesValue>
datePrototypeSymbolToPrimitive(void *, Runtime &runtime, NativeArgs args) {
auto O = args.dyncastThis<JSObject>();
if (LLVM_UNLIKELY(!O)) {
return runtime.raiseTypeError(
"Date[Symbol.toPrimitive]() must be called on an object");
}
auto hint = args.getArgHandle(0);
if (LLVM_UNLIKELY(!hint->isString())) {
return runtime.raiseTypeError(
"Date[Symbol.toPrimitive]() argument must be a string");
}
PreferredType tryFirst;
if (runtime.symbolEqualsToStringPrim(
Predefined::getSymbolID(Predefined::string), hint->getString()) ||
runtime.symbolEqualsToStringPrim(
Predefined::getSymbolID(Predefined::defaultStr), hint->getString())) {
tryFirst = PreferredType::STRING;
} else if (runtime.symbolEqualsToStringPrim(
Predefined::getSymbolID(Predefined::number),
hint->getString())) {
tryFirst = PreferredType::NUMBER;
} else {
return runtime.raiseTypeError(
"Type hint to Date[Symbol.primitive] must be "
"'number', 'string', or 'default'");
}
return ordinaryToPrimitive(O, runtime, tryFirst);
}
} // namespace vm
} // namespace hermes