QDateTime, QTimeZone: fix mappings from zone time to UTC
Such mappings are ill-defined in the presence of daylight-savings time (DST); at its transitions, you need information about whether DST is active or not to determine the correct UTC value. Existing code did not have a way to be told that hint, so could not be correct. Fixing this required changing the (thankfully private) APIs by which QDateTime accessed QTimeZone's information stipulated by zone time. In QDateTime, this required propagating the needed hint, when DST status was known. QAndroidTimeZonePrivate overloaded QTimeZonePrivate::dataForLocalTime with an implementation that works whenever !hasTransitions(); the base implementation handled this case lamely, so I've moved the Android implementation there, to have only one place for both re-writes. Amended tst_QDateTime's expected failures; passing a date and time to the constructor *is* ambiguous when the moment indicated is in a transition. I have changed which way we resolve that ambiguity. Added round-trip test of QDateTime's fromMSecs/toMSecs (but as a QTimeZone test, since that's what's actually getting tested), based on a test-case from Marko Kangas. Initially failed for various zones, each at one hour-offset; and, on some platforms, for some zones, at all offsets. These last revealed that a platform may claim to have zone information yet, for some zones, lack it (or have very incomplete information). In each case, despite this, the platform does give offsetFromUtc(). (The test also found another pre-existing bug on Linux; fixed in an earlier commit.) To accommodate these gaps in transition data, the transition-based code now falls back to the offsetFromUtc()-based code (used when there are no transitions) if it can't find a previous transition (which, in any case, it needs to do its job). Task-number: QTBUG-56460 Task-number: QTBUG-56397 Task-number: QTBUG-52284 Change-Id: I2f7422a9e9d3767940b1901d887c6a2c1f36ac9f Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
57b0f54bb6
commit
6e3f58cbbe
@ -2594,7 +2594,7 @@ static void refreshDateTime(QDateTimeData &d)
|
||||
if (!d->m_timeZone.isValid())
|
||||
status &= ~QDateTimePrivate::ValidDateTime;
|
||||
else
|
||||
epochMSecs = QDateTimePrivate::zoneMSecsToEpochMSecs(msecs, d->m_timeZone, &testDate, &testTime);
|
||||
epochMSecs = QDateTimePrivate::zoneMSecsToEpochMSecs(msecs, d->m_timeZone, extractDaylightStatus(status), &testDate, &testTime);
|
||||
}
|
||||
#endif // timezone
|
||||
|
||||
@ -2915,11 +2915,13 @@ inline QDateTime::Data QDateTimePrivate::create(const QDate &toDate, const QTime
|
||||
}
|
||||
|
||||
// Convert a TimeZone time expressed in zone msecs encoding into a UTC epoch msecs
|
||||
// DST transitions are disambiguated by hint.
|
||||
inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QTimeZone &zone,
|
||||
DaylightStatus hint,
|
||||
QDate *localDate, QTime *localTime)
|
||||
{
|
||||
// Get the effective data from QTimeZone
|
||||
QTimeZonePrivate::Data data = zone.d->dataForLocalTime(zoneMSecs);
|
||||
QTimeZonePrivate::Data data = zone.d->dataForLocalTime(zoneMSecs, int(hint));
|
||||
// Docs state any LocalTime before 1970-01-01 will *not* have any DST applied
|
||||
// but all affected times afterwards will have DST applied.
|
||||
if (data.atMSecsSinceEpoch >= 0) {
|
||||
@ -3532,7 +3534,8 @@ qint64 QDateTime::toMSecsSinceEpoch() const
|
||||
#if !QT_CONFIG(timezone)
|
||||
return 0;
|
||||
#else
|
||||
return QDateTimePrivate::zoneMSecsToEpochMSecs(d->m_msecs, d->m_timeZone);
|
||||
return QDateTimePrivate::zoneMSecsToEpochMSecs(d->m_msecs, d->m_timeZone,
|
||||
extractDaylightStatus(getStatus(d)));
|
||||
#endif
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
@ -3631,10 +3634,16 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
|
||||
// Docs state any LocalTime before 1970-01-01 will *not* have any DST applied
|
||||
// but all affected times afterwards will have DST applied.
|
||||
d.detach();
|
||||
if (msecs >= 0)
|
||||
if (msecs >= 0) {
|
||||
status = mergeDaylightStatus(status,
|
||||
d->m_timeZone.d->isDaylightTime(msecs)
|
||||
? QDateTimePrivate::DaylightTime
|
||||
: QDateTimePrivate::StandardTime);
|
||||
d->m_offsetFromUtc = d->m_timeZone.d->offsetFromUtc(msecs);
|
||||
else
|
||||
} else {
|
||||
status = mergeDaylightStatus(status, QDateTimePrivate::StandardTime);
|
||||
d->m_offsetFromUtc = d->m_timeZone.d->standardTimeOffset(msecs);
|
||||
}
|
||||
msecs = msecs + (d->m_offsetFromUtc * 1000);
|
||||
status = status
|
||||
| QDateTimePrivate::ValidDate
|
||||
@ -3922,7 +3931,10 @@ static inline void massageAdjustedDateTime(const QDateTimeData &d, QDate *date,
|
||||
localMSecsToEpochMSecs(timeToMSecs(*date, *time), &status, date, time);
|
||||
#if QT_CONFIG(timezone)
|
||||
} else if (spec == Qt::TimeZone) {
|
||||
QDateTimePrivate::zoneMSecsToEpochMSecs(timeToMSecs(*date, *time), d->m_timeZone, date, time);
|
||||
QDateTimePrivate::zoneMSecsToEpochMSecs(timeToMSecs(*date, *time),
|
||||
d->m_timeZone,
|
||||
QDateTimePrivate::UnknownDaylightTime,
|
||||
date, time);
|
||||
#endif // timezone
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,7 @@ public:
|
||||
|
||||
#if QT_CONFIG(timezone)
|
||||
static qint64 zoneMSecsToEpochMSecs(qint64 msecs, const QTimeZone &zone,
|
||||
DaylightStatus hint = UnknownDaylightTime,
|
||||
QDate *localDate = 0, QTime *localTime = 0);
|
||||
#endif // timezone
|
||||
|
||||
|
@ -49,10 +49,6 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
enum {
|
||||
MSECS_TRAN_WINDOW = 21600000 // 6 hour window for possible recent transitions
|
||||
};
|
||||
|
||||
/*
|
||||
Static utilities for looking up Windows ID tables
|
||||
*/
|
||||
@ -248,67 +244,206 @@ QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
|
||||
}
|
||||
|
||||
// Private only method for use by QDateTime to convert local msecs to epoch msecs
|
||||
// TODO Could be platform optimised if needed
|
||||
QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs) const
|
||||
QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int hint) const
|
||||
{
|
||||
if (!hasDaylightTime() ||!hasTransitions()) {
|
||||
// No DST means same offset for all local msecs
|
||||
// Having DST but no transitions means we can't calculate, so use nearest
|
||||
return data(forLocalMSecs - (standardTimeOffset(forLocalMSecs) * 1000));
|
||||
}
|
||||
if (!hasDaylightTime()) // No DST means same offset for all local msecs
|
||||
return data(forLocalMSecs - standardTimeOffset(forLocalMSecs) * 1000);
|
||||
|
||||
// Get the transition for the local msecs which most of the time should be the right one
|
||||
// Only around the transition times might it not be the right one
|
||||
Data tran = previousTransition(forLocalMSecs);
|
||||
Data nextTran;
|
||||
/*
|
||||
We need a UTC time at which to ask for the offset, in order to be able to
|
||||
add that offset to forLocalMSecs, to get the UTC time we
|
||||
need. Fortunately, no time-zone offset is more than 14 hours; and DST
|
||||
transitions happen (much) more than thirty-two hours apart. So sampling
|
||||
offset sixteen hours each side gives us information we can be sure
|
||||
brackets the correct time and at most one DST transition.
|
||||
*/
|
||||
const qint64 sixteenHoursInMSecs(16 * 3600 * 1000);
|
||||
/*
|
||||
Offsets are Local - UTC, positive to the east of Greenwich, negative to
|
||||
the west; DST offset always exceeds standard offset, when DST applies.
|
||||
When we have offsets on either side of a transition, the lower one is
|
||||
standard, the higher is DST.
|
||||
|
||||
// If the local msecs is less than the real local time of the transition
|
||||
// then get the previous transition to use instead
|
||||
if (forLocalMSecs < tran.atMSecsSinceEpoch + (tran.offsetFromUtc * 1000)) {
|
||||
while (tran.atMSecsSinceEpoch != invalidMSecs()
|
||||
&& forLocalMSecs < tran.atMSecsSinceEpoch + (tran.offsetFromUtc * 1000)) {
|
||||
nextTran = tran;
|
||||
tran = previousTransition(tran.atMSecsSinceEpoch);
|
||||
}
|
||||
} else {
|
||||
// The zone msecs is after the transition, so check it is before the next tran
|
||||
// If not try use the next transition instead
|
||||
nextTran = nextTransition(tran.atMSecsSinceEpoch);
|
||||
Non-DST transitions (jurisdictions changing time-zone and time-zones
|
||||
changing their standard offset, typically) are described below as if they
|
||||
were DST transitions (since these are more usual and familiar); the code
|
||||
mostly concerns itself with offsets from UTC, described in terms of the
|
||||
common case for changes in that. If there is no actual change in offset
|
||||
(e.g. a DST transition cancelled by a standard offset change), this code
|
||||
should handle it gracefully; without transitions, it'll see early == late
|
||||
and take the easy path; with transitions, tran and nextTran get the
|
||||
correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
|
||||
the right one. In all other cases, the transition changes offset and the
|
||||
reasoning that applies to DST applies just the same. Aside from hinting,
|
||||
the only thing that looks at DST-ness at all, other than inferred from
|
||||
offset changes, is the case without transition data handling an invalid
|
||||
time in the gap that a transition passed over.
|
||||
|
||||
The handling of hint (see below) is apt to go wrong in non-DST
|
||||
transitions. There isn't really a great deal we can hope to do about that
|
||||
without adding yet more unreliable complexity to the heuristics in use for
|
||||
already obscure corner-cases.
|
||||
*/
|
||||
|
||||
/*
|
||||
The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller
|
||||
thinks we're in DST, 0 if in standard. A value of -2 means never-DST, so
|
||||
should have been handled above; if it slips through, it's wrong but we
|
||||
should probably treat it as standard anyway (never-DST means
|
||||
always-standard, after all). If the hint turns out to be wrong, fall back
|
||||
on trying the other possibility: which makes it harmless to treat -1
|
||||
(meaning unknown) as standard (i.e. try standard first, then try DST). In
|
||||
practice, away from a transition, the only difference hint makes is to
|
||||
which candidate we try first: if the hint is wrong (or unknown and
|
||||
standard fails), we'll try the other candidate and it'll work.
|
||||
|
||||
For the obscure (and invalid) case where forLocalMSecs falls in a
|
||||
spring-forward's missing hour, a common case is that we started with a
|
||||
date/time for which the hint was valid and adjusted it naively; for that
|
||||
case, we should correct the adjustment by shunting across the transition
|
||||
into where hint is wrong. So half-way through the gap, arrived at from
|
||||
the DST side, should be read as an hour earlier, in standard time; but, if
|
||||
arrived at from the standard side, should be read as an hour later, in
|
||||
DST. (This shall be wrong in some cases; for example, when a country
|
||||
changes its transition dates and changing a date/time by more than six
|
||||
months lands it on a transition. However, these cases are even more
|
||||
obscure than those where the heuristic is good.)
|
||||
*/
|
||||
|
||||
if (hasTransitions()) {
|
||||
/*
|
||||
We have transitions.
|
||||
|
||||
Each transition gives the offsets to use until the next; so we need the
|
||||
most recent transition before the time forLocalMSecs describes. If it
|
||||
describes a time *in* a transition, we'll need both that transition and
|
||||
the one before it. So find one transition that's probably after (and not
|
||||
much before, otherwise) and another that's definitely before, then work
|
||||
out which one to use. When both or neither work on forLocalMSecs, use
|
||||
hint to disambiguate.
|
||||
*/
|
||||
|
||||
// Get a transition definitely before the local MSecs; usually all we need.
|
||||
// Only around the transition times might we need another.
|
||||
Data tran = previousTransition(forLocalMSecs - sixteenHoursInMSecs);
|
||||
Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
|
||||
forLocalMSecs >= tran.atMSecsSinceEpoch + tran.offsetFromUtc * 1000);
|
||||
Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
|
||||
/*
|
||||
Now walk those forward until they bracket forLocalMSecs with transitions.
|
||||
|
||||
One of the transitions should then be telling us the right offset to use.
|
||||
In a transition, we need the transition before it (to describe the run-up
|
||||
to the transition) and the transition itself; so we need to stop when
|
||||
nextTran is that transition.
|
||||
*/
|
||||
while (nextTran.atMSecsSinceEpoch != invalidMSecs()
|
||||
&& forLocalMSecs >= nextTran.atMSecsSinceEpoch + (nextTran.offsetFromUtc * 1000)) {
|
||||
&& forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
|
||||
Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
|
||||
if (newTran.atMSecsSinceEpoch == invalidMSecs()
|
||||
|| newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000
|
||||
> forLocalMSecs + sixteenHoursInMSecs) {
|
||||
// Definitely not a relevant tansition: too far in the future.
|
||||
break;
|
||||
}
|
||||
tran = nextTran;
|
||||
nextTran = nextTransition(tran.atMSecsSinceEpoch);
|
||||
nextTran = newTran;
|
||||
}
|
||||
|
||||
// Check we do *really* have transitions for this zone:
|
||||
if (tran.atMSecsSinceEpoch != invalidMSecs()) {
|
||||
|
||||
/*
|
||||
So now tran is definitely before and nextTran is either after or only
|
||||
slightly before. The one with the larger offset is in DST; the other in
|
||||
standard time. Our hint tells us which of those to use (defaulting to
|
||||
standard if no hint): try it first; if that fails, try the other; if both
|
||||
fail life's tricky.
|
||||
*/
|
||||
Q_ASSERT(forLocalMSecs < 0
|
||||
|| forLocalMSecs > tran.atMSecsSinceEpoch + tran.offsetFromUtc * 1000);
|
||||
const qint64 nextStart = nextTran.atMSecsSinceEpoch;
|
||||
// Work out the UTC values it might make sense to return:
|
||||
nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
|
||||
tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
|
||||
|
||||
const bool nextIsDst = tran.offsetFromUtc < nextTran.offsetFromUtc;
|
||||
// If that agrees with hint > 0, our first guess is to use nextTran; else tran.
|
||||
const bool nextFirst = nextIsDst == (hint > 0) && nextStart != invalidMSecs();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
/*
|
||||
On the first pass, the case we consider is what hint told us to expect
|
||||
(except when hint was -1 and didn't actually tell us what to expect),
|
||||
so it's likely right. We only get a second pass if the first failed,
|
||||
by which time the second case, that we're trying, is likely right. If
|
||||
an overwhelming majority of calls have hint == -1, the Q_LIKELY here
|
||||
shall be wrong half the time; otherwise, its errors shall be rarer
|
||||
than that.
|
||||
*/
|
||||
if (nextFirst ? i == 0 : i) {
|
||||
Q_ASSERT(nextStart != invalidMSecs());
|
||||
if (Q_LIKELY(nextStart <= nextTran.atMSecsSinceEpoch))
|
||||
return nextTran;
|
||||
} else {
|
||||
// If next is invalid, nextFirst is false, to route us here first:
|
||||
if (nextStart == invalidMSecs() || Q_LIKELY(nextStart > tran.atMSecsSinceEpoch))
|
||||
return tran;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Neither is valid (e.g. in a spring-forward's gap) and
|
||||
nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch, so
|
||||
0 < tran.atMSecsSinceEpoch - nextTran.atMSecsSinceEpoch
|
||||
= (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000
|
||||
*/
|
||||
int dstStep = nextTran.offsetFromUtc - tran.offsetFromUtc;
|
||||
Q_ASSERT(dstStep > 0); // How else could we get here ?
|
||||
if (nextFirst) { // hint thought we needed nextTran, so use tran
|
||||
tran.atMSecsSinceEpoch -= dstStep;
|
||||
return tran;
|
||||
}
|
||||
nextTran.atMSecsSinceEpoch += dstStep;
|
||||
return nextTran;
|
||||
}
|
||||
// System has transitions but not for this zone.
|
||||
// Try falling back to offsetFromUtc
|
||||
}
|
||||
|
||||
/* Bracket and refine to discover offset. */
|
||||
qint64 utcEpochMSecs;
|
||||
|
||||
int early = offsetFromUtc(forLocalMSecs - sixteenHoursInMSecs);
|
||||
int late = offsetFromUtc(forLocalMSecs + sixteenHoursInMSecs);
|
||||
if (Q_LIKELY(early == late)) { // > 99% of the time
|
||||
utcEpochMSecs = forLocalMSecs - early * 1000;
|
||||
} else {
|
||||
// Close to a DST transition: early > late is near a fall-back,
|
||||
// early < late is near a spring-forward.
|
||||
const int offsetInDst = qMax(early, late);
|
||||
const int offsetInStd = qMin(early, late);
|
||||
// Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
|
||||
const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
|
||||
const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
|
||||
// Best guess at the answer:
|
||||
const qint64 hinted = hint > 0 ? forDst : forStd;
|
||||
if (Q_LIKELY(offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd))) {
|
||||
utcEpochMSecs = hinted;
|
||||
} else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) {
|
||||
utcEpochMSecs = forDst;
|
||||
} else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) {
|
||||
utcEpochMSecs = forStd;
|
||||
} else {
|
||||
// Invalid forLocalMSecs: in spring-forward gap.
|
||||
const int dstStep = daylightTimeOffset(early < late ?
|
||||
forLocalMSecs + sixteenHoursInMSecs :
|
||||
forLocalMSecs - sixteenHoursInMSecs);
|
||||
Q_ASSERT(dstStep); // There can't be a transition without it !
|
||||
utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
|
||||
}
|
||||
}
|
||||
|
||||
if (tran.daylightTimeOffset == 0) {
|
||||
// If tran is in StandardTime, then need to check if falls close to either DST transition.
|
||||
// If it does, then it may need adjusting for missing hour or for second occurrence
|
||||
qint64 diffPrevTran = forLocalMSecs
|
||||
- (tran.atMSecsSinceEpoch + (tran.offsetFromUtc * 1000));
|
||||
qint64 diffNextTran = nextTran.atMSecsSinceEpoch + (nextTran.offsetFromUtc * 1000)
|
||||
- forLocalMSecs;
|
||||
if (diffPrevTran >= 0 && diffPrevTran < MSECS_TRAN_WINDOW) {
|
||||
// If tran picked is for standard time check if changed from DST in last 6 hours,
|
||||
// as the local msecs may be ambiguous and represent two valid utc msecs.
|
||||
// If in last 6 hours then get prev tran and if diff falls within the DST offset
|
||||
// then use the prev tran as we default to the FirstOccurrence
|
||||
// TODO Check if faster to just always get prev tran, or if faster using 6 hour check.
|
||||
Data dstTran = previousTransition(tran.atMSecsSinceEpoch);
|
||||
if (dstTran.atMSecsSinceEpoch != invalidMSecs()
|
||||
&& dstTran.daylightTimeOffset > 0 && diffPrevTran < (dstTran.daylightTimeOffset * 1000))
|
||||
tran = dstTran;
|
||||
} else if (diffNextTran >= 0 && diffNextTran <= (nextTran.daylightTimeOffset * 1000)) {
|
||||
// If time falls within last hour of standard time then is actually the missing hour
|
||||
// So return the next tran instead and adjust the local time to be valid
|
||||
tran = nextTran;
|
||||
forLocalMSecs = forLocalMSecs + (nextTran.daylightTimeOffset * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// tran should now hold the right transition offset to use
|
||||
tran.atMSecsSinceEpoch = forLocalMSecs - (tran.offsetFromUtc * 1000);
|
||||
return tran;
|
||||
return data(utcEpochMSecs);
|
||||
}
|
||||
|
||||
bool QTimeZonePrivate::hasTransitions() const
|
||||
|
@ -207,58 +207,6 @@ QTimeZonePrivate::Data QAndroidTimeZonePrivate::previousTransition(qint64 before
|
||||
return invalidData();
|
||||
}
|
||||
|
||||
// Since Android does not provide an API to access transitions,
|
||||
// dataForLocalTime needs to be reimplemented without direct use of transitions
|
||||
QTimeZonePrivate::Data QAndroidTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs) const
|
||||
{
|
||||
if (!androidTimeZone.isValid()) {
|
||||
return invalidData();
|
||||
} else {
|
||||
qint64 UTCepochMSecs;
|
||||
|
||||
// compare the UTC time with standard offset against normal DST offset of one hour
|
||||
qint64 standardUTCMSecs(forLocalMSecs - (standardTimeOffset(forLocalMSecs) * 1000));
|
||||
qint64 daylightUTCMsecs;
|
||||
|
||||
// Check if daylight-saving time applies,
|
||||
// checking also for DST boundaries
|
||||
if (isDaylightTime(standardUTCMSecs)) {
|
||||
// If DST does apply, then standardUTCMSecs will be an hour or so ahead of the real epoch time
|
||||
// so check that time
|
||||
daylightUTCMsecs = standardUTCMSecs - daylightTimeOffset(standardUTCMSecs)*1000;
|
||||
if (isDaylightTime(daylightUTCMsecs)) {
|
||||
// DST confirmed
|
||||
UTCepochMSecs = daylightUTCMsecs;
|
||||
} else {
|
||||
// DST has just finished
|
||||
UTCepochMSecs = standardUTCMSecs;
|
||||
}
|
||||
} else {
|
||||
// Standard time indicated, but check for a false negative.
|
||||
// Would a standard one-hour DST offset indicate DST?
|
||||
daylightUTCMsecs = standardUTCMSecs - 3600000; // 3600000 MSECS_PER_HOUR
|
||||
if (isDaylightTime(daylightUTCMsecs)) {
|
||||
// DST may have just started,
|
||||
// but double check against timezone's own DST offset
|
||||
// (don't necessarily assume a one-hour offset)
|
||||
daylightUTCMsecs = standardUTCMSecs - daylightTimeOffset(daylightUTCMsecs)*1000;
|
||||
if (isDaylightTime(daylightUTCMsecs)) {
|
||||
// DST confirmed
|
||||
UTCepochMSecs = daylightUTCMsecs;
|
||||
} else {
|
||||
// false positive, apply standard time after all
|
||||
UTCepochMSecs = standardUTCMSecs;
|
||||
}
|
||||
} else {
|
||||
// confirmed standard time
|
||||
UTCepochMSecs = standardUTCMSecs;
|
||||
}
|
||||
}
|
||||
|
||||
return data(UTCepochMSecs);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray QAndroidTimeZonePrivate::systemTimeZoneId() const
|
||||
{
|
||||
QJNIObjectPrivate androidSystemTimeZone = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;");
|
||||
|
@ -123,7 +123,7 @@ public:
|
||||
virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const;
|
||||
|
||||
virtual Data data(qint64 forMSecsSinceEpoch) const;
|
||||
virtual Data dataForLocalTime(qint64 forLocalMSecs) const;
|
||||
Data dataForLocalTime(qint64 forLocalMSecs, int hint) const;
|
||||
|
||||
virtual bool hasTransitions() const;
|
||||
virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const;
|
||||
@ -475,8 +475,6 @@ public:
|
||||
Data nextTransition(qint64 afterMSecsSinceEpoch) const Q_DECL_OVERRIDE;
|
||||
Data previousTransition(qint64 beforeMSecsSinceEpoch) const Q_DECL_OVERRIDE;
|
||||
|
||||
Data dataForLocalTime(qint64 forLocalMSecs) const Q_DECL_OVERRIDE;
|
||||
|
||||
QByteArray systemTimeZoneId() const Q_DECL_OVERRIDE;
|
||||
|
||||
QList<QByteArray> availableTimeZoneIds() const Q_DECL_OVERRIDE;
|
||||
|
@ -3145,17 +3145,17 @@ void tst_QDateTime::timeZones() const
|
||||
// Test local to MSecs
|
||||
// - Test first occurrence 02:00:00 = 1 hour before tran
|
||||
hourBeforeStd = QDateTime(QDate(2013, 10, 27), QTime(2, 0, 0), cet);
|
||||
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
|
||||
QCOMPARE(hourBeforeStd.toMSecsSinceEpoch(), dstToStdMSecs - 3600000);
|
||||
// - Test first occurrence 02:59:59.999 = 1 msec before tran
|
||||
msecBeforeStd = QDateTime(QDate(2013, 10, 27), QTime(2, 59, 59, 999), cet);
|
||||
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
|
||||
QCOMPARE(msecBeforeStd.toMSecsSinceEpoch(), dstToStdMSecs - 1);
|
||||
// - Test second occurrence 02:00:00 = at tran
|
||||
atStd = QDateTime(QDate(2013, 10, 27), QTime(2, 0, 0), cet);
|
||||
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
|
||||
QCOMPARE(atStd.toMSecsSinceEpoch(), dstToStdMSecs);
|
||||
// - Test second occurrence 03:00:00 = 59 mins after tran
|
||||
afterStd = QDateTime(QDate(2013, 10, 27), QTime(2, 59, 59, 999), cet);
|
||||
QEXPECT_FAIL("", "QDateTime doesn't properly support Daylight Transitions", Continue);
|
||||
QCOMPARE(afterStd.toMSecsSinceEpoch(), dstToStdMSecs + 3600000 - 1);
|
||||
// - Test 03:00:00 = 1 hour after tran
|
||||
hourAfterStd = QDateTime(QDate(2013, 10, 27), QTime(3, 0, 0), cet);
|
||||
|
@ -45,6 +45,8 @@ private slots:
|
||||
void dataStreamTest();
|
||||
void isTimeZoneIdAvailable();
|
||||
void availableTimeZoneIds();
|
||||
void transitionEachZone_data();
|
||||
void transitionEachZone();
|
||||
void stressTest();
|
||||
void windowsId();
|
||||
void isValidId_data();
|
||||
@ -367,6 +369,56 @@ void tst_QTimeZone::isTimeZoneIdAvailable()
|
||||
QCOMPARE(QTimeZonePrivate::isValidId("12345678901234/123456789012345"), false);
|
||||
}
|
||||
|
||||
void tst_QTimeZone::transitionEachZone_data()
|
||||
{
|
||||
QTest::addColumn<QByteArray>("zone");
|
||||
QTest::addColumn<qint64>("secs");
|
||||
QTest::addColumn<int>("start");
|
||||
QTest::addColumn<int>("stop");
|
||||
|
||||
struct {
|
||||
qint64 baseSecs;
|
||||
int start, stop;
|
||||
int year;
|
||||
} table[] = {
|
||||
{ 25666200, 3, 12, 1970 }, // 1970-10-25 01:30 UTC; North America
|
||||
{ 1288488600, -4, 8, 2010 } // 2010-10-31 01:30 UTC; Europe, Russia
|
||||
};
|
||||
|
||||
QString name;
|
||||
for (int k = sizeof(table) / sizeof(table[0]); k-- > 0; ) {
|
||||
foreach (QByteArray zone, QTimeZone::availableTimeZoneIds()) {
|
||||
name.sprintf("%s@%d", zone.constData(), table[k].year);
|
||||
QTest::newRow(name.toUtf8().constData())
|
||||
<< zone
|
||||
<< table[k].baseSecs
|
||||
<< table[k].start
|
||||
<< table[k].stop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QTimeZone::transitionEachZone()
|
||||
{
|
||||
// Regression test: round-trip fromMsecs/toMSecs should be idempotent; but
|
||||
// various zones failed during fall-back transitions.
|
||||
QFETCH(QByteArray, zone);
|
||||
QFETCH(qint64, secs);
|
||||
QFETCH(int, start);
|
||||
QFETCH(int, stop);
|
||||
QTimeZone named(zone);
|
||||
|
||||
for (int i = start; i < stop; i++) {
|
||||
qint64 here = secs + i * 3600;
|
||||
QDateTime when = QDateTime::fromMSecsSinceEpoch(here * 1000, named);
|
||||
qint64 stamp = when.toMSecsSinceEpoch();
|
||||
if (here * 1000 != stamp) // (The +1 is due to using *1*:30 as baseSecs.)
|
||||
qDebug() << "Failing for" << zone << "at half past" << (i + 1) << "UTC";
|
||||
QCOMPARE(stamp % 1000, 0);
|
||||
QCOMPARE(here - stamp / 1000, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QTimeZone::availableTimeZoneIds()
|
||||
{
|
||||
if (debug) {
|
||||
|
Loading…
Reference in New Issue
Block a user