Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / prmjtime.cpp
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * ***** BEGIN LICENSE BLOCK *****
4  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5  *
6  * The contents of this file are subject to the Mozilla Public License Version
7  * 1.1 (the "License"); you may not use this file except in compliance with
8  * the License. You may obtain a copy of the License at
9  * http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the
14  * License.
15  *
16  * The Original Code is Mozilla Communicator client code, released
17  * March 31, 1998.
18  *
19  * The Initial Developer of the Original Code is
20  * Netscape Communications Corporation.
21  * Portions created by the Initial Developer are Copyright (C) 1998
22  * the Initial Developer. All Rights Reserved.
23  *
24  * Contributor(s):
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either of the GNU General Public License Version 2 or later (the "GPL"),
28  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
39
40 /*
41  * PR time code.
42  */
43 #ifdef SOLARIS
44 #define _REENTRANT 1
45 #endif
46 #include <string.h>
47 #include <time.h>
48
49 #include "jsstdint.h"
50 #include "jstypes.h"
51 #include "jsutil.h"
52
53 #include "jsprf.h"
54 #include "jslock.h"
55 #include "prmjtime.h"
56
57 #define PRMJ_DO_MILLISECONDS 1
58
59 #ifdef XP_OS2
60 #include <sys/timeb.h>
61 #endif
62 #ifdef XP_WIN
63 #include <windef.h>
64 #include <winbase.h>
65 #include <math.h>     /* for fabs */
66 #include <mmsystem.h> /* for timeBegin/EndPeriod */
67 /* VC++ 8.0 or later, and not WINCE */
68 #if _MSC_VER >= 1400 && !defined(WINCE)
69 #define NS_HAVE_INVALID_PARAMETER_HANDLER 1
70 #endif
71 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
72 #include <stdlib.h>   /* for _set_invalid_parameter_handler */
73 #include <crtdbg.h>   /* for _CrtSetReportMode */
74 #endif
75
76 #ifdef JS_THREADSAFE
77 #include <prinit.h>
78 #endif
79
80 #endif
81
82 #if defined(XP_UNIX) || defined(XP_BEOS)
83
84 #ifdef _SVID_GETTOD   /* Defined only on Solaris, see Solaris <sys/types.h> */
85 extern int gettimeofday(struct timeval *tv);
86 #endif
87
88 #include <sys/time.h>
89
90 #endif /* XP_UNIX */
91
92 #define PRMJ_YEAR_DAYS 365L
93 #define PRMJ_FOUR_YEARS_DAYS (4 * PRMJ_YEAR_DAYS + 1)
94 #define PRMJ_CENTURY_DAYS (25 * PRMJ_FOUR_YEARS_DAYS - 1)
95 #define PRMJ_FOUR_CENTURIES_DAYS (4 * PRMJ_CENTURY_DAYS + 1)
96 #define PRMJ_HOUR_SECONDS  3600L
97 #define PRMJ_DAY_SECONDS  (24L * PRMJ_HOUR_SECONDS)
98 #define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * PRMJ_YEAR_DAYS)
99 #define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */
100
101 /* Get the local time. localtime_r is preferred as it is reentrant. */
102 static inline bool
103 ComputeLocalTime(time_t local, struct tm *ptm)
104 {
105 #ifdef HAVE_LOCALTIME_R
106     return localtime_r(&local, ptm);
107 #else
108     struct tm *otm = localtime(&local);
109     if (!otm)
110         return false;
111     *ptm = *otm;
112     return true;
113 #endif
114 }
115
116 /*
117  * get the difference in seconds between this time zone and UTC (GMT)
118  */
119 JSInt32
120 PRMJ_LocalGMTDifference()
121 {
122 #if defined(XP_WIN) && !defined(WINCE)
123     /* Windows does not follow POSIX. Updates to the
124      * TZ environment variable are not reflected
125      * immediately on that platform as they are
126      * on UNIX systems without this call.
127      */
128     _tzset();
129 #endif
130
131     /*
132      * Get the difference between this time zone and GMT, by checking the local
133      * time for days 0 and 180 of 1970, using a date for which daylight savings
134      * time was not in effect.
135      */
136     int day = 0;
137     struct tm tm;
138
139     if (!ComputeLocalTime(0, &tm))
140         return 0;
141     if (tm.tm_isdst > 0) {
142         day = 180;
143         if (!ComputeLocalTime(PRMJ_DAY_SECONDS * day, &tm))
144             return 0;
145     }
146
147     int time = (tm.tm_hour * 3600) + (tm.tm_min * 60) + tm.tm_sec;
148     time = PRMJ_DAY_SECONDS - time;
149
150     if (tm.tm_yday == day)
151         time -= PRMJ_DAY_SECONDS;
152
153     return time;
154 }
155
156 /* Constants for GMT offset from 1970 */
157 #define G1970GMTMICROHI        0x00dcdcad /* micro secs to 1970 hi */
158 #define G1970GMTMICROLOW       0x8b3fa000 /* micro secs to 1970 low */
159
160 #define G2037GMTMICROHI        0x00e45fab /* micro secs to 2037 high */
161 #define G2037GMTMICROLOW       0x7a238000 /* micro secs to 2037 low */
162
163 #ifdef HAVE_SYSTEMTIMETOFILETIME
164
165 static const JSInt64 win2un = JSLL_INIT(0x19DB1DE, 0xD53E8000);
166
167 #define FILETIME2INT64(ft) (((JSInt64)ft.dwHighDateTime) << 32LL | (JSInt64)ft.dwLowDateTime)
168
169 #endif
170
171 #if defined(HAVE_GETSYSTEMTIMEASFILETIME) || defined(HAVE_SYSTEMTIMETOFILETIME)
172
173 #if defined(HAVE_GETSYSTEMTIMEASFILETIME)
174 inline void
175 LowResTime(LPFILETIME lpft)
176 {
177     GetSystemTimeAsFileTime(lpft);
178 }
179 #elif defined(HAVE_SYSTEMTIMETOFILETIME)
180 inline void
181 LowResTime(LPFILETIME lpft)
182 {
183     GetCurrentFT(lpft);
184 }
185 #else
186 #error "No implementation of PRMJ_Now was selected."
187 #endif
188
189 typedef struct CalibrationData {
190     long double freq;         /* The performance counter frequency */
191     long double offset;       /* The low res 'epoch' */
192     long double timer_offset; /* The high res 'epoch' */
193
194     /* The last high res time that we returned since recalibrating */
195     JSInt64 last;
196
197     JSBool calibrated;
198
199 #ifdef JS_THREADSAFE
200     CRITICAL_SECTION data_lock;
201     CRITICAL_SECTION calibration_lock;
202 #endif
203 #ifdef WINCE
204     JSInt64 granularity;
205 #endif
206 } CalibrationData;
207
208 static CalibrationData calibration = { 0 };
209
210 static void
211 NowCalibrate()
212 {
213     FILETIME ft, ftStart;
214     LARGE_INTEGER liFreq, now;
215
216     if (calibration.freq == 0.0) {
217         if(!QueryPerformanceFrequency(&liFreq)) {
218             /* High-performance timer is unavailable */
219             calibration.freq = -1.0;
220         } else {
221             calibration.freq = (long double) liFreq.QuadPart;
222         }
223     }
224     if (calibration.freq > 0.0) {
225         JSInt64 calibrationDelta = 0;
226
227         /* By wrapping a timeBegin/EndPeriod pair of calls around this loop,
228            the loop seems to take much less time (1 ms vs 15ms) on Vista. */
229         timeBeginPeriod(1);
230         LowResTime(&ftStart);
231         do {
232             LowResTime(&ft);
233         } while (memcmp(&ftStart,&ft, sizeof(ft)) == 0);
234         timeEndPeriod(1);
235
236 #ifdef WINCE
237         calibration.granularity = (FILETIME2INT64(ft) -
238                                    FILETIME2INT64(ftStart))/10;
239 #endif
240         /*
241         calibrationDelta = (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10;
242         fprintf(stderr, "Calibration delta was %I64d us\n", calibrationDelta);
243         */
244
245         QueryPerformanceCounter(&now);
246
247         calibration.offset = (long double) FILETIME2INT64(ft);
248         calibration.timer_offset = (long double) now.QuadPart;
249
250         /* The windows epoch is around 1600. The unix epoch is around
251            1970. win2un is the difference (in windows time units which
252            are 10 times more highres than the JS time unit) */
253         calibration.offset -= win2un;
254         calibration.offset *= 0.1;
255         calibration.last = 0;
256
257         calibration.calibrated = JS_TRUE;
258     }
259 }
260
261 #define CALIBRATIONLOCK_SPINCOUNT 0
262 #define DATALOCK_SPINCOUNT 4096
263 #define LASTLOCK_SPINCOUNT 4096
264
265 #ifdef JS_THREADSAFE
266 static PRStatus
267 NowInit(void)
268 {
269     memset(&calibration, 0, sizeof(calibration));
270     NowCalibrate();
271 #ifdef WINCE
272     InitializeCriticalSection(&calibration.calibration_lock);
273     InitializeCriticalSection(&calibration.data_lock);
274 #else
275     InitializeCriticalSectionAndSpinCount(&calibration.calibration_lock, CALIBRATIONLOCK_SPINCOUNT);
276     InitializeCriticalSectionAndSpinCount(&calibration.data_lock, DATALOCK_SPINCOUNT);
277 #endif
278     return PR_SUCCESS;
279 }
280
281 void
282 PRMJ_NowShutdown()
283 {
284     DeleteCriticalSection(&calibration.calibration_lock);
285     DeleteCriticalSection(&calibration.data_lock);
286 }
287
288 #define MUTEX_LOCK(m) EnterCriticalSection(m)
289 #define MUTEX_TRYLOCK(m) TryEnterCriticalSection(m)
290 #define MUTEX_UNLOCK(m) LeaveCriticalSection(m)
291 #ifdef WINCE
292 #define MUTEX_SETSPINCOUNT(m, c)
293 #else
294 #define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m),(c))
295 #endif
296
297 static PRCallOnceType calibrationOnce = { 0 };
298
299 #else
300
301 #define MUTEX_LOCK(m)
302 #define MUTEX_TRYLOCK(m) 1
303 #define MUTEX_UNLOCK(m)
304 #define MUTEX_SETSPINCOUNT(m, c)
305
306 #endif
307
308 #endif /* HAVE_GETSYSTEMTIMEASFILETIME */
309
310
311 #if defined(XP_OS2)
312 JSInt64
313 PRMJ_Now(void)
314 {
315     JSInt64 s, us, ms2us, s2us;
316     struct timeb b;
317
318     ftime(&b);
319     JSLL_UI2L(ms2us, PRMJ_USEC_PER_MSEC);
320     JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC);
321     JSLL_UI2L(s, b.time);
322     JSLL_UI2L(us, b.millitm);
323     JSLL_MUL(us, us, ms2us);
324     JSLL_MUL(s, s, s2us);
325     JSLL_ADD(s, s, us);
326     return s;
327 }
328
329 #elif defined(XP_UNIX) || defined(XP_BEOS)
330 JSInt64
331 PRMJ_Now(void)
332 {
333     struct timeval tv;
334     JSInt64 s, us, s2us;
335
336 #ifdef _SVID_GETTOD   /* Defined only on Solaris, see Solaris <sys/types.h> */
337     gettimeofday(&tv);
338 #else
339     gettimeofday(&tv, 0);
340 #endif /* _SVID_GETTOD */
341     JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC);
342     JSLL_UI2L(s, tv.tv_sec);
343     JSLL_UI2L(us, tv.tv_usec);
344     JSLL_MUL(s, s, s2us);
345     JSLL_ADD(s, s, us);
346     return s;
347 }
348
349 #else
350 /*
351
352 Win32 python-esque pseudo code
353 Please see bug 363258 for why the win32 timing code is so complex.
354
355 calibration mutex : Win32CriticalSection(spincount=0)
356 data mutex : Win32CriticalSection(spincount=4096)
357
358 def NowInit():
359   init mutexes
360   PRMJ_NowCalibration()
361
362 def NowCalibration():
363   expensive up-to-15ms call
364
365 def PRMJ_Now():
366   returnedTime = 0
367   needCalibration = False
368   cachedOffset = 0.0
369   calibrated = False
370   PR_CallOnce(PRMJ_NowInit)
371   do
372     if not global.calibrated or needCalibration:
373       acquire calibration mutex
374         acquire data mutex
375
376           // Only recalibrate if someone didn't already
377           if cachedOffset == calibration.offset:
378             // Have all waiting threads immediately wait
379             set data mutex spin count = 0
380             PRMJ_NowCalibrate()
381             calibrated = 1
382
383             set data mutex spin count = default
384         release data mutex
385       release calibration mutex
386
387     calculate lowres time
388
389     if highres timer available:
390       acquire data mutex
391         calculate highres time
392         cachedOffset = calibration.offset
393         highres time = calibration.last = max(highres time, calibration.last)
394       release data mutex
395
396       get kernel tick interval
397
398       if abs(highres - lowres) < kernel tick:
399         returnedTime = highres time
400         needCalibration = False
401       else:
402         if calibrated:
403           returnedTime = lowres
404           needCalibration = False
405         else:
406           needCalibration = True
407     else:
408       returnedTime = lowres
409   while needCalibration
410
411 */
412
413 // We parameterize the delay count just so that shell builds can
414 // set it to 0 in order to get high-resolution benchmarking.
415 // 10 seems to be the number of calls to load with a blank homepage.
416 int CALIBRATION_DELAY_COUNT = 10;
417
418 JSInt64
419 PRMJ_Now(void)
420 {
421     static int nCalls = 0;
422     long double lowresTime, highresTimerValue;
423     FILETIME ft;
424     LARGE_INTEGER now;
425     JSBool calibrated = JS_FALSE;
426     JSBool needsCalibration = JS_FALSE;
427     JSInt64 returnedTime;
428     long double cachedOffset = 0.0;
429
430     /* To avoid regressing startup time (where high resolution is likely
431        not needed), give the old behavior for the first few calls.
432        This does not appear to be needed on Vista as the timeBegin/timeEndPeriod
433        calls seem to immediately take effect. */
434     int thiscall = JS_ATOMIC_INCREMENT(&nCalls);
435     if (thiscall <= CALIBRATION_DELAY_COUNT) {
436         LowResTime(&ft);
437         return (FILETIME2INT64(ft)-win2un)/10L;
438     }
439
440     /* For non threadsafe platforms, NowInit is not necessary */
441 #ifdef JS_THREADSAFE
442     PR_CallOnce(&calibrationOnce, NowInit);
443 #endif
444     do {
445         if (!calibration.calibrated || needsCalibration) {
446             MUTEX_LOCK(&calibration.calibration_lock);
447             MUTEX_LOCK(&calibration.data_lock);
448
449             /* Recalibrate only if no one else did before us */
450             if(calibration.offset == cachedOffset) {
451                 /* Since calibration can take a while, make any other
452                    threads immediately wait */
453                 MUTEX_SETSPINCOUNT(&calibration.data_lock, 0);
454
455                 NowCalibrate();
456
457                 calibrated = JS_TRUE;
458
459                 /* Restore spin count */
460                 MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT);
461             }
462             MUTEX_UNLOCK(&calibration.data_lock);
463             MUTEX_UNLOCK(&calibration.calibration_lock);
464         }
465
466
467         /* Calculate a low resolution time */
468         LowResTime(&ft);
469         lowresTime = 0.1*(long double)(FILETIME2INT64(ft) - win2un);
470
471         if (calibration.freq > 0.0) {
472             long double highresTime, diff;
473
474             DWORD timeAdjustment, timeIncrement;
475             BOOL timeAdjustmentDisabled;
476
477             /* Default to 15.625 ms if the syscall fails */
478             long double skewThreshold = 15625.25;
479             /* Grab high resolution time */
480             QueryPerformanceCounter(&now);
481             highresTimerValue = (long double)now.QuadPart;
482
483             MUTEX_LOCK(&calibration.data_lock);
484             highresTime = calibration.offset + PRMJ_USEC_PER_SEC*
485                  (highresTimerValue-calibration.timer_offset)/calibration.freq;
486             cachedOffset = calibration.offset;
487
488             /* On some dual processor/core systems, we might get an earlier time
489                so we cache the last time that we returned */
490             calibration.last = JS_MAX(calibration.last,(JSInt64)highresTime);
491             returnedTime = calibration.last;
492             MUTEX_UNLOCK(&calibration.data_lock);
493
494 #ifdef WINCE
495             /* Get an estimate of clock ticks per second from our own test */
496             skewThreshold = calibration.granularity;
497 #else
498             /* Rather than assume the NT kernel ticks every 15.6ms, ask it */
499             if (GetSystemTimeAdjustment(&timeAdjustment,
500                                         &timeIncrement,
501                                         &timeAdjustmentDisabled)) {
502                 if (timeAdjustmentDisabled) {
503                     /* timeAdjustment is in units of 100ns */
504                     skewThreshold = timeAdjustment/10.0;
505                 } else {
506                     /* timeIncrement is in units of 100ns */
507                     skewThreshold = timeIncrement/10.0;
508                 }
509             }
510 #endif
511             /* Check for clock skew */
512             diff = lowresTime - highresTime;
513
514             /* For some reason that I have not determined, the skew can be
515                up to twice a kernel tick. This does not seem to happen by
516                itself, but I have only seen it triggered by another program
517                doing some kind of file I/O. The symptoms are a negative diff
518                followed by an equally large positive diff. */
519             if (fabs(diff) > 2*skewThreshold) {
520                 /*fprintf(stderr,"Clock skew detected (diff = %f)!\n", diff);*/
521
522                 if (calibrated) {
523                     /* If we already calibrated once this instance, and the
524                        clock is still skewed, then either the processor(s) are
525                        wildly changing clockspeed or the system is so busy that
526                        we get switched out for long periods of time. In either
527                        case, it would be infeasible to make use of high
528                        resolution results for anything, so let's resort to old
529                        behavior for this call. It's possible that in the
530                        future, the user will want the high resolution timer, so
531                        we don't disable it entirely. */
532                     returnedTime = (JSInt64)lowresTime;
533                     needsCalibration = JS_FALSE;
534                 } else {
535                     /* It is possible that when we recalibrate, we will return a
536                        value less than what we have returned before; this is
537                        unavoidable. We cannot tell the different between a
538                        faulty QueryPerformanceCounter implementation and user
539                        changes to the operating system time. Since we must
540                        respect user changes to the operating system time, we
541                        cannot maintain the invariant that Date.now() never
542                        decreases; the old implementation has this behavior as
543                        well. */
544                     needsCalibration = JS_TRUE;
545                 }
546             } else {
547                 /* No detectable clock skew */
548                 returnedTime = (JSInt64)highresTime;
549                 needsCalibration = JS_FALSE;
550             }
551         } else {
552             /* No high resolution timer is available, so fall back */
553             returnedTime = (JSInt64)lowresTime;
554         }
555     } while (needsCalibration);
556
557     return returnedTime;
558 }
559 #endif
560
561 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
562 static void
563 PRMJ_InvalidParameterHandler(const wchar_t *expression,
564                              const wchar_t *function,
565                              const wchar_t *file,
566                              unsigned int   line,
567                              uintptr_t      pReserved)
568 {
569     /* empty */
570 }
571 #endif
572
573 /* Format a time value into a buffer. Same semantics as strftime() */
574 size_t
575 PRMJ_FormatTime(char *buf, int buflen, const char *fmt, PRMJTime *prtm)
576 {
577     size_t result = 0;
578 #if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS)
579     struct tm a;
580     int fake_tm_year = 0;
581 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
582     _invalid_parameter_handler oldHandler;
583     int oldReportMode;
584 #endif
585
586     memset(&a, 0, sizeof(struct tm));
587
588     a.tm_sec = prtm->tm_sec;
589     a.tm_min = prtm->tm_min;
590     a.tm_hour = prtm->tm_hour;
591     a.tm_mday = prtm->tm_mday;
592     a.tm_mon = prtm->tm_mon;
593     a.tm_wday = prtm->tm_wday;
594
595     /*
596      * On systems where |struct tm| has members tm_gmtoff and tm_zone, we
597      * must fill in those values, or else strftime will return wrong results
598      * (e.g., bug 511726, bug 554338).
599      */
600 #if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF)
601     {
602         /*
603          * Fill out |td| to the time represented by |prtm|, leaving the
604          * timezone fields zeroed out. localtime_r will then fill in the
605          * timezone fields for that local time according to the system's
606          * timezone parameters.
607          */
608         struct tm td;
609         memset(&td, 0, sizeof(td));
610         td.tm_sec = prtm->tm_sec;
611         td.tm_min = prtm->tm_min;
612         td.tm_hour = prtm->tm_hour;
613         td.tm_mday = prtm->tm_mday;
614         td.tm_mon = prtm->tm_mon;
615         td.tm_wday = prtm->tm_wday;
616         td.tm_year = prtm->tm_year - 1900;
617         td.tm_yday = prtm->tm_yday;
618         td.tm_isdst = prtm->tm_isdst;
619         time_t t = mktime(&td);
620         localtime_r(&t, &td);
621
622         a.tm_gmtoff = td.tm_gmtoff;
623         a.tm_zone = td.tm_zone;
624     }
625 #endif
626
627     /*
628      * Years before 1900 and after 9999 cause strftime() to abort on Windows.
629      * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then
630      * replace matching substrings in the strftime() result with the real year.
631      * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit
632      * year formats (%y) work correctly (since we won't find the fake year
633      * in that case).
634      * e.g. new Date(1873, 0).toLocaleFormat('%Y %y') => "1873 73"
635      * See bug 327869.
636      */
637 #define FAKE_YEAR_BASE 9900
638     if (prtm->tm_year < 1900 || prtm->tm_year > 9999) {
639         fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100;
640         a.tm_year = fake_tm_year - 1900;
641     }
642     else {
643         a.tm_year = prtm->tm_year - 1900;
644     }
645     a.tm_yday = prtm->tm_yday;
646     a.tm_isdst = prtm->tm_isdst;
647
648     /*
649      * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff
650      * are null.  This doesn't quite work, though - the timezone is off by
651      * tzoff + dst.  (And mktime seems to return -1 for the exact dst
652      * changeover time.)
653      */
654
655 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
656     oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
657     oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
658 #endif
659
660     result = strftime(buf, buflen, fmt, &a);
661
662 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
663     _set_invalid_parameter_handler(oldHandler);
664     _CrtSetReportMode(_CRT_ASSERT, oldReportMode);
665 #endif
666
667     if (fake_tm_year && result) {
668         char real_year[16];
669         char fake_year[16];
670         size_t real_year_len;
671         size_t fake_year_len;
672         char* p;
673
674         sprintf(real_year, "%d", prtm->tm_year);
675         real_year_len = strlen(real_year);
676         sprintf(fake_year, "%d", fake_tm_year);
677         fake_year_len = strlen(fake_year);
678
679         /* Replace the fake year in the result with the real year. */
680         for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) {
681             size_t new_result = result + real_year_len - fake_year_len;
682             if ((int)new_result >= buflen) {
683                 return 0;
684             }
685             memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len));
686             memcpy(p, real_year, real_year_len);
687             result = new_result;
688             *(buf + result) = '\0';
689         }
690     }
691 #endif
692     return result;
693 }
694
695 JSInt64
696 DSTOffsetCache::computeDSTOffsetMilliseconds(int64 localTimeSeconds)
697 {
698     JS_ASSERT(localTimeSeconds >= 0);
699     JS_ASSERT(localTimeSeconds <= MAX_UNIX_TIMET);
700
701 #if defined(XP_WIN) && !defined(WINCE)
702     /* Windows does not follow POSIX. Updates to the
703      * TZ environment variable are not reflected
704      * immediately on that platform as they are
705      * on UNIX systems without this call.
706      */
707     _tzset();
708 #endif
709
710     struct tm tm;
711     if (!ComputeLocalTime(static_cast<time_t>(localTimeSeconds), &tm))
712         return 0;
713
714     JSInt32 base = PRMJ_LocalGMTDifference();
715
716     int32 dayoff = int32((localTimeSeconds - base) % (SECONDS_PER_HOUR * 24));
717     int32 tmoff = tm.tm_sec + (tm.tm_min * SECONDS_PER_MINUTE) +
718         (tm.tm_hour * SECONDS_PER_HOUR);
719
720     JSInt32 diff = tmoff - dayoff;
721
722     if (diff < 0)
723         diff += SECONDS_PER_DAY;
724
725     return diff * MILLISECONDS_PER_SECOND;
726 }
727
728 JSInt64
729 DSTOffsetCache::getDSTOffsetMilliseconds(JSInt64 localTimeMilliseconds, JSContext *cx)
730 {
731     sanityCheck();
732     noteOffsetCalculation();
733
734     JSInt64 localTimeSeconds = localTimeMilliseconds / MILLISECONDS_PER_SECOND;
735
736     if (localTimeSeconds > MAX_UNIX_TIMET) {
737         localTimeSeconds = MAX_UNIX_TIMET;
738     } else if (localTimeSeconds < 0) {
739         /* Go ahead a day to make localtime work (does not work with 0). */
740         localTimeSeconds = SECONDS_PER_DAY;
741     }
742
743     /*
744      * NB: Be aware of the initial range values when making changes to this
745      *     code: the first call to this method, with those initial range
746      *     values, must result in a cache miss.
747      */
748
749     if (rangeStartSeconds <= localTimeSeconds &&
750         localTimeSeconds <= rangeEndSeconds) {
751         noteCacheHit();
752         return offsetMilliseconds;
753     }
754
755     if (oldRangeStartSeconds <= localTimeSeconds &&
756         localTimeSeconds <= oldRangeEndSeconds) {
757         noteCacheHit();
758         return oldOffsetMilliseconds;
759     }
760
761     oldOffsetMilliseconds = offsetMilliseconds;
762     oldRangeStartSeconds = rangeStartSeconds;
763     oldRangeEndSeconds = rangeEndSeconds;
764
765     if (rangeStartSeconds <= localTimeSeconds) {
766         JSInt64 newEndSeconds = JS_MIN(rangeEndSeconds + RANGE_EXPANSION_AMOUNT, MAX_UNIX_TIMET);
767         if (newEndSeconds >= localTimeSeconds) {
768             JSInt64 endOffsetMilliseconds = computeDSTOffsetMilliseconds(newEndSeconds);
769             if (endOffsetMilliseconds == offsetMilliseconds) {
770                 noteCacheMissIncrease();
771                 rangeEndSeconds = newEndSeconds;
772                 return offsetMilliseconds;
773             }
774
775             offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
776             if (offsetMilliseconds == endOffsetMilliseconds) {
777                 noteCacheMissIncreasingOffsetChangeUpper();
778                 rangeStartSeconds = localTimeSeconds;
779                 rangeEndSeconds = newEndSeconds;
780             } else {
781                 noteCacheMissIncreasingOffsetChangeExpand();
782                 rangeEndSeconds = localTimeSeconds;
783             }
784             return offsetMilliseconds;
785         }
786
787         noteCacheMissLargeIncrease();
788         offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
789         rangeStartSeconds = rangeEndSeconds = localTimeSeconds;
790         return offsetMilliseconds;
791     }
792
793     JSInt64 newStartSeconds = JS_MAX(rangeStartSeconds - RANGE_EXPANSION_AMOUNT, 0);
794     if (newStartSeconds <= localTimeSeconds) {
795         JSInt64 startOffsetMilliseconds = computeDSTOffsetMilliseconds(newStartSeconds);
796         if (startOffsetMilliseconds == offsetMilliseconds) {
797             noteCacheMissDecrease();
798             rangeStartSeconds = newStartSeconds;
799             return offsetMilliseconds;
800         }
801
802         offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
803         if (offsetMilliseconds == startOffsetMilliseconds) {
804             noteCacheMissDecreasingOffsetChangeLower();
805             rangeStartSeconds = newStartSeconds;
806             rangeEndSeconds = localTimeSeconds;
807         } else {
808             noteCacheMissDecreasingOffsetChangeExpand();
809             rangeStartSeconds = localTimeSeconds;
810         }
811         return offsetMilliseconds;
812     }
813
814     noteCacheMissLargeDecrease();
815     rangeStartSeconds = rangeEndSeconds = localTimeSeconds;
816     offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
817     return offsetMilliseconds;
818 }
819
820 void
821 DSTOffsetCache::sanityCheck()
822 {
823     JS_ASSERT(rangeStartSeconds <= rangeEndSeconds);
824     JS_ASSERT_IF(rangeStartSeconds == INT64_MIN, rangeEndSeconds == INT64_MIN);
825     JS_ASSERT_IF(rangeEndSeconds == INT64_MIN, rangeStartSeconds == INT64_MIN);
826     JS_ASSERT_IF(rangeStartSeconds != INT64_MIN,
827                  rangeStartSeconds >= 0 && rangeEndSeconds >= 0);
828     JS_ASSERT_IF(rangeStartSeconds != INT64_MIN,
829                  rangeStartSeconds <= MAX_UNIX_TIMET && rangeEndSeconds <= MAX_UNIX_TIMET);
830
831 #ifdef JS_METER_DST_OFFSET_CACHING
832     JS_ASSERT(totalCalculations ==
833               hit +
834               missIncreasing + missDecreasing +
835               missIncreasingOffsetChangeExpand + missIncreasingOffsetChangeUpper +
836               missDecreasingOffsetChangeExpand + missDecreasingOffsetChangeLower +
837               missLargeIncrease + missLargeDecrease);
838 #endif
839 }
840
841 #ifdef JS_METER_DST_OFFSET_CACHING
842 void
843 DSTOffsetCache::dumpStats()
844 {
845     if (!getenv("JS_METER_DST_OFFSET_CACHING"))
846         return;
847     FILE *fp = fopen("/tmp/dst-offset-cache.stats", "a");
848     if (!fp)
849         return;
850     typedef unsigned long UL;
851     fprintf(fp,
852             "hit:\n"
853             "  in range: %lu\n"
854             "misses:\n"
855             "  increase range end:                 %lu\n"
856             "  decrease range start:               %lu\n"
857             "  increase, offset change, expand:    %lu\n"
858             "  increase, offset change, new range: %lu\n"
859             "  decrease, offset change, expand:    %lu\n"
860             "  decrease, offset change, new range: %lu\n"
861             "  large increase:                     %lu\n"
862             "  large decrease:                     %lu\n"
863             "total: %lu\n\n",
864             UL(hit),
865             UL(missIncreasing), UL(missDecreasing),
866             UL(missIncreasingOffsetChangeExpand), UL(missIncreasingOffsetChangeUpper),
867             UL(missDecreasingOffsetChangeExpand), UL(missDecreasingOffsetChangeLower),
868             UL(missLargeIncrease), UL(missLargeDecrease),
869             UL(totalCalculations));
870     fclose(fp);
871 }
872 #endif