/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
 * JS date methods.
 */
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "prtypes.h"
#include "prprf.h"
#include "prmjtime.h"
#include "prlog.h"
#include "jsapi.h"
#include "jsconfig.h"
#include "jscntxt.h"
#include "jsdate.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsstr.h"

#include <stdio.h>

/*
 * The JS 'Date' object is patterned after the Java 'Date' object.
 * Here is an script:
 *
 *    today = new Date();
 *
 *    print(today.toLocaleString());
 *
 *    weekDay = today.getDay();
 *
 *
 * These Java (and ECMA-262) methods are supported:
 *
 *     UTC
 *     getDate (getUTCDate)
 *     getDay (getUTCDay)
 *     getHours (getUTCHours)
 *     getMinutes (getUTCMinutes)
 *     getMonth (getUTCMonth)
 *     getSeconds (getUTCSeconds)
 *     getMilliseconds (getUTCMilliseconds)
 *     getTime
 *     getTimezoneOffset
 *     getYear
 *     getFullYear (getUTCFullYear)
 *     parse
 *     setDate (setUTCDate)
 *     setHours (setUTCHours)
 *     setMinutes (setUTCMinutes)
 *     setMonth (setUTCMonth)
 *     setSeconds (setUTCSeconds)
 *     setMilliseconds (setUTCMilliseconds)
 *     setTime
 *     setYear (setFullYear, setUTCFullYear)
 *     toGMTString (toUTCString)
 *     toLocaleString
 *     toString
 *
 *
 * These Java methods are not supported
 *
 *     setDay
 *     before
 *     after
 *     equals
 *     hashCode
 */

/*
 * 11/97 - jsdate.c has been rewritten to conform to the ECMA-262
 * language definition and reduce dependence on NSPR.  NSPR is used to
 * get the current time in milliseconds, the time zone offset, and the
 * daylight savings time offset for a given time.  NSPR is also used
 * for Date.toLocaleString() (only), for locale-specific formatting.

 * To do:
 * (I did some performance tests by timing how long it took to run what
 *  I had of the js ECMA conformance tests.)
 *
 * - look at saving results across multiple calls to supporting
 * functions; the toString functions compute some of the same values
 * multiple times.  Although - I took a quick stab at this, and I lost
 * rather than gained.  (Fractionally.)  Hard to tell what compilers/processors
 * are doing these days.
 * - look at tweaking function return types to return double instead
 * of int; this seems to make things run slightly faster sometimes.
 * (though it could be architecture-dependent.)  It'd be good to see
 * how this does on win32.  (Tried it on irix.)  Types could use a
 * general going-over.
 */

/*
 * Supporting functions - ECMA 15.9.1.*
 */

#define HalfTimeDomain  8.64e15
#define HoursPerDay     24.0
#define MinutesPerDay   (HoursPerDay * MinutesPerHour)
#define MinutesPerHour  60.0
#define SecondsPerDay   (MinutesPerDay * SecondsPerMinute)
#define SecondsPerHour  (MinutesPerHour * SecondsPerMinute)
#define SecondsPerMinute 60.0
#define msPerDay        (SecondsPerDay * msPerSecond)
#define msPerHour       (SecondsPerHour * msPerSecond)
#define msPerMinute     (SecondsPerMinute * msPerSecond)
#define msPerSecond     1000.0

#define Day(t)          floor((t) / msPerDay)

static jsdouble
TimeWithinDay(jsdouble t)
{
    jsdouble result;
    result = fmod(t, msPerDay);
    if (result < 0)
        result += msPerDay;
    return result;
}

#define DaysInYear(y)   ((y) % 4 == 0 && ((y) % 100 || ((y) % 400 == 0))  \
                         ? 366 : 365)

/* math here has to be f.p, because we need
 *  floor((1968 - 1969) / 4) == -1
 */
#define DayFromYear(y)  (365 * ((y)-1970) + floor(((y)-1969)/4.0)            \
                         - floor(((y)-1901)/100.0) + floor(((y)-1601)/400.0))
#define TimeFromYear(y) (DayFromYear(y) * msPerDay)

static jsint
YearFromTime(jsdouble t)
{
    jsint lo = (jsint) floor((t / msPerDay) / 366) + 1970;
    jsint hi = (jsint) floor((t / msPerDay) / 365) + 1970;
    jsint mid;

    /* above doesn't work for negative dates... */
    if (hi < lo) {
        jsint temp = lo;
        lo = hi;
        hi = temp;
    }

    /* Use a simple binary search algorithm to find the right
       year.  This seems like brute force... but the computation
       of hi and lo years above lands within one year of the
       correct answer for years within a thousand years of
       1970; the loop below only requires six iterations
       for year 270000. */
    while (hi > lo) {
        mid = (hi + lo) / 2;
        if (TimeFromYear(mid) > t) {
            hi = mid - 1;
        } else {
            if (TimeFromYear(mid) <= t) {
                jsint temp = mid + 1;
                if (TimeFromYear(temp) > t) {
                    return mid;
                }
                lo = mid + 1;
            }
        }
    }
    return lo;
}

#define InLeapYear(t)   (JSBool) (DaysInYear(YearFromTime(t)) == 366)

#define DayWithinYear(t, year) ((intN) (Day(t) - DayFromYear(year)))

/*
 * The following array contains the day of year for the first day of
 * each month, where index 0 is January, and day 0 is January 1.
 */
static jsdouble firstDayOfMonth[2][12] = {
    {0.0, 31.0, 59.0, 90.0, 120.0, 151.0, 181.0, 212.0, 243.0, 273.0, 304.0, 334.0},
    {0.0, 31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0}
};

#define DayFromMonth(m, leap) firstDayOfMonth[leap][(intN)m];

static intN
MonthFromTime(jsdouble t)
{
    intN d, step;
    jsint year = YearFromTime(t);
    d = DayWithinYear(t, year);

    if (d < (step = 31))
        return 0;
    step += (InLeapYear(t) ? 29 : 28);
    if (d < step)
        return 1;
    if (d < (step += 31))
        return 2;
    if (d < (step += 30))
        return 3;
    if (d < (step += 31))
        return 4;
    if (d < (step += 30))
        return 5;
    if (d < (step += 31))
        return 6;
    if (d < (step += 31))
        return 7;
    if (d < (step += 30))
        return 8;
    if (d < (step += 31))
        return 9;
    if (d < (step += 30))
        return 10;
    return 11;
}

static intN
DateFromTime(jsdouble t)
{
    intN d, step, next;
    jsint year = YearFromTime(t);
    d = DayWithinYear(t, year);

    if (d <= (next = 30))
        return d + 1;
    step = next;
    next += (InLeapYear(t) ? 29 : 28);
    if (d <= next)
        return d - step;
    step = next;
    if (d <= (next += 31))
        return d - step;
    step = next;
    if (d <= (next += 30))
        return d - step;
    step = next;
    if (d <= (next += 31))
        return d - step;
    step = next;
    if (d <= (next += 30))
        return d - step;
    step = next;
    if (d <= (next += 31))
        return d - step;
    step = next;
    if (d <= (next += 31))
        return d - step;
    step = next;
    if (d <= (next += 30))
        return d - step;
    step = next;
    if (d <= (next += 31))
        return d - step;
    step = next;
    if (d <= (next += 30))
        return d - step;
    step = next;
    return d - step;
}

static intN
WeekDay(jsdouble t)
{
    jsint result;
    result = (jsint) Day(t) + 4;
    result = result % 7;
    if (result < 0)
        result += 7;
    return (intN) result;
}

/* LocalTZA gets set by js_InitDateClass() */
static jsdouble LocalTZA;

static jsdouble
DaylightSavingTA(jsdouble t)
{
    int64 PR_t;
    int64 ms2us;
    int64 offset;
    jsdouble result;

    /* abort if NaN */
    if (t != t)
        return t;

    /* put our t in an LL, and map it to usec for prtime */
    LL_D2L(PR_t, t);
    LL_I2L(ms2us, PRMJ_USEC_PER_MSEC);
    LL_MUL(PR_t, PR_t, ms2us);

    offset = PRMJ_DSTOffset(PR_t);

    LL_DIV(offset, offset, ms2us);
    LL_L2D(result, offset);
    return result;
}

#define LocalTime(t)    ((t) + LocalTZA + DaylightSavingTA(t))
/* #define UTC(t)          ((t) - LocalTZA - DaylightSavingTA((t) - LocalTZA)) */
static jsdouble
UTC(jsdouble t)
{
/*     fprintf(stderr, "time %f\n", t); */
/*     fprintf(stderr, "LocalTZA %f\n", LocalTZA); */
/*     fprintf(stderr, "DaylightSavingTA %f\n", DaylightSavingTA(t - LocalTZA)); */

    return t - LocalTZA - DaylightSavingTA(t - LocalTZA);
}

static intN
HourFromTime(jsdouble t)
{
    intN result = (intN) fmod(floor(t/msPerHour), HoursPerDay);
    if (result < 0)
        result += (intN)HoursPerDay;
    return result;
}

static intN
MinFromTime(jsdouble t)
{
    intN result = (intN) fmod(floor(t / msPerMinute), MinutesPerHour);
    if (result < 0)
        result += (intN)MinutesPerHour;
    return result;
}

static intN
SecFromTime(jsdouble t)
{
    intN result = (intN) fmod(floor(t / msPerSecond), SecondsPerMinute);
    if (result < 0)
        result += (intN)SecondsPerMinute;
    return result;
}

static intN
msFromTime(jsdouble t)
{
    intN result = (intN) fmod(t, msPerSecond);
    if (result < 0)
        result += (intN)msPerSecond;
    return result;
}

#define MakeTime(hour, min, sec, ms) \
(((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms)

static jsdouble
MakeDay(jsdouble year, jsdouble month, jsdouble date)
{
    jsdouble result;
    JSBool leap;
    jsdouble yearday;
    jsdouble monthday;

    year += floor(month / 12);

    month = fmod(month, 12);
    if (month < 0)
        month += 12;

    leap = (DaysInYear((jsint) year) == 366);

    yearday = floor(TimeFromYear(year) / msPerDay);
    monthday = DayFromMonth(month, leap);

    result = yearday
             + monthday
             + date - 1;
    return result;
}

#define MakeDate(day, time) (day * msPerDay + time)

#define TIMECLIP(d) ((JSDOUBLE_IS_FINITE(d) \
                      && !((d < 0 ? -d : d) > HalfTimeDomain)) \
                     ? js_DoubleToInteger(d + (+0.)) : *(cx->runtime->jsNaN))

/**
 * end of ECMA 'support' functions
 */

/*
 * Other Support routines and definitions
 */

static JSClass date_class = {
    "Date",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,
    JS_EnumerateStub, JS_ResolveStub,   JS_ConvertStub,   JS_FinalizeStub
};

/* for use by date_parse */

static char* wtb[] = {
    "am", "pm",
    "monday", "tuesday", "wednesday", "thursday", "friday",
    "saturday", "sunday",
    "january", "february", "march", "april", "may", "june",
    "july", "august", "september", "october", "november", "december",
    "gmt", "ut", "utc",
    "est", "edt",
    "cst", "cdt",
    "mst", "mdt",
    "pst", "pdt"
    /* time zone table needs to be expanded */
};

static int ttb[] = {
    0, 1, 0, 0, 0, 0, 0, 0, 0,
    2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
    10000 + 0, 10000 + 0, 10000 + 0,   /* GMT/UT/UTC */
    10000 + 5 * 60, 10000 + 4 * 60,    /* EST/EDT */
    10000 + 6 * 60, 10000 + 5 * 60,    /* CST/CDT */
    10000 + 7 * 60, 10000 + 6 * 60,    /* MST/MDT */
    10000 + 8 * 60, 10000 + 7 * 60     /* PST/PDT */
};

/* helper for date_parse */
static JSBool
date_regionMatches(const char* s1, int s1off, const jschar* s2, int s2off,
                   int count, int ignoreCase)
{
    JSBool result = JS_FALSE;
    /* return true if matches, otherwise, false */

    while (count > 0 && s1[s1off] && s2[s2off]) {
        if (ignoreCase) {
            if (JS_TOLOWER((jschar)s1[s1off]) != JS_TOLOWER(s2[s2off])) {
                break;
            }
        } else {
            if ((jschar)s1[s1off] != s2[s2off]) {
                break;
            }
        }
        s1off++;
        s2off++;
        count--;
    }

    if (count == 0) {
        result = JS_TRUE;
    }

    return result;
}

/* find UTC time from given date... no 1900 correction! */
static jsdouble
date_msecFromDate(jsdouble year, jsdouble mon, jsdouble mday, jsdouble hour,
                  jsdouble min, jsdouble sec, jsdouble msec)
{
    jsdouble day;
    jsdouble time;
    jsdouble result;

    day = MakeDay(year, mon, mday);
    time = MakeTime(hour, min, sec, msec);
    result = MakeDate(day, time);
    return result;
}

/*
 * See ECMA 15.9.4.[3-10];
 */
/* XXX this function must be above date_parseString to avoid a
   horrid bug in the Win16 1.52 compiler */
#define MAXARGS        7
static JSBool
date_UTC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble array[MAXARGS];
    uintN loop;
    jsdouble d;

    for (loop = 0; loop < MAXARGS; loop++) {
        if (loop < argc) {
            if (!JS_ValueToNumber(cx, argv[loop], &d))
                return JS_FALSE;
            /* return NaN if any arg is NaN */
            if (!JSDOUBLE_IS_FINITE(d)) {
                return js_NewNumberValue(cx, d, rval);
            }
            array[loop] = floor(d);
        } else {
            array[loop] = 0;
        }
    }

    /* adjust 2-digit years into the 20th century */
    if (array[0] >= 0 && array[0] <= 99)
        array[0] += 1900;

    /* if we got a 0 for 'date' (which is out of range)
     * pretend it's a 1.  (So Date.UTC(1972, 5) works) */
    if (array[2] < 1)
        array[2] = 1;

    d = date_msecFromDate(array[0], array[1], array[2],
                              array[3], array[4], array[5], array[6]);
    d = TIMECLIP(d);

    return js_NewNumberValue(cx, d, rval);
}

static JSBool
date_parseString(const jschar *s, jsdouble *result)
{
    jsdouble msec;

    int year = -1;
    int mon = -1;
    int mday = -1;
    int hour = -1;
    int min = -1;
    int sec = -1;
    int c = -1;
    int i = 0;
    int n = -1;
    jsdouble tzoffset = -1;  /* was an int, overflowed on win16!!! */
    int prevc = 0;
    int limit = 0;
    JSBool seenplusminus = JS_FALSE;

    if (s == 0)
        goto syntax;
    limit = js_strlen(s);
    while (i < limit) {
        c = s[i];
        i++;
        if (c <= ' ' || c == ',' || c == '-') {
            if (c == '-' && '0' <= s[i] && s[i] <= '9') {
              prevc = c;
            }
            continue;
        }
        if (c == '(') { /* comments) */
            int depth = 1;
            while (i < limit) {
                c = s[i];
                i++;
                if (c == '(') depth++;
                else if (c == ')')
                    if (--depth <= 0)
                        break;
            }
            continue;
        }
        if ('0' <= c && c <= '9') {
            n = c - '0';
            while (i < limit && '0' <= (c = s[i]) && c <= '9') {
                n = n * 10 + c - '0';
                i++;
            }

            /* allow TZA before the year, so
             * 'Wed Nov 05 21:49:11 GMT-0800 1997'
             * works */

            /* uses of seenplusminus allow : in TZA, so Java
             * no-timezone style of GMT+4:30 works
             */

            if ((prevc == '+' || prevc == '-')/*  && year>=0 */) {
                /* make ':' case below change tzoffset */
                seenplusminus = JS_TRUE;

                /* offset */
                if (n < 24)
                    n = n * 60; /* EG. "GMT-3" */
                else
                    n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
                if (prevc == '+')       /* plus means east of GMT */
                    n = -n;
                if (tzoffset != 0 && tzoffset != -1)
                    goto syntax;
                tzoffset = n;
            } else if (n >= 70  ||
                       (prevc == '/' && mon >= 0 && mday >= 0 && year < 0)) {
                if (year >= 0)
                    goto syntax;
                else if (c <= ' ' || c == ',' || c == '/' || i >= limit)
                    year = n < 100 ? n + 1900 : n;
                else
                    goto syntax;
            } else if (c == ':') {
                if (hour < 0)
                    hour = /*byte*/ n;
                else if (min < 0)
                    min = /*byte*/ n;
                else
                    goto syntax;
            } else if (c == '/') {
                if (mon < 0)
                    mon = /*byte*/ n-1;
                else if (mday < 0)
                    mday = /*byte*/ n;
                else
                    goto syntax;
            } else if (i < limit && c != ',' && c > ' ' && c != '-') {
                goto syntax;
            } else if (seenplusminus && n < 60) {  /* handle GMT-3:30 */
                if (tzoffset < 0)
                    tzoffset -= n;
                else
                    tzoffset += n;
            } else if (hour >= 0 && min < 0) {
                min = /*byte*/ n;
            } else if (min >= 0 && sec < 0) {
                sec = /*byte*/ n;
            } else if (mday < 0) {
                mday = /*byte*/ n;
            } else {
                goto syntax;
            }
            prevc = 0;
        } else if (c == '/' || c == ':' || c == '+' || c == '-') {
            prevc = c;
        } else {
            int st = i - 1;
            int k;
            while (i < limit) {
                c = s[i];
                if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
                    break;
                i++;
            }
            if (i <= st + 1)
                goto syntax;
            for (k = (sizeof(wtb)/sizeof(char*)); --k >= 0;)
                if (date_regionMatches(wtb[k], 0, s, st, i-st, 1)) {
                    int action = ttb[k];
                    if (action != 0)
                        if (action == 1) /* pm */
                            if (hour > 12 || hour < 0)
                                goto syntax;
                            else
                                hour += 12;
                        else if (action <= 13) /* month! */
                            if (mon < 0)
                                mon = /*byte*/ (action - 2);
                            else
                                goto syntax;
                        else
                            tzoffset = action - 10000;
                    break;
                }
            if (k < 0)
                goto syntax;
            prevc = 0;
        }
    }
    if (year < 0 || mon < 0 || mday < 0)
        goto syntax;
    if (sec < 0)
        sec = 0;
    if (min < 0)
        min = 0;
    if (hour < 0)
        hour = 0;
    if (tzoffset == -1) { /* no time zone specified, have to use local */
        jsdouble time;
        time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);

        *result = UTC(time);
        return JS_TRUE;
    }

    msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
    msec += tzoffset * msPerMinute;
    *result = msec;
    return JS_TRUE;

syntax:
    /* syntax error */
    *result = 0;
    return JS_FALSE;
}

static JSBool
date_parse(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str;
    jsdouble result;

    str = JS_ValueToString(cx, argv[0]);
    if (!str)
        return JS_FALSE;
    if (!date_parseString(str->chars, &result)) {
        *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN);
        return JS_TRUE;
    }

    result = TIMECLIP(result);
    return js_NewNumberValue(cx, result, rval);
}

/*
 * Check that obj is an object of class Date,
 * and get the date value.  Return NULL on failure,
 * which should be out of band, because the value
 * not publicly accessable.
 */
static jsdouble *
date_getProlog(JSContext *cx, JSObject *obj, jsval *argv) {
    jsdouble *result;
    if (!JS_InstanceOf(cx, obj, &date_class, argv)) {
        return NULL;
    }
    JS_LOCK(cx);
    result = JSVAL_TO_DOUBLE(OBJ_GET_SLOT(obj, JSSLOT_PRIVATE));
    JS_UNLOCK(cx);
    return result;
}

/*
 * See ECMA 15.9.5.4 thru 15.9.5.23
 */
static JSBool
date_getTime(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    return js_NewNumberValue(cx, *date, rval);
}

static JSBool
date_getYear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = YearFromTime(LocalTime(result));
    result -= 1900;
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getFullYear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                 jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = YearFromTime(LocalTime(result));
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getUTCFullYear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                    jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = YearFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getMonth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
              jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = MonthFromTime(LocalTime(result));
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getUTCMonth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                 jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = MonthFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getDate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = LocalTime(result);
    result = DateFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getUTCDate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = DateFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getDay(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = LocalTime(result);
    result = WeekDay(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getUTCDay(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
               jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = WeekDay(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getHours(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
              jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = HourFromTime(LocalTime(result));
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getUTCHours(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                 jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = HourFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getMinutes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = MinFromTime(LocalTime(result));
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getUTCMinutes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                   jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = MinFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

/* Date.getSeconds is mapped to getUTCSeconds */

static JSBool
date_getUTCSeconds(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = SecFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

/* Date.getMilliseconds is mapped to getUTCMilliseconds */

static JSBool
date_getUTCMilliseconds(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                     jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    result = msFromTime(result);
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_getTimezoneOffset(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                       jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    result = *date;

    /*
     * Return the time zone offset in minutes for the current locale
     * that is appropriate for this time. This value would be a
     * constant except for daylight savings time.
     */
    result = (result - LocalTime(result)) / msPerMinute;
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_setTime(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble result;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    if (!JS_ValueToNumber(cx, argv[0], &result))
        return JS_FALSE;

    *date = result;
    return js_NewNumberValue(cx, result, rval);
}

static JSBool
date_makeTime(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
              uintN maxargs, JSBool local, jsval *rval)
{
    uintN i;
    jsdouble args[4], *argp, *stop;
    jsdouble hour, min, sec, msec;
    jsdouble lorutime; /* Local or UTC version of *date */

    jsdouble time;
    jsdouble result;

    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    result = *date;

    /* just return NaN if the date is already NaN */
    if (!JSDOUBLE_IS_FINITE(result))
        return js_NewNumberValue(cx, result, rval);

    for (i = 0; i < argc; i++) {
        if (!JS_ValueToNumber(cx, argv[i], &args[i]))
            return JS_FALSE;
        if (!JSDOUBLE_IS_FINITE(args[i])) {
            *date = *(cx->runtime->jsNaN);
            return js_NewNumberValue(cx, *date, rval);
        }
        args[i] = js_DoubleToInteger(args[i]);
    }

    if (local)
        lorutime = LocalTime(result);
    else
        lorutime = result;

    argp = args;
    stop = argp + argc;
    if (maxargs >= 4 && argp < stop)
        hour = *argp++;
    else
        hour = HourFromTime(lorutime);

    if (maxargs >= 3 && argp < stop)
        min = *argp++;
    else
        min = MinFromTime(lorutime);

    if (maxargs >= 2 && argp < stop)
        sec = *argp++;
    else
        sec = SecFromTime(lorutime);

    if (maxargs >= 1 && argp < stop)
        msec = *argp;
    else
        msec = msFromTime(lorutime);

    time = MakeTime(hour, min, sec, msec);
    result = MakeDate(Day(lorutime), time);

/*     fprintf(stderr, "%f\n", result); */

    if (local)
        result = UTC(result);

/*     fprintf(stderr, "%f\n", result); */

    *date = TIMECLIP(result);
    return js_NewNumberValue(cx, *date, rval);
}

static JSBool
date_setMilliseconds(JSContext *cx, JSObject *obj, uintN argc,
                     jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 1, JS_TRUE, rval);
}

static JSBool
date_setUTCMilliseconds(JSContext *cx, JSObject *obj, uintN argc,
                        jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 1, JS_FALSE, rval);
}

static JSBool
date_setSeconds(JSContext *cx, JSObject *obj, uintN argc,
                jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 2, JS_TRUE, rval);
}

static JSBool
date_setUTCSeconds(JSContext *cx, JSObject *obj, uintN argc,
                   jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 2, JS_FALSE, rval);
}

static JSBool
date_setMinutes(JSContext *cx, JSObject *obj, uintN argc,
                jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 3, JS_TRUE, rval);
}

static JSBool
date_setUTCMinutes(JSContext *cx, JSObject *obj, uintN argc,
                   jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 3, JS_FALSE, rval);
}

static JSBool
date_setHours(JSContext *cx, JSObject *obj, uintN argc,
              jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 4, JS_TRUE, rval);
}

static JSBool
date_setUTCHours(JSContext *cx, JSObject *obj, uintN argc,
                 jsval *argv, jsval *rval)
{
    return date_makeTime(cx, obj, argc, argv, 4, JS_FALSE, rval);
}

static JSBool
date_makeDate(JSContext *cx, JSObject *obj, uintN argc,
              jsval *argv, uintN maxargs, JSBool local, jsval *rval)
{
    uintN i;
    jsdouble lorutime; /* local or UTC version of *date */
    jsdouble args[3], *argp, *stop;
    jsdouble year, month, day;
    jsdouble result;

    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    result = *date;

    for (i = 0; i < argc; i++) {
        if (!JS_ValueToNumber(cx, argv[i], &args[i]))
            return JS_FALSE;
        if (!JSDOUBLE_IS_FINITE(args[i])) {
            *date = *(cx->runtime->jsNaN);
            return js_NewNumberValue(cx, *date, rval);
        }
        args[i] = js_DoubleToInteger(args[i]);
    }

    /* return NaN if date is NaN and we're not setting the year,
     * If we are, use 0 as the time. */
    if (!(JSDOUBLE_IS_FINITE(result))) {
        if (argc < 3)
            return js_NewNumberValue(cx, result, rval);
        else
            lorutime = +0.;
    } else {
        if (local)
            lorutime = LocalTime(result);
        else
            lorutime = result;
    }

    argp = args;
    stop = argp + argc;
    if (maxargs >= 3 && argp < stop)
        year = *argp++;
    else
        year = YearFromTime(lorutime);

    if (maxargs >= 2 && argp < stop)
        month = *argp++;
    else
        month = MonthFromTime(lorutime);

    if (maxargs >= 1 && argp < stop)
        day = *argp++;
    else
        day = DateFromTime(lorutime);

    day = MakeDay(year, month, day); /* day within year */
    result = MakeDate(day, TimeWithinDay(lorutime));

    if (local)
        result = UTC(result);

    *date = TIMECLIP(result);
    return js_NewNumberValue(cx, *date, rval);
}

static JSBool
date_setDate(JSContext *cx, JSObject *obj, uintN argc,
             jsval *argv, jsval *rval)
{
    return date_makeDate(cx, obj, argc, argv, 1, JS_TRUE, rval);
}

static JSBool
date_setUTCDate(JSContext *cx, JSObject *obj, uintN argc,
                jsval *argv, jsval *rval)
{
    return date_makeDate(cx, obj, argc, argv, 1, JS_FALSE, rval);
}

static JSBool
date_setMonth(JSContext *cx, JSObject *obj, uintN argc,
              jsval *argv, jsval *rval)
{
    return date_makeDate(cx, obj, argc, argv, 2, JS_TRUE, rval);
}

static JSBool
date_setUTCMonth(JSContext *cx, JSObject *obj, uintN argc,
                 jsval *argv, jsval *rval)
{
    return date_makeDate(cx, obj, argc, argv, 2, JS_FALSE, rval);
}

static JSBool
date_setFullYear(JSContext *cx, JSObject *obj, uintN argc,
                 jsval *argv, jsval *rval)
{
    return date_makeDate(cx, obj, argc, argv, 3, JS_TRUE, rval);
}

static JSBool
date_setUTCFullYear(JSContext *cx, JSObject *obj, uintN argc,
                    jsval *argv, jsval *rval)
{
    return date_makeDate(cx, obj, argc, argv, 3, JS_FALSE, rval);
}

static JSBool
date_setYear(JSContext *cx, JSObject *obj, uintN argc,
             jsval *argv, jsval *rval)
{
    jsdouble t;
    jsdouble year;
    jsdouble day;
    jsdouble result;

    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    result = *date;

    if (!JS_ValueToNumber(cx, argv[0], &year))
        return JS_FALSE;
    if (!JSDOUBLE_IS_FINITE(year)) {
        *date = *(cx->runtime->jsNaN);
        return js_NewNumberValue(cx, *date, rval);
    }

    year = js_DoubleToInteger(year);

    if (!JSDOUBLE_IS_FINITE(result)) {
        t = +0.0;
    } else {
        t = LocalTime(result);
    }

    if (year >= 0 && year <= 99)
        year += 1900;

    day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
    result = MakeDate(day, TimeWithinDay(t));
    result = UTC(result);

    *date = TIMECLIP(result);
    return js_NewNumberValue(cx, *date, rval);
}

/* constants for toString, toUTCString */
static char js_NaN_date_str[] = "Invalid Date";
static const char* days[] =
{
   "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};
static const char* months[] =
{
   "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

static JSBool
date_toGMTString(JSContext *cx, JSObject *obj, uintN argc,
                 jsval *argv, jsval *rval)
{
    char buf[100];
    JSString *str;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    if (!JSDOUBLE_IS_FINITE(*date)) {
        PR_snprintf(buf, sizeof buf, js_NaN_date_str);
    } else {
        jsdouble temp = *date;

        /* Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
         * requires a PRMJTime... which only has 16-bit years.  Sub-ECMA.
         */
        PR_snprintf(buf, sizeof buf, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
                    days[WeekDay(temp)],
                    DateFromTime(temp),
                    months[MonthFromTime(temp)],
                    YearFromTime(temp),
                    HourFromTime(temp),
                    MinFromTime(temp),
                    SecFromTime(temp));
    }
    str = JS_NewStringCopyZ(cx, buf);
    if (!str)
        return JS_FALSE;
    *rval = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

/* for Date.toLocaleString; interface to PRMJTime date struct. */
static void
new_explode(jsdouble time, PRMJTime *split)
{
    jsint year = YearFromTime(time);

    split->tm_usec = (int32) msFromTime(time) * 1000;
    split->tm_sec = (int8) SecFromTime(time);
    split->tm_min = (int8) MinFromTime(time);
    split->tm_hour = (int8) HourFromTime(time);
    split->tm_mday = (int8) DateFromTime(time);
    split->tm_mon = (int8) MonthFromTime(time);
    split->tm_wday = (int8) WeekDay(time);
    split->tm_year = (int16) year;
    split->tm_yday = (int16) DayWithinYear(time, year);

    /* not sure how this affects things, but it doesn't seem
       to matter. */
    split->tm_isdst = (DaylightSavingTA(time) != 0);
}

static JSBool
date_toLocaleString(JSContext *cx, JSObject *obj, uintN argc,
                    jsval *argv, jsval *rval)
{
    char buf[100];
    JSString *str;
    PRMJTime split;
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;

    if (!JSDOUBLE_IS_FINITE(*date)) {
        PR_snprintf(buf, sizeof buf, js_NaN_date_str);
    } else {
        jsdouble local = LocalTime(*date);
        new_explode(local, &split);

        /* let PRMJTime format it. */
        PRMJ_FormatTime(buf, sizeof buf, "%c", &split);
    }

    str = JS_NewStringCopyZ(cx, buf);
    if (!str)
        return JS_FALSE;
    *rval = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

/* helper function */
static JSBool
date_format(JSContext *cx, jsdouble date, jsval *rval)
{
    char buf[100];
    JSString *str;
    char tzbuf[100];
    PRMJTime split;

    if (!JSDOUBLE_IS_FINITE(date)) {
        PR_snprintf(buf, sizeof buf, js_NaN_date_str);
    } else {
        jsdouble local = LocalTime(date);

        /* offset from GMT in minutes.  The offset includes daylight savings,
           if it applies. */
        jsint minutes = (jsint) floor((LocalTZA + DaylightSavingTA(local))
                                      / msPerMinute);

        /* map 510 minutes to 0830 hours */
        intN offset = (minutes / 60) * 100 + minutes % 60;

        /* print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997" The TZA is
         * printed as 'GMT-0800' rather than as 'PST' to avoid
         * operating-system dependence on strftime (which
         * PRMJ_FormatTimeUSEnglish calls, for %Z only.)  win32 prints
         * PST as 'Pacific Standard Time.'  This way we always know
         * what we're getting, and can parse it if we produce it.
         * The OS TZA string is included as a comment.
         */

        /* get a timezone string from the OS to include as a
           comment. */
        new_explode(local, &split);
        PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z) ", &split);

        /* Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
         * requires a PRMJTime... which only has 16-bit years.  Sub-ECMA.
         */
        PR_snprintf(buf, sizeof buf, "%s %s %.2d %.2d:%.2d:%.2d GMT%+.4d %s%.4d",
                    days[WeekDay(local)],
                    months[MonthFromTime(local)],
                    DateFromTime(local),
                    HourFromTime(local),
                    MinFromTime(local),
                    SecFromTime(local),
                    offset,

                    /* don't print anything for the TZA comment if we
                       got the empty string from the OS. */
		    /* (balance: */
                    (tzbuf[1] != ')' ? tzbuf : ""),

                    YearFromTime(local));
    }

    str = JS_NewStringCopyZ(cx, buf);
    if (!str)
        return JS_FALSE;
    *rval = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

static JSBool
date_toString(JSContext *cx, JSObject *obj, uintN argc,
              jsval *argv, jsval *rval)
{
    jsdouble *date = date_getProlog(cx, obj, argv);
    if (!date)
        return JS_FALSE;
    return date_format(cx, *date, rval);
}

#if JS_HAS_VALUEOF_HINT
static JSBool
date_valueOf(JSContext *cx, JSObject *obj, uintN argc,
             jsval *argv, jsval *rval)
{
    /* If called directly with no arguments, convert to a time number. */
    if (argc == 0)
        return date_getTime(cx, obj, argc, argv, rval);

    /* Convert to number only if the hint was given, otherwise favor string. */
    if (argc == 1) {
        JSString *str, *str2;

        str = JS_ValueToString(cx, argv[0]);
        if (!str)
            return JS_FALSE;
        str2 = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_NUMBER]);
        if (!js_CompareStrings(str, str2))
            return date_getTime(cx, obj, argc, argv, rval);
    }
    return date_toString(cx, obj, argc, argv, rval);
}
#else
#define date_valueOf date_getTime
#endif


/*
 * creation and destruction
 */

static JSFunctionSpec date_static_methods[] = {
    {"UTC",               date_UTC,               MAXARGS },
    {"parse",             date_parse,             1 },
    {0}
};

static JSFunctionSpec date_methods[] = {
    {"getTime",           date_getTime,           0 },
    {"getTimezoneOffset", date_getTimezoneOffset, 0 },
    {"getYear",           date_getYear,           0 },
    {"getFullYear",       date_getFullYear,       0 },
    {"getUTCFullYear",    date_getUTCFullYear,    0 },
    {"getMonth",          date_getMonth,          0 },
    {"getUTCMonth",       date_getUTCMonth,       0 },
    {"getDate",           date_getDate,           0 },
    {"getUTCDate",        date_getUTCDate,        0 },
    {"getDay",            date_getDay,            0 },
    {"getUTCDay",         date_getUTCDay,         0 },
    {"getHours",          date_getHours,          0 },
    {"getUTCHours",       date_getUTCHours,       0 },
    {"getMinutes",        date_getMinutes,        0 },
    {"getUTCMinutes",     date_getUTCMinutes,     0 },
    {"getSeconds",        date_getUTCSeconds,     0 },
    {"getUTCSeconds",     date_getUTCSeconds,     0 },
    {"getMilliseconds",   date_getUTCMilliseconds,0 },
    {"getUTCMilliseconds",date_getUTCMilliseconds,0 },
    {"setTime",           date_setTime,           1 },
    {"setYear",           date_setYear,           1 },
    {"setFullYear",       date_setFullYear,       1 },
    {"setUTCFullYear",    date_setUTCFullYear,    1 },
    {"setMonth",          date_setMonth,          1 },
    {"setUTCMonth",       date_setUTCMonth,       1 },
    {"setDate",           date_setDate,           1 },
    {"setUTCDate",        date_setUTCDate,        1 },
    {"setHours",          date_setHours,          1 },
    {"setUTCHours",       date_setUTCHours,       1 },
    {"setMinutes",        date_setMinutes,        1 },
    {"setUTCMinutes",     date_setUTCMinutes,     1 },
    {"setSeconds",        date_setSeconds,        1 },
    {"setUTCSeconds",     date_setUTCSeconds,     1 },
    {"setMilliseconds",   date_setMilliseconds,   1 },
    {"setUTCMilliseconds",date_setUTCMilliseconds,1 },
    {"toGMTString",       date_toGMTString,       0 },
    {"toUTCString",       date_toGMTString,       0 },
    {"toLocaleString",    date_toLocaleString,    0 },
    {js_toString_str,     date_toString,          0 },
    {js_valueOf_str,      date_valueOf,           0 },
    {0}
};

static jsdouble *
date_constructor(JSContext *cx, JSObject* obj)
{
    jsdouble *date;

    date = js_NewDouble(cx, 0.0);
    if (!date)
        return NULL;

    JS_LOCK(cx);
    OBJ_SET_SLOT(obj, JSSLOT_PRIVATE, DOUBLE_TO_JSVAL(date));
    JS_UNLOCK(cx);

    return date;
}

static JSBool
Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    jsdouble *date;
    JSString *str;
    jsdouble d;

    /* Date called as function */
    if (obj->map->clasp != &date_class) {
        int64 us, ms, us2ms;
        jsdouble time;

        /* NSPR 2.0 docs say 'We do not support PRMJ_NowMS and PRMJ_NowS',
         * so compute ms from PRMJ_Now.
         */
        us = PRMJ_Now();
        LL_UI2L(us2ms, PRMJ_USEC_PER_MSEC);
        LL_DIV(ms, us, us2ms);
        LL_L2D(time, ms);

        return date_format(cx, time, rval);
    }

    /* Date called as constructor */
    if (argc == 0) {
        int64 us, ms, us2ms;
        jsdouble time;

        date = date_constructor(cx, obj);
        if (!date)
            return JS_FALSE;

        us = PRMJ_Now();
        LL_UI2L(us2ms, PRMJ_USEC_PER_MSEC);
        LL_DIV(ms, us, us2ms);
        LL_L2D(time, ms);

        *date = time;
    } else if (argc == 1) {
        if (!JSVAL_IS_STRING(argv[0])) {
            /* the argument is a millisecond number */
            if (!JS_ValueToNumber(cx, argv[0], &d))
                    return JS_FALSE;
            date = date_constructor(cx, obj);
            if (!date)
                return JS_FALSE;
            *date = TIMECLIP(d);
        } else {
            /* the argument is a string; parse it. */
            date = date_constructor(cx, obj);
            if (!date)
                return JS_FALSE;

            str = JS_ValueToString(cx, argv[0]);
            if (!str)
                return JS_FALSE;

            if (!date_parseString(str->chars, date))
                *date = *(cx->runtime->jsNaN);
            *date = TIMECLIP(*date);
        }
    } else {
        jsdouble array[MAXARGS];
        uintN loop;
        jsdouble d;
        jsdouble day;
        jsdouble time;

        for (loop = 0; loop < MAXARGS; loop++) {
            if (loop < argc) {
                if (!JS_ValueToNumber(cx, argv[loop], &d))
                    return JS_FALSE;
                /* if any arg is NaN, make a NaN date object
                   and return */
                if (!JSDOUBLE_IS_FINITE(d)) {
                    date = date_constructor(cx, obj);
                    if (!date)
                        return JS_FALSE;
                    *date = *(cx->runtime->jsNaN);
                    return JS_TRUE;
                }
                array[loop] = js_DoubleToInteger(d);
            } else {
                array[loop] = 0;
            }
        }

        date = date_constructor(cx, obj);
        if (!date)
            return JS_FALSE;

        /* adjust 2-digit years into the 20th century */
        if (array[0] >= 0 && array[0] <= 99)
            array[0] += 1900;

        /* if we got a 0 for 'date' (which is out of range)
         * pretend it's a 1 */
        if (array[2] < 1)
            array[2] = 1;

        day = MakeDay(array[0], array[1], array[2]);

/*         fprintf(stderr, "%f\n", day); */

        time = MakeTime(array[3], array[4], array[5], array[6]);

/*         fprintf(stderr, "%f\n", time); */

        time = MakeDate(day, time);

/*         fprintf(stderr, "%f\n", time); */

        time = UTC(time);

/*         fprintf(stderr, "%f\n", time); */

        *date = TIMECLIP(time);

/*         fprintf(stderr, "%f\n", *date); */
    }
    return JS_TRUE;
}

JSObject *
js_InitDateClass(JSContext *cx, JSObject *obj)
{
    /* set static LocalTZA */
    LocalTZA = -(PRMJ_LocalGMTDifference() * msPerSecond);
    return JS_InitClass(cx, obj, NULL, &date_class, Date, MAXARGS,
                        NULL, date_methods, NULL, date_static_methods);
}

JS_FRIEND_API(JSObject *)
js_NewDateObject(JSContext* cx, int year, int mon, int mday,
                 int hour, int min, int sec)
{
    JSObject *obj;
    jsdouble *date;
    jsdouble time;

    obj = js_NewObject(cx, &date_class, NULL, NULL);
    if (!obj)
        return NULL;

    JS_DefineFunctions(cx, obj, date_methods);

    date = date_constructor(cx, obj);
    if (!date)
        return NULL;

    time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
    *date = UTC(time);
    return obj;
}

JS_FRIEND_API(int)
js_DateGetYear(JSContext *cx, JSObject* obj)
{
    jsdouble *date = date_getProlog(cx, obj, NULL);

    if (!date)
        return 0;
    return (int) YearFromTime(LocalTime(*date));
}

JS_FRIEND_API(int)
js_DateGetMonth(JSContext *cx, JSObject* obj)
{
    jsdouble *date = date_getProlog(cx, obj, NULL);

    if (!date)
        return 0;
    return (int) MonthFromTime(LocalTime(*date));
}

JS_FRIEND_API(int)
js_DateGetDate(JSContext *cx, JSObject* obj)
{
    jsdouble *date = date_getProlog(cx, obj, NULL);

    if (!date)
        return 0;
    return (int) DateFromTime(LocalTime(*date));
}

JS_FRIEND_API(int)
js_DateGetHours(JSContext *cx, JSObject* obj)
{
    jsdouble *date = date_getProlog(cx, obj, NULL);

    if (!date)
        return 0;
    return (int) HourFromTime(LocalTime(*date));
}

JS_FRIEND_API(int)
js_DateGetMinutes(JSContext *cx, JSObject* obj)
{
    jsdouble *date = date_getProlog(cx, obj, NULL);

    if (!date)
        return 0;
    return (int) MinFromTime(LocalTime(*date));
}

JS_FRIEND_API(int)
js_DateGetSeconds(JSContext *cx, JSObject* obj)
{
    jsdouble *date = date_getProlog(cx, obj, NULL);

    if (!date)
        return 0;
    return (int) SecFromTime(*date);
}

extern JS_FRIEND_API(void)
js_DateSetYear(JSContext *cx, JSObject *obj, int year)
{
    jsdouble local;
    jsdouble *date = date_getProlog(cx, obj, NULL);
    if (!date)
        return;
    local = LocalTime(*date);
    /* reset date if it was NaN */
    if (local != local)
        local = 0;
    local = date_msecFromDate(year,
                              MonthFromTime(local),
                              DateFromTime(local),
                              HourFromTime(local),
                              MinFromTime(local),
                              SecFromTime(local),
                              msFromTime(local));
    *date = UTC(local);
}

extern JS_FRIEND_API(void)
js_DateSetMonth(JSContext *cx, JSObject *obj, int month)
{
    jsdouble local;
    jsdouble *date = date_getProlog(cx, obj, NULL);
    if (!date)
        return;
    local = LocalTime(*date);
    /* bail if date was NaN */
    if (local != local)
        return;
    local = date_msecFromDate(YearFromTime(local),
                              month,
                              DateFromTime(local),
                              HourFromTime(local),
                              MinFromTime(local),
                              SecFromTime(local),
                              msFromTime(local));
    *date = UTC(local);
}

extern JS_FRIEND_API(void)
js_DateSetDate(JSContext *cx, JSObject *obj, int date)
{
    jsdouble local;
    jsdouble *datep = date_getProlog(cx, obj, NULL);
    if (!datep)
        return;
    local = LocalTime(*datep);
    if (local != local)
        return;
    local = date_msecFromDate(YearFromTime(local),
                              MonthFromTime(local),
                              date,
                              HourFromTime(local),
                              MinFromTime(local),
                              SecFromTime(local),
                              msFromTime(local));
    *datep = UTC(local);
}

extern JS_FRIEND_API(void)
js_DateSetHours(JSContext *cx, JSObject *obj, int hours)
{
    jsdouble local;
    jsdouble *date = date_getProlog(cx, obj, NULL);
    if (!date)
        return;
    local = LocalTime(*date);
    if (local != local)
        return;
    local = date_msecFromDate(YearFromTime(local),
                              MonthFromTime(local),
                              DateFromTime(local),
                              hours,
                              MinFromTime(local),
                              SecFromTime(local),
                              msFromTime(local));
    *date = UTC(local);
}

extern JS_FRIEND_API(void)
js_DateSetMinutes(JSContext *cx, JSObject *obj, int minutes)
{
    jsdouble local;
    jsdouble *date = date_getProlog(cx, obj, NULL);
    if (!date)
        return;
    local = LocalTime(*date);
    if (local != local)
        return;
    local = date_msecFromDate(YearFromTime(local),
                              MonthFromTime(local),
                              DateFromTime(local),
                              HourFromTime(local),
                              minutes,
                              SecFromTime(local),
                              msFromTime(local));
    *date = UTC(local);
}

extern JS_FRIEND_API(void)
js_DateSetSeconds(JSContext *cx, JSObject *obj, int seconds)
{
    jsdouble local;
    jsdouble *date = date_getProlog(cx, obj, NULL);
    if (!date)
        return;
    local = LocalTime(*date);
    if (local != local)
        return;
    local = date_msecFromDate(YearFromTime(local),
                              MonthFromTime(local),
                              DateFromTime(local),
                              HourFromTime(local),
                              MinFromTime(local),
                              seconds,
                              msFromTime(local));
    *date = UTC(local);
}
