Check value is in range when setting a QDateTime
Previously, a QDate representing more than about 0.3 gigayears before or after the epoch would overflow the millisecond count and produce a "valid" date-time that didn't represent the date and time passed to its constructor. Changed to detect such overflow and produce an invalid date-time instead, if it happens. Corrected some tests that wrongly expected to be able to represent extreme date-time values with every time-spec. The (milli)seconds since epoch are from UTC's epoch, so converting to another offset, zone or local time may give a value outside the actual range. Added some tests for the actual exact bounds. Task-number: QTBUG-68855 Change-Id: I866a4974aeb54bba92dbe7eab0a440baf02124f0 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
This commit is contained in:
parent
83bff8951a
commit
cb0ecd6b6d
@ -55,6 +55,7 @@
|
||||
#include "private/qcore_mac_p.h"
|
||||
#endif
|
||||
#include "private/qgregoriancalendar_p.h"
|
||||
#include "private/qnumeric_p.h"
|
||||
#include "private/qstringiterator_p.h"
|
||||
#if QT_CONFIG(timezone)
|
||||
#include "private/qtimezoneprivate_p.h"
|
||||
@ -2977,10 +2978,20 @@ static void setDateTime(QDateTimeData &d, QDate date, QTime time)
|
||||
ds = useTime.msecsSinceStartOfDay();
|
||||
newStatus |= QDateTimePrivate::ValidTime;
|
||||
}
|
||||
Q_ASSERT(ds < MSECS_PER_DAY);
|
||||
// Only the later parts of the very first day are representable - its start
|
||||
// would overflow - so get ds the same side of 0 as days:
|
||||
if (days < 0 && ds > 0) {
|
||||
days++;
|
||||
ds -= MSECS_PER_DAY;
|
||||
}
|
||||
|
||||
// Set msecs serial value
|
||||
qint64 msecs = (days * MSECS_PER_DAY) + ds;
|
||||
if (d.isShort()) {
|
||||
// Check in representable range:
|
||||
qint64 msecs = 0;
|
||||
if (mul_overflow(days, std::integral_constant<qint64, MSECS_PER_DAY>(), &msecs)
|
||||
|| add_overflow(msecs, qint64(ds), &msecs)) {
|
||||
newStatus = QDateTimePrivate::StatusFlags{};
|
||||
} else if (d.isShort()) {
|
||||
// let's see if we can keep this short
|
||||
if (msecsCanBeSmall(msecs)) {
|
||||
// yes, we can
|
||||
@ -3905,8 +3916,8 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
|
||||
status = mergeDaylightStatus(status, QDateTimePrivate::StandardTime);
|
||||
d->m_offsetFromUtc = d->m_timeZone.d->standardTimeOffset(msecs);
|
||||
}
|
||||
msecs = msecs + (d->m_offsetFromUtc * 1000);
|
||||
status |= QDateTimePrivate::ValidWhenMask;
|
||||
if (!add_overflow(msecs, qint64(d->m_offsetFromUtc * 1000), &msecs))
|
||||
status |= QDateTimePrivate::ValidWhenMask;
|
||||
#endif // timezone
|
||||
break;
|
||||
case Qt::LocalTime: {
|
||||
|
@ -755,17 +755,22 @@ void tst_QDateTime::fromMSecsSinceEpoch()
|
||||
void tst_QDateTime::fromSecsSinceEpoch()
|
||||
{
|
||||
const qint64 maxSeconds = std::numeric_limits<qint64>::max() / 1000;
|
||||
const QDateTime early = QDateTime::fromSecsSinceEpoch(-maxSeconds, Qt::UTC);
|
||||
const QDateTime late = QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::UTC);
|
||||
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1).isValid());
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(-maxSeconds).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1).isValid());
|
||||
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::UTC).isValid());
|
||||
QVERIFY(late.isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, Qt::UTC).isValid());
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(-maxSeconds, Qt::UTC).isValid());
|
||||
QVERIFY(early.isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1, Qt::UTC).isValid());
|
||||
|
||||
// Local time: need to adjust for its zone offset
|
||||
const qint64 last = maxSeconds - qMax(late.addYears(-1).toLocalTime().offsetFromUtc(), 0);
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(last).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(last + 1).isValid());
|
||||
const qint64 first = -maxSeconds - qMin(early.addYears(1).toLocalTime().offsetFromUtc(), 0);
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(first).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(first - 1).isValid());
|
||||
|
||||
// Use an offset for which .toUTC()'s return would flip the validity:
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::OffsetFromUTC, 7200).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, Qt::OffsetFromUTC, -7200).isValid());
|
||||
@ -775,10 +780,10 @@ void tst_QDateTime::fromSecsSinceEpoch()
|
||||
#if QT_CONFIG(timezone)
|
||||
// As for offset, use zones each side of UTC:
|
||||
const QTimeZone west("UTC-02:00"), east("UTC+02:00");
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds, east).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, west).isValid());
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(-maxSeconds, west).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1, east).isValid());
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds, west).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, east).isValid());
|
||||
QVERIFY(QDateTime::fromSecsSinceEpoch(-maxSeconds, east).isValid());
|
||||
QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1, west).isValid());
|
||||
#endif // timezone
|
||||
}
|
||||
|
||||
@ -3759,6 +3764,21 @@ void tst_QDateTime::range() const
|
||||
int(QDateTime::YearRange::First));
|
||||
QCOMPARE(QDateTime::fromMSecsSinceEpoch(Bounds::max() - 1, Qt::UTC).date().year(),
|
||||
int(QDateTime::YearRange::Last));
|
||||
constexpr qint64 millisPerDay = 24 * 3600 * 1000;
|
||||
constexpr qint64 wholeDays = Bounds::max() / millisPerDay;
|
||||
constexpr qint64 millisRemainder = Bounds::max() % millisPerDay;
|
||||
QVERIFY(QDateTime(QDate(1970, 1, 1).addDays(wholeDays),
|
||||
QTime::fromMSecsSinceStartOfDay(millisRemainder),
|
||||
Qt::UTC).isValid());
|
||||
QVERIFY(!QDateTime(QDate(1970, 1, 1).addDays(wholeDays),
|
||||
QTime::fromMSecsSinceStartOfDay(millisRemainder + 1),
|
||||
Qt::UTC).isValid());
|
||||
QVERIFY(QDateTime(QDate(1970, 1, 1).addDays(-wholeDays - 1),
|
||||
QTime::fromMSecsSinceStartOfDay(3600 * 24000 - millisRemainder - 1),
|
||||
Qt::UTC).isValid());
|
||||
QVERIFY(!QDateTime(QDate(1970, 1, 1).addDays(-wholeDays - 1),
|
||||
QTime::fromMSecsSinceStartOfDay(3600 * 24000 - millisRemainder - 2),
|
||||
Qt::UTC).isValid());
|
||||
}
|
||||
|
||||
void tst_QDateTime::macTypes()
|
||||
|
Loading…
Reference in New Issue
Block a user