summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohn Layt <jlayt@kde.org>2013-08-19 16:50:54 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-20 23:45:06 +0200
commit7b07c3ff781c9fcdaf7a60aabed9b7aa57e25006 (patch)
tree6bcf141cb1d0e97234746a131fc1335a38eb43f4 /src
parent18322bfabc3c349040bb370167e2642dc734bd50 (diff)
QDateTime - Fix Daylight Transition for "missing" hour
When Daylight Time transtion goes from Standard Time to Daylight Time there is a "missing" hour, i.e. at 2am CET the clock goes forward to 3am. Currently QDateTime ignores this gap and considers the time to be valid and able to be manipulated. This change respects the transition, so any time set in the missing hour is considered invalid, and any date maths returns valid results. The validity in the current time zone needs to be checked every time isValid() is called in case the system time zone has changed since the last time it was checked. This is done by calling mktime to check the returned result matches the expected result. This could be very inefficient, but the returned offset value is cached each time so mktime is not required to be called again within each method call, effectively meaning mktime is called the same number of times by each method. Note that this means any new methods added must be careful to ensure either isValid() or refreshLocalTime() is called first by any method needing to use the UTC value. [ChangeLog][QtCore][QDateTime] The Standard Time to Daylight Time transition for Qt::LocalTime is now handled correctly. Any date set in the "missing" hour is now considered invalid. All date math results that fall into the missing hour will be automatically adjusted to a valid time in the following hour. Change-Id: Ia652c8511b45df15f4917acf12403ec01a7f08e7 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/tools/qdatetime.cpp174
-rw-r--r--src/corelib/tools/qdatetime_p.h10
2 files changed, 153 insertions, 31 deletions
diff --git a/src/corelib/tools/qdatetime.cpp b/src/corelib/tools/qdatetime.cpp
index c3ce5b2540..4f7da33991 100644
--- a/src/corelib/tools/qdatetime.cpp
+++ b/src/corelib/tools/qdatetime.cpp
@@ -307,10 +307,25 @@ static qint64 qt_mktime(QDate *date, QTime *time, QDateTimePrivate::DaylightStat
local.tm_wday = 0;
local.tm_yday = 0;
local.tm_isdst = -1;
- const time_t secsSinceEpoch = mktime(&local);
+#if defined(Q_OS_WIN)
+ int hh = local.tm_hour;
+#endif // Q_OS_WIN
+ time_t secsSinceEpoch = mktime(&local);
if (secsSinceEpoch != time_t(-1)) {
*date = QDate(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday);
*time = QTime(local.tm_hour, local.tm_min, local.tm_sec, msec);
+#if defined(Q_OS_WIN)
+ // Windows mktime for the missing hour subtracts 1 hour from the time
+ // instead of adding 1 hour. If time differs and is standard time then
+ // this has happened, so add 2 hours to the time and 1 hour to the msecs
+ if (local.tm_isdst == 0 && local.tm_hour != hh) {
+ if (time->hour() >= 22)
+ *date = date->addDays(1);
+ *time = time->addSecs(2 * SECS_PER_HOUR);
+ secsSinceEpoch += SECS_PER_HOUR;
+ local.tm_isdst = 1;
+ }
+#endif // Q_OS_WIN
if (local.tm_isdst >= 1) {
if (daylightStatus)
*daylightStatus = QDateTimePrivate::DaylightTime;
@@ -2502,6 +2517,8 @@ static qint64 localMSecsToEpochMSecs(qint64 localMsecs, QDate *localDate = 0, QT
void QDateTimePrivate::setTimeSpec(Qt::TimeSpec spec, int offsetSeconds)
{
+ clearValidDateTime();
+
switch (spec) {
case Qt::OffsetFromUTC:
if (offsetSeconds == 0) {
@@ -2550,6 +2567,9 @@ void QDateTimePrivate::setDateTime(const QDate &date, const QTime &time)
// Set msecs serial value
m_msecs = (days * MSECS_PER_DAY) + ds;
+
+ // Set if date and time are valid
+ checkValidDateTime();
}
void QDateTimePrivate::getDateTime(QDate *date, QTime *time) const
@@ -2563,6 +2583,58 @@ void QDateTimePrivate::getDateTime(QDate *date, QTime *time) const
*time = QTime();
}
+// Check the UTC / offsetFromUTC validity
+void QDateTimePrivate::checkValidDateTime()
+{
+ switch (m_spec) {
+ case Qt::OffsetFromUTC:
+ case Qt::UTC:
+ if (isValidDate() && isValidTime())
+ setValidDateTime();
+ else
+ clearValidDateTime();
+ break;
+ case Qt::LocalTime:
+ // Defer checking until required as can be expensive
+ clearValidDateTime();
+ m_offsetFromUtc = 0;
+ break;
+ }
+}
+
+// Refresh the LocalTime validity and offset
+void QDateTimePrivate::refreshDateTime()
+{
+ // Always set by setDateTime so just return
+ if (m_spec == Qt::UTC || m_spec == Qt::OffsetFromUTC)
+ return;
+
+ // If not valid date and time then is invalid
+ if (!isValidDate() || !isValidTime()) {
+ clearValidDateTime();
+ m_offsetFromUtc = 0;
+ return;
+ }
+
+ // We have a valid date and time and a Qt::LocalTime that needs calculating
+ QDate date;
+ QTime time;
+ getDateTime(&date, &time);
+ // LocalTime and TimeZone might fall into "missing" DaylightTime transition hour
+ // Calling toEpochMSecs will adjust the returned date/time if it does
+ QDate testDate;
+ QTime testTime;
+ qint64 epochMSecs = localMSecsToEpochMSecs(m_msecs, &testDate, &testTime);
+ if (testDate == date && testTime == time) {
+ setValidDateTime();
+ // Cache the offset to use in toMSecsSinceEpoch()
+ m_offsetFromUtc = (m_msecs - epochMSecs) / 1000;
+ } else {
+ clearValidDateTime();
+ m_offsetFromUtc = 0;
+ }
+}
+
/*****************************************************************************
QDateTime member functions
*****************************************************************************/
@@ -2659,6 +2731,12 @@ void QDateTimePrivate::getDateTime(QDate *date, QTime *time) const
time zone before 1970, even if the system's time zone database
supports that information.
+ QDateTime takes into consideration the Standard Time to Daylight Time
+ transition. For example if the transition is at 2am and the clock goes
+ forward to 3am, then there is a "missing" hour from 02:00:00 to 02:59:59.999
+ which QDateTime considers to be invalid. Any date maths performed
+ will take this missing hour into account and return a valid result.
+
\section2 Offset From UTC
A Qt::TimeSpec of Qt::OffsetFromUTC is also supported. This allows you
@@ -2800,15 +2878,21 @@ bool QDateTime::isNull() const
}
/*!
- Returns true if both the date and the time are valid; otherwise
- returns false.
+ Returns true if both the date and the time are valid and they are valid in
+ the current Qt::TimeSpec, otherwise returns false.
+
+ If the timeSpec() is Qt::LocalTime then the date and time are
+ checked to see if they fall in the Standard Time to Daylight Time transition
+ hour, i.e. if the transition is at 2am and the clock goes forward to 3am
+ then the time from 02:00:00 to 02:59:59.999 is considered to be invalid.
\sa QDate::isValid(), QTime::isValid()
*/
bool QDateTime::isValid() const
{
- return d->isValidDate() && d->isValidTime();
+ d->refreshDateTime();
+ return (d->isValidDateTime());
}
/*!
@@ -2867,16 +2951,8 @@ Qt::TimeSpec QDateTime::timeSpec() const
int QDateTime::offsetFromUtc() const
{
- switch (d->m_spec) {
- case Qt::OffsetFromUTC:
- return d->m_offsetFromUtc;
- case Qt::UTC:
- return 0;
- case Qt::LocalTime:
- if (isValid())
- return (d->m_msecs - toMSecsSinceEpoch()) / 1000;
- }
- return 0;
+ d->refreshDateTime();
+ return d->m_offsetFromUtc;
}
/*!
@@ -2958,6 +3034,7 @@ void QDateTime::setTimeSpec(Qt::TimeSpec spec)
{
detach();
d->setTimeSpec(spec, 0);
+ d->checkValidDateTime();
}
/*!
@@ -2979,6 +3056,7 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds)
{
detach();
d->setTimeSpec(Qt::OffsetFromUTC, offsetSeconds);
+ d->checkValidDateTime();
}
/*!
@@ -2998,15 +3076,8 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds)
*/
qint64 QDateTime::toMSecsSinceEpoch() const
{
- switch (d->m_spec) {
- case Qt::UTC:
- return d->m_msecs;
- case Qt::OffsetFromUTC:
- return d->m_msecs - (d->m_offsetFromUtc * 1000);
- case Qt::LocalTime:
- return localMSecsToEpochMSecs(d->m_msecs);
- }
- return 0;
+ d->refreshDateTime();
+ return d->toMSecsSinceEpoch();
}
/*!
@@ -3033,7 +3104,9 @@ qint64 QDateTime::toMSecsSinceEpoch() const
uint QDateTime::toTime_t() const
{
- qint64 retval = toMSecsSinceEpoch() / 1000;
+ if (!isValid())
+ return uint(-1);
+ qint64 retval = d->toMSecsSinceEpoch() / 1000;
if (quint64(retval) >= Q_UINT64_C(0xFFFFFFFF))
return uint(-1);
return uint(retval);
@@ -3061,11 +3134,17 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
switch (d->m_spec) {
case Qt::UTC:
d->m_msecs = msecs;
- d->m_status = d->m_status | QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime;
+ d->m_status = d->m_status
+ | QDateTimePrivate::ValidDate
+ | QDateTimePrivate::ValidTime
+ | QDateTimePrivate::ValidDateTime;
break;
case Qt::OffsetFromUTC:
d->m_msecs = msecs + (d->m_offsetFromUtc * 1000);
- d->m_status = d->m_status | QDateTimePrivate::ValidDate | QDateTimePrivate::ValidTime;
+ d->m_status = d->m_status
+ | QDateTimePrivate::ValidDate
+ | QDateTimePrivate::ValidTime
+ | QDateTimePrivate::ValidDateTime;
break;
case Qt::LocalTime: {
QDate dt;
@@ -3311,6 +3390,12 @@ QString QDateTime::toString(const QString& format) const
later than the datetime of this object (or earlier if \a ndays is
negative).
+ If the timeSpec() is Qt::LocalTime and the resulting
+ date and time fall in the Standard Time to Daylight Time transition
+ hour then the result will be adjusted accordingly, i.e. if the transition
+ is at 2am and the clock goes forward to 3am and the result falls between
+ 2am and 3am then the result will be adjusted to fall after 3am.
+
\sa daysTo(), addMonths(), addYears(), addSecs()
*/
@@ -3321,7 +3406,12 @@ QDateTime QDateTime::addDays(qint64 ndays) const
QDate date;
QTime time;
d->getDateTime(&date, &time);
- dt.d->setDateTime(date.addDays(ndays), time);
+ date = date.addDays(ndays);
+ // Result might fall into "missing" DaylightTime transition hour,
+ // so call conversion and use the adjusted returned time
+ if (d->m_spec == Qt::LocalTime)
+ localMSecsToEpochMSecs(timeToMSecs(date, time), &date, &time);
+ dt.d->setDateTime(date, time);
return dt;
}
@@ -3330,6 +3420,12 @@ QDateTime QDateTime::addDays(qint64 ndays) const
later than the datetime of this object (or earlier if \a nmonths
is negative).
+ If the timeSpec() is Qt::LocalTime and the resulting
+ date and time fall in the Standard Time to Daylight Time transition
+ hour then the result will be adjusted accordingly, i.e. if the transition
+ is at 2am and the clock goes forward to 3am and the result falls between
+ 2am and 3am then the result will be adjusted to fall after 3am.
+
\sa daysTo(), addDays(), addYears(), addSecs()
*/
@@ -3340,7 +3436,12 @@ QDateTime QDateTime::addMonths(int nmonths) const
QDate date;
QTime time;
d->getDateTime(&date, &time);
- dt.d->setDateTime(date.addMonths(nmonths), time);
+ date = date.addMonths(nmonths);
+ // Result might fall into "missing" DaylightTime transition hour,
+ // so call conversion and use the adjusted returned time
+ if (d->m_spec == Qt::LocalTime)
+ localMSecsToEpochMSecs(timeToMSecs(date, time), &date, &time);
+ dt.d->setDateTime(date, time);
return dt;
}
@@ -3349,6 +3450,12 @@ QDateTime QDateTime::addMonths(int nmonths) const
later than the datetime of this object (or earlier if \a nyears is
negative).
+ If the timeSpec() is Qt::LocalTime and the resulting
+ date and time fall in the Standard Time to Daylight Time transition
+ hour then the result will be adjusted accordingly, i.e. if the transition
+ is at 2am and the clock goes forward to 3am and the result falls between
+ 2am and 3am then the result will be adjusted to fall after 3am.
+
\sa daysTo(), addDays(), addMonths(), addSecs()
*/
@@ -3359,7 +3466,12 @@ QDateTime QDateTime::addYears(int nyears) const
QDate date;
QTime time;
d->getDateTime(&date, &time);
- dt.d->setDateTime(date.addYears(nyears), time);
+ date = date.addYears(nyears);
+ // Result might fall into "missing" DaylightTime transition hour,
+ // so call conversion and use the adjusted returned time
+ if (d->m_spec == Qt::LocalTime)
+ localMSecsToEpochMSecs(timeToMSecs(date, time), &date, &time);
+ dt.d->setDateTime(date, time);
return dt;
}
@@ -3396,7 +3508,7 @@ QDateTime QDateTime::addMSecs(qint64 msecs) const
dt.detach();
if (d->m_spec == Qt::LocalTime)
// Convert to real UTC first in case crosses daylight transition
- dt.setMSecsSinceEpoch(toMSecsSinceEpoch() + msecs);
+ dt.setMSecsSinceEpoch(d->toMSecsSinceEpoch() + msecs);
else
// No need to convert, just add on
dt.d->m_msecs = dt.d->m_msecs + msecs;
@@ -3465,7 +3577,7 @@ qint64 QDateTime::msecsTo(const QDateTime &other) const
if (!isValid() || !other.isValid())
return 0;
- return other.toMSecsSinceEpoch() - toMSecsSinceEpoch();
+ return other.d->toMSecsSinceEpoch() - d->toMSecsSinceEpoch();
}
/*!
diff --git a/src/corelib/tools/qdatetime_p.h b/src/corelib/tools/qdatetime_p.h
index 124bd98a31..7fd36bc4e0 100644
--- a/src/corelib/tools/qdatetime_p.h
+++ b/src/corelib/tools/qdatetime_p.h
@@ -86,6 +86,7 @@ public:
NullTime = 0x02,
ValidDate = 0x04,
ValidTime = 0x08,
+ ValidDateTime = 0x10
};
Q_DECLARE_FLAGS(StatusFlags, StatusFlag)
@@ -114,11 +115,20 @@ public:
void setDateTime(const QDate &date, const QTime &time);
void getDateTime(QDate *date, QTime *time) const;
+ // Returns msecs since epoch, assumes offset value is current
+ inline qint64 toMSecsSinceEpoch() const { return (m_msecs - (m_offsetFromUtc * 1000)); }
+
+ void checkValidDateTime();
+ void refreshDateTime();
+
// Get/set date and time status
inline bool isNullDate() const { return (m_status & NullDate) == NullDate; }
inline bool isNullTime() const { return (m_status & NullTime) == NullTime; }
inline bool isValidDate() const { return (m_status & ValidDate) == ValidDate; }
inline bool isValidTime() const { return (m_status & ValidTime) == ValidTime; }
+ inline bool isValidDateTime() const { return (m_status & ValidDateTime) == ValidDateTime; }
+ inline void setValidDateTime() { m_status = m_status | ValidDateTime; }
+ inline void clearValidDateTime() { m_status = m_status & ~ValidDateTime; }
static inline qint64 minJd() { return QDate::minJd(); }
static inline qint64 maxJd() { return QDate::maxJd(); }