/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QDebug>

#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlcontext.h>
#include <QtCore/QDateTime>
#include <QtCore/qscopeguard.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimezone.h>
#include <qcolor.h>
#include "../../shared/util.h"

#include <time.h>

class tst_qqmllocale : public QQmlDataTest
{
    Q_OBJECT
public:
    tst_qqmllocale() { }

private slots:
    void defaultLocale();

    void properties_data();
    void properties();
    void currencySymbol_data();
    void currencySymbol();
    void monthName_data();
    void monthName();
    void standaloneMonthName_data();
    void standaloneMonthName();
    void dayName_data();
    void dayName();
    void standaloneDayName_data();
    void standaloneDayName();
    void firstDayOfWeek_data();
    void firstDayOfWeek();
    void weekDays_data();
    void weekDays();
    void uiLanguages_data();
    void uiLanguages();
    void dateFormat_data();
    void dateFormat();
    void dateTimeFormat_data();
    void dateTimeFormat();
    void timeFormat_data();
    void timeFormat();
#if defined(Q_OS_UNIX) && QT_CONFIG(timezone)
    void timeZoneUpdated();
#endif

    void dateToLocaleString_data();
    void dateToLocaleString();
    void dateToLocaleStringFormatted_data();
    void dateToLocaleStringFormatted();
    void dateToLocaleDateString_data();
    void dateToLocaleDateString();
    void dateToLocaleDateStringFormatted_data();
    void dateToLocaleDateStringFormatted();
    void dateToLocaleTimeString_data();
    void dateToLocaleTimeString();
    void dateToLocaleTimeStringFormatted_data();
    void dateToLocaleTimeStringFormatted();
    void dateFromLocaleString_data();
    void dateFromLocaleString();
    void dateFromLocaleDateString_data();
    void dateFromLocaleDateString();
    void dateFromLocaleTimeString_data();
    void dateFromLocaleTimeString();

    void numberToLocaleString_data();
    void numberToLocaleString();
    void numberToLocaleCurrencyString_data();
    void numberToLocaleCurrencyString();
    void numberFromLocaleString_data();
    void numberFromLocaleString();
    void numberConstToLocaleString();
    void numberOptions();

    void stringLocaleCompare_data();
    void stringLocaleCompare();

    void localeAsCppProperty();
private:
    void addPropertyData(const QString &l);
    QVariant getProperty(QObject *obj, const QString &locale, const QString &property);
    void addCurrencySymbolData(const QString &locale);
    void addStandardFormatData();
    void addFormatNameData(const QString &locale);
    void addDateTimeFormatData(const QString &l);
    void addDateFormatData(const QString &l);
    void addTimeFormatData(const QString &l);
    QQmlEngine engine;
};

void tst_qqmllocale::defaultLocale()
{
    QQmlComponent c(&engine, testFileUrl("properties.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QCOMPARE(obj->property("name").toString(), QLocale().name());
}

#define LOCALE_PROP(type,prop) { #prop, QVariant(type(qlocale.prop())) }

void tst_qqmllocale::addPropertyData(const QString &l)
{
    QLocale qlocale(l);

    struct {
        const char *name;
        QVariant value;
    }
    values[] = {
        LOCALE_PROP(QString,name),
        LOCALE_PROP(QString,amText),
        LOCALE_PROP(QString,pmText),
        LOCALE_PROP(QString,nativeLanguageName),
        LOCALE_PROP(QString,nativeCountryName),
        LOCALE_PROP(QString,decimalPoint),
        LOCALE_PROP(QString,groupSeparator),
        LOCALE_PROP(QString,percent),
        LOCALE_PROP(QString,zeroDigit),
        LOCALE_PROP(QString,negativeSign),
        LOCALE_PROP(QString,positiveSign),
        LOCALE_PROP(QString,exponential),
        LOCALE_PROP(int,measurementSystem),
        LOCALE_PROP(int,textDirection),
        { nullptr, QVariant() }
    };

    int i = 0;
    while (values[i].name) {
        QByteArray n = l.toLatin1() + ':' + values[i].name;
        QTest::newRow(n.constData()) << l << QByteArray(values[i].name) << values[i].value;
        ++i;
    }
}

void tst_qqmllocale::properties_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QByteArray>("property");
    QTest::addColumn<QVariant>("value");

    addPropertyData("en_US");
    addPropertyData("de_DE");
    addPropertyData("ar_SA");
    addPropertyData("hi_IN");
    addPropertyData("zh_CN");
    addPropertyData("th_TH");
}

void tst_qqmllocale::properties()
{
    QFETCH(QString, locale);
    QFETCH(QByteArray, property);
    QFETCH(QVariant, value);

    QQmlComponent c(&engine, testFileUrl("properties.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QCOMPARE(obj->property(property), value);

    delete obj;
}

void tst_qqmllocale::addCurrencySymbolData(const QString &l)
{
    QByteArray locale = l.toLatin1();
    QTest::newRow(locale.constData()) << l << -1;
    QByteArray t(locale);
    t += " CurrencyIsoCode";
    QTest::newRow(t.constData()) << l << (int)QLocale::CurrencyIsoCode;
    t = locale + " CurrencySymbol";
    QTest::newRow(t.constData()) << l << (int)QLocale::CurrencySymbol;
    t = locale + " CurrencyDisplayName";
    QTest::newRow(t.constData()) << l << (int)QLocale::CurrencyDisplayName;
}

void tst_qqmllocale::currencySymbol_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<int>("param");

    addCurrencySymbolData("en_US");
    addCurrencySymbolData("de_DE");
    addCurrencySymbolData("ar_SA");
    addCurrencySymbolData("hi_IN");
    addCurrencySymbolData("zh_CN");
    addCurrencySymbolData("th_TH");
}

void tst_qqmllocale::currencySymbol()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;
    QLocale::CurrencySymbolFormat format = QLocale::CurrencySymbol;

    if (param >= 0)
        format = QLocale::CurrencySymbolFormat(param);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QMetaObject::invokeMethod(obj, "currencySymbol", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(int(format))));

    QCOMPARE(val.toString(), l.currencySymbol(format));

    delete obj;
}

void tst_qqmllocale::addFormatNameData(const QString &l)
{
    QByteArray locale = l.toLatin1();
    QTest::newRow(locale.constData()) << l << -1;
    QByteArray t(locale);
    t += " LongFormat";
    QTest::newRow(t.constData()) << l << (int)QLocale::LongFormat;
    t = locale + " ShortFormat";
    QTest::newRow(t.constData()) << l << (int)QLocale::ShortFormat;
    t = locale + " NarrowFormat";
    QTest::newRow(t.constData()) << l << (int)QLocale::NarrowFormat;
}

void tst_qqmllocale::addStandardFormatData()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<int>("param");

    addFormatNameData("en_US");
    addFormatNameData("de_DE");
    addFormatNameData("ar_SA");
    addFormatNameData("hi_IN");
    addFormatNameData("zh_CN");
    addFormatNameData("th_TH");
}

void tst_qqmllocale::monthName_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::monthName()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;
    QLocale::FormatType format = QLocale::LongFormat;
    if (param >= 0)
        format = QLocale::FormatType(param);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    for (int i = 0; i <= 11; ++i) {
        QMetaObject::invokeMethod(obj, "monthName", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(i)),
            Q_ARG(QVariant, QVariant(int(format))));

        // QLocale January == 1, JS Date January == 0
        QCOMPARE(val.toString(), l.monthName(i+1, format));
    }

    delete obj;
}

void tst_qqmllocale::standaloneMonthName_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::standaloneMonthName()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;
    QLocale::FormatType format = QLocale::LongFormat;
    if (param >= 0)
        format = QLocale::FormatType(param);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    for (int i = 0; i <= 11; ++i) {
        QMetaObject::invokeMethod(obj, "standaloneMonthName", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(i)),
            Q_ARG(QVariant, QVariant(int(format))));

        // QLocale January == 1, JS Date January == 0
        QCOMPARE(val.toString(), l.standaloneMonthName(i+1, format));
    }

    delete obj;
}

void tst_qqmllocale::dayName_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::dayName()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;
    QLocale::FormatType format = QLocale::LongFormat;
    if (param >= 0)
        format = QLocale::FormatType(param);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    for (int i = 1; i <= 7; ++i) {
        QMetaObject::invokeMethod(obj, "dayName", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(i)),
            Q_ARG(QVariant, QVariant(int(format))));

        QCOMPARE(val.toString(), l.dayName(i, format));
    }

    delete obj;
}

void tst_qqmllocale::standaloneDayName_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::standaloneDayName()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;
    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    for (int i = 1; i <= 7; ++i) {
        QMetaObject::invokeMethod(obj, "standaloneDayName", Qt::DirectConnection,
                Q_RETURN_ARG(QVariant, val),
                Q_ARG(QVariant, QVariant(i)),
                Q_ARG(QVariant, QVariant(int(format))));

        QCOMPARE(val.toString(), l.standaloneDayName(i, format));
    }

    delete obj;
}

void tst_qqmllocale::firstDayOfWeek_data()
{
    QTest::addColumn<QString>("locale");

    QTest::newRow("en_US") << "en_US";
    QTest::newRow("de_DE") << "de_DE";
    QTest::newRow("ar_SA") << "ar_SA";
    QTest::newRow("hi_IN") << "hi_IN";
    QTest::newRow("zh_CN") << "zh_CN";
    QTest::newRow("th_TH") << "th_TH";
}

void tst_qqmllocale::firstDayOfWeek()
{
    QFETCH(QString, locale);

    QQmlComponent c(&engine, testFileUrl("properties.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val = obj->property("firstDayOfWeek");
    QCOMPARE(val.userType(), QMetaType::Int);

    int day = int(QLocale(locale).firstDayOfWeek());
    if (day == 7) // JS Date days in range 0(Sunday) to 6(Saturday)
        day = 0;
    QCOMPARE(day, val.toInt());

    delete obj;
}

void tst_qqmllocale::weekDays_data()
{
    QTest::addColumn<QString>("locale");

    QTest::newRow("en_US") << "en_US";
    QTest::newRow("de_DE") << "de_DE";
    QTest::newRow("ar_SA") << "ar_SA";
    QTest::newRow("hi_IN") << "hi_IN";
    QTest::newRow("zh_CN") << "zh_CN";
    QTest::newRow("th_TH") << "th_TH";
}

void tst_qqmllocale::weekDays()
{
    QFETCH(QString, locale);

    QQmlComponent c(&engine, testFileUrl("properties.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val = obj->property("weekDays");
    QCOMPARE(val.userType(), qMetaTypeId<QJSValue>());

    QList<QVariant> qmlDays = val.toList();
    QList<Qt::DayOfWeek> days = QLocale(locale).weekdays();

    QCOMPARE(days.count(), qmlDays.count());

    for (int i = 0; i < days.count(); ++i) {
        int day = int(days.at(i));
        if (day == 7) // JS Date days in range 0(Sunday) to 6(Saturday)
            day = 0;
        QCOMPARE(day, qmlDays.at(i).toInt());
    }

    delete obj;
}

void tst_qqmllocale::uiLanguages_data()
{
    QTest::addColumn<QString>("locale");

    QTest::newRow("en_US") << "en_US";
    QTest::newRow("de_DE") << "de_DE";
    QTest::newRow("ar_SA") << "ar_SA";
    QTest::newRow("hi_IN") << "hi_IN";
    QTest::newRow("zh_CN") << "zh_CN";
    QTest::newRow("th_TH") << "th_TH";
}

void tst_qqmllocale::uiLanguages()
{
    QFETCH(QString, locale);

    QQmlComponent c(&engine, testFileUrl("properties.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val = obj->property("uiLanguages");
    QCOMPARE(val.userType(), qMetaTypeId<QJSValue>());

    QList<QVariant> qmlLangs = val.toList();
    QStringList langs = QLocale(locale).uiLanguages();

    QCOMPARE(langs.count(), qmlLangs.count());

    for (int i = 0; i < langs.count(); ++i) {
        QCOMPARE(langs.at(i), qmlLangs.at(i).toString());
    }

    delete obj;
}


void tst_qqmllocale::dateTimeFormat_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::dateTimeFormat()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);
    QMetaObject::invokeMethod(obj, "dateTimeFormat", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(param)));

    QCOMPARE(val.toString(), l.dateTimeFormat(format));
}

void tst_qqmllocale::dateFormat_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::dateFormat()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);
    QMetaObject::invokeMethod(obj, "dateFormat", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(param)));

    QCOMPARE(val.toString(), l.dateFormat(format));
}

void tst_qqmllocale::timeFormat_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::timeFormat()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("functions.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QVariant val;

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);
    QMetaObject::invokeMethod(obj, "timeFormat", Qt::DirectConnection,
            Q_RETURN_ARG(QVariant, val),
            Q_ARG(QVariant, QVariant(param)));

    QCOMPARE(val.toString(), l.timeFormat(format));
}

void tst_qqmllocale::dateToLocaleString_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::dateToLocaleString()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);

    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(param)));

    QLocale l(locale);
    QCOMPARE(val.toString(), l.toString(dt, format));
}

void tst_qqmllocale::addDateTimeFormatData(const QString &l)
{
    const char *formats[] = {
        "hh:mm dd.MM.yyyy",
        "h:m:sap ddd MMMM d yy",
        "'The date and time is: 'H:mm:ss:zzz dd/MM/yy",
        "MMM d yyyy HH:mm t",
        nullptr
    };
    QByteArray locale = l.toLatin1();
    int i = 0;
    while (formats[i]) {
        QByteArray t(locale);
        t += ' ';
        t += formats[i];
        QTest::newRow(t.constData()) << l << QString(formats[i]);
        ++i;
    }
}

void tst_qqmllocale::dateToLocaleStringFormatted_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("format");

    addDateTimeFormatData("en_US");
    addDateTimeFormatData("de_DE");
    addDateTimeFormatData("ar_SA");
    addDateTimeFormatData("hi_IN");
    addDateTimeFormatData("zh_CN");
    addDateTimeFormatData("th_TH");
}

void tst_qqmllocale::dateToLocaleStringFormatted()
{
    QFETCH(QString, locale);
    QFETCH(QString, format);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(format)));

    QLocale l(locale);
    QCOMPARE(val.toString(), l.toString(dt, format));
}

void tst_qqmllocale::dateToLocaleDateString_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::dateToLocaleDateString()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);

    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleDateString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(param)));

    QLocale l(locale);
    QCOMPARE(val.toString(), l.toString(dt.date(), format));
}

void tst_qqmllocale::addDateFormatData(const QString &l)
{
    const char *formats[] = {
        "dd.MM.yyyy",
        "ddd MMMM d yy",
        "'The date is: 'dd/MM/yy",
        "MMM d yyyy",
        nullptr
    };
    QByteArray locale = l.toLatin1();
    int i = 0;
    while (formats[i]) {
        QByteArray t(locale);
        t += ' ';
        t += formats[i];
        QTest::newRow(t.constData()) << l << QString(formats[i]);
        ++i;
    }
}

void tst_qqmllocale::dateToLocaleDateStringFormatted_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("format");

    addDateFormatData("en_US");
    addDateFormatData("de_DE");
    addDateFormatData("ar_SA");
    addDateFormatData("hi_IN");
    addDateFormatData("zh_CN");
    addDateFormatData("th_TH");
}

void tst_qqmllocale::dateToLocaleDateStringFormatted()
{
    QFETCH(QString, locale);
    QFETCH(QString, format);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(format)));

    QLocale l(locale);
    QCOMPARE(val.toString(), l.toString(dt.date(), format));
}

void tst_qqmllocale::dateToLocaleTimeString_data()
{
    addStandardFormatData();
}

void tst_qqmllocale::dateToLocaleTimeString()
{
    QFETCH(QString, locale);
    QFETCH(int, param);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale::FormatType format = param < 0 ? QLocale::LongFormat : QLocale::FormatType(param);

    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleTimeString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(param)));

    QLocale l(locale);
    QCOMPARE(val.toString(), l.toString(dt.time(), format));
}

void tst_qqmllocale::addTimeFormatData(const QString &l)
{
    const char *formats[] = {
        "hh:mm",
        "h:m:sap",
        "'The time is: 'H:mm:ss:zzz",
        "HH:mm t",
        nullptr
    };
    QByteArray locale = l.toLatin1();
    int i = 0;
    while (formats[i]) {
        QByteArray t(locale);
        t += ' ';
        t += formats[i];
        QTest::newRow(t.constData()) << l << QString(formats[i]);
        ++i;
    }
}

void tst_qqmllocale::dateToLocaleTimeStringFormatted_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("format");

    addTimeFormatData("en_US");
    addTimeFormatData("de_DE");
    addTimeFormatData("ar_SA");
    addTimeFormatData("hi_IN");
    addTimeFormatData("zh_CN");
    addTimeFormatData("th_TH");
}

void tst_qqmllocale::dateToLocaleTimeStringFormatted()
{
    QFETCH(QString, locale);
    QFETCH(QString, format);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(format)));

    QLocale l(locale);
    QCOMPARE(val.toString(), l.toString(dt, format));
}

void tst_qqmllocale::dateFromLocaleString_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("format");

    QTest::newRow("en_US 1") << "en_US" << "dddd, MMMM d, yyyy h:mm:ss AP";
    QTest::newRow("en_US long") << "en_US" << QLocale("en_US").dateTimeFormat();
    QTest::newRow("en_US short") << "en_US" << QLocale("en_US").dateTimeFormat(QLocale::ShortFormat);
    QTest::newRow("de_DE long") << "de_DE" << QLocale("de_DE").dateTimeFormat();
    QTest::newRow("de_DE short") << "de_DE" << QLocale("de_DE").dateTimeFormat(QLocale::ShortFormat);
    QTest::newRow("ar_SA long") << "ar_SA" << QLocale("ar_SA").dateTimeFormat();
    QTest::newRow("ar_SA short") << "ar_SA" << QLocale("ar_SA").dateTimeFormat(QLocale::ShortFormat);
    QTest::newRow("zh_CN long") << "zh_CN" << QLocale("zh_CN").dateTimeFormat();
    QTest::newRow("zh_CN short") << "zh_CN" << QLocale("zh_CN").dateTimeFormat(QLocale::ShortFormat);
}

void tst_qqmllocale::dateFromLocaleString()
{
    QFETCH(QString, locale);
    QFETCH(QString, format);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale l(locale);
    const QString localeText(l.toString(dt, format));
    QVariant val;
    QMetaObject::invokeMethod(obj, "fromLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(localeText)),
        Q_ARG(QVariant, QVariant(format)));

    QDateTime pd = l.toDateTime(localeText, format);
    QCOMPARE(val.toDateTime(), pd);
}

void tst_qqmllocale::dateFromLocaleDateString_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("format");

    QTest::newRow("en_US 1") << "en_US" << "dddd, MMMM d, yyyy h:mm:ss AP";
    QTest::newRow("en_US long") << "en_US" << QLocale("en_US").dateFormat();
    QTest::newRow("en_US short") << "en_US" << QLocale("en_US").dateFormat(QLocale::ShortFormat);
    QTest::newRow("de_DE long") << "de_DE" << QLocale("de_DE").dateFormat();
    QTest::newRow("de_DE short") << "de_DE" << QLocale("de_DE").dateFormat(QLocale::ShortFormat);
    QTest::newRow("ar_SA long") << "ar_SA" << QLocale("ar_SA").dateFormat();
    QTest::newRow("ar_SA short") << "ar_SA" << QLocale("ar_SA").dateFormat(QLocale::ShortFormat);
    QTest::newRow("zh_CN long") << "zh_CN" << QLocale("zh_CN").dateFormat();
    QTest::newRow("zh_CN short") << "zh_CN" << QLocale("zh_CN").dateFormat(QLocale::ShortFormat);
}

void tst_qqmllocale::dateFromLocaleDateString()
{
    QFETCH(QString, locale);
    QFETCH(QString, format);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale l(locale);
    const QString localeText(l.toString(dt, format));
    QVariant val;
    QMetaObject::invokeMethod(obj, "fromLocaleDateString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(localeText)),
        Q_ARG(QVariant, QVariant(format)));

    QDate pd = l.toDate(localeText, format);
    QCOMPARE(val.toDate(), pd);
}

void tst_qqmllocale::dateFromLocaleTimeString_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("format");

    QTest::newRow("en_US 1") << "en_US" << "dddd, MMMM d, yyyy h:mm:ss AP";
    QTest::newRow("en_US long") << "en_US" << QLocale("en_US").timeFormat();
    QTest::newRow("en_US short") << "en_US" << QLocale("en_US").timeFormat(QLocale::ShortFormat);
    QTest::newRow("de_DE long") << "de_DE" << QLocale("de_DE").timeFormat();
    QTest::newRow("de_DE short") << "de_DE" << QLocale("de_DE").timeFormat(QLocale::ShortFormat);
    QTest::newRow("ar_SA long") << "ar_SA" << QLocale("ar_SA").timeFormat();
    QTest::newRow("ar_SA short") << "ar_SA" << QLocale("ar_SA").timeFormat(QLocale::ShortFormat);
    QTest::newRow("zh_CN long") << "zh_CN" << QLocale("zh_CN").timeFormat();
    QTest::newRow("zh_CN short") << "zh_CN" << QLocale("zh_CN").timeFormat(QLocale::ShortFormat);
}

void tst_qqmllocale::dateFromLocaleTimeString()
{
    QFETCH(QString, locale);
    QFETCH(QString, format);

    QQmlComponent c(&engine, testFileUrl("date.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    const QDateTime dt(QDate(2011, 10, 7), QTime(18, 53, 48, 345));

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale l(locale);
    const QString localeText(l.toString(dt, format));
    QVariant val;
    QMetaObject::invokeMethod(obj, "fromLocaleTimeString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(localeText)),
        Q_ARG(QVariant, QVariant(format)));

    QTime pd = l.toTime(localeText, format);
    QCOMPARE(val.toTime(), pd);
}

void tst_qqmllocale::numberToLocaleString_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<char>("format");
    QTest::addColumn<int>("prec");

    QTest::newRow("en_US 1") << "en_US" << 'f' << 2;
    QTest::newRow("en_US 2") << "en_US" << 'g' << 3;
    QTest::newRow("en_US 3") << "en_US" << 'f' << 0;
    QTest::newRow("en_US 4") << "en_US" << 'f' << -1;
    QTest::newRow("de_DE 1") << "de_DE" << 'f' << 2;
    QTest::newRow("de_DE 2") << "de_DE" << 'g' << 3;
    QTest::newRow("ar_SA 1") << "ar_SA" << 'f' << 2;
    QTest::newRow("ar_SA 2") << "ar_SA" << 'g' << 3;
}

void tst_qqmllocale::numberToLocaleString()
{
    QFETCH(QString, locale);
    QFETCH(char, format);
    QFETCH(int, prec);

    QQmlComponent c(&engine, testFileUrl("number.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    double number = 2344423.3289;

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale l(locale);
    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(number)),
        Q_ARG(QVariant, QVariant(QString(format))),
        Q_ARG(QVariant, QVariant(prec)));

    if (prec < 0) prec = 2;
    QCOMPARE(val.toString(), l.toString(number, format, prec));
}

void tst_qqmllocale::numberToLocaleCurrencyString_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<QString>("symbol");

    QTest::newRow("en_US 1") << "en_US" << QString();
    QTest::newRow("en_US 2") << "en_US" << "USD";
    QTest::newRow("de_DE") << "de_DE" << QString();
    QTest::newRow("ar_SA") << "ar_SA" << QString();
    QTest::newRow("hi_IN") << "hi_IN" << QString();
    QTest::newRow("zh_CN") << "zh_CN" << QString();
    QTest::newRow("th_TH") << "th_TH" << QString();
}

void tst_qqmllocale::numberToLocaleCurrencyString()
{
    QFETCH(QString, locale);
    QFETCH(QString, symbol);

    QQmlComponent c(&engine, testFileUrl("number.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    double number = 2344423.3289;

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QLocale l(locale);
    QVariant val;
    QMetaObject::invokeMethod(obj, "toLocaleCurrencyString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(number)),
        Q_ARG(QVariant, QVariant(symbol)));

    QCOMPARE(val.toString(), l.toCurrencyString(number, symbol));
}

void tst_qqmllocale::numberFromLocaleString_data()
{
    QTest::addColumn<QString>("locale");
    QTest::addColumn<double>("number");

    QTest::newRow("en_US 1") << "en_US" << 1234567.2345;
    QTest::newRow("en_US 2") << "en_US" << 0.234;
    QTest::newRow("en_US 3") << "en_US" << 234.0;
    QTest::newRow("de_DE") << "de_DE" << 1234567.2345;
    QTest::newRow("ar_SA") << "ar_SA" << 1234567.2345;
    QTest::newRow("hi_IN") << "hi_IN" << 1234567.2345;
    QTest::newRow("zh_CN") << "zh_CN" << 1234567.2345;
    QTest::newRow("th_TH") << "th_TH" << 1234567.2345;
}

void tst_qqmllocale::numberFromLocaleString()
{
    QFETCH(QString, locale);
    QFETCH(double, number);

    QQmlComponent c(&engine, testFileUrl("number.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QLocale l(locale);
    QString strNumber = l.toString(number, 'f');

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant(locale)));

    QVariant val;
    QMetaObject::invokeMethod(obj, "fromLocaleString", Qt::DirectConnection,
        Q_RETURN_ARG(QVariant, val),
        Q_ARG(QVariant, QVariant(strNumber)));

    QCOMPARE(val.toDouble(), l.toDouble(strNumber));
}

void tst_qqmllocale::numberConstToLocaleString()
{
    QQmlComponent c(&engine, testFileUrl("number.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    QMetaObject::invokeMethod(obj, "setLocale", Qt::DirectConnection,
        Q_ARG(QVariant, QVariant("en_US")));

    QLocale l("en_US");
    QCOMPARE(obj->property("const1").toString(), l.toString(1234.56, 'f', 2));
    QCOMPARE(obj->property("const2").toString(), l.toString(1234., 'f', 2));
}

void tst_qqmllocale::numberOptions()
{
    QQmlEngine engine;
    QQmlComponent comp(&engine);
    comp.setData(R"(
        import QtQml 2.15
        QtObject {
            id: root
            property string formatted
            property bool caughtException: false
            Component.onCompleted: () => {
                const myLocale = Qt.locale("de_DE")
                myLocale.numberOptions = Locale.OmitGroupSeparator | Locale.RejectTrailingZeroesAfterDot
                root.formatted = Number(10000).toLocaleString(myLocale, 'f', 4)
                try {
                    Number.fromLocaleString(myLocale, "1,10");
                } catch (e) {console.warn(e); root.caughtException = true}
            }
        }
    )", QUrl("testdata"));
    QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Error: Locale: Number.fromLocaleString(): Invalid format");
    QScopedPointer<QObject> root {comp.create()};
    qDebug() << comp.errorString();
    QVERIFY(root);
    QCOMPARE(root->property("formatted").toString(), QLatin1String("10000,0000"));
    QCOMPARE(root->property("caughtException").toBool(), true);

}

void tst_qqmllocale::stringLocaleCompare_data()
{
    QTest::addColumn<QString>("string1");
    QTest::addColumn<QString>("string2");

    QTest::newRow("before") << "a" << "b";
    QTest::newRow("equal") << "a" << "a";
    QTest::newRow("after") << "b" << "a";

    // Copied from QString::localeAwareCompare tests
    // We don't actually change locale - we just care that String.localeCompare()
    // matches QString::localeAwareCompare();
    QTest::newRow("swedish1") << QString::fromLatin1("\xe5") << QString::fromLatin1("\xe4");
    QTest::newRow("swedish2") << QString::fromLatin1("\xe4") << QString::fromLatin1("\xf6");
    QTest::newRow("swedish3") << QString::fromLatin1("\xe5") << QString::fromLatin1("\xf6");
    QTest::newRow("swedish4") << QString::fromLatin1("z") << QString::fromLatin1("\xe5");

    QTest::newRow("german1") << QString::fromLatin1("z") << QString::fromLatin1("\xe4");
    QTest::newRow("german2") << QString::fromLatin1("\xe4") << QString::fromLatin1("\xf6");
    QTest::newRow("german3") << QString::fromLatin1("z") << QString::fromLatin1("\xf6");
}

void tst_qqmllocale::stringLocaleCompare()
{
    QFETCH(QString, string1);
    QFETCH(QString, string2);

    QQmlComponent c(&engine, testFileUrl("localeCompare.qml"));

    QObject *obj = c.create();
    QVERIFY(obj);

    obj->setProperty("string1", string1);
    obj->setProperty("string2", string2);

    QCOMPARE(obj->property("comparison").toInt(), QString::localeAwareCompare(string1, string2));
}

class Calendar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QLocale locale READ locale WRITE setLocale)
public:
    Calendar() {
    }

    QLocale locale() const {
        return mLocale;
    }

    void setLocale(const QLocale &locale) {
        mLocale = locale;
    }
private:
    QLocale mLocale;
};

void tst_qqmllocale::localeAsCppProperty()
{
    qmlRegisterType<Calendar>("Test", 1, 0, "Calendar");
    QQmlComponent component(&engine, testFileUrl("localeAsCppProperty.qml"));
    QVERIFY2(!component.isError(), qPrintable(component.errorString()));
    QTRY_VERIFY(component.isReady());

    Calendar *item = qobject_cast<Calendar*>(component.create());
    QCOMPARE(item->property("locale").toLocale().name(), QLatin1String("en_GB"));

    QVariant localeVariant(QLocale("nb_NO"));
    item->setProperty("testLocale", localeVariant);
    QCOMPARE(item->property("testLocale").toLocale().name(), QLatin1String("nb_NO"));
}

class DateFormatter : public QObject
{
    Q_OBJECT
public:
    DateFormatter() {}

    Q_INVOKABLE QString getLocalizedForm(const QString &isoTimestamp);
};

QString DateFormatter::getLocalizedForm(const QString &isoTimestamp)
{
    QDateTime input = QDateTime::fromString(isoTimestamp, Qt::ISODate);
    QLocale locale;
    return locale.toString(input);
}

#if defined(Q_OS_UNIX) && QT_CONFIG(timezone)
// Currently disabled on Windows as adjusting the timezone
// requires additional privileges that aren't normally
// enabled for a process. This can be achieved by calling
// AdjustTokenPrivileges() and then SetTimeZoneInformation(),
// which will require linking to a different library to access that API.
static void setTimeZone(const QByteArray &tz)
{
    if (tz.isEmpty())
        qunsetenv("TZ");
    else
        qputenv("TZ", tz);
    ::tzset();

// following left for future reference, see comment above
// #if defined(Q_OS_WIN32)
//     ::_tzset();
// #endif
}

void tst_qqmllocale::timeZoneUpdated()
{
    // Note: This test may not reliably hit the QEXPECT_FAIL clauses below if the initial
    //       system time zone is equivalent to either Australia/Brisbane or Asia/Kalkota.

    // Initialize the system time zone, so that we actually _change_ something below.
    QVERIFY2(QTimeZone::systemTimeZone().isValid(),
             "You know, Toto, I do believe we're not in Kansas any more.");

    QByteArray original(qgetenv("TZ"));

    // Set the timezone to Brisbane time, AEST-10:00
    setTimeZone(QByteArray("Australia/Brisbane"));

    QScopedPointer<QObject> obj;
    auto cleanup = qScopeGuard([&original, &obj] {
        // Restore to original time zone
        setTimeZone(original);
        QMetaObject::invokeMethod(obj.data(), "resetTimeZone");
    });

    DateFormatter formatter;

    QQmlEngine e;
    e.rootContext()->setContextObject(&formatter);

    QQmlComponent c(&e, testFileUrl("timeZoneUpdated.qml"));
    QVERIFY2(!c.isError(), qPrintable(c.errorString()));
    obj.reset(c.create());
    QVERIFY(obj);

#if !defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
    QEXPECT_FAIL("",
                 "Date.timeZoneUpdated() only works on non-Android Linux with QT_CONFIG(timezone).",
                 Continue);
#endif
    QVERIFY(obj->property("success").toBool());

    // Change to Indian time, IST-05:30
    setTimeZone(QByteArray("Asia/Kolkata"));

    QMetaObject::invokeMethod(obj.data(), "check");

#if !defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
    QEXPECT_FAIL("",
                 "Date.timeZoneUpdated() only works on non-Android Linux with QT_CONFIG(timezone).",
                 Continue);
#endif
    QVERIFY(obj->property("success").toBool());
}
#endif // Unix && timezone

QTEST_MAIN(tst_qqmllocale)

#include "tst_qqmllocale.moc"
