date_object.cpp

00001 // -*- c-basic-offset: 2 -*-
00002 /*
00003  *  This file is part of the KDE libraries
00004  *  Copyright (C) 1999-2005 Harri Porten (porten@kde.org)
00005  *  Copyright (C) 2004 Apple Computer, Inc.
00006  *
00007  *  This library is free software; you can redistribute it and/or
00008  *  modify it under the terms of the GNU Lesser General Public
00009  *  License as published by the Free Software Foundation; either
00010  *  version 2 of the License, or (at your option) any later version.
00011  *
00012  *  This library is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015  *  Lesser General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU Lesser General Public
00018  *  License along with this library; if not, write to the Free Software
00019  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00020  *
00021  */
00022 
00023 #ifdef HAVE_CONFIG_H
00024 #include <config.h>
00025 #endif
00026 
00027 #if TIME_WITH_SYS_TIME
00028 # include <sys/time.h>
00029 # include <time.h>
00030 #else
00031 #if HAVE_SYS_TIME_H
00032 #include <sys/time.h>
00033 #else
00034 #  include <time.h>
00035 # endif
00036 #endif
00037 #ifdef HAVE_SYS_TIMEB_H
00038 #include <sys/timeb.h>
00039 #endif
00040 
00041 #include <errno.h>
00042 
00043 #ifdef HAVE_SYS_PARAM_H
00044 #  include <sys/param.h>
00045 #endif // HAVE_SYS_PARAM_H
00046 
00047 #include <math.h>
00048 #include <string.h>
00049 #ifdef HAVE_STRINGS_H
00050 #  include <strings.h>
00051 #endif
00052 #include <stdio.h>
00053 #include <stdlib.h>
00054 #include <locale.h>
00055 #include <ctype.h>
00056 #include <assert.h>
00057 #include <limits.h>
00058 
00059 #include "date_object.h"
00060 #include "error_object.h"
00061 #include "operations.h"
00062 
00063 #include "date_object.lut.h"
00064 
00065 #ifdef _MSC_VER
00066 #  define strncasecmp(a,b,c) _strnicmp(a,b,c)
00067 #endif
00068 
00069 using namespace KJS;
00070 
00071 // come constants
00072 const time_t invalidDate = LONG_MIN;
00073 const double hoursPerDay = 24;
00074 const double minutesPerHour = 60;
00075 const double secondsPerMinute = 60;
00076 const double msPerSecond = 1000;
00077 const double msPerMinute = msPerSecond * secondsPerMinute;
00078 const double msPerHour = msPerMinute * minutesPerHour;
00079 const double msPerDay = msPerHour * hoursPerDay;
00080 static const char * const weekdayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
00081 static const char * const monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
00082 
00083 static UString formatDate(struct tm &tm)
00084 {
00085     char buffer[100];
00086     snprintf(buffer, sizeof(buffer), "%s %s %02d %04d",
00087             weekdayName[(tm.tm_wday + 6) % 7],
00088             monthName[tm.tm_mon], tm.tm_mday, tm.tm_year + 1900);
00089     return buffer;
00090 }
00091 
00092 static UString formatDateUTCVariant(struct tm &tm)
00093 {
00094     char buffer[100];
00095     snprintf(buffer, sizeof(buffer), "%s, %02d %s %04d",
00096             weekdayName[(tm.tm_wday + 6) % 7],
00097             tm.tm_mday, monthName[tm.tm_mon], tm.tm_year + 1900);
00098     return buffer;
00099 }
00100 
00101 static UString formatTime(struct tm &tm)
00102 {
00103     int tz;
00104     char buffer[100];
00105 #if defined BSD || defined(__linux__) || defined(__APPLE__)
00106     tz = tm.tm_gmtoff;
00107 #else
00108 #  if defined(__BORLANDC__) || defined (__CYGWIN__)
00109     tz = - _timezone;
00110 #  else
00111     tz = - timezone;
00112 #  endif
00113 #endif
00114     if (tz == 0) {
00115         snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT", tm.tm_hour, tm.tm_min, tm.tm_sec);
00116     } else {
00117         int offset = tz;
00118         if (offset < 0) {
00119             offset = -offset;
00120         }
00121         snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%c%02d%02d",
00122                 tm.tm_hour, tm.tm_min, tm.tm_sec,
00123                 tz < 0 ? '-' : '+', offset / (60*60), (offset / 60) % 60);
00124     }
00125     return UString(buffer);
00126 }
00127 
00128 static int day(double t)
00129 {
00130   return int(floor(t / msPerDay));
00131 }
00132 
00133 static double dayFromYear(int year)
00134 {
00135   return 365.0 * (year - 1970)
00136     + floor((year - 1969) / 4.0)
00137     - floor((year - 1901) / 100.0)
00138     + floor((year - 1601) / 400.0);
00139 }
00140 
00141 // depending on whether it's a leap year or not
00142 static int daysInYear(int year)
00143 {
00144   if (year % 4 != 0)
00145     return 365;
00146   else if (year % 400 == 0)
00147     return 366;
00148   else if (year % 100 == 0)
00149     return 365;
00150   else
00151     return 366;
00152 }
00153 
00154 // time value of the start of a year
00155 double timeFromYear(int year)
00156 {
00157   return msPerDay * dayFromYear(year);
00158 }
00159 
00160 // year determined by time value
00161 int yearFromTime(double t)
00162 {
00163   // ### there must be an easier way
00164   // initial guess
00165   int y = 1970 + int(t / (365.25 * msPerDay));
00166   // adjustment
00167   if (timeFromYear(y) > t) {
00168     do {
00169       --y;
00170     } while (timeFromYear(y) > t);
00171   } else {
00172     while (timeFromYear(y + 1) < t)
00173       ++y;
00174   }
00175 
00176   return y;
00177 }
00178 
00179 // 0: Sunday, 1: Monday, etc.
00180 int weekDay(double t)
00181 {
00182   int wd = (day(t) + 4) % 7;
00183   if (wd < 0)
00184     wd += 7;
00185   return wd;
00186 }
00187 
00188 static double timeZoneOffset(const struct tm *t)
00189 {
00190 #if defined BSD || defined(__linux__) || defined(__APPLE__)
00191   return -(t->tm_gmtoff / 60);
00192 #else
00193 #  if defined(__BORLANDC__) || defined(__CYGWIN__)
00194 // FIXME consider non one-hour DST change
00195 #if !defined(__CYGWIN__)
00196 #error please add daylight savings offset here!
00197 #endif
00198   return _timezone / 60 - (t->tm_isdst > 0 ? 60 : 0);
00199 #  else
00200   return timezone / 60 - (t->tm_isdst > 0 ? 60 : 0 );
00201 #  endif
00202 #endif
00203 }
00204 
00205 // Converts a list of arguments sent to a Date member function into milliseconds, updating
00206 // ms (representing milliseconds) and t (representing the rest of the date structure) appropriately.
00207 //
00208 // Format of member function: f([hour,] [min,] [sec,] [ms])
00209 static void fillStructuresUsingTimeArgs(ExecState *exec, const List &args, int maxArgs, double *ms, struct tm *t)
00210 {
00211     double milliseconds = 0;
00212     int idx = 0;
00213     int numArgs = args.size();
00214     
00215     // JS allows extra trailing arguments -- ignore them
00216     if (numArgs > maxArgs)
00217         numArgs = maxArgs;
00218 
00219     // hours
00220     if (maxArgs >= 4 && idx < numArgs) {
00221         t->tm_hour = 0;
00222         milliseconds += args[idx++].toInt32(exec) * msPerHour;
00223     }
00224 
00225     // minutes
00226     if (maxArgs >= 3 && idx < numArgs) {
00227         t->tm_min = 0;
00228         milliseconds += args[idx++].toInt32(exec) * msPerMinute;
00229     }
00230     
00231     // seconds
00232     if (maxArgs >= 2 && idx < numArgs) {
00233         t->tm_sec = 0;
00234         milliseconds += args[idx++].toInt32(exec) * msPerSecond;
00235     }
00236     
00237     // milliseconds
00238     if (idx < numArgs) {
00239         milliseconds += roundValue(exec, args[idx]);
00240     } else {
00241         milliseconds += *ms;
00242     }
00243     
00244     *ms = milliseconds;
00245 }
00246 
00247 // Converts a list of arguments sent to a Date member function into years, months, and milliseconds, updating
00248 // ms (representing milliseconds) and t (representing the rest of the date structure) appropriately.
00249 //
00250 // Format of member function: f([years,] [months,] [days])
00251 static void fillStructuresUsingDateArgs(ExecState *exec, const List &args, int maxArgs, double *ms, struct tm *t)
00252 {
00253   int idx = 0;
00254   int numArgs = args.size();
00255   
00256   // JS allows extra trailing arguments -- ignore them
00257   if (numArgs > maxArgs)
00258     numArgs = maxArgs;
00259   
00260   // years
00261   if (maxArgs >= 3 && idx < numArgs) {
00262     t->tm_year = args[idx++].toInt32(exec) - 1900;
00263   }
00264   
00265   // months
00266   if (maxArgs >= 2 && idx < numArgs) {
00267     t->tm_mon = args[idx++].toInt32(exec);
00268   }
00269   
00270   // days
00271   if (idx < numArgs) {
00272     t->tm_mday = 0;
00273     *ms += args[idx].toInt32(exec) * msPerDay;
00274   }
00275 }
00276 
00277 // ------------------------------ DateInstanceImp ------------------------------
00278 
00279 const ClassInfo DateInstanceImp::info = {"Date", 0, 0, 0};
00280 
00281 DateInstanceImp::DateInstanceImp(ObjectImp *proto)
00282   : ObjectImp(proto)
00283 {
00284 }
00285 
00286 // ------------------------------ DatePrototypeImp -----------------------------
00287 
00288 const ClassInfo DatePrototypeImp::info = {"Date", &DateInstanceImp::info, &dateTable, 0};
00289 
00290 /* Source for date_object.lut.h
00291    We use a negative ID to denote the "UTC" variant.
00292 @begin dateTable 61
00293   toString      DateProtoFuncImp::ToString      DontEnum|Function   0
00294   toUTCString       DateProtoFuncImp::ToUTCString       DontEnum|Function   0
00295   toDateString      DateProtoFuncImp::ToDateString      DontEnum|Function   0
00296   toTimeString      DateProtoFuncImp::ToTimeString      DontEnum|Function   0
00297   toLocaleString    DateProtoFuncImp::ToLocaleString    DontEnum|Function   0
00298   toLocaleDateString    DateProtoFuncImp::ToLocaleDateString    DontEnum|Function   0
00299   toLocaleTimeString    DateProtoFuncImp::ToLocaleTimeString    DontEnum|Function   0
00300   valueOf       DateProtoFuncImp::ValueOf       DontEnum|Function   0
00301   getTime       DateProtoFuncImp::GetTime       DontEnum|Function   0
00302   getFullYear       DateProtoFuncImp::GetFullYear       DontEnum|Function   0
00303   getUTCFullYear    -DateProtoFuncImp::GetFullYear      DontEnum|Function   0
00304   toGMTString       DateProtoFuncImp::ToGMTString       DontEnum|Function   0
00305   getMonth      DateProtoFuncImp::GetMonth      DontEnum|Function   0
00306   getUTCMonth       -DateProtoFuncImp::GetMonth     DontEnum|Function   0
00307   getDate       DateProtoFuncImp::GetDate       DontEnum|Function   0
00308   getUTCDate        -DateProtoFuncImp::GetDate      DontEnum|Function   0
00309   getDay        DateProtoFuncImp::GetDay        DontEnum|Function   0
00310   getUTCDay     -DateProtoFuncImp::GetDay       DontEnum|Function   0
00311   getHours      DateProtoFuncImp::GetHours      DontEnum|Function   0
00312   getUTCHours       -DateProtoFuncImp::GetHours     DontEnum|Function   0
00313   getMinutes        DateProtoFuncImp::GetMinutes        DontEnum|Function   0
00314   getUTCMinutes     -DateProtoFuncImp::GetMinutes       DontEnum|Function   0
00315   getSeconds        DateProtoFuncImp::GetSeconds        DontEnum|Function   0
00316   getUTCSeconds     -DateProtoFuncImp::GetSeconds       DontEnum|Function   0
00317   getMilliseconds   DateProtoFuncImp::GetMilliSeconds   DontEnum|Function   0
00318   getUTCMilliseconds    -DateProtoFuncImp::GetMilliSeconds  DontEnum|Function   0
00319   getTimezoneOffset DateProtoFuncImp::GetTimezoneOffset DontEnum|Function   0
00320   setTime       DateProtoFuncImp::SetTime       DontEnum|Function   1
00321   setMilliseconds   DateProtoFuncImp::SetMilliSeconds   DontEnum|Function   1
00322   setUTCMilliseconds    -DateProtoFuncImp::SetMilliSeconds  DontEnum|Function   1
00323   setSeconds        DateProtoFuncImp::SetSeconds        DontEnum|Function   2
00324   setUTCSeconds     -DateProtoFuncImp::SetSeconds       DontEnum|Function   2
00325   setMinutes        DateProtoFuncImp::SetMinutes        DontEnum|Function   3
00326   setUTCMinutes     -DateProtoFuncImp::SetMinutes       DontEnum|Function   3
00327   setHours      DateProtoFuncImp::SetHours      DontEnum|Function   4
00328   setUTCHours       -DateProtoFuncImp::SetHours     DontEnum|Function   4
00329   setDate       DateProtoFuncImp::SetDate       DontEnum|Function   1
00330   setUTCDate        -DateProtoFuncImp::SetDate      DontEnum|Function   1
00331   setMonth      DateProtoFuncImp::SetMonth      DontEnum|Function   2
00332   setUTCMonth       -DateProtoFuncImp::SetMonth     DontEnum|Function   2
00333   setFullYear       DateProtoFuncImp::SetFullYear       DontEnum|Function   3
00334   setUTCFullYear    -DateProtoFuncImp::SetFullYear      DontEnum|Function   3
00335   setYear       DateProtoFuncImp::SetYear       DontEnum|Function   1
00336   getYear       DateProtoFuncImp::GetYear       DontEnum|Function   0
00337   toGMTString       DateProtoFuncImp::ToGMTString       DontEnum|Function   0
00338 @end
00339 */
00340 // ECMA 15.9.4
00341 
00342 DatePrototypeImp::DatePrototypeImp(ExecState *,
00343                                    ObjectPrototypeImp *objectProto)
00344   : DateInstanceImp(objectProto)
00345 {
00346   Value protect(this);
00347   setInternalValue(Number(NaN));
00348   // The constructor will be added later, after DateObjectImp has been built
00349 }
00350 
00351 Value DatePrototypeImp::get(ExecState *exec, const Identifier &propertyName) const
00352 {
00353   return lookupGetFunction<DateProtoFuncImp, ObjectImp>( exec, propertyName, &dateTable, this );
00354 }
00355 
00356 // ------------------------------ DateProtoFuncImp -----------------------------
00357 
00358 DateProtoFuncImp::DateProtoFuncImp(ExecState *exec, int i, int len)
00359   : InternalFunctionImp(
00360     static_cast<FunctionPrototypeImp*>(exec->lexicalInterpreter()->builtinFunctionPrototype().imp())
00361     ), id(abs(i)), utc(i<0)
00362   // We use a negative ID to denote the "UTC" variant.
00363 {
00364   Value protect(this);
00365   putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
00366 }
00367 
00368 bool DateProtoFuncImp::implementsCall() const
00369 {
00370   return true;
00371 }
00372 
00373 Value DateProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args)
00374 {
00375   if ((id == ToString || id == ValueOf || id == GetTime || id == SetTime) &&
00376       !thisObj.inherits(&DateInstanceImp::info)) {
00377     // non-generic function called on non-date object
00378 
00379     // ToString and ValueOf are generic according to the spec, but the mozilla
00380     // tests suggest otherwise...
00381     Object err = Error::create(exec,TypeError);
00382     exec->setException(err);
00383     return err;
00384   }
00385 
00386 
00387   Value result;
00388   UString s;
00389   const int bufsize=100;
00390   char timebuffer[bufsize];
00391   CString oldlocale = setlocale(LC_TIME,NULL);
00392   if (!oldlocale.c_str())
00393     oldlocale = setlocale(LC_ALL, NULL);
00394   Value v = thisObj.internalValue();
00395   double milli = v.toNumber(exec);
00396   // special case: time value is NaN
00397   if (isNaN(milli)) {
00398     switch (id) {
00399     case ToString:
00400     case ToDateString:
00401     case ToTimeString:
00402     case ToGMTString:
00403     case ToUTCString:
00404     case ToLocaleString:
00405     case ToLocaleDateString:
00406     case ToLocaleTimeString:
00407       return String("Invalid Date");
00408     case ValueOf:
00409     case GetTime:
00410     case GetYear:
00411     case GetFullYear:
00412     case GetMonth:
00413     case GetDate:
00414     case GetDay:
00415     case GetHours:
00416     case GetMinutes:
00417     case GetSeconds:
00418     case GetMilliSeconds:
00419     case GetTimezoneOffset:
00420     case SetMilliSeconds:
00421     case SetSeconds:
00422     case SetMinutes:
00423     case SetHours:
00424     case SetDate:
00425     case SetMonth:
00426     case SetFullYear:
00427       return Number(NaN);
00428     }
00429   }
00430 
00431   if (id == SetTime) {
00432     result = Number(roundValue(exec,args[0]));
00433     thisObj.setInternalValue(result);
00434     return result;
00435   }
00436 
00437   // check whether time value is outside time_t's usual range
00438   // make the necessary transformations if necessary
00439   int realYearOffset = 0;
00440   double milliOffset = 0.0;
00441   if (milli < 0 || milli >= timeFromYear(2038)) {
00442     // ### ugly and probably not very precise
00443     int realYear = yearFromTime(milli);
00444     int base = daysInYear(realYear) == 365 ? 2001 : 2000;
00445     milliOffset = timeFromYear(base) - timeFromYear(realYear);
00446     milli += milliOffset;
00447     realYearOffset = realYear - base;
00448   }
00449 
00450   time_t tv = (time_t) floor(milli / 1000.0);
00451   double ms = milli - tv * 1000.0;
00452 
00453   struct tm *t;
00454   if ( (id == DateProtoFuncImp::ToGMTString) ||
00455        (id == DateProtoFuncImp::ToUTCString) )
00456     t = gmtime(&tv);
00457   else if (id == DateProtoFuncImp::ToString)
00458     t = localtime(&tv);
00459   else if (utc)
00460     t = gmtime(&tv);
00461   else
00462     t = localtime(&tv);
00463 
00464   // we had an out of range year. use that one (plus/minus offset
00465   // found by calculating tm_year) and fix the week day calculation
00466   if (realYearOffset != 0) {
00467     t->tm_year += realYearOffset;
00468     milli -= milliOffset;
00469     // our own weekday calculation. beware of need for local time.
00470     double m = milli;
00471     if (!utc)
00472       m -= timeZoneOffset(t) * msPerMinute;
00473     t->tm_wday = weekDay(m);
00474   }
00475 
00476   // trick gcc. We don't want the Y2K warnings.
00477   const char xFormat[] = "%x";
00478   const char cFormat[] = "%c";
00479 
00480   switch (id) {
00481   case ToString:
00482     result = String(formatDate(*t) + " " + formatTime(*t));
00483     break;
00484   case ToDateString:
00485     result = String(formatDate(*t));
00486     break;
00487   case ToTimeString:
00488     result = String(formatTime(*t));
00489     break;
00490   case ToGMTString:
00491   case ToUTCString:
00492     result = String(formatDateUTCVariant(*t) + " " + formatTime(*t));
00493     break;
00494   case ToLocaleString:
00495     strftime(timebuffer, bufsize, cFormat, t);
00496     result = String(timebuffer);
00497     break;
00498   case ToLocaleDateString:
00499     strftime(timebuffer, bufsize, xFormat, t);
00500     result = String(timebuffer);
00501     break;
00502   case ToLocaleTimeString:
00503     strftime(timebuffer, bufsize, "%X", t);
00504     result = String(timebuffer);
00505     break;
00506   case ValueOf:
00507     result = Number(milli);
00508     break;
00509   case GetTime:
00510     result = Number(milli);
00511     break;
00512   case GetYear:
00513     // IE returns the full year even in getYear.
00514     if ( exec->dynamicInterpreter()->compatMode() != Interpreter::IECompat )
00515       result = Number(t->tm_year);
00516     else
00517       result = Number(1900 + t->tm_year);
00518     break;
00519   case GetFullYear:
00520     result = Number(1900 + t->tm_year);
00521     break;
00522   case GetMonth:
00523     result = Number(t->tm_mon);
00524     break;
00525   case GetDate:
00526     result = Number(t->tm_mday);
00527     break;
00528   case GetDay:
00529     result = Number(t->tm_wday);
00530     break;
00531   case GetHours:
00532     result = Number(t->tm_hour);
00533     break;
00534   case GetMinutes:
00535     result = Number(t->tm_min);
00536     break;
00537   case GetSeconds:
00538     result = Number(t->tm_sec);
00539     break;
00540   case GetMilliSeconds:
00541     result = Number(ms);
00542     break;
00543   case GetTimezoneOffset:
00544     result = Number(timeZoneOffset(t));
00545     break;
00546   case SetMilliSeconds:
00547     fillStructuresUsingTimeArgs(exec, args, 1, &ms, t);
00548     break;
00549   case SetSeconds:
00550     fillStructuresUsingTimeArgs(exec, args, 2, &ms, t);
00551     break;
00552   case SetMinutes:
00553     fillStructuresUsingTimeArgs(exec, args, 3, &ms, t);
00554     break;
00555   case SetHours:
00556     fillStructuresUsingTimeArgs(exec, args, 4, &ms, t);
00557     break;
00558   case SetDate:
00559     fillStructuresUsingDateArgs(exec, args, 1, &ms, t);
00560     break;
00561   case SetMonth:
00562     fillStructuresUsingDateArgs(exec, args, 2, &ms, t);    
00563     break;
00564   case SetFullYear:
00565     fillStructuresUsingDateArgs(exec, args, 3, &ms, t);
00566     break;
00567   case SetYear:
00568     int y = args[0].toInt32(exec);
00569     if (y < 1900) {
00570       if (y >= 0 && y <= 99) {
00571         t->tm_year = y;
00572       } else {
00573         fillStructuresUsingDateArgs(exec, args, 3, &ms, t);
00574       }
00575     } else {
00576       t->tm_year = y - 1900;
00577     }
00578     break;
00579   }
00580 
00581   if (id == SetYear || id == SetMilliSeconds || id == SetSeconds ||
00582       id == SetMinutes || id == SetHours || id == SetDate ||
00583       id == SetMonth || id == SetFullYear ) {
00584     result = Number(makeTime(t, ms, utc));
00585     thisObj.setInternalValue(result);
00586   }
00587 
00588   return result;
00589 }
00590 
00591 // ------------------------------ DateObjectImp --------------------------------
00592 
00593 // TODO: MakeTime (15.9.11.1) etc. ?
00594 
00595 DateObjectImp::DateObjectImp(ExecState *exec,
00596                              FunctionPrototypeImp *funcProto,
00597                              DatePrototypeImp *dateProto)
00598   : InternalFunctionImp(funcProto)
00599 {
00600   Value protect(this);
00601 
00602   // ECMA 15.9.4.1 Date.prototype
00603   putDirect(prototypePropertyName, dateProto, DontEnum|DontDelete|ReadOnly);
00604 
00605   static const Identifier parsePropertyName("parse");
00606   putDirect(parsePropertyName, new DateObjectFuncImp(exec,funcProto,DateObjectFuncImp::Parse, 1), DontEnum);
00607   static const Identifier UTCPropertyName("UTC");
00608   putDirect(UTCPropertyName,   new DateObjectFuncImp(exec,funcProto,DateObjectFuncImp::UTC,   7),   DontEnum);
00609 
00610   // no. of arguments for constructor
00611   putDirect(lengthPropertyName, 7, ReadOnly|DontDelete|DontEnum);
00612 }
00613 
00614 bool DateObjectImp::implementsConstruct() const
00615 {
00616   return true;
00617 }
00618 
00619 // ECMA 15.9.3
00620 Object DateObjectImp::construct(ExecState *exec, const List &args)
00621 {
00622   int numArgs = args.size();
00623 
00624 #ifdef KJS_VERBOSE
00625   fprintf(stderr,"DateObjectImp::construct - %d args\n", numArgs);
00626 #endif
00627   double value;
00628 
00629   if (numArgs == 0) { // new Date() ECMA 15.9.3.3
00630 #ifdef HAVE_SYS_TIMEB_H
00631 #  if defined(__BORLANDC__)
00632     struct timeb timebuffer;
00633     ftime(&timebuffer);
00634 #  else
00635     struct _timeb timebuffer;
00636     _ftime(&timebuffer);
00637 #  endif
00638     double utc = floor((double)timebuffer.time * 1000.0 + (double)timebuffer.millitm);
00639 #else
00640     struct timeval tv;
00641     gettimeofday(&tv, 0L);
00642     double utc = floor((double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0);
00643 #endif
00644     value = utc;
00645   } else if (numArgs == 1) {
00646     Value prim = args[0].toPrimitive(exec);
00647     if (prim.isA(StringType))
00648       value = parseDate(prim.toString(exec));
00649     else
00650       value = prim.toNumber(exec);
00651   } else {
00652     if (isNaN(args[0].toNumber(exec))
00653         || isNaN(args[1].toNumber(exec))
00654         || (numArgs >= 3 && isNaN(args[2].toNumber(exec)))
00655         || (numArgs >= 4 && isNaN(args[3].toNumber(exec)))
00656         || (numArgs >= 5 && isNaN(args[4].toNumber(exec)))
00657         || (numArgs >= 6 && isNaN(args[5].toNumber(exec)))
00658         || (numArgs >= 7 && isNaN(args[6].toNumber(exec)))) {
00659       value = NaN;
00660     } else {
00661       struct tm t;
00662       memset(&t, 0, sizeof(t));
00663       int year = args[0].toInt32(exec);
00664       t.tm_year = (year >= 0 && year <= 99) ? year : year - 1900;
00665       t.tm_mon = args[1].toInt32(exec);
00666       t.tm_mday = (numArgs >= 3) ? args[2].toInt32(exec) : 1;
00667       t.tm_hour = (numArgs >= 4) ? args[3].toInt32(exec) : 0;
00668       t.tm_min = (numArgs >= 5) ? args[4].toInt32(exec) : 0;
00669       t.tm_sec = (numArgs >= 6) ? args[5].toInt32(exec) : 0;
00670       t.tm_isdst = -1;
00671       int ms = (numArgs >= 7) ? args[6].toInt32(exec) : 0;
00672       value = makeTime(&t, ms, false);
00673     }
00674   }
00675 
00676   Object proto = exec->lexicalInterpreter()->builtinDatePrototype();
00677   Object ret(new DateInstanceImp(proto.imp()));
00678   ret.setInternalValue(Number(timeClip(value)));
00679   return ret;
00680 }
00681 
00682 bool DateObjectImp::implementsCall() const
00683 {
00684   return true;
00685 }
00686 
00687 // ECMA 15.9.2
00688 Value DateObjectImp::call(ExecState* /*exec*/, Object &/*thisObj*/, const List &/*args*/)
00689 {
00690 #ifdef KJS_VERBOSE
00691   fprintf(stderr,"DateObjectImp::call - current time\n");
00692 #endif
00693   time_t t = time(0L);
00694   // FIXME: not threadsafe
00695   struct tm *tm = localtime(&t);
00696   return String(formatDate(*tm) + " " + formatTime(*tm));
00697 }
00698 
00699 // ------------------------------ DateObjectFuncImp ----------------------------
00700 
00701 DateObjectFuncImp::DateObjectFuncImp(ExecState* /*exec*/, FunctionPrototypeImp *funcProto,
00702                                      int i, int len)
00703   : InternalFunctionImp(funcProto), id(i)
00704 {
00705   Value protect(this);
00706   putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
00707 }
00708 
00709 bool DateObjectFuncImp::implementsCall() const
00710 {
00711   return true;
00712 }
00713 
00714 // ECMA 15.9.4.2 - 3
00715 Value DateObjectFuncImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
00716 {
00717   if (id == Parse) {
00718     return Number(parseDate(args[0].toString(exec)));
00719   } else { // UTC
00720     int n = args.size();
00721     if (isNaN(args[0].toNumber(exec))
00722         || isNaN(args[1].toNumber(exec))
00723         || (n >= 3 && isNaN(args[2].toNumber(exec)))
00724         || (n >= 4 && isNaN(args[3].toNumber(exec)))
00725         || (n >= 5 && isNaN(args[4].toNumber(exec)))
00726         || (n >= 6 && isNaN(args[5].toNumber(exec)))
00727         || (n >= 7 && isNaN(args[6].toNumber(exec)))) {
00728       return Number(NaN);
00729     }
00730 
00731     struct tm t;
00732     memset(&t, 0, sizeof(t));
00733     int year = args[0].toInt32(exec);
00734     t.tm_year = (year >= 0 && year <= 99) ? year : year - 1900;
00735     t.tm_mon = args[1].toInt32(exec);
00736     t.tm_mday = (n >= 3) ? args[2].toInt32(exec) : 1;
00737     t.tm_hour = (n >= 4) ? args[3].toInt32(exec) : 0;
00738     t.tm_min = (n >= 5) ? args[4].toInt32(exec) : 0;
00739     t.tm_sec = (n >= 6) ? args[5].toInt32(exec) : 0;
00740     int ms = (n >= 7) ? args[6].toInt32(exec) : 0;
00741     return Number(makeTime(&t, ms, true));
00742   }
00743 }
00744 
00745 // -----------------------------------------------------------------------------
00746 
00747 
00748 double KJS::parseDate(const UString &u)
00749 {
00750 #ifdef KJS_VERBOSE
00751   fprintf(stderr,"KJS::parseDate %s\n",u.ascii());
00752 #endif
00753   double /*time_t*/ seconds = KRFCDate_parseDate( u );
00754 
00755   return seconds == invalidDate ? NaN : seconds * 1000.0;
00756 }
00757 
00759 
00760 static double ymdhms_to_seconds(int year, int mon, int day, int hour, int minute, int second)
00761 {
00762     //printf("year=%d month=%d day=%d hour=%d minute=%d second=%d\n", year, mon, day, hour, minute, second);
00763 
00764     double ret = (day - 32075)       /* days */
00765             + 1461L * (year + 4800L + (mon - 14) / 12) / 4
00766             + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12
00767             - 3 * ((year + 4900L + (mon - 14) / 12) / 100) / 4
00768             - 2440588;
00769     ret = 24*ret + hour;     /* hours   */
00770     ret = 60*ret + minute;   /* minutes */
00771     ret = 60*ret + second;   /* seconds */
00772 
00773     return ret;
00774 }
00775 
00776 // we follow the recommendation of rfc2822 to consider all
00777 // obsolete time zones not listed here equivalent to "-0000"
00778 static const struct KnownZone {
00779 #ifdef _WIN32
00780     char tzName[4];
00781 #else
00782     const char tzName[4];
00783 #endif
00784     int tzOffset;
00785 } known_zones[] = {
00786     { "UT", 0 },
00787     { "GMT", 0 },
00788     { "EST", -300 },
00789     { "EDT", -240 },
00790     { "CST", -360 },
00791     { "CDT", -300 },
00792     { "MST", -420 },
00793     { "MDT", -360 },
00794     { "PST", -480 },
00795     { "PDT", -420 }
00796 };
00797 
00798 double KJS::makeTime(struct tm *t, double ms, bool utc)
00799 {
00800     int utcOffset;
00801     if (utc) {
00802     time_t zero = 0;
00803 #if defined BSD || defined(__linux__) || defined(__APPLE__)
00804     struct tm t3;
00805         localtime_r(&zero, &t3);
00806         utcOffset = t3.tm_gmtoff;
00807         t->tm_isdst = t3.tm_isdst;
00808 #else
00809         (void)localtime(&zero);
00810 #  if defined(__BORLANDC__) || defined(__CYGWIN__)
00811         utcOffset = - _timezone;
00812 #  else
00813         utcOffset = - timezone;
00814 #  endif
00815         t->tm_isdst = 0;
00816 #endif
00817     } else {
00818         utcOffset = 0;
00819         t->tm_isdst = -1;
00820     }
00821 
00822     double yearOffset = 0.0;
00823     if (t->tm_year < (1970 - 1900) || t->tm_year > (2038 - 1900)) {
00824       // we'll fool mktime() into believing that this year is within
00825       // its normal, portable range (1970-2038) by setting tm_year to
00826       // 2000 or 2001 and adding the difference in milliseconds later.
00827       // choice between offset will depend on whether the year is a
00828       // leap year or not.
00829       int y = t->tm_year + 1900;
00830       int baseYear = daysInYear(y) == 365 ? 2001 : 2000;
00831       const double baseTime = timeFromYear(baseYear);
00832       yearOffset = timeFromYear(y) - baseTime;
00833       t->tm_year = baseYear - 1900;
00834     }
00835 
00836     // Determine if we passed over a DST change boundary
00837     if (!utc) {
00838       time_t tval = mktime(t) + utcOffset + int((ms + yearOffset)/1000);
00839       struct tm t3;
00840       localtime_r(&tval, &t3);
00841       t->tm_isdst = t3.tm_isdst;
00842     }
00843 
00844     return (mktime(t) + utcOffset) * 1000.0 + ms + yearOffset;
00845 }
00846 
00847 // returns 0-11 (Jan-Dec); -1 on failure
00848 static int findMonth(const char *monthStr)
00849 {
00850   assert(monthStr);
00851   static const char haystack[37] = "janfebmaraprmayjunjulaugsepoctnovdec";
00852   char needle[4];
00853   for (int i = 0; i < 3; ++i) {
00854     if (!*monthStr)
00855       return -1;
00856     needle[i] = tolower(*monthStr++);
00857   }
00858   needle[3] = '\0';
00859   const char *str = strstr(haystack, needle);
00860   if (str) {
00861     int position = str - haystack;
00862     if (position % 3 == 0) {
00863       return position / 3;
00864     }
00865   }
00866   return -1;
00867 }
00868 
00869 // maybe this should be more often than just isspace() but beware of
00870 // conflicts with : in time strings.
00871 static bool isSpaceLike(char c)
00872 {
00873     return isspace(c) || c == ',' || c == ':' || c == '-';
00874 }
00875 
00876 double KJS::KRFCDate_parseDate(const UString &_date)
00877 {
00878      // This parse a date in the form:
00879      //     Wednesday, 09-Nov-99 23:12:40 GMT
00880      // or
00881      //     Sat, 01-Jan-2000 08:00:00 GMT
00882      // or
00883      //     Sat, 01 Jan 2000 08:00:00 GMT
00884      // or
00885      //     01 Jan 99 22:00 +0100    (exceptions in rfc822/rfc2822)
00886      // ### non RFC formats, added for Javascript:
00887      //     [Wednesday] January 09 1999 23:12:40 GMT
00888      //     [Wednesday] January 09 23:12:40 GMT 1999
00889      //
00890      // We ignore the weekday
00891      //
00892      double result = -1;
00893      int offset = 0;
00894      bool have_tz = false;
00895      char *newPosStr;
00896      const char *dateString = _date.ascii();
00897      int day = 0;
00898      int month = -1; // not set yet
00899      int year = 0;
00900      int hour = 0;
00901      int minute = 0;
00902      int second = 0;
00903      bool have_time = false;
00904 
00905      // Skip leading space
00906      while(*dateString && isSpaceLike(*dateString))
00907         dateString++;
00908 
00909      const char *wordStart = dateString;
00910      // Check contents of first words if not number
00911      while(*dateString && !isdigit(*dateString))
00912      {
00913         if (isSpaceLike(*dateString) && dateString - wordStart >= 3)
00914         {
00915           month = findMonth(wordStart);
00916           while(*dateString && isSpaceLike(*dateString))
00917              dateString++;
00918           wordStart = dateString;
00919         }
00920         else
00921            dateString++;
00922      }
00923      // missing delimiter between month and day (like "January29")?
00924      if (month == -1 && dateString && wordStart != dateString) {
00925        month = findMonth(wordStart);
00926        // TODO: emit warning about dubious format found
00927      }
00928 
00929      while(*dateString && isSpaceLike(*dateString))
00930         dateString++;
00931 
00932      if (!*dateString)
00933         return invalidDate;
00934 
00935      // ' 09-Nov-99 23:12:40 GMT'
00936      errno = 0;
00937      day = strtol(dateString, &newPosStr, 10);
00938      if (errno)
00939        return invalidDate;
00940      dateString = newPosStr;
00941 
00942      if (!*dateString)
00943         return invalidDate;
00944 
00945      if (day < 0)
00946        return invalidDate;
00947      if (day > 31) {
00948        // ### where is the boundary and what happens below?
00949        if (*dateString == '/') {
00950          // looks like a YYYY/MM/DD date
00951          if (!*++dateString)
00952            return invalidDate;
00953          year = day;
00954          month = strtol(dateString, &newPosStr, 10) - 1;
00955          if (errno)
00956            return invalidDate;
00957          dateString = newPosStr;
00958          if (*dateString++ != '/' || !*dateString)
00959            return invalidDate;
00960          day = strtol(dateString, &newPosStr, 10);
00961          if (errno)
00962            return invalidDate;
00963          dateString = newPosStr;
00964        } else {
00965          return invalidDate;
00966        }
00967      } else if (*dateString == '/' && month == -1)
00968      {
00969         dateString++;
00970         // This looks like a MM/DD/YYYY date, not an RFC date.....
00971         month = day - 1; // 0-based
00972         day = strtol(dateString, &newPosStr, 10);
00973         if (errno)
00974           return invalidDate;
00975         dateString = newPosStr;
00976         if (*dateString == '/')
00977           dateString++;
00978         if (!*dateString)
00979           return invalidDate;
00980         //printf("month=%d day=%d dateString=%s\n", month, day, dateString);
00981      }
00982      else
00983      {
00984        if (*dateString == '-')
00985          dateString++;
00986 
00987        while(*dateString && isSpaceLike(*dateString))
00988          dateString++;
00989 
00990        if (*dateString == ',')
00991          dateString++;
00992 
00993        if ( month == -1 ) // not found yet
00994        {
00995          month = findMonth(dateString);
00996          if (month == -1)
00997            return invalidDate;
00998 
00999          while(*dateString && (*dateString != '-') && !isSpaceLike(*dateString))
01000            dateString++;
01001 
01002          if (!*dateString)
01003            return invalidDate;
01004 
01005          // '-99 23:12:40 GMT'
01006          if ((*dateString != '-') && (*dateString != '/') && !isspace(*dateString))
01007            return invalidDate;
01008          dateString++;
01009        }
01010 
01011        if ((month < 0) || (month > 11))
01012          return invalidDate;
01013      }
01014 
01015      // '99 23:12:40 GMT'
01016      if (year <= 0 && *dateString) {
01017        year = strtol(dateString, &newPosStr, 10);
01018        if (errno)
01019          return invalidDate;
01020      }
01021 
01022      // Don't fail if the time is missing.
01023      if (*newPosStr)
01024      {
01025         // ' 23:12:40 GMT'
01026     if (*newPosStr == ':') // Ah, so there was no year, but the number was the hour
01027         year = -1;
01028         else if (isSpaceLike(*newPosStr)) // we parsed the year
01029             dateString = ++newPosStr;
01030     else
01031         return invalidDate;
01032 
01033         hour = strtol(dateString, &newPosStr, 10);
01034 
01035         // Do not check for errno here since we want to continue
01036         // even if errno was set becasue we are still looking
01037         // for the timezone!
01038         // read a number? if not this might be a timezone name
01039         if (newPosStr != dateString) {
01040           have_time = true;
01041           dateString = newPosStr;
01042 
01043           if ((hour < 0) || (hour > 23))
01044             return invalidDate;
01045 
01046           if (!*dateString)
01047             return invalidDate;
01048 
01049           // ':12:40 GMT'
01050           if (*dateString++ != ':')
01051             return invalidDate;
01052 
01053           minute = strtol(dateString, &newPosStr, 10);
01054           if (errno)
01055             return invalidDate;
01056           dateString = newPosStr;
01057 
01058           if ((minute < 0) || (minute > 59))
01059             return invalidDate;
01060 
01061           // ':40 GMT'
01062           if (*dateString && *dateString != ':' && !isspace(*dateString))
01063             return invalidDate;
01064 
01065           // seconds are optional in rfc822 + rfc2822
01066           if (*dateString ==':') {
01067             dateString++;
01068 
01069             second = strtol(dateString, &newPosStr, 10);
01070             if (errno)
01071               return invalidDate;
01072             dateString = newPosStr;
01073 
01074             if ((second < 0) || (second > 59))
01075               return invalidDate;
01076 
01077         // disallow trailing colon seconds
01078         if (*dateString == ':')
01079         return invalidDate;
01080           }
01081 
01082           while(*dateString && isspace(*dateString))
01083             dateString++;
01084 
01085       if (strncasecmp(dateString, "AM", 2) == 0) {
01086         if (hour > 12)
01087           return invalidDate;
01088         if (hour == 12)
01089           hour = 0;
01090         dateString += 2;
01091         while (isspace(*dateString))
01092           dateString++;
01093       } else if (strncasecmp(dateString, "PM", 2) == 0) {
01094         if (hour > 12)
01095           return invalidDate;
01096         if (hour != 12)
01097           hour += 12;
01098         dateString += 2;
01099         while (isspace(*dateString))
01100           dateString++;
01101       }
01102         }
01103      } else {
01104        dateString = newPosStr;
01105      }
01106 
01107      // don't fail if the time zone is missing, some
01108      // broken mail-/news-clients omit the time zone
01109      if (*dateString) {
01110 
01111        if (strncasecmp(dateString, "GMT", 3) == 0 ||
01112        strncasecmp(dateString, "UTC", 3) == 0)
01113        {
01114          dateString += 3;
01115          have_tz = true;
01116        }
01117 
01118        while (*dateString && isspace(*dateString))
01119          ++dateString;
01120 
01121        if (strncasecmp(dateString, "GMT", 3) == 0) {
01122          dateString += 3;
01123        }
01124        if ((*dateString == '+') || (*dateString == '-')) {
01125          offset = strtol(dateString, &newPosStr, 10);
01126          if (errno)
01127            return invalidDate;
01128          dateString = newPosStr;
01129 
01130          if ((offset < -9959) || (offset > 9959))
01131             return invalidDate;
01132 
01133          int sgn = (offset < 0)? -1:1;
01134          offset = abs(offset);
01135          if ( *dateString == ':' ) { // GMT+05:00
01136            int offset2 = strtol(dateString, &newPosStr, 10);
01137            if (errno)
01138              return invalidDate;
01139            dateString = newPosStr;
01140            offset = (offset*60 + offset2)*sgn;
01141          }
01142          else
01143            offset = ((offset / 100)*60 + (offset % 100))*sgn;
01144          have_tz = true;
01145        } else {
01146          for (int i=0; i < int(sizeof(known_zones)/sizeof(KnownZone)); i++) {
01147            if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
01148              offset = known_zones[i].tzOffset;
01149              dateString += strlen(known_zones[i].tzName);
01150              have_tz = true;
01151              break;
01152            }
01153          }
01154        }
01155      }
01156 
01157      while(*dateString && isspace(*dateString))
01158         dateString++;
01159 
01160      if ( *dateString && year == -1 ) {
01161        year = strtol(dateString, &newPosStr, 10);
01162        if (errno)
01163          return invalidDate;
01164        dateString = newPosStr;
01165      }
01166 
01167      while (isspace(*dateString))
01168        dateString++;
01169 
01170 #if 0
01171      // Trailing garbage
01172      if (*dateString != '\0')
01173        return invalidDate;
01174 #endif
01175 
01176      // Y2K: Solve 2 digit years
01177      if ((year >= 0) && (year < 50))
01178          year += 2000;
01179 
01180      if ((year >= 50) && (year < 100))
01181          year += 1900;  // Y2K
01182 
01183      if (!have_tz) {
01184        // fall back to midnight, local timezone
01185        struct tm t;
01186        memset(&t, 0, sizeof(tm));
01187        t.tm_mday = day;
01188        t.tm_mon = month;
01189        t.tm_year = year - 1900;
01190        t.tm_isdst = -1;
01191        if (have_time) {
01192          t.tm_sec = second;
01193          t.tm_min = minute;
01194          t.tm_hour = hour;
01195        }
01196 
01197        // better not use mktime() as it can't handle the full year range
01198        return makeTime(&t, 0, false) / 1000.0;
01199      }
01200 
01201      result = ymdhms_to_seconds(year, month+1, day, hour, minute, second) - offset*60;
01202      return result;
01203 }
01204 
01205 
01206 double KJS::timeClip(double t)
01207 {
01208   if (isInf(t))
01209     return NaN;
01210   double at = fabs(t);
01211   if (at > 8.64E15)
01212     return NaN;
01213   return floor(at) * (t != at ? -1 : 1);
01214 }
01215 
KDE Home | KDE Accessibility Home | Description of Access Keys