Tobias Leupold
Tobias Leupold

Reputation: 1722

QDateTime::secsTo returns the same value for different QDateTime's

I recently wrote a stopwatch and noticed some strange behavior of QDateTime::secsTo. I'm not sure if it's a bug or a feature (or perhaps I only did a crappy implementation ;-).

My stopwatch code can be stripped down to this minimal example to produce the questionable result (at least on Linux using Qt 5.7.1):

StopWatch.h

#ifndef STOPWATCH_H
#define STOPWATCH_H

#include <QDialog>
#include <QDateTime>

class QTimer;

class StopWatch : public QDialog
{
    Q_OBJECT

public:
    explicit StopWatch(QWidget *parent);

private slots:
    void update();

private:
    QTimer *m_timer;
    QDateTime m_targetTime;
};

#endif // STOPWATCH_H

StopWatch.cpp

#include "StopWatch.h"
#include <QDebug>
#include <QTimer>

StopWatch::StopWatch(QWidget *parent) : QDialog(parent)
{
    m_timer = new QTimer(this);
    m_timer->setTimerType(Qt::PreciseTimer);
    connect(m_timer, &QTimer::timeout, this, &StopWatch::update);
    m_targetTime = QDateTime::currentDateTime().addSecs(10);
    m_timer->start(1000);
}

void StopWatch::update()
{
    QDateTime currentDateTime = QDateTime::currentDateTime();
    qint64 secondsLeft = currentDateTime.secsTo(m_targetTime);
    qDebug() << secondsLeft;
}

And here's (a part of) the output:

4
3
2
1
0
0
-1
-2
-3
-4

So here we are: QDateTime::secsTo outputs 0 for the same QDateTime and for a QDateTime one second before.

I worked around this by doing

if (currentDateTime <= m_targetTime) {
    secondsLeft++;
}

but I don't understand the behavoir. Why is this the case?

Upvotes: 2

Views: 1935

Answers (2)

huysentruitw
huysentruitw

Reputation: 28111

It's a rounding issue. The secsTo function doesn't round to the nearest integer, but just drops the decimal part (this is what compilers do by default):

int QTime::secsTo(const QTime &t) const
{
    if (!isValid() || !t.isValid())
        return 0;

    // Truncate milliseconds as we do not want to consider them.
    int ourSeconds = ds() / 1000;
    int theirSeconds = t.ds() / 1000;
    return theirSeconds - ourSeconds;
}

or 4.x version:

int QTime::secsTo(const QTime &t) const
{
    return (t.ds() - ds()) / 1000;
}

So what you probably see is:

 4.8 -> 4
 3.8 -> 3
 2.8 -> 2
 1.8 -> 1
 0.8 -> 0
-0.2 -> 0
-1.2 -> -1
-2.2 -> -2
-3.2 -> -3
-4.2 -> -4

For the expected result, use something like:

qint64 secondsLeft = qRound64(currentDateTime.msecsTo(m_targetTime) / 1000.0);

Upvotes: 2

user2836202
user2836202

Reputation: 639

Looking at the source code http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/tools/qdatetime.cpp

int QTime::secsTo(const QTime &t) const
{
    if (!isValid() || !t.isValid())
        return 0;

    // Truncate milliseconds as we do not want to consider them.
    int ourSeconds = ds() / 1000;
    int theirSeconds = t.ds() / 1000;
    return theirSeconds - ourSeconds;
}

It looks like it takes two positive integers that are under 1000, divides them by 1000, and then subtracts them from each other. If you use mSecsTo(), you will not have this problem.

Upvotes: 4

Related Questions