Serialise QDateTime as UTC and timeSpec() instead of private spec.
authorMitch Curtis <mitch.curtis@nokia.com>
Thu, 16 Aug 2012 13:55:31 +0000 (15:55 +0200)
committerQt by Nokia <qt-info@nokia.com>
Tue, 21 Aug 2012 09:05:20 +0000 (11:05 +0200)
QDateTime currently serialises its private Spec enum. If a user was to
deserialise the individual components of a QDateTime separately, the
resulting timeSpec may be invalid when cast to the Qt::TimeSpec enum.

E.g.:

QDateTime dateTime(QDate(2012, 8, 14), QTime(8, 0, 0), Qt::UTC);
// ... serialise
// ... deserialise date, time, spec separately.
// spec == 2, the value of QDateTimePrivate::UTC.
// spec != UTC, will be set to LocalUnknown.
QDateTime deserialised(date, time, spec);

This patch serialises QDateTime objects in UTC and the value of
timeSpec() as opposed to QDateTimePrivate's spec. This changes the
serialisation behaviour of QDateTime for version 13 of QDataStream.

Task-number: QTBUG-4057
Change-Id: If650e7960dca7b6ab44b8233410a6369c41df73a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
src/corelib/tools/qdatetime.cpp
tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp

index 293838e..3324408 100644 (file)
@@ -3666,9 +3666,19 @@ QDataStream &operator>>(QDataStream &in, QTime &time)
 */
 QDataStream &operator<<(QDataStream &out, const QDateTime &dateTime)
 {
-    out << dateTime.d->date << dateTime.d->time;
-    if (out.version() >= 7)
-        out << (qint8)dateTime.d->spec;
+    if (out.version() >= 13) {
+        if (dateTime.isValid()) {
+            QDateTime asUTC = dateTime.toUTC();
+            out << asUTC.d->date << asUTC.d->time;
+        } else {
+            out << dateTime.d->date << dateTime.d->time;
+        }
+        out << (qint8)dateTime.timeSpec();
+    } else {
+        out << dateTime.d->date << dateTime.d->time;
+        if (out.version() >= 7)
+            out << (qint8)dateTime.d->spec;
+    }
     return out;
 }
 
@@ -3684,11 +3694,22 @@ QDataStream &operator>>(QDataStream &in, QDateTime &dateTime)
 {
     dateTime.detach();
 
-    qint8 ts = (qint8)QDateTimePrivate::LocalUnknown;
     in >> dateTime.d->date >> dateTime.d->time;
-    if (in.version() >= 7)
+
+    if (in.version() >= 13) {
+        qint8 ts = 0;
         in >> ts;
-    dateTime.d->spec = (QDateTimePrivate::Spec)ts;
+        if (dateTime.isValid()) {
+            // We always store the datetime as UTC in 13 onwards.
+            dateTime.d->spec = QDateTimePrivate::UTC;
+            dateTime = dateTime.toTimeSpec(static_cast<Qt::TimeSpec>(ts));
+        }
+    } else {
+        qint8 ts = (qint8)QDateTimePrivate::LocalUnknown;
+        if (in.version() >= 7)
+            in >> ts;
+        dateTime.d->spec = (QDateTimePrivate::Spec)ts;
+    }
     return in;
 }
 #endif // QT_NO_DATASTREAM
index 6327e73..70b761f 100644 (file)
@@ -106,6 +106,8 @@ private slots:
     void msecsTo();
     void operator_eqeq_data();
     void operator_eqeq();
+    void operator_insert_extract_data();
+    void operator_insert_extract();
     void currentDateTime();
     void currentDateTimeUtc();
     void currentDateTimeUtc2();
@@ -1221,6 +1223,80 @@ void tst_QDateTime::operator_eqeq()
     }
 }
 
+void tst_QDateTime::operator_insert_extract_data()
+{
+    QTest::addColumn<QDateTime>("dateTime");
+    QTest::addColumn<QString>("serialiseAs");
+    QTest::addColumn<QString>("deserialiseAs");
+
+    const QDateTime positiveYear(QDateTime(QDate(2012, 8, 14), QTime(8, 0, 0), Qt::LocalTime));
+    const QDateTime negativeYear(QDateTime(QDate(-2012, 8, 14), QTime(8, 0, 0), Qt::LocalTime));
+
+    const QString westernAustralia(QString::fromLatin1("AWST-8AWDT-9,M10.5.0,M3.5.0/03:00:00"));
+    const QString hawaii(QString::fromLatin1("HAW10"));
+
+    QTest::newRow("14/08/2012 08:00 WA => HAWAII") << positiveYear << westernAustralia << hawaii;
+    QTest::newRow("14/08/2012 08:00 WA => HAWAII") << positiveYear << westernAustralia << hawaii;
+    QTest::newRow("14/08/2012 08:00 WA => HAWAII") << positiveYear << westernAustralia << hawaii;
+    QTest::newRow("14/08/2012 08:00 WA => WA") << positiveYear << westernAustralia << westernAustralia;
+    QTest::newRow("14/08/-2012 08:00 HAWAII => WA") << negativeYear << hawaii << westernAustralia;
+    QTest::newRow("14/08/-2012 08:00 HAWAII => WA") << negativeYear << hawaii << westernAustralia;
+    QTest::newRow("14/08/-2012 08:00 HAWAII => WA") << negativeYear << hawaii << westernAustralia;
+    QTest::newRow("14/08/2012 08:00 HAWAII => HAWAII") << positiveYear << hawaii << hawaii;
+}
+
+void tst_QDateTime::operator_insert_extract()
+{
+    QFETCH(QDateTime, dateTime);
+    QFETCH(QString, serialiseAs);
+    QFETCH(QString, deserialiseAs);
+
+    QString previousTimeZone = qgetenv("TZ");
+    qputenv("TZ", serialiseAs.toLocal8Bit().constData());
+    tzset();
+    QDateTime dateTimeAsUTC(dateTime.toUTC());
+
+    QByteArray byteArray;
+    {
+        QDataStream dataStream(&byteArray, QIODevice::WriteOnly);
+        dataStream << dateTime;
+        dataStream << dateTime;
+    }
+
+    // Ensure that a change in timezone between serialisation and deserialisation
+    // still results in identical UTC-converted datetimes.
+    qputenv("TZ", deserialiseAs.toLocal8Bit().constData());
+    tzset();
+    QDateTime expectedLocalTime(dateTimeAsUTC.toLocalTime());
+    {
+        // Deserialise whole QDateTime at once.
+        QDataStream dataStream(&byteArray, QIODevice::ReadOnly);
+        QDateTime deserialised;
+        dataStream >> deserialised;
+        // Ensure local time is still correct.
+        QCOMPARE(deserialised, expectedLocalTime);
+        // Sanity check UTC times.
+        QCOMPARE(deserialised.toUTC(), expectedLocalTime.toUTC());
+
+        // Deserialise each component individually.
+        QDate deserialisedDate;
+        dataStream >> deserialisedDate;
+        QTime deserialisedTime;
+        dataStream >> deserialisedTime;
+        qint8 deserialisedSpec;
+        dataStream >> deserialisedSpec;
+        deserialised = QDateTime(deserialisedDate, deserialisedTime, Qt::UTC);
+        deserialised = deserialised.toTimeSpec(static_cast<Qt::TimeSpec>(deserialisedSpec));
+        // Ensure local time is still correct.
+        QCOMPARE(deserialised, expectedLocalTime);
+        // Sanity check UTC times.
+        QCOMPARE(deserialised.toUTC(), expectedLocalTime.toUTC());
+    }
+
+    qputenv("TZ", previousTimeZone.toLocal8Bit().constData());
+    tzset();
+}
+
 void tst_QDateTime::toString_strformat_data()
 {
     QTest::addColumn<QDateTime>("dt");