From e7cd32274e144144c1630d65d09d24a1ae0af2d7 Mon Sep 17 00:00:00 2001 From: Maurice Kalinowski Date: Tue, 15 Mar 2016 13:04:50 +0100 Subject: 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 --- src/corelib/tools/qtimezone.cpp | 4 +- src/corelib/tools/qtimezoneprivate_win.cpp | 100 ++++++++++++++++++++++++++++- src/corelib/tools/tools.pri | 6 +- 3 files changed, 105 insertions(+), 5 deletions(-) (limited to 'src/corelib') diff --git a/src/corelib/tools/qtimezone.cpp b/src/corelib/tools/qtimezone.cpp index 333a5c3471..bf34a8b16f 100644 --- a/src/corelib/tools/qtimezone.cpp +++ b/src/corelib/tools/qtimezone.cpp @@ -61,7 +61,7 @@ static QTimeZonePrivate *newBackendTimeZone() #elif defined Q_OS_UNIX return new QTzTimeZonePrivate(); // 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(); #elif defined QT_USE_ICU return new QIcuTimeZonePrivate(); @@ -88,7 +88,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) #elif defined Q_OS_UNIX return new QTzTimeZonePrivate(ianaId); // 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); #elif defined QT_USE_ICU return new QIcuTimeZonePrivate(ianaId); diff --git a/src/corelib/tools/qtimezoneprivate_win.cpp b/src/corelib/tools/qtimezoneprivate_win.cpp index a9bb3aa3b5..0cb26c2e5b 100644 --- a/src/corelib/tools/qtimezoneprivate_win.cpp +++ b/src/corelib/tools/qtimezoneprivate_win.cpp @@ -42,6 +42,10 @@ QT_BEGIN_NAMESPACE +#ifndef Q_OS_WINRT +#define QT_USE_REGISTRY_TIMEZONE 1 +#endif + /* Private @@ -59,9 +63,10 @@ QT_BEGIN_NAMESPACE // 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 - +#ifdef QT_USE_REGISTRY_TIMEZONE static const char tzRegPath[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"; static const char currTzRegPath[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"; +#endif enum { 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); } +#ifdef QT_USE_REGISTRY_TIMEZONE static bool openRegistryKey(const QString &keyPath, HKEY *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; } +#else // QT_USE_REGISTRY_TIMEZONE +struct QWinDynamicTimeZone +{ + QString standardName; + QString daylightName; + QString timezoneName; + qint32 bias; + bool daylightTime; +}; + +typedef QHash 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 availableWindowsIds() { +#ifdef QT_USE_REGISTRY_TIMEZONE // TODO Consider caching results in a global static, very unlikely to change. QList list; HKEY key = NULL; @@ -217,10 +275,16 @@ static QList availableWindowsIds() RegCloseKey(key); } return list; +#else // QT_USE_REGISTRY_TIMEZONE + if (gTimeZones->isEmpty()) + enumerateTimeZones(); + return gTimeZones->keys(); +#endif // QT_USE_REGISTRY_TIMEZONE } static QByteArray windowsSystemZoneId() { +#ifdef QT_USE_REGISTRY_TIMEZONE // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath QString id; HKEY key = NULL; @@ -241,6 +305,11 @@ static QByteArray windowsSystemZoneId() if (equalTzi(getRegistryTzi(winId, &ok), sysTzi)) 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 return QTimeZonePrivate::utcQByteArray(); @@ -361,6 +430,7 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId) } if (!m_windowsId.isEmpty()) { +#ifdef QT_USE_REGISTRY_TIMEZONE // Open the base TZI for the time zone HKEY baseKey = NULL; const QString baseKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\') @@ -397,6 +467,34 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId) } 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 diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index ed07f70e87..ed6afe70ce 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -147,9 +147,11 @@ else:unix { SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp tools/qtimezoneprivate_tz.cpp } else:win32 { - SOURCES += tools/qelapsedtimer_win.cpp tools/qlocale_win.cpp - !winrt: SOURCES += tools/qtimezoneprivate_win.cpp + SOURCES += tools/qelapsedtimer_win.cpp \ + tools/qlocale_win.cpp \ + tools/qtimezoneprivate_win.cpp winphone: LIBS_PRIVATE += -lWindowsPhoneGlobalizationUtil + winrt-*-msvc2013: LIBS += advapi32.lib } else:integrity:SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp else:SOURCES += tools/qelapsedtimer_generic.cpp -- cgit v1.2.3