Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QDateTime>
#include <QTest>
#include <QLocale>
#include <QMap>
#include <QTimeZone>
#include <private/qglobal_p.h> // for the icu feature test
#include <private/qcomparisontesthelper_p.h>
#include <private/qdatetime_p.h>
#if !QT_CONFIG(timezone)
# include <private/qtenvironmentvariables_p.h> // for qTzName()
#endif
using namespace QtPrivate::DateTimeConstants;
using namespace Qt::StringLiterals;
#undef USING_MS_TZDB
#undef USING_WIN_TZ
#ifdef Q_OS_WIN
# if QT_CONFIG(timezone_tzdb)
# define USING_MS_TZDB
# elif !QT_CONFIG(icu)
# define USING_WIN_TZ
# endif
#endif
#undef GLIBC_TZDB_MISPARSE
#if QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) // && _GLIBCXX_RELEASE <= 14
# define GLIBC_TZDB_MISPARSE // QTBUG-127598
#endif
class tst_QDate : public QObject
{
Q_OBJECT
private Q_SLOTS:
void isNull_data();
void isNull();
void isValid_data();
void isValid();
void julianDay_data();
void julianDay();
void dayOfWeek_data();
void dayOfWeek();
void dayOfYear_data();
void dayOfYear();
void daysInMonth_data();
void daysInMonth();
void daysInYear_data();
void daysInYear();
void getDate();
void weekNumber_invalid_data();
void weekNumber_invalid();
void weekNumber_data();
void weekNumber();
void startOfDay_endOfDay_data();
void startOfDay_endOfDay();
void startOfDay_endOfDay_fixed_data();
void startOfDay_endOfDay_fixed();
void startOfDay_endOfDay_bounds();
void julianDaysLimits();
void addDays_data();
void addDays();
void incrementable_data() { addDays_data(); }
void incrementable();
void addMonths_data();
void addMonths();
void addYears_data();
void addYears();
void daysTo();
void orderingCompiles();
void operator_eq_eq_data();
void operator_eq_eq();
void ordering_data();
void ordering();
void ordering_chrono_types();
void operator_insert_extract_data();
void operator_insert_extract();
void extractOutOfRange_data();
void extractOutOfRange();
#if QT_CONFIG(datestring)
void fromStringDateFormat_data();
void fromStringDateFormat();
# if QT_CONFIG(datetimeparser)
void fromStringFormat_data();
void fromStringFormat();
void fromStringFormatCal_data();
void fromStringFormatCal();
# endif
void toStringFormat_data();
void toStringFormat();
void toStringDateFormat_data();
void toStringDateFormat();
#endif
void isLeapYear();
void yearsZeroToNinetyNine();
void printNegativeYear_data() const;
void printNegativeYear() const;
#if QT_CONFIG(datestring)
void roundtripString() const;
#endif
void roundtrip() const;
void qdebug() const;
private:
QDate defDate() const { return QDate(1900, 1, 1); }
QDate epochDate() const {
using namespace QtPrivate::DateTimeConstants;
Q_ASSERT(JULIAN_DAY_FOR_EPOCH == QDate(1970, 1, 1).toJulianDay());
return QDate::fromJulianDay(JULIAN_DAY_FOR_EPOCH);
}
static constexpr qint64 minJd = JulianDayMin;
static constexpr qint64 maxJd = JulianDayMax;
QDate invalidDate() const { return QDate(); }
};
Q_DECLARE_METATYPE(Qt::DateFormat)
void tst_QDate::isNull_data()
{
QTest::addColumn<qint64>("jd");
QTest::addColumn<bool>("null");
QTest::newRow("qint64 min") << std::numeric_limits<qint64>::min() << true;
QTest::newRow("minJd - 1") << minJd - 1 << true;
QTest::newRow("minJd") << minJd << false;
QTest::newRow("minJd + 1") << minJd + 1 << false;
QTest::newRow("maxJd - 1") << maxJd - 1 << false;
QTest::newRow("maxJd") << maxJd << false;
QTest::newRow("maxJd + 1") << maxJd + 1 << true;
QTest::newRow("qint64 max") << std::numeric_limits<qint64>::max() << true;
}
void tst_QDate::isNull()
{
QFETCH(qint64, jd);
QTEST(QDate::fromJulianDay(jd).isNull(), "null");
}
void tst_QDate::isValid_data()
{
qint64 nullJd = std::numeric_limits<qint64>::min();
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<qint64>("jd");
QTest::addColumn<bool>("valid");
QTest::newRow("0-0-0") << 0 << 0 << 0 << nullJd << false;
QTest::newRow("month 0") << 2000 << 0 << 1 << nullJd << false;
QTest::newRow("day 0") << 2000 << 1 << 0 << nullJd << false;
QTest::newRow("month 13") << 2000 << 13 << 1 << nullJd << false;
// test leap years
QTest::newRow("non-leap") << 2006 << 2 << 29 << nullJd << false;
QTest::newRow("normal leap") << 2004 << 2 << 29 << qint64(2453065) << true;
QTest::newRow("century leap 1900") << 1900 << 2 << 29 << nullJd << false;
QTest::newRow("century leap 2100") << 2100 << 2 << 29 << nullJd << false;
QTest::newRow("400-years leap 2000") << 2000 << 2 << 29 << qint64(2451604) << true;
QTest::newRow("400-years leap 2400") << 2400 << 2 << 29 << qint64(2597701) << true;
QTest::newRow("400-years leap 1600") << 1600 << 2 << 29 << qint64(2305507) << true;
QTest::newRow("year 0") << 0 << 2 << 27 << nullJd << false;
// Test end of four-digit years:
QTest::newRow("late") << 9999 << 12 << 31 << qint64(5373484) << true;
// test the number of days in months:
QTest::newRow("jan") << 2000 << 1 << 31 << qint64(2451575) << true;
QTest::newRow("feb") << 2000 << 2 << 29 << qint64(2451604) << true; // same data as 400-years leap
QTest::newRow("mar") << 2000 << 3 << 31 << qint64(2451635) << true;
QTest::newRow("apr") << 2000 << 4 << 30 << qint64(2451665) << true;
QTest::newRow("may") << 2000 << 5 << 31 << qint64(2451696) << true;
QTest::newRow("jun") << 2000 << 6 << 30 << qint64(2451726) << true;
QTest::newRow("jul") << 2000 << 7 << 31 << qint64(2451757) << true;
QTest::newRow("aug") << 2000 << 8 << 31 << qint64(2451788) << true;
QTest::newRow("sep") << 2000 << 9 << 30 << qint64(2451818) << true;
QTest::newRow("oct") << 2000 << 10 << 31 << qint64(2451849) << true;
QTest::newRow("nov") << 2000 << 11 << 30 << qint64(2451879) << true;
QTest::newRow("dec") << 2000 << 12 << 31 << qint64(2451910) << true;
// and invalid dates:
QTest::newRow("ijan") << 2000 << 1 << 32 << nullJd << false;
QTest::newRow("ifeb") << 2000 << 2 << 30 << nullJd << false;
QTest::newRow("imar") << 2000 << 3 << 32 << nullJd << false;
QTest::newRow("iapr") << 2000 << 4 << 31 << nullJd << false;
QTest::newRow("imay") << 2000 << 5 << 32 << nullJd << false;
QTest::newRow("ijun") << 2000 << 6 << 31 << nullJd << false;
QTest::newRow("ijul") << 2000 << 7 << 32 << nullJd << false;
QTest::newRow("iaug") << 2000 << 8 << 32 << nullJd << false;
QTest::newRow("isep") << 2000 << 9 << 31 << nullJd << false;
QTest::newRow("ioct") << 2000 << 10 << 32 << nullJd << false;
QTest::newRow("inov") << 2000 << 11 << 31 << nullJd << false;
QTest::newRow("idec") << 2000 << 12 << 32 << nullJd << false;
// the beginning of the Julian Day calendar:
QTest::newRow("jd earliest formula") << -4800 << 1 << 1 << qint64( -31738) << true;
QTest::newRow("jd -1") << -4714 << 11 << 23 << qint64( -1) << true;
QTest::newRow("jd 0") << -4714 << 11 << 24 << qint64( 0) << true;
QTest::newRow("jd 1") << -4714 << 11 << 25 << qint64( 1) << true;
QTest::newRow("jd latest formula") << 1400000 << 12 << 31 << qint64(513060925) << true;
}
// INTEGRITY incident-85878 (timezone and clock_cast are not supported)
#if __cpp_lib_chrono >= 201907L && !defined(Q_OS_INTEGRITY)
// QDate has a bigger range than year_month_date. The tests use this bigger
// range. However building a year_month_time with "out of range" data has
// unspecified results, so don't do that. See [time.cal.year],
// [time.cal.month], [time.cal.day]. Also, std::chrono::year has a year 0, so
// take that into account.
static std::optional<std::chrono::year_month_day> convertToStdYearMonthDay(int y, int m, int d)
{
using namespace std::chrono;
if (y >= int((year::min)())
&& y <= int((year::max)())
&& m >= 0
&& m <= 255
&& d >= 0
&& d <= 255)
{
if (y < 0)
++y;
return std::make_optional(year(y) / m / d);
}
return std::nullopt;
}
#endif
void tst_QDate::isValid()
{
QFETCH(int, year);
QFETCH(int, month);
QFETCH(int, day);
QFETCH(qint64, jd);
QFETCH(bool, valid);
QCOMPARE(QDate::isValid(year, month, day), valid);
QDate d;
d.setDate(year, month, day);
QCOMPARE(d.isValid(), valid);
QCOMPARE(d.toJulianDay(), jd);
if (valid) {
QCOMPARE(d.year(), year);
QCOMPARE(d.month(), month);
QCOMPARE(d.day(), day);
// INTEGRITY incident-85878 (timezone and clock_cast are not supported)
#if __cpp_lib_chrono >= 201907L && !defined(Q_OS_INTEGRITY)
std::optional<std::chrono::year_month_day> ymd = convertToStdYearMonthDay(year, month, day);
if (ymd) {
QDate d = *ymd;
QCOMPARE(d.year(), year);
QCOMPARE(d.month(), month);
QCOMPARE(d.day(), day);
const std::chrono::sys_days qdateSysDays = d.toStdSysDays();
const std::chrono::sys_days ymdSysDays = *ymd;
QCOMPARE(qdateSysDays, ymdSysDays);
}
#endif
} else {
QCOMPARE(d.year(), 0);
QCOMPARE(d.month(), 0);
QCOMPARE(d.day(), 0);
}
}
void tst_QDate::julianDay_data()
{
isValid_data();
}
void tst_QDate::julianDay()
{
QFETCH(int, year);
QFETCH(int, month);
QFETCH(int, day);
QFETCH(qint64, jd);
{
QDate d;
d.setDate(year, month, day);
QCOMPARE(d.toJulianDay(), jd);
}
if (jd != std::numeric_limits<qint64>::min()) {
QDate d = QDate::fromJulianDay(jd);
QCOMPARE(d.year(), year);
QCOMPARE(d.month(), month);
QCOMPARE(d.day(), day);
}
}
void tst_QDate::dayOfWeek_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<int>("dayOfWeek");
QTest::newRow("data0") << 0 << 0 << 0 << 0;
QTest::newRow("data1") << 2000 << 1 << 3 << 1;
QTest::newRow("data2") << 2000 << 1 << 4 << 2;
QTest::newRow("data3") << 2000 << 1 << 5 << 3;
QTest::newRow("data4") << 2000 << 1 << 6 << 4;
QTest::newRow("data5") << 2000 << 1 << 7 << 5;
QTest::newRow("data6") << 2000 << 1 << 8 << 6;
QTest::newRow("data7") << 2000 << 1 << 9 << 7;
QTest::newRow("data8") << -4800 << 1 << 1 << 1;
QTest::newRow("data9") << -4800 << 1 << 2 << 2;
QTest::newRow("data10") << -4800 << 1 << 3 << 3;
QTest::newRow("data11") << -4800 << 1 << 4 << 4;
QTest::newRow("data12") << -4800 << 1 << 5 << 5;
QTest::newRow("data13") << -4800 << 1 << 6 << 6;
QTest::newRow("data14") << -4800 << 1 << 7 << 7;
QTest::newRow("data15") << -4800 << 1 << 8 << 1;
}
void tst_QDate::dayOfWeek()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QTEST(QDate(year, month, day).dayOfWeek(), "dayOfWeek");
}
void tst_QDate::dayOfYear_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<int>("dayOfYear");
QTest::newRow("data0") << 0 << 0 << 0 << 0;
QTest::newRow("data1") << 2000 << 1 << 1 << 1;
QTest::newRow("data2") << 2000 << 1 << 2 << 2;
QTest::newRow("data3") << 2000 << 1 << 3 << 3;
QTest::newRow("data4") << 2000 << 12 << 31 << 366;
QTest::newRow("data5") << 2001 << 12 << 31 << 365;
QTest::newRow("data6") << 1815 << 1 << 1 << 1;
QTest::newRow("data7") << 1815 << 12 << 31 << 365;
QTest::newRow("data8") << 1500 << 1 << 1 << 1;
QTest::newRow("data9") << 1500 << 12 << 31 << 365;
QTest::newRow("data10") << -1500 << 1 << 1 << 1;
QTest::newRow("data11") << -1500 << 12 << 31 << 365;
QTest::newRow("data12") << -4800 << 1 << 1 << 1;
QTest::newRow("data13") << -4800 << 12 << 31 << 365;
}
void tst_QDate::dayOfYear()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QTEST(QDate(year, month, day).dayOfYear(), "dayOfYear");
}
void tst_QDate::daysInMonth_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<int>("daysInMonth");
QTest::newRow("data0") << 0 << 0 << 0 << 0;
QTest::newRow("data1") << 2000 << 1 << 1 << 31;
QTest::newRow("data2") << 2000 << 2 << 1 << 29;
QTest::newRow("data3") << 2000 << 3 << 1 << 31;
QTest::newRow("data4") << 2000 << 4 << 1 << 30;
QTest::newRow("data5") << 2000 << 5 << 1 << 31;
QTest::newRow("data6") << 2000 << 6 << 1 << 30;
QTest::newRow("data7") << 2000 << 7 << 1 << 31;
QTest::newRow("data8") << 2000 << 8 << 1 << 31;
QTest::newRow("data9") << 2000 << 9 << 1 << 30;
QTest::newRow("data10") << 2000 << 10 << 1 << 31;
QTest::newRow("data11") << 2000 << 11 << 1 << 30;
QTest::newRow("data12") << 2000 << 12 << 1 << 31;
QTest::newRow("data13") << 2001 << 2 << 1 << 28;
QTest::newRow("data14") << 2000 << 0 << 1 << 0;
}
void tst_QDate::daysInMonth()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QTEST(QDate(year, month, day).daysInMonth(), "daysInMonth");
}
void tst_QDate::daysInYear_data()
{
QTest::addColumn<QDate>("date");
QTest::addColumn<int>("expectedDaysInYear");
QTest::newRow("2000, 1, 1") << QDate(2000, 1, 1) << 366;
QTest::newRow("2001, 1, 1") << QDate(2001, 1, 1) << 365;
QTest::newRow("4, 1, 1") << QDate(4, 1, 1) << 366;
QTest::newRow("5, 1, 1") << QDate(5, 1, 1) << 365;
QTest::newRow("0, 0, 0") << QDate(0, 0, 0) << 0;
}
void tst_QDate::daysInYear()
{
QFETCH(const QDate, date);
QTEST(date.daysInYear(), "expectedDaysInYear");
}
void tst_QDate::getDate()
{
int y, m, d;
QDate dt(2000, 1, 1);
dt.getDate(&y, &m, &d);
QCOMPARE(y, 2000);
QCOMPARE(m, 1);
QCOMPARE(d, 1);
dt.setDate(0, 0, 0);
dt.getDate(&y, &m, &d);
QCOMPARE(y, 0);
QCOMPARE(m, 0);
QCOMPARE(d, 0);
}
void tst_QDate::weekNumber_data()
{
QTest::addColumn<int>("expectedWeekNum");
QTest::addColumn<int>("expectedYearNum");
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
enum { Thursday = 4 };
bool wasLastYearLong = false; // 1999 was not a long (53-week) year
bool isLongYear;
// full 400-year cycle for Jan 1, 4 and Dec 28, 31
for (int yr = 2000; yr < 2400; ++yr, wasLastYearLong = isLongYear) {
QByteArray yrstr = QByteArray::number(yr);
int wday = QDate(yr, 1, 1).dayOfWeek();
// the year is 53-week long if Jan 1 is Thursday or, if it's a leap year, a Wednesday
isLongYear = (wday == Thursday) || (QDate::isLeapYear(yr) && wday == Thursday - 1);
// Jan 4 is always on week 1
QTest::newRow(yrstr + "-01-04") << 1 << yr << yr << 1 << 4;
// Dec 28 is always on the last week
QTest::newRow(yrstr + "-12-28") << (52 + isLongYear) << yr << yr << 12 << 28;
// Jan 1 is on either on week 1 or on the last week of the previous year
QTest::newRow(yrstr + "-01-01")
<< (wday <= Thursday ? 1 : 52 + wasLastYearLong)
<< (wday <= Thursday ? yr : yr - 1)
<< yr << 1 << 1;
// Dec 31 is either on the last week or week 1 of the next year
wday = QDate(yr, 12, 31).dayOfWeek();
QTest::newRow(yrstr + "-12-31")
<< (wday >= Thursday ? 52 + isLongYear : 1)
<< (wday >= Thursday ? yr : yr + 1)
<< yr << 12 << 31;
}
}
void tst_QDate::weekNumber()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
const QDate dt(year, month, day);
int yearNumber;
QTEST(dt.weekNumber(&yearNumber), "expectedWeekNum");
QTEST(yearNumber, "expectedYearNum");
}
void tst_QDate::weekNumber_invalid_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::newRow("0000-00-00") << 0 << 0 << 0;
QTest::newRow("2001-01-32") << 2001 << 1 << 32;
QTest::newRow("1999-02-29") << 1999 << 2 << 29;
}
void tst_QDate::weekNumber_invalid()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
const QDate dt(year, month, day);
constexpr int initialYear = (std::numeric_limits<int>::min)();
int yearNumber = initialYear;
QCOMPARE(dt.weekNumber(&yearNumber), 0);
QCOMPARE(yearNumber, initialYear); // weekNumber() didn't set it.
}
/* The MS backend tends to lack data for historical transitions. So some of the
transition-based tests will get wrong results, that we can't do anything
about, when using that backend. Rather than complicating the #if-ery more,
overtly record, in a flags column, which we need to ignore and merely make
the testing of these flags subject to #if-ery.
Android appears to lack at least one other.
*/
enum BackendKludge { IgnoreStart = 1, IgnoreEnd = 2, };
Q_DECLARE_FLAGS(BackendKludges, BackendKludge)
Q_DECLARE_OPERATORS_FOR_FLAGS(BackendKludges)
#undef KLUDGING
void tst_QDate::startOfDay_endOfDay_data()
{
QTest::addColumn<QDate>("date"); // Typically a spring-forward.
// A zone in which that date's start and end are worth checking:
QTest::addColumn<QTimeZone>("zone");
// The start and end times in that zone:
QTest::addColumn<QTime>("start");
QTest::addColumn<QTime>("end");
// Ignored for backends that don't need it:
QTest::addColumn<BackendKludges>("kludge");
const QTime early(0, 0), late(23, 59, 59, 999), invalid(QDateTime().time());
constexpr BackendKludges Clean = {};
constexpr BackendKludges IgnoreBoth = IgnoreStart | IgnoreEnd;
// Use IgnoreBoth directly for the one transition Android lacks; the other
// two that need kludges also fail this one.
#ifdef Q_OS_ANDROID
#define KLUDGING
#endif
#ifdef USING_WIN_TZ
constexpr BackendKludges MsNoStart = IgnoreStart;
constexpr BackendKludges MsNoBoth = IgnoreBoth;
#define KLUDGING
#else
constexpr BackendKludges MsNoStart = Clean;
constexpr BackendKludges MsNoBoth = Clean;
#endif
#if QT_CONFIG(timezone) && QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__)
// The IANA-DB parser in libstdc++ (at least up to _GLIBCXX_RELEASE == 14) gets
// a lot of zone-transitions wrong in C++20's tzdb :-(
constexpr BackendKludges GlibCxxNoBoth = IgnoreBoth;
#define KLUDGING
#else
constexpr BackendKludges GlibCxxNoBoth = Clean;
#endif
const QTimeZone UTC(QTimeZone::UTC);
using Bound = std::numeric_limits<qint64>;
const auto dateAtMillis = [UTC](qint64 millis) {
return QDateTime::fromMSecsSinceEpoch(millis, UTC).date();
};
// UTC and fixed offset are always available and predictable:
QTest::newRow("epoch") << epochDate() << UTC << early << late << Clean;
// First and last days in QDateTime's supported range:
QTest::newRow("earliest")
<< dateAtMillis(Bound::min()) << UTC << invalid << late << Clean;
QTest::newRow("latest")
<< dateAtMillis(Bound::max()) << UTC << early << invalid << Clean;
const struct {
const char *test;
const char *zone;
const QDate day;
const QTime start;
const QTime end;
const BackendKludges msOpt;
} transitions[] = {
// Compare tst_QDateTime::fromStringDateFormat(ISO 24:00 in DST).
{ "Brazil", "America/Sao_Paulo", QDate(2008, 10, 19), QTime(1, 0), late, Clean },
// Several southern zones within EET (but not the northern ones) spent
// part of the 1990s using midnight as spring transition.
{ "Sofia", "Europe/Sofia", QDate(1994, 3, 27), QTime(1, 0), late, MsNoStart },
// Two Pacific zones skipped days to get on the west of the
// International Date Line; those days have neither start nor end.
{ "Kiritimati", "Pacific/Kiritimati", QDate(1994, 12, 31), invalid, invalid, IgnoreBoth },
{ "Samoa", "Pacific/Apia", QDate(2011, 12, 30), invalid, invalid,
MsNoBoth | GlibCxxNoBoth },
// TODO: find other zones with transitions at/crossing midnight.
};
const QTimeZone local = QTimeZone::LocalTime;
#if QT_CONFIG(timezone)
const QTimeZone sys = QTimeZone::systemTimeZone();
QVERIFY2(sys.isValid(), "Test depends on properly configured system");
for (const auto &tran : transitions) {
if (QTimeZone zone(tran.zone); zone.isValid()) {
QTest::newRow(tran.test)
<< tran.day << zone << tran.start << tran.end << tran.msOpt;
if (zone == sys) {
QTest::addRow("Local=%s", tran.test)
<< tran.day << local << tran.start << tran.end << tran.msOpt;
}
}
}
#else
const auto isLocalZone = [](const char *zone) {
const QLatin1StringView name(zone);
for (int i = 0; i < 2; ++i) {
if (qTzName(i) == name)
return true;
}
return false;
};
for (const auto &tran : transitions) {
if (isLocalZone(tran.zone)) { // Might need a different name to match
QTest::addRow("Local=%s", tran.test)
<< tran.day << local << tran.start << tran.end << tran.msOpt;
}
}
#endif // timezone
}
void tst_QDate::startOfDay_endOfDay()
{
QFETCH(const QDate, date);
QFETCH(const QTimeZone, zone);
QFETCH(const QTime, start);
QFETCH(const QTime, end);
#ifdef KLUDGING // Cope with backend limitations:
QFETCH(const BackendKludges, kludge);
#define UNLESSKLUDGE(flag) if (!kludge.testFlag(flag))
#else
#define UNLESSKLUDGE(flag)
#endif
QVERIFY(zone.isValid());
QDateTime front(date.startOfDay(zone)), back(date.endOfDay(zone));
if (end.isValid())
QCOMPARE(date.addDays(1).startOfDay(zone).addMSecs(-1), back);
if (start.isValid())
QCOMPARE(date.addDays(-1).endOfDay(zone).addMSecs(1), front);
if (start.isValid()) {
QVERIFY(front.isValid());
QCOMPARE(front.date(), date);
#ifdef USING_MS_TZDB
QEXPECT_FAIL("Brazil", "MS misreads the IANA DB", Continue);
#endif
UNLESSKLUDGE(IgnoreStart) QCOMPARE(front.time(), start);
} else UNLESSKLUDGE(IgnoreStart) {
auto report = qScopeGuard([front]() { qDebug() << "Start of day:" << front; });
QVERIFY(!front.isValid());
report.dismiss();
}
if (end.isValid()) {
QVERIFY(back.isValid());
QCOMPARE(back.date(), date);
UNLESSKLUDGE(IgnoreEnd) QCOMPARE(back.time(), end);
} else UNLESSKLUDGE(IgnoreEnd) {
auto report = qScopeGuard([back]() { qDebug() << "End of day:" << back; });
QVERIFY(!back.isValid());
report.dismiss();
}
#undef UNLESSKLUDGE
}
#undef KLUDGING
void tst_QDate::startOfDay_endOfDay_fixed_data()
{
QTest::addColumn<QDate>("date");
const qint64 kilo(1000);
using Bounds = std::numeric_limits<qint64>;
const auto UTC = QTimeZone::UTC;
const QDateTime first(QDateTime::fromMSecsSinceEpoch(Bounds::min() + 1, UTC));
const QDateTime start32sign(QDateTime::fromMSecsSinceEpoch(Q_INT64_C(-0x80000000) * kilo, UTC));
const QDateTime end32sign(QDateTime::fromMSecsSinceEpoch(Q_INT64_C(0x80000000) * kilo, UTC));
const QDateTime end32unsign(QDateTime::fromMSecsSinceEpoch(Q_INT64_C(0x100000000) * kilo, UTC));
const QDateTime last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), UTC));
QTest::newRow("epoch") << epochDate();
QTest::newRow("y2k-leap-day") << QDate(2000, 2, 29);
QTest::newRow("start-1900") << QDate(1900, 1, 1); // QTBUG-99747
// Just outside the start and end of 32-bit time_t:
QTest::newRow("pre-sign32") << QDate(start32sign.date().year(), 1, 1);
QTest::newRow("post-sign32") << QDate(end32sign.date().year(), 12, 31);
QTest::newRow("post-uint32") << QDate(end32unsign.date().year(), 12, 31);
// Just inside the start and end of QDateTime's range:
QTest::newRow("first-full") << first.date().addDays(1);
QTest::newRow("last-full") << last.date().addDays(-1);
}
void tst_QDate::startOfDay_endOfDay_fixed()
{
const QTime early(0, 0), late(23, 59, 59, 999);
QFETCH(QDate, date);
QDateTime start(date.startOfDay(QTimeZone::UTC));
QDateTime end(date.endOfDay(QTimeZone::UTC));
QCOMPARE(start.date(), date);
QCOMPARE(end.date(), date);
QCOMPARE(start.time(), early);
QCOMPARE(end.time(), late);
QCOMPARE(date.addDays(1).startOfDay(QTimeZone::UTC).addMSecs(-1), end);
QCOMPARE(date.addDays(-1).endOfDay(QTimeZone::UTC).addMSecs(1), start);
for (int offset = -60 * 16; offset <= 60 * 16; offset += 65) {
const auto zone = QTimeZone::fromSecondsAheadOfUtc(offset);
start = date.startOfDay(zone);
end = date.endOfDay(zone);
QCOMPARE(start.date(), date);
QCOMPARE(end.date(), date);
QCOMPARE(start.time(), early);
QCOMPARE(end.time(), late);
QCOMPARE(date.addDays(1).startOfDay(zone).addMSecs(-1), end);
QCOMPARE(date.addDays(-1).endOfDay(zone).addMSecs(1), start);
}
// Minimal testing for LocalTime and TimeZone
QCOMPARE(date.startOfDay().date(), date);
QCOMPARE(date.endOfDay().date(), date);
#if QT_CONFIG(timezone) && !defined(GLIBC_TZDB_MISPARSE)
const QTimeZone cet("Europe/Oslo");
if (cet.isValid()) {
QCOMPARE(date.startOfDay(cet).date(), date);
QCOMPARE(date.endOfDay(cet).date(), date);
}
#endif
}
void tst_QDate::startOfDay_endOfDay_bounds()
{
// Check the days in which QDateTime's range starts and ends:
using Bounds = std::numeric_limits<qint64>;
const auto UTC = QTimeZone::UTC;
const QDateTime
first(QDateTime::fromMSecsSinceEpoch(Bounds::min(), UTC)),
last(QDateTime::fromMSecsSinceEpoch(Bounds::max(), UTC)),
epoch(QDateTime::fromMSecsSinceEpoch(0, UTC));
// First, check these *are* the start and end of QDateTime's range:
QVERIFY(first.isValid());
QVERIFY(last.isValid());
QCOMPARE_LT(first, epoch);
QCOMPARE_GT(last, epoch);
// Step off end of range => either invalid or at opposite end:
if (first.addMSecs(-1).isValid())
QCOMPARE_GT(first.addMSecs(-1), first);
if (last.addMSecs(1).isValid())
QCOMPARE_LT(last.addMSecs(1), last);
// Now test start/end methods with them:
QCOMPARE(first.date().endOfDay(UTC).time(), QTime(23, 59, 59, 999));
QCOMPARE(last.date().startOfDay(UTC).time(), QTime(0, 0));
QVERIFY(!first.date().startOfDay(UTC).isValid());
QVERIFY(!last.date().endOfDay(UTC).isValid());
// Test for QTBUG-100873, shouldn't assert:
const QDate qdteMin(1752, 9, 14); // Used by QDateTimeEdit
QCOMPARE(qdteMin.startOfDay(UTC).date(), qdteMin);
QCOMPARE(qdteMin.startOfDay().date(), qdteMin);
#if QT_CONFIG(timezone)
const QTimeZone sys = QTimeZone::systemTimeZone();
QVERIFY2(sys.isValid(), "Test depends on properly configured system");
QCOMPARE(qdteMin.startOfDay(sys).date(), qdteMin);
QTimeZone berlin("Europe/Berlin");
if (berlin.isValid())
QCOMPARE(qdteMin.startOfDay(berlin).date(), qdteMin);
#endif
}
void tst_QDate::julianDaysLimits()
{
qint64 min = std::numeric_limits<qint64>::min();
qint64 max = std::numeric_limits<qint64>::max();
QDate maxDate = QDate::fromJulianDay(maxJd);
QDate minDate = QDate::fromJulianDay(minJd);
QDate zeroDate = QDate::fromJulianDay(0);
QDate dt = QDate::fromJulianDay(min);
QCOMPARE(dt.isValid(), false);
dt = QDate::fromJulianDay(minJd - 1);
QCOMPARE(dt.isValid(), false);
dt = QDate::fromJulianDay(minJd);
QCOMPARE(dt.isValid(), true);
dt = QDate::fromJulianDay(minJd + 1);
QCOMPARE(dt.isValid(), true);
dt = QDate::fromJulianDay(maxJd - 1);
QCOMPARE(dt.isValid(), true);
dt = QDate::fromJulianDay(maxJd);
QCOMPARE(dt.isValid(), true);
dt = QDate::fromJulianDay(maxJd + 1);
QCOMPARE(dt.isValid(), false);
dt = QDate::fromJulianDay(max);
QCOMPARE(dt.isValid(), false);
dt = maxDate.addDays(1);
QCOMPARE(dt.isValid(), false);
dt = maxDate.addDays(0);
QCOMPARE(dt.isValid(), true);
dt = maxDate.addDays(-1);
QCOMPARE(dt.isValid(), true);
dt = maxDate.addDays(max);
QCOMPARE(dt.isValid(), false);
dt = maxDate.addDays(min);
QCOMPARE(dt.isValid(), false);
dt = minDate.addDays(-1);
QCOMPARE(dt.isValid(), false);
dt = minDate.addDays(0);
QCOMPARE(dt.isValid(), true);
dt = minDate.addDays(1);
QCOMPARE(dt.isValid(), true);
dt = minDate.addDays(min);
QCOMPARE(dt.isValid(), false);
dt = minDate.addDays(max);
QCOMPARE(dt.isValid(), false);
dt = zeroDate.addDays(-1);
QCOMPARE(dt.isValid(), true);
dt = zeroDate.addDays(0);
QCOMPARE(dt.isValid(), true);
dt = zeroDate.addDays(1);
QCOMPARE(dt.isValid(), true);
dt = zeroDate.addDays(min);
QCOMPARE(dt.isValid(), false);
dt = zeroDate.addDays(max);
QCOMPARE(dt.isValid(), false);
}
void tst_QDate::addDays()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QFETCH(const int, amountToAdd);
const QDate dt(year, month, day);
const QDate dt2 = dt.addDays(amountToAdd);
QTEST(dt2.year(), "expectedYear");
QTEST(dt2.month(), "expectedMonth");
QTEST(dt2.day(), "expectedDay");
// INTEGRITY incident-85878 (timezone and clock_cast are not supported)
#if __cpp_lib_chrono >= 201907L && !defined(Q_OS_INTEGRITY)
const QDate dt3 = dt.addDuration(std::chrono::days(amountToAdd));
QTEST(dt3.year(), "expectedYear");
QTEST(dt3.month(), "expectedMonth");
QTEST(dt3.day(), "expectedDay");
#endif
}
void tst_QDate::addDays_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<int>("amountToAdd");
QTest::addColumn<int>("expectedYear");
QTest::addColumn<int>("expectedMonth");
QTest::addColumn<int>("expectedDay");
QTest::newRow( "data0" ) << 2000 << 1 << 1 << 1 << 2000 << 1 << 2;
QTest::newRow( "data1" ) << 2000 << 1 << 31 << 1 << 2000 << 2 << 1;
QTest::newRow( "data2" ) << 2000 << 2 << 28 << 1 << 2000 << 2 << 29;
QTest::newRow( "data3" ) << 2000 << 2 << 29 << 1 << 2000 << 3 << 1;
QTest::newRow( "data4" ) << 2000 << 12 << 31 << 1 << 2001 << 1 << 1;
QTest::newRow( "data5" ) << 2001 << 2 << 28 << 1 << 2001 << 3 << 1;
QTest::newRow( "data6" ) << 2001 << 2 << 28 << 30 << 2001 << 3 << 30;
QTest::newRow( "data7" ) << 2001 << 3 << 30 << 5 << 2001 << 4 << 4;
QTest::newRow( "data8" ) << 2000 << 1 << 1 << -1 << 1999 << 12 << 31;
QTest::newRow( "data9" ) << 2000 << 1 << 31 << -1 << 2000 << 1 << 30;
QTest::newRow( "data10" ) << 2000 << 2 << 28 << -1 << 2000 << 2 << 27;
QTest::newRow( "data11" ) << 2001 << 2 << 28 << -30 << 2001 << 1 << 29;
QTest::newRow( "data12" ) << -4713 << 1 << 2 << -2 << -4714 << 12 << 31;
QTest::newRow( "data13" ) << -4713 << 1 << 2 << 2 << -4713 << 1 << 4;
QTest::newRow( "invalid" ) << 0 << 0 << 0 << 1 << 0 << 0 << 0;
}
void tst_QDate::incrementable()
{
#ifdef __cpp_concepts
static_assert(std::weakly_incrementable<QDate>);
#endif
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QFETCH(const int, amountToAdd);
const QDate dt( year, month, day);
QDate pre = dt;
if (amountToAdd < 0) {
for (int i = amountToAdd; i < 0; ++i) {
const auto old = pre;
const auto now = --pre;
QCOMPARE(now, pre); // We got the new value.
if (old.isValid())
QCOMPARE_LT(now, old);
else
QVERIFY(!now.isValid());
}
} else {
for (int i = 0; i < amountToAdd; ++i) {
const auto old = pre;
const auto now = ++pre;
QCOMPARE(now, pre); // We got the new value.
if (old.isValid())
QCOMPARE_GT(now, old);
else
QVERIFY(!now.isValid());
}
}
QTEST(pre.year(), "expectedYear");
QTEST(pre.month(), "expectedMonth");
QTEST(pre.day(), "expectedDay");
QDate post = dt;
if (amountToAdd < 0) {
for (int i = amountToAdd; i < 0; ++i) {
const auto old = post;
const auto now = post--;
QCOMPARE(now, old); // We got the prior value.
if (old.isValid())
QCOMPARE_GT(now, pre);
else
QVERIFY(!now.isValid());
}
} else {
for (int i = 0; i < amountToAdd; ++i) {
const auto old = post;
const auto now = post++;
QCOMPARE(now, old); // We got the prior value.
if (old.isValid())
QCOMPARE_LT(now, post);
else
QVERIFY(!now.isValid());
}
}
QTEST(post.year(), "expectedYear");
QTEST(post.month(), "expectedMonth");
QTEST(post.day(), "expectedDay");
}
void tst_QDate::addMonths()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QFETCH(const int, amountToAdd);
const auto dt = QDate(year, month, day).addMonths(amountToAdd);
QTEST(dt.year(), "expectedYear");
QTEST(dt.month(), "expectedMonth");
QTEST(dt.day(), "expectedDay");
}
void tst_QDate::addMonths_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<int>("amountToAdd");
QTest::addColumn<int>("expectedYear");
QTest::addColumn<int>("expectedMonth");
QTest::addColumn<int>("expectedDay");
QTest::newRow( "data0" ) << 2000 << 1 << 1 << 1 << 2000 << 2 << 1;
QTest::newRow( "data1" ) << 2000 << 1 << 31 << 1 << 2000 << 2 << 29;
QTest::newRow( "data2" ) << 2000 << 2 << 28 << 1 << 2000 << 3 << 28;
QTest::newRow( "data3" ) << 2000 << 2 << 29 << 1 << 2000 << 3 << 29;
QTest::newRow( "data4" ) << 2000 << 12 << 31 << 1 << 2001 << 1 << 31;
QTest::newRow( "data5" ) << 2001 << 2 << 28 << 1 << 2001 << 3 << 28;
QTest::newRow( "data6" ) << 2001 << 2 << 28 << 12 << 2002 << 2 << 28;
QTest::newRow( "data7" ) << 2000 << 2 << 29 << 12 << 2001 << 2 << 28;
QTest::newRow( "data8" ) << 2000 << 10 << 15 << 4 << 2001 << 2 << 15;
QTest::newRow( "data9" ) << 2000 << 1 << 1 << -1 << 1999 << 12 << 1;
QTest::newRow( "data10" ) << 2000 << 1 << 31 << -1 << 1999 << 12 << 31;
QTest::newRow( "data11" ) << 2000 << 12 << 31 << -1 << 2000 << 11 << 30;
QTest::newRow( "data12" ) << 2001 << 2 << 28 << -12 << 2000 << 2 << 28;
QTest::newRow( "data13" ) << 2000 << 1 << 31 << -7 << 1999 << 6 << 30;
QTest::newRow( "data14" ) << 2000 << 2 << 29 << -12 << 1999 << 2 << 28;
// year sign change:
QTest::newRow( "data15" ) << 1 << 1 << 1 << -1 << -1 << 12 << 1;
QTest::newRow( "data16" ) << 1 << 1 << 1 << -12 << -1 << 1 << 1;
QTest::newRow( "data17" ) << -1 << 12 << 1 << 1 << 1 << 1 << 1;
QTest::newRow( "data18" ) << -1 << 1 << 1 << 12 << 1 << 1 << 1;
QTest::newRow( "data19" ) << -2 << 1 << 1 << 12 << -1 << 1 << 1;
QTest::newRow( "invalid" ) << 0 << 0 << 0 << 1 << 0 << 0 << 0;
}
void tst_QDate::addYears()
{
QFETCH(const int, year);
QFETCH(const int, month);
QFETCH(const int, day);
QFETCH(const int, amountToAdd);
const auto dt = QDate(year, month, day).addYears(amountToAdd);
QTEST(dt.year(), "expectedYear");
QTEST(dt.month(), "expectedMonth");
QTEST(dt.day(), "expectedDay");
}
void tst_QDate::addYears_data()
{
QTest::addColumn<int>("year");
QTest::addColumn<int>("month");
QTest::addColumn<int>("day");
QTest::addColumn<int>("amountToAdd");
QTest::addColumn<int>("expectedYear");
QTest::addColumn<int>("expectedMonth");
QTest::addColumn<int>("expectedDay");
QTest::newRow( "data0" ) << 2000 << 1 << 1 << 1 << 2001 << 1 << 1;
QTest::newRow( "data1" ) << 2000 << 1 << 31 << 1 << 2001 << 1 << 31;
QTest::newRow( "data2" ) << 2000 << 2 << 28 << 1 << 2001 << 2 << 28;
QTest::newRow( "data3" ) << 2000 << 2 << 29 << 1 << 2001 << 2 << 28;
QTest::newRow( "data4" ) << 2000 << 12 << 31 << 1 << 2001 << 12 << 31;
QTest::newRow( "data5" ) << 2001 << 2 << 28 << 3 << 2004 << 2 << 28;
QTest::newRow( "data6" ) << 2000 << 2 << 29 << 4 << 2004 << 2 << 29;
QTest::newRow( "data7" ) << 2000 << 1 << 31 << -1 << 1999 << 1 << 31;
QTest::newRow( "data9" ) << 2000 << 2 << 29 << -1 << 1999 << 2 << 28;
QTest::newRow( "data10" ) << 2000 << 12 << 31 << -1 << 1999 << 12 << 31;
QTest::newRow( "data11" ) << 2001 << 2 << 28 << -3 << 1998 << 2 << 28;
QTest::newRow( "data12" ) << 2000 << 2 << 29 << -4 << 1996 << 2 << 29;
QTest::newRow( "data13" ) << 2000 << 2 << 29 << -5 << 1995 << 2 << 28;
QTest::newRow( "data14" ) << 2000 << 1 << 1 << -1999 << 1 << 1 << 1;
QTest::newRow( "data15" ) << 2000 << 1 << 1 << -2000 << -1 << 1 << 1;
QTest::newRow( "data16" ) << 2000 << 1 << 1 << -2001 << -2 << 1 << 1;
QTest::newRow( "data17" ) << -2000 << 1 << 1 << 1999 << -1 << 1 << 1;
QTest::newRow( "data18" ) << -2000 << 1 << 1 << 2000 << 1 << 1 << 1;
QTest::newRow( "data19" ) << -2000 << 1 << 1 << 2001 << 2 << 1 << 1;
QTest::newRow( "invalid" ) << 0 << 0 << 0 << 1 << 0 << 0 << 0;
}
void tst_QDate::daysTo()
{
QDate dt1(2000, 1, 1);
QDate dt2(2000, 1, 5);
QCOMPARE(dt1.daysTo(dt2), (qint64) 4);
QCOMPARE(dt2.daysTo(dt1), (qint64) -4);
dt1.setDate(0, 0, 0);
QCOMPARE(dt1.daysTo(dt2), (qint64) 0);
dt1.setDate(2000, 1, 1);
dt2.setDate(0, 0, 0);
QCOMPARE(dt1.daysTo(dt2), (qint64) 0);
QDate maxDate = QDate::fromJulianDay(maxJd);
QDate minDate = QDate::fromJulianDay(minJd);
QDate zeroDate = QDate::fromJulianDay(0);
QCOMPARE(maxDate.daysTo(minDate), minJd - maxJd);
QCOMPARE(minDate.daysTo(maxDate), maxJd - minJd);
QCOMPARE(maxDate.daysTo(zeroDate), -maxJd);
QCOMPARE(zeroDate.daysTo(maxDate), maxJd);
QCOMPARE(minDate.daysTo(zeroDate), -minJd);
QCOMPARE(zeroDate.daysTo(minDate), minJd);
}
void tst_QDate::orderingCompiles()
{
QTestPrivate::testAllComparisonOperatorsCompile<QDate>();
// INTEGRITY incident-85878 (timezone and clock_cast are not supported)
#if __cpp_lib_chrono >= 201907L && !defined(Q_OS_INTEGRITY)
QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_day>();
QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_day_last>();
QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_weekday>();
QTestPrivate::testAllComparisonOperatorsCompile<QDate, std::chrono::year_month_weekday_last>();
#endif
}
void tst_QDate::operator_eq_eq_data()
{
QTest::addColumn<QDate>("d1");
QTest::addColumn<QDate>("d2");
QTest::addColumn<bool>("expectEqual");
QTest::newRow("data0") << QDate(2000,1,2) << QDate(2000,1,2) << true;
QTest::newRow("data1") << QDate(2001,12,5) << QDate(2001,12,5) << true;
QTest::newRow("data2") << QDate(2001,12,5) << QDate(2001,12,5) << true;
QTest::newRow("data3") << QDate(2001,12,5) << QDate(2002,12,5) << false;
QDate date1(1900, 1, 1);
QDate date2 = date1.addDays(1);
QDate date3 = date1.addDays(-1);
QDate date4 = date1.addMonths(1);
QDate date5 = date1.addMonths(-1);
QDate date6 = date1.addYears(1);
QDate date7 = date1.addYears(-1);
QTest::newRow("data4") << date2 << date3 << false;
QTest::newRow("data5") << date4 << date5 << false;
QTest::newRow("data6") << date6 << date7 << false;
QTest::newRow("data7") << date1 << date2 << false;
QTest::newRow("data8") << date1 << date3 << false;
QTest::newRow("data9") << date1 << date4 << false;
QTest::newRow("data10") << date1 << date5 << false;
QTest::newRow("data11") << date1 << date6 << false;
QTest::newRow("data12") << date1 << date7 << false;
}
void tst_QDate::operator_eq_eq()
{
QFETCH(QDate, d1);
QFETCH(QDate, d2);
QFETCH(bool, expectEqual);
QT_TEST_EQUALITY_OPS(d1, d2, expectEqual);
if (expectEqual)
QVERIFY(qHash(d1) == qHash(d2));
}
void tst_QDate::ordering_data()
{
QTest::addColumn<QDate>("left");
QTest::addColumn<QDate>("right");
QTest::addColumn<Qt::strong_ordering>("expectedOrdering");
QTest::newRow("2000-1-2_vs_2000-1-2")
<< QDate(2000, 1, 2) << QDate(2000, 1, 2) << Qt::strong_ordering::equivalent;
QTest::newRow("2001-12-4_vs_2001-12-5")
<< QDate(2001, 12, 4) << QDate(2001, 12, 5) << Qt::strong_ordering::less;
QTest::newRow("2001-11-5_vs_2001-12-5")
<< QDate(2001, 11, 5) << QDate(2001, 12, 5) << Qt::strong_ordering::less;
QTest::newRow("2000-12-5_vs_2001-12-5")
<< QDate(2000, 12, 5) << QDate(2001, 12, 5) << Qt::strong_ordering::less;
QTest::newRow("2002-12-5_vs_2001-12-5")
<< QDate(2002, 12, 5) << QDate(2001, 12, 5) << Qt::strong_ordering::greater;
QTest::newRow("2001-12-5_vs_2001-11-5")
<< QDate(2001, 12, 5) << QDate(2001, 11, 5) << Qt::strong_ordering::greater;
QTest::newRow("2001-12-6_vs_2001-12-5")
<< QDate(2001, 12, 6) << QDate(2001, 12, 5) << Qt::strong_ordering::greater;
}
void tst_QDate::ordering()
{
QFETCH(QDate, left);
QFETCH(QDate, right);
QFETCH(Qt::strong_ordering, expectedOrdering);
QT_TEST_ALL_COMPARISON_OPS(left, right, expectedOrdering);
}
void tst_QDate::ordering_chrono_types()
{
// INTEGRITY incident-85878 (timezone and clock_cast are not supported)
#if __cpp_lib_chrono >= 201907L && !defined(Q_OS_INTEGRITY)
using namespace std::chrono;
QDate friday(2001, 11, 30); // the 5th Friday of November 2001
// std::chrono::year_month_day
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day(2001y, November, 29d),
Qt::strong_ordering::greater);
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day(2001y, November, 30d),
Qt::strong_ordering::equivalent);
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day(2001y, December, 1d),
Qt::strong_ordering::less);
// std::chrono::year_month_day_last
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day_last(2001y, {October / last}),
Qt::strong_ordering::greater);
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day_last(2001y, {November / last}),
Qt::strong_ordering::equivalent);
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_day_last(2001y, {December / last}),
Qt::strong_ordering::less);
// std::chrono::year_month_weekday
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_weekday(2001y, November, Thursday[5]),
Qt::strong_ordering::greater);
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_weekday(2001y, November, Friday[5]),
Qt::strong_ordering::equivalent);
QT_TEST_ALL_COMPARISON_OPS(friday, year_month_weekday(2001y, December, Saturday[1]),
Qt::strong_ordering::less);
// std::chrono::year_month_weekday_last
QDate thursday(2001, 11, 29); // the last Thursday of November 2001
QT_TEST_ALL_COMPARISON_OPS(thursday, year_month_weekday_last(2001y, November, Wednesday[last]),
Qt::strong_ordering::greater);
QT_TEST_ALL_COMPARISON_OPS(thursday, year_month_weekday_last(2001y, November, Thursday[last]),
Qt::strong_ordering::equivalent);
QT_TEST_ALL_COMPARISON_OPS(thursday, year_month_weekday_last(2001y, November, Friday[last]),
Qt::strong_ordering::less);
#else
QSKIP("This test requires C++20-level <chrono> support enabled in the standard library.");
#endif // __cpp_lib_chrono >= 201907L
}
Q_DECLARE_METATYPE(QDataStream::Version)
void tst_QDate::operator_insert_extract_data()
{
QTest::addColumn<QDate>("date");
QTest::addColumn<QDataStream::Version>("dataStreamVersion");
const QMetaEnum e = QMetaEnum::fromType<QDataStream::Version>();
for (int version = QDataStream::Qt_1_0; version <= QDataStream::Qt_DefaultCompiledVersion;
++version) {
if (e.value(version) == -1 || qstrcmp(e.key(version), "Qt_DefaultCompiledVersion") == 0)
continue;
const auto dataStreamVersion = static_cast<QDataStream::Version>(version);
const char *const tag = e.key(version);
QTest::addRow("(invalid) %s", tag) << invalidDate() << dataStreamVersion;
QTest::addRow("(1, 1, 1) %s", tag) << QDate(1, 1, 1) << dataStreamVersion;
QTest::addRow("(-1, 1, 1) %s", tag) << QDate(-1, 1, 1) << dataStreamVersion;
QTest::addRow("(1995, 5, 20) %s", tag) << QDate(1995, 5, 20) << dataStreamVersion;
// Test minimums for quint32/qint64.
if (dataStreamVersion >= QDataStream::Qt_5_0)
QTest::addRow("(-4714, 11, 24) %s", tag) << QDate(-4714, 11, 24) << dataStreamVersion;
else
QTest::addRow("(-4713, 1, 2) %s", tag) << QDate(-4713, 1, 2) << dataStreamVersion;
}
}
void tst_QDate::operator_insert_extract()
{
QFETCH(QDate, date);
QFETCH(QDataStream::Version, dataStreamVersion);
QByteArray byteArray;
QDataStream dataStream(&byteArray, QIODevice::ReadWrite);
dataStream.setVersion(dataStreamVersion);
dataStream << date;
dataStream.device()->reset();
QDate deserialised;
dataStream >> deserialised;
QCOMPARE(dataStream.status(), QDataStream::Ok);
QCOMPARE(deserialised, date);
}
void tst_QDate::extractOutOfRange_data()
{
QTest::addColumn<qint64>("julianDay");
QTest::addColumn<bool>("valid");
QTest::newRow("minJd") << minJd << true;
QTest::newRow("maxJd") << maxJd << true;
QTest::newRow("below-minJd") << minJd - 1 << false;
QTest::newRow("above-maxJd") << maxJd + 1 << false;
QTest::newRow("int64-min") << std::numeric_limits<qint64>::min() << false;
QTest::newRow("int64-max") << std::numeric_limits<qint64>::max() << false;
}
void tst_QDate::extractOutOfRange()
{
QFETCH(const qint64, julianDay);
QFETCH(const bool, valid);
QByteArray byteArray;
QDataStream dataStream(&byteArray, QIODevice::ReadWrite);
dataStream << julianDay;
dataStream.device()->reset();
QDate deserialised;
dataStream >> deserialised;
QCOMPARE(dataStream.status(), QDataStream::Ok);
QCOMPARE(deserialised.isValid(), valid);
if (valid) {
QCOMPARE(deserialised.toJulianDay(), julianDay);
} else {
QVERIFY(deserialised.isNull());
QCOMPARE(deserialised.toJulianDay(), QDate().toJulianDay());
}
}
#if QT_CONFIG(datestring)
void tst_QDate::fromStringDateFormat_data()
{
QTest::addColumn<QString>("dateStr");
QTest::addColumn<Qt::DateFormat>("dateFormat");
QTest::addColumn<QDate>("expectedDate");
QTest::newRow("text0") << u"Sat May 20 1995"_s << Qt::TextDate << QDate(1995, 5, 20);
QTest::newRow("text1") << u"Tue Dec 17 2002"_s << Qt::TextDate << QDate(2002, 12, 17);
QTest::newRow("text2") << QDate(1999, 11, 14).toString(Qt::TextDate) << Qt::TextDate << QDate(1999, 11, 14);
QTest::newRow("text3") << u"xxx Jan 1 0999"_s << Qt::TextDate << QDate(999, 1, 1);
QTest::newRow("text3b") << u"xxx Jan 1 999"_s << Qt::TextDate << QDate(999, 1, 1);
QTest::newRow("text4") << u"xxx Jan 1 12345"_s << Qt::TextDate << QDate(12345, 1, 1);
QTest::newRow("text5") << u"xxx Jan 1 -0001"_s << Qt::TextDate << QDate(-1, 1, 1);
QTest::newRow("text6") << u"xxx Jan 1 -4712"_s << Qt::TextDate << QDate(-4712, 1, 1);
QTest::newRow("text7") << u"xxx Nov 25 -4713"_s << Qt::TextDate << QDate(-4713, 11, 25);
QTest::newRow("text, empty") << QString() << Qt::TextDate << QDate();
QTest::newRow("text, 3 part") << u"part1 part2 part3"_s << Qt::TextDate << QDate();
QTest::newRow("text, invalid month name") << u"Wed BabytownFrolics 8 2012"_s << Qt::TextDate << QDate();
QTest::newRow("text, invalid day") << u"Wed May Wilhelm 2012"_s << Qt::TextDate << QDate();
QTest::newRow("text, invalid year") << u"Wed May 8 Cats"_s << Qt::TextDate << QDate();
QTest::newRow("iso0") << u"1995-05-20"_s << Qt::ISODate << QDate(1995, 5, 20);
QTest::newRow("iso1") << u"2002-12-17"_s << Qt::ISODate << QDate(2002, 12, 17);
QTest::newRow("iso2") << QDate(1999, 11, 14).toString(Qt::ISODate) << Qt::ISODate << QDate(1999, 11, 14);
QTest::newRow("iso3") << u"0999-01-01"_s << Qt::ISODate << QDate(999, 1, 1);
QTest::newRow("iso3b") << u"0999-01-01"_s << Qt::ISODate << QDate(999, 1, 1);
QTest::newRow("iso4") << u"2000101101"_s << Qt::ISODate << QDate();
QTest::newRow("iso5") << u"2000/01/01"_s << Qt::ISODate << QDate(2000, 1, 1);
QTest::newRow("iso6") << u"2000-01-01 blah"_s << Qt::ISODate << QDate();
QTest::newRow("iso7") << u"2000-01-011blah"_s << Qt::ISODate << QDate();
QTest::newRow("iso8") << u"2000-01-01blah"_s << Qt::ISODate << QDate();
QTest::newRow("iso9") << u"-001-01-01"_s << Qt::ISODate << QDate();
QTest::newRow("iso10") << u"99999-01-01"_s << Qt::ISODate << QDate();
QTest::newRow("iso-yr-0") << u"0000-01-01"_s << Qt::ISODate << QDate();
// Test Qt::RFC2822Date format (RFC 2822).
QTest::newRow("RFC 2822") << u"13 Feb 1987 13:24:51 +0100"_s
<< Qt::RFC2822Date << QDate(1987, 2, 13);
QTest::newRow("RFC 2822 after space")
<< u" 13 Feb 1987 13:24:51 +0100"_s
<< Qt::RFC2822Date << QDate(1987, 2, 13);
QTest::newRow("RFC 2822 with day") << u"Thu, 01 Jan 1970 00:12:34 +0000"_s
<< Qt::RFC2822Date << epochDate();
QTest::newRow("RFC 2822 with day after space")
<< u" Thu, 01 Jan 1970 00:12:34 +0000"_s
<< Qt::RFC2822Date << epochDate();
// No timezone
QTest::newRow("RFC 2822 no timezone") << u"01 Jan 1970 00:12:34"_s
<< Qt::RFC2822Date << epochDate();
// No time specified
QTest::newRow("RFC 2822 date only") << u"01 Nov 2002"_s
<< Qt::RFC2822Date << QDate(2002, 11, 1);
QTest::newRow("RFC 2822 with day date only") << u"Fri, 01 Nov 2002"_s
<< Qt::RFC2822Date << QDate(2002, 11, 1);
QTest::newRow("RFC 2822 malformed time")
<< u"01 Nov 2002 0:"_s << Qt::RFC2822Date << QDate();
// Test invalid month, day, year
QTest::newRow("RFC 2822 invalid month name") << u"13 Fev 1987 13:24:51 +0100"_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid day") << u"36 Fev 1987 13:24:51 +0100"_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid year") << u"13 Fev 0000 13:24:51 +0100"_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid character at end")
<< u"01 Jan 2012 08:00:00 +0100!"_s << Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid character at front")
<< u"!01 Jan 2012 08:00:00 +0100"_s << Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid character both ends")
<< u"!01 Jan 2012 08:00:00 +0100!"_s << Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid character at front, 2 at back")
<< u"!01 Jan 2012 08:00:00 +0100.."_s << Qt::RFC2822Date << QDate();
QTest::newRow("RFC 2822 invalid character 2 at front")
<< u"!!01 Jan 2012 08:00:00 +0100"_s << Qt::RFC2822Date << QDate();
// The common date text used by the "invalid character" tests, just to be
// sure *it's* not what's invalid:
QTest::newRow("RFC 2822 (not invalid)")
<< u"01 Jan 2012 08:00:00 +0100"_s
<< Qt::RFC2822Date << QDate(2012, 1, 1);
// Test Qt::RFC2822Date format (RFC 850 and 1036, permissive).
QTest::newRow("RFC 850 and 1036") << u"Fri Feb 13 13:24:51 1987 +0100"_s
<< Qt::RFC2822Date << QDate(1987, 2, 13);
QTest::newRow("RFC 850 and 1036 after space")
<< u" Fri Feb 13 13:24:51 1987 +0100"_s
<< Qt::RFC2822Date << QDate(1987, 2, 13);
// No timezone
QTest::newRow("RFC 850 and 1036 no timezone") << u"Thu Jan 01 00:12:34 1970"_s
<< Qt::RFC2822Date << epochDate();
// No time specified
QTest::newRow("RFC 850 and 1036 date only") << u"Fri Nov 01 2002"_s
<< Qt::RFC2822Date << QDate(2002, 11, 1);
// Test invalid characters.
QTest::newRow("RFC 850 and 1036 invalid character at end")
<< u"Sun Jan 01 08:00:00 2012 +0100!"_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 850 and 1036 invalid character at front")
<< u"!Sun Jan 01 08:00:00 2012 +0100"_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 850 and 1036 invalid character both ends")
<< u"!Sun Jan 01 08:00:00 2012 +0100!"_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 850 and 1036 invalid character at front, 2 at back")
<< u"!Sun Jan 01 08:00:00 2012 +0100.."_s
<< Qt::RFC2822Date << QDate();
QTest::newRow("RFC 850 and 1036 invalid character 2 at front")
<< u"!!Sun Jan 01 08:00:00 2012 +0100"_s
<< Qt::RFC2822Date << QDate();
// Again, check the text in the "invalid character" tests isn't the source of invalidity:
QTest::newRow("RFC 850 and 1036 (not invalid)")
<< u"Sun Jan 01 08:00:00 2012 +0100"_s
<< Qt::RFC2822Date << QDate(2012, 1, 1);
QTest::newRow("RFC empty") << u""_s << Qt::RFC2822Date << QDate();
}
void tst_QDate::fromStringDateFormat()
{
QFETCH(const QString, dateStr);
QFETCH(const Qt::DateFormat, dateFormat);
QTEST(QDate::fromString(dateStr, dateFormat), "expectedDate");
}
# if QT_CONFIG(datetimeparser)
void tst_QDate::fromStringFormat_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<QString>("format");
QTest::addColumn<int>("baseYear");
QTest::addColumn<QDate>("expected");
QTest::newRow("empty") << u""_s << u""_s << 1900 << defDate();
QTest::newRow("space-as-empty") << u" "_s << u""_s << 1900 << invalidDate();
QTest::newRow("space") << u" "_s << u" "_s << 1900 << defDate();
QTest::newRow("mispunc") << u"-%$%#"_s << u"$*(#@"_s << 1900 << invalidDate();
QTest::newRow("literal-d") << u"d"_s << u"'d'"_s << 1900 << defDate();
QTest::newRow("greedy") << u"101010"_s << u"dMyy"_s << 1900 << QDate(1910, 10, 10);
QTest::newRow("greedy-miss") << u"101010b"_s << u"dMyy"_s << 1900 << invalidDate();
QTest::newRow("January") << u"January"_s << u"MMMM"_s << 1900 << defDate();
QTest::newRow("mistext") << u"ball"_s << u"balle"_s << 1900 << invalidDate();
QTest::newRow("text") << u"balleh"_s << u"balleh"_s << 1900 << defDate();
QTest::newRow("yearless:19") << u"10.01.1"_s << u"M.dd.d"_s << 1900 << QDate(1900, 10, 1);
QTest::newRow("yearless:20") << u"10.01.1"_s << u"M.dd.d"_s << 2000 << QDate(2000, 10, 1);
QTest::newRow("neg-month") << u"-1.01.1"_s << u"M.dd.d"_s << 1900 << invalidDate();
QTest::newRow("restrained-greed") << u"11010"_s << u"dMMyy"_s << 1900 << QDate(1910, 10, 1);
QTest::newRow("neg-day") << u"-2"_s << u"d"_s << 1900 << invalidDate();
QTest::newRow("Md:132") << u"132"_s << u"Md"_s << 1900 << invalidDate();
QTest::newRow("February") << u"February"_s << u"MMMM"_s << 1900 << QDate(1900, 2, 1);
QTest::newRow("mon-aug-8th")
<< u"Mon August 8 2005"_s << u"ddd MMMM d yyyy"_s << 1900 << QDate(2005, 8, 8);
QTest::newRow("year-match-2000") << u"2000:00"_s << u"yyyy:yy"_s << 1900 << QDate(2000, 1, 1);
QTest::newRow("year-match-1999") << u"1999:99"_s << u"yyyy:yy"_s << 1900 << QDate(1999, 1, 1);
QTest::newRow("year-match-2099") << u"2099:99"_s << u"yyyy:yy"_s << 1900 << QDate(2099, 1, 1);
QTest::newRow("year-match-2001") << u"2001:01"_s << u"yyyy:yy"_s << 1900 << QDate(2001, 1, 1);
QTest::newRow("just-yy-1999") << u"99"_s << u"yy"_s << 1900 << QDate(1999, 1, 1);
QTest::newRow("just-yy-1901") << u"01"_s << u"yy"_s << 1900 << QDate(1901, 1, 1);
QTest::newRow("just-yy-2001") << u"01"_s << u"yy"_s << 1970 << QDate(2001, 1, 1);
QTest::newRow("Monday") << u"Monday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 1);
QTest::newRow("Tuesday") << u"Tuesday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 2);
QTest::newRow("Wednesday") << u"Wednesday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 3);
QTest::newRow("Thursday") << u"Thursday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 4);
QTest::newRow("Friday") << u"Friday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 5);
QTest::newRow("Saturday") << u"Saturday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 6);
QTest::newRow("Sunday") << u"Sunday"_s << u"dddd"_s << 1900 << QDate(1900, 1, 7);
QTest::newRow("Mon06") << u"Monday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 2);
QTest::newRow("Tues06") << u"Tuesday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 3);
QTest::newRow("Wed06") << u"Wednesday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 4);
QTest::newRow("Thu06") << u"Thursday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 5);
QTest::newRow("Fri06") << u"Friday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 6);
QTest::newRow("Sat06") << u"Saturday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 7);
QTest::newRow("Sun06") << u"Sunday 2006"_s << u"dddd yyyy"_s << 1900 << QDate(2006, 1, 1);
QTest::newRow("Tue07Mar")
<< u"Tuesday 2007 March"_s << u"dddd yyyy MMMM"_s << 1900 << QDate(2007, 3, 6);
QTest::newRow("21May2006")
<< u"21052006"_s << u"ddMMyyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("21May06:19")
<< u"210506"_s << u"ddMMyy"_s << 1900 << QDate(1906, 5, 21);
QTest::newRow("21May06:20")
<< u"210506"_s << u"ddMMyy"_s << 1970 << QDate(2006, 5, 21);
QTest::newRow("21/May/2006")
<< u"21/5/2006"_s << u"d/M/yyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("21/5/06")
<< u"21/5/06"_s << u"d/M/yy"_s << 1900 << QDate(1906, 5, 21);
QTest::newRow("21/5/06:19")
<< u"21/5/06"_s << u"d/M/yy"_s << 1900 << QDate(1906, 5, 21);
QTest::newRow("21/5/06:20")
<< u"21/5/06"_s << u"d/M/yy"_s << 1910 << QDate(2006, 5, 21);
QTest::newRow("2006May21")
<< u"20060521"_s << u"yyyyMMdd"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("06May21:19")
<< u"060521"_s << u"yyMMdd"_s << 1900 << QDate(1906, 5, 21);
QTest::newRow("06May21:20")
<< u"060521"_s << u"yyMMdd"_s << 1907 << QDate(2006, 5, 21);
QTest::newRow("lateMarch")
<< u"9999-03-06"_s << u"yyyy-MM-dd"_s << 1900 << QDate(9999, 3, 6);
QTest::newRow("late")
<< u"9999-12-31"_s << u"yyyy-MM-dd"_s << 1900 << QDate(9999, 12, 31);
QTest::newRow("quoted-dd")
<< u"21dd-05-2006"_s << u"dd'dd'-MM-yyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("quoted-MM")
<< u"21-MM05-2006"_s << u"dd-'MM'MM-yyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("quotes-empty")
<< u"21-'05-2006"_s << u"dd-''MM-yyyy"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("quote-verbose")
<< u"On day 22 of the Gregorian month named April in the Common Era year 2026"_s
<< u"'On day' dd 'of the' 'Gregorian month named' MMMM 'in the' 'Common Era year' yyyy"_s
<< 1900 << QDate(2026, 4, 22);
// Empty format is matched by empty string, simply regurgitating defaults:
QTest::newRow("empty-matches-empty")
<< u""_s << u""_s << 2000 << QDate(2000, 1, 1);
// An unmatched quote in the format makes it malformed, so parsing fails:
QTest::newRow("lone-quote") << u"20-'05-2006"_s << u"dd-'MM-yyyy"_s << 1900 << QDate();
QTest::newRow("empty-lone-quote") // Regression test against invalid format "matching" empty:
<< u""_s << u"dd yyyy' MM"_s << 1900 << QDate();
// Test unicode handling.
QTest::newRow("Unicode in format string")
<< u"2020🤣09🤣21"_s << u"yyyy🤣MM🤣dd"_s << 1900 << QDate(2020, 9, 21);
QTest::newRow("Unicode-in-format-string-quoted-emoji")
<< u"🤣🤣2020👍09🤣21"_s << u"'🤣🤣'yyyy👍MM🤣dd"_s << 1900
<< QDate(2020, 9, 21);
QTest::newRow("Unicode-in-quoted-dd-format-string")
<< u"🤣🤣2020👍09🤣21dd"_s << u"🤣🤣yyyy👍MM🤣dd'dd'"_s << 1900
<< QDate(2020, 9, 21);
QTest::newRow("Unicode-in-all-formats-quoted-string")
<< u"🤣🤣yyyy2020👍MM09🤣21dd"_s << u"🤣🤣'yyyy'yyyy👍'MM'MM🤣dd'dd'"_s
<< 1900 << QDate(2020, 9, 21);
// QTBUG-84334
QTest::newRow("-ve year: front, nosep")
<< u"-20060521"_s << u"yyyyMMdd"_s << 1900 << QDate(-2006, 5, 21);
QTest::newRow("-ve year: mid, nosep")
<< u"05-200621"_s << u"MMyyyydd"_s << 1900 << QDate(-2006, 5, 21);
QTest::newRow("-ve year: back, nosep")
<< u"0521-2006"_s << u"MMddyyyy"_s << 1900 << QDate(-2006, 5, 21);
// - as separator should not interfere with negative year numbers:
QTest::newRow("-ve year: front, dash")
<< u"-2006-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(-2006, 5, 21);
QTest::newRow("positive year: front, dash")
<< u"-2006-05-21"_s << u"-yyyy-MM-dd"_s << 1900 << QDate(2006, 5, 21);
QTest::newRow("-ve year: mid, dash")
<< u"05--2006-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(-2006, 5, 21);
QTest::newRow("-ve year: back, dash")
<< u"05-21--2006"_s << u"MM-dd-yyyy"_s << 1900 << QDate(-2006, 5, 21);
// zero year number is invalid
QTest::newRow("year-zero-front")
<< u"0000-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
QTest::newRow("year-zero-mid")
<< u"05-0000-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
QTest::newRow("year-zero-back")
<< u"05-21-0000"_s << u"MM-dd-yyyy"_s << 1900 << QDate();
// negative three digit year numbers should be rejected:
QTest::newRow("-ve 3digit year: front")
<< u"-206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
QTest::newRow("-ve 3digit year: mid")
<< u"05--206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
QTest::newRow("-ve 3digit year: back")
<< u"05-21--206"_s << u"MM-dd-yyyy"_s << 1900 << QDate();
// negative month numbers should be rejected:
QTest::newRow("-ve 2digit month: mid")
<< u"2060--05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
QTest::newRow("-ve 2digit month: front")
<< u"-05-2060-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
QTest::newRow("-ve 2digit month: back")
<< u"21-2060--05"_s << u"dd-yyyy-MM"_s << 1900 << QDate();
// negative single digit month numbers should be rejected:
QTest::newRow("-ve 1digit month: mid")
<< u"2060--5-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
QTest::newRow("-ve 1digit month: front")
<< u"-5-2060-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
QTest::newRow("-ve 1digit month: back")
<< u"21-2060--5"_s << u"dd-yyyy-MM"_s << 1900 << QDate();
// negative day numbers should be rejected:
QTest::newRow("-ve 2digit day: front")
<< u"-21-2060-05"_s << u"dd-yyyy-MM"_s << 1900 << QDate();
QTest::newRow("-ve 2digit day: mid")
<< u"2060--21-05"_s << u"yyyy-dd-MM"_s << 1900 << QDate();
QTest::newRow("-ve 2digit day: back")
<< u"05-2060--21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
// negative single digit day numbers should be rejected:
QTest::newRow("-ve 1digit day: front")
<< u"-2-2060-05"_s << u"dd-yyyy-MM"_s << 1900 << QDate();
QTest::newRow("-ve 1digit day: mid")
<< u"05--2-2060"_s << u"MM-dd-yyyy"_s << 1900 << QDate();
QTest::newRow("-ve 1digit day: back")
<< u"2060-05--2"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
// positive three digit year numbers should be rejected:
QTest::newRow("3digit year, front") << u"206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
QTest::newRow("3digit year, mid") << u"05-206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
QTest::newRow("3digit year, back") << u"05-21-206"_s << u"MM-dd-yyyy"_s << 1900 << QDate();
// positive five digit year numbers should be rejected:
QTest::newRow("5digit year, front")
<< u"00206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate();
QTest::newRow("5digit year, mid")
<< u"05-00206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate();
QTest::newRow("5digit year, back")
<< u"05-21-00206"_s << u"MM-dd-yyyy"_s << 1900 << QDate();
// ... unless they have an overt sign:
QTest::newRow("signed 5digit year, front")
<< u"+00206-05-21"_s << u"yyyy-MM-dd"_s << 1900 << QDate(206, 5, 21);
QTest::newRow("signed 5digit year, mid")
<< u"05-+00206-21"_s << u"MM-yyyy-dd"_s << 1900 << QDate(206, 5, 21);
QTest::newRow("signed 5digit year, back")
<< u"05-21-+00206"_s << u"MM-dd-yyyy"_s << 1900 << QDate(206, 5, 21);
QTest::newRow("non-leap-feb-29") // QTBUG-132115: should fail but not assert
<< u"290215"_s << u"ddMMyy"_s << 1900 << QDate();
QTest::newRow("dash separator, no year at end")
<< u"05-21-"_s << u"dd-MM-yyyy"_s << 1900 << QDate();
QTest::newRow("slash separator, no year at end")
<< u"11/05/"_s << u"d/MM/yyyy"_s << 1900 << QDate();
// QTBUG-84349
QTest::newRow("+ sign in year field") << u"+0200322"_s << u"yyyyMMdd"_s << 1900 << QDate();
QTest::newRow("+ sign in month field") << u"2020+322"_s << u"yyyyMMdd"_s << 1900 << QDate();
QTest::newRow("+ sign in day field") << u"202003+1"_s << u"yyyyMMdd"_s << 1900 << QDate();
}
void tst_QDate::fromStringFormat()
{
QFETCH(const QString, string);
QFETCH(const QString, format);
QFETCH(const int, baseYear);
QTEST(QDate::fromString(string, format, baseYear), "expected");
}
void tst_QDate::fromStringFormatCal_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<QString>("format");
QTest::addColumn<int>("baseYear");
QTest::addColumn<QCalendar>("calendar");
QTest::addColumn<QDate>("expected");
using Sys = QCalendar::System;
constexpr Sys systems[] = {
Sys::Gregorian, Sys::Julian, Sys::Milankovic,
#if QT_CONFIG(jalalicalendar)
Sys::Jalali,
#endif
#if QT_CONFIG(islamiccivilcalendar)
Sys::IslamicCivil,
#endif
};
for (const Sys system : systems) {
const QCalendar cal(system);
const QDate date(2024, 4, 12, cal);
const QByteArray calName = cal.name().toUtf8();
const QString format = u"ddd, d MMM yy"_s; // QTBUG-146744
QTest::addRow("ddd, d MMM yy/%s", calName.constData())
<< cal.dateTimeToString(format, QDateTime(), date, QTime(), QLocale::c())
<< format << 2000 << cal << date;
}
}
void tst_QDate::fromStringFormatCal()
{
QFETCH(const QString, string);
QFETCH(const QString, format);
QFETCH(const int, baseYear);
QFETCH(const QCalendar, calendar);
QTEST(QDate::fromString(string, format, baseYear, calendar), "expected");
}
#endif // datetimeparser
void tst_QDate::toStringFormat_data()
{
QTest::addColumn<QDate>("t");
QTest::addColumn<QString>("format");
QTest::addColumn<QString>("str");
QTest::newRow( "data0" ) << QDate(1995,5,20) << u"d-M-yy"_s << u"20-5-95"_s;
QTest::newRow( "data1" ) << QDate(2002,12,17) << u"dd-MM-yyyy"_s << u"17-12-2002"_s;
QTest::newRow( "data2" ) << QDate(1995,5,20) << u"M-yy"_s << u"5-95"_s;
QTest::newRow( "data3" ) << QDate(2002,12,17) << u"dd"_s << u"17"_s;
QTest::newRow( "data4" ) << QDate() << u"dd-mm-yyyy"_s << QString();
}
void tst_QDate::toStringFormat()
{
QFETCH(const QDate, t);
QFETCH(const QString, format);
QTEST(t.toString(format), "str");
}
void tst_QDate::toStringDateFormat_data()
{
QTest::addColumn<QDate>("date");
QTest::addColumn<Qt::DateFormat>("format");
QTest::addColumn<QString>("expectedStr");
QTest::newRow("data0") << QDate(1,1,1) << Qt::ISODate << u"0001-01-01"_s;
QTest::newRow("data1") << QDate(11,1,1) << Qt::ISODate << u"0011-01-01"_s;
QTest::newRow("data2") << QDate(111,1,1) << Qt::ISODate << u"0111-01-01"_s;
QTest::newRow("data3") << QDate(1974,12,1) << Qt::ISODate << u"1974-12-01"_s;
QTest::newRow("year < 0") << QDate(-1,1,1) << Qt::ISODate << QString();
QTest::newRow("year > 9999") << QDate(10000, 1, 1) << Qt::ISODate << QString();
QTest::newRow("RFC2822Date") << QDate(1974,12,1) << Qt::RFC2822Date << u"01 Dec 1974"_s;
QTest::newRow("ISODateWithMs") << QDate(1974,12,1) << Qt::ISODateWithMs << u"1974-12-01"_s;
}
void tst_QDate::toStringDateFormat()
{
QFETCH(const QDate, date);
QFETCH(const Qt::DateFormat, format);
QTEST(date.toString(format), "expectedStr");
}
#endif // datestring
void tst_QDate::isLeapYear()
{
QVERIFY(QDate::isLeapYear(-4801));
QVERIFY(!QDate::isLeapYear(-4800));
QVERIFY(QDate::isLeapYear(-4445));
QVERIFY(!QDate::isLeapYear(-4444));
QVERIFY(!QDate::isLeapYear(-6));
QVERIFY(QDate::isLeapYear(-5));
QVERIFY(!QDate::isLeapYear(-4));
QVERIFY(!QDate::isLeapYear(-3));
QVERIFY(!QDate::isLeapYear(-2));
QVERIFY(QDate::isLeapYear(-1));
QVERIFY(!QDate::isLeapYear(0)); // Doesn't exist
QVERIFY(!QDate::isLeapYear(1));
QVERIFY(!QDate::isLeapYear(2));
QVERIFY(!QDate::isLeapYear(3));
QVERIFY(QDate::isLeapYear(4));
QVERIFY(!QDate::isLeapYear(7));
QVERIFY(QDate::isLeapYear(8));
QVERIFY(!QDate::isLeapYear(100));
QVERIFY(QDate::isLeapYear(400));
QVERIFY(!QDate::isLeapYear(700));
QVERIFY(!QDate::isLeapYear(1500));
QVERIFY(QDate::isLeapYear(1600));
QVERIFY(!QDate::isLeapYear(1700));
QVERIFY(!QDate::isLeapYear(1800));
QVERIFY(!QDate::isLeapYear(1900));
QVERIFY(QDate::isLeapYear(2000));
QVERIFY(!QDate::isLeapYear(2100));
QVERIFY(!QDate::isLeapYear(2200));
QVERIFY(!QDate::isLeapYear(2300));
QVERIFY(QDate::isLeapYear(2400));
QVERIFY(!QDate::isLeapYear(2500));
QVERIFY(!QDate::isLeapYear(2600));
QVERIFY(!QDate::isLeapYear(2700));
QVERIFY(QDate::isLeapYear(2800));
for (int i = -4713; i <= 10000; ++i) {
if (i == 0)
continue;
QVERIFY(!QDate(i, 2, 29).isValid() == !QDate::isLeapYear(i));
}
}
void tst_QDate::yearsZeroToNinetyNine()
{
{
QDate dt(-1, 2, 3);
QCOMPARE(dt.year(), -1);
QCOMPARE(dt.month(), 2);
QCOMPARE(dt.day(), 3);
}
{
QDate dt(1, 2, 3);
QCOMPARE(dt.year(), 1);
QCOMPARE(dt.month(), 2);
QCOMPARE(dt.day(), 3);
}
{
QDate dt(99, 2, 3);
QCOMPARE(dt.year(), 99);
QCOMPARE(dt.month(), 2);
QCOMPARE(dt.day(), 3);
}
QVERIFY(!QDate::isValid(0, 2, 3));
QVERIFY(QDate::isValid(1, 2, 3));
QVERIFY(QDate::isValid(-1, 2, 3));
{
QDate dt;
dt.setDate(1, 2, 3);
QCOMPARE(dt.year(), 1);
QCOMPARE(dt.month(), 2);
QCOMPARE(dt.day(), 3);
dt.setDate(0, 2, 3);
QVERIFY(!dt.isValid());
}
}
void tst_QDate::printNegativeYear_data() const
{
QTest::addColumn<int>("year");
QTest::addColumn<QString>("expect");
QTest::newRow("millennium") << -1000 << u"-1000"_s;
QTest::newRow("century") << -500 << u"-0500"_s;
QTest::newRow("decade") << -20 << u"-0020"_s;
QTest::newRow("year") << -7 << u"-0007"_s;
}
void tst_QDate::printNegativeYear() const
{
QFETCH(int, year);
QFETCH(QString, expect);
expect.replace(QLatin1Char('-'), QLocale().negativeSign());
QDate date(year, 3, 4);
QVERIFY(date.isValid());
QCOMPARE(date.year(), year);
QCOMPARE(date.toString(u"yyyy"), expect);
}
#if QT_CONFIG(datestring)
void tst_QDate::roundtripString() const
{
/* This code path should not result in warnings. */
const QDate date(QDate::currentDate());
QCOMPARE(date.fromString(date.toString(Qt::TextDate), Qt::TextDate), date);
const QDateTime now(QDateTime::currentDateTime());
// TextDate discards milliseconds, so clip to whole second:
const QDateTime when = now.addMSecs(-now.time().msec());
QCOMPARE(when.fromString(when.toString(Qt::TextDate), Qt::TextDate), when);
}
#endif
void tst_QDate::roundtrip() const
{
// Test round trip, this exercises setDate(), isValid(), isLeapYear(),
// year(), month(), day(), julianDayFromDate(), and getDateFromJulianDay()
// to ensure they are internally consistent (but doesn't guarantee correct)
// Test Julian round trip around JD 0 and the c++ integer division rounding
// problem point (eg. negative numbers) in the conversion functions.
QDate testDate;
QDate loopDate = QDate::fromJulianDay(-50001); // 1 Jan 4850 BC
while (loopDate.toJulianDay() <= 5150) { // 31 Dec 4700 BC
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);
}
// Test Julian round trip in both BC and AD
loopDate = QDate::fromJulianDay(1684901); // 1 Jan 100 BC
while (loopDate.toJulianDay() <= 1757949) { // 31 Dec 100 AD
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);
}
// Test Gregorian round trip during current useful period
loopDate = QDate::fromJulianDay(2378497); // 1 Jan 1900 AD
while (loopDate.toJulianDay() <= 2488433) { // 31 Dec 2100 AD
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);
}
// Test Gregorian round trip at top end of widget/format range
loopDate = QDate::fromJulianDay(5336961); // 1 Jan 9900 AD
while (loopDate.toJulianDay() <= 5373484) { // 31 Dec 9999 AD
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);
}
// Test Gregorian round trip at top end of conversion range
loopDate = QDate::fromJulianDay(maxJd);
while (loopDate.toJulianDay() >= maxJd - 146397) {
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(-1);
}
// Test Gregorian round trip at low end of conversion range
loopDate = QDate::fromJulianDay(minJd);
while (loopDate.toJulianDay() <= minJd + 146397) {
testDate.setDate(loopDate.year(), loopDate.month(), loopDate.day());
QCOMPARE(loopDate.toJulianDay(), testDate.toJulianDay());
loopDate = loopDate.addDays(1);
}
// INTEGRITY incident-85878 (timezone and clock_cast are not supported)
#if __cpp_lib_chrono >= 201907L && !defined(Q_OS_INTEGRITY)
// Test roundtrip for from/to std::chrono conversions.
// Compile-time test, to verify it's all constexpr.
using namespace std::chrono;
{
constexpr sys_days expected{days{minJd}};
constexpr sys_days actual{QDate::fromStdSysDays(expected).toStdSysDays()};
static_assert(actual == expected);
}
{
// constexpr year_month_day expected{sys_days{days{maxJd}}}; // Overflow at least on MSVC
constexpr year_month_day expected{1970y, January, 1d};
constexpr sys_days actual{QDate(expected).toStdSysDays()};
static_assert(actual == sys_days(expected));
}
{
constexpr year_month_day_last expected{2001y, {October / last}};
constexpr sys_days actual{QDate(expected).toStdSysDays()};
static_assert(actual == sys_days(expected));
}
{
constexpr year_month_weekday expected{2001y, December, Saturday[1]};
constexpr sys_days actual{QDate(expected).toStdSysDays()};
static_assert(actual == sys_days(expected));
}
{
constexpr year_month_weekday_last expected{2001y, November, Friday[last]};
constexpr sys_days actual{QDate(expected).toStdSysDays()};
static_assert(actual == sys_days(expected));
}
#endif // __cpp_lib_chrono >= 201907L
}
void tst_QDate::qdebug() const
{
QTest::ignoreMessage(QtDebugMsg, "QDate(Invalid)");
qDebug() << QDate();
QTest::ignoreMessage(QtDebugMsg, "QDate(\"1983-08-07\")");
qDebug() << QDate(1983, 8, 7);
}
QTEST_APPLESS_MAIN(tst_QDate)
#include "tst_qdate.moc"