source: webkit/trunk/JavaScriptCore/kjs/DateMath.cpp@ 36578

Last change on this file since 36578 was 36578, checked in by [email protected], 17 years ago

Improve timer accuracy for JavaScript Date object on Windows.


Use a combination of ftime and QueryPerformanceCounter.
ftime returns the information we want, but doesn't have sufficient resolution.
QueryPerformanceCounter has high resolution, but is only usable to measure time intervals.
To combine them, we call ftime and QueryPerformanceCounter initially. Later calls will use
QueryPerformanceCounter by itself, adding the delta to the saved ftime. We re-sync to
correct for drift if the low-res and high-res elapsed time between calls differs by more
than twice the low-resolution timer resolution.


QueryPerformanceCounter may be inaccurate due to a problems with:


Reviewed by Darin Adler.

  • kjs/DateMath.cpp: (JSC::highResUpTime): (JSC::lowResUTCTime): (JSC::qpcAvailable): (JSC::getCurrentUTCTimeWithMicroseconds):
  • Property svn:eol-style set to native
File size: 31.1 KB
Line 
1/*
2 * Copyright (C) 1999-2000 Harri Porten ([email protected])
3 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 *
5 * The Original Code is Mozilla Communicator client code, released
6 * March 31, 1998.
7 *
8 * The Initial Developer of the Original Code is
9 * Netscape Communications Corporation.
10 * Portions created by the Initial Developer are Copyright (C) 1998
11 * the Initial Developer. All Rights Reserved.
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 *
27 * Alternatively, the contents of this file may be used under the terms
28 * of either the Mozilla Public License Version 1.1, found at
29 * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
30 * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
31 * (the "GPL"), in which case the provisions of the MPL or the GPL are
32 * applicable instead of those above. If you wish to allow use of your
33 * version of this file only under the terms of one of those two
34 * licenses (the MPL or the GPL) and not to allow others to use your
35 * version of this file under the LGPL, indicate your decision by
36 * deletingthe provisions above and replace them with the notice and
37 * other provisions required by the MPL or the GPL, as the case may be.
38 * If you do not delete the provisions above, a recipient may use your
39 * version of this file under any of the LGPL, the MPL or the GPL.
40 */
41
42#include "config.h"
43#include "DateMath.h"
44
45#include "JSNumberCell.h"
46#include <math.h>
47#include <stdint.h>
48#include <time.h>
49#include <wtf/ASCIICType.h>
50#include <wtf/Assertions.h>
51#include <wtf/MathExtras.h>
52#include <wtf/StringExtras.h>
53
54#if HAVE(ERRNO_H)
55#include <errno.h>
56#endif
57
58#if PLATFORM(DARWIN)
59#include <notify.h>
60#endif
61
62#if HAVE(SYS_TIME_H)
63#include <sys/time.h>
64#endif
65
66#if HAVE(SYS_TIMEB_H)
67#include <sys/timeb.h>
68#endif
69
70using namespace WTF;
71
72namespace JSC {
73
74/* Constants */
75
76static const double minutesPerDay = 24.0 * 60.0;
77static const double secondsPerDay = 24.0 * 60.0 * 60.0;
78static const double secondsPerYear = 24.0 * 60.0 * 60.0 * 365.0;
79
80static const double usecPerSec = 1000000.0;
81
82static const double maxUnixTime = 2145859200.0; // 12/31/2037
83
84// Day of year for the first day of each month, where index 0 is January, and day 0 is January 1.
85// First for non-leap years, then for leap years.
86static const int firstDayOfMonth[2][12] = {
87 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
88 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
89};
90
91static inline bool isLeapYear(int year)
92{
93 if (year % 4 != 0)
94 return false;
95 if (year % 400 == 0)
96 return true;
97 if (year % 100 == 0)
98 return false;
99 return true;
100}
101
102static inline int daysInYear(int year)
103{
104 return 365 + isLeapYear(year);
105}
106
107static inline double daysFrom1970ToYear(int year)
108{
109 // The Gregorian Calendar rules for leap years:
110 // Every fourth year is a leap year. 2004, 2008, and 2012 are leap years.
111 // However, every hundredth year is not a leap year. 1900 and 2100 are not leap years.
112 // Every four hundred years, there's a leap year after all. 2000 and 2400 are leap years.
113
114 static const int leapDaysBefore1971By4Rule = 1970 / 4;
115 static const int excludedLeapDaysBefore1971By100Rule = 1970 / 100;
116 static const int leapDaysBefore1971By400Rule = 1970 / 400;
117
118 const double yearMinusOne = year - 1;
119 const double yearsToAddBy4Rule = floor(yearMinusOne / 4.0) - leapDaysBefore1971By4Rule;
120 const double yearsToExcludeBy100Rule = floor(yearMinusOne / 100.0) - excludedLeapDaysBefore1971By100Rule;
121 const double yearsToAddBy400Rule = floor(yearMinusOne / 400.0) - leapDaysBefore1971By400Rule;
122
123 return 365.0 * (year - 1970) + yearsToAddBy4Rule - yearsToExcludeBy100Rule + yearsToAddBy400Rule;
124}
125
126static inline double msToDays(double ms)
127{
128 return floor(ms / msPerDay);
129}
130
131static inline int msToYear(double ms)
132{
133 int approxYear = static_cast<int>(floor(ms / (msPerDay * 365.2425)) + 1970);
134 double msFromApproxYearTo1970 = msPerDay * daysFrom1970ToYear(approxYear);
135 if (msFromApproxYearTo1970 > ms)
136 return approxYear - 1;
137 if (msFromApproxYearTo1970 + msPerDay * daysInYear(approxYear) <= ms)
138 return approxYear + 1;
139 return approxYear;
140}
141
142static inline int dayInYear(double ms, int year)
143{
144 return static_cast<int>(msToDays(ms) - daysFrom1970ToYear(year));
145}
146
147static inline double msToMilliseconds(double ms)
148{
149 double result = fmod(ms, msPerDay);
150 if (result < 0)
151 result += msPerDay;
152 return result;
153}
154
155// 0: Sunday, 1: Monday, etc.
156static inline int msToWeekDay(double ms)
157{
158 int wd = (static_cast<int>(msToDays(ms)) + 4) % 7;
159 if (wd < 0)
160 wd += 7;
161 return wd;
162}
163
164static inline int msToSeconds(double ms)
165{
166 double result = fmod(floor(ms / msPerSecond), secondsPerMinute);
167 if (result < 0)
168 result += secondsPerMinute;
169 return static_cast<int>(result);
170}
171
172static inline int msToMinutes(double ms)
173{
174 double result = fmod(floor(ms / msPerMinute), minutesPerHour);
175 if (result < 0)
176 result += minutesPerHour;
177 return static_cast<int>(result);
178}
179
180static inline int msToHours(double ms)
181{
182 double result = fmod(floor(ms/msPerHour), hoursPerDay);
183 if (result < 0)
184 result += hoursPerDay;
185 return static_cast<int>(result);
186}
187
188static inline int monthFromDayInYear(int dayInYear, bool leapYear)
189{
190 const int d = dayInYear;
191 int step;
192
193 if (d < (step = 31))
194 return 0;
195 step += (leapYear ? 29 : 28);
196 if (d < step)
197 return 1;
198 if (d < (step += 31))
199 return 2;
200 if (d < (step += 30))
201 return 3;
202 if (d < (step += 31))
203 return 4;
204 if (d < (step += 30))
205 return 5;
206 if (d < (step += 31))
207 return 6;
208 if (d < (step += 31))
209 return 7;
210 if (d < (step += 30))
211 return 8;
212 if (d < (step += 31))
213 return 9;
214 if (d < (step += 30))
215 return 10;
216 return 11;
217}
218
219static inline bool checkMonth(int dayInYear, int& startDayOfThisMonth, int& startDayOfNextMonth, int daysInThisMonth)
220{
221 startDayOfThisMonth = startDayOfNextMonth;
222 startDayOfNextMonth += daysInThisMonth;
223 return (dayInYear <= startDayOfNextMonth);
224}
225
226static inline int dayInMonthFromDayInYear(int dayInYear, bool leapYear)
227{
228 const int d = dayInYear;
229 int step;
230 int next = 30;
231
232 if (d <= next)
233 return d + 1;
234 const int daysInFeb = (leapYear ? 29 : 28);
235 if (checkMonth(d, step, next, daysInFeb))
236 return d - step;
237 if (checkMonth(d, step, next, 31))
238 return d - step;
239 if (checkMonth(d, step, next, 30))
240 return d - step;
241 if (checkMonth(d, step, next, 31))
242 return d - step;
243 if (checkMonth(d, step, next, 30))
244 return d - step;
245 if (checkMonth(d, step, next, 31))
246 return d - step;
247 if (checkMonth(d, step, next, 31))
248 return d - step;
249 if (checkMonth(d, step, next, 30))
250 return d - step;
251 if (checkMonth(d, step, next, 31))
252 return d - step;
253 if (checkMonth(d, step, next, 30))
254 return d - step;
255 step = next;
256 return d - step;
257}
258
259static inline int monthToDayInYear(int month, bool isLeapYear)
260{
261 return firstDayOfMonth[isLeapYear][month];
262}
263
264static inline double timeToMS(double hour, double min, double sec, double ms)
265{
266 return (((hour * minutesPerHour + min) * secondsPerMinute + sec) * msPerSecond + ms);
267}
268
269static int dateToDayInYear(int year, int month, int day)
270{
271 year += month / 12;
272
273 month %= 12;
274 if (month < 0) {
275 month += 12;
276 --year;
277 }
278
279 int yearday = static_cast<int>(floor(daysFrom1970ToYear(year)));
280 int monthday = monthToDayInYear(month, isLeapYear(year));
281
282 return yearday + monthday + day - 1;
283}
284
285double getCurrentUTCTime()
286{
287 return floor(getCurrentUTCTimeWithMicroseconds());
288}
289
290#if PLATFORM(WIN_OS)
291
292static LARGE_INTEGER qpcFrequency;
293static bool syncedTime;
294
295static double highResUpTime()
296{
297 // We use QPC, but only after sanity checking its result, due to bugs:
298 // http://support.microsoft.com/kb/274323
299 // http://support.microsoft.com/kb/895980
300 // http://msdn.microsoft.com/en-us/library/ms644904.aspx ("...you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL)."
301
302 static LARGE_INTEGER qpcLast;
303 static DWORD tickCountLast;
304 static bool inited;
305
306 LARGE_INTEGER qpc;
307 QueryPerformanceCounter(&qpc);
308 DWORD tickCount = GetTickCount();
309
310 if (inited) {
311 __int64 qpcElapsed = ((qpc.QuadPart - qpcLast.QuadPart) * 1000) / qpcFrequency.QuadPart;
312 __int64 tickCountElapsed;
313 if (tickCount >= tickCountLast)
314 tickCountElapsed = (tickCount - tickCountLast);
315 else {
316 __int64 tickCountLarge = tickCount + 0x100000000I64;
317 tickCountElapsed = tickCountLarge - tickCountLast;
318 }
319
320 // force a re-sync if QueryPerformanceCounter differs from GetTickCount by more than 500ms.
321 // (500ms value is from http://support.microsoft.com/kb/274323)
322 __int64 diff = tickCountElapsed - qpcElapsed;
323 if (diff > 500 || diff < -500)
324 syncedTime = false;
325 } else
326 inited = true;
327
328 qpcLast = qpc;
329 tickCountLast = tickCount;
330
331 return (1000.0 * qpc.QuadPart) / static_cast<double>(qpcFrequency.QuadPart);;
332}
333
334static double lowResUTCTime()
335{
336 struct _timeb timebuffer;
337 _ftime(&timebuffer);
338 return timebuffer.time * msPerSecond + timebuffer.millitm;
339}
340
341static bool qpcAvailable()
342{
343 static bool available;
344 static bool checked;
345
346 if (checked)
347 return available;
348
349 available = QueryPerformanceFrequency(&qpcFrequency);
350 checked = true;
351 return available;
352}
353
354#endif
355
356double getCurrentUTCTimeWithMicroseconds()
357{
358#if PLATFORM(WIN_OS)
359 // Use a combination of ftime and QueryPerformanceCounter.
360 // ftime returns the information we want, but doesn't have sufficient resolution.
361 // QueryPerformanceCounter has high resolution, but is only usable to measure time intervals.
362 // To combine them, we call ftime and QueryPerformanceCounter initially. Later calls will use QueryPerformanceCounter
363 // by itself, adding the delta to the saved ftime. We periodically re-sync to correct for drift.
364 static bool started;
365 static double syncLowResUTCTime;
366 static double syncHighResUpTime;
367 static double lastUTCTime;
368
369 double lowResTime = lowResUTCTime();
370
371 if (!qpcAvailable())
372 return lowResTime;
373
374 double highResTime = highResUpTime();
375
376 if (!syncedTime) {
377 timeBeginPeriod(1); // increase time resolution around low-res time getter
378 syncLowResUTCTime = lowResTime = lowResUTCTime();
379 timeEndPeriod(1); // restore time resolution
380 syncHighResUpTime = highResTime;
381 syncedTime = true;
382 }
383
384 double highResElapsed = highResTime - syncHighResUpTime;
385 double utc = syncLowResUTCTime + highResElapsed;
386
387 // force a clock re-sync if we've drifted
388 double lowResElapsed = lowResTime - syncLowResUTCTime;
389 const double maximumAllowedDriftMsec = 15.625 * 2.0; // 2x the typical low-res accuracy
390 if (fabs(highResElapsed - lowResElapsed) > maximumAllowedDriftMsec)
391 syncedTime = false;
392
393 // make sure time doesn't run backwards (only correct if difference is < 2 seconds, since DST or clock changes could occur)
394 const double backwardTimeLimit = 2000.0;
395 if (utc < lastUTCTime && (lastUTCTime - utc) < backwardTimeLimit)
396 return lastUTCTime;
397 lastUTCTime = utc;
398#else
399 struct timeval tv;
400 gettimeofday(&tv, 0);
401 double utc = tv.tv_sec * msPerSecond + tv.tv_usec / 1000.0;
402#endif
403 return utc;
404}
405
406void getLocalTime(const time_t* localTime, struct tm* localTM)
407{
408#if COMPILER(MSVC7) || COMPILER(MINGW)
409 *localTM = *localtime(localTime);
410#elif COMPILER(MSVC)
411 localtime_s(localTM, localTime);
412#else
413 localtime_r(localTime, localTM);
414#endif
415}
416
417// There is a hard limit at 2038 that we currently do not have a workaround
418// for (rdar://problem/5052975).
419static inline int maximumYearForDST()
420{
421 return 2037;
422}
423
424static inline int mimimumYearForDST()
425{
426 // Because of the 2038 issue (see maximumYearForDST) if the current year is
427 // greater than the max year minus 27 (2010), we want to use the max year
428 // minus 27 instead, to ensure there is a range of 28 years that all years
429 // can map to.
430 return std::min(msToYear(getCurrentUTCTime()), maximumYearForDST() - 27) ;
431}
432
433/*
434 * Find an equivalent year for the one given, where equivalence is deterined by
435 * the two years having the same leapness and the first day of the year, falling
436 * on the same day of the week.
437 *
438 * This function returns a year between this current year and 2037, however this
439 * function will potentially return incorrect results if the current year is after
440 * 2010, (rdar://problem/5052975), if the year passed in is before 1900 or after
441 * 2100, (rdar://problem/5055038).
442 */
443int equivalentYearForDST(int year)
444{
445 // It is ok if the cached year is not the current year as long as the rules
446 // for DST did not change between the two years; if they did the app would need
447 // to be restarted.
448 static int minYear = mimimumYearForDST();
449 int maxYear = maximumYearForDST();
450
451 int difference;
452 if (year > maxYear)
453 difference = minYear - year;
454 else if (year < minYear)
455 difference = maxYear - year;
456 else
457 return year;
458
459 int quotient = difference / 28;
460 int product = (quotient) * 28;
461
462 year += product;
463 ASSERT((year >= minYear && year <= maxYear) || (product - year == static_cast<int>(NaN)));
464 return year;
465}
466
467static int32_t calculateUTCOffset()
468{
469 tm localt;
470 memset(&localt, 0, sizeof(localt));
471
472 // get the difference between this time zone and UTC on Jan 01, 2000 12:00:00 AM
473 localt.tm_mday = 1;
474 localt.tm_year = 100;
475 time_t utcOffset = 946684800 - mktime(&localt);
476
477 return static_cast<int32_t>(utcOffset * 1000);
478}
479
480#if PLATFORM(DARWIN)
481static int32_t s_cachedUTCOffset; // In milliseconds. An assumption here is that access to an int32_t variable is atomic on platforms that take this code path.
482static bool s_haveCachedUTCOffset;
483static int s_notificationToken;
484#endif
485
486/*
487 * Get the difference in milliseconds between this time zone and UTC (GMT)
488 * NOT including DST.
489 */
490double getUTCOffset()
491{
492#if PLATFORM(DARWIN)
493 if (s_haveCachedUTCOffset) {
494 int notified;
495 uint32_t status = notify_check(s_notificationToken, &notified);
496 if (status == NOTIFY_STATUS_OK && !notified)
497 return s_cachedUTCOffset;
498 }
499#endif
500
501 int32_t utcOffset = calculateUTCOffset();
502
503#if PLATFORM(DARWIN)
504 // Theoretically, it is possible that several threads will be executing this code at once, in which case we will have a race condition,
505 // and a newer value may be overwritten. In practice, time zones don't change that often.
506 s_cachedUTCOffset = utcOffset;
507#endif
508
509 return utcOffset;
510}
511
512/*
513 * Get the DST offset for the time passed in. Takes
514 * seconds (not milliseconds) and cannot handle dates before 1970
515 * on some OS'
516 */
517static double getDSTOffsetSimple(double localTimeSeconds, double utcOffset)
518{
519 if (localTimeSeconds > maxUnixTime)
520 localTimeSeconds = maxUnixTime;
521 else if (localTimeSeconds < 0) // Go ahead a day to make localtime work (does not work with 0)
522 localTimeSeconds += secondsPerDay;
523
524 //input is UTC so we have to shift back to local time to determine DST thus the + getUTCOffset()
525 double offsetTime = (localTimeSeconds * msPerSecond) + utcOffset;
526
527 // Offset from UTC but doesn't include DST obviously
528 int offsetHour = msToHours(offsetTime);
529 int offsetMinute = msToMinutes(offsetTime);
530
531 // FIXME: time_t has a potential problem in 2038
532 time_t localTime = static_cast<time_t>(localTimeSeconds);
533
534 tm localTM;
535 getLocalTime(&localTime, &localTM);
536
537 double diff = ((localTM.tm_hour - offsetHour) * secondsPerHour) + ((localTM.tm_min - offsetMinute) * 60);
538
539 if (diff < 0)
540 diff += secondsPerDay;
541
542 return (diff * msPerSecond);
543}
544
545// Get the DST offset, given a time in UTC
546static double getDSTOffset(double ms, double utcOffset)
547{
548 // On Mac OS X, the call to localtime (see getDSTOffsetSimple) will return historically accurate
549 // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript
550 // standard explicitly dictates that historical information should not be considered when
551 // determining DST. For this reason we shift away from years that localtime can handle but would
552 // return historically accurate information.
553 int year = msToYear(ms);
554 int equivalentYear = equivalentYearForDST(year);
555 if (year != equivalentYear) {
556 bool leapYear = isLeapYear(year);
557 int dayInYearLocal = dayInYear(ms, year);
558 int dayInMonth = dayInMonthFromDayInYear(dayInYearLocal, leapYear);
559 int month = monthFromDayInYear(dayInYearLocal, leapYear);
560 int day = dateToDayInYear(equivalentYear, month, dayInMonth);
561 ms = (day * msPerDay) + msToMilliseconds(ms);
562 }
563
564 return getDSTOffsetSimple(ms / msPerSecond, utcOffset);
565}
566
567double gregorianDateTimeToMS(const GregorianDateTime& t, double milliSeconds, bool inputIsUTC)
568{
569 int day = dateToDayInYear(t.year + 1900, t.month, t.monthDay);
570 double ms = timeToMS(t.hour, t.minute, t.second, milliSeconds);
571 double result = (day * msPerDay) + ms;
572
573 if (!inputIsUTC) { // convert to UTC
574 double utcOffset = getUTCOffset();
575 result -= utcOffset;
576 result -= getDSTOffset(result, utcOffset);
577 }
578
579 return result;
580}
581
582void msToGregorianDateTime(double ms, bool outputIsUTC, GregorianDateTime& tm)
583{
584 // input is UTC
585 double dstOff = 0.0;
586 const double utcOff = getUTCOffset();
587
588 if (!outputIsUTC) { // convert to local time
589 dstOff = getDSTOffset(ms, utcOff);
590 ms += dstOff + utcOff;
591 }
592
593 const int year = msToYear(ms);
594 tm.second = msToSeconds(ms);
595 tm.minute = msToMinutes(ms);
596 tm.hour = msToHours(ms);
597 tm.weekDay = msToWeekDay(ms);
598 tm.yearDay = dayInYear(ms, year);
599 tm.monthDay = dayInMonthFromDayInYear(tm.yearDay, isLeapYear(year));
600 tm.month = monthFromDayInYear(tm.yearDay, isLeapYear(year));
601 tm.year = year - 1900;
602 tm.isDST = dstOff != 0.0;
603
604 tm.utcOffset = static_cast<long>((dstOff + utcOff) / msPerSecond);
605 tm.timeZone = NULL;
606}
607
608void initDateMath()
609{
610#ifndef NDEBUG
611 static bool alreadyInitialized;
612 ASSERT(!alreadyInitialized++);
613#endif
614
615 equivalentYearForDST(2000); // Need to call once to initialize a static used in this function.
616#if PLATFORM(DARWIN)
617 // Register for a notification whenever the time zone changes.
618 uint32_t status = notify_register_check("com.apple.system.timezone", &s_notificationToken);
619 if (status == NOTIFY_STATUS_OK) {
620 s_cachedUTCOffset = calculateUTCOffset();
621 s_haveCachedUTCOffset = true;
622 }
623#endif
624}
625
626static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, int second)
627{
628 double days = (day - 32075)
629 + floor(1461 * (year + 4800.0 + (mon - 14) / 12) / 4)
630 + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12
631 - floor(3 * ((year + 4900.0 + (mon - 14) / 12) / 100) / 4)
632 - 2440588;
633 return ((days * hoursPerDay + hour) * minutesPerHour + minute) * secondsPerMinute + second;
634}
635
636// We follow the recommendation of RFC 2822 to consider all
637// obsolete time zones not listed here equivalent to "-0000".
638static const struct KnownZone {
639#if !PLATFORM(WIN_OS)
640 const
641#endif
642 char tzName[4];
643 int tzOffset;
644} known_zones[] = {
645 { "UT", 0 },
646 { "GMT", 0 },
647 { "EST", -300 },
648 { "EDT", -240 },
649 { "CST", -360 },
650 { "CDT", -300 },
651 { "MST", -420 },
652 { "MDT", -360 },
653 { "PST", -480 },
654 { "PDT", -420 }
655};
656
657inline static void skipSpacesAndComments(const char*& s)
658{
659 int nesting = 0;
660 char ch;
661 while ((ch = *s)) {
662 if (!isASCIISpace(ch)) {
663 if (ch == '(')
664 nesting++;
665 else if (ch == ')' && nesting > 0)
666 nesting--;
667 else if (nesting == 0)
668 break;
669 }
670 s++;
671 }
672}
673
674// returns 0-11 (Jan-Dec); -1 on failure
675static int findMonth(const char* monthStr)
676{
677 ASSERT(monthStr);
678 char needle[4];
679 for (int i = 0; i < 3; ++i) {
680 if (!*monthStr)
681 return -1;
682 needle[i] = static_cast<char>(toASCIILower(*monthStr++));
683 }
684 needle[3] = '\0';
685 const char *haystack = "janfebmaraprmayjunjulaugsepoctnovdec";
686 const char *str = strstr(haystack, needle);
687 if (str) {
688 int position = static_cast<int>(str - haystack);
689 if (position % 3 == 0)
690 return position / 3;
691 }
692 return -1;
693}
694
695double parseDate(const UString &date)
696{
697 // This parses a date in the form:
698 // Tuesday, 09-Nov-99 23:12:40 GMT
699 // or
700 // Sat, 01-Jan-2000 08:00:00 GMT
701 // or
702 // Sat, 01 Jan 2000 08:00:00 GMT
703 // or
704 // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822)
705 // ### non RFC formats, added for Javascript:
706 // [Wednesday] January 09 1999 23:12:40 GMT
707 // [Wednesday] January 09 23:12:40 GMT 1999
708 //
709 // We ignore the weekday.
710
711 CString dateCString = date.UTF8String();
712 const char *dateString = dateCString.c_str();
713
714 // Skip leading space
715 skipSpacesAndComments(dateString);
716
717 long month = -1;
718 const char *wordStart = dateString;
719 // Check contents of first words if not number
720 while (*dateString && !isASCIIDigit(*dateString)) {
721 if (isASCIISpace(*dateString) || *dateString == '(') {
722 if (dateString - wordStart >= 3)
723 month = findMonth(wordStart);
724 skipSpacesAndComments(dateString);
725 wordStart = dateString;
726 } else
727 dateString++;
728 }
729
730 // Missing delimiter between month and day (like "January29")?
731 if (month == -1 && wordStart != dateString)
732 month = findMonth(wordStart);
733
734 skipSpacesAndComments(dateString);
735
736 if (!*dateString)
737 return NaN;
738
739 // ' 09-Nov-99 23:12:40 GMT'
740 char *newPosStr;
741 errno = 0;
742 long day = strtol(dateString, &newPosStr, 10);
743 if (errno)
744 return NaN;
745 dateString = newPosStr;
746
747 if (!*dateString)
748 return NaN;
749
750 if (day < 0)
751 return NaN;
752
753 long year = 0;
754 if (day > 31) {
755 // ### where is the boundary and what happens below?
756 if (*dateString != '/')
757 return NaN;
758 // looks like a YYYY/MM/DD date
759 if (!*++dateString)
760 return NaN;
761 year = day;
762 month = strtol(dateString, &newPosStr, 10) - 1;
763 if (errno)
764 return NaN;
765 dateString = newPosStr;
766 if (*dateString++ != '/' || !*dateString)
767 return NaN;
768 day = strtol(dateString, &newPosStr, 10);
769 if (errno)
770 return NaN;
771 dateString = newPosStr;
772 } else if (*dateString == '/' && month == -1) {
773 dateString++;
774 // This looks like a MM/DD/YYYY date, not an RFC date.
775 month = day - 1; // 0-based
776 day = strtol(dateString, &newPosStr, 10);
777 if (errno)
778 return NaN;
779 if (day < 1 || day > 31)
780 return NaN;
781 dateString = newPosStr;
782 if (*dateString == '/')
783 dateString++;
784 if (!*dateString)
785 return NaN;
786 } else {
787 if (*dateString == '-')
788 dateString++;
789
790 skipSpacesAndComments(dateString);
791
792 if (*dateString == ',')
793 dateString++;
794
795 if (month == -1) { // not found yet
796 month = findMonth(dateString);
797 if (month == -1)
798 return NaN;
799
800 while (*dateString && *dateString != '-' && *dateString != ',' && !isASCIISpace(*dateString))
801 dateString++;
802
803 if (!*dateString)
804 return NaN;
805
806 // '-99 23:12:40 GMT'
807 if (*dateString != '-' && *dateString != '/' && *dateString != ',' && !isASCIISpace(*dateString))
808 return NaN;
809 dateString++;
810 }
811 }
812
813 if (month < 0 || month > 11)
814 return NaN;
815
816 // '99 23:12:40 GMT'
817 if (year <= 0 && *dateString) {
818 year = strtol(dateString, &newPosStr, 10);
819 if (errno)
820 return NaN;
821 }
822
823 // Don't fail if the time is missing.
824 long hour = 0;
825 long minute = 0;
826 long second = 0;
827 if (!*newPosStr)
828 dateString = newPosStr;
829 else {
830 // ' 23:12:40 GMT'
831 if (!(isASCIISpace(*newPosStr) || *newPosStr == ',')) {
832 if (*newPosStr != ':')
833 return NaN;
834 // There was no year; the number was the hour.
835 year = -1;
836 } else {
837 // in the normal case (we parsed the year), advance to the next number
838 dateString = ++newPosStr;
839 skipSpacesAndComments(dateString);
840 }
841
842 hour = strtol(dateString, &newPosStr, 10);
843 // Do not check for errno here since we want to continue
844 // even if errno was set becasue we are still looking
845 // for the timezone!
846
847 // Read a number? If not, this might be a timezone name.
848 if (newPosStr != dateString) {
849 dateString = newPosStr;
850
851 if (hour < 0 || hour > 23)
852 return NaN;
853
854 if (!*dateString)
855 return NaN;
856
857 // ':12:40 GMT'
858 if (*dateString++ != ':')
859 return NaN;
860
861 minute = strtol(dateString, &newPosStr, 10);
862 if (errno)
863 return NaN;
864 dateString = newPosStr;
865
866 if (minute < 0 || minute > 59)
867 return NaN;
868
869 // ':40 GMT'
870 if (*dateString && *dateString != ':' && !isASCIISpace(*dateString))
871 return NaN;
872
873 // seconds are optional in rfc822 + rfc2822
874 if (*dateString ==':') {
875 dateString++;
876
877 second = strtol(dateString, &newPosStr, 10);
878 if (errno)
879 return NaN;
880 dateString = newPosStr;
881
882 if (second < 0 || second > 59)
883 return NaN;
884 }
885
886 skipSpacesAndComments(dateString);
887
888 if (strncasecmp(dateString, "AM", 2) == 0) {
889 if (hour > 12)
890 return NaN;
891 if (hour == 12)
892 hour = 0;
893 dateString += 2;
894 skipSpacesAndComments(dateString);
895 } else if (strncasecmp(dateString, "PM", 2) == 0) {
896 if (hour > 12)
897 return NaN;
898 if (hour != 12)
899 hour += 12;
900 dateString += 2;
901 skipSpacesAndComments(dateString);
902 }
903 }
904 }
905
906 bool haveTZ = false;
907 int offset = 0;
908
909 // Don't fail if the time zone is missing.
910 // Some websites omit the time zone (4275206).
911 if (*dateString) {
912 if (strncasecmp(dateString, "GMT", 3) == 0 || strncasecmp(dateString, "UTC", 3) == 0) {
913 dateString += 3;
914 haveTZ = true;
915 }
916
917 if (*dateString == '+' || *dateString == '-') {
918 long o = strtol(dateString, &newPosStr, 10);
919 if (errno)
920 return NaN;
921 dateString = newPosStr;
922
923 if (o < -9959 || o > 9959)
924 return NaN;
925
926 int sgn = (o < 0) ? -1 : 1;
927 o = abs(o);
928 if (*dateString != ':') {
929 offset = ((o / 100) * 60 + (o % 100)) * sgn;
930 } else { // GMT+05:00
931 long o2 = strtol(dateString, &newPosStr, 10);
932 if (errno)
933 return NaN;
934 dateString = newPosStr;
935 offset = (o * 60 + o2) * sgn;
936 }
937 haveTZ = true;
938 } else {
939 for (int i = 0; i < int(sizeof(known_zones) / sizeof(KnownZone)); i++) {
940 if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
941 offset = known_zones[i].tzOffset;
942 dateString += strlen(known_zones[i].tzName);
943 haveTZ = true;
944 break;
945 }
946 }
947 }
948 }
949
950 skipSpacesAndComments(dateString);
951
952 if (*dateString && year == -1) {
953 year = strtol(dateString, &newPosStr, 10);
954 if (errno)
955 return NaN;
956 dateString = newPosStr;
957 }
958
959 skipSpacesAndComments(dateString);
960
961 // Trailing garbage
962 if (*dateString)
963 return NaN;
964
965 // Y2K: Handle 2 digit years.
966 if (year >= 0 && year < 100) {
967 if (year < 50)
968 year += 2000;
969 else
970 year += 1900;
971 }
972
973 // fall back to local timezone
974 if (!haveTZ) {
975 GregorianDateTime t;
976 t.monthDay = day;
977 t.month = month;
978 t.year = year - 1900;
979 t.isDST = -1;
980 t.second = second;
981 t.minute = minute;
982 t.hour = hour;
983
984 // Use our gregorianDateTimeToMS() rather than mktime() as the latter can't handle the full year range.
985 return gregorianDateTimeToMS(t, 0, false);
986 }
987
988 return (ymdhmsToSeconds(year, month + 1, day, hour, minute, second) - (offset * 60.0)) * msPerSecond;
989}
990
991double timeClip(double t)
992{
993 if (!isfinite(t))
994 return NaN;
995 if (fabs(t) > 8.64E15)
996 return NaN;
997 return trunc(t);
998}
999
1000UString formatDate(const GregorianDateTime &t)
1001{
1002 char buffer[100];
1003 snprintf(buffer, sizeof(buffer), "%s %s %02d %04d",
1004 weekdayName[(t.weekDay + 6) % 7],
1005 monthName[t.month], t.monthDay, t.year + 1900);
1006 return buffer;
1007}
1008
1009UString formatDateUTCVariant(const GregorianDateTime &t)
1010{
1011 char buffer[100];
1012 snprintf(buffer, sizeof(buffer), "%s, %02d %s %04d",
1013 weekdayName[(t.weekDay + 6) % 7],
1014 t.monthDay, monthName[t.month], t.year + 1900);
1015 return buffer;
1016}
1017
1018UString formatTime(const GregorianDateTime &t, bool utc)
1019{
1020 char buffer[100];
1021 if (utc) {
1022 snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT", t.hour, t.minute, t.second);
1023 } else {
1024 int offset = abs(gmtoffset(t));
1025 char tzname[70];
1026 struct tm gtm = t;
1027 strftime(tzname, sizeof(tzname), "%Z", &gtm);
1028
1029 if (tzname[0]) {
1030 snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%c%02d%02d (%s)",
1031 t.hour, t.minute, t.second,
1032 gmtoffset(t) < 0 ? '-' : '+', offset / (60*60), (offset / 60) % 60, tzname);
1033 } else {
1034 snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d GMT%c%02d%02d",
1035 t.hour, t.minute, t.second,
1036 gmtoffset(t) < 0 ? '-' : '+', offset / (60*60), (offset / 60) % 60);
1037 }
1038 }
1039 return UString(buffer);
1040}
1041
1042} // namespace JSC
Note: See TracBrowser for help on using the repository browser.