File indexing completed on 2024-05-12 15:43:17

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
0004  *  Copyright (C) 2004 Apple Computer, Inc.
0005  *
0006  *  This library is free software; you can redistribute it and/or
0007  *  modify it under the terms of the GNU Lesser General Public
0008  *  License as published by the Free Software Foundation; either
0009  *  version 2 of the License, or (at your option) any later version.
0010  *
0011  *  This library is distributed in the hope that it will be useful,
0012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  *  Lesser General Public License for more details.
0015  *
0016  *  You should have received a copy of the GNU Lesser General Public
0017  *  License along with this library; if not, write to the Free Software
0018  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0019  *
0020  */
0021 
0022 #include "date_object.h"
0023 #include "global.h"
0024 #include "date_object.lut.h"
0025 #include "internal.h"
0026 
0027 #if HAVE_ERRNO_H
0028 #include <errno.h>
0029 #endif
0030 
0031 #if HAVE_SYS_PARAM_H
0032 #include <sys/param.h>
0033 #endif
0034 
0035 #if HAVE_SYS_TIME_H
0036 #include <sys/time.h>
0037 #endif
0038 
0039 #if HAVE_SYS_TIMEB_H
0040 #include <sys/timeb.h>
0041 #endif
0042 
0043 #include <float.h>
0044 #include <limits.h>
0045 #include <locale.h>
0046 #include <math.h>
0047 #include <stdio.h>
0048 #include <stdlib.h>
0049 #include <cstring>
0050 #include <time.h>
0051 
0052 #if PLATFORM(SOLARIS_OS)
0053 #include <strings.h>
0054 #endif
0055 
0056 #include "error_object.h"
0057 #include "operations.h"
0058 
0059 #if PLATFORM(MAC)
0060 #include <CoreFoundation/CoreFoundation.h>
0061 #endif
0062 
0063 #if PLATFORM(WIN_OS)
0064 #include <windows.h>
0065 #if defined(WTF_COMPILER_MSVC)
0066 #define copysign(x, y) _copysign(x, y)
0067 #define snprintf _snprintf
0068 #endif 
0069 #if !defined(WTF_COMPILER_GCC)
0070 #ifndef strncasecmp
0071 #define strncasecmp(x, y, z) strnicmp(x, y, z)
0072 #endif
0073 #endif
0074 #endif
0075 
0076 #include "wtf/DisallowCType.h"
0077 #include "wtf/ASCIICType.h"
0078 
0079 // GCC cstring uses these automatically, but not all implementations do.
0080 using std::strlen;
0081 using std::strcpy;
0082 using std::strncpy;
0083 using std::memset;
0084 using std::memcpy;
0085 
0086 using namespace WTF;
0087 
0088 inline int gmtoffset(const tm &t)
0089 {
0090 #if PLATFORM(WIN_OS)
0091     // Time is supposed to be in the current timezone.
0092     // FIXME: Use undocumented _dstbias?
0093     return -(_timezone / 60 - (t.tm_isdst > 0 ? 60 : 0)) * 60;
0094 #else
0095 #if HAVE_TM_GMTOFF
0096     return t.tm_gmtoff;
0097 #else
0098     return - timezone;
0099 #endif
0100 #endif
0101 }
0102 
0103 namespace KJS
0104 {
0105 
0106 /**
0107  * @internal
0108  *
0109  * Class to implement all methods that are properties of the
0110  * Date object
0111  */
0112 class DateObjectFuncImp : public InternalFunctionImp
0113 {
0114 public:
0115     DateObjectFuncImp(ExecState *, FunctionPrototype *, int i, int len, const Identifier &);
0116 
0117     JSValue *callAsFunction(ExecState *, JSObject *thisObj, const List &args) override;
0118 
0119     enum { Parse, UTC, Now };
0120 
0121 private:
0122     int id;
0123 };
0124 
0125 // some constants
0126 const double hoursPerDay = 24;
0127 const double minutesPerHour = 60;
0128 const double secondsPerMinute = 60;
0129 const double msPerSecond = 1000;
0130 const double msPerMinute = 60 * 1000;
0131 const double msPerHour = 60 * 60 * 1000;
0132 const double msPerDay = 24 * 60 * 60 * 1000;
0133 
0134 static const char *const weekdayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
0135 static const char *const monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
0136 
0137 static double makeTime(tm *, double ms, bool utc);
0138 static double parseDate(const UString &);
0139 static double timeClip(double);
0140 static void millisecondsToTM(double milli, bool utc, tm *t);
0141 
0142 #if PLATFORM(MAC)
0143 
0144 static CFDateFormatterStyle styleFromArgString(const UString &string, CFDateFormatterStyle defaultStyle)
0145 {
0146     if (string == "short") {
0147         return kCFDateFormatterShortStyle;
0148     }
0149     if (string == "medium") {
0150         return kCFDateFormatterMediumStyle;
0151     }
0152     if (string == "long") {
0153         return kCFDateFormatterLongStyle;
0154     }
0155     if (string == "full") {
0156         return kCFDateFormatterFullStyle;
0157     }
0158     return defaultStyle;
0159 }
0160 
0161 static UString formatLocaleDate(ExecState *exec, double time, bool includeDate, bool includeTime, const List &args)
0162 {
0163     CFDateFormatterStyle dateStyle = (includeDate ? kCFDateFormatterLongStyle : kCFDateFormatterNoStyle);
0164     CFDateFormatterStyle timeStyle = (includeTime ? kCFDateFormatterLongStyle : kCFDateFormatterNoStyle);
0165 
0166     bool useCustomFormat = false;
0167     UString customFormatString;
0168 
0169     JSValue *arg0 = args[0];
0170     JSValue *arg1 = args[1];
0171     UString arg0String = arg0->toString(exec);
0172     if (arg0String == "custom" && !arg1->isUndefined()) {
0173         useCustomFormat = true;
0174         customFormatString = arg1->toString(exec);
0175     } else if (includeDate && includeTime && !arg1->isUndefined()) {
0176         dateStyle = styleFromArgString(arg0String, dateStyle);
0177         timeStyle = styleFromArgString(arg1->toString(exec), timeStyle);
0178     } else if (includeDate && !arg0->isUndefined()) {
0179         dateStyle = styleFromArgString(arg0String, dateStyle);
0180     } else if (includeTime && !arg0->isUndefined()) {
0181         timeStyle = styleFromArgString(arg0String, timeStyle);
0182     }
0183 
0184     CFLocaleRef locale = CFLocaleCopyCurrent();
0185     CFDateFormatterRef formatter = CFDateFormatterCreate(0, locale, dateStyle, timeStyle);
0186     CFRelease(locale);
0187 
0188     if (useCustomFormat) {
0189         CFStringRef customFormatCFString = CFStringCreateWithCharacters(0, (UniChar *)customFormatString.data(), customFormatString.size());
0190         CFDateFormatterSetFormat(formatter, customFormatCFString);
0191         CFRelease(customFormatCFString);
0192     }
0193 
0194     CFStringRef string = CFDateFormatterCreateStringWithAbsoluteTime(0, formatter, time - kCFAbsoluteTimeIntervalSince1970);
0195 
0196     CFRelease(formatter);
0197 
0198     // We truncate the string returned from CFDateFormatter if it's absurdly long (> 200 characters).
0199     // That's not great error handling, but it just won't happen so it doesn't matter.
0200     UChar buffer[200];
0201     const size_t bufferLength = sizeof(buffer) / sizeof(buffer[0]);
0202     size_t length = CFStringGetLength(string);
0203     assert(length <= bufferLength);
0204     if (length > bufferLength) {
0205         length = bufferLength;
0206     }
0207     CFStringGetCharacters(string, CFRangeMake(0, length), reinterpret_cast<UniChar *>(buffer));
0208 
0209     CFRelease(string);
0210 
0211     return UString(buffer, length);
0212 }
0213 
0214 #endif // PLATFORM(MAC)
0215 
0216 static UString formatDate(const tm &t)
0217 {
0218     char buffer[100];
0219     int len = snprintf(buffer, sizeof(buffer), "%s %s %02d %04d",
0220                        weekdayName[(t.tm_wday + 6) % 7],
0221                        monthName[t.tm_mon], t.tm_mday, t.tm_year + 1900);
0222     return UString(buffer, len);
0223 }
0224 
0225 static UString formatDateUTCVariant(const tm &t)
0226 {
0227     char buffer[100];
0228     int len = snprintf(buffer, sizeof(buffer), "%s, %02d %s %04d",
0229                        weekdayName[(t.tm_wday + 6) % 7],
0230                        t.tm_mday, monthName[t.tm_mon], t.tm_year + 1900);
0231     return UString(buffer, len);
0232 }
0233 
0234 static UString formatDateISOVariant(const tm &t, bool utc, double absoluteMS)
0235 {
0236     char buffer[100];
0237     // YYYY-MM-DD
0238     int len;
0239     if (utc) {
0240         len = snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d",
0241                        t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
0242     } else {
0243         int offset = gmtoffset(t);
0244         tm t_fixed;
0245         millisecondsToTM(absoluteMS - offset * 1000, true, &t_fixed);
0246         len = snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d",
0247                        t_fixed.tm_year + 1900, t_fixed.tm_mon + 1, t_fixed.tm_mday);
0248     }
0249     return UString(buffer, len);
0250 }
0251 
0252 static UString formatTime(const tm &t, bool utc)
0253 {
0254     char buffer[100];
0255     int len;
0256     if (utc) {
0257         // FIXME: why not on windows?
0258 #if !PLATFORM(WIN_OS)
0259         ASSERT(gmtoffset(t) == 0);
0260 #endif
0261         len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT", t.tm_hour, t.tm_min, t.tm_sec);
0262     } else {
0263         int offset = abs(gmtoffset(t));
0264         len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%c%02d%02d",
0265                        t.tm_hour, t.tm_min, t.tm_sec,
0266                        gmtoffset(t) < 0 ? '-' : '+', offset / (60 * 60), (offset / 60) % 60);
0267     }
0268     return UString(buffer, len);
0269 }
0270 
0271 static UString formatTimeISOVariant(const tm &t, bool utc, double absoluteMS, double ms)
0272 {
0273     char buffer[100];
0274     // HH:mm:ss.sss
0275     int len;
0276     if (utc) {
0277         len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%03d",
0278                        t.tm_hour, t.tm_min, t.tm_sec, int(ms));
0279     } else {
0280         int offset = gmtoffset(t);
0281         tm t_fixed;
0282         millisecondsToTM(absoluteMS - offset * 1000, true, &t_fixed);
0283         len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%03d",
0284                        t_fixed.tm_hour, t_fixed.tm_min, t_fixed.tm_sec, int(ms));
0285     }
0286     return UString(buffer, len);
0287 }
0288 
0289 static int day(double t)
0290 {
0291     return int(floor(t / msPerDay));
0292 }
0293 
0294 static double dayFromYear(int year)
0295 {
0296     return 365.0 * (year - 1970)
0297            + floor((year - 1969) / 4.0)
0298            - floor((year - 1901) / 100.0)
0299            + floor((year - 1601) / 400.0);
0300 }
0301 
0302 // based on the rule for whether it's a leap year or not
0303 static int daysInYear(int year)
0304 {
0305     if (year % 4 != 0) {
0306         return 365;
0307     }
0308     if (year % 400 == 0) {
0309         return 366;
0310     }
0311     if (year % 100 == 0) {
0312         return 365;
0313     }
0314     return 366;
0315 }
0316 
0317 // time value of the start of a year
0318 static double timeFromYear(int year)
0319 {
0320     return msPerDay * dayFromYear(year);
0321 }
0322 
0323 // year determined by time value
0324 static int yearFromTime(double t)
0325 {
0326     // ### there must be an easier way
0327 
0328     // initial guess
0329     int y = 1970 + int(t / (365.25 * msPerDay));
0330 
0331     // adjustment
0332     if (timeFromYear(y) > t) {
0333         do {
0334             --y;
0335         } while (timeFromYear(y) > t);
0336     } else {
0337         while (timeFromYear(y + 1) < t) {
0338             ++y;
0339         }
0340     }
0341 
0342     return y;
0343 }
0344 
0345 // 0: Sunday, 1: Monday, etc.
0346 static int weekDay(double t)
0347 {
0348     int wd = (day(t) + 4) % 7;
0349     if (wd < 0) {
0350         wd += 7;
0351     }
0352     return wd;
0353 }
0354 
0355 // Converts a list of arguments sent to a Date member function into milliseconds, updating
0356 // ms (representing milliseconds) and t (representing the rest of the date structure) appropriately.
0357 //
0358 // Format of member function: f([hour,] [min,] [sec,] [ms])
0359 static double setTimeFields(ExecState *exec, const List &args, int id, double ms, tm *t)
0360 {
0361     assert(DateProtoFunc::SetSeconds - DateProtoFunc::SetMilliSeconds + 1 == 2);
0362     assert(DateProtoFunc::SetMinutes - DateProtoFunc::SetMilliSeconds + 1 == 3);
0363     assert(DateProtoFunc::SetHours   - DateProtoFunc::SetMilliSeconds + 1 == 4);
0364 
0365     assert(id == DateProtoFunc::SetMilliSeconds || id == DateProtoFunc::SetSeconds ||
0366            id == DateProtoFunc::SetMinutes || id == DateProtoFunc::SetHours);
0367 
0368     int maxArgs = id - DateProtoFunc::SetMilliSeconds + 1;
0369     double milliseconds = 0;
0370     int idx = 0;
0371     int numArgs = args.size();
0372 
0373     // JS allows extra trailing arguments -- ignore them
0374     if (numArgs > maxArgs) {
0375         numArgs = maxArgs;
0376     }
0377 
0378     // hours
0379     if (maxArgs >= 4 && idx < numArgs) {
0380         t->tm_hour = 0;
0381         milliseconds += JSValue::toInt32(args[idx++], exec) * msPerHour;
0382     }
0383 
0384     // minutes
0385     if (maxArgs >= 3 && idx < numArgs) {
0386         t->tm_min = 0;
0387         milliseconds += JSValue::toInt32(args[idx++], exec) * msPerMinute;
0388     }
0389 
0390     // seconds
0391     if (maxArgs >= 2 && idx < numArgs) {
0392         t->tm_sec = 0;
0393         milliseconds += JSValue::toInt32(args[idx++], exec) * msPerSecond;
0394     }
0395 
0396     // milliseconds
0397     if (idx < numArgs) {
0398         milliseconds += roundValue(exec, args[idx]);
0399     } else {
0400         milliseconds += ms;
0401     }
0402 
0403     return milliseconds;
0404 }
0405 
0406 // Converts a list of arguments sent to a Date member function into years, months, and milliseconds, updating
0407 // ms (representing milliseconds) and t (representing the rest of the date structure) appropriately.
0408 //
0409 // Format of member function: f([years,] [months,] [days])
0410 static double setDateFields(ExecState *exec, const List &args, int id, double ms, tm *t)
0411 {
0412     assert(DateProtoFunc::SetMonth    - DateProtoFunc::SetDate + 1 == 2);
0413     assert(DateProtoFunc::SetFullYear - DateProtoFunc::SetDate + 1 == 3);
0414 
0415     assert(id == DateProtoFunc::SetDate || id == DateProtoFunc::SetMonth || id == DateProtoFunc::SetFullYear);
0416 
0417     int maxArgs = id - DateProtoFunc::SetDate + 1;
0418     int idx = 0;
0419     int numArgs = args.size();
0420 
0421     // JS allows extra trailing arguments -- ignore them
0422     if (numArgs > maxArgs) {
0423         numArgs = maxArgs;
0424     }
0425 
0426     // years
0427     if (maxArgs >= 3 && idx < numArgs) {
0428         t->tm_year = JSValue::toInt32(args[idx++], exec) - 1900;
0429     }
0430 
0431     // months
0432     if (maxArgs >= 2 && idx < numArgs) {
0433         t->tm_mon = JSValue::toInt32(args[idx++], exec);
0434     }
0435 
0436     // days
0437     if (idx < numArgs) {
0438         t->tm_mday = 0;
0439         ms += JSValue::toInt32(args[idx], exec) * msPerDay;
0440     }
0441 
0442     return ms;
0443 }
0444 
0445 // ------------------------------ DateInstance ------------------------------
0446 
0447 const ClassInfo DateInstance::info = {"Date", nullptr, nullptr, nullptr};
0448 
0449 DateInstance::DateInstance(JSObject *proto)
0450     : JSWrapperObject(proto)
0451 {
0452 }
0453 
0454 JSObject *DateInstance::valueClone(Interpreter *targetCtx) const
0455 {
0456     DateInstance *copy = new DateInstance(targetCtx->builtinDatePrototype());
0457     copy->setInternalValue(internalValue());
0458     return copy;
0459 }
0460 
0461 bool DateInstance::getTime(tm &t, int &offset) const
0462 {
0463     double milli = JSValue::getNumber(internalValue());
0464     if (isNaN(milli)) {
0465         return false;
0466     }
0467 
0468     millisecondsToTM(milli, false, &t);
0469     offset = gmtoffset(t);
0470     return true;
0471 }
0472 
0473 bool DateInstance::getUTCTime(tm &t) const
0474 {
0475     double milli = JSValue::getNumber(internalValue());
0476     if (isNaN(milli)) {
0477         return false;
0478     }
0479 
0480     millisecondsToTM(milli, true, &t);
0481     return true;
0482 }
0483 
0484 bool DateInstance::getTime(double &milli, int &offset) const
0485 {
0486     milli = JSValue::getNumber(internalValue());
0487     if (isNaN(milli)) {
0488         return false;
0489     }
0490 
0491     tm t;
0492     millisecondsToTM(milli, false, &t);
0493     offset = gmtoffset(t);
0494     return true;
0495 }
0496 
0497 bool DateInstance::getUTCTime(double &milli) const
0498 {
0499     milli = JSValue::getNumber(internalValue());
0500     if (isNaN(milli)) {
0501         return false;
0502     }
0503 
0504     return true;
0505 }
0506 
0507 static inline bool isTime_tSigned()
0508 {
0509     time_t minusOne = (time_t)(-1);
0510     return minusOne < 0;
0511 }
0512 
0513 static void millisecondsToTM(double milli, bool utc, tm *t)
0514 {
0515     // check whether time value is outside time_t's usual range
0516     // make the necessary transformations if necessary
0517     static bool time_tIsSigned = isTime_tSigned();
0518 #if PLATFORM(WIN_OS)
0519     static double time_tMin = 0; //on windows localtime/gmtime returns NULL for pre 1970 dates
0520 #else
0521     static double time_tMin = (time_tIsSigned ? - (double)(1ULL << (8 * sizeof(time_t) - 1)) : 0);
0522 #endif
0523     static double time_tMax = (time_tIsSigned ? (1ULL << (8 * sizeof(time_t) - 1)) - 1 : 2 * (double)(1ULL << (8 * sizeof(time_t) - 1)) - 1);
0524     int realYearOffset = 0;
0525     double milliOffset = 0.0;
0526     double secs = floor(milli / msPerSecond);
0527 
0528     if (secs < time_tMin || secs > time_tMax) {
0529         // ### ugly and probably not very precise
0530         int realYear = yearFromTime(milli);
0531         int base = daysInYear(realYear) == 365 ? 2001 : 2000;
0532         milliOffset = timeFromYear(base) - timeFromYear(realYear);
0533         milli += milliOffset;
0534         realYearOffset = realYear - base;
0535     }
0536 
0537     time_t tv = (time_t) floor(milli / msPerSecond);
0538 
0539     *t = *(utc ? gmtime(&tv) : localtime(&tv));
0540     // We had an out of range year. Restore the year (plus/minus offset
0541     // found by calculating tm_year) and fix the week day calculation.
0542     if (realYearOffset != 0) {
0543         t->tm_year += realYearOffset;
0544         milli -= milliOffset;
0545         // Do our own weekday calculation. Use time zone offset to handle local time.
0546         double m = milli;
0547         if (!utc) {
0548             m += gmtoffset(*t) * msPerSecond;
0549         }
0550         t->tm_wday = weekDay(m);
0551     }
0552 }
0553 
0554 static bool isNaNorInf(double value)
0555 {
0556     return isNaN(value) || isInf(value);
0557 }
0558 
0559 // ------------------------------ DatePrototype -----------------------------
0560 
0561 const ClassInfo DatePrototype::info = {"Date", &DateInstance::info, &dateTable, nullptr};
0562 
0563 /* Source for date_object.lut.h
0564    We use a negative ID to denote the "UTC" variant.
0565 @begin dateTable 61
0566   toString      DateProtoFunc::ToString     DontEnum|Function   0
0567   toUTCString       -DateProtoFunc::ToUTCString     DontEnum|Function   0
0568   toDateString      DateProtoFunc::ToDateString     DontEnum|Function   0
0569   toTimeString      DateProtoFunc::ToTimeString     DontEnum|Function   0
0570   toISOString       DateProtoFunc::ToISOString      DontEnum|Function   0
0571   toJSON            DateProtoFunc::ToJSON           DontEnum|Function   1
0572   toLocaleString    DateProtoFunc::ToLocaleString   DontEnum|Function   0
0573   toLocaleDateString    DateProtoFunc::ToLocaleDateString   DontEnum|Function   0
0574   toLocaleTimeString    DateProtoFunc::ToLocaleTimeString   DontEnum|Function   0
0575   valueOf       DateProtoFunc::ValueOf      DontEnum|Function   0
0576   getTime       DateProtoFunc::GetTime      DontEnum|Function   0
0577   getFullYear       DateProtoFunc::GetFullYear      DontEnum|Function   0
0578   getUTCFullYear    -DateProtoFunc::GetFullYear     DontEnum|Function   0
0579   toGMTString       -DateProtoFunc::ToGMTString     DontEnum|Function   0
0580   getMonth      DateProtoFunc::GetMonth     DontEnum|Function   0
0581   getUTCMonth       -DateProtoFunc::GetMonth        DontEnum|Function   0
0582   getDate       DateProtoFunc::GetDate      DontEnum|Function   0
0583   getUTCDate        -DateProtoFunc::GetDate     DontEnum|Function   0
0584   getDay        DateProtoFunc::GetDay       DontEnum|Function   0
0585   getUTCDay     -DateProtoFunc::GetDay      DontEnum|Function   0
0586   getHours      DateProtoFunc::GetHours     DontEnum|Function   0
0587   getUTCHours       -DateProtoFunc::GetHours        DontEnum|Function   0
0588   getMinutes        DateProtoFunc::GetMinutes       DontEnum|Function   0
0589   getUTCMinutes     -DateProtoFunc::GetMinutes      DontEnum|Function   0
0590   getSeconds        DateProtoFunc::GetSeconds       DontEnum|Function   0
0591   getUTCSeconds     -DateProtoFunc::GetSeconds      DontEnum|Function   0
0592   getMilliseconds   DateProtoFunc::GetMilliSeconds  DontEnum|Function   0
0593   getUTCMilliseconds    -DateProtoFunc::GetMilliSeconds DontEnum|Function   0
0594   getTimezoneOffset DateProtoFunc::GetTimezoneOffset    DontEnum|Function   0
0595   setTime       DateProtoFunc::SetTime      DontEnum|Function   1
0596   setMilliseconds   DateProtoFunc::SetMilliSeconds  DontEnum|Function   1
0597   setUTCMilliseconds    -DateProtoFunc::SetMilliSeconds DontEnum|Function   1
0598   setSeconds        DateProtoFunc::SetSeconds       DontEnum|Function   2
0599   setUTCSeconds     -DateProtoFunc::SetSeconds      DontEnum|Function   2
0600   setMinutes        DateProtoFunc::SetMinutes       DontEnum|Function   3
0601   setUTCMinutes     -DateProtoFunc::SetMinutes      DontEnum|Function   3
0602   setHours      DateProtoFunc::SetHours     DontEnum|Function   4
0603   setUTCHours       -DateProtoFunc::SetHours        DontEnum|Function   4
0604   setDate       DateProtoFunc::SetDate      DontEnum|Function   1
0605   setUTCDate        -DateProtoFunc::SetDate     DontEnum|Function   1
0606   setMonth      DateProtoFunc::SetMonth     DontEnum|Function   2
0607   setUTCMonth       -DateProtoFunc::SetMonth        DontEnum|Function   2
0608   setFullYear       DateProtoFunc::SetFullYear      DontEnum|Function   3
0609   setUTCFullYear    -DateProtoFunc::SetFullYear     DontEnum|Function   3
0610   setYear       DateProtoFunc::SetYear      DontEnum|Function   1
0611   getYear       DateProtoFunc::GetYear      DontEnum|Function   0
0612 @end
0613 */
0614 // ECMA 15.9.4
0615 
0616 DatePrototype::DatePrototype(ExecState *, ObjectPrototype *objectProto)
0617     : DateInstance(objectProto)
0618 {
0619     setInternalValue(jsNaN());
0620     // The constructor will be added later, after DateObjectImp has been built.
0621 }
0622 
0623 bool DatePrototype::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
0624 {
0625     return getStaticFunctionSlot<DateProtoFunc, JSObject>(exec, &dateTable, this, propertyName, slot);
0626 }
0627 
0628 // ------------------------------ DateProtoFunc -----------------------------
0629 
0630 DateProtoFunc::DateProtoFunc(ExecState *exec, int i, int len, const Identifier &name)
0631     : InternalFunctionImp(static_cast<FunctionPrototype *>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
0632     , id(abs(i))
0633     , utc(i < 0)
0634     // We use a negative ID to denote the "UTC" variant.
0635 {
0636     putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
0637 }
0638 
0639 JSValue *DateProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
0640 {
0641     if (id == ToJSON) {
0642         JSValue *tv = thisObj->toPrimitive(exec, NumberType);
0643         if (JSValue::isNumber(tv)) {
0644             double ms = JSValue::toNumber(tv, exec);
0645             if (isNaNorInf(ms)) {
0646                 return jsNull();
0647             }
0648         }
0649 
0650         JSValue *toISO = thisObj->get(exec, exec->propertyNames().toISOString);
0651         if (!JSValue::implementsCall(toISO)) {
0652             return throwError(exec, TypeError, "toISOString is not callable");
0653         }
0654         JSObject *toISOobj = JSValue::toObject(toISO, exec);
0655         if (!toISOobj) {
0656             return throwError(exec, TypeError, "toISOString is not callable");
0657         }
0658         return toISOobj->call(exec, thisObj, List::empty());
0659     }
0660 
0661     if (!thisObj->inherits(&DateInstance::info)) {
0662     if (id == ToString)
0663         return jsString("Invalid Date");
0664         return throwError(exec, TypeError, "Incompatible object");
0665     }
0666 
0667     DateInstance *thisDateObj = static_cast<DateInstance *>(thisObj);
0668 
0669     JSValue *result = nullptr;
0670 #if !PLATFORM(MAC)
0671     const int bufsize = 100;
0672     char timebuffer[bufsize];
0673     CString oldlocale = setlocale(LC_TIME, nullptr);
0674     if (!oldlocale.size()) {
0675         oldlocale = setlocale(LC_ALL, nullptr);
0676     }
0677     // FIXME: Where's the code to set the locale back to oldlocale?
0678 #endif
0679     JSValue *v = thisDateObj->internalValue();
0680     double milli = JSValue::toNumber(v, exec);
0681     if (isNaN(milli)) {
0682         switch (id) {
0683         case ToString:
0684         case ToDateString:
0685         case ToTimeString:
0686         case ToGMTString:
0687         case ToUTCString:
0688         case ToLocaleString:
0689         case ToLocaleDateString:
0690         case ToLocaleTimeString:
0691             return jsString("Invalid Date", 12);
0692         case ValueOf:
0693         case GetTime:
0694         case GetYear:
0695         case GetFullYear:
0696         case GetMonth:
0697         case GetDate:
0698         case GetDay:
0699         case GetHours:
0700         case GetMinutes:
0701         case GetSeconds:
0702         case GetMilliSeconds:
0703         case GetTimezoneOffset:
0704         case SetMilliSeconds:
0705         case SetSeconds:
0706         case SetMinutes:
0707         case SetHours:
0708         case SetDate:
0709         case SetMonth:
0710         case SetFullYear:
0711             return jsNaN();
0712         case ToISOString:
0713             return throwError(exec, RangeError, "Invalid Date");
0714         }
0715     }
0716 
0717     if (id == SetTime) {
0718         double milli = roundValue(exec, args[0]);
0719         result = jsNumber(timeClip(milli));
0720         thisDateObj->setInternalValue(result);
0721         return result;
0722     }
0723 
0724     double secs = floor(milli / msPerSecond);
0725     double ms = milli - secs * msPerSecond;
0726 
0727     tm t;
0728     millisecondsToTM(milli, utc, &t);
0729 
0730     switch (id) {
0731     case ToString:
0732         return jsString(formatDate(t).append(' ').append(formatTime(t, utc)));
0733 
0734     case ToDateString:
0735         return jsString(formatDate(t));
0736 
0737     case ToTimeString:
0738         return jsString(formatTime(t, utc));
0739 
0740     case ToGMTString:
0741     case ToUTCString:
0742         return jsString(formatDateUTCVariant(t).append(' ').append(formatTime(t, utc)));
0743     case ToISOString:
0744         return jsString(formatDateISOVariant(t, utc, milli).append('T').append(formatTimeISOVariant(t, utc, milli, ms)).append('Z'));
0745 
0746 #if PLATFORM(MAC)
0747     case ToLocaleString:
0748         return jsString(formatLocaleDate(exec, secs, true, true, args));
0749 
0750     case ToLocaleDateString:
0751         return jsString(formatLocaleDate(exec, secs, true, false, args));
0752 
0753     case ToLocaleTimeString:
0754         return jsString(formatLocaleDate(exec, secs, false, true, args));
0755 
0756 #else
0757     case ToLocaleString:
0758         return jsString(timebuffer, strftime(timebuffer, bufsize, "%c", &t));
0759 
0760     case ToLocaleDateString:
0761         return jsString(timebuffer, strftime(timebuffer, bufsize, "%x", &t));
0762 
0763     case ToLocaleTimeString:
0764         return jsString(timebuffer, strftime(timebuffer, bufsize, "%X", &t));
0765 
0766 #endif
0767     case ValueOf:
0768     case GetTime:
0769         return jsNumber(milli);
0770     case GetYear:
0771         // IE returns the full year even in getYear.
0772         if (exec->dynamicInterpreter()->compatMode() == Interpreter::IECompat) {
0773             return jsNumber(1900 + t.tm_year);
0774         }
0775         return jsNumber(t.tm_year);
0776     case GetFullYear:
0777         return jsNumber(1900 + t.tm_year);
0778     case GetMonth:
0779         return jsNumber(t.tm_mon);
0780     case GetDate:
0781         return jsNumber(t.tm_mday);
0782     case GetDay:
0783         return jsNumber(t.tm_wday);
0784     case GetHours:
0785         return jsNumber(t.tm_hour);
0786     case GetMinutes:
0787         return jsNumber(t.tm_min);
0788     case GetSeconds:
0789         return jsNumber(t.tm_sec);
0790     case GetMilliSeconds:
0791         return jsNumber(ms);
0792     case GetTimezoneOffset:
0793         return jsNumber(-gmtoffset(t) / 60);
0794 
0795     case SetMilliSeconds:
0796     case SetSeconds:
0797     case SetMinutes:
0798     case SetHours:
0799         ms = args.size() > 0 ? setTimeFields(exec, args, id, ms, &t) : NaN;
0800         break;
0801 
0802     case SetDate:
0803     case SetMonth:
0804     case SetFullYear:
0805         ms = args.size() > 0 ? setDateFields(exec, args, id, ms, &t) : NaN;
0806         break;
0807 
0808     case SetYear: {
0809         int32_t year = JSValue::toInt32(args[0], exec);
0810         t.tm_year = (year > 99 || year < 0) ? year - 1900 : year;
0811         break;
0812     }
0813     }
0814 
0815     if (id == SetYear || id == SetMilliSeconds || id == SetSeconds ||
0816             id == SetMinutes || id == SetHours || id == SetDate ||
0817             id == SetMonth || id == SetFullYear) {
0818         result = jsNumber(isNaN(ms) ? ms : timeClip(makeTime(&t, ms, utc)));
0819         thisDateObj->setInternalValue(result);
0820     }
0821 
0822     return result;
0823 }
0824 
0825 // ------------------------------ DateObjectImp --------------------------------
0826 
0827 DateObjectImp::DateObjectImp(ExecState *exec,
0828                              FunctionPrototype *funcProto,
0829                              DatePrototype *dateProto)
0830     : InternalFunctionImp(funcProto)
0831 {
0832     // ECMA 15.9.4.1 Date.prototype
0833     static const Identifier *parsePropertyName = new Identifier("parse");
0834     static const Identifier *UTCPropertyName = new Identifier("UTC");
0835     static const Identifier *nowPropertyName = new Identifier("now");
0836 
0837     putDirect(exec->propertyNames().prototype, dateProto, DontEnum | DontDelete | ReadOnly);
0838     putDirectFunction(new DateObjectFuncImp(exec, funcProto, DateObjectFuncImp::Parse, 1, *parsePropertyName), DontEnum);
0839     putDirectFunction(new DateObjectFuncImp(exec, funcProto, DateObjectFuncImp::UTC, 7, *UTCPropertyName), DontEnum);
0840     putDirectFunction(new DateObjectFuncImp(exec, funcProto, DateObjectFuncImp::Now, 0, *nowPropertyName), DontEnum);
0841 
0842     // no. of arguments for constructor
0843     putDirect(exec->propertyNames().length, 7, ReadOnly | DontDelete | DontEnum);
0844 }
0845 
0846 bool DateObjectImp::implementsConstruct() const
0847 {
0848     return true;
0849 }
0850 
0851 static double getCurrentUTCTime()
0852 {
0853 #if PLATFORM(WIN_OS)
0854 #if defined(WTF_COMPILER_BORLAND)
0855     struct timeb timebuffer;
0856     ftime(&timebuffer);
0857 #else
0858     struct _timeb timebuffer;
0859     _ftime(&timebuffer);
0860 #endif
0861     double utc = timebuffer.time * msPerSecond + timebuffer.millitm;
0862 #else
0863     struct timeval tv;
0864     gettimeofday(&tv, nullptr);
0865     double utc = floor(tv.tv_sec * msPerSecond + tv.tv_usec / 1000);
0866 #endif
0867     return utc;
0868 }
0869 
0870 static double makeTimeFromList(ExecState *exec, const List &args, bool utc)
0871 {
0872     const int numArgs = args.size();
0873     if (isNaNorInf(JSValue::toNumber(args[0], exec))
0874             || isNaNorInf(JSValue::toNumber(args[1], exec))
0875             || (numArgs >= 3 && isNaNorInf(JSValue::toNumber(args[2], exec)))
0876             || (numArgs >= 4 && isNaNorInf(JSValue::toNumber(args[3], exec)))
0877             || (numArgs >= 5 && isNaNorInf(JSValue::toNumber(args[4], exec)))
0878             || (numArgs >= 6 && isNaNorInf(JSValue::toNumber(args[5], exec)))
0879             || (numArgs >= 7 && isNaNorInf(JSValue::toNumber(args[6], exec)))) {
0880         return NaN;
0881     }
0882 
0883     tm t;
0884     memset(&t, 0, sizeof(t));
0885     int year = JSValue::toInt32(args[0], exec);
0886     t.tm_year = (year >= 0 && year <= 99) ? year : year - 1900;
0887     t.tm_mon = JSValue::toInt32(args[1], exec);
0888     t.tm_mday = (numArgs >= 3) ? JSValue::toInt32(args[2], exec) : 1;
0889     t.tm_hour = (numArgs >= 4) ? JSValue::toInt32(args[3], exec) : 0;
0890     t.tm_min = (numArgs >= 5) ? JSValue::toInt32(args[4], exec) : 0;
0891     t.tm_sec = (numArgs >= 6) ? JSValue::toInt32(args[5], exec) : 0;
0892     if (!utc) {
0893         t.tm_isdst = -1;
0894     }
0895     double ms = (numArgs >= 7) ? roundValue(exec, args[6]) : 0;
0896     return makeTime(&t, ms, utc);
0897 }
0898 
0899 // ECMA 15.9.3
0900 JSObject *DateObjectImp::construct(ExecState *exec, const List &args)
0901 {
0902     int numArgs = args.size();
0903     double value;
0904 
0905     if (numArgs == 0) { // new Date() ECMA 15.9.3.3
0906         value = getCurrentUTCTime();
0907     } else if (numArgs == 1) {
0908         JSValue *arg0 = args[0];
0909         if (JSValue::isObject(arg0, &DateInstance::info)) {
0910             value = JSValue::toNumber(static_cast<DateInstance *>(arg0)->internalValue(), exec);
0911         } else {
0912             JSValue *primitive = JSValue::toPrimitive(arg0, exec);
0913             if (JSValue::isString(primitive)) {
0914                 value = parseDate(JSValue::getString(primitive));
0915             } else {
0916                 value = JSValue::toNumber(primitive, exec);
0917             }
0918         }
0919     } else {
0920         value = makeTimeFromList(exec, args, false);
0921     }
0922 
0923     DateInstance *ret = new DateInstance(exec->lexicalInterpreter()->builtinDatePrototype());
0924     ret->setInternalValue(jsNumber(timeClip(value)));
0925     return ret;
0926 }
0927 
0928 // ECMA 15.9.2
0929 JSValue *DateObjectImp::callAsFunction(ExecState * /*exec*/, JSObject * /*thisObj*/, const List &/*args*/)
0930 {
0931     time_t t = time(nullptr);
0932     tm ts = *localtime(&t);
0933     return jsString(formatDate(ts).append(' ').append(formatTime(ts, false)));
0934 }
0935 
0936 // ------------------------------ DateObjectFuncImp ----------------------------
0937 
0938 DateObjectFuncImp::DateObjectFuncImp(ExecState *exec, FunctionPrototype *funcProto, int i, int len, const Identifier &name)
0939     : InternalFunctionImp(funcProto, name), id(i)
0940 {
0941     putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
0942 }
0943 
0944 // ECMA 15.9.4.2 - 3
0945 JSValue *DateObjectFuncImp::callAsFunction(ExecState *exec, JSObject *, const List &args)
0946 {
0947     if (id == Parse) {
0948         return jsNumber(parseDate(JSValue::toString(args[0], exec)));
0949     } else if (id == Now) {
0950         return jsNumber(getCurrentUTCTime());
0951     } else { // UTC
0952         return jsNumber(makeTimeFromList(exec, args, true));
0953     }
0954 }
0955 
0956 // -----------------------------------------------------------------------------
0957 
0958 // Code originally from krfcdate.cpp, but we don't want to use kdecore, and we want double range.
0959 
0960 static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, double second)
0961 {
0962     // in which case is the floor() needed? breaks day value of
0963     // "new Date('Thu Nov 5 2065 18:15:30 GMT+0500')"
0964 #if 0
0965     double days = (day - 32075)
0966                   + floor(1461 * (year + 4800.0 + (mon - 14) / 12) / 4)
0967                   + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12
0968                   - floor(3 * ((year + 4900.0 + (mon - 14) / 12) / 100) / 4)
0969                   - 2440588;
0970 #else
0971     double days = (day - 32075)
0972                   + 1461 * (year + 4800 + (mon - 14) / 12) / 4
0973                   + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12
0974                   - 3 * ((year + 4900 + (mon - 14) / 12) / 100) / 4
0975                   - 2440588;
0976 #endif
0977     return ((days * hoursPerDay + hour) * minutesPerHour + minute) * secondsPerMinute + second;
0978 }
0979 
0980 // We follow the recommendation of RFC 2822 to consider all
0981 // obsolete time zones not listed here equivalent to "-0000".
0982 static const struct KnownZone {
0983 #if !PLATFORM(WIN_OS)
0984     const
0985 #endif
0986     char tzName[4];
0987     int tzOffset;
0988 } known_zones[] = {
0989     { "UT", 0 },
0990     { "GMT", 0 },
0991     { "EST", -300 },
0992     { "EDT", -240 },
0993     { "CST", -360 },
0994     { "CDT", -300 },
0995     { "MST", -420 },
0996     { "MDT", -360 },
0997     { "PST", -480 },
0998     { "PDT", -420 }
0999 };
1000 
1001 #if PLATFORM(WIN_OS)
1002 void FileTimeToUnixTime(LPFILETIME pft, double *pt)
1003 {
1004     ULARGE_INTEGER ull;
1005     ull.LowPart = pft->dwLowDateTime;
1006     ull.HighPart = pft->dwHighDateTime;
1007     *pt = (double)(ull.QuadPart / 10000000ULL) - 11644473600ULL;
1008 }
1009 
1010 void SystemTimeToUnixTime(LPSYSTEMTIME pst, double *pt)
1011 {
1012     FILETIME ft;
1013     SystemTimeToFileTime(pst, &ft);
1014     FileTimeToUnixTime(&ft, pt);
1015 }
1016 #endif
1017 
1018 static double makeTime(tm *t, double ms, bool utc)
1019 {
1020     int utcOffset;
1021     if (utc) {
1022         time_t zero = 0;
1023 #if PLATFORM(WIN_OS)
1024         // FIXME: not thread safe
1025         (void)localtime(&zero);
1026 #if defined(WTF_COMPILER_BORLAND) || defined(WTF_COMPILER_CYGWIN) || defined(WTF_COMPILER_MSVC)
1027         utcOffset = - _timezone;
1028 #else
1029         utcOffset = - timezone;
1030 #endif
1031         t->tm_isdst = 0;
1032 #elif PLATFORM(DARWIN)
1033         utcOffset = 0;
1034         t->tm_isdst = 0;
1035 #else
1036         tm t3;
1037         localtime_r(&zero, &t3);
1038         utcOffset = gmtoffset(t3);
1039         t->tm_isdst = t3.tm_isdst;
1040 #endif
1041     } else {
1042         utcOffset = 0;
1043         t->tm_isdst = -1;
1044     }
1045 
1046 #if !PLATFORM(WIN_OS)
1047     double yearOffset = 0.0;
1048     if (t->tm_year < (1971 - 1900) || t->tm_year > (2037 - 1900)) {
1049         // we'll fool mktime() into believing that this year is within
1050         // its normal, portable range (1970-2038) by setting tm_year to
1051         // 2000 or 2001 and adding the difference in milliseconds later.
1052         // choice between offset will depend on whether the year is a
1053         // leap year or not.
1054         int y = t->tm_year + 1900;
1055         int baseYear = daysInYear(y) == 365 ? 2001 : 2000;
1056         double baseTime = timeFromYear(baseYear);
1057         yearOffset = timeFromYear(y) - baseTime;
1058         t->tm_year = baseYear - 1900;
1059     }
1060 
1061     // Determine whether DST is in effect. mktime() can't do this for us because
1062     // it doesn't know about ms and yearOffset.
1063     // NOTE: Casting values of large magnitude to time_t (long) will
1064     // produce incorrect results, but there's no other option when calling localtime_r().
1065     if (!utc) {
1066         time_t tval = mktime(t) + (time_t)((ms + yearOffset) / 1000);
1067         tm t3 = *localtime(&tval);
1068         t->tm_isdst = t3.tm_isdst;
1069     }
1070 
1071     return (mktime(t) + utcOffset) * msPerSecond + ms + yearOffset;
1072 #else
1073     SYSTEMTIME st, dt;
1074     double tval;
1075 
1076     st.wYear = 1900 + t->tm_year;
1077     st.wMonth = t->tm_mon + 1;
1078     st.wDayOfWeek = t->tm_wday;
1079     st.wDay = t->tm_mday;
1080     st.wHour = t->tm_hour;
1081     st.wMinute = t->tm_min;
1082     st.wSecond = t->tm_sec;
1083     st.wMilliseconds = 0;
1084 
1085     TzSpecificLocalTimeToSystemTime(0, &st, &dt);
1086     SystemTimeToUnixTime(&dt, &tval);
1087 
1088     return (tval + utcOffset) * msPerSecond + ms;
1089 #endif
1090 }
1091 
1092 inline static bool isSpaceLike(char c)
1093 {
1094     return isASCIISpace(c) || c == ',' || c == ':' || c == '-';
1095 }
1096 
1097 static const char *skipSpacesAndComments(const char *s)
1098 {
1099     int nesting = 0;
1100     char ch;
1101     while ((ch = *s)) {
1102         // interpret - before a number as a sign rather than a comment char
1103         if (ch == '-' && isASCIIDigit(*(s + 1))) {
1104             break;
1105         }
1106         if (!isSpaceLike(ch)) {
1107             if (ch == '(') {
1108                 nesting++;
1109             } else if (ch == ')' && nesting > 0) {
1110                 nesting--;
1111             } else if (nesting == 0) {
1112                 break;
1113             }
1114         }
1115         s++;
1116     }
1117     return s;
1118 }
1119 
1120 // returns 0-11 (Jan-Dec); -1 on failure
1121 static int findMonth(const char *monthStr)
1122 {
1123     assert(monthStr);
1124     char needle[4];
1125     for (int i = 0; i < 3; ++i) {
1126         if (!*monthStr) {
1127             return -1;
1128         }
1129         needle[i] = toASCIILower(*monthStr++);
1130     }
1131     needle[3] = '\0';
1132     const char *haystack = "janfebmaraprmayjunjulaugsepoctnovdec";
1133     const char *str = strstr(haystack, needle);
1134     if (str) {
1135         int position = str - haystack;
1136         if (position % 3 == 0) {
1137             return position / 3;
1138         }
1139     }
1140     return -1;
1141 }
1142 
1143 static bool isTwoDigits(const char *str)
1144 {
1145     return isASCIIDigit(str[0]) && isASCIIDigit(str[1]);
1146 }
1147 
1148 static int twoDigit(const char *str)
1149 {
1150     return (str[0] - '0') * 10 + str[1] - '0';
1151 }
1152 
1153 static double parseDate(const UString &date)
1154 {
1155     // This parses a date in the form:
1156     //     Tuesday, 09-Nov-99 23:12:40 GMT
1157     // or
1158     //     Sat, 01-Jan-2000 08:00:00 GMT
1159     // or
1160     //     Sat, 01 Jan 2000 08:00:00 GMT
1161     // or
1162     //     01 Jan 99 22:00 +0100    (exceptions in rfc822/rfc2822)
1163     // ### non RFC formats, added for Javascript:
1164     //     [Wednesday] January 09 1999 23:12:40 GMT
1165     //     [Wednesday] January 09 23:12:40 GMT 1999
1166     //
1167     // We ignore the weekday.
1168 
1169     CString dateCString = date.UTF8String();
1170     const char *dateString = dateCString.c_str();
1171     if (!dateString) {
1172         return NaN;
1173     }
1174 
1175     // Skip leading space
1176     dateString = skipSpacesAndComments(dateString);
1177 
1178     // ISO 8601: "YYYY-MM-DD('T'|'t')hh:mm:ss[.S+]['Z']"
1179     // e.g. "2006-06-15T23:12:10.207830Z"
1180     if (isTwoDigits(dateString) &&
1181             isTwoDigits(dateString + 2) &&
1182             dateString[4] == '-' &&
1183             isTwoDigits(dateString + 5) &&
1184             dateString[7] == '-' &&
1185             isTwoDigits(dateString + 8)) {
1186         int year  = twoDigit(dateString) * 100 + twoDigit(dateString + 2);
1187         int month = twoDigit(dateString + 5) - 1;
1188         int day   = twoDigit(dateString + 8);
1189         if (month > 11 || day < 1 || day > 31) {
1190             return NaN;
1191         }
1192         int hour = 0, minute = 0;
1193         double second = 0;
1194         dateString += 10;
1195         if ((dateString[0] | 0x20) == 't' &&
1196                 isTwoDigits(dateString + 1) &&
1197                 dateString[3] == ':' &&
1198                 isTwoDigits(dateString + 4)) {
1199             hour   = twoDigit(dateString + 1);
1200             minute = twoDigit(dateString + 4);
1201             if (hour > 23 || minute > 59) {
1202                 return NaN;
1203             }
1204             dateString += 6;
1205             if (dateString[0] == ':' &&
1206                     isTwoDigits(dateString + 1)) {
1207                 second = twoDigit(dateString + 1);
1208                 if (second > 59) {
1209                     return NaN;
1210                 }
1211                 dateString += 3;
1212                 if (dateString[0] == '.' &&
1213                         isASCIIDigit(dateString[1])) {
1214                     dateString++;
1215                     double div = 10;
1216                     do {
1217                         second += (dateString[0] - '0') / div;
1218                         div *= 10;
1219                     } while (isASCIIDigit(*++dateString));
1220                 }
1221             }
1222         }
1223 
1224         if (dateString[0] == 'Z') {
1225             tm t;
1226             memset(&t, 0, sizeof(tm));
1227             int secs = int(second);
1228             t.tm_sec   = secs;
1229             t.tm_min   = minute;
1230             t.tm_hour  = hour;
1231             t.tm_mday  = day;
1232             t.tm_mon   = month;
1233             t.tm_year  = year - 1900;
1234             //  t.tm_isdst = -1;
1235 
1236             // Use our makeTime() rather than mktime() as the latter can't handle the full year range.
1237             return makeTime(&t, (second - secs) * 1000, true);
1238         }
1239 
1240         int offset = 0;
1241         return (ymdhmsToSeconds(year, month + 1, day, hour, minute, second) - (offset * 60.0)) * msPerSecond;
1242     }
1243 
1244     long month = -1;
1245     const char *wordStart = dateString;
1246     // Check contents of first words if not number
1247     while (*dateString && !isASCIIDigit(*dateString)) {
1248         if (isASCIISpace(*dateString) || *dateString == '(') {
1249             if (dateString - wordStart >= 3) {
1250                 month = findMonth(wordStart);
1251             }
1252             dateString = skipSpacesAndComments(dateString);
1253             wordStart = dateString;
1254         } else {
1255             dateString++;
1256         }
1257     }
1258 
1259     // Missing delimiter between month and day (like "January29")?
1260     if (month == -1 && wordStart != dateString) {
1261         month = findMonth(wordStart);
1262     }
1263 
1264     dateString = skipSpacesAndComments(dateString);
1265 
1266     if (!*dateString) {
1267         return NaN;
1268     }
1269 
1270     // ' 09-Nov-99 23:12:40 GMT'
1271     char *newPosStr;
1272     errno = 0;
1273     long day = strtol(dateString, &newPosStr, 10);
1274     dateString = newPosStr;
1275 
1276     if (errno || day < 0 || !*dateString) {
1277         return NaN;
1278     }
1279 
1280     long year = 0;
1281     if (day > 31) {
1282         // ### where is the boundary and what happens below?
1283         if (*dateString != '/') {
1284             return NaN;
1285         }
1286         // looks like a YYYY/MM/DD date
1287         if (!*++dateString) {
1288             return NaN;
1289         }
1290         year = day;
1291         month = strtol(dateString, &newPosStr, 10) - 1;
1292         if (errno) {
1293             return NaN;
1294         }
1295         dateString = newPosStr;
1296         if (*dateString++ != '/' || !*dateString) {
1297             return NaN;
1298         }
1299         day = strtol(dateString, &newPosStr, 10);
1300         if (errno) {
1301             return NaN;
1302         }
1303         dateString = newPosStr;
1304     } else if (*dateString == '/' && month == -1) {
1305         dateString++;
1306         // This looks like a MM/DD/YYYY date, not an RFC date.
1307         month = day - 1; // 0-based
1308         day = strtol(dateString, &newPosStr, 10);
1309         if (errno) {
1310             return NaN;
1311         }
1312         dateString = newPosStr;
1313         if (*dateString == '/') {
1314             dateString++;
1315         }
1316         if (!*dateString) {
1317             return NaN;
1318         }
1319     } else {
1320         if (*dateString == '-') {
1321             dateString++;
1322         }
1323 
1324         dateString = skipSpacesAndComments(dateString);
1325 
1326         if (*dateString == ',') {
1327             dateString++;
1328         }
1329 
1330         if (month == -1) { // not found yet
1331             month = findMonth(dateString);
1332             if (month == -1) {
1333                 return NaN;
1334             }
1335 
1336             while (*dateString && (*dateString != '-') && !isASCIISpace(*dateString)) {
1337                 dateString++;
1338             }
1339 
1340             if (!*dateString) {
1341                 return NaN;
1342             }
1343 
1344             // '-99 23:12:40 GMT'
1345             if (*dateString != '-' && *dateString != '/' && !isASCIISpace(*dateString)) {
1346                 return NaN;
1347             }
1348             dateString++;
1349         }
1350 
1351         if (month < 0 || month > 11) {
1352             return NaN;
1353         }
1354     }
1355 
1356     // '99 23:12:40 GMT'
1357     if (year <= 0 && *dateString) {
1358         year = strtol(dateString, &newPosStr, 10);
1359         if (errno) {
1360             return NaN;
1361         }
1362     }
1363 
1364     // Don't fail if the time is missing.
1365     long hour = 0;
1366     long minute = 0;
1367     long second = 0;
1368     if (!*newPosStr) {
1369         dateString = newPosStr;
1370     } else {
1371         // ' 23:12:40 GMT'
1372         if (*newPosStr == ':') {
1373             // There was no year; the number was the hour.
1374             year = -1;
1375         } else if (isSpaceLike(*newPosStr)) {
1376             // in the normal case (we parsed the year), advance to the next number
1377             dateString = skipSpacesAndComments(newPosStr + 1);
1378         } else {
1379             return NaN;
1380         }
1381 
1382         hour = strtol(dateString, &newPosStr, 10);
1383         // Do not check for errno here since we want to continue
1384         // even if errno was set because we are still looking
1385         // for the timezone!
1386 
1387         // Read a number? If not, this might be a timezone name.
1388         if (newPosStr != dateString) {
1389             dateString = newPosStr;
1390 
1391             if (hour < 0 || hour > 23) {
1392                 return NaN;
1393             }
1394 
1395             if (!*dateString) {
1396                 return NaN;
1397             }
1398 
1399             // ':12:40 GMT'
1400             if (*dateString++ != ':') {
1401                 return NaN;
1402             }
1403 
1404             minute = strtol(dateString, &newPosStr, 10);
1405             if (errno) {
1406                 return NaN;
1407             }
1408             dateString = newPosStr;
1409 
1410             if (minute < 0 || minute > 59) {
1411                 return NaN;
1412             }
1413 
1414             // ':40 GMT'
1415             if (*dateString && *dateString != ':' && !isASCIISpace(*dateString)) {
1416                 return NaN;
1417             }
1418 
1419             // seconds are optional in rfc822 + rfc2822
1420             if (*dateString == ':') {
1421                 dateString++;
1422 
1423                 second = strtol(dateString, &newPosStr, 10);
1424                 if (errno) {
1425                     return NaN;
1426                 }
1427                 dateString = newPosStr;
1428 
1429                 if (second < 0 || second > 59) {
1430                     return NaN;
1431                 }
1432 
1433                 // disallow trailing colon seconds
1434                 if (*dateString == ':') {
1435                     return NaN;
1436                 }
1437             }
1438 
1439             dateString = skipSpacesAndComments(dateString);
1440 
1441             if (strncasecmp(dateString, "AM", 2) == 0) {
1442                 if (hour > 12) {
1443                     return NaN;
1444                 }
1445                 if (hour == 12) {
1446                     hour = 0;
1447                 }
1448                 dateString = skipSpacesAndComments(dateString + 2);
1449             } else if (strncasecmp(dateString, "PM", 2) == 0) {
1450                 if (hour > 12) {
1451                     return NaN;
1452                 }
1453                 if (hour != 12) {
1454                     hour += 12;
1455                 }
1456                 dateString = skipSpacesAndComments(dateString + 2);
1457             }
1458         }
1459     }
1460 
1461     bool haveTZ = false;
1462     int offset = 0;
1463 
1464     // Don't fail if the time zone is missing.
1465     // Some websites omit the time zone (4275206).
1466     if (*dateString) {
1467         if (strncasecmp(dateString, "GMT", 3) == 0 ||
1468                 strncasecmp(dateString, "UTC", 3) == 0) {
1469             dateString += 3;
1470             haveTZ = true;
1471         }
1472 
1473         if (*dateString == '+' || *dateString == '-') {
1474             long o = strtol(dateString, &newPosStr, 10);
1475             if (errno) {
1476                 return NaN;
1477             }
1478             dateString = newPosStr;
1479 
1480             if (o < -9959 || o > 9959) {
1481                 return NaN;
1482             }
1483 
1484             int sgn = (o < 0) ? -1 : 1;
1485             o = abs(o);
1486             if (*dateString != ':') {
1487                 offset = ((o / 100) * 60 + (o % 100)) * sgn;
1488             } else { // GMT+05:00
1489                 dateString++;
1490                 long o2 = strtol(dateString, &newPosStr, 10);
1491                 if (errno) {
1492                     return NaN;
1493                 }
1494                 dateString = newPosStr;
1495                 offset = (o * 60 + o2) * sgn;
1496             }
1497             haveTZ = true;
1498         } else {
1499             for (int i = 0; i < int(sizeof(known_zones) / sizeof(KnownZone)); i++) {
1500                 if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
1501                     offset = known_zones[i].tzOffset;
1502                     dateString += strlen(known_zones[i].tzName);
1503                     haveTZ = true;
1504                     break;
1505                 }
1506             }
1507         }
1508     }
1509 
1510     dateString = skipSpacesAndComments(dateString);
1511 
1512     if (*dateString && year == -1) {
1513         year = strtol(dateString, &newPosStr, 10);
1514         if (errno) {
1515             return NaN;
1516         }
1517         dateString = newPosStr;
1518     }
1519 
1520     dateString = skipSpacesAndComments(dateString);
1521 
1522     // Trailing garbage
1523     if (*dateString) {
1524         return NaN;
1525     }
1526 
1527     // Y2K: Handle 2 digit years.
1528     if (year >= 0 && year < 100) {
1529         if (year < 50) {
1530             year += 2000;
1531         } else {
1532             year += 1900;
1533         }
1534     }
1535 
1536     // fall back to local timezone
1537     if (!haveTZ) {
1538         tm t;
1539         memset(&t, 0, sizeof(tm));
1540         t.tm_mday = day;
1541         t.tm_mon = month;
1542         t.tm_year = year - 1900;
1543         t.tm_isdst = -1;
1544         t.tm_sec = second;
1545         t.tm_min = minute;
1546         t.tm_hour = hour;
1547 
1548         // Use our makeTime() rather than mktime() as the latter can't handle the full year range.
1549         return makeTime(&t, 0, false);
1550     }
1551 
1552     return (ymdhmsToSeconds(year, month + 1, day, hour, minute, second) - (offset * 60.0)) * msPerSecond;
1553 }
1554 
1555 double timeClip(double t)
1556 {
1557     if (isNaN(t) || isInf(t)) {
1558         return NaN;
1559     }
1560     double at = fabs(t);
1561     if (at > 8.64E15) {
1562         return NaN;
1563     }
1564     return copysign(floor(at), t);
1565 }
1566 
1567 }   // namespace KJS