Finish 64-bit time conversion, modify protocol
authorShane Synan <digitalcircuit36939@gmail.com>
Fri, 11 May 2018 00:20:12 +0000 (19:20 -0500)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 23 May 2018 22:33:28 +0000 (00:33 +0200)
Convert the remaining uses of 32-bit quint-based
"[to|from|set]Time_t()" to the modern 64-bit qint64-based
"[to|from|set]MSecsSinceEpoch()", handling the Y2038 problem while
also gaining higher precision in some places.

For places where seconds are needed, e.g. converting incoming time
from the IRC protocol, make use of "[to|set|from]SecsSinceEpoch()"
instead.  As this was only added in Qt 5.8, use the "QT_VERSION"
macro for backwards compatibility, manually dividing by 1000 for the
"MSecs" function equivalents.

Beware, all new code should use "[to|from|set]MSecsSinceEpoch()" and
NOT use "[to|from|set]Time_t()", or you'll re-introduce Y2038 bugs!

Modify protocol to rename "LongMessageTime" to "LongTime", and
send/receive all times as "qint64" instead of "quint64" or "quint".
"LongMessageTime" was never in Quassel git master, so renaming it
should have no impact.

Modify Event timing to be 64-bit compatible, too.

"IrcUser::lastAwayMessage()" will be fixed in a follow-up commit.

SQLite millisecond precision will be fixed in a follow-up commit.

See https://doc.qt.io/qt-5/qdatetime.html
And https://doc.qt.io/qt-5/datastreamformat.html
And https://doc.qt.io/qt-5/qdatetime-obsolete.html

16 files changed:
src/client/messagefilter.cpp
src/client/messagefilter.h
src/client/messagemodel.cpp
src/client/messagemodel.h
src/common/event.cpp
src/common/ircuser.cpp
src/common/message.cpp
src/common/quassel.cpp
src/common/quassel.h
src/core/core.cpp
src/core/corenetwork.cpp
src/core/corenetwork.h
src/core/coresessioneventprocessor.cpp
src/core/eventstringifier.cpp
src/core/postgresqlstorage.cpp
src/core/sqlitestorage.cpp

index 5dd428f..c501c61 100644 (file)
@@ -221,7 +221,8 @@ bool MessageFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourcePar
         if (myNetworkId != msgNetworkId)
             return false;
 
-        uint messageTimestamp = sourceModel()->data(sourceIdx, MessageModel::TimestampRole).value<QDateTime>().toTime_t();
+        qint64 messageTimestamp = sourceModel()->data(sourceIdx, MessageModel::TimestampRole)
+                .value<QDateTime>().toMSecsSinceEpoch();
         QString quiter = sourceModel()->data(sourceIdx, Qt::DisplayRole).toString().section(' ', 0, 0, QString::SectionSkipEmpty).toLower();
         if (quiter != bufferName().toLower())
             return false;
index de14567..b6180fa 100644 (file)
@@ -62,7 +62,7 @@ private:
     void init();
 
     QSet<BufferId> _validBuffers;
-    QMultiHash<QString, uint> _filteredQuitMsgs;
+    QMultiHash<QString, qint64> _filteredQuitMsgs;
     int _messageTypeFilter;
 
     int _userNoticesTarget;
index 4e04d8a..8eae52b 100644 (file)
@@ -41,7 +41,8 @@ MessageModel::MessageModel(QObject *parent)
     QDateTime now = QDateTime::currentDateTime();
     now.setTimeSpec(Qt::UTC);
     _nextDayChange.setTimeSpec(Qt::UTC);
-    _nextDayChange.setTime_t(((now.toTime_t() / 86400) + 1) * 86400);
+    _nextDayChange.setMSecsSinceEpoch(
+                ((now.toMSecsSinceEpoch() / DAY_IN_MSECS) + 1) * DAY_IN_MSECS);
     _nextDayChange.setTimeSpec(Qt::LocalTime);
     _dayChangeTimer.setInterval(QDateTime::currentDateTime().secsTo(_nextDayChange) * 1000);
     _dayChangeTimer.start();
@@ -158,10 +159,10 @@ void MessageModel::insertMessageGroup(const QList<Message> &msglist)
         QDateTime prevTs = msglist.last().timestamp();
         nextTs.setTimeSpec(Qt::UTC);
         prevTs.setTimeSpec(Qt::UTC);
-        uint nextDay = nextTs.toTime_t() / 86400;
-        uint prevDay = prevTs.toTime_t() / 86400;
+        qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
+        qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
         if (nextDay != prevDay) {
-            nextTs.setTime_t(nextDay * 86400);
+            nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
             nextTs.setTimeSpec(Qt::LocalTime);
             dayChangeMsg = Message::ChangeOfDay(nextTs);
             dayChangeMsg.setMsgId(msglist.last().msgId());
@@ -250,10 +251,10 @@ int MessageModel::insertMessagesGracefully(const QList<Message> &msglist)
                     QDateTime prevTs = (*iter).timestamp();
                     nextTs.setTimeSpec(Qt::UTC);
                     prevTs.setTimeSpec(Qt::UTC);
-                    uint nextDay = nextTs.toTime_t() / 86400;
-                    uint prevDay = prevTs.toTime_t() / 86400;
+                    qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
+                    qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
                     if (nextDay != prevDay) {
-                        nextTs.setTime_t(nextDay * 86400);
+                        nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
                         nextTs.setTimeSpec(Qt::LocalTime);
                         Message dayChangeMsg = Message::ChangeOfDay(nextTs);
                         dayChangeMsg.setMsgId((*iter).msgId());
@@ -282,10 +283,10 @@ int MessageModel::insertMessagesGracefully(const QList<Message> &msglist)
                     QDateTime prevTs = (*iter).timestamp();
                     nextTs.setTimeSpec(Qt::UTC);
                     prevTs.setTimeSpec(Qt::UTC);
-                    uint nextDay = nextTs.toTime_t() / 86400;
-                    uint prevDay = prevTs.toTime_t() / 86400;
+                    qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
+                    qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
                     if (nextDay != prevDay) {
-                        nextTs.setTime_t(nextDay * 86400);
+                        nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
                         nextTs.setTimeSpec(Qt::LocalTime);
                         Message dayChangeMsg = Message::ChangeOfDay(nextTs);
                         dayChangeMsg.setMsgId((*iter).msgId());
@@ -360,7 +361,7 @@ int MessageModel::indexForId(MsgId id)
 
 void MessageModel::changeOfDay()
 {
-    _dayChangeTimer.setInterval(86400000);
+    _dayChangeTimer.setInterval(DAY_IN_MSECS);
     if (!messagesIsEmpty()) {
         int idx = messageCount();
         while (idx > 0 && messageItemAt(idx - 1)->timestamp() > _nextDayChange) {
@@ -372,7 +373,7 @@ void MessageModel::changeOfDay()
         insertMessage__(idx, dayChangeMsg);
         endInsertRows();
     }
-    _nextDayChange = _nextDayChange.addSecs(86400);
+    _nextDayChange = _nextDayChange.addMSecs(DAY_IN_MSECS);
 }
 
 
index 72781c1..e26455d 100644 (file)
@@ -114,6 +114,10 @@ private:
     QTimer _dayChangeTimer;
     QDateTime _nextDayChange;
     QHash<BufferId, int> _messagesWaiting;
+
+    /// Period of time for one day in milliseconds
+    /// 24 hours * 60 minutes * 60 seconds * 1000 milliseconds
+    const qint64 DAY_IN_MSECS = 24 * 60 * 60 * 1000;
 };
 
 
index 8604796..b933fa6 100644 (file)
@@ -22,6 +22,8 @@
 #include "ircevent.h"
 #include "networkevent.h"
 #include "messageevent.h"
+#include "peer.h"
+#include "signalproxy.h"
 
 Event::Event(EventManager::EventType type)
     : _type(type)
@@ -40,16 +42,33 @@ Event::Event(EventManager::EventType type, QVariantMap &map)
         return;
     }
 
+    Q_ASSERT(SignalProxy::current());
+    Q_ASSERT(SignalProxy::current()->sourcePeer());
+
     setFlags(static_cast<EventManager::EventFlags>(map.take("flags").toInt())); // TODO sanity check?
-    setTimestamp(QDateTime::fromTime_t(map.take("timestamp").toUInt()));
+
+    if (SignalProxy::current()->sourcePeer()->hasFeature(Quassel::Feature::LongTime)) {
+        // timestamp is a qint64, signed rather than unsigned
+        setTimestamp(QDateTime::fromMSecsSinceEpoch(map.take("timestamp").toLongLong()));
+    } else {
+        setTimestamp(QDateTime::fromTime_t(map.take("timestamp").toUInt()));
+    }
 }
 
 
 void Event::toVariantMap(QVariantMap &map) const
 {
+    Q_ASSERT(SignalProxy::current());
+    Q_ASSERT(SignalProxy::current()->targetPeer());
+
     map["type"] = static_cast<int>(type());
     map["flags"] = static_cast<int>(flags());
-    map["timestamp"] = timestamp().toTime_t();
+    if (SignalProxy::current()->targetPeer()->hasFeature(Quassel::Feature::LongTime)) {
+        // toMSecs returns a qint64, signed rather than unsigned
+        map["timestamp"] = timestamp().toMSecsSinceEpoch();
+    } else {
+        map["timestamp"] = timestamp().toTime_t();
+    }
 }
 
 
index b793d68..6c10cf4 100644 (file)
@@ -68,8 +68,12 @@ QString IrcUser::hostmask() const
 
 QDateTime IrcUser::idleTime()
 {
-    if (QDateTime::currentDateTime().toTime_t() - _idleTimeSet.toTime_t() > 1200)
+    if ((QDateTime::currentDateTime().toMSecsSinceEpoch() - _idleTimeSet.toMSecsSinceEpoch())
+            > 1200000) {
+        // 20 * 60 * 1000 = 1200000
+        // 20 minutes have elapsed, clear the known idle time as it's likely inaccurate by now
         _idleTime = QDateTime();
+    }
     return _idleTime;
 }
 
index 061f16e..fc50d37 100644 (file)
@@ -65,8 +65,9 @@ QDataStream &operator<<(QDataStream &out, const Message &msg)
     // We do not serialize the sender prefixes until we have a new protocol or client-features implemented
     out << msg.msgId();
 
-    if (SignalProxy::current()->targetPeer()->hasFeature(Quassel::Feature::LongMessageTime)) {
-        out << (quint64) msg.timestamp().toMSecsSinceEpoch();
+    if (SignalProxy::current()->targetPeer()->hasFeature(Quassel::Feature::LongTime)) {
+        // toMSecs returns a qint64, signed rather than unsigned
+        out << (qint64) msg.timestamp().toMSecsSinceEpoch();
     } else {
         out << (quint32) msg.timestamp().toTime_t();
     }
@@ -96,8 +97,9 @@ QDataStream &operator>>(QDataStream &in, Message &msg)
 
     in >> msg._msgId;
 
-    if (SignalProxy::current()->sourcePeer()->hasFeature(Quassel::Feature::LongMessageTime)) {
-        quint64 timeStamp;
+    if (SignalProxy::current()->sourcePeer()->hasFeature(Quassel::Feature::LongTime)) {
+        // timestamp is a qint64, signed rather than unsigned
+        qint64 timeStamp;
         in >> timeStamp;
         msg._timestamp = QDateTime::fromMSecsSinceEpoch(timeStamp);
     } else {
index c75836a..9685c24 100644 (file)
@@ -296,7 +296,14 @@ void Quassel::setupBuildInfo()
     if (!QString(GIT_HEAD).isEmpty()) {
         buildInfo.commitHash = GIT_HEAD;
         QDateTime date;
-        date.setTime_t(GIT_COMMIT_DATE);
+#if QT_VERSION >= 0x050800
+        date.setSecsSinceEpoch(GIT_COMMIT_DATE);
+#else
+        // toSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+        // See https://doc.qt.io/qt-5/qdatetime.html#toMSecsSinceEpoch
+        // Warning generated if not converting the 1000 to a qint64 first.
+        date.setMSecsSinceEpoch(GIT_COMMIT_DATE * (qint64)1000);
+#endif
         buildInfo.commitDate = date.toString();
     }
     else if (!QString(DIST_HASH).contains("Format")) {
index 8908365..50ea82a 100644 (file)
@@ -131,7 +131,7 @@ public:
         SenderPrefixes,           ///< Show prefixes for senders in backlog
         RemoteDisconnect,         ///< Allow this peer to be remotely disconnected
         ExtendedFeatures,         ///< Extended features
-        LongMessageTime,          ///< Serialize message time as 64-bit
+        LongTime,                 ///< Serialize time as 64-bit values
         RichMessages,             ///< Real Name and Avatar URL in backlog
         BacklogFilterType,        ///< BacklogManager supports filtering backlog by MessageType
 #if QT_VERSION >= 0x050500
index 37dfe78..4edff84 100644 (file)
@@ -376,7 +376,7 @@ QString Core::setupCoreForInternalUsage()
 {
     Q_ASSERT(!_registeredStorageBackends.empty());
 
-    qsrand(QDateTime::currentDateTime().toTime_t());
+    qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
     int pass = 0;
     for (int i = 0; i < 10; i++) {
         pass *= 10;
index 30f319b..51c6188 100644 (file)
@@ -908,12 +908,17 @@ void CoreNetwork::doAutoReconnect()
 
 void CoreNetwork::sendPing()
 {
-    uint now = QDateTime::currentDateTime().toTime_t();
+    qint64 now = QDateTime::currentDateTime().toMSecsSinceEpoch();
     if (_pingCount != 0) {
         qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
                  << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
     }
-    if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
+    if ((int)_pingCount >= networkConfig()->maxPingCount()
+            && (now - _lastPingTime) <= (_pingTimer.interval() + (1 * 1000))) {
+        // In transitioning to 64-bit time, the interval no longer needs converted down to seconds.
+        // However, to reduce the risk of breaking things by changing past behavior, we still allow
+        // up to 1 second missed instead of enforcing a stricter 1 millisecond allowance.
+        //
         // the second check compares the actual elapsed time since the last ping and the pingTimer interval
         // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
         // and unable to even handle a ping answer. So we ignore those misses.
index b67e2c3..d269358 100644 (file)
@@ -477,7 +477,7 @@ private:
     int _lastUsedServerIndex;
 
     QTimer _pingTimer;
-    uint _lastPingTime;
+    qint64 _lastPingTime;
     uint _pingCount;
     bool _sendPings;
 
index 26f6c6a..8968cd5 100644 (file)
@@ -959,8 +959,18 @@ void CoreSessionEventProcessor::processIrcEvent317(IrcEvent *e)
 
     int idleSecs = e->params()[1].toInt();
     if (e->params().count() > 3) { // if we have more then 3 params we have the above mentioned "real life" situation
-        int logintime = e->params()[2].toInt();
-        loginTime = QDateTime::fromTime_t(logintime);
+        // Allow for 64-bit time
+        qint64 logintime = e->params()[2].toLongLong();
+        // Time in IRC protocol is defined as seconds.  Convert from seconds instead.
+        // See https://doc.qt.io/qt-5/qdatetime.html#fromSecsSinceEpoch
+#if QT_VERSION >= 0x050800
+        loginTime = QDateTime::fromSecsSinceEpoch(logintime);
+#else
+        // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for
+        // now.
+        // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+        loginTime = QDateTime::fromMSecsSinceEpoch((qint64)(logintime * 1000));
+#endif
     }
 
     IrcUser *ircuser = e->network()->ircUser(e->params()[0]);
index b15545a..c1279ff 100644 (file)
@@ -449,7 +449,11 @@ void EventStringifier::processIrcEvent301(IrcEvent *e)
         target = nick;
         IrcUser *ircuser = e->network()->ircUser(nick);
         if (ircuser) {
+            // FIXME: This needs converted to 64-bit time.
+            // For legacy protocol, keep the 32-bit signed int time.  For modern protocol, just send
+            // the actual QDateTime() instead, don't bother converting it.
             int now = QDateTime::currentDateTime().toTime_t();
+            // FIXME: Convert to millisecond comparison, comment the constant value as needed
             const int silenceTime = 60;
             if (ircuser->lastAwayMessage() + silenceTime >= now)
                 send = false;
@@ -542,7 +546,16 @@ void EventStringifier::processIrcEvent317(IrcEvent *e)
     int idleSecs = e->params()[1].toInt();
 
     if (e->params().count() > 3) { // if we have more then 3 params we have the above mentioned "real life" situation
-        QDateTime loginTime = QDateTime::fromTime_t(e->params()[2].toInt()).toUTC();
+        // Time in IRC protocol is defined as seconds.  Convert from seconds instead.
+        // See https://doc.qt.io/qt-5/qdatetime.html#fromSecsSinceEpoch
+#if QT_VERSION >= 0x050800
+        QDateTime loginTime = QDateTime::fromSecsSinceEpoch(e->params()[2].toLongLong()).toUTC();
+#else
+        // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+        // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+        QDateTime loginTime = QDateTime::fromMSecsSinceEpoch(
+                    (qint64)(e->params()[2].toLongLong() * 1000)).toUTC();
+#endif
         displayMsg(e, Message::Server, tr("[Whois] %1 is logged in since %2")
            .arg(e->params()[0], loginTime.toString("yyyy-MM-dd hh:mm:ss UTC")));
     }
@@ -645,12 +658,21 @@ void EventStringifier::processIrcEvent329(IrcEvent *e)
         return;
 
     QString channel = e->params()[0];
-    uint unixtime = e->params()[1].toUInt();
+    // Allow for 64-bit time
+    qint64 unixtime = e->params()[1].toLongLong();
     if (!unixtime) {
         qWarning() << Q_FUNC_INFO << "received invalid timestamp:" << e->params()[1];
         return;
     }
-    QDateTime time = QDateTime::fromTime_t(unixtime).toUTC();
+    // Time in IRC protocol is defined as seconds.  Convert from seconds instead.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromSecsSinceEpoch
+#if QT_VERSION >= 0x050800
+    QDateTime time = QDateTime::fromSecsSinceEpoch(unixtime).toUTC();
+#else
+    // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+    QDateTime time = QDateTime::fromMSecsSinceEpoch((qint64)(unixtime * 1000)).toUTC();
+#endif
     displayMsg(e, Message::Topic, tr("Channel %1 created on %2")
         .arg(channel, time.toString("yyyy-MM-dd hh:mm:ss UTC")),
        QString(), channel);
@@ -696,7 +718,16 @@ void EventStringifier::processIrcEvent333(IrcEvent *e)
         return;
 
     QString channel = e->params().first();
-    QDateTime topicSetTime = QDateTime::fromTime_t(e->params()[2].toInt()).toUTC();
+    // Time in IRC protocol is defined as seconds.  Convert from seconds instead.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromSecsSinceEpoch
+#if QT_VERSION >= 0x050800
+    QDateTime topicSetTime = QDateTime::fromSecsSinceEpoch(e->params()[2].toLongLong()).toUTC();
+#else
+    // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+    QDateTime topicSetTime = QDateTime::fromMSecsSinceEpoch(
+                (qint64)(e->params()[2].toLongLong() * 1000)).toUTC();
+#endif
     displayMsg(e, Message::Topic, tr("Topic set by %1 on %2")
         .arg(e->params()[1],
             topicSetTime.toString("yyyy-MM-dd hh:mm:ss UTC")), QString(), channel);
index cd6d7bf..16ab7c3 100644 (file)
@@ -1634,6 +1634,8 @@ bool PostgreSqlStorage::logMessage(Message &msg)
     }
 
     QVariantList params;
+    // PostgreSQL handles QDateTime()'s serialized format by default, and QDateTime() serializes
+    // to a 64-bit time compatible format by default.
     params << msg.timestamp()
            << msg.bufferInfo().bufferId().toInt()
            << msg.type()
@@ -1718,6 +1720,8 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs)
     for (int i = 0; i < msgs.count(); i++) {
         Message &msg = msgs[i];
         QVariantList params;
+        // PostgreSQL handles QDateTime()'s serialized format by default, and QDateTime() serializes
+        // to a 64-bit time compatible format by default.
         params << msg.timestamp()
                << msg.bufferInfo().bufferId().toInt()
                << msg.type()
@@ -1797,6 +1801,8 @@ QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms
 
     QDateTime timestamp;
     while (query.next()) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
         timestamp = query.value(1).toDateTime();
         timestamp.setTimeSpec(Qt::UTC);
         Message msg(timestamp,
@@ -1861,6 +1867,8 @@ QList<Message> PostgreSqlStorage::requestMsgsFiltered(UserId user, BufferId buff
 
     QDateTime timestamp;
     while (query.next()) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
         timestamp = query.value(1).toDateTime();
         timestamp.setTimeSpec(Qt::UTC);
         Message msg(timestamp,
@@ -1916,6 +1924,8 @@ QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId
 
     QDateTime timestamp;
     for (int i = 0; i < limit && query.next(); i++) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
         timestamp = query.value(2).toDateTime();
         timestamp.setTimeSpec(Qt::UTC);
         Message msg(timestamp,
@@ -1978,6 +1988,8 @@ QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(UserId user, MsgId firs
 
     QDateTime timestamp;
     for (int i = 0; i < limit && query.next(); i++) {
+        // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+        // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
         timestamp = query.value(2).toDateTime();
         timestamp.setTimeSpec(Qt::UTC);
         Message msg(timestamp,
index f38a948..e40150c 100644 (file)
@@ -1756,8 +1756,18 @@ bool SqliteStorage::logMessage(Message &msg)
     {
         QSqlQuery logMessageQuery(db);
         logMessageQuery.prepare(queryString("insert_message"));
-
-        logMessageQuery.bindValue(":time", msg.timestamp().toTime_t());
+        // Store timestamp in seconds as 64-bit integer
+        //
+        // NOTE:  This is a loss of precision.  The database time column would need to store a
+        // fractional number to support toMSecsSinceEpoch(), or an upgrade step would need to
+        // convert all past times to milliseconds, multiplying by 1000.
+#if QT_VERSION >= 0x050800
+        logMessageQuery.bindValue(":time", msg.timestamp().toSecsSinceEpoch());
+#else
+        // toSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+        // See https://doc.qt.io/qt-5/qdatetime.html#toMSecsSinceEpoch
+        logMessageQuery.bindValue(":time", (qint64)(msg.timestamp().toMSecsSinceEpoch() / 1000));
+#endif
         logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
         logMessageQuery.bindValue(":type", msg.type());
         logMessageQuery.bindValue(":flags", (int)msg.flags());
@@ -1839,8 +1849,19 @@ bool SqliteStorage::logMessages(MessageList &msgs)
         logMessageQuery.prepare(queryString("insert_message"));
         for (int i = 0; i < msgs.count(); i++) {
             Message &msg = msgs[i];
-
-            logMessageQuery.bindValue(":time", msg.timestamp().toTime_t());
+            // Store timestamp in seconds as 64-bit integer
+            //
+            // NOTE:  This is a loss of precision.  The database time column would need to store a
+            // fractional number to support toMSecsSinceEpoch(), or an upgrade step would need to
+            // convert all past times to milliseconds, multiplying by 1000.
+#if QT_VERSION >= 0x050800
+            logMessageQuery.bindValue(":time", msg.timestamp().toSecsSinceEpoch());
+#else
+            // toSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+            // See https://doc.qt.io/qt-5/qdatetime.html#toMSecsSinceEpoch
+            logMessageQuery.bindValue(":time",
+                                      (qint64)(msg.timestamp().toMSecsSinceEpoch() / 1000));
+#endif
             logMessageQuery.bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
             logMessageQuery.bindValue(":type", msg.type());
             logMessageQuery.bindValue(":flags", (int)msg.flags());
@@ -1929,7 +1950,20 @@ QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, MsgId
         watchQuery(query);
 
         while (query.next()) {
-            Message msg(QDateTime::fromTime_t(query.value(1).toInt()),
+            Message msg(
+                // Read timestamp in seconds as 64-bit integer
+                //
+                // NOTE:  This is a loss of precision.  The database time column would need to store
+                // a fractional number to support fromMSecsSinceEpoch(), or an upgrade step would
+                // need to convert all past times to milliseconds, multiplying by 1000.
+#if QT_VERSION >= 0x050800
+                QDateTime::fromSecsSinceEpoch(query.value(1).toLongLong()),
+#else
+                // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for
+                // now.
+                // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+                QDateTime::fromMSecsSinceEpoch((qint64)(query.value(1).toLongLong() * 1000)),
+#endif
                 bufferInfo,
                 (Message::Type)query.value(2).toInt(),
                 query.value(8).toString(),
@@ -2005,7 +2039,22 @@ QList<Message> SqliteStorage::requestMsgsFiltered(UserId user, BufferId bufferId
         watchQuery(query);
 
         while (query.next()) {
-            Message msg(QDateTime::fromTime_t(query.value(1).toInt()),
+            Message msg(
+                        // Read timestamp in seconds as 64-bit integer
+                        //
+                        // NOTE:  This is a loss of precision.  The database time column would need
+                        // to store a fractional number to support fromMSecsSinceEpoch(), or an
+                        // upgrade step would need to convert all past times to milliseconds,
+                        // multiplying by 1000.
+#if QT_VERSION >= 0x050800
+                        QDateTime::fromSecsSinceEpoch(query.value(1).toLongLong()),
+#else
+                        // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to
+                        // seconds for now.
+                        // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+                        QDateTime::fromMSecsSinceEpoch(
+                            (qint64)(query.value(1).toLongLong() * 1000)),
+#endif
                         bufferInfo,
                         (Message::Type)query.value(2).toInt(),
                         query.value(8).toString(),
@@ -2062,7 +2111,20 @@ QList<Message> SqliteStorage::requestAllMsgs(UserId user, MsgId first, MsgId las
         watchQuery(query);
 
         while (query.next()) {
-            Message msg(QDateTime::fromTime_t(query.value(2).toInt()),
+            Message msg(
+                // Read timestamp in seconds as 64-bit integer
+                //
+                // NOTE:  This is a loss of precision.  The database time column would need to store
+                // a fractional number to support fromMSecsSinceEpoch(), or an upgrade step would
+                // need to convert all past times to milliseconds, multiplying by 1000.
+#if QT_VERSION >= 0x050800
+                QDateTime::fromSecsSinceEpoch(query.value(2).toLongLong()),
+#else
+                // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for
+                // now.
+                // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+                QDateTime::fromMSecsSinceEpoch((qint64)(query.value(2).toLongLong() * 1000)),
+#endif
                 bufferInfoHash[query.value(1).toInt()],
                 (Message::Type)query.value(3).toInt(),
                 query.value(9).toString(),
@@ -2121,7 +2183,22 @@ QList<Message> SqliteStorage::requestAllMsgsFiltered(UserId user, MsgId first, M
         watchQuery(query);
 
         while (query.next()) {
-            Message msg(QDateTime::fromTime_t(query.value(2).toInt()),
+            Message msg(
+                        // Read timestamp in seconds as 64-bit integer
+                        //
+                        // NOTE:  This is a loss of precision.  The database time column would need
+                        // to store a fractional number to support fromMSecsSinceEpoch(), or an
+                        // upgrade step would need to convert all past times to milliseconds,
+                        // multiplying by 1000.
+#if QT_VERSION >= 0x050800
+                        QDateTime::fromSecsSinceEpoch(query.value(2).toLongLong()),
+#else
+                        // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to
+                        // seconds for now.
+                        // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+                        QDateTime::fromMSecsSinceEpoch(
+                            (qint64)(query.value(2).toLongLong() * 1000)),
+#endif
                         bufferInfoHash[query.value(1).toInt()],
                         (Message::Type)query.value(3).toInt(),
                         query.value(9).toString(),
@@ -2442,7 +2519,20 @@ bool SqliteMigrationReader::readMo(BacklogMO &backlog)
     }
 
     backlog.messageid = value(0).toLongLong();
-    backlog.time = QDateTime::fromTime_t(value(1).toInt()).toUTC();
+    // Read timestamp in seconds as 64-bit integer
+    //
+    // NOTE:  This is a loss of precision.  The database time column would need to store a
+    // fractional number to support fromMSecsSinceEpoch(), or an upgrade step would need to convert
+    // all past times to milliseconds, multiplying by 1000.
+#if QT_VERSION >= 0x050800
+    backlog.time = QDateTime::fromSecsSinceEpoch(value(1).toLongLong()).toUTC();
+#else
+    // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for
+    // now.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+    backlog.time = QDateTime::fromMSecsSinceEpoch((qint64)(value(1).toLongLong() * 1000)
+                                                  ).toUTC();
+#endif
     backlog.bufferid = value(2).toInt();
     backlog.type = value(3).toInt();
     backlog.flags = value(4).toInt();