escargot/src/runtime/TemporalObject.cpp
Gergo Csizi 1763b6f38d Add Temporal JavaScript feature
Signed-off-by: Gergo Csizi gergocs@inf.u-szeged.hu
2022-05-06 19:58:51 +09:00

561 lines
21 KiB
C++

#if defined(ESCARGOT_ENABLE_TEMPORAL)
/*
* Copyright (c) 2022-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "TemporalObject.h"
#include "DateObject.h"
#include "runtime/VMInstance.h"
namespace Escargot {
TemporalObject::TemporalObject(ExecutionState& state)
: TemporalObject(state, state.context()->globalObject()->objectPrototype())
{
}
TemporalObject::TemporalObject(ExecutionState& state, Object* proto)
: Temporal(state, proto)
{
}
Value TemporalObject::toISODateTime(ExecutionState& state, DateObject& d)
{
auto result = std::to_string(d.getFullYear(state))
+ "-"
+ (d.getMonth(state) + 1 < 10 ? "0" : "")
+ std::to_string(d.getMonth(state) + 1)
+ "-"
+ (d.getDate(state) < 10 ? "0" : "")
+ std::to_string(d.getDate(state))
+ "T"
+ (d.getHours(state) < 10 ? "0" : "")
+ std::to_string(d.getHours(state))
+ ":"
+ (d.getMinutes(state) < 10 ? "0" : "")
+ std::to_string(d.getMinutes(state))
+ ":"
+ (d.getSeconds(state) < 10 ? "0" : "")
+ std::to_string(d.getSeconds(state)) + "+00:00";
return Value(new ASCIIString(result.data(), result.length()));
}
Value TemporalObject::toISODate(ExecutionState& state, DateObject& d)
{
auto result = std::to_string(d.getFullYear(state)) + "-" + (d.getMonth(state) + 1 < 10 ? "0" : "") + std::to_string(d.getMonth(state) + 1) + "-" + (d.getDate(state) < 10 ? "0" : "") + std::to_string(d.getDate(state));
return Value(new ASCIIString(result.data(), result.length()));
}
Value TemporalObject::toISOTime(ExecutionState& state, DateObject& d)
{
auto result = (d.getHours(state) < 10 ? "0" : "") + std::to_string(d.getHours(state)) + ":" + (d.getMinutes(state) < 10 ? "0" : "") + std::to_string(d.getMinutes(state)) + ":" + (d.getSeconds(state) < 10 ? "0" : "") + std::to_string(d.getSeconds(state)) + "." + std::to_string(d.getMilliseconds(state));
return Value(new ASCIIString(result.data(), result.length()));
}
std::map<std::string, std::string> TemporalObject::parseValidIso8601String(ExecutionState& state, const Value& str)
{
ASSERT(str.isString());
size_t pos = str.asString()->toNonGCUTF8StringData().find('[');
std::string isoDate = str.asString()->toNonGCUTF8StringData().substr(0, pos);
std::string extra;
if (pos != std::string::npos) {
extra = str.asString()->toNonGCUTF8StringData().substr(pos);
}
pos = isoDate.find('T');
if (pos != std::string::npos) {
std::replace(isoDate.begin(), isoDate.end(), 'T', ' ');
}
pos = isoDate.find(' ');
std::string date = str.asString()->toNonGCUTF8StringData().substr(0, pos);
std::string time = str.asString()->toNonGCUTF8StringData().substr(pos + 1);
std::string year;
std::string month;
std::string week;
std::string day;
pos = date.find('-');
if (pos != std::string::npos) {
if (pos == 0 && date.length() > 4) {
if (date.substr(pos + 1).find('-') == 0) {
date.erase(std::remove(date.begin(), date.end(), '-'), date.end());
if (date.length() == 4) {
month = date.substr(1, 2);
day = date.substr(3, 2);
year = "";
}
} else {
year = date;
}
} else if (date.length() > 4) {
year = date.substr(0, pos);
date = date.substr(pos + 1);
if (date.find('W') != std::string::npos && date.length() == 5) {
week = date.substr(0, 3);
day = date.substr(4);
} else if (date.length() > 2) {
month = date.substr(0, 2);
if (date[2] == '-' && date.length() == 5) {
day = date.substr(3, 2);
}
}
}
} else if (date.length() > 3) {
year = date.substr(0, 4);
if (date.find('W') != std::string::npos && date.length() > 6) {
week = date.substr(4, 3);
if (date.length() > 7) {
day = date[8];
}
} else if (date.length() > 7) {
month = date.substr(4, 2);
day = date.substr(6, 2);
}
}
std::string hour;
std::string minute;
std::string second;
std::string millisecond = "0";
std::string microSecond = "0";
std::string nanoSecond = "0";
std::string timeZone;
std::string fraction;
if (!time.empty()) {
size_t hasTimeZone = (time.find('Z') == std::string::npos ? (time.find('+') == std::string::npos ? time.find('-') : time.find('+')) : time.find('Z'));
if (time.find(',')) {
std::replace(isoDate.begin(), isoDate.end(), ',', '.');
}
if (time.find(':') != std::string::npos && time.length() > 4) {
hour = time.substr(0, 2);
minute = time.substr(2, 2);
if (time.length() > 6) {
second = time.substr(6, 2);
}
} else if (time.length() > 1) {
hour = time.substr(0, 2);
minute = time.substr(2, 2);
second = time.substr(4, 2);
}
pos = time.find('.');
if (pos != std::string::npos) {
fraction = time.substr(pos + 1, hasTimeZone);
}
if (hasTimeZone != std::string::npos) {
timeZone = time.substr(hasTimeZone);
}
}
std::string timeZoneExtension;
std::string calendar;
if (!extra.empty()) {
size_t n = std::count(extra.begin(), extra.end(), '[');
if (n == 2) {
pos = extra.find("[]");
timeZoneExtension = extra.substr(0, pos);
calendar = extra.substr(pos + 2, extra.length() - 1);
} else if (n == 1) {
timeZoneExtension = extra.substr(1, extra.length() - 1);
}
}
if (year == "-000000") {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, "Invalid year");
}
if (month.empty()) {
month = "1";
}
if (day.empty()) {
day = "1";
}
if (second == "60") {
second = "59";
}
if (!fraction.empty()) {
fraction += "000000000";
millisecond = fraction.substr(0, 3);
microSecond = fraction.substr(3, 3);
nanoSecond = fraction.substr(6, 3);
}
if (!TemporalPlainDate::isValidISODate(state, Value(year.c_str()), Value(month.c_str()), Value(day.c_str()))) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, "Invalid ISO Date");
}
if (!TemporalPlainTime::isValidTime(state, Value(hour.c_str()), Value(minute.c_str()), Value(second.c_str()), Value(millisecond.c_str()), Value(microSecond.c_str()), Value(nanoSecond.c_str()))) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, "Invalid time");
}
return std::map<std::string, std::string>{ { "Year", year }, { "Month", month }, { "Day", day }, { "Hour", hour }, { "Minute", minute }, { "Second", second }, { "Millisecond", millisecond }, { "Microsecond", microSecond }, { "Nanosecond", nanoSecond }, { "Calendar", calendar } };
}
TemporalPlainTime::TemporalPlainTime(ExecutionState& state)
: TemporalPlainTime(state, state.context()->globalObject()->objectPrototype())
{
}
TemporalPlainTime::TemporalPlainTime(ExecutionState& state, Object* proto)
: Temporal(state, proto)
{
}
Value TemporalPlainTime::createTemporalTime(ExecutionState& state, size_t argc, Value* argv, Optional<Object*> newTarget)
{
Value hour, min, sec, ms, qs, ns;
if (argc > 5) {
hour = argv[0];
min = argv[1];
sec = argv[2];
ms = argv[3];
qs = argv[4];
ns = argv[5];
} else {
ErrorObject::throwBuiltinError(state, ErrorObject::TypeError, "Too few arguments");
}
ASSERT(hour.isInteger(state) && min.isInteger(state) && sec.isInteger(state) && ms.isInteger(state) && qs.isInteger(state) && ns.isInteger(state));
time64_t timeInMs = hour.asInt32() * 3600000 + min.asInt32() * 60000 + sec.asInt32() * 1000 + ms.asInt32();
if (!IS_IN_TIME_RANGE(timeInMs)) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, ErrorObject::Messages::GlobalObject_IllegalFirstArgument);
}
TemporalPlainTime* temporalPlainTime;
if (!newTarget.hasValue()) {
newTarget = state.resolveCallee();
}
temporalPlainTime = new TemporalPlainTime(state, newTarget->asObject());
temporalPlainTime->setTime(hour.asInt32(), min.asInt32(), sec.asInt32(), ms.asInt32(), qs.asInt32(), ns.asInt32());
temporalPlainTime->setCalendar(state, new ASCIIString("iso8601"), newTarget);
return temporalPlainTime;
}
void TemporalPlainTime::setTime(char h, char m, char s, short ms, short qs, short ns)
{
this->m_hour = h;
this->m_minute = m;
this->m_second = s;
this->m_millisecond = ms;
this->m_microsecond = qs;
this->m_nanosecond = ns;
}
void TemporalPlainTime::setCalendar(ExecutionState& state, String* id, Optional<Object*> newTarget)
{
this->calendar = TemporalCalendar::createTemporalCalendar(state, id, newTarget);
}
bool TemporalPlainTime::isValidTime(ExecutionState& state, const Value& hour, const Value& minute, const Value& second, const Value& millisecond, const Value& microsecond, const Value& nanosecond)
{
ASSERT(hour.isInteger(state) && minute.isInteger(state) && second.isInteger(state) && millisecond.isInteger(state) && microsecond.isInteger(state) && nanosecond.isInteger(state));
int h = hour.asInt32();
int m = minute.asInt32();
int s = second.asInt32();
int ms = millisecond.asInt32();
int us = microsecond.asInt32();
int ns = nanosecond.asInt32();
if (h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59 || ms < 0 || ms > 999 || us < 0 || us > 999 || ns < 0 || ns > 999) {
return false;
}
return true;
}
bool TemporalCalendar::isBuiltinCalendar(String* id)
{
return id->equals("iso8601");
}
TemporalCalendar::TemporalCalendar(ExecutionState& state)
: TemporalCalendar(state, state.context()->globalObject()->objectPrototype())
{
}
TemporalCalendar::TemporalCalendar(ExecutionState& state, Object* proto)
: Temporal(state, proto)
{
}
TemporalCalendar* TemporalCalendar::createTemporalCalendar(ExecutionState& state, String* id, Optional<Object*> newTarget)
{
ASSERT(TemporalCalendar::isBuiltinCalendar(id));
if (!newTarget.hasValue()) {
newTarget = state.resolveCallee();
}
Object* proto = Object::getPrototypeFromConstructor(state, newTarget.value(), [](ExecutionState& state, Context* constructorRealm) -> Object* {
return constructorRealm->globalObject()->aggregateErrorPrototype();
});
auto* O = new TemporalCalendar(state, proto);
O->setIdentifier(id);
return O;
}
String* TemporalCalendar::getIdentifier() const
{
return m_identifier;
}
void TemporalCalendar::setIdentifier(String* id)
{
TemporalCalendar::m_identifier = id;
}
Value TemporalCalendar::getBuiltinCalendar(ExecutionState& state, String* id)
{
if (!TemporalCalendar::isBuiltinCalendar(id)) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, ErrorObject::Messages::IsNotDefined);
}
Value argv[1] = { Value(id) };
return Object::construct(state, state.context()->globalObject()->temporalCalendar(), 1, argv).toObject(state);
}
Value TemporalCalendar::getISO8601Calendar(ExecutionState& state)
{
return TemporalCalendar::getBuiltinCalendar(state, new ASCIIString("iso8601"));
}
Value TemporalCalendar::toTemporalCalendar(ExecutionState& state, const Value& calendar)
{
if (calendar.isObject()) {
auto* calendarObject = calendar.asObject()->asTemporalCalendarObject();
if (calendarObject->internalSlot()->isTemporalObject()) {
return Value(calendarObject->getIdentifier());
}
if (!calendarObject->hasProperty(state, AtomicString(state, "calendar"))) {
return calendar;
}
calendar.asObject()->get(state, AtomicString(state, "calendar"), calendar);
if (!calendarObject->hasProperty(state, AtomicString(state, "calendar"))) {
return calendar;
}
}
String* identifier = calendar.asString();
if (!TemporalCalendar::isBuiltinCalendar(identifier)) {
identifier = TemporalCalendar::parseTemporalCalendarString(state, identifier).asString();
if (!TemporalCalendar::isBuiltinCalendar(identifier)) {
ErrorObject::throwBuiltinError(state, ErrorObject::TypeError, ErrorObject::Messages::IsNotDefined);
}
}
return Value(TemporalCalendar::createTemporalCalendar(state, identifier));
}
Value TemporalCalendar::parseTemporalCalendarString(ExecutionState& state, const Value& isoString)
{
ASSERT(isoString.isString());
return TemporalObject::parseValidIso8601String(state, isoString).at("Calendar").empty() ? Value("iso8601") : Value(TemporalObject::parseValidIso8601String(state, isoString).at("Calendar").c_str());
}
Value TemporalCalendar::ISODaysInMonth(ExecutionState& state, const Value& year, const Value& month)
{
ASSERT(year.isInteger(state));
ASSERT(month.isInteger(state) && month.asInt32() > 0 && month.asInt32() < 13);
int m = month.asInt32();
if (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) {
return Value(31);
}
if (m == 4 || m == 6 || m == 9 || m == 11) {
return Value(30);
}
if (TemporalCalendar::isIsoLeapYear(state, year)) {
return Value(29);
}
return Value(28);
}
bool TemporalCalendar::isIsoLeapYear(ExecutionState& state, const Value& year)
{
ASSERT(year.isInteger(state));
int y = year.asInt32();
if (y % 4 != 0) {
return false;
}
if (y % 400 == 0) {
return true;
}
if (y % 100 == 0) {
return false;
}
return true;
}
/*12.1.6 toTemporalCalendarWithISODefault*/
Value TemporalCalendar::toTemporalCalendarWithISODefault(ExecutionState& state, const Value& calendar)
{
if (calendar.isUndefined()) {
return TemporalCalendar::getISO8601Calendar(state);
}
return TemporalCalendar::toTemporalCalendar(state, calendar);
}
Value TemporalCalendar::defaultMergeFields(ExecutionState& state, const Value& fields, const Value& additionalFields)
{
Object* merged = Object::getPrototypeFromConstructor(state, state.resolveCallee(), [](ExecutionState& state, Context* constructorRealm) -> Object* {
return constructorRealm->globalObject()->objectPrototype();
});
auto originalKeys = Object::enumerableOwnProperties(state, fields.asObject(), EnumerableOwnPropertiesType::Key);
for (auto nextKey : originalKeys) {
if (!nextKey.asString()->equals("month") || !nextKey.asString()->equals("monthCode")) {
Value propValue;
fields.asObject()->get(state, AtomicString(state, nextKey.asString()), propValue);
if (!propValue.isUndefined()) {
merged->defineOwnPropertyThrowsException(state, AtomicString(state, nextKey.asString()), ObjectPropertyDescriptor(propValue, ObjectPropertyDescriptor::AllPresent));
}
}
}
auto newKeys = Object::enumerableOwnProperties(state, additionalFields.asObject(), EnumerableOwnPropertiesType::Key);
bool containsMonth = false;
for (unsigned int i = 0; i < newKeys.size(); ++i) {
Value nextKey = originalKeys[i];
if (!nextKey.asString()->equals("month") || !nextKey.asString()->equals("monthCode")) {
containsMonth = true;
}
Value propValue;
fields.asObject()->get(state, AtomicString(state, nextKey.asString())).value(state, propValue);
if (!propValue.isUndefined()) {
merged->defineOwnPropertyThrowsException(state, AtomicString(state, nextKey.asString()), ObjectPropertyDescriptor(propValue, ObjectPropertyDescriptor::AllPresent));
}
}
if (!containsMonth) {
Value month;
fields.asObject()->get(state, AtomicString(state, "month"), month);
if (!month.isUndefined()) {
merged->defineOwnPropertyThrowsException(state, AtomicString(state, "month"), ObjectPropertyDescriptor(month, ObjectPropertyDescriptor::AllPresent));
}
Value monthCode;
fields.asObject()->get(state, AtomicString(state, "monthCode"), monthCode);
if (!monthCode.isUndefined()) {
merged->defineOwnPropertyThrowsException(state, AtomicString(state, "monthCode"), ObjectPropertyDescriptor(monthCode, ObjectPropertyDescriptor::AllPresent));
}
}
return Value(merged);
}
bool TemporalPlainDate::isValidISODate(ExecutionState& state, const Value& year, const Value& month, const Value& day)
{
ASSERT(year.isInteger(state) && month.isInteger(state) && day.isInteger(state));
if (month.asInt32() < 1 || month.asInt32() > 12) {
return false;
}
Value daysInMonth = TemporalCalendar::ISODaysInMonth(state, year, month);
if (day.asInt32() < 1 || day.asInt32() > daysInMonth.asInt32()) {
return false;
}
return true;
}
Value TemporalPlainDate::createTemporalDate(ExecutionState& state, const Value& isoYear, const Value& isoMonth, const Value& isoDay, const Value& calendar, Optional<Object*> newTarget)
{
ASSERT(isoYear.isInteger(state) && isoMonth.isInteger(state) && isoDay.isInteger(state));
ASSERT(calendar.isObject());
if (!TemporalPlainDate::isValidISODate(state, isoYear, isoMonth, isoDay)) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, "Not valid ISOdate");
}
if (!TemporalPlainDateTime::ISODateTimeWithinLimits(state, isoYear, isoMonth, isoDay, Value(12), Value(0), Value(0), Value(0), Value(0), Value(0))) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, "Not valid ISOdate");
}
Object* newTargetVariable;
if (!newTarget.hasValue()) {
newTargetVariable = state.resolveCallee();
} else {
newTargetVariable = newTarget.value();
}
Object* proto = Object::getPrototypeFromConstructor(state, newTargetVariable, [](ExecutionState& state, Context* realm) -> Object* {
return realm->globalObject()->temporalPlainDatePrototype();
});
Object* object = new TemporalPlainDate(state, proto, isoYear, isoMonth, isoDay, calendar);
return Value(object);
}
TemporalPlainDate::TemporalPlainDate(ExecutionState& state, Value isoYear, Value isoMonth, Value isoDay, Optional<Value> calendarLike)
: TemporalPlainDate(state, state.context()->globalObject()->temporalPlainDatePrototype(), isoYear, isoMonth, isoDay, calendarLike)
{
}
TemporalPlainDate::TemporalPlainDate(ExecutionState& state, Object* proto, Value isoYear, Value isoMonth, Value isoDay, Optional<Value> calendarLike)
: Temporal(state, proto)
, m_y(isoYear.asInt32())
, m_m(isoMonth.asInt32())
, m_d(isoDay.asInt32())
, m_calendar(calendarLike.value())
{
}
TemporalPlainDateTime::TemporalPlainDateTime(ExecutionState& state)
: TemporalPlainDateTime(state, state.context()->globalObject()->objectPrototype())
{
}
TemporalPlainDateTime::TemporalPlainDateTime(ExecutionState& state, Object* proto)
: Temporal(state, proto)
{
}
Value TemporalPlainDateTime::getEpochFromISOParts(ExecutionState& state, const Value& year, const Value& month, const Value& day, const Value& hour, const Value& minute, const Value& second, const Value& millisecond, const Value& microsecond, const Value& nanosecond)
{
ASSERT(year.isInteger(state) && month.isInteger(state) && day.isInteger(state) && hour.isInteger(state) && minute.isInteger(state) && second.isInteger(state) && millisecond.isInteger(state) && microsecond.isInteger(state) && nanosecond.isInteger(state));
ASSERT(TemporalPlainDate::isValidISODate(state, year, month, day));
ASSERT(TemporalPlainTime::isValidTime(state, hour, minute, second, millisecond, microsecond, nanosecond));
Value date = DateObject::makeDay(state, year, month, day);
Value time = DateObject::makeTime(state, hour, minute, second, millisecond);
Value ms = DateObject::makeDate(state, date, time);
ASSERT(std::isfinite(ms.asNumber()));
return Value(ms.toLength(state));
}
bool TemporalPlainDateTime::ISODateTimeWithinLimits(ExecutionState& state, const Value& year, const Value& month, const Value& day, const Value& hour, const Value& minute, const Value& second, const Value& millisecond, const Value& microsecond, const Value& nanosecond)
{
ASSERT(year.isInteger(state) && month.isInteger(state) && day.isInteger(state) && hour.isInteger(state) && minute.isInteger(state) && second.isInteger(state) && millisecond.isInteger(state) && microsecond.isInteger(state) && nanosecond.isInteger(state));
time64_t ms = TemporalPlainDateTime::getEpochFromISOParts(state, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond).toLength(state);
return IS_IN_TIME_RANGE(ms);
}
} // namespace Escargot
#endif