Distinguish invalid datetimes from others

A default-constructed QDateTime is invalid, but compared equal to a
valid one referencing the start of 1970.  This lead to date properties
in QML being initialized invalid but not getting an onChange if the
first value they're set to is the start of 1970.

Fixing that then lead to some tests failing. Indeed, the original
equality check involved using toMSecsSinceEpoch(), whose value is
undefined unless the datetime is valid, without a prior check on its
validity: so ensure all uses of toMSecsSinceEpoch() are guarded with
isValid() checks.

Reworked tst_QDateTime::toSecsSinceEpoch() to use its bool column
(previously unused, after separating from toTime_t(), which uses this
column for "out of time_t's range") for validity of the datetime.

[ChangeLog][QtCore][QDateTime] Invalid datetimes are now treated as
equal and less than all valid ones. They could previously be found
equal to valid datetimes.

Fixes: QTBUG-79006
Change-Id: Ie72deb8af4350a5e808144d0f6e42dc8eb3ff5ef
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2019-10-04 13:26:27 +02:00
parent 4af00753fa
commit 60deb69034
2 changed files with 49 additions and 33 deletions

View File

@ -3861,6 +3861,9 @@ int QDateTime::offsetFromUtc() const
QString QDateTime::timeZoneAbbreviation() const
{
if (!isValid())
return QString();
switch (getSpec(d)) {
case Qt::UTC:
return QLatin1String("UTC");
@ -3895,6 +3898,9 @@ QString QDateTime::timeZoneAbbreviation() const
bool QDateTime::isDaylightTime() const
{
if (!isValid())
return false;
switch (getSpec(d)) {
case Qt::UTC:
case Qt::OffsetFromUTC:
@ -4761,17 +4767,24 @@ QDateTime QDateTime::toTimeZone(const QTimeZone &timeZone) const
Returns \c true if this datetime is equal to the \a other datetime;
otherwise returns \c false.
Since 5.14, all invalid datetimes are equal to one another and differ from
all other datetimes.
\sa operator!=()
*/
bool QDateTime::operator==(const QDateTime &other) const
{
if (getSpec(d) == Qt::LocalTime
&& getStatus(d) == getStatus(other.d)) {
if (!isValid())
return !other.isValid();
if (!other.isValid())
return false;
if (getSpec(d) == Qt::LocalTime && getStatus(d) == getStatus(other.d))
return getMSecs(d) == getMSecs(other.d);
}
// Convert to UTC and compare
return (toMSecsSinceEpoch() == other.toMSecsSinceEpoch());
return toMSecsSinceEpoch() == other.toMSecsSinceEpoch();
}
/*!
@ -4780,8 +4793,9 @@ bool QDateTime::operator==(const QDateTime &other) const
Returns \c true if this datetime is different from the \a other
datetime; otherwise returns \c false.
Two datetimes are different if either the date, the time, or the
time zone components are different.
Two datetimes are different if either the date, the time, or the time zone
components are different. Since 5.14, any invalid datetime is less than all
valid datetimes.
\sa operator==()
*/
@ -4793,12 +4807,16 @@ bool QDateTime::operator==(const QDateTime &other) const
bool QDateTime::operator<(const QDateTime &other) const
{
if (getSpec(d) == Qt::LocalTime
&& getStatus(d) == getStatus(other.d)) {
if (!isValid())
return other.isValid();
if (!other.isValid())
return false;
if (getSpec(d) == Qt::LocalTime && getStatus(d) == getStatus(other.d))
return getMSecs(d) < getMSecs(other.d);
}
// Convert to UTC and compare
return (toMSecsSinceEpoch() < other.toMSecsSinceEpoch());
return toMSecsSinceEpoch() < other.toMSecsSinceEpoch();
}
/*!
@ -5849,7 +5867,7 @@ uint qHash(const QDateTime &key, uint seed)
// QDate/QTime/spec/offset because QDateTime::operator== converts both arguments
// to the same timezone. If we don't, qHash would return different hashes for
// two QDateTimes that are equivalent once converted to the same timezone.
return qHash(key.toMSecsSinceEpoch(), seed);
return key.isValid() ? qHash(key.toMSecsSinceEpoch(), seed) : seed;
}
/*! \fn uint qHash(const QDate &key, uint seed = 0)

View File

@ -360,6 +360,7 @@ void tst_QDateTime::ctor()
void tst_QDateTime::operator_eq()
{
QVERIFY(QDateTime() != QDateTime(QDate(1970, 1, 1), QTime(0, 0))); // QTBUG-79006
QDateTime dt1(QDate(2004, 3, 24), QTime(23, 45, 57), Qt::UTC);
QDateTime dt2(QDate(2005, 3, 11), QTime(), Qt::UTC);
dt2 = dt1;
@ -1675,29 +1676,30 @@ void tst_QDateTime::currentDateTimeUtc2()
void tst_QDateTime::toSecsSinceEpoch_data()
{
QTest::addColumn<QString>("dateTimeStr");
QTest::addColumn<bool>("res");
QTest::addColumn<bool>("valid");
QTest::newRow( "data1" ) << str( 1800, 1, 1, 12, 0, 0 ) << false;
QTest::newRow( "data2" ) << str( 1969, 1, 1, 12, 0, 0 ) << false;
QTest::newRow( "data1" ) << str( 1800, 1, 1, 12, 0, 0 ) << true;
QTest::newRow( "data2" ) << str( 1969, 1, 1, 12, 0, 0 ) << true;
QTest::newRow( "data3" ) << str( 2002, 1, 1, 12, 0, 0 ) << true;
QTest::newRow( "data4" ) << str( 2002, 6, 1, 12, 0, 0 ) << true;
QTest::newRow( "data5" ) << QString("INVALID") << false;
QTest::newRow( "data6" ) << str( 2038, 1, 1, 12, 0, 0 ) << true;
QTest::newRow( "data7" ) << str( 2063, 4, 5, 12, 0, 0 ) << true; // the day of First Contact
QTest::newRow( "data8" ) << str( 2107, 1, 1, 12, 0, 0 )
<< bool( sizeof(uint) > 32 && sizeof(time_t) > 32 );
QTest::newRow( "data8" ) << str( 2107, 1, 1, 12, 0, 0 ) << true;
}
void tst_QDateTime::toSecsSinceEpoch()
{
QFETCH( QString, dateTimeStr );
QDateTime datetime = dt( dateTimeStr );
QFETCH(const QString, dateTimeStr);
const QDateTime datetime = dt(dateTimeStr);
QFETCH(const bool, valid);
QCOMPARE(datetime.isValid(), valid);
qint64 asSecsSinceEpoch = datetime.toSecsSinceEpoch();
QCOMPARE(asSecsSinceEpoch, datetime.toMSecsSinceEpoch() / 1000);
QDateTime datetime2 = QDateTime::fromSecsSinceEpoch(asSecsSinceEpoch);
QCOMPARE(datetime, datetime2);
if (valid) {
const qint64 asSecsSinceEpoch = datetime.toSecsSinceEpoch();
QCOMPARE(asSecsSinceEpoch, datetime.toMSecsSinceEpoch() / 1000);
QCOMPARE(QDateTime::fromSecsSinceEpoch(asSecsSinceEpoch), datetime);
}
}
#if QT_DEPRECATED_SINCE(5, 8)
@ -1725,14 +1727,10 @@ void tst_QDateTime::toTime_t()
uint asTime_t = datetime.toTime_t();
QFETCH( bool, res );
if (res) {
QVERIFY( asTime_t != (uint)-1 );
QVERIFY(asTime_t != uint(-1));
QCOMPARE(QDateTime::fromTime_t(asTime_t), datetime);
} else {
QVERIFY( asTime_t == (uint)-1 );
}
if ( asTime_t != (uint) -1 ) {
QDateTime datetime2 = QDateTime::fromTime_t( asTime_t );
QCOMPARE(datetime, datetime2);
QCOMPARE(asTime_t, uint(-1));
}
}
#endif
@ -1929,8 +1927,8 @@ void tst_QDateTime::operator_eqeq_data()
QDateTime dateTime1(QDate(2012, 6, 20), QTime(14, 33, 2, 500));
QDateTime dateTime1a = dateTime1.addMSecs(1);
QDateTime dateTime2(QDate(2012, 20, 6), QTime(14, 33, 2, 500));
QDateTime dateTime2a = dateTime2.addMSecs(-1);
QDateTime dateTime2(QDate(2012, 20, 6), QTime(14, 33, 2, 500)); // Invalid
QDateTime dateTime2a = dateTime2.addMSecs(-1); // Still invalid
QDateTime dateTime3(QDate(1970, 1, 1), QTime(0, 0, 0, 0), Qt::UTC); // UTC epoch
QDateTime dateTime3a = dateTime3.addDays(1);
QDateTime dateTime3b = dateTime3.addDays(-1);
@ -1946,7 +1944,7 @@ void tst_QDateTime::operator_eqeq_data()
QTest::newRow("data2") << dateTime1a << dateTime1a << true << false;
QTest::newRow("data3") << dateTime1 << dateTime2 << false << false;
QTest::newRow("data4") << dateTime1 << dateTime1a << false << false;
QTest::newRow("data5") << dateTime2 << dateTime2a << false << false;
QTest::newRow("data5") << dateTime2 << dateTime2a << true << false;
QTest::newRow("data6") << dateTime2 << dateTime3 << false << false;
QTest::newRow("data7") << dateTime3 << dateTime3a << false << false;
QTest::newRow("data8") << dateTime3 << dateTime3b << false << false;