From 8af776d4140c19baccb8e2767ed8b57cc79ccb06 Mon Sep 17 00:00:00 2001 From: John Layt Date: Thu, 7 Feb 2013 18:08:39 +0000 Subject: QTimeZone - Add Windows backend Add backend implementation for Windows times zones. Change-Id: I30946f6672488c3f1d1d05754e9479aa62cce46f Reviewed-by: Thiago Macieira --- src/corelib/tools/qtimezone.cpp | 4 + src/corelib/tools/qtimezoneprivate_p.h | 62 +++ src/corelib/tools/qtimezoneprivate_win.cpp | 664 +++++++++++++++++++++++++++++ src/corelib/tools/tools.pri | 2 +- 4 files changed, 731 insertions(+), 1 deletion(-) create mode 100644 src/corelib/tools/qtimezoneprivate_win.cpp (limited to 'src') diff --git a/src/corelib/tools/qtimezone.cpp b/src/corelib/tools/qtimezone.cpp index ad13433583..f5f2b28b88 100644 --- a/src/corelib/tools/qtimezone.cpp +++ b/src/corelib/tools/qtimezone.cpp @@ -63,6 +63,8 @@ static QTimeZonePrivate *newBackendTimeZone() return new QMacTimeZonePrivate(); #elif defined Q_OS_UNIX return new QTzTimeZonePrivate(); +#elif defined Q_OS_WIN + return new QWinTimeZonePrivate(); #elif defined QT_USE_ICU return new QIcuTimeZonePrivate(); #else @@ -85,6 +87,8 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &olsenId) return new QMacTimeZonePrivate(olsenId); #elif defined Q_OS_UNIX return new QTzTimeZonePrivate(olsenId); +#elif defined Q_OS_WIN + return new QWinTimeZonePrivate(olsenId); #elif defined QT_USE_ICU return new QIcuTimeZonePrivate(olsenId); #else diff --git a/src/corelib/tools/qtimezoneprivate_p.h b/src/corelib/tools/qtimezoneprivate_p.h index bc28e627a9..b4e4090c0b 100644 --- a/src/corelib/tools/qtimezoneprivate_p.h +++ b/src/corelib/tools/qtimezoneprivate_p.h @@ -69,6 +69,10 @@ class NSTimeZone; #endif // __OBJC__ #endif // Q_OS_MAC +#ifdef Q_OS_WIN +#include +#endif // Q_OS_WIN + QT_BEGIN_NAMESPACE class Q_CORE_EXPORT QTimeZonePrivate : public QSharedData @@ -357,6 +361,64 @@ private: }; #endif // Q_OS_MAC +#ifdef Q_OS_WIN +class Q_AUTOTEST_EXPORT QWinTimeZonePrivate Q_DECL_FINAL : public QTimeZonePrivate +{ +public: + struct QWinTransitionRule { + int startYear; + int standardTimeBias; + int daylightTimeBias; + SYSTEMTIME standardTimeRule; + SYSTEMTIME daylightTimeRule; + }; + + // Create default time zone + QWinTimeZonePrivate(); + // Create named time zone + QWinTimeZonePrivate(const QByteArray &olsenId); + QWinTimeZonePrivate(const QWinTimeZonePrivate &other); + ~QWinTimeZonePrivate(); + + QTimeZonePrivate *clone(); + + QString comment() const Q_DECL_OVERRIDE; + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const Q_DECL_OVERRIDE; + QString abbreviation(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + bool hasDaylightTime() const Q_DECL_OVERRIDE; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + Data data(qint64 forMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + bool hasTransitions() const Q_DECL_OVERRIDE; + Data nextTransition(qint64 afterMSecsSinceEpoch) const Q_DECL_OVERRIDE; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + QByteArray systemTimeZoneId() const Q_DECL_OVERRIDE; + + QSet availableTimeZoneIds() const Q_DECL_OVERRIDE; + +private: + void init(const QByteArray &olsenId); + QWinTransitionRule ruleForYear(int year) const; + QTimeZonePrivate::Data ruleToData(const QWinTransitionRule &rule, qint64 atMSecsSinceEpoch, + QTimeZone::TimeType type) const; + + QByteArray m_windowsId; + QString m_displayName; + QString m_standardName; + QString m_daylightName; + QList m_tranRules; +}; +#endif // Q_OS_WIN + QT_END_NAMESPACE #endif // QTIMEZONEPRIVATE_P_H diff --git a/src/corelib/tools/qtimezoneprivate_win.cpp b/src/corelib/tools/qtimezoneprivate_win.cpp new file mode 100644 index 0000000000..51300c0cad --- /dev/null +++ b/src/corelib/tools/qtimezoneprivate_win.cpp @@ -0,0 +1,664 @@ +/**************************************************************************** +** +** Copyright (C) 2013 John Layt +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +#include "qdatetime.h" + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +/* + Private + + Windows system implementation +*/ + +#define MAX_KEY_LENGTH 255 +#define FILETIME_UNIX_EPOCH Q_UINT64_C(116444736000000000) + +// MSDN home page for Time support +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724962%28v=vs.85%29.aspx + +// For Windows XP and later refer to MSDN docs on TIME_ZONE_INFORMATION structure +// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms725481%28v=vs.85%29.aspx + +// 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 + +static const char tzRegPath[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"; +static const char currTzRegPath[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"; + +enum { + MIN_YEAR = -292275056, + MAX_YEAR = 292278994, + MSECS_PER_DAY = 86400000, + TIME_T_MAX = 2145916799, // int maximum 2037-12-31T23:59:59 UTC + JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromDate(1970, 1, 1) +}; + +// Copied from MSDN, see above for link +typedef struct _REG_TZI_FORMAT +{ + LONG Bias; + LONG StandardBias; + LONG DaylightBias; + SYSTEMTIME StandardDate; + SYSTEMTIME DaylightDate; +} REG_TZI_FORMAT; + +// Fast and reliable conversion from msecs to date for all values +// Adapted from QDateTime msecsToDate +static QDate msecsToDate(qint64 msecs) +{ + qint64 jd = JULIAN_DAY_FOR_EPOCH; + + if (qAbs(msecs) >= MSECS_PER_DAY) { + jd += (msecs / MSECS_PER_DAY); + msecs %= MSECS_PER_DAY; + } + + if (msecs < 0) { + qint64 ds = MSECS_PER_DAY - msecs - 1; + jd -= ds / MSECS_PER_DAY; + } + + return QDate::fromJulianDay(jd); +} + +static SYSTEMTIME msecsToSystemtime(qint64 forMSecsSinceEpoch) +{ + FILETIME utcFileTime; + ULONGLONG nsecs = (forMSecsSinceEpoch * 10000 ) + FILETIME_UNIX_EPOCH; + utcFileTime.dwLowDateTime = (DWORD) (nsecs & 0xFFFFFFFF); + utcFileTime.dwHighDateTime = (DWORD) (nsecs >> 32); + SYSTEMTIME utcTime; + FileTimeToSystemTime(&utcFileTime, &utcTime); + return utcTime; +} + +static qint64 systemtimeToMsecs(const SYSTEMTIME &systemtime) +{ + FILETIME utcFileTime; + SystemTimeToFileTime(&systemtime, &utcFileTime); + ULONGLONG utcNSecs = (((ULONGLONG) utcFileTime.dwHighDateTime) << 32) + utcFileTime.dwLowDateTime; + return (utcNSecs - FILETIME_UNIX_EPOCH) / 10000; +} + +static bool equalSystemtime(const SYSTEMTIME &t1, const SYSTEMTIME &t2) +{ + return (t1.wYear == t2.wYear + && t1.wMonth == t2.wMonth + && t1.wDay == t2.wDay + && t1.wDayOfWeek == t2.wDayOfWeek + && t1.wHour == t2.wHour + && t1.wMinute == t2.wMinute + && t1.wSecond == t2.wSecond + && t1.wMilliseconds == t2.wMilliseconds); +} + +static bool equalTzi(const TIME_ZONE_INFORMATION &tzi1, const TIME_ZONE_INFORMATION &tzi2) +{ + return(tzi1.Bias == tzi2.Bias + && tzi1.StandardBias == tzi2.StandardBias + && equalSystemtime(tzi1.StandardDate, tzi2.StandardDate) + && wcscmp(tzi1.StandardName, tzi2.StandardName) == 0 + && tzi1.DaylightBias == tzi2.DaylightBias + && equalSystemtime(tzi1.DaylightDate, tzi2.DaylightDate) + && wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0); +} + +static bool openRegistryKey(const QString &keyPath, HKEY *key) +{ + return (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (const wchar_t*)keyPath.utf16(), 0, KEY_READ, key) + == ERROR_SUCCESS); +} + +static QString readRegistryString(const HKEY &key, const wchar_t *value) +{ + wchar_t buffer[MAX_PATH] = {0}; + DWORD size = sizeof(wchar_t) * MAX_PATH; + RegQueryValueEx(key, (LPCWSTR)value, NULL, NULL, (LPBYTE)buffer, &size); + return QString::fromWCharArray(buffer); +} + +static int readRegistryValue(const HKEY &key, const wchar_t *value) +{ + DWORD buffer; + DWORD size = sizeof(buffer); + RegQueryValueEx(key, (LPCWSTR)value, NULL, NULL, (LPBYTE)&buffer, &size); + return buffer; +} + +static QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(const HKEY &key, + const wchar_t *value, bool *ok) +{ + *ok = false; + QWinTimeZonePrivate::QWinTransitionRule rule; + REG_TZI_FORMAT tzi; + DWORD tziSize = sizeof(tzi); + if (RegQueryValueEx(key, (LPCWSTR)value, NULL, NULL, (BYTE *)&tzi, &tziSize) + == ERROR_SUCCESS) { + rule.startYear = 0; + rule.standardTimeBias = tzi.Bias + tzi.StandardBias; + rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias; + rule.standardTimeRule = tzi.StandardDate; + rule.daylightTimeRule = tzi.DaylightDate; + *ok = true; + } + return rule; +} + +static TIME_ZONE_INFORMATION getRegistryTzi(const QByteArray &windowsId, bool *ok) +{ + *ok = false; + TIME_ZONE_INFORMATION tzi; + REG_TZI_FORMAT regTzi; + DWORD regTziSize = sizeof(regTzi); + HKEY key = NULL; + const QString tziKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\') + + QString::fromUtf8(windowsId); + + if (openRegistryKey(tziKeyPath, &key)) { + + DWORD size = sizeof(tzi.DaylightName); + RegQueryValueEx(key, L"Dlt", NULL, NULL, (LPBYTE)tzi.DaylightName, &size); + + size = sizeof(tzi.StandardName); + RegQueryValueEx(key, L"Std", NULL, NULL, (LPBYTE)tzi.StandardName, &size); + + if (RegQueryValueEx(key, L"TZI", NULL, NULL, (BYTE *) ®Tzi, ®TziSize) + == ERROR_SUCCESS) { + tzi.Bias = regTzi.Bias; + tzi.StandardBias = regTzi.StandardBias; + tzi.DaylightBias = regTzi.DaylightBias; + tzi.StandardDate = regTzi.StandardDate; + tzi.DaylightDate = regTzi.DaylightDate; + *ok = true; + } + + RegCloseKey(key); + } + + return tzi; +} + +static QList availableWindowsIds() +{ + // TODO Consider caching results in a global static, very unlikely to change. + QList list; + HKEY key = NULL; + if (openRegistryKey(QString::fromUtf8(tzRegPath), &key)) { + DWORD idCount = 0; + if (RegQueryInfoKey(key, 0, 0, 0, &idCount, 0, 0, 0, 0, 0, 0, 0) == ERROR_SUCCESS + && idCount > 0) { + for (DWORD i = 0; i < idCount; ++i) { + DWORD maxLen = MAX_KEY_LENGTH; + TCHAR buffer[MAX_KEY_LENGTH]; + if (RegEnumKeyEx(key, i, buffer, &maxLen, 0, 0, 0, 0) == ERROR_SUCCESS) + list.append(QString::fromWCharArray(buffer).toUtf8()); + } + } + RegCloseKey(key); + } + return list; +} + +static QByteArray windowsSystemZoneId() +{ + // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath + QString id; + HKEY key = NULL; + QString tziKeyPath = QString::fromUtf8(currTzRegPath); + if (openRegistryKey(tziKeyPath, &key)) { + id = readRegistryString(key, L"TimeZoneKeyName"); + RegCloseKey(key); + if (!id.isEmpty()) + return id.toUtf8(); + } + + // On XP we have to iterate over the zones until we find a match on + // names/offsets with the current data + TIME_ZONE_INFORMATION sysTzi; + GetTimeZoneInformation(&sysTzi); + bool ok = false; + foreach (const QByteArray &winId, availableWindowsIds()) { + if (equalTzi(getRegistryTzi(winId, &ok), sysTzi)) + return winId; + } + + // If we can't determine the current ID use UTC + return QByteArrayLiteral("UTC"); +} + +static QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year) +{ + SYSTEMTIME time = rule; + // If the year isn't set, then the rule date is relative + if (time.wYear == 0) { + if (time.wDayOfWeek == 0) + time.wDayOfWeek = 7; + QDate date(year, time.wMonth, 1); + int startDow = date.dayOfWeek(); + if (startDow <= time.wDayOfWeek) + date = date.addDays(time.wDayOfWeek - startDow - 7); + else + date = date.addDays(time.wDayOfWeek - startDow); + date = date.addDays(time.wDay * 7); + while (date.month() != time.wMonth) + date = date.addDays(-7); + return date; + } else { + return QDate(time.wYear, time.wMonth, time.wDay); + } +} + +// Converts a date/time value into msecs +static inline qint64 timeToMSecs(const QDate &date, const QTime &time) +{ + return ((date.toJulianDay() - JULIAN_DAY_FOR_EPOCH) * MSECS_PER_DAY) + + time.msecsSinceStartOfDay(); +} + +static void calculateTransitionsForYear(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year, + qint64 *stdMSecs, qint64 *dstMSecs) +{ + // TODO Consider caching the calculated values + + // The local time in Daylight Time when switches to Standard TIme + QDate standardDate = calculateTransitionLocalDate(rule.standardTimeRule, year); + QTime standardTime = QTime(rule.standardTimeRule.wHour, rule.standardTimeRule.wMinute, + rule.standardTimeRule.wSecond); + // The local time in Standard Time when switches to Daylight TIme + QDate daylightDate = calculateTransitionLocalDate(rule.daylightTimeRule, year); + QTime daylightTime = QTime(rule.daylightTimeRule.wHour, rule.daylightTimeRule.wMinute, + rule.daylightTimeRule.wSecond); + + *stdMSecs = timeToMSecs(standardDate, standardTime) + + ((rule.standardTimeBias + rule.daylightTimeBias) * 60000); + *dstMSecs = timeToMSecs(daylightDate, daylightTime) + (rule.standardTimeBias * 60000); +} + +static QLocale::Country userCountry() +{ +#if defined(Q_OS_WINCE) + // Guess that the syslem locale country is the right one to use + // TODO Find if WinCE has equivalent api + return QLocale::system().country(); +#else + const GEOID id = GetUserGeoID(GEOCLASS_NATION); + wchar_t code[3]; + const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0); + return (size == 3) ? QLocalePrivate::codeToCountry(QString::fromWCharArray(code)) + : QLocale::AnyCountry; +#endif // Q_OS_WINCE +} + +// Create the system default time zone +QWinTimeZonePrivate::QWinTimeZonePrivate() + : QTimeZonePrivate() +{ + init(QByteArray()); +} + +// Create a named time zone +QWinTimeZonePrivate::QWinTimeZonePrivate(const QByteArray &olsenId) + : QTimeZonePrivate() +{ + init(olsenId); +} + +QWinTimeZonePrivate::QWinTimeZonePrivate(const QWinTimeZonePrivate &other) + : QTimeZonePrivate(other), m_windowsId(other.m_windowsId), + m_displayName(other.m_displayName), m_standardName(other.m_standardName), + m_daylightName(other.m_daylightName), m_tranRules(other.m_tranRules) +{ +} + +QWinTimeZonePrivate::~QWinTimeZonePrivate() +{ +} + +QTimeZonePrivate *QWinTimeZonePrivate::clone() +{ + return new QWinTimeZonePrivate(*this); +} + +void QWinTimeZonePrivate::init(const QByteArray &olsenId) +{ + if (olsenId.isEmpty()) { + m_windowsId = windowsSystemZoneId(); + m_id = systemTimeZoneId(); + } else { + m_windowsId = olsenIdToWindowsId(olsenId); + m_id = olsenId; + } + + if (!m_windowsId.isEmpty()) { + // Open the base TZI for the time zone + HKEY baseKey = NULL; + const QString baseKeyPath = QString::fromUtf8(tzRegPath) + QLatin1Char('\\') + + QString::fromUtf8(m_windowsId); + if (openRegistryKey(baseKeyPath, &baseKey)) { + // Load the localized names + m_displayName = readRegistryString(baseKey, L"Display"); + m_standardName = readRegistryString(baseKey, L"Std"); + m_daylightName = readRegistryString(baseKey, L"Dlt"); + // On Vista and later the optional dynamic key holds historic data + const QString dynamicKeyPath = baseKeyPath + QStringLiteral("\\Dynamic DST"); + HKEY dynamicKey = NULL; + if (openRegistryKey(dynamicKeyPath, &dynamicKey)) { + // Find out the start and end years stored, then iterate over them + int startYear = readRegistryValue(dynamicKey, L"FirstEntry"); + int endYear = readRegistryValue(dynamicKey, L"LastEntry"); + for (int year = startYear; year <= endYear; ++year) { + bool ruleOk; + QWinTransitionRule rule = readRegistryRule(dynamicKey, + (LPCWSTR)QString::number(year).utf16(), + &ruleOk); + rule.startYear = year; + if (ruleOk) + m_tranRules.append(rule); + } + RegCloseKey(dynamicKey); + } else { + // No dynamic data so use the base data + bool ruleOk; + QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk); + rule.startYear = 1970; + if (ruleOk) + m_tranRules.append(rule); + } + RegCloseKey(baseKey); + } + } + + // If there are no rules then we failed to find a windowsId or any tzi info + if (m_tranRules.size() == 0) { + m_id.clear(); + m_windowsId.clear(); + m_displayName.clear(); + } +} + +QString QWinTimeZonePrivate::comment() const +{ + return m_displayName; +} + +QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + // TODO Registry holds MUI keys, should be able to look up translations? + Q_UNUSED(locale); + + if (nameType == QTimeZone::OffsetName) { + QWinTransitionRule rule = ruleForYear(QDate::currentDate().year()); + if (timeType == QTimeZone::DaylightTime) + return isoOffsetFormat((rule.standardTimeBias + rule.daylightTimeBias) * -60); + else + return isoOffsetFormat((rule.standardTimeBias) * -60); + } + + switch (timeType) { + case QTimeZone::DaylightTime : + return m_daylightName; + case QTimeZone::GenericTime : + return m_displayName; + case QTimeZone::StandardTime : + return m_standardName; + } + return m_standardName; +} + +QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).abbreviation; +} + +int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).offsetFromUtc; +} + +int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).standardTimeOffset; +} + +int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const +{ + return data(atMSecsSinceEpoch).daylightTimeOffset; +} + +bool QWinTimeZonePrivate::hasDaylightTime() const +{ + return hasTransitions(); +} + +bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + return (data(atMSecsSinceEpoch).daylightTimeOffset != 0); +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const +{ + // Convert MSecs to year to get transitions for, but around 31 Dec/1 Jan may not be right year + // So get the year after we think we want transitions for, to be safe + QDate date = msecsToDate(forMSecsSinceEpoch); + int year; + int month; + int day; + date.getDate(&year, &month, &day); + if ((month == 12 && day == 31) || (month == 1 && day == 1)) + ++year; + + qint64 first; + qint64 second; + qint64 next = maxMSecs(); + qint64 stdMSecs; + qint64 dstMSecs; + QWinTransitionRule rule; + do { + // Convert the transition rules into msecs for the year we want to try + rule = ruleForYear(year); + calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); + first = qMin(stdMSecs, dstMSecs); + second = qMax(stdMSecs, dstMSecs); + if (forMSecsSinceEpoch >= second) + next = second; + else if (forMSecsSinceEpoch >= first) + next = first; + // If didn't fall in this year, try the previous + --year; + } while (forMSecsSinceEpoch < first && year >= MIN_YEAR); + + if (next == dstMSecs) + return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::DaylightTime); + else + return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime); +} + +bool QWinTimeZonePrivate::hasTransitions() const +{ + foreach (const QWinTransitionRule &rule, m_tranRules) { + if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0) + return true; + } + return false; +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + // Convert MSecs to year to get transitions for, but around 31 Dec/1 Jan may not be right year + // Get the year before we think we want transitions for, to be safe + QDate date = msecsToDate(afterMSecsSinceEpoch); + int year; + int month; + int day; + date.getDate(&year, &month, &day); + if ((month == 12 && day == 31) || (month == 1 && day == 1)) + --year; + + qint64 first; + qint64 second; + qint64 next = minMSecs(); + qint64 stdMSecs; + qint64 dstMSecs; + QWinTransitionRule rule; + do { + // Convert the transition rules into msecs for the year we want to try + rule = ruleForYear(year); + calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); + // Find the first and second transition for the year + first = qMin(stdMSecs, dstMSecs); + second = qMax(stdMSecs, dstMSecs); + if (afterMSecsSinceEpoch < first) + next = first; + else if (afterMSecsSinceEpoch < second) + next = second; + // If didn't fall in this year, try the next + ++year; + } while (afterMSecsSinceEpoch >= second && year <= MAX_YEAR); + + if (next == dstMSecs) + return ruleToData(rule, next, QTimeZone::DaylightTime); + else + return ruleToData(rule, next, QTimeZone::StandardTime); +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // Convert MSecs to year to get transitions for, but around 31 Dec/1 Jan may not be right year + // So get the year after we think we want transitions for, to be safe + QDate date = msecsToDate(beforeMSecsSinceEpoch); + int year; + int month; + int day; + date.getDate(&year, &month, &day); + if ((month == 12 && day == 31) || (month == 1 && day == 1)) + ++year; + + qint64 first; + qint64 second; + qint64 next = maxMSecs(); + qint64 stdMSecs; + qint64 dstMSecs; + QWinTransitionRule rule; + do { + // Convert the transition rules into msecs for the year we want to try + rule = ruleForYear(year); + calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); + first = qMin(stdMSecs, dstMSecs); + second = qMax(stdMSecs, dstMSecs); + if (beforeMSecsSinceEpoch > second) + next = second; + else if (beforeMSecsSinceEpoch > first) + next = first; + // If didn't fall in this year, try the previous + --year; + } while (beforeMSecsSinceEpoch < first && year >= MIN_YEAR); + + if (next == dstMSecs) + return ruleToData(rule, next, QTimeZone::DaylightTime); + else + return ruleToData(rule, next, QTimeZone::StandardTime); +} + +QByteArray QWinTimeZonePrivate::systemTimeZoneId() const +{ + const QLocale::Country country = userCountry(); + const QByteArray windowsId = windowsSystemZoneId(); + QByteArray olsenId; + // If we have a real country, then try get a specific match for that country + if (country != QLocale::AnyCountry) + olsenId = windowsIdToDefaultOlsenId(windowsId, country); + // If we don't have a real country, or there wasn't a specific match, try the global default + if (olsenId.isEmpty()) { + olsenId = windowsIdToDefaultOlsenId(windowsId); + // If no global default then probably an unknown Windows ID so return UTC + if (olsenId.isEmpty()) + return QByteArrayLiteral("UTC"); + } + return olsenId; +} + +QSet QWinTimeZonePrivate::availableTimeZoneIds() const +{ + QSet set; + foreach (const QByteArray &winId, availableWindowsIds()) { + foreach (const QByteArray &olsenId, windowsIdToOlsenIds(winId)) + set << olsenId; + } + return set; +} + +QWinTimeZonePrivate::QWinTransitionRule QWinTimeZonePrivate::ruleForYear(int year) const +{ + for (int i = m_tranRules.size() - 1; i >= 0; --i) { + if (m_tranRules.at(i).startYear <= year) + return m_tranRules.at(i); + } + return m_tranRules.at(0); +} + +QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(const QWinTransitionRule &rule, + qint64 atMSecsSinceEpoch, + QTimeZone::TimeType type) const +{ + QTimeZonePrivate::Data tran = QTimeZonePrivate::invalidData(); + tran.atMSecsSinceEpoch = atMSecsSinceEpoch; + tran.standardTimeOffset = rule.standardTimeBias * -60; + if (type == QTimeZone::DaylightTime) { + tran.daylightTimeOffset = rule.daylightTimeBias * -60; + tran.abbreviation = m_daylightName; + } else { + tran.daylightTimeOffset = 0; + tran.abbreviation = m_standardName; + } + tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset; + return tran; +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 2980d361db..e38bea3181 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -124,7 +124,7 @@ else:blackberry { HEADERS += tools/qlocale_blackberry.h } 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 +else:win32:SOURCES += tools/qelapsedtimer_win.cpp tools/qlocale_win.cpp tools/qtimezoneprivate_win.cpp else:integrity:SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp else:SOURCES += tools/qelapsedtimer_generic.cpp -- cgit v1.2.3