WinRT: Fix QTimeZone transitions by switching backend

Previously WinRT was using the UTC backend which fails on all platforms
for some QDateTime autotests related to timezone items. Hence switch to
the Windows implementation for WinRT as well.

However, the windows backend does query the registry heavily, which is
not supported on WinRT. Instead use the API version provided by the SDK.

Long-term we might want to switch to this version on desktop windows as
well, as direct registry access would not be required and we could
harmonize the codepaths for both platforms.

Change-Id: I620b614e9994aa77b531e5c34c9be1da7e272a30
Reviewed-by: Oliver Wolff <oliver.wolff@theqtcompany.com>
This commit is contained in:
Maurice Kalinowski 2016-03-15 13:04:50 +01:00
parent 7e72a5e11e
commit e7cd32274e
4 changed files with 106 additions and 6 deletions

View File

@ -61,7 +61,7 @@ static QTimeZonePrivate *newBackendTimeZone()
#elif defined Q_OS_UNIX #elif defined Q_OS_UNIX
return new QTzTimeZonePrivate(); return new QTzTimeZonePrivate();
// Registry based timezone backend not available on WinRT // Registry based timezone backend not available on WinRT
#elif defined Q_OS_WIN && !defined Q_OS_WINRT #elif defined Q_OS_WIN
return new QWinTimeZonePrivate(); return new QWinTimeZonePrivate();
#elif defined QT_USE_ICU #elif defined QT_USE_ICU
return new QIcuTimeZonePrivate(); return new QIcuTimeZonePrivate();
@ -88,7 +88,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId)
#elif defined Q_OS_UNIX #elif defined Q_OS_UNIX
return new QTzTimeZonePrivate(ianaId); return new QTzTimeZonePrivate(ianaId);
// Registry based timezone backend not available on WinRT // Registry based timezone backend not available on WinRT
#elif defined Q_OS_WIN && !defined Q_OS_WINRT #elif defined Q_OS_WIN
return new QWinTimeZonePrivate(ianaId); return new QWinTimeZonePrivate(ianaId);
#elif defined QT_USE_ICU #elif defined QT_USE_ICU
return new QIcuTimeZonePrivate(ianaId); return new QIcuTimeZonePrivate(ianaId);

View File

@ -42,6 +42,10 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
#ifndef Q_OS_WINRT
#define QT_USE_REGISTRY_TIMEZONE 1
#endif
/* /*
Private Private
@ -59,9 +63,10 @@ QT_BEGIN_NAMESPACE
// Vista introduced support for historic data, see MSDN docs on DYNAMIC_TIME_ZONE_INFORMATION // Vista introduced support for historic data, see MSDN docs on DYNAMIC_TIME_ZONE_INFORMATION
// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms724253%28v=vs.85%29.aspx // http://msdn.microsoft.com/en-gb/library/windows/desktop/ms724253%28v=vs.85%29.aspx
#ifdef QT_USE_REGISTRY_TIMEZONE
static const char tzRegPath[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"; static const char tzRegPath[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones";
static const char currTzRegPath[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"; static const char currTzRegPath[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
#endif
enum { enum {
MIN_YEAR = -292275056, MIN_YEAR = -292275056,
@ -123,6 +128,7 @@ static bool equalTzi(const TIME_ZONE_INFORMATION &tzi1, const TIME_ZONE_INFORMAT
&& wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0); && wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0);
} }
#ifdef QT_USE_REGISTRY_TIMEZONE
static bool openRegistryKey(const QString &keyPath, HKEY *key) static bool openRegistryKey(const QString &keyPath, HKEY *key)
{ {
return (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (const wchar_t*)keyPath.utf16(), 0, KEY_READ, key) return (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (const wchar_t*)keyPath.utf16(), 0, KEY_READ, key)
@ -197,9 +203,61 @@ static TIME_ZONE_INFORMATION getRegistryTzi(const QByteArray &windowsId, bool *o
return tzi; return tzi;
} }
#else // QT_USE_REGISTRY_TIMEZONE
struct QWinDynamicTimeZone
{
QString standardName;
QString daylightName;
QString timezoneName;
qint32 bias;
bool daylightTime;
};
typedef QHash<QByteArray, QWinDynamicTimeZone> QWinRTTimeZoneHash;
Q_GLOBAL_STATIC(QWinRTTimeZoneHash, gTimeZones)
static void enumerateTimeZones()
{
DYNAMIC_TIME_ZONE_INFORMATION dtzInfo;
quint32 index = 0;
QString prevTimeZoneKeyName;
while (SUCCEEDED(EnumDynamicTimeZoneInformation(index++, &dtzInfo))) {
QWinDynamicTimeZone item;
item.timezoneName = QString::fromWCharArray(dtzInfo.TimeZoneKeyName);
// As soon as key name repeats, break. Some systems continue to always
// return the last item independent of index being out of range
if (item.timezoneName == prevTimeZoneKeyName)
break;
item.standardName = QString::fromWCharArray(dtzInfo.StandardName);
item.daylightName = QString::fromWCharArray(dtzInfo.DaylightName);
item.daylightTime = !dtzInfo.DynamicDaylightTimeDisabled;
item.bias = dtzInfo.Bias;
gTimeZones->insert(item.timezoneName.toUtf8(), item);
prevTimeZoneKeyName = item.timezoneName;
}
}
static DYNAMIC_TIME_ZONE_INFORMATION dynamicInfoForId(const QByteArray &windowsId)
{
DYNAMIC_TIME_ZONE_INFORMATION dtzInfo;
quint32 index = 0;
QString prevTimeZoneKeyName;
while (SUCCEEDED(EnumDynamicTimeZoneInformation(index++, &dtzInfo))) {
const QString timeZoneName = QString::fromWCharArray(dtzInfo.TimeZoneKeyName);
if (timeZoneName == QLatin1String(windowsId))
break;
if (timeZoneName == prevTimeZoneKeyName)
break;
prevTimeZoneKeyName = timeZoneName;
}
return dtzInfo;
}
#endif // QT_USE_REGISTRY_TIMEZONE
static QList<QByteArray> availableWindowsIds() static QList<QByteArray> availableWindowsIds()
{ {
#ifdef QT_USE_REGISTRY_TIMEZONE
// TODO Consider caching results in a global static, very unlikely to change. // TODO Consider caching results in a global static, very unlikely to change.
QList<QByteArray> list; QList<QByteArray> list;
HKEY key = NULL; HKEY key = NULL;
@ -217,10 +275,16 @@ static QList<QByteArray> availableWindowsIds()
RegCloseKey(key); RegCloseKey(key);
} }
return list; return list;
#else // QT_USE_REGISTRY_TIMEZONE
if (gTimeZones->isEmpty())
enumerateTimeZones();
return gTimeZones->keys();
#endif // QT_USE_REGISTRY_TIMEZONE
} }
static QByteArray windowsSystemZoneId() static QByteArray windowsSystemZoneId()
{ {
#ifdef QT_USE_REGISTRY_TIMEZONE
// On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath
QString id; QString id;
HKEY key = NULL; HKEY key = NULL;
@ -241,6 +305,11 @@ static QByteArray windowsSystemZoneId()
if (equalTzi(getRegistryTzi(winId, &ok), sysTzi)) if (equalTzi(getRegistryTzi(winId, &ok), sysTzi))
return winId; return winId;
} }
#else // QT_USE_REGISTRY_TIMEZONE
DYNAMIC_TIME_ZONE_INFORMATION dtzi;
if (SUCCEEDED(GetDynamicTimeZoneInformation(&dtzi)))
return QString::fromWCharArray(dtzi.TimeZoneKeyName).toLocal8Bit();
#endif // QT_USE_REGISTRY_TIMEZONE
// If we can't determine the current ID use UTC // If we can't determine the current ID use UTC
return QTimeZonePrivate::utcQByteArray(); return QTimeZonePrivate::utcQByteArray();
@ -361,6 +430,7 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId)
} }
if (!m_windowsId.isEmpty()) { if (!m_windowsId.isEmpty()) {
#ifdef QT_USE_REGISTRY_TIMEZONE
// Open the base TZI for the time zone // Open the base TZI for the time zone
HKEY baseKey = NULL; HKEY baseKey = NULL;
const QString baseKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\') const QString baseKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\')
@ -397,6 +467,34 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId)
} }
RegCloseKey(baseKey); RegCloseKey(baseKey);
} }
#else // QT_USE_REGISTRY_TIMEZONE
if (gTimeZones->isEmpty())
enumerateTimeZones();
QWinRTTimeZoneHash::const_iterator it = gTimeZones->find(m_windowsId);
if (it != gTimeZones->constEnd()) {
m_displayName = it->timezoneName;
m_standardName = it->standardName;
m_daylightName = it->daylightName;
DWORD firstYear = 0;
DWORD lastYear = 0;
DYNAMIC_TIME_ZONE_INFORMATION dtzi = dynamicInfoForId(m_windowsId);
GetDynamicTimeZoneInformationEffectiveYears(&dtzi, &firstYear, &lastYear);
// If there is no dynamic information, you can still query for
// year 0, which helps simplifying following part
for (DWORD year = firstYear; year <= lastYear; ++year) {
TIME_ZONE_INFORMATION tzi;
if (!GetTimeZoneInformationForYear(year, &dtzi, &tzi))
continue;
QWinTransitionRule rule;
rule.standardTimeBias = tzi.Bias + tzi.StandardBias;
rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias;
rule.standardTimeRule = tzi.StandardDate;
rule.daylightTimeRule = tzi.DaylightDate;
rule.startYear = year;
m_tranRules.append(rule);
}
}
#endif // QT_USE_REGISTRY_TIMEZONE
} }
// If there are no rules then we failed to find a windowsId or any tzi info // If there are no rules then we failed to find a windowsId or any tzi info

View File

@ -147,9 +147,11 @@ else:unix {
SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp tools/qtimezoneprivate_tz.cpp SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp tools/qtimezoneprivate_tz.cpp
} }
else:win32 { else:win32 {
SOURCES += tools/qelapsedtimer_win.cpp tools/qlocale_win.cpp SOURCES += tools/qelapsedtimer_win.cpp \
!winrt: SOURCES += tools/qtimezoneprivate_win.cpp tools/qlocale_win.cpp \
tools/qtimezoneprivate_win.cpp
winphone: LIBS_PRIVATE += -lWindowsPhoneGlobalizationUtil winphone: LIBS_PRIVATE += -lWindowsPhoneGlobalizationUtil
winrt-*-msvc2013: LIBS += advapi32.lib
} else:integrity:SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp } else:integrity:SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp
else:SOURCES += tools/qelapsedtimer_generic.cpp else:SOURCES += tools/qelapsedtimer_generic.cpp

View File

@ -898,7 +898,7 @@ void tst_QTimeZone::macTest()
void tst_QTimeZone::winTest() void tst_QTimeZone::winTest()
{ {
#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_WIN) && !defined(Q_OS_WINRT) #if defined(QT_BUILD_INTERNAL) && defined(Q_OS_WIN)
// Known datetimes // Known datetimes
qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();
qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch();