You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1209 lines
34 KiB
1209 lines
34 KiB
/*
|
|
* 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 (__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<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
|
|
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);
|
|
}
|
|
|