summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Layt <jlayt@kde.org>2013-09-27 14:15:31 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-27 21:47:55 +0200
commit475cbed2446d0e3595e7b8ab71dcbc1ae5f59bcf (patch)
tree7f570d3f4bb8c3c2d9ff507cf996193a1881b447
parent3705c1263d4d2232d5527361692d25a8519c222b (diff)
QDateTime - Fix round-trip of second occurrence times
At the Daylight Tme to Standard Time transition, the local time repeats itself, i.e. 2am occurs twice. Qt's behavior when setting this using the local time is ambiguous, as it depends on the system implementation of mktime, which behaves differently on different platforms. Currently this behavior remains undefined. When setting using an msecs or time_t value however we can determine the correct instance to use and cache it to ensure that any conversion back from local time to msecs is performed consistantly on all platforms. Note that caching this value will result in any calculations being wrong should the system time zone change, or its rules change. This will be fixed in Qt 5.3 when the system time zone change signal is implemented and QDateTime switches to using QTimeZone instead of mktime to provide consistnt behavior across platforms. The QTimeZone spec does not require this fix as it already caches the correct offset in setMSecsFromEpoch(). Change-Id: I799588db474e744a6d81e80f6a0442920569ebd3 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/tools/qdatetime.cpp54
-rw-r--r--src/corelib/tools/qdatetime_p.h16
-rw-r--r--tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp30
3 files changed, 61 insertions, 39 deletions
diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp
index d64d929d5a..4839560c51 100644
--- a/src/corelib/tools/qdatetime.cpp
+++ b/src/corelib/tools/qdatetime.cpp
@@ -2224,7 +2224,7 @@ static QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus)
#endif // Q_OS_WINCE
}
-// Calls the platform variant of mktime for the given date and time,
+// Calls the platform variant of mktime for the given date, time and daylightStatus,
// and updates the date, time, daylightStatus and abbreviation with the returned values
// If the date falls outside the 1970 to 2037 range supported by mktime / time_t
// then null date/time will be returned, you should adjust the date first if
@@ -2288,7 +2288,10 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat
local.tm_year = yy - 1900;
local.tm_wday = 0;
local.tm_yday = 0;
- local.tm_isdst = -1;
+ if (daylightStatus)
+ local.tm_isdst = int(*daylightStatus);
+ else
+ local.tm_isdst = -1;
#if defined(Q_OS_WIN)
int hh = local.tm_hour;
#endif // Q_OS_WIN
@@ -2481,7 +2484,7 @@ static bool epochMSecsToLocalTime(qint64 msecs, QDate *localDate, QTime *localTi
// Convert a LocalTime expressed in local msecs encoding into a UTC epoch msecs
// Optionally populate the returned values from mktime for the adjusted local
-// date and time and daylight status
+// date and time and daylight status. Uses daylightStatus in calculation if populated.
static qint64 localMSecsToEpochMSecs(qint64 localMsecs, QDate *localDate = 0, QTime *localTime = 0,
QDateTimePrivate::DaylightStatus *daylightStatus = 0,
QString *abbreviation = 0, bool *ok = 0)
@@ -2614,6 +2617,7 @@ void QDateTimePrivate::setTimeSpec(Qt::TimeSpec spec, int offsetSeconds)
{
clearValidDateTime();
clearTimeZoneCached();
+ clearSetToDaylightStatus();
#ifndef QT_BOOTSTRAPPED
m_timeZone = QTimeZone();
@@ -2688,6 +2692,30 @@ void QDateTimePrivate::getDateTime(QDate *date, QTime *time) const
*time = QTime();
}
+// Set the Daylight Status if LocalTime set via msecs
+void QDateTimePrivate::setDaylightStatus(QDateTimePrivate::DaylightStatus status)
+{
+ if (status == DaylightTime) {
+ m_status = m_status & ~SetToStandardTime;
+ m_status = m_status | SetToDaylightTime;
+ } else if (status == StandardTime) {
+ m_status = m_status & ~SetToDaylightTime;
+ m_status = m_status | SetToStandardTime;
+ } else {
+ clearSetToDaylightStatus();
+ }
+}
+
+// Get the Daylight Status if LocalTime set via msecs
+QDateTimePrivate::DaylightStatus QDateTimePrivate::daylightStatus() const
+{
+ if ((m_status & SetToDaylightTime) == SetToDaylightTime)
+ return DaylightTime;
+ if ((m_status & SetToStandardTime) == SetToStandardTime)
+ return StandardTime;
+ return UnknownDaylightTime;
+}
+
// Check the UTC / offsetFromUTC validity
void QDateTimePrivate::checkValidDateTime()
{
@@ -2748,12 +2776,14 @@ void QDateTimePrivate::refreshDateTime()
QDate testDate;
QTime testTime;
qint64 epochMSecs = 0;
+ if (m_spec == Qt::LocalTime) {
+ DaylightStatus status = daylightStatus();
+ epochMSecs = localMSecsToEpochMSecs(m_msecs, &testDate, &testTime, &status);
#ifndef QT_BOOTSTRAPPED
- if (m_spec == Qt::TimeZone)
+ } else {
epochMSecs = zoneMSecsToEpochMSecs(m_msecs, m_timeZone, &testDate, &testTime);
- else
#endif // QT_BOOTSTRAPPED
- epochMSecs = localMSecsToEpochMSecs(m_msecs, &testDate, &testTime);
+ }
if (testDate == date && testTime == time) {
setValidDateTime();
// Cache the offset to use in toMSecsSinceEpoch()
@@ -3189,7 +3219,8 @@ QString QDateTime::timeZoneAbbreviation() const
#endif // QT_BOOTSTRAPPED
case Qt::LocalTime: {
QString abbrev;
- localMSecsToEpochMSecs(d->m_msecs, 0, 0, 0, &abbrev);
+ QDateTimePrivate::DaylightStatus status = d->daylightStatus();
+ localMSecsToEpochMSecs(d->m_msecs, 0, 0, &status, &abbrev);
return abbrev;
}
}
@@ -3218,8 +3249,9 @@ bool QDateTime::isDaylightTime() const
return d->m_timeZone.d->isDaylightTime(toMSecsSinceEpoch());
#endif // QT_BOOTSTRAPPED
case Qt::LocalTime: {
- QDateTimePrivate::DaylightStatus status;
- localMSecsToEpochMSecs(d->m_msecs, 0, 0, &status, 0);
+ QDateTimePrivate::DaylightStatus status = d->daylightStatus();
+ if (status == QDateTimePrivate::UnknownDaylightTime)
+ localMSecsToEpochMSecs(d->m_msecs, 0, 0, &status, 0);
return (status == QDateTimePrivate::DaylightTime);
}
}
@@ -3424,8 +3456,10 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
case Qt::LocalTime: {
QDate dt;
QTime tm;
- epochMSecsToLocalTime(msecs, &dt, &tm);
+ QDateTimePrivate::DaylightStatus status;
+ epochMSecsToLocalTime(msecs, &dt, &tm, &status);
d->setDateTime(dt, tm);
+ d->setDaylightStatus(status);
break;
}
}
diff --git a/src/corelib/tools/qdatetime_p.h b/src/corelib/tools/qdatetime_p.h
index f52108d764..70771844d6 100644
--- a/src/corelib/tools/qdatetime_p.h
+++ b/src/corelib/tools/qdatetime_p.h
@@ -77,10 +77,10 @@ public:
// Daylight Time Status
enum DaylightStatus {
- NoDaylightTime,
- UnknownDaylightTime,
- StandardTime,
- DaylightTime
+ NoDaylightTime = -2,
+ UnknownDaylightTime = -1,
+ StandardTime = 0,
+ DaylightTime = 1
};
// Status of date/time
@@ -90,7 +90,9 @@ public:
ValidDate = 0x04,
ValidTime = 0x08,
ValidDateTime = 0x10,
- TimeZoneCached = 0x20
+ TimeZoneCached = 0x20,
+ SetToStandardTime = 0x40,
+ SetToDaylightTime = 0x80
};
Q_DECLARE_FLAGS(StatusFlags, StatusFlag)
@@ -129,6 +131,9 @@ public:
void setDateTime(const QDate &date, const QTime &time);
void getDateTime(QDate *date, QTime *time) const;
+ void setDaylightStatus(DaylightStatus status);
+ DaylightStatus daylightStatus() const;
+
// Returns msecs since epoch, assumes offset value is current
inline qint64 toMSecsSinceEpoch() const { return (m_msecs - (m_offsetFromUtc * 1000)); }
@@ -146,6 +151,7 @@ public:
inline bool isTimeZoneCached() const { return (m_status & TimeZoneCached) == TimeZoneCached; }
inline void setTimeZoneCached() { m_status = m_status | TimeZoneCached; }
inline void clearTimeZoneCached() { m_status = m_status & ~TimeZoneCached; }
+ inline void clearSetToDaylightStatus() { m_status = m_status & ~SetToStandardTime & ~SetToDaylightTime; }
#ifndef QT_BOOTSTRAPPED
static qint64 zoneMSecsToEpochMSecs(qint64 msecs, const QTimeZone &zone,
diff --git a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp
index a282fcabd3..b8b970121c 100644
--- a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp
+++ b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp
@@ -2384,8 +2384,6 @@ void tst_QDateTime::daylightTransitions() const
// 2011-10-30 03:00:00 CEST became 02:00:00 CET at msecs = 1319936400000
// 2012-03-25 02:00:00 CET became 03:00:00 CEST at msecs = 1332637200000
// 2012-10-28 03:00:00 CEST became 02:00:00 CET at msecs = 1351386000000
- const qint64 daylight2011 = 1301187600000;
- const qint64 standard2011 = 1319936400000;
const qint64 daylight2012 = 1332637200000;
const qint64 standard2012 = 1351386000000;
const qint64 msecsOneHour = 3600000;
@@ -2535,10 +2533,6 @@ void tst_QDateTime::daylightTransitions() const
QVERIFY(hourBefore.isValid());
QCOMPARE(hourBefore.date(), QDate(2012, 10, 28));
QCOMPARE(hourBefore.time(), QTime(2, 0, 0));
-#ifndef Q_OS_MAC
- // Linux mktime bug uses last calculation
- QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
-#endif // Q_OS_MAC
QCOMPARE(hourBefore.toMSecsSinceEpoch(), standard2012 - msecsOneHour);
// 1 msec before transition is 2:59:59.999 FirstOccurrence
@@ -2546,7 +2540,6 @@ void tst_QDateTime::daylightTransitions() const
QVERIFY(msecBefore.isValid());
QCOMPARE(msecBefore.date(), QDate(2012, 10, 28));
QCOMPARE(msecBefore.time(), QTime(2, 59, 59, 999));
- QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
QCOMPARE(msecBefore.toMSecsSinceEpoch(), standard2012 - 1);
// At transition is 2:00:00 SecondOccurrence
@@ -2554,10 +2547,6 @@ void tst_QDateTime::daylightTransitions() const
QVERIFY(atTran.isValid());
QCOMPARE(atTran.date(), QDate(2012, 10, 28));
QCOMPARE(atTran.time(), QTime(2, 0, 0));
-#ifdef Q_OS_MAC
- // Mac defaults to FirstOccurrence here
- QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
-#endif // Q_OS_MAC
QCOMPARE(atTran.toMSecsSinceEpoch(), standard2012);
// 59:59.999 after transition is 2:59:59.999 SecondOccurrence
@@ -2708,10 +2697,6 @@ void tst_QDateTime::daylightTransitions() const
QVERIFY(test.isValid());
QCOMPARE(test.date(), QDate(2012, 10, 28));
QCOMPARE(test.time(), QTime(2, 0, 0));
-#ifdef Q_OS_WIN
- // Windows uses SecondOccurrence
- QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
-#endif // Q_OS_WIN
QCOMPARE(test.toMSecsSinceEpoch(), standard2012 - msecsOneHour);
// Add hour to tran FirstOccurrence to get to tran SecondOccurrence
@@ -2724,11 +2709,10 @@ void tst_QDateTime::daylightTransitions() const
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
#endif // Q_OS_WIN
QCOMPARE(test.time(), QTime(2, 0, 0));
-#ifndef Q_OS_MAC
+#ifdef Q_OS_WIN
// Windows uses SecondOccurrence
- // Linux mktime bug uses last calculation
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
-#endif // Q_OS_MAC
+#endif // Q_OS_WIN
QCOMPARE(test.toMSecsSinceEpoch(), standard2012);
// Add hour to tran SecondOccurrence to get to after tran FirstOccurrence
@@ -2736,15 +2720,13 @@ void tst_QDateTime::daylightTransitions() const
test = test.addMSecs(msecsOneHour);
QVERIFY(test.isValid());
QCOMPARE(test.date(), QDate(2012, 10, 28));
-#ifndef Q_OS_WIN
- // Mac uses FirstOccurrence
- // Linux mktime bug uses last calculation
+#ifdef Q_OS_MAC
+ // Mac uses FirstOccurrence, Windows uses SecondOccurrence, Linux uses last calculation
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
#endif // Q_OS_WIN
QCOMPARE(test.time(), QTime(3, 0, 0));
-#ifndef Q_OS_WIN
- // Mac uses FirstOccurrence
- // Linux mktime bug uses last calculation
+#ifdef Q_OS_MAC
+ // Mac uses FirstOccurrence, Windows uses SecondOccurrence, Linux uses last calculation
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
#endif // Q_OS_WIN
QCOMPARE(test.toMSecsSinceEpoch(), standard2012 + msecsOneHour);