PageRenderTime 111ms CodeModel.GetById 75ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/js/prmjtime.cpp

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
C++ | 846 lines | 496 code | 118 blank | 232 comment | 58 complexity | 24cba67ab368621d97c7032e5a7025ce MD5 | raw file
  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#include "jsstddef.h"
 44#ifdef SOLARIS
 45#define _REENTRANT 1
 46#endif
 47#include <string.h>
 48#include <time.h>
 49#include "jstypes.h"
 50#include "jsutil.h"
 51
 52#include "jsprf.h"
 53#include "jslock.h"
 54#include "prmjtime.h"
 55
 56#define PRMJ_DO_MILLISECONDS 1
 57
 58#ifdef XP_OS2
 59#include <sys/timeb.h>
 60#endif
 61#ifdef XP_WIN
 62#include <windef.h>
 63#include <winbase.h>
 64#include <math.h>     /* for fabs */
 65#include <mmsystem.h> /* for timeBegin/EndPeriod */
 66/* VC++ 8.0 or later, and not WINCE */
 67#if _MSC_VER >= 1400 && !defined(WINCE)
 68#define NS_HAVE_INVALID_PARAMETER_HANDLER 1
 69#endif
 70#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
 71#include <stdlib.h>   /* for _set_invalid_parameter_handler */
 72#include <crtdbg.h>   /* for _CrtSetReportMode */
 73#endif
 74
 75#ifdef JS_THREADSAFE
 76#include <prinit.h>
 77#endif
 78
 79#endif
 80
 81#if defined(XP_UNIX) || defined(XP_BEOS)
 82
 83#ifdef _SVID_GETTOD   /* Defined only on Solaris, see Solaris <sys/types.h> */
 84extern int gettimeofday(struct timeval *tv);
 85#endif
 86
 87#include <sys/time.h>
 88
 89#endif /* XP_UNIX */
 90
 91#define PRMJ_YEAR_DAYS 365L
 92#define PRMJ_FOUR_YEARS_DAYS (4 * PRMJ_YEAR_DAYS + 1)
 93#define PRMJ_CENTURY_DAYS (25 * PRMJ_FOUR_YEARS_DAYS - 1)
 94#define PRMJ_FOUR_CENTURIES_DAYS (4 * PRMJ_CENTURY_DAYS + 1)
 95#define PRMJ_HOUR_SECONDS  3600L
 96#define PRMJ_DAY_SECONDS  (24L * PRMJ_HOUR_SECONDS)
 97#define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * PRMJ_YEAR_DAYS)
 98#define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */
 99
100/* function prototypes */
101static void PRMJ_basetime(JSInt64 tsecs, PRMJTime *prtm);
102/*
103 * get the difference in seconds between this time zone and UTC (GMT)
104 */
105JSInt32
106PRMJ_LocalGMTDifference()
107{
108    struct tm ltime;
109
110    /* get the difference between this time zone and GMT */
111    memset((char *)&ltime,0,sizeof(ltime));
112    ltime.tm_mday = 2;
113    ltime.tm_year = 70;
114    return (JSInt32)mktime(&ltime) - (24L * 3600L);
115}
116
117/* Constants for GMT offset from 1970 */
118#define G1970GMTMICROHI        0x00dcdcad /* micro secs to 1970 hi */
119#define G1970GMTMICROLOW       0x8b3fa000 /* micro secs to 1970 low */
120
121#define G2037GMTMICROHI        0x00e45fab /* micro secs to 2037 high */
122#define G2037GMTMICROLOW       0x7a238000 /* micro secs to 2037 low */
123
124/* Convert from base time to extended time */
125static JSInt64
126PRMJ_ToExtendedTime(JSInt32 base_time)
127{
128    JSInt64 exttime;
129    JSInt64 g1970GMTMicroSeconds;
130    JSInt64 low;
131    JSInt32 diff;
132    JSInt64  tmp;
133    JSInt64  tmp1;
134
135    diff = PRMJ_LocalGMTDifference();
136    JSLL_UI2L(tmp, PRMJ_USEC_PER_SEC);
137    JSLL_I2L(tmp1,diff);
138    JSLL_MUL(tmp,tmp,tmp1);
139
140    JSLL_UI2L(g1970GMTMicroSeconds,G1970GMTMICROHI);
141    JSLL_UI2L(low,G1970GMTMICROLOW);
142#ifndef JS_HAVE_LONG_LONG
143    JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,16);
144    JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,16);
145#else
146    JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,32);
147#endif
148    JSLL_ADD(g1970GMTMicroSeconds,g1970GMTMicroSeconds,low);
149
150    JSLL_I2L(exttime,base_time);
151    JSLL_ADD(exttime,exttime,g1970GMTMicroSeconds);
152    JSLL_SUB(exttime,exttime,tmp);
153    return exttime;
154}
155
156#ifdef XP_WIN
157typedef struct CalibrationData
158{
159    long double freq;         /* The performance counter frequency */
160    long double offset;       /* The low res 'epoch' */
161    long double timer_offset; /* The high res 'epoch' */
162
163    /* The last high res time that we returned since recalibrating */
164    JSInt64 last;
165
166    JSBool calibrated;
167
168#ifdef JS_THREADSAFE
169    CRITICAL_SECTION data_lock;
170    CRITICAL_SECTION calibration_lock;
171#endif
172} CalibrationData;
173
174static const JSInt64 win2un = JSLL_INIT(0x19DB1DE, 0xD53E8000);
175
176static CalibrationData calibration = { 0 };
177
178#define FILETIME2INT64(ft) (((JSInt64)ft.dwHighDateTime) << 32LL | (JSInt64)ft.dwLowDateTime)
179
180static void
181NowCalibrate()
182{
183    FILETIME ft, ftStart;
184    LARGE_INTEGER liFreq, now;
185
186    if (calibration.freq == 0.0) {
187        if(!QueryPerformanceFrequency(&liFreq)) {
188            /* High-performance timer is unavailable */
189            calibration.freq = -1.0;
190        } else {
191            calibration.freq = (long double) liFreq.QuadPart;
192        }
193    }
194    if (calibration.freq > 0.0) {
195        JSInt64 calibrationDelta = 0;
196
197        /* By wrapping a timeBegin/EndPeriod pair of calls around this loop,
198           the loop seems to take much less time (1 ms vs 15ms) on Vista. */
199        timeBeginPeriod(1);
200        GetSystemTimeAsFileTime(&ftStart);
201        do {
202            GetSystemTimeAsFileTime(&ft);
203        } while (memcmp(&ftStart,&ft, sizeof(ft)) == 0);
204        timeEndPeriod(1);
205
206        /*
207        calibrationDelta = (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10;
208        fprintf(stderr, "Calibration delta was %I64d us\n", calibrationDelta);
209        */
210
211        QueryPerformanceCounter(&now);
212
213        calibration.offset = (long double) FILETIME2INT64(ft);
214        calibration.timer_offset = (long double) now.QuadPart;
215
216        /* The windows epoch is around 1600. The unix epoch is around
217           1970. win2un is the difference (in windows time units which
218           are 10 times more highres than the JS time unit) */
219        calibration.offset -= win2un;
220        calibration.offset *= 0.1;
221        calibration.last = 0;
222
223        calibration.calibrated = JS_TRUE;
224    }
225}
226
227#define CALIBRATIONLOCK_SPINCOUNT 0
228#define DATALOCK_SPINCOUNT 4096
229#define LASTLOCK_SPINCOUNT 4096
230
231#ifdef JS_THREADSAFE
232static PRStatus
233NowInit(void)
234{
235    memset(&calibration, 0, sizeof(calibration));
236    NowCalibrate();
237    InitializeCriticalSectionAndSpinCount(&calibration.calibration_lock, CALIBRATIONLOCK_SPINCOUNT);
238    InitializeCriticalSectionAndSpinCount(&calibration.data_lock, DATALOCK_SPINCOUNT);
239    return PR_SUCCESS;
240}
241
242void
243PRMJ_NowShutdown()
244{
245    DeleteCriticalSection(&calibration.calibration_lock);
246    DeleteCriticalSection(&calibration.data_lock);
247}
248
249#define MUTEX_LOCK(m) EnterCriticalSection(m)
250#define MUTEX_TRYLOCK(m) TryEnterCriticalSection(m)
251#define MUTEX_UNLOCK(m) LeaveCriticalSection(m)
252#define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m),(c))
253
254static PRCallOnceType calibrationOnce = { 0 };
255
256#else
257
258#define MUTEX_LOCK(m)
259#define MUTEX_TRYLOCK(m) 1
260#define MUTEX_UNLOCK(m)
261#define MUTEX_SETSPINCOUNT(m, c)
262
263#endif
264
265
266#endif /* XP_WIN */
267
268/*
269
270Win32 python-esque pseudo code
271Please see bug 363258 for why the win32 timing code is so complex.
272
273calibration mutex : Win32CriticalSection(spincount=0)
274data mutex : Win32CriticalSection(spincount=4096)
275
276def NowInit():
277  init mutexes
278  PRMJ_NowCalibration()
279
280def NowCalibration():
281  expensive up-to-15ms call
282
283def PRMJ_Now():
284  returnedTime = 0
285  needCalibration = False
286  cachedOffset = 0.0
287  calibrated = False
288  PR_CallOnce(PRMJ_NowInit)
289  do
290    if not global.calibrated or needCalibration:
291      acquire calibration mutex
292        acquire data mutex
293
294          // Only recalibrate if someone didn't already
295          if cachedOffset == calibration.offset:
296            // Have all waiting threads immediately wait
297            set data mutex spin count = 0
298            PRMJ_NowCalibrate()
299            calibrated = 1
300
301            set data mutex spin count = default
302        release data mutex
303      release calibration mutex
304
305    calculate lowres time
306
307    if highres timer available:
308      acquire data mutex
309        calculate highres time
310        cachedOffset = calibration.offset
311        highres time = calibration.last = max(highres time, calibration.last)
312      release data mutex
313
314      get kernel tick interval
315
316      if abs(highres - lowres) < kernel tick:
317        returnedTime = highres time
318        needCalibration = False
319      else:
320        if calibrated:
321          returnedTime = lowres
322          needCalibration = False
323        else:
324          needCalibration = True
325    else:
326      returnedTime = lowres
327  while needCalibration
328
329*/
330
331JSInt64
332PRMJ_Now(void)
333{
334#ifdef XP_OS2
335    JSInt64 s, us, ms2us, s2us;
336    struct timeb b;
337#endif
338#ifdef XP_WIN
339    static int nCalls = 0;
340    long double lowresTime, highresTimerValue;
341    FILETIME ft;
342    LARGE_INTEGER now;
343    JSBool calibrated = JS_FALSE;
344    JSBool needsCalibration = JS_FALSE;
345    JSInt64 returnedTime;
346    long double cachedOffset = 0.0;
347#endif
348#if defined(XP_UNIX) || defined(XP_BEOS)
349    struct timeval tv;
350    JSInt64 s, us, s2us;
351#endif /* XP_UNIX */
352
353#ifdef XP_OS2
354    ftime(&b);
355    JSLL_UI2L(ms2us, PRMJ_USEC_PER_MSEC);
356    JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC);
357    JSLL_UI2L(s, b.time);
358    JSLL_UI2L(us, b.millitm);
359    JSLL_MUL(us, us, ms2us);
360    JSLL_MUL(s, s, s2us);
361    JSLL_ADD(s, s, us);
362    return s;
363#endif
364#ifdef XP_WIN
365
366    /* To avoid regressing startup time (where high resolution is likely
367       not needed), give the old behavior for the first few calls.
368       This does not appear to be needed on Vista as the timeBegin/timeEndPeriod
369       calls seem to immediately take effect. */
370    int thiscall = JS_ATOMIC_INCREMENT(&nCalls);
371    /* 10 seems to be the number of calls to load with a blank homepage */
372    if (thiscall <= 10) {
373        GetSystemTimeAsFileTime(&ft);
374        return (FILETIME2INT64(ft)-win2un)/10L;
375    }
376
377    /* For non threadsafe platforms, NowInit is not necessary */
378#ifdef JS_THREADSAFE
379    PR_CallOnce(&calibrationOnce, NowInit);
380#endif
381    do {
382        if (!calibration.calibrated || needsCalibration) {
383            MUTEX_LOCK(&calibration.calibration_lock);
384            MUTEX_LOCK(&calibration.data_lock);
385
386            /* Recalibrate only if no one else did before us */
387            if(calibration.offset == cachedOffset) {
388                /* Since calibration can take a while, make any other
389                   threads immediately wait */
390                MUTEX_SETSPINCOUNT(&calibration.data_lock, 0);
391
392                NowCalibrate();
393
394                calibrated = JS_TRUE;
395
396                /* Restore spin count */
397                MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT);
398            }
399            MUTEX_UNLOCK(&calibration.data_lock);
400            MUTEX_UNLOCK(&calibration.calibration_lock);
401        }
402
403
404        /* Calculate a low resolution time */
405        GetSystemTimeAsFileTime(&ft);
406        lowresTime = 0.1*(long double)(FILETIME2INT64(ft) - win2un);
407
408        if (calibration.freq > 0.0) {
409            long double highresTime, diff;
410
411            DWORD timeAdjustment, timeIncrement;
412            BOOL timeAdjustmentDisabled;
413
414            /* Default to 15.625 ms if the syscall fails */
415            long double skewThreshold = 15625.25;
416            /* Grab high resolution time */
417            QueryPerformanceCounter(&now);
418            highresTimerValue = (long double)now.QuadPart;
419
420            MUTEX_LOCK(&calibration.data_lock);
421            highresTime = calibration.offset + PRMJ_USEC_PER_SEC*
422                 (highresTimerValue-calibration.timer_offset)/calibration.freq;
423            cachedOffset = calibration.offset;
424
425            /* On some dual processor/core systems, we might get an earlier time
426               so we cache the last time that we returned */
427            calibration.last = max(calibration.last,(JSInt64)highresTime);
428            returnedTime = calibration.last;
429            MUTEX_UNLOCK(&calibration.data_lock);
430
431            /* Rather than assume the NT kernel ticks every 15.6ms, ask it */
432            if (GetSystemTimeAdjustment(&timeAdjustment,
433                                        &timeIncrement,
434                                        &timeAdjustmentDisabled)) {
435                if (timeAdjustmentDisabled) {
436                    /* timeAdjustment is in units of 100ns */
437                    skewThreshold = timeAdjustment/10.0;
438                } else {
439                    /* timeIncrement is in units of 100ns */
440                    skewThreshold = timeIncrement/10.0;
441                }
442            }
443
444            /* Check for clock skew */
445            diff = lowresTime - highresTime;
446
447            /* For some reason that I have not determined, the skew can be
448               up to twice a kernel tick. This does not seem to happen by
449               itself, but I have only seen it triggered by another program
450               doing some kind of file I/O. The symptoms are a negative diff
451               followed by an equally large positive diff. */
452            if (fabs(diff) > 2*skewThreshold) {
453                /*fprintf(stderr,"Clock skew detected (diff = %f)!\n", diff);*/
454
455                if (calibrated) {
456                    /* If we already calibrated once this instance, and the
457                       clock is still skewed, then either the processor(s) are
458                       wildly changing clockspeed or the system is so busy that
459                       we get switched out for long periods of time. In either
460                       case, it would be infeasible to make use of high
461                       resolution results for anything, so let's resort to old
462                       behavior for this call. It's possible that in the
463                       future, the user will want the high resolution timer, so
464                       we don't disable it entirely. */
465                    returnedTime = (JSInt64)lowresTime;
466                    needsCalibration = JS_FALSE;
467                } else {
468                    /* It is possible that when we recalibrate, we will return a
469                       value less than what we have returned before; this is
470                       unavoidable. We cannot tell the different between a
471                       faulty QueryPerformanceCounter implementation and user
472                       changes to the operating system time. Since we must
473                       respect user changes to the operating system time, we
474                       cannot maintain the invariant that Date.now() never
475                       decreases; the old implementation has this behavior as
476                       well. */
477                    needsCalibration = JS_TRUE;
478                }
479            } else {
480                /* No detectable clock skew */
481                returnedTime = (JSInt64)highresTime;
482                needsCalibration = JS_FALSE;
483            }
484        } else {
485            /* No high resolution timer is available, so fall back */
486            returnedTime = (JSInt64)lowresTime;
487        }
488    } while (needsCalibration);
489
490    return returnedTime;
491#endif
492
493#if defined(XP_UNIX) || defined(XP_BEOS)
494#ifdef _SVID_GETTOD   /* Defined only on Solaris, see Solaris <sys/types.h> */
495    gettimeofday(&tv);
496#else
497    gettimeofday(&tv, 0);
498#endif /* _SVID_GETTOD */
499    JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC);
500    JSLL_UI2L(s, tv.tv_sec);
501    JSLL_UI2L(us, tv.tv_usec);
502    JSLL_MUL(s, s, s2us);
503    JSLL_ADD(s, s, us);
504    return s;
505#endif /* XP_UNIX */
506}
507
508/* Get the DST timezone offset for the time passed in */
509JSInt64
510PRMJ_DSTOffset(JSInt64 local_time)
511{
512    JSInt64 us2s;
513    time_t local;
514    JSInt32 diff;
515    JSInt64  maxtimet;
516    struct tm tm;
517    PRMJTime prtm;
518#ifndef HAVE_LOCALTIME_R
519    struct tm *ptm;
520#endif
521
522
523    JSLL_UI2L(us2s, PRMJ_USEC_PER_SEC);
524    JSLL_DIV(local_time, local_time, us2s);
525
526    /* get the maximum of time_t value */
527    JSLL_UI2L(maxtimet,PRMJ_MAX_UNIX_TIMET);
528
529    if(JSLL_CMP(local_time,>,maxtimet)){
530        JSLL_UI2L(local_time,PRMJ_MAX_UNIX_TIMET);
531    } else if(!JSLL_GE_ZERO(local_time)){
532        /*go ahead a day to make localtime work (does not work with 0) */
533        JSLL_UI2L(local_time,PRMJ_DAY_SECONDS);
534    }
535    JSLL_L2UI(local,local_time);
536    PRMJ_basetime(local_time,&prtm);
537#ifndef HAVE_LOCALTIME_R
538    ptm = localtime(&local);
539    if(!ptm){
540        return 0;
541    }
542    tm = *ptm;
543#else
544    localtime_r(&local,&tm); /* get dst information */
545#endif
546
547    diff = ((tm.tm_hour - prtm.tm_hour) * PRMJ_HOUR_SECONDS) +
548	((tm.tm_min - prtm.tm_min) * 60);
549
550    if(diff < 0){
551	diff += PRMJ_DAY_SECONDS;
552    }
553
554    JSLL_UI2L(local_time,diff);
555
556    JSLL_MUL(local_time,local_time,us2s);
557
558    return(local_time);
559}
560
561#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
562static void
563PRMJ_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() */
574size_t
575PRMJ_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    /* Zero out the tm struct.  Linux, SunOS 4 struct tm has extra members int
587     * tm_gmtoff, char *tm_zone; when tm_zone is garbage, strftime gets
588     * confused and dumps core.  NSPR20 prtime.c attempts to fill these in by
589     * calling mktime on the partially filled struct, but this doesn't seem to
590     * work as well; the result string has "can't get timezone" for ECMA-valid
591     * years.  Might still make sense to use this, but find the range of years
592     * for which valid tz information exists, and map (per ECMA hint) from the
593     * given year into that range.
594
595     * N.B. This hasn't been tested with anything that actually _uses_
596     * tm_gmtoff; zero might be the wrong thing to set it to if you really need
597     * to format a time.  This fix is for jsdate.c, which only uses
598     * JS_FormatTime to get a string representing the time zone.  */
599    memset(&a, 0, sizeof(struct tm));
600
601    a.tm_sec = prtm->tm_sec;
602    a.tm_min = prtm->tm_min;
603    a.tm_hour = prtm->tm_hour;
604    a.tm_mday = prtm->tm_mday;
605    a.tm_mon = prtm->tm_mon;
606    a.tm_wday = prtm->tm_wday;
607
608    /*
609     * Years before 1900 and after 9999 cause strftime() to abort on Windows.
610     * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then
611     * replace matching substrings in the strftime() result with the real year.
612     * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit
613     * year formats (%y) work correctly (since we won't find the fake year
614     * in that case).
615     * e.g. new Date(1873, 0).toLocaleFormat('%Y %y') => "1873 73"
616     * See bug 327869.
617     */
618#define FAKE_YEAR_BASE 9900
619    if (prtm->tm_year < 1900 || prtm->tm_year > 9999) {
620        fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100;
621        a.tm_year = fake_tm_year - 1900;
622    }
623    else {
624        a.tm_year = prtm->tm_year - 1900;
625    }
626    a.tm_yday = prtm->tm_yday;
627    a.tm_isdst = prtm->tm_isdst;
628
629    /*
630     * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff
631     * are null.  This doesn't quite work, though - the timezone is off by
632     * tzoff + dst.  (And mktime seems to return -1 for the exact dst
633     * changeover time.)
634     */
635
636#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
637    oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
638    oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
639#endif
640
641    result = strftime(buf, buflen, fmt, &a);
642
643#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER
644    _set_invalid_parameter_handler(oldHandler);
645    _CrtSetReportMode(_CRT_ASSERT, oldReportMode);
646#endif
647
648    if (fake_tm_year && result) {
649        char real_year[16];
650        char fake_year[16];
651        size_t real_year_len;
652        size_t fake_year_len;
653        char* p;
654
655        sprintf(real_year, "%d", prtm->tm_year);
656        real_year_len = strlen(real_year);
657        sprintf(fake_year, "%d", fake_tm_year);
658        fake_year_len = strlen(fake_year);
659
660        /* Replace the fake year in the result with the real year. */
661        for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) {
662            size_t new_result = result + real_year_len - fake_year_len;
663            if ((int)new_result >= buflen) {
664                return 0;
665            }
666            memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len));
667            memcpy(p, real_year, real_year_len);
668            result = new_result;
669            *(buf + result) = '\0';
670        }
671    }
672#endif
673    return result;
674}
675
676/* table for number of days in a month */
677static int mtab[] = {
678    /* jan, feb,mar,apr,may,jun */
679    31,28,31,30,31,30,
680    /* july,aug,sep,oct,nov,dec */
681    31,31,30,31,30,31
682};
683
684/*
685 * basic time calculation functionality for localtime and gmtime
686 * setups up prtm argument with correct values based upon input number
687 * of seconds.
688 */
689static void
690PRMJ_basetime(JSInt64 tsecs, PRMJTime *prtm)
691{
692    /* convert tsecs back to year,month,day,hour,secs */
693    JSInt32 year    = 0;
694    JSInt32 month   = 0;
695    JSInt32 yday    = 0;
696    JSInt32 mday    = 0;
697    JSInt32 wday    = 6; /* start on a Sunday */
698    JSInt32 days    = 0;
699    JSInt32 seconds = 0;
700    JSInt32 minutes = 0;
701    JSInt32 hours   = 0;
702    JSInt32 isleap  = 0;
703
704    /* Temporaries used for various computations */
705    JSInt64 result;
706    JSInt64	result1;
707    JSInt64	result2;
708
709    JSInt64 base;
710
711    /* Some variables for intermediate result storage to make computing isleap
712       easier/faster */
713    JSInt32 fourCenturyBlocks;
714    JSInt32 centuriesLeft;
715    JSInt32 fourYearBlocksLeft;
716    JSInt32 yearsLeft;
717
718    /* Since leap years work by 400/100/4 year intervals, precompute the length
719       of those in seconds if they start at the beginning of year 1. */
720    JSInt64 fourYears;
721    JSInt64 century;
722    JSInt64 fourCenturies;
723
724    JSLL_UI2L(result, PRMJ_DAY_SECONDS);
725
726    JSLL_I2L(fourYears, PRMJ_FOUR_YEARS_DAYS);
727    JSLL_MUL(fourYears, fourYears, result);
728
729    JSLL_I2L(century, PRMJ_CENTURY_DAYS);
730    JSLL_MUL(century, century, result);
731
732    JSLL_I2L(fourCenturies, PRMJ_FOUR_CENTURIES_DAYS);
733    JSLL_MUL(fourCenturies, fourCenturies, result);
734
735    /* get the base time via UTC */
736    base = PRMJ_ToExtendedTime(0);
737    JSLL_UI2L(result,  PRMJ_USEC_PER_SEC);
738    JSLL_DIV(base,base,result);
739    JSLL_ADD(tsecs,tsecs,base);
740
741    /* Compute our |year|, |isleap|, and part of |days|.  When this part is
742       done, |year| should hold the year our date falls in (number of whole
743       years elapsed before our date), isleap should hold 1 if the year the
744       date falls in is a leap year and 0 otherwise. */
745
746    /* First do year 0; it's special and nonleap. */
747    JSLL_UI2L(result, PRMJ_YEAR_SECONDS);
748    if (!JSLL_CMP(tsecs,<,result)) {
749        days = PRMJ_YEAR_DAYS;
750        year = 1;
751        JSLL_SUB(tsecs, tsecs, result);
752    }
753
754    /* Now use those constants we computed above */
755    JSLL_UDIVMOD(&result1, &result2, tsecs, fourCenturies);
756    JSLL_L2I(fourCenturyBlocks, result1);
757    year += fourCenturyBlocks * 400;
758    days += fourCenturyBlocks * PRMJ_FOUR_CENTURIES_DAYS;
759    tsecs = result2;
760
761    JSLL_UDIVMOD(&result1, &result2, tsecs, century);
762    JSLL_L2I(centuriesLeft, result1);
763    year += centuriesLeft * 100;
764    days += centuriesLeft * PRMJ_CENTURY_DAYS;
765    tsecs = result2;
766
767    JSLL_UDIVMOD(&result1, &result2, tsecs, fourYears);
768    JSLL_L2I(fourYearBlocksLeft, result1);
769    year += fourYearBlocksLeft * 4;
770    days += fourYearBlocksLeft * PRMJ_FOUR_YEARS_DAYS;
771    tsecs = result2;
772
773    /* Recall that |result| holds PRMJ_YEAR_SECONDS */
774    JSLL_UDIVMOD(&result1, &result2, tsecs, result);
775    JSLL_L2I(yearsLeft, result1);
776    year += yearsLeft;
777    days += yearsLeft * PRMJ_YEAR_DAYS;
778    tsecs = result2;
779
780    /* now compute isleap.  Note that we don't have to use %, since we've
781       already computed those remainders.  Also note that they're all offset by
782       1 because of the 1 for year 0. */
783    isleap =
784        (yearsLeft == 3) && (fourYearBlocksLeft != 24 || centuriesLeft == 3);
785    JS_ASSERT(isleap ==
786              ((year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)));
787
788    JSLL_UI2L(result1,PRMJ_DAY_SECONDS);
789
790    JSLL_DIV(result,tsecs,result1);
791    JSLL_L2I(mday,result);
792
793    /* let's find the month */
794    while(((month == 1 && isleap) ?
795            (mday >= mtab[month] + 1) :
796            (mday >= mtab[month]))){
797	 yday += mtab[month];
798	 days += mtab[month];
799
800	 mday -= mtab[month];
801
802         /* it's a Feb, check if this is a leap year */
803	 if(month == 1 && isleap != 0){
804	     yday++;
805	     days++;
806	     mday--;
807	 }
808	 month++;
809    }
810
811    /* now adjust tsecs */
812    JSLL_MUL(result,result,result1);
813    JSLL_SUB(tsecs,tsecs,result);
814
815    mday++; /* day of month always start with 1 */
816    days += mday;
817    wday = (days + wday) % 7;
818
819    yday += mday;
820
821    /* get the hours */
822    JSLL_UI2L(result1,PRMJ_HOUR_SECONDS);
823    JSLL_DIV(result,tsecs,result1);
824    JSLL_L2I(hours,result);
825    JSLL_MUL(result,result,result1);
826    JSLL_SUB(tsecs,tsecs,result);
827
828    /* get minutes */
829    JSLL_UI2L(result1,60);
830    JSLL_DIV(result,tsecs,result1);
831    JSLL_L2I(minutes,result);
832    JSLL_MUL(result,result,result1);
833    JSLL_SUB(tsecs,tsecs,result);
834
835    JSLL_L2I(seconds,tsecs);
836
837    prtm->tm_usec  = 0L;
838    prtm->tm_sec   = (JSInt8)seconds;
839    prtm->tm_min   = (JSInt8)minutes;
840    prtm->tm_hour  = (JSInt8)hours;
841    prtm->tm_mday  = (JSInt8)mday;
842    prtm->tm_mon   = (JSInt8)month;
843    prtm->tm_wday  = (JSInt8)wday;
844    prtm->tm_year  = (JSInt16)year;
845    prtm->tm_yday  = (JSInt16)yday;
846}