summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/tools/qtimezoneprivate_p.h2
-rw-r--r--src/corelib/tools/qtimezoneprivate_win.cpp121
2 files changed, 106 insertions, 17 deletions
diff --git a/src/corelib/tools/qtimezoneprivate_p.h b/src/corelib/tools/qtimezoneprivate_p.h
index 9b7adbbbbe..ca36f9d297 100644
--- a/src/corelib/tools/qtimezoneprivate_p.h
+++ b/src/corelib/tools/qtimezoneprivate_p.h
@@ -432,7 +432,7 @@ public:
private:
void init(const QByteArray &ianaId);
QTimeZonePrivate::Data ruleToData(const QWinTransitionRule &rule, qint64 atMSecsSinceEpoch,
- QTimeZone::TimeType type) const;
+ QTimeZone::TimeType type, bool fakeDst = false) const;
QByteArray m_windowsId;
QString m_displayName;
diff --git a/src/corelib/tools/qtimezoneprivate_win.cpp b/src/corelib/tools/qtimezoneprivate_win.cpp
index ba6a33e729..56fdd5b181 100644
--- a/src/corelib/tools/qtimezoneprivate_win.cpp
+++ b/src/corelib/tools/qtimezoneprivate_win.cpp
@@ -401,14 +401,82 @@ struct TransitionTimePair
{
// Transition times after the epoch, in ms:
qint64 std, dst;
- TransitionTimePair(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year)
+ // If either is invalidMSecs(), which shall then be < the other, there is no
+ // DST and the other describes a change in actual standard offset.
+
+ TransitionTimePair(const QWinTimeZonePrivate::QWinTransitionRule &rule,
+ int year, int oldYearOffset)
// The local time in Daylight Time of the switch to Standard Time
: std(calculateTransitionForYear(rule.standardTimeRule, year,
rule.standardTimeBias + rule.daylightTimeBias)),
// The local time in Standard Time of the switch to Daylight Time
dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias))
- {}
+ {
+ /*
+ Check for potential "fake DST", used by MS's APIs because the
+ TIME_ZONE_INFORMATION spec either expresses no transitions in the
+ year, or expresses a transition of each kind, even if standard time
+ did change in a year with no DST. We've seen year-start fake-DST
+ (whose offset matches prior standard offset, in which the previous
+ year ended); and conjecture that similar might be used at a year-end.
+ (This might be used for a southern-hemisphere zone, where the start of
+ the year usually is in DST, when applicable.) Note that, here, wDay
+ identifies an instance of a given day-of-week in the month, with 5
+ meaning last.
+
+ Either the alleged standardTimeRule or the alleged daylightTimeRule
+ may be faked; either way, the transition is actually a change to the
+ current standard offset; but the unfaked half of the rule contains the
+ useful bias data, so we have to go along with its lies.
+
+ Example: Russia/Moscow
+ Format: -bias +( -stdBias, stdDate | -dstBias, dstDate ) notes
+ Last year of DST, 2010: 180 +( 0, 0-10-5 3:0 | 60, 0-3-5 2:0 ) normal DST
+ Zone change in 2011: 180 +( 0, 0-1-1 0:0 | 60 0-3-5 2:0 ) fake DST at transition
+ Fixed standard in 2012: 240 +( 0, 0-0-0 0:0 | 60, 0-0-0 0:0 ) standard time years
+ Zone change in 2014: 180 +( 0, 0-10-5 2:0 | 60, 0-1-1 0:0 ) fake DST at year-start
+ The last of these is missing on Win7 VMs (too old to know about it).
+ */
+ if (rule.daylightTimeRule.wMonth == 1 && rule.daylightTimeRule.wDay == 1) {
+ // Fake "DST transition" at start of year producing the same offset as
+ // previous year ended in.
+ if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset)
+ dst = QTimeZonePrivate::invalidMSecs();
+ } else if (rule.daylightTimeRule.wMonth == 12 && rule.daylightTimeRule.wDay > 3) {
+ // Similar, conjectured, for end of year, not changing offset.
+ if (rule.daylightTimeBias == 0)
+ dst = QTimeZonePrivate::invalidMSecs();
+ }
+ if (rule.standardTimeRule.wMonth == 1 && rule.standardTimeRule.wDay == 1) {
+ // Fake "transition out of DST" at start of year producing the same
+ // offset as previous year ended in.
+ if (rule.standardTimeBias == oldYearOffset)
+ std = QTimeZonePrivate::invalidMSecs();
+ } else if (rule.standardTimeRule.wMonth == 12 && rule.standardTimeRule.wDay > 3) {
+ // Similar, conjectured, for end of year, not changing offset.
+ if (rule.daylightTimeBias == 0)
+ std = QTimeZonePrivate::invalidMSecs();
+ }
+ }
+
+ bool fakesDst() const
+ {
+ return std == QTimeZonePrivate::invalidMSecs()
+ || dst == QTimeZonePrivate::invalidMSecs();
+ }
};
+
+int yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year)
+{
+ int offset = rule.standardTimeBias;
+ // Only needed to help another TransitionTimePair work out year + 1's start
+ // offset; and the oldYearOffset we use only affects an alleged transition
+ // at the *start* of this year, so it doesn't matter if we guess wrong here:
+ TransitionTimePair pair(rule, year, offset);
+ if (pair.dst > pair.std)
+ offset += rule.daylightTimeBias;
+ return offset;
+}
} // anonymous namespace
static QLocale::Country userCountry()
@@ -665,18 +733,22 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons
if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
const int endYear = qMax(rule.startYear, year - 1);
while (year >= endYear) {
- const TransitionTimePair pair(rule, year);
+ const int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
+ ? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1)
+ : yearEndOffset(rule, year - 1);
+ const TransitionTimePair pair(rule, year, newYearOffset);
bool isDst = false;
- if (pair.std <= forMSecsSinceEpoch) {
+ if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) {
isDst = pair.std < pair.dst && pair.dst <= forMSecsSinceEpoch;
- } else if (pair.dst <= forMSecsSinceEpoch) {
+ } else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) {
isDst = true;
} else {
--year; // Try an earlier year for this rule (once).
continue;
}
return ruleToData(rule, forMSecsSinceEpoch,
- isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime);
+ isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime,
+ pair.fakesDst());
}
// Fell off start of rule, try previous rule.
} else {
@@ -711,21 +783,28 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinc
year = rule.startYear; // Seek first transition in this rule.
const int endYear = ruleIndex + 1 < m_tranRules.count()
? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2);
+ int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
+ ? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1)
+ : yearEndOffset(rule, year - 1);
while (year < endYear) {
- const TransitionTimePair pair(rule, year);
+ const TransitionTimePair pair(rule, year, newYearOffset);
bool isDst = false;
+ Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch); // invalid is min qint64
if (pair.std > afterMSecsSinceEpoch) {
isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch;
} else if (pair.dst > afterMSecsSinceEpoch) {
isDst = true;
} else {
+ newYearOffset = rule.standardTimeBias;
+ if (pair.dst > pair.std)
+ newYearOffset += rule.daylightTimeBias;
++year; // Try a later year for this rule (once).
continue;
}
if (isDst)
- return ruleToData(rule, pair.dst, QTimeZone::DaylightTime);
- return ruleToData(rule, pair.std, QTimeZone::StandardTime);
+ return ruleToData(rule, pair.dst, QTimeZone::DaylightTime, pair.fakesDst());
+ return ruleToData(rule, pair.std, QTimeZone::StandardTime, pair.fakesDst());
}
// Fell off end of rule, try next rule.
} // else: no transition during rule's period
@@ -744,19 +823,22 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSec
if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
const int endYear = qMax(rule.startYear, year - 1);
while (year >= endYear) {
- const TransitionTimePair pair(rule, year);
+ const int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
+ ? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1)
+ : yearEndOffset(rule, year - 1);
+ const TransitionTimePair pair(rule, year, newYearOffset);
bool isDst = false;
- if (pair.std < beforeMSecsSinceEpoch) {
+ if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) {
isDst = pair.std < pair.dst && pair.dst < beforeMSecsSinceEpoch;
- } else if (pair.dst < beforeMSecsSinceEpoch) {
+ } else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) {
isDst = true;
} else {
--year; // Try an earlier year for this rule (once).
continue;
}
if (isDst)
- return ruleToData(rule, pair.dst, QTimeZone::DaylightTime);
- return ruleToData(rule, pair.std, QTimeZone::StandardTime);
+ return ruleToData(rule, pair.dst, QTimeZone::DaylightTime, pair.fakesDst());
+ return ruleToData(rule, pair.std, QTimeZone::StandardTime, pair.fakesDst());
}
// Fell off start of rule, try previous rule.
} // else: no transition during rule's period
@@ -798,12 +880,19 @@ QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds() const
QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(const QWinTransitionRule &rule,
qint64 atMSecsSinceEpoch,
- QTimeZone::TimeType type) const
+ QTimeZone::TimeType type,
+ bool fakeDst) const
{
Data tran = invalidData();
tran.atMSecsSinceEpoch = atMSecsSinceEpoch;
tran.standardTimeOffset = rule.standardTimeBias * -60;
- if (type == QTimeZone::DaylightTime) {
+ if (fakeDst) {
+ tran.daylightTimeOffset = 0;
+ tran.abbreviation = m_standardName;
+ // Rule may claim we're in DST when it's actually a standard time change:
+ if (type == QTimeZone::DaylightTime)
+ tran.standardTimeOffset += rule.daylightTimeBias * -60;
+ } else if (type == QTimeZone::DaylightTime) {
tran.daylightTimeOffset = rule.daylightTimeBias * -60;
tran.abbreviation = m_daylightName;
} else {