1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Copyright (C) 2013 John Layt <jlayt@kde.org>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtCore module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40 
41 
42 #include "qtimezone.h"
43 #include "qtimezoneprivate_p.h"
44 #include "qtimezoneprivate_data_p.h"
45 
46 #include <qdatastream.h>
47 #include <qdebug.h>
48 
49 #include <algorithm>
50 
51 QT_BEGIN_NAMESPACE
52 
53 /*
54     Static utilities for looking up Windows ID tables
55 */
56 
57 static const int windowsDataTableSize = sizeof(windowsDataTable) / sizeof(QWindowsData) - 1;
58 static const int zoneDataTableSize = sizeof(zoneDataTable) / sizeof(QZoneData) - 1;
59 static const int utcDataTableSize = sizeof(utcDataTable) / sizeof(QUtcData) - 1;
60 
61 
zoneData(quint16 index)62 static const QZoneData *zoneData(quint16 index)
63 {
64     Q_ASSERT(index < zoneDataTableSize);
65     return &zoneDataTable[index];
66 }
67 
windowsData(quint16 index)68 static const QWindowsData *windowsData(quint16 index)
69 {
70     Q_ASSERT(index < windowsDataTableSize);
71     return &windowsDataTable[index];
72 }
73 
utcData(quint16 index)74 static const QUtcData *utcData(quint16 index)
75 {
76     Q_ASSERT(index < utcDataTableSize);
77     return &utcDataTable[index];
78 }
79 
80 // Return the Windows ID literal for a given QWindowsData
windowsId(const QWindowsData * windowsData)81 static QByteArray windowsId(const QWindowsData *windowsData)
82 {
83     return (windowsIdData + windowsData->windowsIdIndex);
84 }
85 
86 // Return the IANA ID literal for a given QWindowsData
ianaId(const QWindowsData * windowsData)87 static QByteArray ianaId(const QWindowsData *windowsData)
88 {
89     return (ianaIdData + windowsData->ianaIdIndex);
90 }
91 
92 // Return the IANA ID literal for a given QZoneData
ianaId(const QZoneData * zoneData)93 static QByteArray ianaId(const QZoneData *zoneData)
94 {
95     return (ianaIdData + zoneData->ianaIdIndex);
96 }
97 
utcId(const QUtcData * utcData)98 static QByteArray utcId(const QUtcData *utcData)
99 {
100     return (ianaIdData + utcData->ianaIdIndex);
101 }
102 
toWindowsIdKey(const QByteArray & winId)103 static quint16 toWindowsIdKey(const QByteArray &winId)
104 {
105     for (quint16 i = 0; i < windowsDataTableSize; ++i) {
106         const QWindowsData *data = windowsData(i);
107         if (windowsId(data) == winId)
108             return data->windowsIdKey;
109     }
110     return 0;
111 }
112 
toWindowsIdLiteral(quint16 windowsIdKey)113 static QByteArray toWindowsIdLiteral(quint16 windowsIdKey)
114 {
115     for (quint16 i = 0; i < windowsDataTableSize; ++i) {
116         const QWindowsData *data = windowsData(i);
117         if (data->windowsIdKey == windowsIdKey)
118             return windowsId(data);
119     }
120     return QByteArray();
121 }
122 
123 /*
124     Base class implementing common utility routines, only intantiate for a null tz.
125 */
126 
QTimeZonePrivate()127 QTimeZonePrivate::QTimeZonePrivate()
128 {
129 }
130 
QTimeZonePrivate(const QTimeZonePrivate & other)131 QTimeZonePrivate::QTimeZonePrivate(const QTimeZonePrivate &other)
132     : QSharedData(other), m_id(other.m_id)
133 {
134 }
135 
~QTimeZonePrivate()136 QTimeZonePrivate::~QTimeZonePrivate()
137 {
138 }
139 
clone() const140 QTimeZonePrivate *QTimeZonePrivate::clone() const
141 {
142     return new QTimeZonePrivate(*this);
143 }
144 
operator ==(const QTimeZonePrivate & other) const145 bool QTimeZonePrivate::operator==(const QTimeZonePrivate &other) const
146 {
147     // TODO Too simple, but need to solve problem of comparing different derived classes
148     // Should work for all System and ICU classes as names guaranteed unique, but not for Simple.
149     // Perhaps once all classes have working transitions can compare full list?
150     return (m_id == other.m_id);
151 }
152 
operator !=(const QTimeZonePrivate & other) const153 bool QTimeZonePrivate::operator!=(const QTimeZonePrivate &other) const
154 {
155     return !(*this == other);
156 }
157 
isValid() const158 bool QTimeZonePrivate::isValid() const
159 {
160     return !m_id.isEmpty();
161 }
162 
id() const163 QByteArray QTimeZonePrivate::id() const
164 {
165     return m_id;
166 }
167 
country() const168 QLocale::Country QTimeZonePrivate::country() const
169 {
170     // Default fall-back mode, use the zoneTable to find Region of known Zones
171     for (int i = 0; i < zoneDataTableSize; ++i) {
172         const QZoneData *data = zoneData(i);
173         if (ianaId(data).split(' ').contains(m_id))
174             return (QLocale::Country)data->country;
175     }
176     return QLocale::AnyCountry;
177 }
178 
comment() const179 QString QTimeZonePrivate::comment() const
180 {
181     return QString();
182 }
183 
displayName(qint64 atMSecsSinceEpoch,QTimeZone::NameType nameType,const QLocale & locale) const184 QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
185                                       QTimeZone::NameType nameType,
186                                       const QLocale &locale) const
187 {
188     if (nameType == QTimeZone::OffsetName)
189         return isoOffsetFormat(offsetFromUtc(atMSecsSinceEpoch));
190 
191     if (isDaylightTime(atMSecsSinceEpoch))
192         return displayName(QTimeZone::DaylightTime, nameType, locale);
193     else
194         return displayName(QTimeZone::StandardTime, nameType, locale);
195 }
196 
displayName(QTimeZone::TimeType timeType,QTimeZone::NameType nameType,const QLocale & locale) const197 QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
198                                       QTimeZone::NameType nameType,
199                                       const QLocale &locale) const
200 {
201     Q_UNUSED(timeType)
202     Q_UNUSED(nameType)
203     Q_UNUSED(locale)
204     return QString();
205 }
206 
abbreviation(qint64 atMSecsSinceEpoch) const207 QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
208 {
209     Q_UNUSED(atMSecsSinceEpoch)
210     return QString();
211 }
212 
offsetFromUtc(qint64 atMSecsSinceEpoch) const213 int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
214 {
215     return standardTimeOffset(atMSecsSinceEpoch) + daylightTimeOffset(atMSecsSinceEpoch);
216 }
217 
standardTimeOffset(qint64 atMSecsSinceEpoch) const218 int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
219 {
220     Q_UNUSED(atMSecsSinceEpoch)
221     return invalidSeconds();
222 }
223 
daylightTimeOffset(qint64 atMSecsSinceEpoch) const224 int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
225 {
226     Q_UNUSED(atMSecsSinceEpoch)
227     return invalidSeconds();
228 }
229 
hasDaylightTime() const230 bool QTimeZonePrivate::hasDaylightTime() const
231 {
232     return false;
233 }
234 
isDaylightTime(qint64 atMSecsSinceEpoch) const235 bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
236 {
237     Q_UNUSED(atMSecsSinceEpoch)
238     return false;
239 }
240 
data(qint64 forMSecsSinceEpoch) const241 QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
242 {
243     Q_UNUSED(forMSecsSinceEpoch)
244     return invalidData();
245 }
246 
247 // Private only method for use by QDateTime to convert local msecs to epoch msecs
dataForLocalTime(qint64 forLocalMSecs,int hint) const248 QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int hint) const
249 {
250     if (!hasDaylightTime()) // No DST means same offset for all local msecs
251         return data(forLocalMSecs - standardTimeOffset(forLocalMSecs) * 1000);
252 
253     /*
254       We need a UTC time at which to ask for the offset, in order to be able to
255       add that offset to forLocalMSecs, to get the UTC time we
256       need. Fortunately, no time-zone offset is more than 14 hours; and DST
257       transitions happen (much) more than thirty-two hours apart.  So sampling
258       offset sixteen hours each side gives us information we can be sure
259       brackets the correct time and at most one DST transition.
260     */
261     const qint64 sixteenHoursInMSecs(16 * 3600 * 1000);
262     Q_STATIC_ASSERT(-sixteenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
263                   && sixteenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
264     const qint64 recent = forLocalMSecs - sixteenHoursInMSecs;
265     const qint64 imminent = forLocalMSecs + sixteenHoursInMSecs;
266     /*
267       Offsets are Local - UTC, positive to the east of Greenwich, negative to
268       the west; DST offset always exceeds standard offset, when DST applies.
269       When we have offsets on either side of a transition, the lower one is
270       standard, the higher is DST.
271 
272       Non-DST transitions (jurisdictions changing time-zone and time-zones
273       changing their standard offset, typically) are described below as if they
274       were DST transitions (since these are more usual and familiar); the code
275       mostly concerns itself with offsets from UTC, described in terms of the
276       common case for changes in that.  If there is no actual change in offset
277       (e.g. a DST transition cancelled by a standard offset change), this code
278       should handle it gracefully; without transitions, it'll see early == late
279       and take the easy path; with transitions, tran and nextTran get the
280       correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
281       the right one.  In all other cases, the transition changes offset and the
282       reasoning that applies to DST applies just the same.  Aside from hinting,
283       the only thing that looks at DST-ness at all, other than inferred from
284       offset changes, is the case without transition data handling an invalid
285       time in the gap that a transition passed over.
286 
287       The handling of hint (see below) is apt to go wrong in non-DST
288       transitions.  There isn't really a great deal we can hope to do about that
289       without adding yet more unreliable complexity to the heuristics in use for
290       already obscure corner-cases.
291      */
292 
293     /*
294       The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller
295       thinks we're in DST, 0 if in standard.  A value of -2 means never-DST, so
296       should have been handled above; if it slips through, it's wrong but we
297       should probably treat it as standard anyway (never-DST means
298       always-standard, after all).  If the hint turns out to be wrong, fall back
299       on trying the other possibility: which makes it harmless to treat -1
300       (meaning unknown) as standard (i.e. try standard first, then try DST).  In
301       practice, away from a transition, the only difference hint makes is to
302       which candidate we try first: if the hint is wrong (or unknown and
303       standard fails), we'll try the other candidate and it'll work.
304 
305       For the obscure (and invalid) case where forLocalMSecs falls in a
306       spring-forward's missing hour, a common case is that we started with a
307       date/time for which the hint was valid and adjusted it naively; for that
308       case, we should correct the adjustment by shunting across the transition
309       into where hint is wrong.  So half-way through the gap, arrived at from
310       the DST side, should be read as an hour earlier, in standard time; but, if
311       arrived at from the standard side, should be read as an hour later, in
312       DST.  (This shall be wrong in some cases; for example, when a country
313       changes its transition dates and changing a date/time by more than six
314       months lands it on a transition.  However, these cases are even more
315       obscure than those where the heuristic is good.)
316      */
317 
318     if (hasTransitions()) {
319         /*
320           We have transitions.
321 
322           Each transition gives the offsets to use until the next; so we need the
323           most recent transition before the time forLocalMSecs describes.  If it
324           describes a time *in* a transition, we'll need both that transition and
325           the one before it.  So find one transition that's probably after (and not
326           much before, otherwise) and another that's definitely before, then work
327           out which one to use.  When both or neither work on forLocalMSecs, use
328           hint to disambiguate.
329         */
330 
331         // Get a transition definitely before the local MSecs; usually all we need.
332         // Only around the transition times might we need another.
333         Data tran = previousTransition(recent);
334         Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
335                  forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
336         Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
337         /*
338           Now walk those forward until they bracket forLocalMSecs with transitions.
339 
340           One of the transitions should then be telling us the right offset to use.
341           In a transition, we need the transition before it (to describe the run-up
342           to the transition) and the transition itself; so we need to stop when
343           nextTran is that transition.
344         */
345         while (nextTran.atMSecsSinceEpoch != invalidMSecs()
346                && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
347             Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
348             if (newTran.atMSecsSinceEpoch == invalidMSecs()
349                 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
350                 // Definitely not a relevant tansition: too far in the future.
351                 break;
352             }
353             tran = nextTran;
354             nextTran = newTran;
355         }
356 
357         // Check we do *really* have transitions for this zone:
358         if (tran.atMSecsSinceEpoch != invalidMSecs()) {
359 
360             /*
361               So now tran is definitely before and nextTran is either after or only
362               slightly before.  One is standard time; we interpret the other as DST
363               (although the transition might in fact by a change in standard offset).  Our
364               hint tells us which of those to use (defaulting to standard if no hint): try
365               it first; if that fails, try the other; if both fail, life's tricky.
366             */
367             Q_ASSERT(forLocalMSecs < 0
368                      || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
369             const qint64 nextStart = nextTran.atMSecsSinceEpoch;
370             // Work out the UTC values it might make sense to return:
371             nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
372             tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
373 
374             // If both or neither have zero DST, treat the one with lower offset as standard:
375             const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset
376                 ? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset;
377             // If that agrees with hint > 0, our first guess is to use nextTran; else tran.
378             const bool nextFirst = nextIsDst == (hint > 0) && nextStart != invalidMSecs();
379             for (int i = 0; i < 2; i++) {
380                 /*
381                   On the first pass, the case we consider is what hint told us to expect
382                   (except when hint was -1 and didn't actually tell us what to expect),
383                   so it's likely right.  We only get a second pass if the first failed,
384                   by which time the second case, that we're trying, is likely right.
385                 */
386                 if (nextFirst ? i == 0 : i) {
387                     Q_ASSERT(nextStart != invalidMSecs());
388                     if (nextStart <= nextTran.atMSecsSinceEpoch)
389                         return nextTran;
390                 } else {
391                     // If next is invalid, nextFirst is false, to route us here first:
392                     if (nextStart == invalidMSecs() || nextStart > tran.atMSecsSinceEpoch)
393                         return tran;
394                 }
395             }
396 
397             /*
398               Neither is valid (e.g. in a spring-forward's gap) and
399               nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch, so
400               0 < tran.atMSecsSinceEpoch - nextTran.atMSecsSinceEpoch
401               = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000
402             */
403             int dstStep = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000;
404             Q_ASSERT(dstStep > 0); // How else could we get here ?
405             if (nextFirst) { // hint thought we needed nextTran, so use tran
406                 tran.atMSecsSinceEpoch -= dstStep;
407                 return tran;
408             }
409             nextTran.atMSecsSinceEpoch += dstStep;
410             return nextTran;
411         }
412         // System has transitions but not for this zone.
413         // Try falling back to offsetFromUtc
414     }
415 
416     /* Bracket and refine to discover offset. */
417     qint64 utcEpochMSecs;
418 
419     int early = offsetFromUtc(recent);
420     int late = offsetFromUtc(imminent);
421     if (early == late) { // > 99% of the time
422         utcEpochMSecs = forLocalMSecs - early * 1000;
423     } else {
424         // Close to a DST transition: early > late is near a fall-back,
425         // early < late is near a spring-forward.
426         const int offsetInDst = qMax(early, late);
427         const int offsetInStd = qMin(early, late);
428         // Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
429         const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
430         const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
431         // Best guess at the answer:
432         const qint64 hinted = hint > 0 ? forDst : forStd;
433         if (offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd)) {
434             utcEpochMSecs = hinted;
435         } else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) {
436             utcEpochMSecs = forDst;
437         } else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) {
438             utcEpochMSecs = forStd;
439         } else {
440             // Invalid forLocalMSecs: in spring-forward gap.
441             const int dstStep = daylightTimeOffset(early < late ? imminent : recent) * 1000;
442             Q_ASSERT(dstStep); // There can't be a transition without it !
443             utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
444         }
445     }
446 
447     return data(utcEpochMSecs);
448 }
449 
hasTransitions() const450 bool QTimeZonePrivate::hasTransitions() const
451 {
452     return false;
453 }
454 
nextTransition(qint64 afterMSecsSinceEpoch) const455 QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
456 {
457     Q_UNUSED(afterMSecsSinceEpoch)
458     return invalidData();
459 }
460 
previousTransition(qint64 beforeMSecsSinceEpoch) const461 QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
462 {
463     Q_UNUSED(beforeMSecsSinceEpoch)
464     return invalidData();
465 }
466 
transitions(qint64 fromMSecsSinceEpoch,qint64 toMSecsSinceEpoch) const467 QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
468                                                          qint64 toMSecsSinceEpoch) const
469 {
470     DataList list;
471     if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
472         // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec
473         Data next = nextTransition(fromMSecsSinceEpoch - 1);
474         while (next.atMSecsSinceEpoch != invalidMSecs()
475                && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
476             list.append(next);
477             next = nextTransition(next.atMSecsSinceEpoch);
478         }
479     }
480     return list;
481 }
482 
systemTimeZoneId() const483 QByteArray QTimeZonePrivate::systemTimeZoneId() const
484 {
485     return QByteArray();
486 }
487 
isTimeZoneIdAvailable(const QByteArray & ianaId) const488 bool QTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray& ianaId) const
489 {
490     // Fall-back implementation, can be made faster in subclasses
491     const QList<QByteArray> tzIds = availableTimeZoneIds();
492     return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
493 }
494 
availableTimeZoneIds() const495 QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds() const
496 {
497     return QList<QByteArray>();
498 }
499 
availableTimeZoneIds(QLocale::Country country) const500 QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
501 {
502     // Default fall-back mode, use the zoneTable to find Region of know Zones
503     QList<QByteArray> regions;
504 
505     // First get all Zones in the Zones table belonging to the Region
506     for (int i = 0; i < zoneDataTableSize; ++i) {
507         if (zoneData(i)->country == country)
508             regions += ianaId(zoneData(i)).split(' ');
509     }
510 
511     std::sort(regions.begin(), regions.end());
512     regions.erase(std::unique(regions.begin(), regions.end()), regions.end());
513 
514     // Then select just those that are available
515     const QList<QByteArray> all = availableTimeZoneIds();
516     QList<QByteArray> result;
517     result.reserve(qMin(all.size(), regions.size()));
518     std::set_intersection(all.begin(), all.end(), regions.cbegin(), regions.cend(),
519                           std::back_inserter(result));
520     return result;
521 }
522 
availableTimeZoneIds(int offsetFromUtc) const523 QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const
524 {
525     // Default fall-back mode, use the zoneTable to find Offset of know Zones
526     QList<QByteArray> offsets;
527     // First get all Zones in the table using the Offset
528     for (int i = 0; i < windowsDataTableSize; ++i) {
529         const QWindowsData *winData = windowsData(i);
530         if (winData->offsetFromUtc == offsetFromUtc) {
531             for (int j = 0; j < zoneDataTableSize; ++j) {
532                 const QZoneData *data = zoneData(j);
533                 if (data->windowsIdKey == winData->windowsIdKey)
534                     offsets += ianaId(data).split(' ');
535             }
536         }
537     }
538 
539     std::sort(offsets.begin(), offsets.end());
540     offsets.erase(std::unique(offsets.begin(), offsets.end()), offsets.end());
541 
542     // Then select just those that are available
543     const QList<QByteArray> all = availableTimeZoneIds();
544     QList<QByteArray> result;
545     result.reserve(qMin(all.size(), offsets.size()));
546     std::set_intersection(all.begin(), all.end(), offsets.cbegin(), offsets.cend(),
547                           std::back_inserter(result));
548     return result;
549 }
550 
551 #ifndef QT_NO_DATASTREAM
serialize(QDataStream & ds) const552 void QTimeZonePrivate::serialize(QDataStream &ds) const
553 {
554     ds << QString::fromUtf8(m_id);
555 }
556 #endif // QT_NO_DATASTREAM
557 
558 // Static Utility Methods
559 
invalidData()560 QTimeZonePrivate::Data QTimeZonePrivate::invalidData()
561 {
562     Data data;
563     data.atMSecsSinceEpoch = invalidMSecs();
564     data.offsetFromUtc = invalidSeconds();
565     data.standardTimeOffset = invalidSeconds();
566     data.daylightTimeOffset = invalidSeconds();
567     return data;
568 }
569 
invalidOffsetData()570 QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
571 {
572     QTimeZone::OffsetData offsetData;
573     offsetData.atUtc = QDateTime();
574     offsetData.offsetFromUtc = invalidSeconds();
575     offsetData.standardTimeOffset = invalidSeconds();
576     offsetData.daylightTimeOffset = invalidSeconds();
577     return offsetData;
578 }
579 
toOffsetData(const QTimeZonePrivate::Data & data)580 QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Data &data)
581 {
582     QTimeZone::OffsetData offsetData = invalidOffsetData();
583     if (data.atMSecsSinceEpoch != invalidMSecs()) {
584         offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, Qt::UTC);
585         offsetData.offsetFromUtc = data.offsetFromUtc;
586         offsetData.standardTimeOffset = data.standardTimeOffset;
587         offsetData.daylightTimeOffset = data.daylightTimeOffset;
588         offsetData.abbreviation = data.abbreviation;
589     }
590     return offsetData;
591 }
592 
593 // Is the format of the ID valid ?
isValidId(const QByteArray & ianaId)594 bool QTimeZonePrivate::isValidId(const QByteArray &ianaId)
595 {
596     /*
597       Main rules for defining TZ/IANA names as per ftp://ftp.iana.org/tz/code/Theory
598        1. Use only valid POSIX file name components
599        2. Within a file name component, use only ASCII letters, `.', `-' and `_'.
600        3. Do not use digits (except in a [+-]\d+ suffix, when used).
601        4. A file name component must not exceed 14 characters or start with `-'
602       However, the rules are really guidelines - a later one says
603        - Do not change established names if they only marginally violate the
604          above rules.
605       We may, therefore, need to be a bit slack in our check here, if we hit
606       legitimate exceptions in real time-zone databases.
607 
608       In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid
609       so we need to accept digits, ':', and '+'; aliases typically have the form
610       of POSIX TZ strings, which allow a suffix to a proper IANA name.  A POSIX
611       suffix starts with an offset (as in GMT+7) and may continue with another
612       name (as in EST5EDT, giving the DST name of the zone); a further offset is
613       allowed (for DST).  The ("hard to describe and [...] error-prone in
614       practice") POSIX form even allows a suffix giving the dates (and
615       optionally times) of the annual DST transitions.  Hopefully, no TZ aliases
616       go that far, but we at least need to accept an offset and (single
617       fragment) DST-name.
618 
619       But for the legacy complications, the following would be preferable if
620       QRegExp would work on QByteArrays directly:
621           const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}"
622                                       "(?:/[a-z+._][a-z+._-]{,13})*"
623                                           // Optional suffix:
624                                           "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset
625                                              // one name fragment (DST):
626                                              "(?:[a-z+._][a-z+._-]{,13})?)"),
627                            Qt::CaseInsensitive);
628           return rx.exactMatch(ianaId);
629     */
630 
631     // Somewhat slack hand-rolled version:
632     const int MinSectionLength = 1;
633 #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
634     // Android has its own naming of zones.
635     // "Canada/East-Saskatchewan" has a 17-character second component.
636     const int MaxSectionLength = 17;
637 #else
638     const int MaxSectionLength = 14;
639 #endif
640     int sectionLength = 0;
641     for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) {
642         const char ch = *it;
643         if (ch == '/') {
644             if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
645                 return false; // violates (4)
646             sectionLength = -1;
647         } else if (ch == '-') {
648             if (sectionLength == 0)
649                 return false; // violates (4)
650         } else if (!(ch >= 'a' && ch <= 'z')
651                 && !(ch >= 'A' && ch <= 'Z')
652                 && !(ch == '_')
653                 && !(ch == '.')
654                    // Should ideally check these only happen as an offset:
655                 && !(ch >= '0' && ch <= '9')
656                 && !(ch == '+')
657                 && !(ch == ':')) {
658             return false; // violates (2)
659         }
660     }
661     if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
662         return false; // violates (4)
663     return true;
664 }
665 
isoOffsetFormat(int offsetFromUtc)666 QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc)
667 {
668     const int mins = offsetFromUtc / 60;
669     return QString::fromUtf8("UTC%1%2:%3").arg(mins >= 0 ? QLatin1Char('+') : QLatin1Char('-'))
670                                           .arg(qAbs(mins) / 60, 2, 10, QLatin1Char('0'))
671                                           .arg(qAbs(mins) % 60, 2, 10, QLatin1Char('0'));
672 }
673 
ianaIdToWindowsId(const QByteArray & id)674 QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id)
675 {
676     for (int i = 0; i < zoneDataTableSize; ++i) {
677         const QZoneData *data = zoneData(i);
678         if (ianaId(data).split(' ').contains(id))
679             return toWindowsIdLiteral(data->windowsIdKey);
680     }
681     return QByteArray();
682 }
683 
windowsIdToDefaultIanaId(const QByteArray & windowsId)684 QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId)
685 {
686     const quint16 windowsIdKey = toWindowsIdKey(windowsId);
687     for (int i = 0; i < windowsDataTableSize; ++i) {
688         const QWindowsData *data = windowsData(i);
689         if (data->windowsIdKey == windowsIdKey)
690             return ianaId(data);
691     }
692     return QByteArray();
693 }
694 
windowsIdToDefaultIanaId(const QByteArray & windowsId,QLocale::Country country)695 QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId,
696                                                        QLocale::Country country)
697 {
698     const QList<QByteArray> list = windowsIdToIanaIds(windowsId, country);
699     if (list.count() > 0)
700         return list.first();
701     else
702         return QByteArray();
703 }
704 
windowsIdToIanaIds(const QByteArray & windowsId)705 QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId)
706 {
707     const quint16 windowsIdKey = toWindowsIdKey(windowsId);
708     QList<QByteArray> list;
709 
710     for (int i = 0; i < zoneDataTableSize; ++i) {
711         const QZoneData *data = zoneData(i);
712         if (data->windowsIdKey == windowsIdKey)
713             list << ianaId(data).split(' ');
714     }
715 
716     // Return the full list in alpha order
717     std::sort(list.begin(), list.end());
718     return list;
719 }
720 
windowsIdToIanaIds(const QByteArray & windowsId,QLocale::Country country)721 QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId,
722                                                         QLocale::Country country)
723 {
724     const quint16 windowsIdKey = toWindowsIdKey(windowsId);
725     for (int i = 0; i < zoneDataTableSize; ++i) {
726         const QZoneData *data = zoneData(i);
727         // Return the region matches in preference order
728         if (data->windowsIdKey == windowsIdKey && data->country == (quint16) country)
729             return ianaId(data).split(' ');
730     }
731 
732     return QList<QByteArray>();
733 }
734 
735 // Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly
clone()736 template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone()
737 {
738     return d->clone();
739 }
740 
741 /*
742     UTC Offset implementation, used when QT_NO_SYSTEMLOCALE set and ICU is not being used,
743     or for QDateTimes with a Qt:Spec of Qt::OffsetFromUtc.
744 */
745 
746 // Create default UTC time zone
QUtcTimeZonePrivate()747 QUtcTimeZonePrivate::QUtcTimeZonePrivate()
748 {
749     const QString name = utcQString();
750     init(utcQByteArray(), 0, name, name, QLocale::AnyCountry, name);
751 }
752 
753 // Create a named UTC time zone
QUtcTimeZonePrivate(const QByteArray & id)754 QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &id)
755 {
756     // Look for the name in the UTC list, if found set the values
757     for (int i = 0; i < utcDataTableSize; ++i) {
758         const QUtcData *data = utcData(i);
759         const QByteArray uid = utcId(data);
760         if (uid == id) {
761             QString name = QString::fromUtf8(id);
762             init(id, data->offsetFromUtc, name, name, QLocale::AnyCountry, name);
763             break;
764         }
765     }
766 }
767 
offsetFromUtcString(const QByteArray & id)768 qint64 QUtcTimeZonePrivate::offsetFromUtcString(const QByteArray &id)
769 {
770     // Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds.
771     // Assumption: id has already been tried as a CLDR UTC offset ID (notably
772     // including plain "UTC" itself) and a system offset ID; it's neither.
773     if (!id.startsWith("UTC") || id.size() < 5)
774         return invalidSeconds(); // Doesn't match
775     const char signChar = id.at(3);
776     if (signChar != '-' && signChar != '+')
777         return invalidSeconds(); // No sign
778     const int sign = signChar == '-' ? -1 : 1;
779 
780     const auto offsets = id.mid(4).split(':');
781     if (offsets.isEmpty() || offsets.size() > 3)
782         return invalidSeconds(); // No numbers, or too many.
783 
784     qint32 seconds = 0;
785     int prior = 0; // Number of fields parsed thus far
786     for (const auto &offset : offsets) {
787         bool ok = false;
788         unsigned short field = offset.toUShort(&ok);
789         // Bound hour above at 24, minutes and seconds at 60:
790         if (!ok || field >= (prior ? 60 : 24))
791             return invalidSeconds();
792         seconds = seconds * 60 + field;
793         ++prior;
794     }
795     while (prior++ < 3)
796         seconds *= 60;
797 
798     return seconds * sign;
799 }
800 
801 // Create offset from UTC
QUtcTimeZonePrivate(qint32 offsetSeconds)802 QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
803 {
804     QString utcId;
805 
806     if (offsetSeconds == 0)
807         utcId = utcQString();
808     else
809         utcId = isoOffsetFormat(offsetSeconds);
810 
811     init(utcId.toUtf8(), offsetSeconds, utcId, utcId, QLocale::AnyCountry, utcId);
812 }
813 
QUtcTimeZonePrivate(const QByteArray & zoneId,int offsetSeconds,const QString & name,const QString & abbreviation,QLocale::Country country,const QString & comment)814 QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds,
815                                          const QString &name, const QString &abbreviation,
816                                          QLocale::Country country, const QString &comment)
817 {
818     init(zoneId, offsetSeconds, name, abbreviation, country, comment);
819 }
820 
QUtcTimeZonePrivate(const QUtcTimeZonePrivate & other)821 QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other)
822     : QTimeZonePrivate(other), m_name(other.m_name),
823       m_abbreviation(other.m_abbreviation),
824       m_comment(other.m_comment),
825       m_country(other.m_country),
826       m_offsetFromUtc(other.m_offsetFromUtc)
827 {
828 }
829 
~QUtcTimeZonePrivate()830 QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
831 {
832 }
833 
clone() const834 QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone() const
835 {
836     return new QUtcTimeZonePrivate(*this);
837 }
838 
data(qint64 forMSecsSinceEpoch) const839 QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
840 {
841     Data d;
842     d.abbreviation = m_abbreviation;
843     d.atMSecsSinceEpoch = forMSecsSinceEpoch;
844     d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
845     d.daylightTimeOffset = 0;
846     return d;
847 }
848 
init(const QByteArray & zoneId)849 void QUtcTimeZonePrivate::init(const QByteArray &zoneId)
850 {
851     m_id = zoneId;
852 }
853 
init(const QByteArray & zoneId,int offsetSeconds,const QString & name,const QString & abbreviation,QLocale::Country country,const QString & comment)854 void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name,
855                                const QString &abbreviation, QLocale::Country country,
856                                const QString &comment)
857 {
858     m_id = zoneId;
859     m_offsetFromUtc = offsetSeconds;
860     m_name = name;
861     m_abbreviation = abbreviation;
862     m_country = country;
863     m_comment = comment;
864 }
865 
country() const866 QLocale::Country QUtcTimeZonePrivate::country() const
867 {
868     return m_country;
869 }
870 
comment() const871 QString QUtcTimeZonePrivate::comment() const
872 {
873     return m_comment;
874 }
875 
displayName(QTimeZone::TimeType timeType,QTimeZone::NameType nameType,const QLocale & locale) const876 QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
877                                          QTimeZone::NameType nameType,
878                                          const QLocale &locale) const
879 {
880     Q_UNUSED(timeType)
881     Q_UNUSED(locale)
882     if (nameType == QTimeZone::ShortName)
883         return m_abbreviation;
884     else if (nameType == QTimeZone::OffsetName)
885         return isoOffsetFormat(m_offsetFromUtc);
886     return m_name;
887 }
888 
abbreviation(qint64 atMSecsSinceEpoch) const889 QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
890 {
891     Q_UNUSED(atMSecsSinceEpoch)
892     return m_abbreviation;
893 }
894 
standardTimeOffset(qint64 atMSecsSinceEpoch) const895 qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
896 {
897     Q_UNUSED(atMSecsSinceEpoch)
898     return m_offsetFromUtc;
899 }
900 
daylightTimeOffset(qint64 atMSecsSinceEpoch) const901 qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
902 {
903     Q_UNUSED(atMSecsSinceEpoch)
904     return 0;
905 }
906 
systemTimeZoneId() const907 QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const
908 {
909     return utcQByteArray();
910 }
911 
isTimeZoneIdAvailable(const QByteArray & ianaId) const912 bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
913 {
914     // Only the zone IDs supplied by CLDR and recognized by constructor.
915     for (int i = 0; i < utcDataTableSize; ++i) {
916         const QUtcData *data = utcData(i);
917         if (utcId(data) == ianaId)
918             return true;
919     }
920     // But see offsetFromUtcString(), which lets us accept some "unavailable" IDs.
921     return false;
922 }
923 
availableTimeZoneIds() const924 QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds() const
925 {
926     // Only the zone IDs supplied by CLDR and recognized by constructor.
927     QList<QByteArray> result;
928     result.reserve(utcDataTableSize);
929     for (int i = 0; i < utcDataTableSize; ++i)
930         result << utcId(utcData(i));
931     // Not guaranteed to be sorted, so sort:
932     std::sort(result.begin(), result.end());
933     // ### assuming no duplicates
934     return result;
935 }
936 
availableTimeZoneIds(QLocale::Country country) const937 QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
938 {
939     // If AnyCountry then is request for all non-region offset codes
940     if (country == QLocale::AnyCountry)
941         return availableTimeZoneIds();
942     return QList<QByteArray>();
943 }
944 
availableTimeZoneIds(qint32 offsetSeconds) const945 QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds) const
946 {
947     // Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00
948     // and UTC-00:00 all have the same offset.)
949     QList<QByteArray> result;
950     for (int i = 0; i < utcDataTableSize; ++i) {
951         const QUtcData *data = utcData(i);
952         if (data->offsetFromUtc == offsetSeconds)
953             result << utcId(data);
954     }
955     // Not guaranteed to be sorted, so sort:
956     std::sort(result.begin(), result.end());
957     // ### assuming no duplicates
958     return result;
959 }
960 
961 #ifndef QT_NO_DATASTREAM
serialize(QDataStream & ds) const962 void QUtcTimeZonePrivate::serialize(QDataStream &ds) const
963 {
964     ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
965        << m_abbreviation << (qint32) m_country << m_comment;
966 }
967 #endif // QT_NO_DATASTREAM
968 
969 QT_END_NAMESPACE
970