diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 (patch) | |
tree | 5ac38a06f3dde268dc7927dc155896926aaf7012 /kjs/date_object.cpp | |
download | tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.tar.gz tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kjs/date_object.cpp')
-rw-r--r-- | kjs/date_object.cpp | 1214 |
1 files changed, 1214 insertions, 0 deletions
diff --git a/kjs/date_object.cpp b/kjs/date_object.cpp new file mode 100644 index 000000000..26635611d --- /dev/null +++ b/kjs/date_object.cpp @@ -0,0 +1,1214 @@ +// -*- c-basic-offset: 2 -*- +/* + * 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 <config.h> +#endif + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +# include <time.h> +# endif +#endif +#ifdef HAVE_SYS_TIMEB_H +#include <sys/timeb.h> +#endif + +#include <errno.h> + +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif // HAVE_SYS_PARAM_H + +#include <math.h> +#include <string.h> +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <locale.h> +#include <ctype.h> +#include <assert.h> +#include <limits.h> + +#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(__BORLANDC__) || 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(__BORLANDC__) || 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<DateProtoFuncImp, ObjectImp>( exec, propertyName, &dateTable, this ); +} + +// ------------------------------ DateProtoFuncImp ----------------------------- + +DateProtoFuncImp::DateProtoFuncImp(ExecState *exec, int i, int len) + : InternalFunctionImp( + static_cast<FunctionPrototypeImp*>(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 +# if defined(__BORLANDC__) + struct timeb timebuffer; + ftime(&timebuffer); +# else + struct _timeb timebuffer; + _ftime(&timebuffer); +# endif + 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 kdecore + +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(__BORLANDC__) || 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); +} + |