1/*
2 * This file is part of the KDE libraries
3 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
4 * Copyright (C) 2004 Apple Computer, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 */
21
22#include "date_object.h"
23#include <config-kjs.h>
24#include "date_object.lut.h"
25#include "internal.h"
26
27#if HAVE(ERRNO_H)
28#include <errno.h>
29#endif
30
31#if HAVE(SYS_PARAM_H)
32#include <sys/param.h>
33#endif
34
35#if HAVE(SYS_TIME_H)
36#include <sys/time.h>
37#endif
38
39#if HAVE(SYS_TIMEB_H)
40#include <sys/timeb.h>
41#endif
42
43#include <float.h>
44#include <limits.h>
45#include <locale.h>
46#include <math.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <cstring>
50#include <time.h>
51
52#if PLATFORM(SOLARIS_OS)
53#include <strings.h>
54#endif
55
56#include "error_object.h"
57#include "operations.h"
58
59#if PLATFORM(MAC)
60#include <CoreFoundation/CoreFoundation.h>
61#endif
62
63#if PLATFORM(WIN_OS)
64#include <windows.h>
65#define copysign(x, y) _copysign(x, y)
66#define snprintf _snprintf
67#if !COMPILER(GCC)
68#define isfinite(x) _finite(x)
69#ifndef strncasecmp
70#define strncasecmp(x, y, z) strnicmp(x, y, z)
71#endif
72#endif
73#endif
74
75#include "wtf/DisallowCType.h"
76#include "wtf/ASCIICType.h"
77
78// GCC cstring uses these automatically, but not all implementations do.
79using std::strlen;
80using std::strcpy;
81using std::strncpy;
82using std::memset;
83using std::memcpy;
84
85using namespace WTF;
86
87
88inline int gmtoffset(const tm& t)
89{
90#if PLATFORM(WIN_OS)
91 // Time is supposed to be in the current timezone.
92 // FIXME: Use undocumented _dstbias?
93 return -(_timezone / 60 - (t.tm_isdst > 0 ? 60 : 0 )) * 60;
94#else
95#ifdef HAVE_TM_GMTOFF
96 return t.tm_gmtoff;
97#else
98 return - timezone;
99#endif
100#endif
101}
102
103namespace KJS {
104
105/**
106 * @internal
107 *
108 * Class to implement all methods that are properties of the
109 * Date object
110 */
111class DateObjectFuncImp : public InternalFunctionImp {
112public:
113 DateObjectFuncImp(ExecState *, FunctionPrototype *, int i, int len, const Identifier& );
114
115 virtual JSValue *callAsFunction(ExecState *, JSObject *thisObj, const List &args);
116
117 enum { Parse, UTC, Now };
118
119private:
120 int id;
121};
122
123// some constants
124const double hoursPerDay = 24;
125const double minutesPerHour = 60;
126const double secondsPerMinute = 60;
127const double msPerSecond = 1000;
128const double msPerMinute = 60 * 1000;
129const double msPerHour = 60 * 60 * 1000;
130const double msPerDay = 24 * 60 * 60 * 1000;
131
132static const char * const weekdayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
133static const char * const monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
134
135static double makeTime(tm *, double ms, bool utc);
136static double parseDate(const UString &);
137static double timeClip(double);
138static void millisecondsToTM(double milli, bool utc, tm *t);
139
140#if PLATFORM(MAC)
141
142static CFDateFormatterStyle styleFromArgString(const UString& string, CFDateFormatterStyle defaultStyle)
143{
144 if (string == "short")
145 return kCFDateFormatterShortStyle;
146 if (string == "medium")
147 return kCFDateFormatterMediumStyle;
148 if (string == "long")
149 return kCFDateFormatterLongStyle;
150 if (string == "full")
151 return kCFDateFormatterFullStyle;
152 return defaultStyle;
153}
154
155static UString formatLocaleDate(ExecState *exec, double time, bool includeDate, bool includeTime, const List &args)
156{
157 CFDateFormatterStyle dateStyle = (includeDate ? kCFDateFormatterLongStyle : kCFDateFormatterNoStyle);
158 CFDateFormatterStyle timeStyle = (includeTime ? kCFDateFormatterLongStyle : kCFDateFormatterNoStyle);
159
160 bool useCustomFormat = false;
161 UString customFormatString;
162
163 JSValue* arg0 = args[0];
164 JSValue* arg1 = args[1];
165 UString arg0String = arg0->toString(exec);
166 if (arg0String == "custom" && !arg1->isUndefined()) {
167 useCustomFormat = true;
168 customFormatString = arg1->toString(exec);
169 } else if (includeDate && includeTime && !arg1->isUndefined()) {
170 dateStyle = styleFromArgString(arg0String, dateStyle);
171 timeStyle = styleFromArgString(arg1->toString(exec), timeStyle);
172 } else if (includeDate && !arg0->isUndefined()) {
173 dateStyle = styleFromArgString(arg0String, dateStyle);
174 } else if (includeTime && !arg0->isUndefined()) {
175 timeStyle = styleFromArgString(arg0String, timeStyle);
176 }
177
178 CFLocaleRef locale = CFLocaleCopyCurrent();
179 CFDateFormatterRef formatter = CFDateFormatterCreate(0, locale, dateStyle, timeStyle);
180 CFRelease(locale);
181
182 if (useCustomFormat) {
183 CFStringRef customFormatCFString = CFStringCreateWithCharacters(0, (UniChar *)customFormatString.data(), customFormatString.size());
184 CFDateFormatterSetFormat(formatter, customFormatCFString);
185 CFRelease(customFormatCFString);
186 }
187
188 CFStringRef string = CFDateFormatterCreateStringWithAbsoluteTime(0, formatter, time - kCFAbsoluteTimeIntervalSince1970);
189
190 CFRelease(formatter);
191
192 // We truncate the string returned from CFDateFormatter if it's absurdly long (> 200 characters).
193 // That's not great error handling, but it just won't happen so it doesn't matter.
194 UChar buffer[200];
195 const size_t bufferLength = sizeof(buffer) / sizeof(buffer[0]);
196 size_t length = CFStringGetLength(string);
197 assert(length <= bufferLength);
198 if (length > bufferLength)
199 length = bufferLength;
200 CFStringGetCharacters(string, CFRangeMake(0, length), reinterpret_cast<UniChar *>(buffer));
201
202 CFRelease(string);
203
204 return UString(buffer, length);
205}
206
207#endif // PLATFORM(MAC)
208
209static UString formatDate(const tm &t)
210{
211 char buffer[100];
212 int len = snprintf(buffer, sizeof(buffer), "%s %s %02d %04d",
213 weekdayName[(t.tm_wday + 6) % 7],
214 monthName[t.tm_mon], t.tm_mday, t.tm_year + 1900);
215 return UString(buffer, len);
216}
217
218static UString formatDateUTCVariant(const tm &t)
219{
220 char buffer[100];
221 int len = snprintf(buffer, sizeof(buffer), "%s, %02d %s %04d",
222 weekdayName[(t.tm_wday + 6) % 7],
223 t.tm_mday, monthName[t.tm_mon], t.tm_year + 1900);
224 return UString(buffer, len);
225}
226
227static UString formatDateISOVariant(const tm &t, bool utc, double absoluteMS)
228{
229 char buffer[100];
230 // YYYY-MM-DD
231 int len;
232 if (utc) {
233 len = snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d",
234 t.tm_year + 1900, t.tm_mon+1, t.tm_mday);
235 } else {
236 int offset = gmtoffset(t);
237 tm t_fixed;
238 millisecondsToTM(absoluteMS - offset*1000, true, &t_fixed);
239 len = snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d",
240 t_fixed.tm_year + 1900, t_fixed.tm_mon+1, t_fixed.tm_mday);
241 }
242 return UString(buffer, len);
243}
244
245static UString formatTime(const tm &t, bool utc)
246{
247 char buffer[100];
248 int len;
249 if (utc) {
250 // FIXME: why not on windows?
251#if !PLATFORM(WIN_OS)
252 ASSERT(gmtoffset(t) == 0);
253#endif
254 len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT", t.tm_hour, t.tm_min, t.tm_sec);
255 } else {
256 int offset = abs(gmtoffset(t));
257 len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%c%02d%02d",
258 t.tm_hour, t.tm_min, t.tm_sec,
259 gmtoffset(t) < 0 ? '-' : '+', offset / (60*60), (offset / 60) % 60);
260 }
261 return UString(buffer, len);
262}
263
264static UString formatTimeISOVariant(const tm &t, bool utc, double absoluteMS, double ms)
265{
266 char buffer[100];
267 // HH:mm:ss.sss
268 int len;
269 if (utc) {
270 len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%03d",
271 t.tm_hour, t.tm_min, t.tm_sec, int(ms));
272 } else {
273 int offset = gmtoffset(t);
274 tm t_fixed;
275 millisecondsToTM(absoluteMS - offset*1000, true, &t_fixed);
276 len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%03d",
277 t_fixed.tm_hour, t_fixed.tm_min, t_fixed.tm_sec, int(ms));
278 }
279 return UString(buffer, len);
280}
281
282static int day(double t)
283{
284 return int(floor(t / msPerDay));
285}
286
287static double dayFromYear(int year)
288{
289 return 365.0 * (year - 1970)
290 + floor((year - 1969) / 4.0)
291 - floor((year - 1901) / 100.0)
292 + floor((year - 1601) / 400.0);
293}
294
295// based on the rule for whether it's a leap year or not
296static int daysInYear(int year)
297{
298 if (year % 4 != 0)
299 return 365;
300 if (year % 400 == 0)
301 return 366;
302 if (year % 100 == 0)
303 return 365;
304 return 366;
305}
306
307// time value of the start of a year
308static double timeFromYear(int year)
309{
310 return msPerDay * dayFromYear(year);
311}
312
313// year determined by time value
314static int yearFromTime(double t)
315{
316 // ### there must be an easier way
317
318 // initial guess
319 int y = 1970 + int(t / (365.25 * msPerDay));
320
321 // adjustment
322 if (timeFromYear(y) > t) {
323 do
324 --y;
325 while (timeFromYear(y) > t);
326 } else {
327 while (timeFromYear(y + 1) < t)
328 ++y;
329 }
330
331 return y;
332}
333
334// 0: Sunday, 1: Monday, etc.
335static int weekDay(double t)
336{
337 int wd = (day(t) + 4) % 7;
338 if (wd < 0)
339 wd += 7;
340 return wd;
341}
342
343// Converts a list of arguments sent to a Date member function into milliseconds, updating
344// ms (representing milliseconds) and t (representing the rest of the date structure) appropriately.
345//
346// Format of member function: f([hour,] [min,] [sec,] [ms])
347static double setTimeFields(ExecState* exec, const List& args, int id, double ms, tm* t)
348{
349 assert(DateProtoFunc::SetSeconds - DateProtoFunc::SetMilliSeconds + 1 == 2);
350 assert(DateProtoFunc::SetMinutes - DateProtoFunc::SetMilliSeconds + 1 == 3);
351 assert(DateProtoFunc::SetHours - DateProtoFunc::SetMilliSeconds + 1 == 4);
352
353 assert(id == DateProtoFunc::SetMilliSeconds || id == DateProtoFunc::SetSeconds ||
354 id == DateProtoFunc::SetMinutes || id == DateProtoFunc::SetHours);
355
356 int maxArgs = id - DateProtoFunc::SetMilliSeconds + 1;
357 double milliseconds = 0;
358 int idx = 0;
359 int numArgs = args.size();
360
361 // JS allows extra trailing arguments -- ignore them
362 if (numArgs > maxArgs)
363 numArgs = maxArgs;
364
365 // hours
366 if (maxArgs >= 4 && idx < numArgs) {
367 t->tm_hour = 0;
368 milliseconds += args[idx++]->toInt32(exec) * msPerHour;
369 }
370
371 // minutes
372 if (maxArgs >= 3 && idx < numArgs) {
373 t->tm_min = 0;
374 milliseconds += args[idx++]->toInt32(exec) * msPerMinute;
375 }
376
377 // seconds
378 if (maxArgs >= 2 && idx < numArgs) {
379 t->tm_sec = 0;
380 milliseconds += args[idx++]->toInt32(exec) * msPerSecond;
381 }
382
383 // milliseconds
384 if (idx < numArgs) {
385 milliseconds += roundValue(exec, args[idx]);
386 } else {
387 milliseconds += ms;
388 }
389
390 return milliseconds;
391}
392
393// Converts a list of arguments sent to a Date member function into years, months, and milliseconds, updating
394// ms (representing milliseconds) and t (representing the rest of the date structure) appropriately.
395//
396// Format of member function: f([years,] [months,] [days])
397static double setDateFields(ExecState* exec, const List& args, int id, double ms, tm* t)
398{
399 assert(DateProtoFunc::SetMonth - DateProtoFunc::SetDate + 1 == 2);
400 assert(DateProtoFunc::SetFullYear - DateProtoFunc::SetDate + 1 == 3);
401
402 assert(id == DateProtoFunc::SetDate || id == DateProtoFunc::SetMonth || id == DateProtoFunc::SetFullYear);
403
404 int maxArgs = id - DateProtoFunc::SetDate + 1;
405 int idx = 0;
406 int numArgs = args.size();
407
408 // JS allows extra trailing arguments -- ignore them
409 if (numArgs > maxArgs)
410 numArgs = maxArgs;
411
412 // years
413 if (maxArgs >= 3 && idx < numArgs)
414 t->tm_year = args[idx++]->toInt32(exec) - 1900;
415
416 // months
417 if (maxArgs >= 2 && idx < numArgs)
418 t->tm_mon = args[idx++]->toInt32(exec);
419
420 // days
421 if (idx < numArgs) {
422 t->tm_mday = 0;
423 ms += args[idx]->toInt32(exec) * msPerDay;
424 }
425
426 return ms;
427}
428
429// ------------------------------ DateInstance ------------------------------
430
431const ClassInfo DateInstance::info = {"Date", 0, 0, 0};
432
433DateInstance::DateInstance(JSObject *proto)
434 : JSWrapperObject(proto)
435{
436}
437
438JSObject* DateInstance::valueClone(Interpreter* targetCtx) const
439{
440 DateInstance* copy = new DateInstance(targetCtx->builtinDatePrototype());
441 copy->setInternalValue(internalValue());
442 return copy;
443}
444
445bool DateInstance::getTime(tm &t, int &offset) const
446{
447 double milli = internalValue()->getNumber();
448 if (isNaN(milli))
449 return false;
450
451 millisecondsToTM(milli, false, &t);
452 offset = gmtoffset(t);
453 return true;
454}
455
456bool DateInstance::getUTCTime(tm &t) const
457{
458 double milli = internalValue()->getNumber();
459 if (isNaN(milli))
460 return false;
461
462 millisecondsToTM(milli, true, &t);
463 return true;
464}
465
466bool DateInstance::getTime(double &milli, int &offset) const
467{
468 milli = internalValue()->getNumber();
469 if (isNaN(milli))
470 return false;
471
472 tm t;
473 millisecondsToTM(milli, false, &t);
474 offset = gmtoffset(t);
475 return true;
476}
477
478bool DateInstance::getUTCTime(double &milli) const
479{
480 milli = internalValue()->getNumber();
481 if (isNaN(milli))
482 return false;
483
484 return true;
485}
486
487static inline bool isTime_tSigned()
488{
489 time_t minusOne = (time_t)(-1);
490 return minusOne < 0;
491}
492
493static void millisecondsToTM(double milli, bool utc, tm *t)
494{
495 // check whether time value is outside time_t's usual range
496 // make the necessary transformations if necessary
497 static bool time_tIsSigned = isTime_tSigned();
498#if PLATFORM(WIN_OS)
499 static double time_tMin = 0; //on windows localtime/gmtime returns NULL for pre 1970 dates
500#else
501 static double time_tMin = (time_tIsSigned ? - (double)(1ULL << (8 * sizeof(time_t) - 1)) : 0);
502#endif
503 static double time_tMax = (time_tIsSigned ? (1ULL << (8 * sizeof(time_t) - 1)) - 1 : 2 * (double)(1ULL << (8 * sizeof(time_t) - 1)) - 1);
504 int realYearOffset = 0;
505 double milliOffset = 0.0;
506 double secs = floor(milli / msPerSecond);
507
508 if (secs < time_tMin || secs > time_tMax) {
509 // ### ugly and probably not very precise
510 int realYear = yearFromTime(milli);
511 int base = daysInYear(realYear) == 365 ? 2001 : 2000;
512 milliOffset = timeFromYear(base) - timeFromYear(realYear);
513 milli += milliOffset;
514 realYearOffset = realYear - base;
515 }
516
517 time_t tv = (time_t) floor(milli / msPerSecond);
518
519 *t = *(utc ? gmtime(&tv) : localtime(&tv));
520 // We had an out of range year. Restore the year (plus/minus offset
521 // found by calculating tm_year) and fix the week day calculation.
522 if (realYearOffset != 0) {
523 t->tm_year += realYearOffset;
524 milli -= milliOffset;
525 // Do our own weekday calculation. Use time zone offset to handle local time.
526 double m = milli;
527 if (!utc)
528 m += gmtoffset(*t) * msPerSecond;
529 t->tm_wday = weekDay(m);
530 }
531}
532
533static bool isNaNorInf(double value)
534{
535 return isNaN(value) || isInf(value);
536}
537
538// ------------------------------ DatePrototype -----------------------------
539
540const ClassInfo DatePrototype::info = {"Date", &DateInstance::info, &dateTable, 0};
541
542/* Source for date_object.lut.h
543 We use a negative ID to denote the "UTC" variant.
544@begin dateTable 61
545 toString DateProtoFunc::ToString DontEnum|Function 0
546 toUTCString -DateProtoFunc::ToUTCString DontEnum|Function 0
547 toDateString DateProtoFunc::ToDateString DontEnum|Function 0
548 toTimeString DateProtoFunc::ToTimeString DontEnum|Function 0
549 toISOString DateProtoFunc::ToISOString DontEnum|Function 0
550 toJSON DateProtoFunc::ToJSON DontEnum|Function 1
551 toLocaleString DateProtoFunc::ToLocaleString DontEnum|Function 0
552 toLocaleDateString DateProtoFunc::ToLocaleDateString DontEnum|Function 0
553 toLocaleTimeString DateProtoFunc::ToLocaleTimeString DontEnum|Function 0
554 valueOf DateProtoFunc::ValueOf DontEnum|Function 0
555 getTime DateProtoFunc::GetTime DontEnum|Function 0
556 getFullYear DateProtoFunc::GetFullYear DontEnum|Function 0
557 getUTCFullYear -DateProtoFunc::GetFullYear DontEnum|Function 0
558 toGMTString -DateProtoFunc::ToGMTString DontEnum|Function 0
559 getMonth DateProtoFunc::GetMonth DontEnum|Function 0
560 getUTCMonth -DateProtoFunc::GetMonth DontEnum|Function 0
561 getDate DateProtoFunc::GetDate DontEnum|Function 0
562 getUTCDate -DateProtoFunc::GetDate DontEnum|Function 0
563 getDay DateProtoFunc::GetDay DontEnum|Function 0
564 getUTCDay -DateProtoFunc::GetDay DontEnum|Function 0
565 getHours DateProtoFunc::GetHours DontEnum|Function 0
566 getUTCHours -DateProtoFunc::GetHours DontEnum|Function 0
567 getMinutes DateProtoFunc::GetMinutes DontEnum|Function 0
568 getUTCMinutes -DateProtoFunc::GetMinutes DontEnum|Function 0
569 getSeconds DateProtoFunc::GetSeconds DontEnum|Function 0
570 getUTCSeconds -DateProtoFunc::GetSeconds DontEnum|Function 0
571 getMilliseconds DateProtoFunc::GetMilliSeconds DontEnum|Function 0
572 getUTCMilliseconds -DateProtoFunc::GetMilliSeconds DontEnum|Function 0
573 getTimezoneOffset DateProtoFunc::GetTimezoneOffset DontEnum|Function 0
574 setTime DateProtoFunc::SetTime DontEnum|Function 1
575 setMilliseconds DateProtoFunc::SetMilliSeconds DontEnum|Function 1
576 setUTCMilliseconds -DateProtoFunc::SetMilliSeconds DontEnum|Function 1
577 setSeconds DateProtoFunc::SetSeconds DontEnum|Function 2
578 setUTCSeconds -DateProtoFunc::SetSeconds DontEnum|Function 2
579 setMinutes DateProtoFunc::SetMinutes DontEnum|Function 3
580 setUTCMinutes -DateProtoFunc::SetMinutes DontEnum|Function 3
581 setHours DateProtoFunc::SetHours DontEnum|Function 4
582 setUTCHours -DateProtoFunc::SetHours DontEnum|Function 4
583 setDate DateProtoFunc::SetDate DontEnum|Function 1
584 setUTCDate -DateProtoFunc::SetDate DontEnum|Function 1
585 setMonth DateProtoFunc::SetMonth DontEnum|Function 2
586 setUTCMonth -DateProtoFunc::SetMonth DontEnum|Function 2
587 setFullYear DateProtoFunc::SetFullYear DontEnum|Function 3
588 setUTCFullYear -DateProtoFunc::SetFullYear DontEnum|Function 3
589 setYear DateProtoFunc::SetYear DontEnum|Function 1
590 getYear DateProtoFunc::GetYear DontEnum|Function 0
591@end
592*/
593// ECMA 15.9.4
594
595DatePrototype::DatePrototype(ExecState *, ObjectPrototype *objectProto)
596 : DateInstance(objectProto)
597{
598 setInternalValue(jsNaN());
599 // The constructor will be added later, after DateObjectImp has been built.
600}
601
602bool DatePrototype::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
603{
604 return getStaticFunctionSlot<DateProtoFunc, JSObject>(exec, &dateTable, this, propertyName, slot);
605}
606
607// ------------------------------ DateProtoFunc -----------------------------
608
609DateProtoFunc::DateProtoFunc(ExecState *exec, int i, int len, const Identifier& name)
610 : InternalFunctionImp(static_cast<FunctionPrototype*>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
611 , id(abs(i))
612 , utc(i < 0)
613 // We use a negative ID to denote the "UTC" variant.
614{
615 putDirect(exec->propertyNames().length, len, DontDelete|ReadOnly|DontEnum);
616}
617
618JSValue *DateProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
619{
620 if (id == ToJSON) {
621 JSValue* tv = thisObj->toPrimitive(exec, NumberType);
622 if (tv->isNumber()) {
623 double ms = tv->toNumber(exec);
624 if (isNaN(ms))
625 return jsNull();
626 }
627
628 JSValue *toISO = thisObj->get(exec, exec->propertyNames().toISOString);
629 if (!toISO->implementsCall())
630 return throwError(exec, TypeError, "toISOString is not callable");
631 JSObject* toISOobj = toISO->toObject(exec);
632 if (!toISOobj)
633 return throwError(exec, TypeError, "toISOString is not callable");
634 return toISOobj->call(exec, thisObj, List::empty());
635 }
636
637 if (!thisObj->inherits(&DateInstance::info))
638 return throwError(exec, TypeError);
639
640 DateInstance* thisDateObj = static_cast<DateInstance*>(thisObj);
641
642 JSValue *result = 0;
643#if !PLATFORM(MAC)
644 const int bufsize=100;
645 char timebuffer[bufsize];
646 CString oldlocale = setlocale(LC_TIME, 0);
647 if (!oldlocale.size())
648 oldlocale = setlocale(LC_ALL, 0);
649 // FIXME: Where's the code to set the locale back to oldlocale?
650#endif
651 JSValue *v = thisDateObj->internalValue();
652 double milli = v->toNumber(exec);
653 if (isNaN(milli)) {
654 switch (id) {
655 case ToString:
656 case ToDateString:
657 case ToTimeString:
658 case ToGMTString:
659 case ToUTCString:
660 case ToLocaleString:
661 case ToLocaleDateString:
662 case ToLocaleTimeString:
663 return jsString("Invalid Date", 12);
664 case ValueOf:
665 case GetTime:
666 case GetYear:
667 case GetFullYear:
668 case GetMonth:
669 case GetDate:
670 case GetDay:
671 case GetHours:
672 case GetMinutes:
673 case GetSeconds:
674 case GetMilliSeconds:
675 case GetTimezoneOffset:
676 case SetMilliSeconds:
677 case SetSeconds:
678 case SetMinutes:
679 case SetHours:
680 case SetDate:
681 case SetMonth:
682 case SetFullYear:
683 return jsNaN();
684 case ToISOString:
685 return throwError(exec, RangeError, "Invalid Date");
686 }
687 }
688
689 if (id == SetTime) {
690 double milli = roundValue(exec, args[0]);
691 result = jsNumber(timeClip(milli));
692 thisDateObj->setInternalValue(result);
693 return result;
694 }
695
696 double secs = floor(milli / msPerSecond);
697 double ms = milli - secs * msPerSecond;
698
699 tm t;
700 millisecondsToTM(milli, utc, &t);
701
702 switch (id) {
703 case ToString:
704 return jsString(formatDate(t).append(' ').append(formatTime(t, utc)));
705
706 case ToDateString:
707 return jsString(formatDate(t));
708
709 case ToTimeString:
710 return jsString(formatTime(t, utc));
711
712 case ToGMTString:
713 case ToUTCString:
714 return jsString(formatDateUTCVariant(t).append(' ').append(formatTime(t, utc)));
715 case ToISOString:
716 return jsString(formatDateISOVariant(t, utc, milli).append('T').append(formatTimeISOVariant(t, utc, milli, ms)).append('Z'));
717
718#if PLATFORM(MAC)
719 case ToLocaleString:
720 return jsString(formatLocaleDate(exec, secs, true, true, args));
721
722 case ToLocaleDateString:
723 return jsString(formatLocaleDate(exec, secs, true, false, args));
724
725 case ToLocaleTimeString:
726 return jsString(formatLocaleDate(exec, secs, false, true, args));
727
728#else
729 case ToLocaleString:
730 return jsString(timebuffer, strftime(timebuffer, bufsize, "%c", &t));
731
732 case ToLocaleDateString:
733 return jsString(timebuffer, strftime(timebuffer, bufsize, "%x", &t));
734
735 case ToLocaleTimeString:
736 return jsString(timebuffer, strftime(timebuffer, bufsize, "%X", &t));
737
738#endif
739 case ValueOf:
740 case GetTime:
741 return jsNumber(milli);
742 case GetYear:
743 // IE returns the full year even in getYear.
744 if (exec->dynamicInterpreter()->compatMode() == Interpreter::IECompat)
745 return jsNumber(1900 + t.tm_year);
746 return jsNumber(t.tm_year);
747 case GetFullYear:
748 return jsNumber(1900 + t.tm_year);
749 case GetMonth:
750 return jsNumber(t.tm_mon);
751 case GetDate:
752 return jsNumber(t.tm_mday);
753 case GetDay:
754 return jsNumber(t.tm_wday);
755 case GetHours:
756 return jsNumber(t.tm_hour);
757 case GetMinutes:
758 return jsNumber(t.tm_min);
759 case GetSeconds:
760 return jsNumber(t.tm_sec);
761 case GetMilliSeconds:
762 return jsNumber(ms);
763 case GetTimezoneOffset:
764 return jsNumber(-gmtoffset(t) / 60);
765
766 case SetMilliSeconds:
767 case SetSeconds:
768 case SetMinutes:
769 case SetHours:
770 ms = args.size() > 0 ? setTimeFields(exec, args, id, ms, &t) : NaN;
771 break;
772
773 case SetDate:
774 case SetMonth:
775 case SetFullYear:
776 ms = args.size() > 0 ? setDateFields(exec, args, id, ms, &t) : NaN;
777 break;
778
779 case SetYear: {
780 int32_t year = args[0]->toInt32(exec);
781 t.tm_year = (year > 99 || year < 0) ? year - 1900 : year;
782 break;
783 }
784 }
785
786 if (id == SetYear || id == SetMilliSeconds || id == SetSeconds ||
787 id == SetMinutes || id == SetHours || id == SetDate ||
788 id == SetMonth || id == SetFullYear ) {
789 result = jsNumber(isnan(ms) ? ms : timeClip(makeTime(&t, ms, utc)));
790 thisDateObj->setInternalValue(result);
791 }
792
793 return result;
794}
795
796// ------------------------------ DateObjectImp --------------------------------
797
798DateObjectImp::DateObjectImp(ExecState *exec,
799 FunctionPrototype *funcProto,
800 DatePrototype *dateProto)
801 : InternalFunctionImp(funcProto)
802{
803 // ECMA 15.9.4.1 Date.prototype
804 static const Identifier* parsePropertyName = new Identifier("parse");
805 static const Identifier* UTCPropertyName = new Identifier("UTC");
806 static const Identifier* nowPropertyName = new Identifier("now");
807
808 putDirect(exec->propertyNames().prototype, dateProto, DontEnum|DontDelete|ReadOnly);
809 putDirectFunction(new DateObjectFuncImp(exec, funcProto, DateObjectFuncImp::Parse, 1, *parsePropertyName), DontEnum);
810 putDirectFunction(new DateObjectFuncImp(exec, funcProto, DateObjectFuncImp::UTC, 7, *UTCPropertyName), DontEnum);
811 putDirectFunction(new DateObjectFuncImp(exec, funcProto, DateObjectFuncImp::Now, 0, *nowPropertyName), DontEnum);
812
813 // no. of arguments for constructor
814 putDirect(exec->propertyNames().length, 7, ReadOnly|DontDelete|DontEnum);
815}
816
817bool DateObjectImp::implementsConstruct() const
818{
819 return true;
820}
821
822static double getCurrentUTCTime()
823{
824#if PLATFORM(WIN_OS)
825#if COMPILER(BORLAND)
826 struct timeb timebuffer;
827 ftime(&timebuffer);
828#else
829 struct _timeb timebuffer;
830 _ftime(&timebuffer);
831#endif
832 double utc = timebuffer.time * msPerSecond + timebuffer.millitm;
833#else
834 struct timeval tv;
835 gettimeofday(&tv, 0);
836 double utc = floor(tv.tv_sec * msPerSecond + tv.tv_usec / 1000);
837#endif
838 return utc;
839}
840
841static double makeTimeFromList(ExecState *exec, const List &args, bool utc)
842{
843 const int numArgs = args.size();
844 if (isNaNorInf(args[0]->toNumber(exec))
845 || isNaNorInf(args[1]->toNumber(exec))
846 || (numArgs >= 3 && isNaNorInf(args[2]->toNumber(exec)))
847 || (numArgs >= 4 && isNaNorInf(args[3]->toNumber(exec)))
848 || (numArgs >= 5 && isNaNorInf(args[4]->toNumber(exec)))
849 || (numArgs >= 6 && isNaNorInf(args[5]->toNumber(exec)))
850 || (numArgs >= 7 && isNaNorInf(args[6]->toNumber(exec)))) {
851 return NaN;
852 }
853
854 tm t;
855 memset(&t, 0, sizeof(t));
856 int year = args[0]->toInt32(exec);
857 t.tm_year = (year >= 0 && year <= 99) ? year : year - 1900;
858 t.tm_mon = args[1]->toInt32(exec);
859 t.tm_mday = (numArgs >= 3) ? args[2]->toInt32(exec) : 1;
860 t.tm_hour = (numArgs >= 4) ? args[3]->toInt32(exec) : 0;
861 t.tm_min = (numArgs >= 5) ? args[4]->toInt32(exec) : 0;
862 t.tm_sec = (numArgs >= 6) ? args[5]->toInt32(exec) : 0;
863 if (!utc)
864 t.tm_isdst = -1;
865 double ms = (numArgs >= 7) ? roundValue(exec, args[6]) : 0;
866 return makeTime(&t, ms, utc);
867}
868
869// ECMA 15.9.3
870JSObject *DateObjectImp::construct(ExecState *exec, const List &args)
871{
872 int numArgs = args.size();
873 double value;
874
875 if (numArgs == 0) { // new Date() ECMA 15.9.3.3
876 value = getCurrentUTCTime();
877 } else if (numArgs == 1) {
878 JSValue* arg0 = args[0];
879 if (arg0->isObject(&DateInstance::info))
880 value = static_cast<DateInstance*>(arg0)->internalValue()->toNumber(exec);
881 else {
882 JSValue* primitive = arg0->toPrimitive(exec);
883 if (primitive->isString())
884 value = parseDate(primitive->getString());
885 else
886 value = primitive->toNumber(exec);
887 }
888 } else {
889 value = makeTimeFromList(exec, args, false);
890 }
891
892 DateInstance *ret = new DateInstance(exec->lexicalInterpreter()->builtinDatePrototype());
893 ret->setInternalValue(jsNumber(timeClip(value)));
894 return ret;
895}
896
897// ECMA 15.9.2
898JSValue *DateObjectImp::callAsFunction(ExecState * /*exec*/, JSObject * /*thisObj*/, const List &/*args*/)
899{
900 time_t t = time(0);
901 tm ts = *localtime(&t);
902 return jsString(formatDate(ts).append(' ').append(formatTime(ts, false)));
903}
904
905// ------------------------------ DateObjectFuncImp ----------------------------
906
907DateObjectFuncImp::DateObjectFuncImp(ExecState* exec, FunctionPrototype* funcProto, int i, int len, const Identifier& name)
908 : InternalFunctionImp(funcProto, name), id(i)
909{
910 putDirect(exec->propertyNames().length, len, DontDelete|ReadOnly|DontEnum);
911}
912
913// ECMA 15.9.4.2 - 3
914JSValue *DateObjectFuncImp::callAsFunction(ExecState* exec, JSObject*, const List& args)
915{
916 if (id == Parse) {
917 return jsNumber(parseDate(args[0]->toString(exec)));
918 } else if (id == Now) {
919 return jsNumber(getCurrentUTCTime());
920 } else { // UTC
921 return jsNumber(makeTimeFromList(exec, args, true));
922 }
923}
924
925// -----------------------------------------------------------------------------
926
927// Code originally from krfcdate.cpp, but we don't want to use kdecore, and we want double range.
928
929static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, double second)
930{
931 // in which case is the floor() needed? breaks day value of
932 // "new Date('Thu Nov 5 2065 18:15:30 GMT+0500')"
933#if 0
934 double days = (day - 32075)
935 + floor(1461 * (year + 4800.0 + (mon - 14) / 12) / 4)
936 + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12
937 - floor(3 * ((year + 4900.0 + (mon - 14) / 12) / 100) / 4)
938 - 2440588;
939#else
940 double days = (day - 32075)
941 + 1461 * (year + 4800 + (mon - 14) / 12) / 4
942 + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12
943 - 3 * ((year + 4900 + (mon - 14) / 12) / 100) / 4
944 - 2440588;
945#endif
946 return ((days * hoursPerDay + hour) * minutesPerHour + minute) * secondsPerMinute + second;
947}
948
949// We follow the recommendation of RFC 2822 to consider all
950// obsolete time zones not listed here equivalent to "-0000".
951static const struct KnownZone {
952#if !PLATFORM(WIN_OS)
953 const
954#endif
955 char tzName[4];
956 int tzOffset;
957} known_zones[] = {
958 { "UT", 0 },
959 { "GMT", 0 },
960 { "EST", -300 },
961 { "EDT", -240 },
962 { "CST", -360 },
963 { "CDT", -300 },
964 { "MST", -420 },
965 { "MDT", -360 },
966 { "PST", -480 },
967 { "PDT", -420 }
968};
969
970#if PLATFORM(WIN_OS)
971void FileTimeToUnixTime(LPFILETIME pft, double* pt)
972{
973 ULARGE_INTEGER ull;
974 ull.LowPart = pft->dwLowDateTime;
975 ull.HighPart = pft->dwHighDateTime;
976 *pt = (double)(ull.QuadPart / 10000000ULL) - 11644473600ULL;
977}
978
979void SystemTimeToUnixTime(LPSYSTEMTIME pst, double* pt)
980{
981 FILETIME ft;
982 SystemTimeToFileTime(pst, &ft);
983 FileTimeToUnixTime(&ft, pt);
984}
985#endif
986
987static double makeTime(tm *t, double ms, bool utc)
988{
989 int utcOffset;
990 if (utc) {
991 time_t zero = 0;
992#if PLATFORM(WIN_OS)
993 // FIXME: not thread safe
994 (void)localtime(&zero);
995#if COMPILER(BORLAND) || COMPILER(CYGWIN)
996 utcOffset = - _timezone;
997#else
998 utcOffset = - timezone;
999#endif
1000 t->tm_isdst = 0;
1001#elif PLATFORM(DARWIN)
1002 utcOffset = 0;
1003 t->tm_isdst = 0;
1004#else
1005 tm t3;
1006 localtime_r(&zero, &t3);
1007 utcOffset = gmtoffset(t3);
1008 t->tm_isdst = t3.tm_isdst;
1009#endif
1010 } else {
1011 utcOffset = 0;
1012 t->tm_isdst = -1;
1013 }
1014
1015#if !PLATFORM(WIN_OS)
1016 double yearOffset = 0.0;
1017 if (t->tm_year < (1971 - 1900) || t->tm_year > (2037 - 1900)) {
1018 // we'll fool mktime() into believing that this year is within
1019 // its normal, portable range (1970-2038) by setting tm_year to
1020 // 2000 or 2001 and adding the difference in milliseconds later.
1021 // choice between offset will depend on whether the year is a
1022 // leap year or not.
1023 int y = t->tm_year + 1900;
1024 int baseYear = daysInYear(y) == 365 ? 2001 : 2000;
1025 double baseTime = timeFromYear(baseYear);
1026 yearOffset = timeFromYear(y) - baseTime;
1027 t->tm_year = baseYear - 1900;
1028 }
1029
1030 // Determine whether DST is in effect. mktime() can't do this for us because
1031 // it doesn't know about ms and yearOffset.
1032 // NOTE: Casting values of large magnitude to time_t (long) will
1033 // produce incorrect results, but there's no other option when calling localtime_r().
1034 if (!utc) {
1035 time_t tval = mktime(t) + (time_t)((ms + yearOffset) / 1000);
1036 tm t3 = *localtime(&tval);
1037 t->tm_isdst = t3.tm_isdst;
1038 }
1039
1040 return (mktime(t) + utcOffset) * msPerSecond + ms + yearOffset;
1041#else
1042 SYSTEMTIME st, dt;
1043 double tval;
1044
1045 st.wYear = 1900 + t->tm_year;
1046 st.wMonth = t->tm_mon + 1;
1047 st.wDayOfWeek = t->tm_wday;
1048 st.wDay = t->tm_mday;
1049 st.wHour = t->tm_hour;
1050 st.wMinute = t->tm_min;
1051 st.wSecond = t->tm_sec;
1052 st.wMilliseconds = 0;
1053
1054 TzSpecificLocalTimeToSystemTime(0, &st, &dt);
1055 SystemTimeToUnixTime(&dt, &tval);
1056
1057 return (tval + utcOffset) * msPerSecond + ms;
1058#endif
1059}
1060
1061inline static bool isSpaceLike(char c)
1062{
1063 return isASCIISpace(c) || c == ',' || c == ':' || c == '-';
1064}
1065
1066static const char* skipSpacesAndComments(const char* s)
1067{
1068 int nesting = 0;
1069 char ch;
1070 while ((ch = *s)) {
1071 // interpret - before a number as a sign rather than a comment char
1072 if (ch == '-' && isASCIIDigit(*(s+1)))
1073 break;
1074 if (!isSpaceLike(ch)) {
1075 if (ch == '(')
1076 nesting++;
1077 else if (ch == ')' && nesting > 0)
1078 nesting--;
1079 else if (nesting == 0)
1080 break;
1081 }
1082 s++;
1083 }
1084 return s;
1085}
1086
1087// returns 0-11 (Jan-Dec); -1 on failure
1088static int findMonth(const char *monthStr)
1089{
1090 assert(monthStr);
1091 char needle[4];
1092 for (int i = 0; i < 3; ++i) {
1093 if (!*monthStr)
1094 return -1;
1095 needle[i] = toASCIILower(*monthStr++);
1096 }
1097 needle[3] = '\0';
1098 const char *haystack = "janfebmaraprmayjunjulaugsepoctnovdec";
1099 const char *str = strstr(haystack, needle);
1100 if (str) {
1101 int position = str - haystack;
1102 if (position % 3 == 0)
1103 return position / 3;
1104 }
1105 return -1;
1106}
1107
1108static bool isTwoDigits (const char* str)
1109{
1110 return isASCIIDigit(str[0]) && isASCIIDigit(str[1]);
1111}
1112
1113static int twoDigit (const char* str)
1114{
1115 return (str[0] - '0') * 10 + str[1] - '0';
1116}
1117
1118static double parseDate(const UString &date)
1119{
1120 // This parses a date in the form:
1121 // Tuesday, 09-Nov-99 23:12:40 GMT
1122 // or
1123 // Sat, 01-Jan-2000 08:00:00 GMT
1124 // or
1125 // Sat, 01 Jan 2000 08:00:00 GMT
1126 // or
1127 // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822)
1128 // ### non RFC formats, added for Javascript:
1129 // [Wednesday] January 09 1999 23:12:40 GMT
1130 // [Wednesday] January 09 23:12:40 GMT 1999
1131 //
1132 // We ignore the weekday.
1133
1134 CString dateCString = date.UTF8String();
1135 const char *dateString = dateCString.c_str();
1136 if(!dateString)
1137 return NaN;
1138
1139 // Skip leading space
1140 dateString = skipSpacesAndComments(dateString);
1141
1142 // ISO 8601: "YYYY-MM-DD('T'|'t')hh:mm:ss[.S+]['Z']"
1143 // e.g. "2006-06-15T23:12:10.207830Z"
1144 if (isTwoDigits(dateString) &&
1145 isTwoDigits(dateString + 2) &&
1146 dateString[4] == '-' &&
1147 isTwoDigits(dateString + 5) &&
1148 dateString[7] == '-' &&
1149 isTwoDigits(dateString + 8))
1150 {
1151 int year = twoDigit(dateString) * 100 + twoDigit(dateString + 2);
1152 int month = twoDigit(dateString + 5) - 1;
1153 int day = twoDigit(dateString + 8);
1154 if (month > 11 || day < 1 || day > 31)
1155 return NaN;
1156 int hour = 0, minute = 0;
1157 double second = 0;
1158 dateString += 10;
1159 if ((dateString[0] | 0x20) == 't' &&
1160 isTwoDigits(dateString + 1) &&
1161 dateString[3] == ':' &&
1162 isTwoDigits(dateString + 4))
1163 {
1164 hour = twoDigit(dateString + 1);
1165 minute = twoDigit(dateString + 4);
1166 if (hour > 23 || minute > 59)
1167 return NaN;
1168 dateString += 6;
1169 if (dateString[0] == ':' &&
1170 isTwoDigits(dateString + 1))
1171 {
1172 second = twoDigit(dateString + 1);
1173 if (second > 59)
1174 return NaN;
1175 dateString += 3;
1176 if (dateString[0] == '.' &&
1177 isASCIIDigit(dateString[1]))
1178 {
1179 dateString++;
1180 double div = 10;
1181 do {
1182 second += (dateString[0] - '0') / div;
1183 div *= 10;
1184 } while (isASCIIDigit(*++dateString));
1185 }
1186 }
1187 }
1188
1189 if (dateString[0] == 'Z')
1190 {
1191 tm t;
1192 memset(&t, 0, sizeof(tm));
1193 int secs = int(second);
1194 t.tm_sec = secs;
1195 t.tm_min = minute;
1196 t.tm_hour = hour;
1197 t.tm_mday = day;
1198 t.tm_mon = month;
1199 t.tm_year = year - 1900;
1200 // t.tm_isdst = -1;
1201
1202 // Use our makeTime() rather than mktime() as the latter can't handle the full year range.
1203 return makeTime(&t, (second - secs) * 1000, true);
1204 }
1205
1206 int offset = 0;
1207 return (ymdhmsToSeconds(year, month + 1, day, hour, minute, second) - (offset * 60.0)) * msPerSecond;
1208 }
1209
1210 long month = -1;
1211 const char *wordStart = dateString;
1212 // Check contents of first words if not number
1213 while (*dateString && !isASCIIDigit(*dateString)) {
1214 if (isASCIISpace(*dateString) || *dateString == '(') {
1215 if (dateString - wordStart >= 3)
1216 month = findMonth(wordStart);
1217 dateString = skipSpacesAndComments(dateString);
1218 wordStart = dateString;
1219 } else
1220 dateString++;
1221 }
1222
1223 // Missing delimiter between month and day (like "January29")?
1224 if (month == -1 && wordStart != dateString)
1225 month = findMonth(wordStart);
1226
1227 dateString = skipSpacesAndComments(dateString);
1228
1229 if (!*dateString)
1230 return NaN;
1231
1232 // ' 09-Nov-99 23:12:40 GMT'
1233 char *newPosStr;
1234 errno = 0;
1235 long day = strtol(dateString, &newPosStr, 10);
1236 dateString = newPosStr;
1237
1238 if (errno || day < 0 || !*dateString)
1239 return NaN;
1240
1241 long year = 0;
1242 if (day > 31) {
1243 // ### where is the boundary and what happens below?
1244 if (*dateString != '/')
1245 return NaN;
1246 // looks like a YYYY/MM/DD date
1247 if (!*++dateString)
1248 return NaN;
1249 year = day;
1250 month = strtol(dateString, &newPosStr, 10) - 1;
1251 if (errno)
1252 return NaN;
1253 dateString = newPosStr;
1254 if (*dateString++ != '/' || !*dateString)
1255 return NaN;
1256 day = strtol(dateString, &newPosStr, 10);
1257 if (errno)
1258 return NaN;
1259 dateString = newPosStr;
1260 } else if (*dateString == '/' && month == -1) {
1261 dateString++;
1262 // This looks like a MM/DD/YYYY date, not an RFC date.
1263 month = day - 1; // 0-based
1264 day = strtol(dateString, &newPosStr, 10);
1265 if (errno)
1266 return NaN;
1267 dateString = newPosStr;
1268 if (*dateString == '/')
1269 dateString++;
1270 if (!*dateString)
1271 return NaN;
1272 } else {
1273 if (*dateString == '-')
1274 dateString++;
1275
1276 dateString = skipSpacesAndComments(dateString);
1277
1278 if (*dateString == ',')
1279 dateString++;
1280
1281 if (month == -1) { // not found yet
1282 month = findMonth(dateString);
1283 if (month == -1)
1284 return NaN;
1285
1286 while (*dateString && (*dateString != '-') && !isASCIISpace(*dateString))
1287 dateString++;
1288
1289 if (!*dateString)
1290 return NaN;
1291
1292 // '-99 23:12:40 GMT'
1293 if (*dateString != '-' && *dateString != '/' && !isASCIISpace(*dateString))
1294 return NaN;
1295 dateString++;
1296 }
1297
1298 if (month < 0 || month > 11)
1299 return NaN;
1300 }
1301
1302 // '99 23:12:40 GMT'
1303 if (year <= 0 && *dateString) {
1304 year = strtol(dateString, &newPosStr, 10);
1305 if (errno)
1306 return NaN;
1307 }
1308
1309 // Don't fail if the time is missing.
1310 long hour = 0;
1311 long minute = 0;
1312 long second = 0;
1313 if (!*newPosStr)
1314 dateString = newPosStr;
1315 else {
1316 // ' 23:12:40 GMT'
1317 if (*newPosStr == ':') {
1318 // There was no year; the number was the hour.
1319 year = -1;
1320 } else if (isSpaceLike(*newPosStr)) {
1321 // in the normal case (we parsed the year), advance to the next number
1322 dateString = skipSpacesAndComments(newPosStr + 1);
1323 } else {
1324 return NaN;
1325 }
1326
1327 hour = strtol(dateString, &newPosStr, 10);
1328 // Do not check for errno here since we want to continue
1329 // even if errno was set because we are still looking
1330 // for the timezone!
1331
1332 // Read a number? If not, this might be a timezone name.
1333 if (newPosStr != dateString) {
1334 dateString = newPosStr;
1335
1336 if (hour < 0 || hour > 23)
1337 return NaN;
1338
1339 if (!*dateString)
1340 return NaN;
1341
1342 // ':12:40 GMT'
1343 if (*dateString++ != ':')
1344 return NaN;
1345
1346 minute = strtol(dateString, &newPosStr, 10);
1347 if (errno)
1348 return NaN;
1349 dateString = newPosStr;
1350
1351 if (minute < 0 || minute > 59)
1352 return NaN;
1353
1354 // ':40 GMT'
1355 if (*dateString && *dateString != ':' && !isASCIISpace(*dateString))
1356 return NaN;
1357
1358 // seconds are optional in rfc822 + rfc2822
1359 if (*dateString ==':') {
1360 dateString++;
1361
1362 second = strtol(dateString, &newPosStr, 10);
1363 if (errno)
1364 return NaN;
1365 dateString = newPosStr;
1366
1367 if (second < 0 || second > 59)
1368 return NaN;
1369
1370 // disallow trailing colon seconds
1371 if (*dateString == ':')
1372 return NaN;
1373 }
1374
1375 dateString = skipSpacesAndComments(dateString);
1376
1377 if (strncasecmp(dateString, "AM", 2) == 0) {
1378 if (hour > 12)
1379 return NaN;
1380 if (hour == 12)
1381 hour = 0;
1382 dateString = skipSpacesAndComments(dateString + 2);
1383 } else if (strncasecmp(dateString, "PM", 2) == 0) {
1384 if (hour > 12)
1385 return NaN;
1386 if (hour != 12)
1387 hour += 12;
1388 dateString = skipSpacesAndComments(dateString + 2);
1389 }
1390 }
1391 }
1392
1393 bool haveTZ = false;
1394 int offset = 0;
1395
1396 // Don't fail if the time zone is missing.
1397 // Some websites omit the time zone (4275206).
1398 if (*dateString) {
1399 if (strncasecmp(dateString, "GMT", 3) == 0 ||
1400 strncasecmp(dateString, "UTC", 3) == 0) {
1401 dateString += 3;
1402 haveTZ = true;
1403 }
1404
1405 if (*dateString == '+' || *dateString == '-') {
1406 long o = strtol(dateString, &newPosStr, 10);
1407 if (errno)
1408 return NaN;
1409 dateString = newPosStr;
1410
1411 if (o < -9959 || o > 9959)
1412 return NaN;
1413
1414 int sgn = (o < 0) ? -1 : 1;
1415 o = abs(o);
1416 if (*dateString != ':') {
1417 offset = ((o / 100) * 60 + (o % 100)) * sgn;
1418 } else { // GMT+05:00
1419 dateString++;
1420 long o2 = strtol(dateString, &newPosStr, 10);
1421 if (errno)
1422 return NaN;
1423 dateString = newPosStr;
1424 offset = (o * 60 + o2) * sgn;
1425 }
1426 haveTZ = true;
1427 } else {
1428 for (int i = 0; i < int(sizeof(known_zones) / sizeof(KnownZone)); i++) {
1429 if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
1430 offset = known_zones[i].tzOffset;
1431 dateString += strlen(known_zones[i].tzName);
1432 haveTZ = true;
1433 break;
1434 }
1435 }
1436 }
1437 }
1438
1439 dateString = skipSpacesAndComments(dateString);
1440
1441 if (*dateString && year == -1) {
1442 year = strtol(dateString, &newPosStr, 10);
1443 if (errno)
1444 return NaN;
1445 dateString = newPosStr;
1446 }
1447
1448 dateString = skipSpacesAndComments(dateString);
1449
1450 // Trailing garbage
1451 if (*dateString)
1452 return NaN;
1453
1454 // Y2K: Handle 2 digit years.
1455 if (year >= 0 && year < 100) {
1456 if (year < 50)
1457 year += 2000;
1458 else
1459 year += 1900;
1460 }
1461
1462 // fall back to local timezone
1463 if (!haveTZ) {
1464 tm t;
1465 memset(&t, 0, sizeof(tm));
1466 t.tm_mday = day;
1467 t.tm_mon = month;
1468 t.tm_year = year - 1900;
1469 t.tm_isdst = -1;
1470 t.tm_sec = second;
1471 t.tm_min = minute;
1472 t.tm_hour = hour;
1473
1474 // Use our makeTime() rather than mktime() as the latter can't handle the full year range.
1475 return makeTime(&t, 0, false);
1476 }
1477
1478 return (ymdhmsToSeconds(year, month + 1, day, hour, minute, second) - (offset * 60.0)) * msPerSecond;
1479}
1480
1481double timeClip(double t)
1482{
1483 if (isnan(t) || isInf(t))
1484 return NaN;
1485 double at = fabs(t);
1486 if (at > 8.64E15)
1487 return NaN;
1488 return copysign(floor(at), t);
1489}
1490
1491} // namespace KJS
1492