Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / js / src / prmjtime.cpp
blobd0114e8bf94c850d32530803794bcbea7584c8ab
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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.
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
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.
24 * Contributor(s):
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.
38 * ***** END LICENSE BLOCK ***** */
41 * PR time code.
43 #ifdef SOLARIS
44 #define _REENTRANT 1
45 #endif
46 #include <string.h>
47 #include <time.h>
49 #include "jsstdint.h"
50 #include "jstypes.h"
51 #include "jsutil.h"
53 #include "jsprf.h"
54 #include "jslock.h"
55 #include "prmjtime.h"
57 #define PRMJ_DO_MILLISECONDS 1
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
76 #ifdef JS_THREADSAFE
77 #include <prinit.h>
78 #endif
80 #endif
82 #if defined(XP_UNIX) || defined(XP_BEOS)
84 #ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */
85 extern int gettimeofday(struct timeval *tv);
86 #endif
88 #include <sys/time.h>
90 #endif /* XP_UNIX */
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 */
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)
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
117 * get the difference in seconds between this time zone and UTC (GMT)
119 JSInt32
120 PRMJ_LocalGMTDifference()
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.
128 _tzset();
129 #endif
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.
136 int day = 0;
137 struct tm tm;
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;
147 int time = (tm.tm_hour * 3600) + (tm.tm_min * 60) + tm.tm_sec;
148 time = PRMJ_DAY_SECONDS - time;
150 if (tm.tm_yday == day)
151 time -= PRMJ_DAY_SECONDS;
153 return time;
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 */
160 #define G2037GMTMICROHI 0x00e45fab /* micro secs to 2037 high */
161 #define G2037GMTMICROLOW 0x7a238000 /* micro secs to 2037 low */
163 #ifdef HAVE_SYSTEMTIMETOFILETIME
165 static const JSInt64 win2un = JSLL_INIT(0x19DB1DE, 0xD53E8000);
167 #define FILETIME2INT64(ft) (((JSInt64)ft.dwHighDateTime) << 32LL | (JSInt64)ft.dwLowDateTime)
169 #endif
171 #if defined(HAVE_GETSYSTEMTIMEASFILETIME) || defined(HAVE_SYSTEMTIMETOFILETIME)
173 #if defined(HAVE_GETSYSTEMTIMEASFILETIME)
174 inline void
175 LowResTime(LPFILETIME lpft)
177 GetSystemTimeAsFileTime(lpft);
179 #elif defined(HAVE_SYSTEMTIMETOFILETIME)
180 inline void
181 LowResTime(LPFILETIME lpft)
183 GetCurrentFT(lpft);
185 #else
186 #error "No implementation of PRMJ_Now was selected."
187 #endif
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' */
194 /* The last high res time that we returned since recalibrating */
195 JSInt64 last;
197 JSBool calibrated;
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;
208 static CalibrationData calibration = { 0 };
210 static void
211 NowCalibrate()
213 FILETIME ft, ftStart;
214 LARGE_INTEGER liFreq, now;
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;
224 if (calibration.freq > 0.0) {
225 JSInt64 calibrationDelta = 0;
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);
236 #ifdef WINCE
237 calibration.granularity = (FILETIME2INT64(ft) -
238 FILETIME2INT64(ftStart))/10;
239 #endif
241 calibrationDelta = (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10;
242 fprintf(stderr, "Calibration delta was %I64d us\n", calibrationDelta);
245 QueryPerformanceCounter(&now);
247 calibration.offset = (long double) FILETIME2INT64(ft);
248 calibration.timer_offset = (long double) now.QuadPart;
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;
257 calibration.calibrated = JS_TRUE;
261 #define CALIBRATIONLOCK_SPINCOUNT 0
262 #define DATALOCK_SPINCOUNT 4096
263 #define LASTLOCK_SPINCOUNT 4096
265 #ifdef JS_THREADSAFE
266 static PRStatus
267 NowInit(void)
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;
281 void
282 PRMJ_NowShutdown()
284 DeleteCriticalSection(&calibration.calibration_lock);
285 DeleteCriticalSection(&calibration.data_lock);
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
297 static PRCallOnceType calibrationOnce = { 0 };
299 #else
301 #define MUTEX_LOCK(m)
302 #define MUTEX_TRYLOCK(m) 1
303 #define MUTEX_UNLOCK(m)
304 #define MUTEX_SETSPINCOUNT(m, c)
306 #endif
308 #endif /* HAVE_GETSYSTEMTIMEASFILETIME */
311 #if defined(XP_OS2)
312 JSInt64
313 PRMJ_Now(void)
315 JSInt64 s, us, ms2us, s2us;
316 struct timeb b;
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;
329 #elif defined(XP_UNIX) || defined(XP_BEOS)
330 JSInt64
331 PRMJ_Now(void)
333 struct timeval tv;
334 JSInt64 s, us, s2us;
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;
349 #else
352 Win32 python-esque pseudo code
353 Please see bug 363258 for why the win32 timing code is so complex.
355 calibration mutex : Win32CriticalSection(spincount=0)
356 data mutex : Win32CriticalSection(spincount=4096)
358 def NowInit():
359 init mutexes
360 PRMJ_NowCalibration()
362 def NowCalibration():
363 expensive up-to-15ms call
365 def PRMJ_Now():
366 returnedTime = 0
367 needCalibration = False
368 cachedOffset = 0.0
369 calibrated = False
370 PR_CallOnce(PRMJ_NowInit)
372 if not global.calibrated or needCalibration:
373 acquire calibration mutex
374 acquire data mutex
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
383 set data mutex spin count = default
384 release data mutex
385 release calibration mutex
387 calculate lowres time
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
396 get kernel tick interval
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
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;
418 JSInt64
419 PRMJ_Now(void)
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;
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;
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);
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);
455 NowCalibrate();
457 calibrated = JS_TRUE;
459 /* Restore spin count */
460 MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT);
462 MUTEX_UNLOCK(&calibration.data_lock);
463 MUTEX_UNLOCK(&calibration.calibration_lock);
467 /* Calculate a low resolution time */
468 LowResTime(&ft);
469 lowresTime = 0.1*(long double)(FILETIME2INT64(ft) - win2un);
471 if (calibration.freq > 0.0) {
472 long double highresTime, diff;
474 DWORD timeAdjustment, timeIncrement;
475 BOOL timeAdjustmentDisabled;
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;
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;
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);
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;
510 #endif
511 /* Check for clock skew */
512 diff = lowresTime - highresTime;
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);*/
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;
546 } else {
547 /* No detectable clock skew */
548 returnedTime = (JSInt64)highresTime;
549 needsCalibration = JS_FALSE;
551 } else {
552 /* No high resolution timer is available, so fall back */
553 returnedTime = (JSInt64)lowresTime;
555 } while (needsCalibration);
557 return returnedTime;
559 #endif
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)
569 /* empty */
571 #endif
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)
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
586 memset(&a, 0, sizeof(struct tm));
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;
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).
600 #if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF)
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.
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);
622 a.tm_gmtoff = td.tm_gmtoff;
623 a.tm_zone = td.tm_zone;
625 #endif
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.
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;
642 else {
643 a.tm_year = prtm->tm_year - 1900;
645 a.tm_yday = prtm->tm_yday;
646 a.tm_isdst = prtm->tm_isdst;
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.)
655 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
656 oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
657 oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
658 #endif
660 result = strftime(buf, buflen, fmt, &a);
662 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
663 _set_invalid_parameter_handler(oldHandler);
664 _CrtSetReportMode(_CRT_ASSERT, oldReportMode);
665 #endif
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;
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);
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;
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';
691 #endif
692 return result;
695 JSInt64
696 DSTOffsetCache::computeDSTOffsetMilliseconds(int64 localTimeSeconds)
698 JS_ASSERT(localTimeSeconds >= 0);
699 JS_ASSERT(localTimeSeconds <= MAX_UNIX_TIMET);
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.
707 _tzset();
708 #endif
710 struct tm tm;
711 if (!ComputeLocalTime(static_cast<time_t>(localTimeSeconds), &tm))
712 return 0;
714 JSInt32 base = PRMJ_LocalGMTDifference();
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);
720 JSInt32 diff = tmoff - dayoff;
722 if (diff < 0)
723 diff += SECONDS_PER_DAY;
725 return diff * MILLISECONDS_PER_SECOND;
728 JSInt64
729 DSTOffsetCache::getDSTOffsetMilliseconds(JSInt64 localTimeMilliseconds, JSContext *cx)
731 sanityCheck();
732 noteOffsetCalculation();
734 JSInt64 localTimeSeconds = localTimeMilliseconds / MILLISECONDS_PER_SECOND;
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;
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.
749 if (rangeStartSeconds <= localTimeSeconds &&
750 localTimeSeconds <= rangeEndSeconds) {
751 noteCacheHit();
752 return offsetMilliseconds;
755 if (oldRangeStartSeconds <= localTimeSeconds &&
756 localTimeSeconds <= oldRangeEndSeconds) {
757 noteCacheHit();
758 return oldOffsetMilliseconds;
761 oldOffsetMilliseconds = offsetMilliseconds;
762 oldRangeStartSeconds = rangeStartSeconds;
763 oldRangeEndSeconds = rangeEndSeconds;
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;
775 offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
776 if (offsetMilliseconds == endOffsetMilliseconds) {
777 noteCacheMissIncreasingOffsetChangeUpper();
778 rangeStartSeconds = localTimeSeconds;
779 rangeEndSeconds = newEndSeconds;
780 } else {
781 noteCacheMissIncreasingOffsetChangeExpand();
782 rangeEndSeconds = localTimeSeconds;
784 return offsetMilliseconds;
787 noteCacheMissLargeIncrease();
788 offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
789 rangeStartSeconds = rangeEndSeconds = localTimeSeconds;
790 return offsetMilliseconds;
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;
802 offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
803 if (offsetMilliseconds == startOffsetMilliseconds) {
804 noteCacheMissDecreasingOffsetChangeLower();
805 rangeStartSeconds = newStartSeconds;
806 rangeEndSeconds = localTimeSeconds;
807 } else {
808 noteCacheMissDecreasingOffsetChangeExpand();
809 rangeStartSeconds = localTimeSeconds;
811 return offsetMilliseconds;
814 noteCacheMissLargeDecrease();
815 rangeStartSeconds = rangeEndSeconds = localTimeSeconds;
816 offsetMilliseconds = computeDSTOffsetMilliseconds(localTimeSeconds);
817 return offsetMilliseconds;
820 void
821 DSTOffsetCache::sanityCheck()
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);
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
841 #ifdef JS_METER_DST_OFFSET_CACHING
842 void
843 DSTOffsetCache::dumpStats()
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);
872 #endif