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>
This commit is contained in:
parent
3705c1263d
commit
475cbed244
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user