escargot/src/runtime/DateObject.cpp
Seonghyun Kim f5ae276846 Disallow -0 year on DateObject parsing
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00

1610 lines
50 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2016 Samsung Electronics Co., Ltd. All Rights Reserved
* Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2009 Google Inc. All rights reserved.
* Copyright (C) 2007-2009 Torch Mobile, Inc.
* Copyright (C) 2010 &yet, LLC. (nate@andyet.net)
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* 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.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
*
* Alternatively, the contents of this file may be used under the terms
* of either the Mozilla Public License Version 1.1, found at
* http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
* License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
* (the "GPL"), in which case the provisions of the MPL or the GPL are
* applicable instead of those above. If you wish to allow use of your
* version of this file only under the terms of one of those two
* licenses (the MPL or the GPL) and not to allow others to use your
* version of this file under the LGPL, indicate your decision by
* deletingthe provisions above and replace them with the notice and
* other provisions required by the MPL or the GPL, as the case may be.
* If you do not delete the provisions above, a recipient may use your
* version of this file under any of the LGPL, the MPL or the GPL.
* Copyright 2006-2008 the V8 project authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Escargot.h"
#include "DateObject.h"
#include "Context.h"
#include "runtime/VMInstance.h"
#if defined(ENABLE_TEMPORAL)
#include "runtime/TemporalInstantObject.h"
#endif
#include <time.h>
namespace Escargot {
#define RESOLVECACHE(state) \
if (m_isCacheDirty) { \
resolveCache(state); \
}
#define LEAP ((int16_t)1)
enum : unsigned { JAN,
FEB,
MAR,
APR,
MAY,
JUN,
JUL,
AUG,
SEP,
OCT,
NOV,
DEC,
INVALID };
enum : unsigned { SUN,
MON,
TUE,
WED,
THU,
FRI,
SAT };
static constexpr int8_t daysInMonth[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static constexpr int16_t _firstDay(int mon)
{
return (mon == 0) ? 0 : (_firstDay(mon - 1) + daysInMonth[mon - 1]);
}
static constexpr int16_t firstDayOfMonth[2][13] = {
{
_firstDay(JAN),
_firstDay(FEB),
_firstDay(MAR),
_firstDay(APR),
_firstDay(MAY),
_firstDay(JUN),
_firstDay(JUL),
_firstDay(AUG),
_firstDay(SEP),
_firstDay(OCT),
_firstDay(NOV),
_firstDay(DEC),
_firstDay(INVALID),
},
{
_firstDay(JAN),
_firstDay(FEB),
_firstDay(MAR) + LEAP,
_firstDay(APR) + LEAP,
_firstDay(MAY) + LEAP,
_firstDay(JUN) + LEAP,
_firstDay(JUL) + LEAP,
_firstDay(AUG) + LEAP,
_firstDay(SEP) + LEAP,
_firstDay(OCT) + LEAP,
_firstDay(NOV) + LEAP,
_firstDay(DEC) + LEAP,
_firstDay(INVALID) + LEAP,
},
};
static const char days[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static const char months[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
static constexpr char invalidDate[] = "Invalid Date";
static const int monthNumberHelper[] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
DateObject::DateObject(ExecutionState& state)
: DateObject(state, state.context()->globalObject()->datePrototype())
{
}
DateObject::DateObject(ExecutionState& state, Object* proto)
: DerivedObject(state, proto)
, m_primitiveValue(TIME64NAN)
, m_cachedLocal()
, m_isCacheDirty(false)
{
}
#if defined(OS_WINDOWS)
#define CLOCK_REALTIME 0
#include <windows.h>
struct timespec {
long tv_sec;
long tv_nsec;
}; // header part
static int clock_gettime(int, struct timespec* spec) // C-file part
{
__int64 wintime;
GetSystemTimeAsFileTime((FILETIME*)&wintime);
wintime -= 116444736000000000i64; // 1jan1601 to 1jan1970
spec->tv_sec = wintime / 10000000i64; // seconds
spec->tv_nsec = wintime % 10000000i64 * 100; // nano-seconds
return 0;
}
#endif
time64_t DateObject::currentTime()
{
struct timespec time;
if (clock_gettime(CLOCK_REALTIME, &time) == 0) {
return time.tv_sec * TimeConstant::MsPerSecond + time.tv_nsec / TimeConstant::NsPerMs;
} else {
return TIME64NAN;
}
}
void DateObject::setTimeValue(time64_t t)
{
m_primitiveValue = t;
if (IS_VALID_TIME(m_primitiveValue)) {
m_isCacheDirty = true;
}
}
void DateObject::setTimeValue(ExecutionState& state, const Value& v)
{
Value pv = v.toPrimitive(state);
if (pv.isNumber()) {
setTimeValue(DateObject::timeClipToTime64(state, pv.asNumber()));
} else {
String* istr = v.toString(state);
setTimeValue(parseStringToDate(state, istr));
}
}
void DateObject::setTimeValue(ExecutionState& state, int year, int month,
int date, int hour, int minute, int64_t second,
int64_t millisecond, bool convertToUTC)
{
int yearComputed = year + floor(month / (double)TimeConstant::MonthsPerYear);
int monthComputed = month % TimeConstant::MonthsPerYear;
if (monthComputed < 0)
monthComputed = (monthComputed + TimeConstant::MonthsPerYear) % TimeConstant::MonthsPerYear;
time64_t primitiveValue = timeinfoToMs(state, yearComputed, monthComputed, date, hour, minute, second, millisecond);
if (convertToUTC) {
primitiveValue = applyLocalTimezoneOffset(state, primitiveValue);
}
if (LIKELY(IS_VALID_TIME(primitiveValue)) && IS_IN_TIME_RANGE(primitiveValue)) {
m_primitiveValue = primitiveValue;
} else {
setTimeValueAsNaN();
}
m_isCacheDirty = true;
}
// code from WebKit 3369f50e501f85e27b6e7baffd0cc7ac70931cc3
// WTF/wtf/DateMath.cpp:340
static int equivalentYearForDST(int year)
{
// It is ok if the cached year is not the current year as long as the rules
// for DST did not change between the two years; if they did the app would need
// to be restarted.
static int minYear = 2010;
int maxYear = 2037;
int difference;
if (year > maxYear) {
difference = minYear - year;
} else if (year < minYear) {
difference = maxYear - year;
} else {
return year;
}
int quotient = difference / 28;
int product = quotient * 28;
year += product;
ASSERT((year >= minYear && year <= maxYear) || (product - year == static_cast<int>(std::numeric_limits<double>::quiet_NaN())));
return year;
}
// Make timeinfo which assumes UTC timezone offset to
// timeinfo which assumes local timezone offset
// e.g. return (t - 32400*1000) on KST zone
time64_t DateObject::applyLocalTimezoneOffset(ExecutionState& state, time64_t t)
{
#if defined(ENABLE_ICU)
UErrorCode status = U_ZERO_ERROR;
#endif
int32_t stdOffset = 0, dstOffset = 0;
// roughly check range before calling yearFromTime function
#if defined(ENABLE_ICU)
auto cal = state.context()->vmInstance()->calendar();
ucal_setMillis(cal, t, &status);
stdOffset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
#else
stdOffset = 0;
#endif
if (!IS_IN_TIME_RANGE(t - stdOffset)) {
return TIME64NAN;
}
// Find the equivalent year because ECMAScript spec says
// The implementation should not try to determine
// whether the exact time was subject to daylight saving time,
// but just whether daylight saving time would have been in effect
// if the current daylight saving time algorithm had been used at the time.
int realYear = yearFromTime(t);
int equivalentYear = equivalentYearForDST(realYear);
time64_t msBetweenYears = (realYear != equivalentYear) ? (timeFromYear(equivalentYear) - timeFromYear(realYear)) : 0;
t += msBetweenYears;
#if defined(ENABLE_ICU)
ucal_setMillis(cal, t, &status);
stdOffset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
dstOffset = ucal_get(cal, UCAL_DST_OFFSET, &status);
#else
dstOffset = 0;
#endif
t -= msBetweenYears;
#if defined(ENABLE_ICU)
// range check should be completed by caller function
if (!U_FAILURE(status)) {
return t - (stdOffset + dstOffset);
}
return TIME64NAN;
#else
return t - (stdOffset + dstOffset);
#endif
}
time64_t DateObject::timeinfoToMs(ExecutionState& state, int year, int mon, int day, int hour, int minute, int64_t second, int64_t millisecond)
{
return (daysToMs(year, mon, day) + hour * TimeConstant::MsPerHour + minute * TimeConstant::MsPerMinute + second * TimeConstant::MsPerSecond + millisecond);
}
static const struct KnownZone {
char tzName[4];
int tzOffset;
} known_zones[] = {
{ "UT", 0 },
{ "GMT", 0 },
{ "EST", -300 },
{ "EDT", -240 },
{ "CST", -360 },
{ "CDT", -300 },
{ "MST", -420 },
{ "MDT", -360 },
{ "PST", -480 },
{ "PDT", -420 }
};
// returns 0-11 (Jan-Dec); -1 on failure
static int findMonth(const char* monthStr)
{
ASSERT(monthStr);
char needle[4];
for (int i = 0; i < 3; ++i) {
if (!*monthStr) {
return -1;
}
// needle[i] = static_cast<char>(toASCIILower(*monthStr++));
needle[i] = (*monthStr++) | (uint8_t)0x20;
}
needle[3] = '\0';
const char* haystack = "janfebmaraprmayjunjulaugsepoctnovdec";
const char* str = strstr(haystack, needle);
if (str) {
int position = static_cast<int>(str - haystack);
if (position % 3 == 0) {
return position / 3;
}
}
return -1;
}
inline bool isASCIISpace(char c)
{
return c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9));
}
inline static void skipSpacesAndComments(const char*& s)
{
int nesting = 0;
char ch;
while ((ch = *s)) {
if (!isASCIISpace(ch)) {
if (ch == '(') {
nesting++;
} else if (ch == ')' && nesting > 0) {
nesting--;
} else if (nesting == 0) {
break;
}
}
s++;
}
}
static bool parseInt(const char* string, char** stopPosition, int base, int* result)
{
long longResult = strtol(string, stopPosition, base);
// Avoid the use of errno as it is not available on Windows CE
if (string == *stopPosition || longResult <= std::numeric_limits<int>::min() || longResult >= std::numeric_limits<int>::max()) {
return false;
}
*result = static_cast<int>(longResult);
return true;
}
static bool parseLong(const char* string, char** stopPosition, int base, long* result, int digits = 0)
{
if (digits == 0) {
*result = strtol(string, stopPosition, base);
// Avoid the use of errno as it is not available on Windows CE
if (string == *stopPosition || *result == std::numeric_limits<long>::min() || *result == std::numeric_limits<long>::max()) {
return false;
}
return true;
} else {
strtol(string, stopPosition, base); // for compute stopPosition
char s[4]; // 4 is temporary number for case (digit == 3)..
s[0] = string[0];
s[1] = string[1];
s[2] = string[2];
s[3] = '\0';
*result = strtol(s, NULL, base);
if (string == *stopPosition || *result == std::numeric_limits<long>::min() || *result == std::numeric_limits<long>::max()) {
return false;
}
return true;
}
}
time64_t DateObject::parseStringToDate_1(ExecutionState& state, String* istr, bool& haveTZ, int& offset)
{
haveTZ = false;
offset = 0;
long month = -1;
const char* dateString = istr->toUTF8StringData().data();
const char* wordStart = dateString;
skipSpacesAndComments(dateString);
while (*dateString && !isASCIIDigit(*dateString)) {
if (isASCIISpace(*dateString) || *dateString == '(') {
if (dateString - wordStart >= 3) {
month = findMonth(wordStart);
}
skipSpacesAndComments(dateString);
wordStart = dateString;
} else {
dateString++;
}
}
if (month == -1 && wordStart != dateString) {
month = findMonth(wordStart);
}
skipSpacesAndComments(dateString);
if (!*dateString) {
return TIME64NAN;
}
char* newPosStr;
long int day;
if (!parseLong(dateString, &newPosStr, 10, &day)) {
return TIME64NAN;
}
dateString = newPosStr;
if (!*dateString) {
return TIME64NAN;
}
if (day < 0) {
return TIME64NAN;
}
int year = 0;
bool cont = false;
time64_t result;
result = parseStringToDate_1_1(day, month, year, dateString, newPosStr, cont);
if (!cont) {
return result;
}
// Don't fail if the time is missing.
long hour = 0;
long minute = 0;
long second = 0;
result = parseStringToDate_1_2(year, hour, minute, second, dateString, newPosStr, cont);
if (!cont) {
return result;
}
result = parseStringToDate_1_3(year, haveTZ, offset, dateString, newPosStr, cont);
if (!cont) {
return result;
}
// Trailing garbage
if (*dateString) {
return TIME64NAN;
}
// Y2K: Handle 2 digit years.
if (year >= 0 && year < 100) {
if (year < 50) {
year += 2000;
} else {
year += 1900;
}
}
return timeinfoToMs(state, year, month, day, hour, minute, second, 0);
}
time64_t DateObject::parseStringToDate_1_1(long int& day, long& month, int& year, const char*& dateString, char*& newPosStr, bool& cont)
{
cont = false;
if (day > 31) {
// ### where is the boundary and what happens below?
if (*dateString != '/') {
return TIME64NAN;
}
// looks like a YYYY/MM/DD date
if (!*++dateString) {
return TIME64NAN;
}
if (day <= std::numeric_limits<int>::min() || day >= std::numeric_limits<int>::max()) {
return TIME64NAN;
}
year = static_cast<int>(day);
if (!parseLong(dateString, &newPosStr, 10, &month)) {
return TIME64NAN;
}
month -= 1;
dateString = newPosStr;
if (*dateString++ != '/' || !*dateString) {
return TIME64NAN;
}
if (!parseLong(dateString, &newPosStr, 10, &day)) {
return TIME64NAN;
}
dateString = newPosStr;
} else if (*dateString == '/' && month == -1) {
dateString++;
// This looks like a MM/DD/YYYY date, not an RFC date.
month = day - 1; // 0-based
if (!parseLong(dateString, &newPosStr, 10, &day)) {
return TIME64NAN;
}
if (day < 1 || day > 31) {
return TIME64NAN;
}
dateString = newPosStr;
if (*dateString == '/') {
dateString++;
}
if (!*dateString) {
return TIME64NAN;
}
} else {
if (*dateString == '-') {
dateString++;
}
skipSpacesAndComments(dateString);
if (*dateString == ',') {
dateString++;
}
if (month == -1) { // not found yet
month = findMonth(dateString);
if (month == -1) {
return TIME64NAN;
}
while (*dateString && *dateString != '-' && *dateString != ',' && !isASCIISpace(*dateString)) {
dateString++;
}
if (!*dateString) {
return TIME64NAN;
}
// '-99 23:12:40 GMT'
if (*dateString != '-' && *dateString != '/' && *dateString != ',' && !isASCIISpace(*dateString)) {
return TIME64NAN;
}
dateString++;
}
}
if (month < 0 || month > 11) {
return TIME64NAN;
}
// '99 23:12:40 GMT'
if (year <= 0 && *dateString && !parseInt(dateString, &newPosStr, 10, &year)) {
return TIME64NAN;
}
cont = true;
return TIME64NAN;
}
time64_t DateObject::parseStringToDate_1_2(int& year, long& hour, long& minute, long& second, const char*& dateString, char*& newPosStr, bool& cont)
{
cont = false;
if (!*newPosStr) {
dateString = newPosStr;
} else {
// ' 23:12:40 GMT'
if (!(isASCIISpace(*newPosStr) || *newPosStr == ',')) {
if (*newPosStr != ':') {
return TIME64NAN;
}
// There was no year; the number was the hour.
year = -1;
} else {
// in the normal case (we parsed the year), advance to the next number
dateString = ++newPosStr;
skipSpacesAndComments(dateString);
}
bool temp = parseLong(dateString, &newPosStr, 10, &hour);
UNUSED_VARIABLE(temp);
// Do not check for errno here since we want to continue
// even if errno was set becasue we are still looking
// for the timezone!
// Read a number? If not, this might be a timezone name.
if (newPosStr != dateString) {
dateString = newPosStr;
if (hour < 0 || hour > 23) {
return TIME64NAN;
}
if (!*dateString) {
return TIME64NAN;
}
// ':12:40 GMT'
if (*dateString++ != ':') {
return TIME64NAN;
}
if (!parseLong(dateString, &newPosStr, 10, &minute)) {
return TIME64NAN;
}
dateString = newPosStr;
if (minute < 0 || minute > 59) {
return TIME64NAN;
}
// ':40 GMT'
if (*dateString && *dateString != ':' && !isASCIISpace(*dateString)) {
return TIME64NAN;
}
// seconds are optional in rfc822 + rfc2822
if (*dateString == ':') {
dateString++;
if (!parseLong(dateString, &newPosStr, 10, &second)) {
return TIME64NAN;
}
dateString = newPosStr;
if (second < 0 || second > 59) {
return TIME64NAN;
}
}
skipSpacesAndComments(dateString);
if (strncasecmp(dateString, "AM", 2) == 0) {
if (hour > 12) {
return TIME64NAN;
}
if (hour == 12) {
hour = 0;
}
dateString += 2;
skipSpacesAndComments(dateString);
} else if (strncasecmp(dateString, "PM", 2) == 0) {
if (hour > 12) {
return TIME64NAN;
}
if (hour != 12) {
hour += 12;
}
dateString += 2;
skipSpacesAndComments(dateString);
}
}
}
cont = true;
return TIME64NAN;
}
time64_t DateObject::parseStringToDate_1_3(int& year, bool& haveTZ, int& offset, const char*& dateString, char*& newPosStr, bool& cont)
{
cont = false;
// The year may be after the time but before the time zone.
if (isASCIIDigit(*dateString) && year == -1) {
if (!parseInt(dateString, &newPosStr, 10, &year)) {
return TIME64NAN;
}
dateString = newPosStr;
skipSpacesAndComments(dateString);
}
// Don't fail if the time zone is missing.
// Some websites omit the time zone (4275206).
if (*dateString) {
if (strncasecmp(dateString, "GMT", 3) == 0 || strncasecmp(dateString, "UTC", 3) == 0) {
dateString += 3;
haveTZ = true;
}
if (*dateString == '+' || *dateString == '-') {
int o;
if (!parseInt(dateString, &newPosStr, 10, &o)) {
return TIME64NAN;
}
dateString = newPosStr;
if (o < -9959 || o > 9959) {
return TIME64NAN;
}
int sgn = (o < 0) ? -1 : 1;
o = abs(o);
if (*dateString != ':') {
if (o >= 24) {
offset = ((o / 100) * 60 + (o % 100)) * sgn;
} else {
offset = o * 60 * sgn;
}
} else { // GMT+05:00
++dateString; // skip the ':'
int o2;
if (!parseInt(dateString, &newPosStr, 10, &o2)) {
return TIME64NAN;
}
dateString = newPosStr;
offset = (o * 60 + o2) * sgn;
}
haveTZ = true;
} else {
size_t arrlenOfTZ = sizeof(known_zones) / sizeof(struct KnownZone);
for (size_t i = 0; i < arrlenOfTZ; ++i) {
if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
offset = known_zones[i].tzOffset;
dateString += strlen(known_zones[i].tzName);
haveTZ = true;
break;
}
}
}
}
skipSpacesAndComments(dateString);
if (*dateString && year == -1) {
if (!parseInt(dateString, &newPosStr, 10, &year)) {
return TIME64NAN;
}
dateString = newPosStr;
skipSpacesAndComments(dateString);
}
cont = true;
return TIME64NAN;
}
static char* parseES5DatePortion(const char* currentPosition, int& year, long& month, long& day)
{
char* postParsePosition;
bool minusFirst = *currentPosition == '-';
// This is a bit more lenient on the year string than ES5 specifies:
// instead of restricting to 4 digits (or 6 digits with mandatory +/-),
// it accepts any integer value. Consider this an implementation fallback.
if (!parseInt(currentPosition, &postParsePosition, 10, &year)) {
return 0;
}
// The year 0 is considered positive and must be prefixed with a + sign.
if (minusFirst && year == 0) {
return 0;
}
// Check for presence of -MM portion.
if (*postParsePosition != '-') {
return postParsePosition;
}
currentPosition = postParsePosition + 1;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &month)) {
return 0;
}
if ((postParsePosition - currentPosition) != 2) {
return 0;
}
// Check for presence of -DD portion.
if (*postParsePosition != '-') {
return postParsePosition;
}
currentPosition = postParsePosition + 1;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &day)) {
return 0;
}
if ((postParsePosition - currentPosition) != 2) {
return 0;
}
return postParsePosition;
}
static char* parseES5TimePortion(char* currentPosition, long& hours, long& minutes, long& milliSeconds, long& timeZoneSeconds, bool& haveTZ)
{
char* postParsePosition;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &hours)) {
return 0;
}
if (*postParsePosition != ':' || (postParsePosition - currentPosition) != 2) {
return 0;
}
currentPosition = postParsePosition + 1;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &minutes)) {
return 0;
}
if ((postParsePosition - currentPosition) != 2) {
return 0;
}
currentPosition = postParsePosition;
// Seconds are optional.
if (*currentPosition == ':') {
++currentPosition;
long intSeconds;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &intSeconds)) {
return 0;
}
if ((postParsePosition - currentPosition) != 2) {
return 0;
}
milliSeconds = intSeconds * TimeConstant::MsPerSecond;
if (*postParsePosition == '.') {
currentPosition = postParsePosition + 1;
// In ECMA-262-5 it's a bit unclear if '.' can be present without milliseconds, but
// a reasonable interpretation guided by the given examples and RFC 3339 says "no".
// We check the next character to avoid reading +/- timezone hours after an invalid decimal.
if (!isASCIIDigit(*currentPosition))
return 0;
long fracSeconds;
if (!parseLong(currentPosition, &postParsePosition, 10, &fracSeconds, 3)) {
return 0;
}
long numFracDigits = std::min((long)(postParsePosition - currentPosition), 3L);
milliSeconds += fracSeconds * pow(10.0, static_cast<double>(3 - numFracDigits));
}
currentPosition = postParsePosition;
}
if (*currentPosition == 'Z') {
haveTZ = true;
return currentPosition + 1;
}
bool tzNegative;
if (*currentPosition == '-') {
tzNegative = true;
} else if (*currentPosition == '+') {
tzNegative = false;
} else {
return currentPosition; // no timezone
}
++currentPosition;
haveTZ = true;
long tzHours;
long tzHoursAbs;
long tzMinutes;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &tzHours)) {
return 0;
}
if (postParsePosition - currentPosition == 4) {
tzMinutes = tzHours % 100;
tzHours = tzHours / 100;
tzHoursAbs = labs(tzHours);
} else if (postParsePosition - currentPosition == 2) {
if (*postParsePosition != ':') {
return 0;
}
tzHoursAbs = labs(tzHours);
currentPosition = postParsePosition + 1;
if (!isASCIIDigit(*currentPosition)) {
return 0;
}
if (!parseLong(currentPosition, &postParsePosition, 10, &tzMinutes)) {
return 0;
}
if ((postParsePosition - currentPosition) != 2) {
return 0;
}
} else {
return 0;
}
currentPosition = postParsePosition;
if (tzHoursAbs > 24) {
return 0;
}
if (tzMinutes < 0 || tzMinutes > 59) {
return 0;
}
timeZoneSeconds = 60 * (tzMinutes + (60 * tzHoursAbs));
if (tzNegative) {
timeZoneSeconds = -timeZoneSeconds;
}
return currentPosition;
}
time64_t DateObject::parseStringToDate_2(ExecutionState& state, String* istr, bool& haveTZ)
{
haveTZ = true;
const char* dateString = istr->toUTF8StringData().data();
static const long DaysPerMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// The year must be present, but the other fields may be omitted - see ES5.1 15.9.1.15.
int year = 0;
long month = 1;
long day = 1;
long hours = 0;
long minutes = 0;
long milliSeconds = 0;
long timeZoneSeconds = 0;
// Parse the date YYYY[-MM[-DD]]
char* currentPosition = parseES5DatePortion(dateString, year, month, day);
if (!currentPosition)
return TIME64NAN;
// Look for a time portion.
if (*currentPosition == 'T') {
haveTZ = false;
// Parse the time HH:mm[:ss[.sss]][Z|(+|-)00:00]
currentPosition = parseES5TimePortion(currentPosition + 1, hours, minutes, milliSeconds, timeZoneSeconds, haveTZ);
if (!currentPosition) {
return TIME64NAN;
}
}
// Check that we have parsed all characters in the string.
while (*currentPosition) {
if (isspace(*currentPosition)) {
currentPosition++;
} else {
break;
}
}
if (*currentPosition) {
return TIME64NAN;
}
// A few of these checks could be done inline above, but since many of them are interrelated
// we would be sacrificing readability to "optimize" the (presumably less common) failure path.
if (month < 1 || month > 12) {
return TIME64NAN;
}
if (day < 1 || day > DaysPerMonth[month - 1]) {
return TIME64NAN;
}
if (month == 2 && day > 28 && daysInYear(year) != 366) {
return TIME64NAN;
}
if (hours < 0 || hours > 24) {
return TIME64NAN;
}
if (hours == 24 && (minutes || milliSeconds)) {
return TIME64NAN;
}
if (minutes < 0 || minutes > 59) {
return TIME64NAN;
}
if (milliSeconds < 0 || milliSeconds >= TimeConstant::MsPerMinute + TimeConstant::MsPerSecond) {
return TIME64NAN;
}
if (milliSeconds > TimeConstant::MsPerMinute) {
// Discard leap seconds by clamping to the end of a minute.
milliSeconds = TimeConstant::MsPerMinute;
}
time64_t date = timeinfoToMs(state, year, month - 1, day, hours, minutes, (int64_t)(milliSeconds / TimeConstant::MsPerSecond), milliSeconds % TimeConstant::MsPerSecond) - timeZoneSeconds * TimeConstant::MsPerSecond;
return date;
}
time64_t DateObject::parseStringToDate(ExecutionState& state, String* istr)
{
bool haveTZ;
time64_t primitiveValue = parseStringToDate_2(state, istr, haveTZ);
if (IS_VALID_TIME(primitiveValue)) {
if (!haveTZ) { // add local timezone offset
primitiveValue = applyLocalTimezoneOffset(state, primitiveValue);
}
} else {
int offset;
primitiveValue = parseStringToDate_1(state, istr, haveTZ, offset);
if (IS_VALID_TIME(primitiveValue)) {
if (!haveTZ) {
primitiveValue = applyLocalTimezoneOffset(state, primitiveValue);
} else {
primitiveValue = primitiveValue - (offset * TimeConstant::MsPerMinute);
}
}
}
if (IS_VALID_TIME(primitiveValue) && IS_IN_TIME_RANGE(primitiveValue)) {
return primitiveValue;
} else {
// fallback. try to parse date with icu
#if defined(ENABLE_ICU)
UChar* buf = (UChar*)alloca(sizeof(UChar) * (state.context()->vmInstance()->timezoneID().size() + 1));
int32_t len = state.context()->vmInstance()->timezoneID().size();
buf[len] = 0;
for (int32_t i = 0; i < len; i++) {
buf[i] = state.context()->vmInstance()->timezoneID()[i];
}
auto u16 = istr->toUTF16StringData();
for (size_t i = 0; i <= UDateFormatStyle::UDAT_SHORT; i++) {
for (size_t j = 0; j <= UDateFormatStyle::UDAT_SHORT; j++) {
UErrorCode err = U_ZERO_ERROR;
UDateFormat* format = udat_open(static_cast<UDateFormatStyle>(i), static_cast<UDateFormatStyle>(j),
state.context()->vmInstance()->locale().data(), buf, len, nullptr, 0, &err);
if (U_FAILURE(err)) {
continue;
}
auto result = udat_parse(format, u16.data(), u16.length(), nullptr, &err);
udat_close(format);
if (!U_FAILURE(err)) {
return result;
}
}
}
#endif
return TIME64NAN;
}
}
int DateObject::daysInYear(int year)
{
if (year % 4 != 0) {
return TimeConstant::DaysPerYear;
} else if (year % 100 != 0) {
return TimeConstant::DaysPerLeapYear;
} else if (year % 400 != 0) {
return TimeConstant::DaysPerYear;
} else { // year % 400 == 0
return TimeConstant::DaysPerLeapYear;
}
}
// The number of days from (1970.1.1) to (year.1.1)
inline int DateObject::daysFromYear(int year)
{
if (LIKELY(year >= 1970)) {
return 365 * (year - 1970)
+ (year - 1969) / 4
- (year - 1901) / 100
+ (year - 1601) / 400;
} else {
return 365 * (year - 1970)
+ floor((year - 1969) / 4.0)
- floor((year - 1901) / 100.0)
+ floor((year - 1601) / 400.0);
}
}
int DateObject::yearFromTime(time64_t t)
{
int estimate = floor(t / TimeConstant::MsPerDay / 365.2425) + 1970;
time64_t yearAsMs = daysToMs(estimate, 0, 1);
if (yearAsMs > t) {
estimate--;
}
if (yearAsMs + daysInYear(estimate) * TimeConstant::MsPerDay <= t) {
estimate++;
}
return estimate;
}
int DateObject::monthFromTime(time64_t t)
{
int year = yearFromTime(t);
int day = daysFromTime(t) - daysFromYear(year);
bool leap = DateObject::inLeapYear(year);
int l = 0, r = 10;
if (day < 31) {
return 0;
}
day -= leap;
while (true) {
int m = l + (r - l) / 2;
if (monthNumberHelper[m] >= day && monthNumberHelper[m - 1] < day) {
return m + (monthNumberHelper[m] == day);
} else if (monthNumberHelper[m] < day) {
l = m + 1;
} else {
r = m - 1;
}
}
}
int DateObject::dateFromTime(time64_t t)
{
bool leap = DateObject::inLeapYear(yearFromTime(t));
int dayWithinYear = daysFromTime(t) - daysFromYear(yearFromTime(t));
int monthNumber = DateObject::monthFromTime(t);
if (monthNumber == 0) {
return dayWithinYear + 1;
}
if (monthNumber == 1) {
return dayWithinYear - (monthNumberHelper[0] - 1);
}
return dayWithinYear - (monthNumberHelper[monthNumber - 1] - 1) - leap;
}
int DateObject::hourFromTime(time64_t t)
{
if (t >= 0) {
return (int)((t / TimeConstant::MsPerHour) % TimeConstant::HoursPerDay);
}
int hours = (int)(((t - (TimeConstant::MsPerHour - 1)) / TimeConstant::MsPerHour) % TimeConstant::HoursPerDay);
return hours == 0 ? 0 : TimeConstant::HoursPerDay + hours;
}
int DateObject::minFromTime(time64_t t)
{
if (t >= 0) {
return (int)((t / TimeConstant::MsPerMinute) % TimeConstant::MinutesPerHour);
}
int minutes = (int)(((t - (TimeConstant::MsPerMinute - 1)) / TimeConstant::MsPerMinute) % TimeConstant::MinutesPerHour);
return minutes == 0 ? 0 : TimeConstant::MinutesPerHour + minutes;
}
int DateObject::secFromTime(time64_t t)
{
if (t >= 0) {
return (int)(((t / TimeConstant::MsPerSecond) % TimeConstant::SecondsPerMinute));
}
int seconds = (int)((t - (TimeConstant::MsPerSecond - 1)) / TimeConstant::MsPerSecond) % TimeConstant::SecondsPerMinute;
return seconds == 0 ? 0 : TimeConstant::SecondsPerMinute + seconds;
}
int DateObject::msFromTime(time64_t t)
{
int milliseconds = (int)(t % TimeConstant::MsPerSecond);
return milliseconds >= 0 ? milliseconds : TimeConstant::MsPerSecond + milliseconds;
}
inline bool DateObject::inLeapYear(int year)
{
int days = daysInYear(year);
// assume 'days' is 365 or 366
return (bool)(days - TimeConstant::DaysPerYear);
}
void DateObject::computeTimeInfoFromEpoch(time64_t t, struct DateTimeInfo& cachedLocal)
{
int estimate = floor(t / TimeConstant::MsPerDay / 365.2425) + 1970;
time64_t yearAsMs = daysToMs(estimate, 0, 1);
if (yearAsMs > t) {
estimate--;
}
if (yearAsMs + daysInYear(estimate) * TimeConstant::MsPerDay <= t) {
estimate++;
}
cachedLocal.year = estimate;
int dayWithinYear = daysFromTime(t) - daysFromYear(cachedLocal.year);
ASSERT(0 <= dayWithinYear && dayWithinYear < TimeConstant::DaysPerLeapYear);
int leap = (int)inLeapYear(cachedLocal.year);
for (int i = 1; i <= 12; i++) {
if (dayWithinYear < firstDayOfMonth[leap][i]) {
cachedLocal.month = i - 1;
break;
}
}
cachedLocal.mday = dayWithinYear + 1 - firstDayOfMonth[leap][cachedLocal.month];
int days = daysFromTime(t);
int timeInDay = static_cast<int>(t - days * TimeConstant::MsPerDay);
ASSERT(timeInDay >= 0);
int weekday = (days + 4) % TimeConstant::DaysPerWeek;
cachedLocal.wday = weekday >= 0 ? weekday : weekday + TimeConstant::DaysPerWeek;
// Do not cast TimeConstant::MsPer[Hour|Minute|Second] into double
cachedLocal.hour = timeInDay / TimeConstant::MsPerHour;
cachedLocal.min = (timeInDay / TimeConstant::MsPerMinute) % TimeConstant::MinutesPerHour;
cachedLocal.sec = (timeInDay / TimeConstant::MsPerSecond) % TimeConstant::SecondsPerMinute;
cachedLocal.millisec = (timeInDay) % TimeConstant::MsPerSecond;
}
int DateObject::daysFromMonth(int year, int month)
{
int leap = (int)inLeapYear(year);
return firstDayOfMonth[leap][month];
}
int DateObject::daysFromTime(time64_t t)
{
if (t < 0) {
t -= (TimeConstant::MsPerDay - 1);
}
return static_cast<int>(t / (double)TimeConstant::MsPerDay);
}
// This function expects m_primitiveValue is valid.
void DateObject::resolveCache(ExecutionState& state)
{
time64_t t = m_primitiveValue;
int realYear = yearFromTime(t);
int equivalentYear = equivalentYearForDST(realYear);
time64_t msBetweenYears = (realYear != equivalentYear) ? (timeFromYear(equivalentYear) - timeFromYear(realYear)) : 0;
t += msBetweenYears;
int32_t stdOffset = 0, dstOffset = 0;
#if defined(ENABLE_ICU)
UErrorCode status = U_ZERO_ERROR;
auto cal = state.context()->vmInstance()->calendar();
ucal_setMillis(cal, t, &status);
stdOffset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
dstOffset = ucal_get(cal, UCAL_DST_OFFSET, &status);
#endif
m_cachedLocal.isdst = dstOffset == 0 ? 0 : 1;
m_cachedLocal.gmtoff = -1 * (stdOffset + dstOffset) / TimeConstant::MsPerMinute;
t += (stdOffset + dstOffset);
t -= msBetweenYears;
computeTimeInfoFromEpoch(t, m_cachedLocal);
m_isCacheDirty = false;
}
time64_t DateObject::daysToMs(int year, int month, int date)
{
ASSERT(0 <= month && month < 12);
time64_t t = timeFromYear(year) + daysFromMonth(year, month) * TimeConstant::MsPerDay;
return t + (date - 1) * TimeConstant::MsPerDay;
}
String* DateObject::toDateString(ExecutionState& state)
{
RESOLVECACHE(state);
if (IS_VALID_TIME(m_primitiveValue)) {
char buffer[32];
int year = getFullYear(state);
size_t len;
if (year < 0) {
len = snprintf(buffer, sizeof(buffer), "%s %s %02d %05d", days[getDay(state)], months[getMonth(state)], getDate(state), year);
} else {
len = snprintf(buffer, sizeof(buffer), "%s %s %02d %04d", days[getDay(state)], months[getMonth(state)], getDate(state), year);
}
return new ASCIIString(buffer, len);
} else {
return new ASCIIStringFromExternalMemory(invalidDate);
}
}
String* DateObject::toTimeString(ExecutionState& state)
{
RESOLVECACHE(state);
if (IS_VALID_TIME(m_primitiveValue)) {
char buffer[256];
int tzOffsetAsMin = -getTimezoneOffset(state); // 540
int tzOffsetHour = (tzOffsetAsMin / TimeConstant::MinutesPerHour);
int tzOffsetMin = ((tzOffsetAsMin / (double)TimeConstant::MinutesPerHour) - tzOffsetHour) * 60;
tzOffsetHour *= 100;
const std::string& timeZoneName = state.context()->vmInstance()->tzname(m_cachedLocal.isdst ? 1 : 0);
#if defined(OS_WINDOWS)
size_t len = snprintf(buffer, sizeof(buffer),
"%02d:%02d:%02d GMT%s%04d", getHours(state), getMinutes(state), getSeconds(state), (tzOffsetAsMin < 0) ? "-" : "+", std::abs(tzOffsetHour + tzOffsetMin));
StringBuilder sb;
sb.appendString(buffer, len);
sb.appendChar(' ');
sb.appendChar('(');
sb.appendString(String::fromUTF8(timeZoneName.data(), timeZoneName.length()));
sb.appendChar(')');
return sb.finalize();
#else
size_t len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%s%04d (%s)",
getHours(state), getMinutes(state), getSeconds(state), (tzOffsetAsMin < 0) ? "-" : "+", std::abs(tzOffsetHour + tzOffsetMin), timeZoneName.data());
return String::fromUTF8(buffer, len);
#endif
} else {
return new ASCIIStringFromExternalMemory(invalidDate);
}
}
String* DateObject::toFullString(ExecutionState& state)
{
if (IS_VALID_TIME(m_primitiveValue)) {
StringBuilder builder;
builder.appendString(toDateString(state));
builder.appendChar(' ');
builder.appendString(toTimeString(state));
return builder.finalize();
} else {
return new ASCIIStringFromExternalMemory(invalidDate);
}
}
String* DateObject::toISOString(ExecutionState& state)
{
if (IS_VALID_TIME(m_primitiveValue)) {
char buffer[64];
size_t len;
if (getUTCFullYear(state) >= 0 && getUTCFullYear(state) <= 9999) {
len = snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", getUTCFullYear(state), getUTCMonth(state) + 1, getUTCDate(state), getUTCHours(state), getUTCMinutes(state), getUTCSeconds(state), getUTCMilliseconds(state));
} else {
len = snprintf(buffer, sizeof(buffer), "%+07d-%02d-%02dT%02d:%02d:%02d.%03dZ", getUTCFullYear(state), getUTCMonth(state) + 1, getUTCDate(state), getUTCHours(state), getUTCMinutes(state), getUTCSeconds(state), getUTCMilliseconds(state));
}
return new ASCIIString(buffer, len);
} else {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().Date.string(), true, state.context()->staticStrings().toISOString.string(), ErrorObject::Messages::GlobalObject_InvalidDate);
}
RELEASE_ASSERT_NOT_REACHED();
}
String* DateObject::toUTCString(ExecutionState& state, String* functionName)
{
if (IS_VALID_TIME(m_primitiveValue)) {
char buffer[64];
int year = getUTCFullYear(state);
size_t len;
if (year < 0) {
len = snprintf(buffer, sizeof(buffer), "%s, %02d %s %05d %02d:%02d:%02d GMT", days[getUTCDay(state)], getUTCDate(state),
months[getUTCMonth(state)], year,
getUTCHours(state), getUTCMinutes(state), getUTCSeconds(state));
} else {
len = snprintf(buffer, sizeof(buffer), "%s, %02d %s %04d %02d:%02d:%02d GMT", days[getUTCDay(state)], getUTCDate(state),
months[getUTCMonth(state)], year,
getUTCHours(state), getUTCMinutes(state), getUTCSeconds(state));
}
return new ASCIIString(buffer, len);
} else {
return new ASCIIStringFromExternalMemory("Invalid Date");
}
}
#if defined(ENABLE_ICU)
static String* formatDateTimeString(ExecutionState& state, DateObject* self, bool isDate)
{
UChar* buf = (UChar*)alloca(sizeof(UChar) * (state.context()->vmInstance()->timezoneID().size() + 1));
int32_t len = state.context()->vmInstance()->timezoneID().size();
buf[len] = 0;
for (int32_t i = 0; i < len; i++) {
buf[i] = state.context()->vmInstance()->timezoneID()[i];
}
UDateFormat* format = nullptr;
UErrorCode err = U_ZERO_ERROR;
if (isDate) {
format = udat_open(UDateFormatStyle::UDAT_NONE, UDateFormatStyle::UDAT_MEDIUM, state.context()->vmInstance()->locale().data(),
buf, len, nullptr, 0, &err);
} else {
format = udat_open(UDateFormatStyle::UDAT_MEDIUM, UDateFormatStyle::UDAT_NONE, state.context()->vmInstance()->locale().data(),
buf, len, nullptr, 0, &err);
}
UFieldPosition field;
field.field = -1;
field.endIndex = field.beginIndex = 0;
int32_t bufSize;
bufSize = udat_format(format, self->primitiveValue(), nullptr, 0, &field, &err);
char16_t* result = (char16_t*)alloca(sizeof(char16_t) * (bufSize + 1));
result[bufSize] = 0;
udat_format(format, self->primitiveValue(), result, bufSize, &field, &err);
udat_close(format);
return new UTF16String(result, bufSize);
}
#endif
String* DateObject::toLocaleDateString(ExecutionState& state)
{
if (IS_VALID_TIME(m_primitiveValue)) {
#if defined(ENABLE_ICU)
return formatDateTimeString(state, this, true);
#else
return toDateString(state);
#endif
} else {
return new ASCIIStringFromExternalMemory(invalidDate);
}
}
String* DateObject::toLocaleTimeString(ExecutionState& state)
{
if (IS_VALID_TIME(m_primitiveValue)) {
#if defined(ENABLE_ICU)
return formatDateTimeString(state, this, false);
#else
return toTimeString(state);
#endif
} else {
return new ASCIIStringFromExternalMemory(invalidDate);
}
}
String* DateObject::toLocaleFullString(ExecutionState& state)
{
if (IS_VALID_TIME(m_primitiveValue)) {
StringBuilder builder;
builder.appendString(toLocaleDateString(state));
builder.appendChar(' ');
builder.appendString(toLocaleTimeString(state));
return builder.finalize();
} else {
return new ASCIIStringFromExternalMemory(invalidDate);
}
}
int DateObject::getDate(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.mday;
}
int DateObject::getDay(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.wday;
}
int DateObject::getFullYear(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.year;
}
int DateObject::getHours(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.hour;
}
int DateObject::getMilliseconds(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.millisec;
}
int DateObject::getMinutes(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.min;
}
int DateObject::getMonth(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.month;
}
int DateObject::getSeconds(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.sec;
}
int DateObject::getTimezoneOffset(ExecutionState& state)
{
RESOLVECACHE(state);
return m_cachedLocal.gmtoff;
}
#define DECLARE_DATE_UTC_GETTER(Name) \
int DateObject::getUTC##Name(ExecutionState& state) \
{ \
DateObject* cachedUTC = state.context()->vmInstance()->cachedUTC(state); \
time64_t primitiveValueUTC \
= m_primitiveValue + getTimezoneOffset(state) * TimeConstant::MsPerMinute; \
if (!(cachedUTC->isValid()) || cachedUTC->primitiveValue() != primitiveValueUTC) { \
cachedUTC->setTimeValue(primitiveValueUTC); \
if (UNLIKELY(cachedUTC->getTimezoneOffset(state) != getTimezoneOffset(state))) { \
primitiveValueUTC = m_primitiveValue \
+ cachedUTC->getTimezoneOffset(state) * TimeConstant::MsPerMinute; \
cachedUTC->setTimeValue(primitiveValueUTC); \
} \
} \
return cachedUTC->get##Name(state); \
}
DECLARE_DATE_UTC_GETTER(Date);
DECLARE_DATE_UTC_GETTER(Day);
DECLARE_DATE_UTC_GETTER(FullYear);
DECLARE_DATE_UTC_GETTER(Hours);
DECLARE_DATE_UTC_GETTER(Milliseconds);
DECLARE_DATE_UTC_GETTER(Minutes);
DECLARE_DATE_UTC_GETTER(Month);
DECLARE_DATE_UTC_GETTER(Seconds);
#if defined(ENABLE_TEMPORAL)
TemporalInstantObject* DateObject::toTemporalInstant(ExecutionState& state)
{
// Let t be dateObject.[[DateValue]].
// Let ns be ? NumberToBigInt(t) × (10**6).
double val = primitiveValue();
if (std::trunc(val) != val) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Date primitiveValue is not intergral");
}
Int128 n = int64_t(val);
Int128 ns = n * 1000000;
// Return ! CreateTemporalInstant(ns).
return new TemporalInstantObject(state, state.context()->globalObject()->temporalInstantPrototype(), ns);
}
#endif
void* DateObject::operator new(size_t size)
{
ASSERT(size == sizeof(DateObject));
static MAY_THREAD_LOCAL bool typeInited = false;
static MAY_THREAD_LOCAL GC_descr descr;
if (!typeInited) {
GC_word obj_bitmap[GC_BITMAP_SIZE(DateObject)] = { 0 };
Object::fillGCDescriptor(obj_bitmap);
descr = GC_make_descriptor(obj_bitmap, GC_WORD_LEN(DateObject));
typeInited = true;
}
return GC_MALLOC_EXPLICITLY_TYPED(size, descr);
}
} // namespace Escargot