mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
1610 lines
50 KiB
C++
1610 lines
50 KiB
C++
/*
|
||
* 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
|