From 55f5b29d7975d7e642ec258e44ca23726e1dfa0d Mon Sep 17 00:00:00 2001 From: John Layt Date: Mon, 11 Feb 2013 13:44:37 +0000 Subject: QDateTime - Add QTimeZone support Add support to QDateTime for time zones using the new QTimeZone class. [ChangeLog][QtCore][QDateTime] Add support for a new Qt::TimeZone spec to be used with QTimeZone to define times in a specific time zone. Change-Id: I21bfa52a8ba8989b55bb74e025d1f2b2b623b2a7 Reviewed-by: Thiago Macieira --- .../auto/corelib/tools/qdatetime/tst_qdatetime.cpp | 200 +++++++++++++++++++++ tests/auto/corelib/tools/qlocale/tst_qlocale.cpp | 12 ++ tests/benchmarks/corelib/tools/qdatetime/main.cpp | 91 +++++++++- 3 files changed, 302 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp index b51fa0095c..a282fcabd3 100644 --- a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp @@ -145,6 +145,7 @@ private slots: void isDaylightTime() const; void daylightTransitions() const; + void timeZones() const; private: bool europeanTimeZone; @@ -547,6 +548,8 @@ void tst_QDateTime::setMSecsSinceEpoch() dt.setMSecsSinceEpoch(msecs); QCOMPARE(dt, utc); + QCOMPARE(dt.date(), utc.date()); + QCOMPARE(dt.time(), utc.time()); QCOMPARE(dt.timeSpec(), Qt::UTC); if (europeanTimeZone) { @@ -561,6 +564,23 @@ void tst_QDateTime::setMSecsSinceEpoch() if (msecs != std::numeric_limits::max()) QCOMPARE(localDt, utc); QCOMPARE(localDt.timeSpec(), Qt::LocalTime); + + // Compare result for LocalTime to TimeZone + QTimeZone europe("Europe/Oslo"); + QDateTime dt2; + dt2.setTimeZone(europe); + dt2.setMSecsSinceEpoch(msecs); + QCOMPARE(dt2.date(), european.date()); +#ifdef Q_OS_MAC + // NSTimeZone doesn't apply DST to high values + if (msecs < (Q_INT64_C(123456) << 32)) +#else + // Linux and Win are OK except when they overflow + if (msecs != std::numeric_limits::max()) +#endif + QCOMPARE(dt2.time(), european.time()); + QCOMPARE(dt2.timeSpec(), Qt::TimeZone); + QCOMPARE(dt2.timeZone(), europe); } QCOMPARE(dt.toMSecsSinceEpoch(), msecs); @@ -2107,6 +2127,12 @@ void tst_QDateTime::offsetFromUtc() } else { QSKIP("You must test using Central European (CET/CEST) time zone, e.g. TZ=Europe/Oslo"); } + + QDateTime dt5(QDate(2013, 1, 1), QTime(0, 0, 0), QTimeZone("Pacific/Auckland")); + QCOMPARE(dt5.offsetFromUtc(), 46800); + + QDateTime dt6(QDate(2013, 6, 1), QTime(0, 0, 0), QTimeZone("Pacific/Auckland")); + QCOMPARE(dt6.offsetFromUtc(), 43200); } void tst_QDateTime::setOffsetFromUtc() @@ -2232,6 +2258,17 @@ void tst_QDateTime::timeZoneAbbreviation() } else { QSKIP("You must test using Central European (CET/CEST) time zone, e.g. TZ=Europe/Oslo"); } + + QDateTime dt5(QDate(2013, 1, 1), QTime(0, 0, 0), QTimeZone("Europe/Berlin")); +#ifdef Q_OS_WIN + QEXPECT_FAIL("", "QTimeZone windows backend only returns long name", Continue); +#endif + QCOMPARE(dt5.timeZoneAbbreviation(), QString("CET")); + QDateTime dt6(QDate(2013, 6, 1), QTime(0, 0, 0), QTimeZone("Europe/Berlin")); +#ifdef Q_OS_WIN + QEXPECT_FAIL("", "QTimeZone windows backend only returns long name", Continue); +#endif + QCOMPARE(dt6.timeZoneAbbreviation(), QString("CEST")); } void tst_QDateTime::getDate() @@ -2717,5 +2754,168 @@ void tst_QDateTime::daylightTransitions() const } } +void tst_QDateTime::timeZones() const +{ + QTimeZone nzTz = QTimeZone("Pacific/Auckland"); + + // During Standard Time NZ is +12:00 + QDateTime utcStd(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime nzStd(QDate(2012, 6, 1), QTime(12, 0, 0), nzTz); + + QCOMPARE(nzStd.isValid(), true); + QCOMPARE(nzStd.timeSpec(), Qt::TimeZone); + QCOMPARE(nzStd.date(), QDate(2012, 6, 1)); + QCOMPARE(nzStd.time(), QTime(12, 0, 0)); + QVERIFY(nzStd.timeZone() == nzTz); + QCOMPARE(nzStd.timeZone().id(), QByteArray("Pacific/Auckland")); + QCOMPARE(nzStd.offsetFromUtc(), 43200); + QCOMPARE(nzStd.isDaylightTime(), false); + QCOMPARE(nzStd.toMSecsSinceEpoch(), utcStd.toMSecsSinceEpoch()); + + // During Daylight Time NZ is +13:00 + QDateTime utcDst(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime nzDst(QDate(2012, 1, 1), QTime(13, 0, 0), nzTz); + + QCOMPARE(nzDst.isValid(), true); + QCOMPARE(nzDst.date(), QDate(2012, 1, 1)); + QCOMPARE(nzDst.time(), QTime(13, 0, 0)); + QCOMPARE(nzDst.offsetFromUtc(), 46800); + QCOMPARE(nzDst.isDaylightTime(), true); + QCOMPARE(nzDst.toMSecsSinceEpoch(), utcDst.toMSecsSinceEpoch()); + + QDateTime utc = nzStd.toUTC(); + QCOMPARE(utc.date(), utcStd.date()); + QCOMPARE(utc.time(), utcStd.time()); + + utc = nzDst.toUTC(); + QCOMPARE(utc.date(), utcDst.date()); + QCOMPARE(utc.time(), utcDst.time()); + + // Sydney is 2 hours behind New Zealand + QTimeZone ausTz = QTimeZone("Australia/Sydney"); + QDateTime aus = nzStd.toTimeZone(ausTz); + QCOMPARE(aus.date(), QDate(2012, 6, 1)); + QCOMPARE(aus.time(), QTime(10, 0, 0)); + + QDateTime dt1(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC); + QCOMPARE(dt1.timeSpec(), Qt::UTC); + dt1.setTimeZone(nzTz); + QCOMPARE(dt1.timeSpec(), Qt::TimeZone); + QCOMPARE(dt1.date(), QDate(2012, 6, 1)); + QCOMPARE(dt1.time(), QTime(0, 0, 0)); + QCOMPARE(dt1.timeZone(), nzTz); + + QDateTime dt2 = QDateTime::fromTime_t(1338465600, nzTz); + QCOMPARE(dt2.date(), dt1.date()); + QCOMPARE(dt2.time(), dt1.time()); + QCOMPARE(dt2.timeSpec(), dt1.timeSpec()); + QCOMPARE(dt2.timeZone(), dt1.timeZone()); + + QDateTime dt3 = QDateTime::fromMSecsSinceEpoch(1338465600000, nzTz); + QCOMPARE(dt3.date(), dt1.date()); + QCOMPARE(dt3.time(), dt1.time()); + QCOMPARE(dt3.timeSpec(), dt1.timeSpec()); + QCOMPARE(dt3.timeZone(), dt1.timeZone()); + + // Check datastream serialises the time zone + QByteArray tmp; + { + QDataStream ds(&tmp, QIODevice::WriteOnly); + ds << dt1; + } + QDateTime dt4; + { + QDataStream ds(&tmp, QIODevice::ReadOnly); + ds >> dt4; + } + QCOMPARE(dt4, dt1); + QCOMPARE(dt4.timeSpec(), Qt::TimeZone); + QCOMPARE(dt4.timeZone(), nzTz); + + // Check handling of transition times + QTimeZone cet("Europe/Oslo"); + + // Standard Time to Daylight Time 2013 on 2013-03-31 is 2:00 local time / 1:00 UTC + qint64 stdToDstMSecs = 1364691600000; + + // Test MSecs to local + // - Test 1 msec before tran = 01:59:59.999 + QDateTime beforeDst = QDateTime::fromMSecsSinceEpoch(stdToDstMSecs - 1, cet); + QCOMPARE(beforeDst.date(), QDate(2013, 3, 31)); + QCOMPARE(beforeDst.time(), QTime(1, 59, 59, 999)); + // - Test at tran = 03:00:00 + QDateTime atDst = QDateTime::fromMSecsSinceEpoch(stdToDstMSecs, cet); + QCOMPARE(atDst.date(), QDate(2013, 3, 31)); + QCOMPARE(atDst.time(), QTime(3, 0, 0)); + + // Test local to MSecs + // - Test 1 msec before tran = 01:59:59.999 + beforeDst = QDateTime(QDate(2013, 3, 31), QTime(1, 59, 59, 999), cet); + QCOMPARE(beforeDst.toMSecsSinceEpoch(), stdToDstMSecs - 1); + // - Test at tran = 03:00:00 + atDst = QDateTime(QDate(2013, 3, 31), QTime(3, 0, 0), cet); + QCOMPARE(atDst.toMSecsSinceEpoch(), stdToDstMSecs); + // - Test transition hole, setting 03:00:00 is valid + atDst = QDateTime(QDate(2013, 3, 31), QTime(3, 0, 0), cet); + QVERIFY(atDst.isValid()); + QCOMPARE(atDst.date(), QDate(2013, 3, 31)); + QCOMPARE(atDst.time(), QTime(3, 0, 0)); + QCOMPARE(atDst.toMSecsSinceEpoch(), stdToDstMSecs); + // - Test transition hole, setting 02:00:00 is invalid + atDst = QDateTime(QDate(2013, 3, 31), QTime(2, 0, 0), cet); + QVERIFY(!atDst.isValid()); + QCOMPARE(atDst.date(), QDate(2013, 3, 31)); + QCOMPARE(atDst.time(), QTime(2, 0, 0)); + // - Test transition hole, setting 02:59:59.999 is invalid + atDst = QDateTime(QDate(2013, 3, 31), QTime(2, 59, 59, 999), cet); + QVERIFY(!atDst.isValid()); + QCOMPARE(atDst.date(), QDate(2013, 3, 31)); + QCOMPARE(atDst.time(), QTime(2, 59, 59, 999)); + + // Standard Time to Daylight Time 2013 on 2013-10-27 is 3:00 local time / 1:00 UTC + qint64 dstToStdMSecs = 1382835600000; + + // Test MSecs to local + // - Test 1 hour before tran = 02:00:00 local first occurrence + QDateTime hourBeforeStd = QDateTime::fromMSecsSinceEpoch(dstToStdMSecs - 3600000, cet); + QCOMPARE(hourBeforeStd.date(), QDate(2013, 10, 27)); + QCOMPARE(hourBeforeStd.time(), QTime(2, 0, 0)); + // - Test 1 msec before tran = 02:59:59.999 local first occurrence + QDateTime msecBeforeStd = QDateTime::fromMSecsSinceEpoch(dstToStdMSecs - 1, cet); + QCOMPARE(msecBeforeStd.date(), QDate(2013, 10, 27)); + QCOMPARE(msecBeforeStd.time(), QTime(2, 59, 59, 999)); + // - Test at tran = 03:00:00 local becomes 02:00:00 local second occurrence + QDateTime atStd = QDateTime::fromMSecsSinceEpoch(dstToStdMSecs, cet); + QCOMPARE(atStd.date(), QDate(2013, 10, 27)); + QCOMPARE(atStd.time(), QTime(2, 0, 0)); + // - Test 59 mins after tran = 02:59:59.999 local second occurrence + QDateTime afterStd = QDateTime::fromMSecsSinceEpoch(dstToStdMSecs + 3600000 -1, cet); + QCOMPARE(afterStd.date(), QDate(2013, 10, 27)); + QCOMPARE(afterStd.time(), QTime(2, 59, 59, 999)); + // - Test 1 hour after tran = 03:00:00 local + QDateTime hourAfterStd = QDateTime::fromMSecsSinceEpoch(dstToStdMSecs + 3600000, cet); + QCOMPARE(hourAfterStd.date(), QDate(2013, 10, 27)); + QCOMPARE(hourAfterStd.time(), QTime(3, 00, 00)); + + // Test local to MSecs + // - Test first occurrence 02:00:00 = 1 hour before tran + hourBeforeStd = QDateTime(QDate(2013, 10, 27), QTime(2, 0, 0), cet); + QCOMPARE(hourBeforeStd.toMSecsSinceEpoch(), dstToStdMSecs - 3600000); + // - Test first occurrence 02:59:59.999 = 1 msec before tran + msecBeforeStd = QDateTime(QDate(2013, 10, 27), QTime(2, 59, 59, 999), cet); + QCOMPARE(msecBeforeStd.toMSecsSinceEpoch(), dstToStdMSecs - 1); + // - Test second occurrence 02:00:00 = at tran + atStd = QDateTime(QDate(2013, 10, 27), QTime(2, 0, 0), cet); + QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue); + QCOMPARE(atStd.toMSecsSinceEpoch(), dstToStdMSecs); + // - Test second occurrence 03:00:00 = 59 mins after tran + afterStd = QDateTime(QDate(2013, 10, 27), QTime(2, 59, 59, 999), cet); + QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue); + QCOMPARE(afterStd.toMSecsSinceEpoch(), dstToStdMSecs + 3600000 - 1); + // - Test 03:00:00 = 1 hour after tran + hourAfterStd = QDateTime(QDate(2013, 10, 27), QTime(3, 0, 0), cet); + QCOMPARE(hourAfterStd.toMSecsSinceEpoch(), dstToStdMSecs + 3600000); +} + QTEST_APPLESS_MAIN(tst_QDateTime) #include "tst_qdatetime.moc" diff --git a/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp b/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp index ea0e90a503..8411cfe29b 100644 --- a/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp @@ -1303,6 +1303,18 @@ void tst_QLocale::formatTimeZone() QSKIP("You must test using Central European (CET/CEST) time zone, e.g. TZ=Europe/Oslo"); } + QDateTime dt6(QDate(2013, 1, 1), QTime(0, 0, 0), QTimeZone("Europe/Berlin")); +#ifdef Q_OS_WIN + QEXPECT_FAIL("", "QTimeZone windows backend only returns long name", Continue); +#endif + QCOMPARE(enUS.toString(dt6, "t"), QString("CET")); + + QDateTime dt7(QDate(2013, 6, 1), QTime(0, 0, 0), QTimeZone("Europe/Berlin")); +#ifdef Q_OS_WIN + QEXPECT_FAIL("", "QTimeZone windows backend only returns long name", Continue); +#endif + QCOMPARE(enUS.toString(dt7, "t"), QString("CEST")); + // Current datetime should return current abbreviation QCOMPARE(enUS.toString(QDateTime::currentDateTime(), "t"), QDateTime::currentDateTime().timeZoneAbbreviation()); diff --git a/tests/benchmarks/corelib/tools/qdatetime/main.cpp b/tests/benchmarks/corelib/tools/qdatetime/main.cpp index 9da6611ec0..8550ef6cb2 100644 --- a/tests/benchmarks/corelib/tools/qdatetime/main.cpp +++ b/tests/benchmarks/corelib/tools/qdatetime/main.cpp @@ -39,7 +39,8 @@ ** ****************************************************************************/ -#include +#include +#include #include #include @@ -71,21 +72,27 @@ private Q_SLOTS: void toMSecsSinceEpoch(); void toMSecsSinceEpoch1950(); void toMSecsSinceEpoch2050(); + void toMSecsSinceEpochTz(); + void toMSecsSinceEpoch1950Tz(); + void toMSecsSinceEpoch2050Tz(); void toTime_t(); void setDate(); void setTime(); void setTimeSpec(); void setOffsetFromUtc(); void setMSecsSinceEpoch(); + void setMSecsSinceEpochTz(); void setTime_t(); void toString(); void toStringTextFormat(); void toStringIsoFormat(); void addDays(); + void addDaysTz(); void addMonths(); void addYears(); void addSecs(); void addMSecs(); + void addMSecsTz(); void toTimeSpec(); void toOffsetFromUtc(); void daysTo(); @@ -106,6 +113,7 @@ private Q_SLOTS: void fromTime_t(); void fromMSecsSinceEpoch(); void fromMSecsSinceEpochUtc(); + void fromMSecsSinceEpochTz(); }; void tst_QDateTime::create() @@ -228,6 +236,42 @@ void tst_QDateTime::toMSecsSinceEpoch2050() } } +void tst_QDateTime::toMSecsSinceEpochTz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QList list; + for (int jd = JULIAN_DAY_2010; jd < JULIAN_DAY_2020; ++jd) + list.append(QDateTime(QDate::fromJulianDay(jd), QTime::fromMSecsSinceStartOfDay(0), cet)); + QBENCHMARK { + foreach (const QDateTime &test, list) + qint64 result = test.toMSecsSinceEpoch(); + } +} + +void tst_QDateTime::toMSecsSinceEpoch1950Tz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QList list; + for (int jd = JULIAN_DAY_1950; jd < JULIAN_DAY_1960; ++jd) + list.append(QDateTime(QDate::fromJulianDay(jd), QTime::fromMSecsSinceStartOfDay(0), cet)); + QBENCHMARK { + foreach (const QDateTime &test, list) + qint64 result = test.toMSecsSinceEpoch(); + } +} + +void tst_QDateTime::toMSecsSinceEpoch2050Tz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QList list; + for (int jd = JULIAN_DAY_2050; jd < JULIAN_DAY_2060; ++jd) + list.append(QDateTime(QDate::fromJulianDay(jd), QTime::fromMSecsSinceStartOfDay(0), cet)); + QBENCHMARK { + foreach (const QDateTime &test, list) + qint64 result = test.toMSecsSinceEpoch(); + } +} + void tst_QDateTime::toTime_t() { QList list; @@ -295,6 +339,18 @@ void tst_QDateTime::setMSecsSinceEpoch() } } +void tst_QDateTime::setMSecsSinceEpochTz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QList list; + for (int jd = JULIAN_DAY_2010; jd < JULIAN_DAY_2020; ++jd) + list.append(QDateTime(QDate::fromJulianDay(jd), QTime::fromMSecsSinceStartOfDay(0), cet)); + QBENCHMARK { + foreach (QDateTime test, list) + test.setMSecsSinceEpoch((JULIAN_DAY_2010 + 180) * MSECS_PER_DAY); + } +} + void tst_QDateTime::setTime_t() { time_t secs = time_t(JULIAN_DAY_2010 + 180) * SECS_PER_DAY; @@ -351,6 +407,18 @@ void tst_QDateTime::addDays() } } +void tst_QDateTime::addDaysTz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QList list; + for (int jd = JULIAN_DAY_2010; jd < JULIAN_DAY_2020; ++jd) + list.append(QDateTime(QDate::fromJulianDay(jd), QTime::fromMSecsSinceStartOfDay(0), cet)); + QBENCHMARK { + foreach (const QDateTime &test, list) + QDateTime result = test.addDays(1); + } +} + void tst_QDateTime::addMonths() { QList list; @@ -395,6 +463,18 @@ void tst_QDateTime::addMSecs() } } +void tst_QDateTime::addMSecsTz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QList list; + for (int jd = JULIAN_DAY_2010; jd < JULIAN_DAY_2020; ++jd) + list.append(QDateTime(QDate::fromJulianDay(jd), QTime::fromMSecsSinceStartOfDay(0), cet)); + QBENCHMARK { + foreach (const QDateTime &test, list) + QDateTime result = test.addMSecs(1); + } +} + void tst_QDateTime::toTimeSpec() { QList list; @@ -601,6 +681,15 @@ void tst_QDateTime::fromMSecsSinceEpochUtc() } } +void tst_QDateTime::fromMSecsSinceEpochTz() +{ + QTimeZone cet = QTimeZone("Europe/Oslo"); + QBENCHMARK { + for (int jd = JULIAN_DAY_2010; jd < JULIAN_DAY_2020; ++jd) + QDateTime test = QDateTime::fromMSecsSinceEpoch(jd * MSECS_PER_DAY, cet); + } +} + QTEST_MAIN(tst_QDateTime) #include "main.moc" -- cgit v1.2.3