/* * This file is part of the KDE libraries * Copyright (C) 1999-2005 Harri Porten (porten@kde.org) * Copyright (C) 2004 Apple Computer, Inc. * * 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 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 * */ #ifdef HAVE_CONFIG_H #include #endif #if TIME_WITH_SYS_TIME # include # include #else #if HAVE_SYS_TIME_H #include #else # include # endif #endif #ifdef HAVE_SYS_TIMEB_H #include #endif #include #ifdef HAVE_SYS_PARAM_H # include #endif // HAVE_SYS_PARAM_H #include #include #ifdef HAVE_STRINGS_H # include #endif #include #include #include #include #include #include #include "date_object.h" #include "error_object.h" #include "operations.h" #include "date_object.lut.h" #ifdef _MSC_VER # define strncasecmp(a,b,c) _strnicmp(a,b,c) #endif using namespace KJS; // come constants const time_t invalidDate = LONG_MIN; const double hoursPerDay = 24; const double minutesPerHour = 60; const double secondsPerMinute = 60; const double msPerSecond = 1000; const double msPerMinute = msPerSecond * secondsPerMinute; const double msPerHour = msPerMinute * minutesPerHour; const double msPerDay = msPerHour * hoursPerDay; static const char * const weekdayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; static const char * const monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static UString formatDate(struct tm &tm) { char buffer[100]; snprintf(buffer, sizeof(buffer), "%s %s %02d %04d", weekdayName[(tm.tm_wday + 6) % 7], monthName[tm.tm_mon], tm.tm_mday, tm.tm_year + 1900); return buffer; } static UString formatDateUTCVariant(struct tm &tm) { char buffer[100]; snprintf(buffer, sizeof(buffer), "%s, %02d %s %04d", weekdayName[(tm.tm_wday + 6) % 7], tm.tm_mday, monthName[tm.tm_mon], tm.tm_year + 1900); return buffer; } static UString formatTime(struct tm &tm) { int tz; char buffer[100]; #if defined BSD || defined(__linux__) || defined(__APPLE__) tz = tm.tm_gmtoff; #else # if defined (__CYGWIN__) tz = - _timezone; # else tz = - timezone; # endif #endif if (tz == 0) { snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT", tm.tm_hour, tm.tm_min, tm.tm_sec); } else { int offset = tz; if (offset < 0) { offset = -offset; } snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%c%02d%02d", tm.tm_hour, tm.tm_min, tm.tm_sec, tz < 0 ? '-' : '+', offset / (60*60), (offset / 60) % 60); } return UString(buffer); } static int day(double t) { return int(floor(t / msPerDay)); } static double dayFromYear(int year) { return 365.0 * (year - 1970) + floor((year - 1969) / 4.0) - floor((year - 1901) / 100.0) + floor((year - 1601) / 400.0); } // depending on whether it's a leap year or not static int daysInYear(int year) { if (year % 4 != 0) return 365; else if (year % 400 == 0) return 366; else if (year % 100 == 0) return 365; else return 366; } // time value of the start of a year double timeFromYear(int year) { return msPerDay * dayFromYear(year); } // year determined by time value int yearFromTime(double t) { // ### there must be an easier way // initial guess int y = 1970 + int(t / (365.25 * msPerDay)); // adjustment if (timeFromYear(y) > t) { do { --y; } while (timeFromYear(y) > t); } else { while (timeFromYear(y + 1) < t) ++y; } return y; } // 0: Sunday, 1: Monday, etc. int weekDay(double t) { int wd = (day(t) + 4) % 7; if (wd < 0) wd += 7; return wd; } static double timeZoneOffset(const struct tm *t) { #if defined BSD || defined(__linux__) || defined(__APPLE__) return -(t->tm_gmtoff / 60); #else # if defined(__CYGWIN__) // FIXME consider non one-hour DST change #if !defined(__CYGWIN__) #error please add daylight savings offset here! #endif return _timezone / 60 - (t->tm_isdst > 0 ? 60 : 0); # else return timezone / 60 - (t->tm_isdst > 0 ? 60 : 0 ); # endif #endif } // Converts a list of arguments sent to a Date member function into milliseconds, updating // ms (representing milliseconds) and t (representing the rest of the date structure) appropriately. // // Format of member function: f([hour,] [min,] [sec,] [ms]) static void fillStructuresUsingTimeArgs(ExecState *exec, const List &args, int maxArgs, double *ms, struct tm *t) { double milliseconds = 0; int idx = 0; int numArgs = args.size(); // JS allows extra trailing arguments -- ignore them if (numArgs > maxArgs) numArgs = maxArgs; // hours if (maxArgs >= 4 && idx < numArgs) { t->tm_hour = 0; milliseconds += args[idx++].toInt32(exec) * msPerHour; } // minutes if (maxArgs >= 3 && idx < numArgs) { t->tm_min = 0; milliseconds += args[idx++].toInt32(exec) * msPerMinute; } // seconds if (maxArgs >= 2 && idx < numArgs) { t->tm_sec = 0; milliseconds += args[idx++].toInt32(exec) * msPerSecond; } // milliseconds if (idx < numArgs) { milliseconds += roundValue(exec, args[idx]); } else { milliseconds += *ms; } *ms = milliseconds; } // Converts a list of arguments sent to a Date member function into years, months, and milliseconds, updating // ms (representing milliseconds) and t (representing the rest of the date structure) appropriately. // // Format of member function: f([years,] [months,] [days]) static void fillStructuresUsingDateArgs(ExecState *exec, const List &args, int maxArgs, double *ms, struct tm *t) { int idx = 0; int numArgs = args.size(); // JS allows extra trailing arguments -- ignore them if (numArgs > maxArgs) numArgs = maxArgs; // years if (maxArgs >= 3 && idx < numArgs) { t->tm_year = args[idx++].toInt32(exec) - 1900; } // months if (maxArgs >= 2 && idx < numArgs) { t->tm_mon = args[idx++].toInt32(exec); } // days if (idx < numArgs) { t->tm_mday = 0; *ms += args[idx].toInt32(exec) * msPerDay; } } // ------------------------------ DateInstanceImp ------------------------------ const ClassInfo DateInstanceImp::info = {"Date", 0, 0, 0}; DateInstanceImp::DateInstanceImp(ObjectImp *proto) : ObjectImp(proto) { } // ------------------------------ DatePrototypeImp ----------------------------- const ClassInfo DatePrototypeImp::info = {"Date", &DateInstanceImp::info, &dateTable, 0}; /* Source for date_object.lut.h We use a negative ID to denote the "UTC" variant. @begin dateTable 61 toString DateProtoFuncImp::ToString DontEnum|Function 0 toUTCString DateProtoFuncImp::ToUTCString DontEnum|Function 0 toDateString DateProtoFuncImp::ToDateString DontEnum|Function 0 toTimeString DateProtoFuncImp::ToTimeString DontEnum|Function 0 toLocaleString DateProtoFuncImp::ToLocaleString DontEnum|Function 0 toLocaleDateString DateProtoFuncImp::ToLocaleDateString DontEnum|Function 0 toLocaleTimeString DateProtoFuncImp::ToLocaleTimeString DontEnum|Function 0 valueOf DateProtoFuncImp::ValueOf DontEnum|Function 0 getTime DateProtoFuncImp::GetTime DontEnum|Function 0 getFullYear DateProtoFuncImp::GetFullYear DontEnum|Function 0 getUTCFullYear -DateProtoFuncImp::GetFullYear DontEnum|Function 0 toGMTString DateProtoFuncImp::ToGMTString DontEnum|Function 0 getMonth DateProtoFuncImp::GetMonth DontEnum|Function 0 getUTCMonth -DateProtoFuncImp::GetMonth DontEnum|Function 0 getDate DateProtoFuncImp::GetDate DontEnum|Function 0 getUTCDate -DateProtoFuncImp::GetDate DontEnum|Function 0 getDay DateProtoFuncImp::GetDay DontEnum|Function 0 getUTCDay -DateProtoFuncImp::GetDay DontEnum|Function 0 getHours DateProtoFuncImp::GetHours DontEnum|Function 0 getUTCHours -DateProtoFuncImp::GetHours DontEnum|Function 0 getMinutes DateProtoFuncImp::GetMinutes DontEnum|Function 0 getUTCMinutes -DateProtoFuncImp::GetMinutes DontEnum|Function 0 getSeconds DateProtoFuncImp::GetSeconds DontEnum|Function 0 getUTCSeconds -DateProtoFuncImp::GetSeconds DontEnum|Function 0 getMilliseconds DateProtoFuncImp::GetMilliSeconds DontEnum|Function 0 getUTCMilliseconds -DateProtoFuncImp::GetMilliSeconds DontEnum|Function 0 getTimezoneOffset DateProtoFuncImp::GetTimezoneOffset DontEnum|Function 0 setTime DateProtoFuncImp::SetTime DontEnum|Function 1 setMilliseconds DateProtoFuncImp::SetMilliSeconds DontEnum|Function 1 setUTCMilliseconds -DateProtoFuncImp::SetMilliSeconds DontEnum|Function 1 setSeconds DateProtoFuncImp::SetSeconds DontEnum|Function 2 setUTCSeconds -DateProtoFuncImp::SetSeconds DontEnum|Function 2 setMinutes DateProtoFuncImp::SetMinutes DontEnum|Function 3 setUTCMinutes -DateProtoFuncImp::SetMinutes DontEnum|Function 3 setHours DateProtoFuncImp::SetHours DontEnum|Function 4 setUTCHours -DateProtoFuncImp::SetHours DontEnum|Function 4 setDate DateProtoFuncImp::SetDate DontEnum|Function 1 setUTCDate -DateProtoFuncImp::SetDate DontEnum|Function 1 setMonth DateProtoFuncImp::SetMonth DontEnum|Function 2 setUTCMonth -DateProtoFuncImp::SetMonth DontEnum|Function 2 setFullYear DateProtoFuncImp::SetFullYear DontEnum|Function 3 setUTCFullYear -DateProtoFuncImp::SetFullYear DontEnum|Function 3 setYear DateProtoFuncImp::SetYear DontEnum|Function 1 getYear DateProtoFuncImp::GetYear DontEnum|Function 0 toGMTString DateProtoFuncImp::ToGMTString DontEnum|Function 0 @end */ // ECMA 15.9.4 DatePrototypeImp::DatePrototypeImp(ExecState *, ObjectPrototypeImp *objectProto) : DateInstanceImp(objectProto) { Value protect(this); setInternalValue(Number(NaN)); // The constructor will be added later, after DateObjectImp has been built } Value DatePrototypeImp::get(ExecState *exec, const Identifier &propertyName) const { return lookupGetFunction( exec, propertyName, &dateTable, this ); } // ------------------------------ DateProtoFuncImp ----------------------------- DateProtoFuncImp::DateProtoFuncImp(ExecState *exec, int i, int len) : InternalFunctionImp( static_cast(exec->lexicalInterpreter()->builtinFunctionPrototype().imp()) ), id(abs(i)), utc(i<0) // We use a negative ID to denote the "UTC" variant. { Value protect(this); putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum); } bool DateProtoFuncImp::implementsCall() const { return true; } Value DateProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args) { if (!thisObj.inherits(&DateInstanceImp::info)) { // non-generic function called on non-date object // ToString and ValueOf are generic according to the spec, but the mozilla // tests suggest otherwise... Object err = Error::create(exec,TypeError); exec->setException(err); return err; } Value result; UString s; const int bufsize=100; char timebuffer[bufsize]; CString oldlocale = setlocale(LC_TIME,NULL); if (!oldlocale.c_str()) oldlocale = setlocale(LC_ALL, NULL); Value v = thisObj.internalValue(); double milli = v.toNumber(exec); // special case: time value is NaN if (isNaN(milli)) { switch (id) { case ToString: case ToDateString: case ToTimeString: case ToGMTString: case ToUTCString: case ToLocaleString: case ToLocaleDateString: case ToLocaleTimeString: return String("Invalid Date"); case ValueOf: case GetTime: case GetYear: case GetFullYear: case GetMonth: case GetDate: case GetDay: case GetHours: case GetMinutes: case GetSeconds: case GetMilliSeconds: case GetTimezoneOffset: case SetMilliSeconds: case SetSeconds: case SetMinutes: case SetHours: case SetDate: case SetMonth: case SetFullYear: return Number(NaN); } } if (id == SetTime) { result = Number(roundValue(exec,args[0])); thisObj.setInternalValue(result); return result; } // check whether time value is outside time_t's usual range // make the necessary transformations if necessary int realYearOffset = 0; double milliOffset = 0.0; if (milli < 0 || milli >= timeFromYear(2038)) { // ### ugly and probably not very precise int realYear = yearFromTime(milli); int base = daysInYear(realYear) == 365 ? 2001 : 2000; milliOffset = timeFromYear(base) - timeFromYear(realYear); milli += milliOffset; realYearOffset = realYear - base; } time_t tv = (time_t) floor(milli / 1000.0); double ms = milli - tv * 1000.0; struct tm *t; if ( (id == DateProtoFuncImp::ToGMTString) || (id == DateProtoFuncImp::ToUTCString) ) t = gmtime(&tv); else if (id == DateProtoFuncImp::ToString) t = localtime(&tv); else if (utc) t = gmtime(&tv); else t = localtime(&tv); // we had an out of range year. use that one (plus/minus offset // found by calculating tm_year) and fix the week day calculation if (realYearOffset != 0) { t->tm_year += realYearOffset; milli -= milliOffset; // our own weekday calculation. beware of need for local time. double m = milli; if (!utc) m -= timeZoneOffset(t) * msPerMinute; t->tm_wday = weekDay(m); } // trick gcc. We don't want the Y2K warnings. const char xFormat[] = "%x"; const char cFormat[] = "%c"; switch (id) { case ToString: result = String(formatDate(*t) + " " + formatTime(*t)); break; case ToDateString: result = String(formatDate(*t)); break; case ToTimeString: result = String(formatTime(*t)); break; case ToGMTString: case ToUTCString: result = String(formatDateUTCVariant(*t) + " " + formatTime(*t)); break; case ToLocaleString: strftime(timebuffer, bufsize, cFormat, t); result = String(timebuffer); break; case ToLocaleDateString: strftime(timebuffer, bufsize, xFormat, t); result = String(timebuffer); break; case ToLocaleTimeString: strftime(timebuffer, bufsize, "%X", t); result = String(timebuffer); break; case ValueOf: result = Number(milli); break; case GetTime: result = Number(milli); break; case GetYear: // IE returns the full year even in getYear. if ( exec->dynamicInterpreter()->compatMode() != Interpreter::IECompat ) result = Number(t->tm_year); else result = Number(1900 + t->tm_year); break; case GetFullYear: result = Number(1900 + t->tm_year); break; case GetMonth: result = Number(t->tm_mon); break; case GetDate: result = Number(t->tm_mday); break; case GetDay: result = Number(t->tm_wday); break; case GetHours: result = Number(t->tm_hour); break; case GetMinutes: result = Number(t->tm_min); break; case GetSeconds: result = Number(t->tm_sec); break; case GetMilliSeconds: result = Number(ms); break; case GetTimezoneOffset: result = Number(timeZoneOffset(t)); break; case SetMilliSeconds: fillStructuresUsingTimeArgs(exec, args, 1, &ms, t); break; case SetSeconds: fillStructuresUsingTimeArgs(exec, args, 2, &ms, t); break; case SetMinutes: fillStructuresUsingTimeArgs(exec, args, 3, &ms, t); break; case SetHours: fillStructuresUsingTimeArgs(exec, args, 4, &ms, t); break; case SetDate: fillStructuresUsingDateArgs(exec, args, 1, &ms, t); break; case SetMonth: fillStructuresUsingDateArgs(exec, args, 2, &ms, t); break; case SetFullYear: fillStructuresUsingDateArgs(exec, args, 3, &ms, t); break; case SetYear: int y = args[0].toInt32(exec); if (y < 1900) { if (y >= 0 && y <= 99) { t->tm_year = y; } else { fillStructuresUsingDateArgs(exec, args, 3, &ms, t); } } else { t->tm_year = y - 1900; } break; } if (id == SetYear || id == SetMilliSeconds || id == SetSeconds || id == SetMinutes || id == SetHours || id == SetDate || id == SetMonth || id == SetFullYear ) { result = Number(makeTime(t, ms, utc)); thisObj.setInternalValue(result); } return result; } // ------------------------------ DateObjectImp -------------------------------- // TODO: MakeTime (15.9.11.1) etc. ? DateObjectImp::DateObjectImp(ExecState *exec, FunctionPrototypeImp *funcProto, DatePrototypeImp *dateProto) : InternalFunctionImp(funcProto) { Value protect(this); // ECMA 15.9.4.1 Date.prototype putDirect(prototypePropertyName, dateProto, DontEnum|DontDelete|ReadOnly); static const Identifier parsePropertyName("parse"); putDirect(parsePropertyName, new DateObjectFuncImp(exec,funcProto,DateObjectFuncImp::Parse, 1), DontEnum); static const Identifier UTCPropertyName("UTC"); putDirect(UTCPropertyName, new DateObjectFuncImp(exec,funcProto,DateObjectFuncImp::UTC, 7), DontEnum); // no. of arguments for constructor putDirect(lengthPropertyName, 7, ReadOnly|DontDelete|DontEnum); } bool DateObjectImp::implementsConstruct() const { return true; } // ECMA 15.9.3 Object DateObjectImp::construct(ExecState *exec, const List &args) { int numArgs = args.size(); #ifdef KJS_VERBOSE fprintf(stderr,"DateObjectImp::construct - %d args\n", numArgs); #endif double value; if (numArgs == 0) { // new Date() ECMA 15.9.3.3 #ifdef HAVE_SYS_TIMEB_H struct _timeb timebuffer; _ftime(&timebuffer); double utc = floor((double)timebuffer.time * 1000.0 + (double)timebuffer.millitm); #else struct timeval tv; gettimeofday(&tv, 0L); double utc = floor((double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0); #endif value = utc; } else if (numArgs == 1) { Value prim = args[0].toPrimitive(exec); if (prim.isA(StringType)) value = parseDate(prim.toString(exec)); else value = prim.toNumber(exec); } else { if (isNaN(args[0].toNumber(exec)) || isNaN(args[1].toNumber(exec)) || (numArgs >= 3 && isNaN(args[2].toNumber(exec))) || (numArgs >= 4 && isNaN(args[3].toNumber(exec))) || (numArgs >= 5 && isNaN(args[4].toNumber(exec))) || (numArgs >= 6 && isNaN(args[5].toNumber(exec))) || (numArgs >= 7 && isNaN(args[6].toNumber(exec)))) { value = NaN; } else { struct tm t; memset(&t, 0, sizeof(t)); int year = args[0].toInt32(exec); t.tm_year = (year >= 0 && year <= 99) ? year : year - 1900; t.tm_mon = args[1].toInt32(exec); t.tm_mday = (numArgs >= 3) ? args[2].toInt32(exec) : 1; t.tm_hour = (numArgs >= 4) ? args[3].toInt32(exec) : 0; t.tm_min = (numArgs >= 5) ? args[4].toInt32(exec) : 0; t.tm_sec = (numArgs >= 6) ? args[5].toInt32(exec) : 0; t.tm_isdst = -1; int ms = (numArgs >= 7) ? args[6].toInt32(exec) : 0; value = makeTime(&t, ms, false); } } Object proto = exec->lexicalInterpreter()->builtinDatePrototype(); Object ret(new DateInstanceImp(proto.imp())); ret.setInternalValue(Number(timeClip(value))); return ret; } bool DateObjectImp::implementsCall() const { return true; } // ECMA 15.9.2 Value DateObjectImp::call(ExecState* /*exec*/, Object &/*thisObj*/, const List &/*args*/) { #ifdef KJS_VERBOSE fprintf(stderr,"DateObjectImp::call - current time\n"); #endif time_t t = time(0L); // FIXME: not threadsafe struct tm *tm = localtime(&t); return String(formatDate(*tm) + " " + formatTime(*tm)); } // ------------------------------ DateObjectFuncImp ---------------------------- DateObjectFuncImp::DateObjectFuncImp(ExecState* /*exec*/, FunctionPrototypeImp *funcProto, int i, int len) : InternalFunctionImp(funcProto), id(i) { Value protect(this); putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum); } bool DateObjectFuncImp::implementsCall() const { return true; } // ECMA 15.9.4.2 - 3 Value DateObjectFuncImp::call(ExecState *exec, Object &/*thisObj*/, const List &args) { if (id == Parse) { return Number(parseDate(args[0].toString(exec))); } else { // UTC int n = args.size(); if (isNaN(args[0].toNumber(exec)) || isNaN(args[1].toNumber(exec)) || (n >= 3 && isNaN(args[2].toNumber(exec))) || (n >= 4 && isNaN(args[3].toNumber(exec))) || (n >= 5 && isNaN(args[4].toNumber(exec))) || (n >= 6 && isNaN(args[5].toNumber(exec))) || (n >= 7 && isNaN(args[6].toNumber(exec)))) { return Number(NaN); } struct tm t; memset(&t, 0, sizeof(t)); int year = args[0].toInt32(exec); t.tm_year = (year >= 0 && year <= 99) ? year : year - 1900; t.tm_mon = args[1].toInt32(exec); t.tm_mday = (n >= 3) ? args[2].toInt32(exec) : 1; t.tm_hour = (n >= 4) ? args[3].toInt32(exec) : 0; t.tm_min = (n >= 5) ? args[4].toInt32(exec) : 0; t.tm_sec = (n >= 6) ? args[5].toInt32(exec) : 0; int ms = (n >= 7) ? args[6].toInt32(exec) : 0; return Number(makeTime(&t, ms, true)); } } // ----------------------------------------------------------------------------- double KJS::parseDate(const UString &u) { #ifdef KJS_VERBOSE fprintf(stderr,"KJS::parseDate %s\n",u.ascii()); #endif double /*time_t*/ seconds = KRFCDate_parseDate( u ); return seconds == invalidDate ? NaN : seconds * 1000.0; } ///// Awful duplication from krfcdate.cpp - we don't link to tdecore static double ymdhms_to_seconds(int year, int mon, int day, int hour, int minute, int second) { //printf("year=%d month=%d day=%d hour=%d minute=%d second=%d\n", year, mon, day, hour, minute, second); double ret = (day - 32075) /* days */ + 1461L * (year + 4800L + (mon - 14) / 12) / 4 + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12 - 3 * ((year + 4900L + (mon - 14) / 12) / 100) / 4 - 2440588; ret = 24*ret + hour; /* hours */ ret = 60*ret + minute; /* minutes */ ret = 60*ret + second; /* seconds */ return ret; } // we follow the recommendation of rfc2822 to consider all // obsolete time zones not listed here equivalent to "-0000" static const struct KnownZone { #ifdef _WIN32 char tzName[4]; #else const char tzName[4]; #endif int tzOffset; } known_zones[] = { { "UT", 0 }, { "GMT", 0 }, { "EST", -300 }, { "EDT", -240 }, { "CST", -360 }, { "CDT", -300 }, { "MST", -420 }, { "MDT", -360 }, { "PST", -480 }, { "PDT", -420 } }; double KJS::makeTime(struct tm *t, double ms, bool utc) { int utcOffset; if (utc) { time_t zero = 0; #if defined BSD || defined(__linux__) || defined(__APPLE__) struct tm t3; localtime_r(&zero, &t3); utcOffset = t3.tm_gmtoff; t->tm_isdst = t3.tm_isdst; #else (void)localtime(&zero); # if defined(__CYGWIN__) utcOffset = - _timezone; # else utcOffset = - timezone; # endif t->tm_isdst = 0; #endif } else { utcOffset = 0; t->tm_isdst = -1; } double yearOffset = 0.0; if (t->tm_year < (1970 - 1900) || t->tm_year > (2038 - 1900)) { // we'll fool mktime() into believing that this year is within // its normal, portable range (1970-2038) by setting tm_year to // 2000 or 2001 and adding the difference in milliseconds later. // choice between offset will depend on whether the year is a // leap year or not. int y = t->tm_year + 1900; int baseYear = daysInYear(y) == 365 ? 2001 : 2000; const double baseTime = timeFromYear(baseYear); yearOffset = timeFromYear(y) - baseTime; t->tm_year = baseYear - 1900; } // Determine if we passed over a DST change boundary if (!utc) { time_t tval = mktime(t) + utcOffset + int((ms + yearOffset)/1000); struct tm t3; localtime_r(&tval, &t3); t->tm_isdst = t3.tm_isdst; } return (mktime(t) + utcOffset) * 1000.0 + ms + yearOffset; } // returns 0-11 (Jan-Dec); -1 on failure static int findMonth(const char *monthStr) { assert(monthStr); static const char haystack[37] = "janfebmaraprmayjunjulaugsepoctnovdec"; char needle[4]; for (int i = 0; i < 3; ++i) { if (!*monthStr) return -1; needle[i] = tolower(*monthStr++); } needle[3] = '\0'; const char *str = strstr(haystack, needle); if (str) { int position = str - haystack; if (position % 3 == 0) { return position / 3; } } return -1; } // maybe this should be more often than just isspace() but beware of // conflicts with : in time strings. static bool isSpaceLike(char c) { return isspace(c) || c == ',' || c == ':' || c == '-'; } double KJS::KRFCDate_parseDate(const UString &_date) { // This parse a date in the form: // Wednesday, 09-Nov-99 23:12:40 GMT // or // Sat, 01-Jan-2000 08:00:00 GMT // or // Sat, 01 Jan 2000 08:00:00 GMT // or // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822) // ### non RFC formats, added for Javascript: // [Wednesday] January 09 1999 23:12:40 GMT // [Wednesday] January 09 23:12:40 GMT 1999 // // We ignore the weekday // double result = -1; int offset = 0; bool have_tz = false; char *newPosStr; const char *dateString = _date.ascii(); int day = 0; int month = -1; // not set yet int year = 0; int hour = 0; int minute = 0; int second = 0; bool have_time = false; // Skip leading space while(*dateString && isSpaceLike(*dateString)) dateString++; const char *wordStart = dateString; // Check contents of first words if not number while(*dateString && !isdigit(*dateString)) { if (isSpaceLike(*dateString) && dateString - wordStart >= 3) { month = findMonth(wordStart); while(*dateString && isSpaceLike(*dateString)) dateString++; wordStart = dateString; } else dateString++; } // missing delimiter between month and day (like "January29")? if (month == -1 && dateString && wordStart != dateString) { month = findMonth(wordStart); // TODO: emit warning about dubious format found } while(*dateString && isSpaceLike(*dateString)) dateString++; if (!*dateString) return invalidDate; // ' 09-Nov-99 23:12:40 GMT' errno = 0; day = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; if (!*dateString) return invalidDate; if (day < 0) return invalidDate; if (day > 31) { // ### where is the boundary and what happens below? if (*dateString == '/') { // looks like a YYYY/MM/DD date if (!*++dateString) return invalidDate; year = day; month = strtol(dateString, &newPosStr, 10) - 1; if (errno) return invalidDate; dateString = newPosStr; if (*dateString++ != '/' || !*dateString) return invalidDate; day = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; } else { return invalidDate; } } else if (*dateString == '/' && month == -1) { dateString++; // This looks like a MM/DD/YYYY date, not an RFC date..... month = day - 1; // 0-based day = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; if (*dateString == '/') dateString++; if (!*dateString) return invalidDate; //printf("month=%d day=%d dateString=%s\n", month, day, dateString); } else { if (*dateString == '-') dateString++; while(*dateString && isSpaceLike(*dateString)) dateString++; if (*dateString == ',') dateString++; if ( month == -1 ) // not found yet { month = findMonth(dateString); if (month == -1) return invalidDate; while(*dateString && (*dateString != '-') && !isSpaceLike(*dateString)) dateString++; if (!*dateString) return invalidDate; // '-99 23:12:40 GMT' if ((*dateString != '-') && (*dateString != '/') && !isspace(*dateString)) return invalidDate; dateString++; } if ((month < 0) || (month > 11)) return invalidDate; } // '99 23:12:40 GMT' if (year <= 0 && *dateString) { year = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; } // Don't fail if the time is missing. if (*newPosStr) { // ' 23:12:40 GMT' if (*newPosStr == ':') // Ah, so there was no year, but the number was the hour year = -1; else if (isSpaceLike(*newPosStr)) // we parsed the year dateString = ++newPosStr; else return invalidDate; hour = strtol(dateString, &newPosStr, 10); // 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) { have_time = true; dateString = newPosStr; if ((hour < 0) || (hour > 23)) return invalidDate; if (!*dateString) return invalidDate; // ':12:40 GMT' if (*dateString++ != ':') return invalidDate; minute = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; if ((minute < 0) || (minute > 59)) return invalidDate; // ':40 GMT' if (*dateString && *dateString != ':' && !isspace(*dateString)) return invalidDate; // seconds are optional in rfc822 + rfc2822 if (*dateString ==':') { dateString++; second = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; if ((second < 0) || (second > 59)) return invalidDate; // disallow trailing colon seconds if (*dateString == ':') return invalidDate; } while(*dateString && isspace(*dateString)) dateString++; if (strncasecmp(dateString, "AM", 2) == 0) { if (hour > 12) return invalidDate; if (hour == 12) hour = 0; dateString += 2; while (isspace(*dateString)) dateString++; } else if (strncasecmp(dateString, "PM", 2) == 0) { if (hour > 12) return invalidDate; if (hour != 12) hour += 12; dateString += 2; while (isspace(*dateString)) dateString++; } } } else { dateString = newPosStr; } // don't fail if the time zone is missing, some // broken mail-/news-clients omit the time zone if (*dateString) { if (strncasecmp(dateString, "GMT", 3) == 0 || strncasecmp(dateString, "UTC", 3) == 0) { dateString += 3; have_tz = true; } while (*dateString && isspace(*dateString)) ++dateString; if (strncasecmp(dateString, "GMT", 3) == 0) { dateString += 3; } if ((*dateString == '+') || (*dateString == '-')) { offset = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; if ((offset < -9959) || (offset > 9959)) return invalidDate; int sgn = (offset < 0)? -1:1; offset = abs(offset); if ( *dateString == ':' ) { // GMT+05:00 int offset2 = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; offset = (offset*60 + offset2)*sgn; } else offset = ((offset / 100)*60 + (offset % 100))*sgn; have_tz = true; } else { for (int i=0; i < int(sizeof(known_zones)/sizeof(KnownZone)); 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); have_tz = true; break; } } } } while(*dateString && isspace(*dateString)) dateString++; if ( *dateString && year == -1 ) { year = strtol(dateString, &newPosStr, 10); if (errno) return invalidDate; dateString = newPosStr; } while (isspace(*dateString)) dateString++; #if 0 // Trailing garbage if (*dateString != '\0') return invalidDate; #endif // Y2K: Solve 2 digit years if ((year >= 0) && (year < 50)) year += 2000; if ((year >= 50) && (year < 100)) year += 1900; // Y2K if (!have_tz) { // fall back to midnight, local timezone struct tm t; memset(&t, 0, sizeof(tm)); t.tm_mday = day; t.tm_mon = month; t.tm_year = year - 1900; t.tm_isdst = -1; if (have_time) { t.tm_sec = second; t.tm_min = minute; t.tm_hour = hour; } // better not use mktime() as it can't handle the full year range return makeTime(&t, 0, false) / 1000.0; } result = ymdhms_to_seconds(year, month+1, day, hour, minute, second) - offset*60; return result; } double KJS::timeClip(double t) { if (isInf(t)) return NaN; double at = fabs(t); if (at > 8.64E15) return NaN; return floor(at) * (t != at ? -1 : 1); }