/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include "qv4dateobject_p.h"
#include "qv4objectproto_p.h"
#include "qv4scopedvalue_p.h"
#include "qv4runtime_p.h"
#include "qv4string_p.h"
#include "qv4jscall_p.h"
#include "qv4symbol_p.h"

#include <QtCore/QDebug>
#include <QtCore/QDateTime>
#include <QtCore/QStringList>

#include <time.h>

#include <wtf/MathExtras.h>

#if QT_CONFIG(timezone) && !defined(Q_OS_WIN)
/*
  See QTBUG-56899.  Although we don't (yet) have a proper way to reset the
  system zone, the code below, that uses QTimeZone::systemTimeZone(), works
  adequately on Linux.

  QTimeZone::systemTimeZone() will automatically produce an updated value on
  non-android Linux systems when the TZ environment variable or the relevant
  files in /etc are changed. On other platforms it won't, and the information
  produced here may be incorrect after changes to the system time zone.

  We accept this defect for now because the localtime_r approach will
  consistently produce incorrect results for some time zones, not only when
  the system time zone changes. This is a worse problem, see also QTBUG-84474.

  On windows we have a better implementation of getLocalTZA that hopefully
  updates on time zone changes. However, we currently use the worse
  implementation of DaylightSavingTA (returning either an hour or 0).

  QTimeZone::systemTimeZone() on Linux is also slower than other approaches
  because it has to poll the relevant files (if TZ is not set). See
  QTBUG-75585 for an explanation and possible workarounds.
 */
#define USE_QTZ_SYSTEM_ZONE
#elif defined(Q_OS_WASM)
/*
    TODO: evaluate using this version of the code more generally, rather than
    the #else branches of the various USE_QTZ_SYSTEM_ZONE choices. It might even
    work better than the timezone variant; experiments needed.
*/
// Kludge around the lack of time-zone info using QDateTime.
// It uses localtime() and friends to determine offsets from UTC.
#define USE_QDT_LOCAL_TIME
#endif

#ifdef USE_QTZ_SYSTEM_ZONE
#include <QtCore/QTimeZone>
#elif defined(USE_QDT_LOCAL_TIME)
// QDateTime already included above
#else
#  ifdef Q_OS_WIN
#    include <windows.h>
#  else
#    ifndef Q_OS_VXWORKS
#      include <sys/time.h>
#    else
#      include "qplatformdefs.h"
#    endif
#    include <unistd.h> // for _POSIX_THREAD_SAFE_FUNCTIONS
#  endif
#endif // USE_QTZ_SYSTEM_ZONE

using namespace QV4;

static const double HoursPerDay = 24.0;
static const double MinutesPerHour = 60.0;
static const double SecondsPerMinute = 60.0;
static const double msPerSecond = 1000.0;
static const double msPerMinute = 60000.0;
static const double msPerHour = 3600000.0;
static const double msPerDay = 86400000.0;

static inline double TimeWithinDay(double t)
{
    double r = ::fmod(t, msPerDay);
    return (r >= 0) ? r : r + msPerDay;
}

static inline int HourFromTime(double t)
{
    int r = int(::fmod(::floor(t / msPerHour), HoursPerDay));
    return (r >= 0) ? r : r + int(HoursPerDay);
}

static inline int MinFromTime(double t)
{
    int r = int(::fmod(::floor(t / msPerMinute), MinutesPerHour));
    return (r >= 0) ? r : r + int(MinutesPerHour);
}

static inline int SecFromTime(double t)
{
    int r = int(::fmod(::floor(t / msPerSecond), SecondsPerMinute));
    return (r >= 0) ? r : r + int(SecondsPerMinute);
}

static inline int msFromTime(double t)
{
    int r = int(::fmod(t, msPerSecond));
    return (r >= 0) ? r : r + int(msPerSecond);
}

static inline double Day(double t)
{
    return ::floor(t / msPerDay);
}

static inline double DaysInYear(double y)
{
    if (::fmod(y, 4))
        return 365;

    else if (::fmod(y, 100))
        return 366;

    else if (::fmod(y, 400))
        return 365;

    return 366;
}

static inline double DayFromYear(double y)
{
    return 365 * (y - 1970)
        + ::floor((y - 1969) / 4)
        - ::floor((y - 1901) / 100)
        + ::floor((y - 1601) / 400);
}

static inline double TimeFromYear(double y)
{
    return msPerDay * DayFromYear(y);
}

static inline double YearFromTime(double t)
{
    int y = 1970;
    y += (int) ::floor(t / (msPerDay * 365.2425));

    double t2 = TimeFromYear(y);
    return (t2 > t) ? y - 1 : ((t2 + msPerDay * DaysInYear(y)) <= t) ? y + 1 : y;
}

static inline bool InLeapYear(double t)
{
    double x = DaysInYear(YearFromTime(t));
    if (x == 365)
        return 0;

    Q_ASSERT(x == 366);
    return 1;
}

static inline double DayWithinYear(double t)
{
    return Day(t) - DayFromYear(YearFromTime(t));
}

static inline double MonthFromTime(double t)
{
    double d = DayWithinYear(t);
    double l = InLeapYear(t);

    if (d < 31.0)
        return 0;

    else if (d < 59.0 + l)
        return 1;

    else if (d < 90.0 + l)
        return 2;

    else if (d < 120.0 + l)
        return 3;

    else if (d < 151.0 + l)
        return 4;

    else if (d < 181.0 + l)
        return 5;

    else if (d < 212.0 + l)
        return 6;

    else if (d < 243.0 + l)
        return 7;

    else if (d < 273.0 + l)
        return 8;

    else if (d < 304.0 + l)
        return 9;

    else if (d < 334.0 + l)
        return 10;

    else if (d < 365.0 + l)
        return 11;

    return qt_qnan(); // ### assert?
}

static inline double DateFromTime(double t)
{
    int m = (int) QV4::Value::toInteger(MonthFromTime(t));
    double d = DayWithinYear(t);
    double l = InLeapYear(t);

    switch (m) {
    case 0: return d + 1.0;
    case 1: return d - 30.0;
    case 2: return d - 58.0 - l;
    case 3: return d - 89.0 - l;
    case 4: return d - 119.0 - l;
    case 5: return d - 150.0 - l;
    case 6: return d - 180.0 - l;
    case 7: return d - 211.0 - l;
    case 8: return d - 242.0 - l;
    case 9: return d - 272.0 - l;
    case 10: return d - 303.0 - l;
    case 11: return d - 333.0 - l;
    }

    return qt_qnan(); // ### assert
}

static inline double WeekDay(double t)
{
    double r = ::fmod (Day(t) + 4.0, 7.0);
    return (r >= 0) ? r : r + 7.0;
}


static inline double MakeTime(double hour, double min, double sec, double ms)
{
    if (!qIsFinite(hour) || !qIsFinite(min) || !qIsFinite(sec) || !qIsFinite(ms))
        return qQNaN();
    hour = QV4::Value::toInteger(hour);
    min = QV4::Value::toInteger(min);
    sec = QV4::Value::toInteger(sec);
    ms = QV4::Value::toInteger(ms);
    return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms;
}

static inline double DayFromMonth(double month, double leap)
{
    switch ((int) month) {
    case 0: return 0;
    case 1: return 31.0;
    case 2: return 59.0 + leap;
    case 3: return 90.0 + leap;
    case 4: return 120.0 + leap;
    case 5: return 151.0 + leap;
    case 6: return 181.0 + leap;
    case 7: return 212.0 + leap;
    case 8: return 243.0 + leap;
    case 9: return 273.0 + leap;
    case 10: return 304.0 + leap;
    case 11: return 334.0 + leap;
    }

    return qt_qnan(); // ### assert?
}

static double MakeDay(double year, double month, double day)
{
    if (!qIsFinite(year) || !qIsFinite(month) || !qIsFinite(day))
        return qQNaN();
    year = QV4::Value::toInteger(year);
    month = QV4::Value::toInteger(month);
    day = QV4::Value::toInteger(day);

    year += ::floor(month / 12.0);

    month = ::fmod(month, 12.0);
    if (month < 0)
        month += 12.0;

    /* Quoting the spec:

       Find a value t such that YearFromTime(t) is ym and MonthFromTime(t) is mn
       and DateFromTime(t) is 1; but if this is not possible (because some
       argument is out of range), return NaN.
    */
    double first = DayFromYear(year);
    /* Beware floating-point glitches: don't test the first millisecond of a
     * year, month or day when we could test a moment firmly in the interior of
     * the interval. A rounding glitch might give the first millisecond to the
     * preceding interval.
     */
    bool leap = InLeapYear((first + 60) * msPerDay);

    first += DayFromMonth(month, leap);
    const double t = first * msPerDay + msPerDay / 2; // Noon on the first of the month
    Q_ASSERT(Day(t) == first);
    if (YearFromTime(t) != year || MonthFromTime(t) != month || DateFromTime(t) != 1) {
        qWarning("Apparently out-of-range date %.0f-%02.0f-%02.0f", year, month, day);
        return qt_qnan();
    }
    return first + day - 1;
}

static inline double MakeDate(double day, double time)
{
    return day * msPerDay + time;
}

#ifdef USE_QTZ_SYSTEM_ZONE
/*
  ECMAScript specifies use of a fixed (current, standard) time-zone offset,
  LocalTZA; and LocalTZA + DaylightSavingTA(t) is taken to be (see LocalTime and
  UTC, following) local time's offset from UTC at time t.  For simple zones,
  DaylightSavingTA(t) is thus the DST offset applicable at date/time t; however,
  if a zone has changed its standard offset, the only way to make LocalTime and
  UTC (if implemented in accord with the spec) perform correct transformations
  is to have DaylightSavingTA(t) correct for the zone's standard offset change
  as well as its actual DST offset.

  This means we have to treat any historical changes in the zone's standard
  offset as DST perturbations, regardless of historical reality.  (This shall
  mean a whole day of DST offset for some zones, that have crossed the
  international date line.  This shall confuse client code.)  The bug report
  against the ECMAScript spec is https://github.com/tc39/ecma262/issues/725
  and they've now changed the spec so that the following conforms to it ;^>
*/

static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time
{
    return QTimeZone::systemTimeZone().offsetFromUtc(
        QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC)) * 1e3 - localTZA;
}
#elif defined(USE_QDT_LOCAL_TIME)
static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time
{
    return QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC
        ).toLocalTime().offsetFromUtc() * 1e3 - localTZA;
}
#else
// This implementation fails to take account of past changes in standard offset.
static inline double DaylightSavingTA(double t, double /*localTZA*/)
{
    struct tm tmtm;
#if defined(Q_CC_MSVC)
    __time64_t  tt = (__time64_t)(t / msPerSecond);
    // _localtime_64_s returns non-zero on failure
    if (_localtime64_s(&tmtm, &tt) != 0)
#elif !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
    long int tt = (long int)(t / msPerSecond);
    if (!localtime_r((const time_t*) &tt, &tmtm))
#else
    // Returns shared static data which may be overwritten at any time
    // (for MinGW/Windows localtime is luckily thread safe)
    long int tt = (long int)(t / msPerSecond);
    if (struct tm *tmtm_p = localtime((const time_t*) &tt))
        tmtm = *tmtm_p;
    else
#endif
        return 0;
    return (tmtm.tm_isdst > 0) ? msPerHour : 0;
}
#endif // USE_QTZ_SYSTEM_ZONE

static inline double LocalTime(double t, double localTZA)
{
    // Flawed, yet verbatim from the spec:
    return t + localTZA + DaylightSavingTA(t, localTZA);
}

// The spec does note [*] that UTC and LocalTime are not quite mutually inverse.
// [*] http://www.ecma-international.org/ecma-262/7.0/index.html#sec-utc-t

static inline double UTC(double t, double localTZA)
{
    // Flawed, yet verbatim from the spec:
    return t - localTZA - DaylightSavingTA(t - localTZA, localTZA);
}

static inline double currentTime()
{
    return QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
}

static inline double TimeClip(double t)
{
    if (! qt_is_finite(t) || fabs(t) > 8.64e15)
        return qt_qnan();

    // +0 looks weird, but is correct. See ES6 20.3.1.15. We must not return -0.
    return QV4::Value::toInteger(t) + 0;
}

static inline double ParseString(const QString &s, double localTZA)
{
    /*
      First, try the format defined in ECMA 262's "Date Time String Format";
      only if that fails, fall back to QDateTime for parsing

      The defined string format is YYYY-MM-DDTHH:mm:ss.sssZ; the time (T and all
      after it) may be omitted; in each part, the second and later components
      are optional; and there's an extended syntax for negative and large
      positive years: +/-YYYYYY; the leading sign, even when +, isn't optional.
      If month or day is omitted, it is 01; if minute or second is omitted, it's
      00; if milliseconds are omitted, they're 000.

      When the time zone offset is absent, date-only forms are interpreted as
      indicating a UTC time and date-time forms are interpreted in local time.
    */

    enum Format {
        Year,
        Month,
        Day,
        Hour,
        Minute,
        Second,
        MilliSecond,
        TimezoneHour,
        TimezoneMinute,
        Done
    };

    const QChar *ch = s.constData();
    const QChar *end = ch + s.length();

    uint format = Year;
    int current = 0;
    int currentSize = 0;
    bool extendedYear = false;

    int yearSign = 1;
    int year = 0;
    int month = 0;
    int day = 1;
    int hour = 0;
    int minute = 0;
    int second = 0;
    int msec = 0;
    int offsetSign = 1;
    int offset = 0;
    bool seenT = false;
    bool seenZ = false; // Have seen zone, i.e. +HH:mm or literal Z.

    bool error = false;
    if (*ch == '+' || *ch == '-') {
        extendedYear = true;
        if (*ch == '-')
            yearSign = -1;
        ++ch;
    }
    for (; ch <= end && !error && format != Done; ++ch) {
        if (*ch >= '0' && *ch <= '9') {
            current *= 10;
            current += ch->unicode() - '0';
            ++currentSize;
        } else { // other char, delimits field
            switch (format) {
            case Year:
                year = current;
                if (extendedYear)
                    error = (currentSize != 6);
                else
                    error = (currentSize != 4);
                break;
            case Month:
                month = current - 1;
                error = (currentSize != 2) || month > 11;
                break;
            case Day:
                day = current;
                error = (currentSize != 2) || day > 31;
                break;
            case Hour:
                hour = current;
                error = (currentSize != 2) || hour > 24;
                break;
            case Minute:
                minute = current;
                error = (currentSize != 2) || minute >= 60;
                break;
            case Second:
                second = current;
                error = (currentSize != 2) || second > 60;
                break;
            case MilliSecond:
                msec = current;
                error = (currentSize != 3);
                break;
            case TimezoneHour:
                Q_ASSERT(offset == 0 && !seenZ);
                offset = current * 60;
                error = (currentSize != 2) || current > 23;
                seenZ = true;
                break;
            case TimezoneMinute:
                offset += current;
                error = (currentSize != 2) || current >= 60;
                break;
            }
            if (*ch == 'T') {
                if (format >= Hour)
                    error = true;
                format = Hour;
                seenT = true;
            } else if (*ch == '-') {
                if (format < Day)
                    ++format;
                else if (format < Minute)
                    error = true;
                else if (format >= TimezoneHour)
                    error = true;
                else {
                    Q_ASSERT(offset == 0 && !seenZ);
                    offsetSign = -1;
                    format = TimezoneHour;
                }
            } else if (*ch == ':') {
                if (format != Hour && format != Minute && format != TimezoneHour)
                    error = true;
                ++format;
            } else if (*ch == '.') {
                if (format != Second)
                    error = true;
                ++format;
            } else if (*ch == '+') {
                if (seenZ || format < Minute || format >= TimezoneHour)
                    error = true;
                format = TimezoneHour;
            } else if (*ch == 'Z') {
                if (seenZ || format < Minute || format >= TimezoneHour)
                    error = true;
                else
                    Q_ASSERT(offset == 0);
                format = Done;
                seenZ = true;
            } else if (ch->unicode() == 0) {
                format = Done;
            }
            current = 0;
            currentSize = 0;
        }
    }

    if (!error) {
        double t = MakeDate(MakeDay(year * yearSign, month, day), MakeTime(hour, minute, second, msec));
        if (seenZ)
            t -= offset * offsetSign * 60 * 1000;
        else if (seenT) // No zone specified, treat date-time as local time
            t = UTC(t, localTZA);
        // else: treat plain date as already in UTC
        return TimeClip(t);
    }

    QDateTime dt = QDateTime::fromString(s, Qt::TextDate);
    if (!dt.isValid())
        dt = QDateTime::fromString(s, Qt::ISODate);
    if (!dt.isValid())
        dt = QDateTime::fromString(s, Qt::RFC2822Date);
    if (!dt.isValid()) {
        const QString formats[] = {
            QStringLiteral("M/d/yyyy"),
            QStringLiteral("M/d/yyyy hh:mm"),
            QStringLiteral("M/d/yyyy hh:mm A"),

            QStringLiteral("M/d/yyyy, hh:mm"),
            QStringLiteral("M/d/yyyy, hh:mm A"),

            QStringLiteral("MMM d yyyy"),
            QStringLiteral("MMM d yyyy hh:mm"),
            QStringLiteral("MMM d yyyy hh:mm:ss"),
            QStringLiteral("MMM d yyyy, hh:mm"),
            QStringLiteral("MMM d yyyy, hh:mm:ss"),

            QStringLiteral("MMMM d yyyy"),
            QStringLiteral("MMMM d yyyy hh:mm"),
            QStringLiteral("MMMM d yyyy hh:mm:ss"),
            QStringLiteral("MMMM d yyyy, hh:mm"),
            QStringLiteral("MMMM d yyyy, hh:mm:ss"),

            QStringLiteral("MMM d, yyyy"),
            QStringLiteral("MMM d, yyyy hh:mm"),
            QStringLiteral("MMM d, yyyy hh:mm:ss"),

            QStringLiteral("MMMM d, yyyy"),
            QStringLiteral("MMMM d, yyyy hh:mm"),
            QStringLiteral("MMMM d, yyyy hh:mm:ss"),

            QStringLiteral("d MMM yyyy"),
            QStringLiteral("d MMM yyyy hh:mm"),
            QStringLiteral("d MMM yyyy hh:mm:ss"),
            QStringLiteral("d MMM yyyy, hh:mm"),
            QStringLiteral("d MMM yyyy, hh:mm:ss"),

            QStringLiteral("d MMMM yyyy"),
            QStringLiteral("d MMMM yyyy hh:mm"),
            QStringLiteral("d MMMM yyyy hh:mm:ss"),
            QStringLiteral("d MMMM yyyy, hh:mm"),
            QStringLiteral("d MMMM yyyy, hh:mm:ss"),

            QStringLiteral("d MMM, yyyy"),
            QStringLiteral("d MMM, yyyy hh:mm"),
            QStringLiteral("d MMM, yyyy hh:mm:ss"),

            QStringLiteral("d MMMM, yyyy"),
            QStringLiteral("d MMMM, yyyy hh:mm"),
            QStringLiteral("d MMMM, yyyy hh:mm:ss"),
        };

        for (const QString &format : formats) {
            dt = format.indexOf(QLatin1String("hh:mm")) < 0
                ? QDateTime(QDate::fromString(s, format),
                            QTime(0, 0, 0), Qt::UTC)
                : QDateTime::fromString(s, format); // as local time
            if (dt.isValid())
                break;
        }
    }
    if (!dt.isValid())
        return qt_qnan();
    return TimeClip(dt.toMSecsSinceEpoch());
}

/*!
  \internal

  Converts the ECMA Date value \tt (in UTC form) to QDateTime
  according to \a spec.
*/
static inline QDateTime ToDateTime(double t, Qt::TimeSpec spec)
{
    if (std::isnan(t))
        return QDateTime();
    return QDateTime::fromMSecsSinceEpoch(t, Qt::UTC).toTimeSpec(spec);
}

static inline QString ToString(double t, double localTZA)
{
    if (std::isnan(t))
        return QStringLiteral("Invalid Date");
    QString str = ToDateTime(t, Qt::LocalTime).toString() + QLatin1String(" GMT");
    double tzoffset = localTZA + DaylightSavingTA(t, localTZA);
    if (tzoffset) {
        int hours = static_cast<int>(::fabs(tzoffset) / 1000 / 60 / 60);
        int mins = int(::fabs(tzoffset) / 1000 / 60) % 60;
        str.append(QLatin1Char((tzoffset > 0) ?  '+' : '-'));
        if (hours < 10)
            str.append(QLatin1Char('0'));
        str.append(QString::number(hours));
        if (mins < 10)
            str.append(QLatin1Char('0'));
        str.append(QString::number(mins));
    }
    return str;
}

static inline QString ToUTCString(double t)
{
    if (std::isnan(t))
        return QStringLiteral("Invalid Date");
    return ToDateTime(t, Qt::UTC).toString();
}

static inline QString ToDateString(double t)
{
    return ToDateTime(t, Qt::LocalTime).date().toString();
}

static inline QString ToTimeString(double t)
{
    return ToDateTime(t, Qt::LocalTime).time().toString();
}

static inline QString ToLocaleString(double t)
{
    return QLocale().toString(ToDateTime(t, Qt::LocalTime), QLocale::ShortFormat);
}

static inline QString ToLocaleDateString(double t)
{
    return QLocale().toString(ToDateTime(t, Qt::LocalTime).date(), QLocale::ShortFormat);
}

static inline QString ToLocaleTimeString(double t)
{
    return QLocale().toString(ToDateTime(t, Qt::LocalTime).time(), QLocale::ShortFormat);
}

static double getLocalTZA()
{
#ifndef Q_OS_WIN
    tzset();
#endif
#ifdef USE_QTZ_SYSTEM_ZONE
    // TODO: QTimeZone::resetSystemTimeZone(), see QTBUG-56899 and comment above.
    // Standard offset, with no daylight-savings adjustment, in ms:
    return QTimeZone::systemTimeZone().standardTimeOffset(QDateTime::currentDateTime()) * 1e3;
#elif defined(USE_QDT_LOCAL_TIME)
    QDate today = QDate::currentDate();
    QDateTime near = today.startOfDay(Qt::LocalTime);
    // Early out if we're in standard time anyway:
    if (!near.isDaylightTime())
        return near.offsetFromUtc() * 1000;
    int year, month;
    today.getDate(&year, &month, nullptr);
    // One of the solstices is probably in standard time:
    QDate summer(year, 6, 21), winter(year - (month < 7 ? 1 : 0), 12, 21);
    // But check the one closest to the present by preference, in case there's a
    // standard time offset change between them:
    QDateTime far = summer.startOfDay(Qt::LocalTime);
    near = winter.startOfDay(Qt::LocalTime);
    if (month > 3 && month < 10)
        near.swap(far);
    bool isDst = near.isDaylightTime();
    if (isDst && far.isDaylightTime()) // Permanent DST, probably an hour west:
        return (qMin(near.offsetFromUtc(), far.offsetFromUtc()) - 3600) * 1000;
    return (isDst ? far : near).offsetFromUtc() * 1000;
#else
#  ifdef Q_OS_WIN
    TIME_ZONE_INFORMATION tzInfo;
    GetTimeZoneInformation(&tzInfo);
    return -tzInfo.Bias * 60.0 * 1000.0;
#  else
    struct tm t;
    time_t curr;
    time(&curr);
    localtime_r(&curr, &t); // Wrong: includes DST offset
    time_t locl = mktime(&t);
    gmtime_r(&curr, &t);
    time_t globl = mktime(&t);
    return (double(locl) - double(globl)) * 1000.0;
#  endif
#endif // USE_QTZ_SYSTEM_ZONE
}

DEFINE_OBJECT_VTABLE(DateObject);

void Heap::DateObject::init(const QDateTime &date)
{
    Object::init();
    this->date = date.isValid() ? TimeClip(date.toMSecsSinceEpoch()) : qt_qnan();
}

void Heap::DateObject::init(const QTime &time)
{
    Object::init();
    if (!time.isValid()) {
        date = qt_qnan();
        return;
    }

    /* We have to chose a date on which to instantiate this time.  All we really
     * care about is that it round-trips back to the same time if we extract the
     * time from it, which shall (via toQDateTime(), below) discard the date
     * part.  We need a date for which time-zone data is likely to be sane (so
     * MakeDay(0, 0, 0) was a bad choice; 2 BC, December 31st is before
     * time-zones were standardized), with no transition nearby in date.
     * QDateTime ignores DST transitions before 1970, but even then zone
     * transitions did happen; and DaylightSavingTA() will include DST, at odds
     * with QDateTime.  So pick a date since 1970 and prefer one when no zone
     * was in DST.  One such interval (according to the Olson database, at
     * least) was 1971 March 15th to April 17th.  Since converting a time to a
     * date-time without specifying a date is foolish, let's use April Fools'
     * day.
     */
    static const double d = MakeDay(1971, 3, 1);
    double t = MakeTime(time.hour(), time.minute(), time.second(), time.msec());
    date = TimeClip(UTC(MakeDate(d, t), internalClass->engine->localTZA));
}

QDateTime DateObject::toQDateTime() const
{
    return ToDateTime(date(), Qt::LocalTime);
}

DEFINE_OBJECT_VTABLE(DateCtor);

void Heap::DateCtor::init(QV4::ExecutionContext *scope)
{
    Heap::FunctionObject::init(scope, QStringLiteral("Date"));
}

ReturnedValue DateCtor::virtualCallAsConstructor(const FunctionObject *that, const Value *argv, int argc, const Value *newTarget)
{
    ExecutionEngine *v4 = that->engine();
    double t = 0;

    if (argc == 0)
        t = currentTime();

    else if (argc == 1) {
        Scope scope(v4);
        ScopedValue arg(scope, argv[0]);
        if (DateObject *d = arg->as<DateObject>()) {
            t = d->date();
        } else {
            arg = RuntimeHelpers::toPrimitive(arg, PREFERREDTYPE_HINT);

            if (String *s = arg->stringValue())
                t = ParseString(s->toQString(), v4->localTZA);
            else
                t = TimeClip(arg->toNumber());
        }
    }

    else { // d.argc > 1
        double year  = argv[0].toNumber();
        double month = argv[1].toNumber();
        double day  = argc >= 3 ? argv[2].toNumber() : 1;
        double hours = argc >= 4 ? argv[3].toNumber() : 0;
        double mins = argc >= 5 ? argv[4].toNumber() : 0;
        double secs = argc >= 6 ? argv[5].toNumber() : 0;
        double ms    = argc >= 7 ? argv[6].toNumber() : 0;
        if (year >= 0 && year <= 99)
            year += 1900;
        t = MakeDate(MakeDay(year, month, day), MakeTime(hours, mins, secs, ms));
        t = TimeClip(UTC(t, v4->localTZA));
    }

    ReturnedValue o = Encode(v4->newDateObject(Value::fromDouble(t)));
    if (!newTarget)
        return o;
    Scope scope(v4);
    ScopedObject obj(scope, o);
    obj->setProtoFromNewTarget(newTarget);
    return obj->asReturnedValue();
}

ReturnedValue DateCtor::virtualCall(const FunctionObject *m, const Value *, const Value *, int)
{
    ExecutionEngine *e = m->engine();
    double t = currentTime();
    return e->newString(ToString(t, e->localTZA))->asReturnedValue();
}

void DatePrototype::init(ExecutionEngine *engine, Object *ctor)
{
    Scope scope(engine);
    ScopedObject o(scope);
    ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
    ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(7));
    engine->localTZA = getLocalTZA();

    ctor->defineDefaultProperty(QStringLiteral("parse"), method_parse, 1);
    ctor->defineDefaultProperty(QStringLiteral("UTC"), method_UTC, 7);
    ctor->defineDefaultProperty(QStringLiteral("now"), method_now, 0);

    defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
    defineDefaultProperty(engine->id_toString(), method_toString, 0);
    defineDefaultProperty(QStringLiteral("toDateString"), method_toDateString, 0);
    defineDefaultProperty(QStringLiteral("toTimeString"), method_toTimeString, 0);
    defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0);
    defineDefaultProperty(QStringLiteral("toLocaleDateString"), method_toLocaleDateString, 0);
    defineDefaultProperty(QStringLiteral("toLocaleTimeString"), method_toLocaleTimeString, 0);
    defineDefaultProperty(engine->id_valueOf(), method_valueOf, 0);
    defineDefaultProperty(QStringLiteral("getTime"), method_getTime, 0);
    defineDefaultProperty(QStringLiteral("getYear"), method_getYear, 0);
    defineDefaultProperty(QStringLiteral("getFullYear"), method_getFullYear, 0);
    defineDefaultProperty(QStringLiteral("getUTCFullYear"), method_getUTCFullYear, 0);
    defineDefaultProperty(QStringLiteral("getMonth"), method_getMonth, 0);
    defineDefaultProperty(QStringLiteral("getUTCMonth"), method_getUTCMonth, 0);
    defineDefaultProperty(QStringLiteral("getDate"), method_getDate, 0);
    defineDefaultProperty(QStringLiteral("getUTCDate"), method_getUTCDate, 0);
    defineDefaultProperty(QStringLiteral("getDay"), method_getDay, 0);
    defineDefaultProperty(QStringLiteral("getUTCDay"), method_getUTCDay, 0);
    defineDefaultProperty(QStringLiteral("getHours"), method_getHours, 0);
    defineDefaultProperty(QStringLiteral("getUTCHours"), method_getUTCHours, 0);
    defineDefaultProperty(QStringLiteral("getMinutes"), method_getMinutes, 0);
    defineDefaultProperty(QStringLiteral("getUTCMinutes"), method_getUTCMinutes, 0);
    defineDefaultProperty(QStringLiteral("getSeconds"), method_getSeconds, 0);
    defineDefaultProperty(QStringLiteral("getUTCSeconds"), method_getUTCSeconds, 0);
    defineDefaultProperty(QStringLiteral("getMilliseconds"), method_getMilliseconds, 0);
    defineDefaultProperty(QStringLiteral("getUTCMilliseconds"), method_getUTCMilliseconds, 0);
    defineDefaultProperty(QStringLiteral("getTimezoneOffset"), method_getTimezoneOffset, 0);
    defineDefaultProperty(QStringLiteral("setTime"), method_setTime, 1);
    defineDefaultProperty(QStringLiteral("setMilliseconds"), method_setMilliseconds, 1);
    defineDefaultProperty(QStringLiteral("setUTCMilliseconds"), method_setUTCMilliseconds, 1);
    defineDefaultProperty(QStringLiteral("setSeconds"), method_setSeconds, 2);
    defineDefaultProperty(QStringLiteral("setUTCSeconds"), method_setUTCSeconds, 2);
    defineDefaultProperty(QStringLiteral("setMinutes"), method_setMinutes, 3);
    defineDefaultProperty(QStringLiteral("setUTCMinutes"), method_setUTCMinutes, 3);
    defineDefaultProperty(QStringLiteral("setHours"), method_setHours, 4);
    defineDefaultProperty(QStringLiteral("setUTCHours"), method_setUTCHours, 4);
    defineDefaultProperty(QStringLiteral("setDate"), method_setDate, 1);
    defineDefaultProperty(QStringLiteral("setUTCDate"), method_setUTCDate, 1);
    defineDefaultProperty(QStringLiteral("setMonth"), method_setMonth, 2);
    defineDefaultProperty(QStringLiteral("setUTCMonth"), method_setUTCMonth, 2);
    defineDefaultProperty(QStringLiteral("setYear"), method_setYear, 1);
    defineDefaultProperty(QStringLiteral("setFullYear"), method_setFullYear, 3);
    defineDefaultProperty(QStringLiteral("setUTCFullYear"), method_setUTCFullYear, 3);

    // ES6: B.2.4.3 & 20.3.4.43:
    // We have to use the *same object* for toUTCString and toGMTString
    {
        QString toUtcString(QStringLiteral("toUTCString"));
        QString toGmtString(QStringLiteral("toGMTString"));
        ScopedString us(scope, engine->newIdentifier(toUtcString));
        ScopedString gs(scope, engine->newIdentifier(toGmtString));
        ScopedFunctionObject toUtcGmtStringFn(scope, FunctionObject::createBuiltinFunction(engine, us, method_toUTCString, 0));
        defineDefaultProperty(us, toUtcGmtStringFn);
        defineDefaultProperty(gs, toUtcGmtStringFn);
    }

    defineDefaultProperty(QStringLiteral("toISOString"), method_toISOString, 0);
    defineDefaultProperty(QStringLiteral("toJSON"), method_toJSON, 1);
    defineDefaultProperty(engine->symbol_toPrimitive(), method_symbolToPrimitive, 1, Attr_ReadOnly_ButConfigurable);
}

double DatePrototype::getThisDate(ExecutionEngine *v4, const Value *thisObject)
{
    if (const DateObject *that = thisObject->as<DateObject>())
        return that->date();
    v4->throwTypeError();
    return 0;
}

ReturnedValue DatePrototype::method_parse(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    if (!argc)
        return Encode(qt_qnan());
    else
        return Encode(ParseString(argv[0].toQString(), f->engine()->localTZA));
}

ReturnedValue DatePrototype::method_UTC(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    const int numArgs = argc;
    if (numArgs < 1)
        return Encode(qQNaN());
    ExecutionEngine *e = f->engine();
    double year  = argv[0].toNumber();
    if (e->hasException)
        return Encode::undefined();
    double month = numArgs >= 2 ? argv[1].toNumber() : 0;
    if (e->hasException)
        return Encode::undefined();
    double day   = numArgs >= 3 ? argv[2].toNumber() : 1;
    if (e->hasException)
        return Encode::undefined();
    double hours = numArgs >= 4 ? argv[3].toNumber() : 0;
    if (e->hasException)
        return Encode::undefined();
    double mins  = numArgs >= 5 ? argv[4].toNumber() : 0;
    if (e->hasException)
        return Encode::undefined();
    double secs  = numArgs >= 6 ? argv[5].toNumber() : 0;
    if (e->hasException)
        return Encode::undefined();
    double ms    = numArgs >= 7 ? argv[6].toNumber() : 0;
    if (e->hasException)
        return Encode::undefined();
    double iyear = QV4::Value::toInteger(year);
    if (!qIsNaN(year) && iyear >= 0 && iyear <= 99)
        year = 1900 + iyear;
    double t = MakeDate(MakeDay(year, month, day),
                        MakeTime(hours, mins, secs, ms));
    return Encode(TimeClip(t));
}

ReturnedValue DatePrototype::method_now(const FunctionObject *, const Value *, const Value *, int)
{
    return Encode(currentTime());
}

ReturnedValue DatePrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(v4->newString(ToString(t, v4->localTZA)));
}

ReturnedValue DatePrototype::method_toDateString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(v4->newString(ToDateString(t)));
}

ReturnedValue DatePrototype::method_toTimeString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(v4->newString(ToTimeString(t)));
}

ReturnedValue DatePrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(v4->newString(ToLocaleString(t)));
}

ReturnedValue DatePrototype::method_toLocaleDateString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(v4->newString(ToLocaleDateString(t)));
}

ReturnedValue DatePrototype::method_toLocaleTimeString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(v4->newString(ToLocaleTimeString(t)));
}

ReturnedValue DatePrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getTime(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getYear(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = YearFromTime(LocalTime(t, v4->localTZA)) - 1900;
    return Encode(t);
}

ReturnedValue DatePrototype::method_getFullYear(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = YearFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCFullYear(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = YearFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getMonth(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = MonthFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCMonth(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = MonthFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getDate(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = DateFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCDate(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = DateFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getDay(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = WeekDay(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCDay(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = WeekDay(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getHours(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = HourFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCHours(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = HourFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getMinutes(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = MinFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCMinutes(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = MinFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getSeconds(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = SecFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCSeconds(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = SecFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = msFromTime(LocalTime(t, v4->localTZA));
    return Encode(t);
}

ReturnedValue DatePrototype::method_getUTCMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = msFromTime(t);
    return Encode(t);
}

ReturnedValue DatePrototype::method_getTimezoneOffset(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    double t = getThisDate(v4, thisObject);
    if (!std::isnan(t))
        t = (t - LocalTime(t, v4->localTZA)) / msPerMinute;
    return Encode(t);
}

ReturnedValue DatePrototype::method_setTime(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    self->setDate(TimeClip(t));
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    double ms = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    self->setDate(TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms)), v4->localTZA)));
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double ms = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    self->setDate(TimeClip(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms))));
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setSeconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    double sec = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double ms = (argc < 2) ? msFromTime(t) : argv[1].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms)), v4->localTZA));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCSeconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    double sec = argc ? argv[0].toNumber() : qt_qnan();
    double ms = (argc < 2) ? msFromTime(t) : argv[1].toNumber();
    t = TimeClip(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms)));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setMinutes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    double min = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double sec = (argc < 2) ? SecFromTime(t) : argv[1].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double ms = (argc < 3) ? msFromTime(t) : argv[2].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms)), v4->localTZA));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCMinutes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    double min = argc ? argv[0].toNumber() : qt_qnan();
    double sec = (argc < 2) ? SecFromTime(t) : argv[1].toNumber();
    double ms = (argc < 3) ? msFromTime(t) : argv[2].toNumber();
    t = TimeClip(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms)));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setHours(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    double hour = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double min = (argc < 2) ? MinFromTime(t) : argv[1].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double sec = (argc < 3) ? SecFromTime(t) : argv[2].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double ms = (argc < 4) ? msFromTime(t) : argv[3].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(UTC(MakeDate(Day(t), MakeTime(hour, min, sec, ms)), v4->localTZA));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCHours(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    double hour = argc ? argv[0].toNumber() : qt_qnan();
    double min = (argc < 2) ? MinFromTime(t) : argv[1].toNumber();
    double sec = (argc < 3) ? SecFromTime(t) : argv[2].toNumber();
    double ms = (argc < 4) ? msFromTime(t) : argv[3].toNumber();
    t = TimeClip(MakeDate(Day(t), MakeTime(hour, min, sec, ms)));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setDate(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    double date = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)), v4->localTZA));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCDate(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double date = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setMonth(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    double month = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double date = (argc < 2) ? DateFromTime(t) : argv[1].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t)), v4->localTZA));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCMonth(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    double month = argc ? argv[0].toNumber() : qt_qnan();
    double date = (argc < 2) ? DateFromTime(t) : argv[1].toNumber();
    t = TimeClip(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t)));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setYear(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    if (std::isnan(t))
        t = 0;
    else
        t = LocalTime(t, v4->localTZA);
    double year = argc ? argv[0].toNumber() : qt_qnan();
    double r;
    if (std::isnan(year)) {
        r = qt_qnan();
    } else {
        if ((QV4::Value::toInteger(year) >= 0) && (QV4::Value::toInteger(year) <= 99))
            year += 1900;
        r = MakeDay(year, MonthFromTime(t), DateFromTime(t));
        r = UTC(MakeDate(r, TimeWithinDay(t)), v4->localTZA);
        r = TimeClip(r);
    }
    self->setDate(r);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setUTCFullYear(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    double year = argc ? argv[0].toNumber() : qt_qnan();
    double month = (argc < 2) ? MonthFromTime(t) : argv[1].toNumber();
    double date = (argc < 3) ? DateFromTime(t) : argv[2].toNumber();
    t = TimeClip(MakeDate(MakeDay(year, month, date), TimeWithinDay(t)));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_setFullYear(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = LocalTime(self->date(), v4->localTZA);
    if (v4->hasException)
        return QV4::Encode::undefined();
    if (std::isnan(t))
        t = 0;
    double year = argc ? argv[0].toNumber() : qt_qnan();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double month = (argc < 2) ? MonthFromTime(t) : argv[1].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    double date = (argc < 3) ? DateFromTime(t) : argv[2].toNumber();
    if (v4->hasException)
        return QV4::Encode::undefined();
    t = TimeClip(UTC(MakeDate(MakeDay(year, month, date), TimeWithinDay(t)), v4->localTZA));
    self->setDate(t);
    return Encode(self->date());
}

ReturnedValue DatePrototype::method_toUTCString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    return Encode(v4->newString(ToUTCString(t)));
}

static void addZeroPrefixedInt(QString &str, int num, int nDigits)
{
    str.resize(str.size() + nDigits);

    QChar *c = str.data() + str.size() - 1;
    while (nDigits) {
        *c = QChar(num % 10 + '0');
        num /= 10;
        --c;
        --nDigits;
    }
}

ReturnedValue DatePrototype::method_toISOString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>());
    if (!self)
        return v4->throwTypeError();

    double t = self->date();
    if (!std::isfinite(t))
        RETURN_RESULT(v4->throwRangeError(*thisObject));

    QString result;
    int year = (int)YearFromTime(t);
    if (year < 0 || year > 9999) {
        if (qAbs(year) >= 1000000)
            RETURN_RESULT(v4->throwRangeError(*thisObject));
        result += year < 0 ? QLatin1Char('-') : QLatin1Char('+');
        year = qAbs(year);
        addZeroPrefixedInt(result, year, 6);
    } else {
        addZeroPrefixedInt(result, year, 4);
    }
    result += QLatin1Char('-');
    addZeroPrefixedInt(result, (int)MonthFromTime(t) + 1, 2);
    result += QLatin1Char('-');
    addZeroPrefixedInt(result, (int)DateFromTime(t), 2);
    result += QLatin1Char('T');
    addZeroPrefixedInt(result, HourFromTime(t), 2);
    result += QLatin1Char(':');
    addZeroPrefixedInt(result, MinFromTime(t), 2);
    result += QLatin1Char(':');
    addZeroPrefixedInt(result, SecFromTime(t), 2);
    result += QLatin1Char('.');
    addZeroPrefixedInt(result, msFromTime(t), 3);
    result += QLatin1Char('Z');

    return Encode(v4->newString(result));
}

ReturnedValue DatePrototype::method_toJSON(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    Scope scope(v4);
    ScopedObject O(scope, thisObject->toObject(v4));
    if (v4->hasException)
        return QV4::Encode::undefined();

    ScopedValue tv(scope, RuntimeHelpers::toPrimitive(O, NUMBER_HINT));

    if (tv->isNumber() && !std::isfinite(tv->toNumber()))
        return Encode::null();

    ScopedString s(scope, v4->newString(QStringLiteral("toISOString")));
    ScopedValue v(scope, O->get(s));
    FunctionObject *toIso = v->as<FunctionObject>();

    if (!toIso)
        return v4->throwTypeError();

    return checkedResult(v4, toIso->call(O, nullptr, 0));
}

ReturnedValue DatePrototype::method_symbolToPrimitive(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
    ExecutionEngine *e = f->engine();
    if (!thisObject->isObject() || !argc || !argv->isString())
        return e->throwTypeError();

    String *hint = argv->stringValue();
    PropertyKey id = hint->toPropertyKey();
    if (id == e->id_default()->propertyKey())
        hint = e->id_string();
    else if (id != e->id_string()->propertyKey() && id != e->id_number()->propertyKey())
        return e->throwTypeError();

    return RuntimeHelpers::ordinaryToPrimitive(e, static_cast<const Object *>(thisObject), hint);
}

void DatePrototype::timezoneUpdated(ExecutionEngine *e)
{
    e->localTZA = getLocalTZA();
}
