From 582cbf2d92addb34088830a3de97c08ddb147e30 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 29 Mar 2010 16:54:26 +0200 Subject: Add QDateTime members that operate on 64-bit milliseconds. This complements QDateTime::currentMsecsSinceEpoch. Task-number: QTBUG-9017 Reviewed-by: Denis Dzyubenko --- src/corelib/tools/qdatetime.cpp | 117 ++++++++++++++++++++++++++++----- src/corelib/tools/qdatetime.h | 3 + tests/auto/qdatetime/tst_qdatetime.cpp | 69 +++++++++++++++++++ 3 files changed, 174 insertions(+), 15 deletions(-) diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp index 54a4205..7628dd2 100644 --- a/src/corelib/tools/qdatetime.cpp +++ b/src/corelib/tools/qdatetime.cpp @@ -2315,17 +2315,35 @@ void QDateTime::setTimeSpec(Qt::TimeSpec spec) } } -static uint toTime_tHelper(const QDate &utcDate, const QTime &utcTime) +qint64 toMsecsSinceEpoch_helper(qint64 jd, int msecs) { - int days = QDate(1970, 1, 1).daysTo(utcDate); - int secs = QTime().secsTo(utcTime); - if (days < 0 || (days == 0 && secs < 0)) - return uint(-1); + int days = jd - julianDayFromGregorianDate(1970, 1, 1); + qint64 retval = (qlonglong(days) * MSECS_PER_DAY) + msecs; + return retval; +} - qlonglong retval = (qlonglong(days) * SECS_PER_DAY) + secs; - if (retval >= Q_INT64_C(0xFFFFFFFF)) - return uint(-1); - return uint(retval); +/*! + \since 4.7 + + Returns the datetime as the number of milliseconds that have passed + since 1970-01-01T00:00:00.000, Coordinated Universal Time (Qt::UTC). + + On systems that do not support time zones, this function will + behave as if local time were Qt::UTC. + + The behavior for this function is undefined if the datetime stored in + this object is not valid. However, for all valid dates, this function + returns a unique value. + + \sa toTime_t(), setMsecsSinceEpoch() +*/ +qint64 QDateTime::toMsecsSinceEpoch() const +{ + QDate utcDate; + QTime utcTime; + d->getUTC(utcDate, utcTime); + + return toMsecsSinceEpoch_helper(utcDate.jd, utcTime.ds()); } /*! @@ -2335,16 +2353,63 @@ static uint toTime_tHelper(const QDate &utcDate, const QTime &utcTime) On systems that do not support time zones, this function will behave as if local time were Qt::UTC. - \sa setTime_t() + \note This function returns a 32-bit unsigned integer, so it does not + support dates before 1970, but it does support dates after + 2038-01-19T03:14:06, which may not be valid time_t values. Be careful + when passing those time_t values to system functions, which could + interpret them as negative dates. + + If the date is outside the range 1970-01-01T00:00:00 to + 2106-02-07T06:28:14, this function returns -1 cast to an unsigned integer + (i.e., 0xFFFFFFFF). + + To get an extended range, use toMsecsSinceEpoch(). + + \sa toMsecsSinceEpoch(), setTime_t() */ uint QDateTime::toTime_t() const { - QDate utcDate; - QTime utcTime; - d->getUTC(utcDate, utcTime); + qint64 retval = toMsecsSinceEpoch() / 1000; + if (quint64(retval) >= Q_UINT64_C(0xFFFFFFFF)) + return uint(-1); + return uint(retval); +} - return toTime_tHelper(utcDate, utcTime); +/*! + \since 4.7 + + Sets the date and time given the number of \a mulliseconds that have + passed since 1970-01-01T00:00:00.000, Coordinated Universal Time + (Qt::UTC). On systems that do not support time zones this function + will behave as if local time were Qt::UTC. + + Note that there are possible values for \a msecs that lie outside the + valid range of QDateTime, both negative and positive. The behavior of + this function is undefined for those values. + + \sa toMsecsSinceEpoch(), setTime_t() +*/ +void QDateTime::setMsecsSinceEpoch(qint64 msecs) +{ + detach(); + + QDateTimePrivate::Spec oldSpec = d->spec; + + int ddays = msecs / MSECS_PER_DAY; + msecs %= MSECS_PER_DAY; + if (msecs < 0) { + // negative + --ddays; + msecs += MSECS_PER_DAY; + } + + d->date = QDate(1970, 1, 1).addDays(ddays); + d->time = QTime().addMSecs(msecs); + d->spec = QDateTimePrivate::UTC; + + if (oldSpec != QDateTimePrivate::UTC) + d->spec = d->getLocal(d->date, d->time); } /*! @@ -3088,6 +3153,27 @@ QDateTime QDateTime::fromTime_t(uint seconds) } /*! + \since 4.7 + + Returns a datetime whose date and time are the number of milliseconds \a msec + that have passed since 1970-01-01T00:00:00.000, Coordinated Universal + Time (Qt::UTC). On systems that do not support time zones, the time + will be set as if local time were Qt::UTC. + + Note that there are possible values for \a msecs that lie outside the valid + range of QDateTime, both negative and positive. The behavior of this + function is undefined for those values. + + \sa toTime_t(), setTime_t() +*/ +QDateTime QDateTime::fromMsecsSinceEpoch(qint64 msecs) +{ + QDateTime d; + d.setMsecsSinceEpoch(msecs); + return d; +} + +/*! \since 4.4 \internal @@ -3841,7 +3927,8 @@ static QDateTimePrivate::Spec utcToLocal(QDate &date, QTime &time) { QDate fakeDate = adjustDate(date); - time_t secsSince1Jan1970UTC = toTime_tHelper(fakeDate, time); + // won't overflow because of fakeDate + time_t secsSince1Jan1970UTC = toMsecsSinceEpoch_helper(fakeDate.toJulianDay(), QTime().msecsTo(time)) / 1000; tm *brokenDown = 0; #if defined(Q_OS_WINCE) diff --git a/src/corelib/tools/qdatetime.h b/src/corelib/tools/qdatetime.h index ef5968e..248793b 100644 --- a/src/corelib/tools/qdatetime.h +++ b/src/corelib/tools/qdatetime.h @@ -230,10 +230,12 @@ public: QDate date() const; QTime time() const; Qt::TimeSpec timeSpec() const; + qint64 toMsecsSinceEpoch() const; uint toTime_t() const; void setDate(const QDate &date); void setTime(const QTime &time); void setTimeSpec(Qt::TimeSpec spec); + void setMsecsSinceEpoch(qint64 msecs); void setTime_t(uint secsSince1Jan1970UTC); #ifndef QT_NO_DATESTRING QString toString(Qt::DateFormat f = Qt::TextDate) const; @@ -267,6 +269,7 @@ public: static QDateTime fromString(const QString &s, const QString &format); #endif static QDateTime fromTime_t(uint secsSince1Jan1970UTC); + static QDateTime fromMsecsSinceEpoch(qint64 msecs); static qint64 currentMsecsSinceEpoch(); #ifdef QT3_SUPPORT diff --git a/tests/auto/qdatetime/tst_qdatetime.cpp b/tests/auto/qdatetime/tst_qdatetime.cpp index a6b9a5f..b04a1b5 100644 --- a/tests/auto/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/qdatetime/tst_qdatetime.cpp @@ -83,6 +83,8 @@ private slots: void setTime(); void setTimeSpec(); void setTime_t(); + void setMsecsSinceEpoch_data(); + void setMsecsSinceEpoch(); void toString_enumformat(); void toString_strformat_data(); void toString_strformat(); @@ -437,6 +439,71 @@ void tst_QDateTime::setTime_t() } } +void tst_QDateTime::setMsecsSinceEpoch_data() +{ + QTest::addColumn("msecs"); + QTest::addColumn("utc"); + QTest::addColumn("european"); + + QTest::newRow("zero") + << Q_INT64_C(0) + << QDateTime(QDate(1970, 1, 1), QTime(), Qt::UTC) + << QDateTime(QDate(1970, 1, 1), QTime(1, 0)); + QTest::newRow("-1") + << Q_INT64_C(-1) + << QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999), Qt::UTC) + << QDateTime(QDate(1970, 1, 1), QTime(0, 59, 59, 999)); + QTest::newRow("123456789") + << Q_INT64_C(123456789) + << QDateTime(QDate(1970, 1, 2), QTime(10, 17, 36, 789), Qt::UTC) + << QDateTime(QDate(1970, 1, 2), QTime(11, 17, 36, 789), Qt::LocalTime); + QTest::newRow("-123456789") + << Q_INT64_C(-123456789) + << QDateTime(QDate(1969, 12, 30), QTime(13, 42, 23, 211), Qt::UTC) + << QDateTime(QDate(1969, 12, 30), QTime(14, 42, 23, 211), Qt::LocalTime); + QTest::newRow("non-time_t") + << (Q_INT64_C(1000) << 32) + << QDateTime(QDate(2106, 2, 7), QTime(6, 28, 16), Qt::UTC) + << QDateTime(QDate(2106, 2, 7), QTime(7, 28, 16)); + QTest::newRow("very-large") + << (Q_INT64_C(123456) << 32) + << QDateTime(QDate(18772, 8, 15), QTime(1, 8, 14, 976), Qt::UTC) + << QDateTime(QDate(18772, 8, 15), QTime(3, 8, 14, 976)); + QTest::newRow("min_date") // julian day 0 is an invalid date for QDate + << Q_INT64_C(-210866716800000) + << QDateTime(QDate::fromJulianDay(1), QTime(), Qt::UTC) + << QDateTime(QDate::fromJulianDay(1), QTime(1, 0)); + QTest::newRow("max_date") // technically jd is unsigned, but fromJulianDay takes int + << Q_INT64_C(185331720376799999) + << QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(21, 59, 59, 999), Qt::UTC) + << QDateTime(QDate::fromJulianDay(0x7fffffff), QTime(23, 59, 59, 999)); +} + +void tst_QDateTime::setMsecsSinceEpoch() +{ + QFETCH(qint64, msecs); + QFETCH(QDateTime, utc); + QFETCH(QDateTime, european); + + QDateTime dt; + dt.setTimeSpec(Qt::UTC); + dt.setMsecsSinceEpoch(msecs); + + QCOMPARE(dt, utc); + if (europeanTimeZone) { + QCOMPARE(dt.toLocalTime(), european); + } + + QCOMPARE(dt.toMsecsSinceEpoch(), msecs); + + if (quint64(msecs / 1000) < 0xFFFFFFFF) { + QCOMPARE(qint64(dt.toTime_t()), msecs / 1000); + } + + QDateTime reference(QDate(1970, 1, 1), QTime(), Qt::UTC); + QCOMPARE(dt, reference.addMSecs(msecs)); +} + void tst_QDateTime::toString_enumformat() { QDateTime dt1(QDate(1995, 5, 20), QTime(12, 34, 56)); @@ -953,6 +1020,8 @@ void tst_QDateTime::currentDateTimeUtc2() // and finally, the time_t should equal our number QCOMPARE(qint64(utc.toTime_t()), msec / 1000); QCOMPARE(qint64(local.toTime_t()), msec / 1000); + QCOMPARE(utc.toMsecsSinceEpoch(), msec); + QCOMPARE(local.toMsecsSinceEpoch(), msec); } void tst_QDateTime::toTime_t_data() -- cgit v0.12