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. |
79 | using std::strlen; |
80 | using std::strcpy; |
81 | using std::strncpy; |
82 | using std::memset; |
83 | using std::memcpy; |
84 | |
85 | using namespace WTF; |
86 | |
87 | |
88 | inline 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 | |
103 | namespace KJS { |
104 | |
105 | /** |
106 | * @internal |
107 | * |
108 | * Class to implement all methods that are properties of the |
109 | * Date object |
110 | */ |
111 | class DateObjectFuncImp : public InternalFunctionImp { |
112 | public: |
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 | |
119 | private: |
120 | int id; |
121 | }; |
122 | |
123 | // some constants |
124 | const double hoursPerDay = 24; |
125 | const double minutesPerHour = 60; |
126 | const double secondsPerMinute = 60; |
127 | const double msPerSecond = 1000; |
128 | const double msPerMinute = 60 * 1000; |
129 | const double msPerHour = 60 * 60 * 1000; |
130 | const double msPerDay = 24 * 60 * 60 * 1000; |
131 | |
132 | static const char * const weekdayName[7] = { "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" , "Sun" }; |
133 | static const char * const monthName[12] = { "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" }; |
134 | |
135 | static double makeTime(tm *, double ms, bool utc); |
136 | static double parseDate(const UString &); |
137 | static double timeClip(double); |
138 | static void millisecondsToTM(double milli, bool utc, tm *t); |
139 | |
140 | #if PLATFORM(MAC) |
141 | |
142 | static 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 | |
155 | static 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 | |
209 | static 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 | |
218 | static 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 | |
227 | static 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 | |
245 | static 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 | |
264 | static 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 | |
282 | static int day(double t) |
283 | { |
284 | return int(floor(t / msPerDay)); |
285 | } |
286 | |
287 | static 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 |
296 | static 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 |
308 | static double timeFromYear(int year) |
309 | { |
310 | return msPerDay * dayFromYear(year); |
311 | } |
312 | |
313 | // year determined by time value |
314 | static 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. |
335 | static 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]) |
347 | static 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]) |
397 | static 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 | |
431 | const ClassInfo DateInstance::info = {"Date" , 0, 0, 0}; |
432 | |
433 | DateInstance::DateInstance(JSObject *proto) |
434 | : JSWrapperObject(proto) |
435 | { |
436 | } |
437 | |
438 | JSObject* DateInstance::valueClone(Interpreter* targetCtx) const |
439 | { |
440 | DateInstance* copy = new DateInstance(targetCtx->builtinDatePrototype()); |
441 | copy->setInternalValue(internalValue()); |
442 | return copy; |
443 | } |
444 | |
445 | bool 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 | |
456 | bool 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 | |
466 | bool 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 | |
478 | bool DateInstance::getUTCTime(double &milli) const |
479 | { |
480 | milli = internalValue()->getNumber(); |
481 | if (isNaN(milli)) |
482 | return false; |
483 | |
484 | return true; |
485 | } |
486 | |
487 | static inline bool isTime_tSigned() |
488 | { |
489 | time_t minusOne = (time_t)(-1); |
490 | return minusOne < 0; |
491 | } |
492 | |
493 | static 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 | |
533 | static bool isNaNorInf(double value) |
534 | { |
535 | return isNaN(value) || isInf(value); |
536 | } |
537 | |
538 | // ------------------------------ DatePrototype ----------------------------- |
539 | |
540 | const 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 | |
595 | DatePrototype::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 | |
602 | bool 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 | |
609 | DateProtoFunc::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 | |
618 | JSValue *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 | |
798 | DateObjectImp::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 | |
817 | bool DateObjectImp::implementsConstruct() const |
818 | { |
819 | return true; |
820 | } |
821 | |
822 | static 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 | |
841 | static 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 |
870 | JSObject *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 |
898 | JSValue *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 | |
907 | DateObjectFuncImp::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 |
914 | JSValue *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 | |
929 | static 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". |
951 | static 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) |
971 | void 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 | |
979 | void SystemTimeToUnixTime(LPSYSTEMTIME pst, double* pt) |
980 | { |
981 | FILETIME ft; |
982 | SystemTimeToFileTime(pst, &ft); |
983 | FileTimeToUnixTime(&ft, pt); |
984 | } |
985 | #endif |
986 | |
987 | static 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 | |
1061 | inline static bool isSpaceLike(char c) |
1062 | { |
1063 | return isASCIISpace(c) || c == ',' || c == ':' || c == '-'; |
1064 | } |
1065 | |
1066 | static 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 |
1088 | static 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 | |
1108 | static bool isTwoDigits (const char* str) |
1109 | { |
1110 | return isASCIIDigit(str[0]) && isASCIIDigit(str[1]); |
1111 | } |
1112 | |
1113 | static int twoDigit (const char* str) |
1114 | { |
1115 | return (str[0] - '0') * 10 + str[1] - '0'; |
1116 | } |
1117 | |
1118 | static 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 | |
1481 | double 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 | |