From 6a63070246d89aa2a2474e3a9a1035fa889ad77e Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Thu, 10 May 2018 19:20:12 -0500 Subject: [PATCH] Finish 64-bit time conversion, modify protocol 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 --- src/client/messagefilter.cpp | 3 +- src/client/messagefilter.h | 2 +- src/client/messagemodel.cpp | 25 +++--- src/client/messagemodel.h | 4 + src/common/event.cpp | 23 +++++- src/common/ircuser.cpp | 6 +- src/common/message.cpp | 10 ++- src/common/quassel.cpp | 9 ++- src/common/quassel.h | 2 +- src/core/core.cpp | 2 +- src/core/corenetwork.cpp | 9 ++- src/core/corenetwork.h | 2 +- src/core/coresessioneventprocessor.cpp | 14 +++- src/core/eventstringifier.cpp | 39 ++++++++- src/core/postgresqlstorage.cpp | 12 +++ src/core/sqlitestorage.cpp | 108 ++++++++++++++++++++++--- 16 files changed, 228 insertions(+), 42 deletions(-) diff --git a/src/client/messagefilter.cpp b/src/client/messagefilter.cpp index 5dd428f9..c501c614 100644 --- a/src/client/messagefilter.cpp +++ b/src/client/messagefilter.cpp @@ -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().toTime_t(); + qint64 messageTimestamp = sourceModel()->data(sourceIdx, MessageModel::TimestampRole) + .value().toMSecsSinceEpoch(); QString quiter = sourceModel()->data(sourceIdx, Qt::DisplayRole).toString().section(' ', 0, 0, QString::SectionSkipEmpty).toLower(); if (quiter != bufferName().toLower()) return false; diff --git a/src/client/messagefilter.h b/src/client/messagefilter.h index de145674..b6180fa2 100644 --- a/src/client/messagefilter.h +++ b/src/client/messagefilter.h @@ -62,7 +62,7 @@ private: void init(); QSet _validBuffers; - QMultiHash _filteredQuitMsgs; + QMultiHash _filteredQuitMsgs; int _messageTypeFilter; int _userNoticesTarget; diff --git a/src/client/messagemodel.cpp b/src/client/messagemodel.cpp index 4e04d8aa..8eae52bb 100644 --- a/src/client/messagemodel.cpp +++ b/src/client/messagemodel.cpp @@ -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 &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 &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 &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); } diff --git a/src/client/messagemodel.h b/src/client/messagemodel.h index 72781c16..e26455d5 100644 --- a/src/client/messagemodel.h +++ b/src/client/messagemodel.h @@ -114,6 +114,10 @@ private: QTimer _dayChangeTimer; QDateTime _nextDayChange; QHash _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; }; diff --git a/src/common/event.cpp b/src/common/event.cpp index 8604796a..b933fa64 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -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(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(type()); map["flags"] = static_cast(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(); + } } diff --git a/src/common/ircuser.cpp b/src/common/ircuser.cpp index b793d683..6c10cf4c 100644 --- a/src/common/ircuser.cpp +++ b/src/common/ircuser.cpp @@ -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; } diff --git a/src/common/message.cpp b/src/common/message.cpp index 061f16e5..fc50d376 100644 --- a/src/common/message.cpp +++ b/src/common/message.cpp @@ -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 { diff --git a/src/common/quassel.cpp b/src/common/quassel.cpp index c75836a3..9685c24b 100644 --- a/src/common/quassel.cpp +++ b/src/common/quassel.cpp @@ -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")) { diff --git a/src/common/quassel.h b/src/common/quassel.h index 89083655..50ea82a2 100644 --- a/src/common/quassel.h +++ b/src/common/quassel.h @@ -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 diff --git a/src/core/core.cpp b/src/core/core.cpp index 37dfe788..4edff846 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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; diff --git a/src/core/corenetwork.cpp b/src/core/corenetwork.cpp index 30f319ba..51c61888 100644 --- a/src/core/corenetwork.cpp +++ b/src/core/corenetwork.cpp @@ -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. diff --git a/src/core/corenetwork.h b/src/core/corenetwork.h index b67e2c34..d2693587 100644 --- a/src/core/corenetwork.h +++ b/src/core/corenetwork.h @@ -477,7 +477,7 @@ private: int _lastUsedServerIndex; QTimer _pingTimer; - uint _lastPingTime; + qint64 _lastPingTime; uint _pingCount; bool _sendPings; diff --git a/src/core/coresessioneventprocessor.cpp b/src/core/coresessioneventprocessor.cpp index 26f6c6a8..8968cd5e 100644 --- a/src/core/coresessioneventprocessor.cpp +++ b/src/core/coresessioneventprocessor.cpp @@ -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]); diff --git a/src/core/eventstringifier.cpp b/src/core/eventstringifier.cpp index b15545af..c1279ffe 100644 --- a/src/core/eventstringifier.cpp +++ b/src/core/eventstringifier.cpp @@ -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); diff --git a/src/core/postgresqlstorage.cpp b/src/core/postgresqlstorage.cpp index cd6d7bf7..16ab7c39 100644 --- a/src/core/postgresqlstorage.cpp +++ b/src/core/postgresqlstorage.cpp @@ -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 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 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 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 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, diff --git a/src/core/sqlitestorage.cpp b/src/core/sqlitestorage.cpp index f38a948c..e40150c4 100644 --- a/src/core/sqlitestorage.cpp +++ b/src/core/sqlitestorage.cpp @@ -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 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 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 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 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(); -- 2.20.1