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
51QT_BEGIN_NAMESPACE
52
53/*
54 Static utilities for looking up Windows ID tables
55*/
56
57static const int windowsDataTableSize = sizeof(windowsDataTable) / sizeof(QWindowsData) - 1;
58static const int zoneDataTableSize = sizeof(zoneDataTable) / sizeof(QZoneData) - 1;
59static const int utcDataTableSize = sizeof(utcDataTable) / sizeof(QUtcData) - 1;
60
61
62static const QZoneData *zoneData(quint16 index)
63{
64 Q_ASSERT(index < zoneDataTableSize);
65 return &zoneDataTable[index];
66}
67
68static const QWindowsData *windowsData(quint16 index)
69{
70 Q_ASSERT(index < windowsDataTableSize);
71 return &windowsDataTable[index];
72}
73
74static 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
81static QByteArray windowsId(const QWindowsData *windowsData)
82{
83 return (windowsIdData + windowsData->windowsIdIndex);
84}
85
86// Return the IANA ID literal for a given QWindowsData
87static QByteArray ianaId(const QWindowsData *windowsData)
88{
89 return (ianaIdData + windowsData->ianaIdIndex);
90}
91
92// Return the IANA ID literal for a given QZoneData
93static QByteArray ianaId(const QZoneData *zoneData)
94{
95 return (ianaIdData + zoneData->ianaIdIndex);
96}
97
98static QByteArray utcId(const QUtcData *utcData)
99{
100 return (ianaIdData + utcData->ianaIdIndex);
101}
102
103static 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
113static 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
127QTimeZonePrivate::QTimeZonePrivate()
128{
129}
130
131QTimeZonePrivate::QTimeZonePrivate(const QTimeZonePrivate &other)
132 : QSharedData(other), m_id(other.m_id)
133{
134}
135
136QTimeZonePrivate::~QTimeZonePrivate()
137{
138}
139
140QTimeZonePrivate *QTimeZonePrivate::clone() const
141{
142 return new QTimeZonePrivate(*this);
143}
144
145bool 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
153bool QTimeZonePrivate::operator!=(const QTimeZonePrivate &other) const
154{
155 return !(*this == other);
156}
157
158bool QTimeZonePrivate::isValid() const
159{
160 return !m_id.isEmpty();
161}
162
163QByteArray QTimeZonePrivate::id() const
164{
165 return m_id;
166}
167
168QLocale::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
179QString QTimeZonePrivate::comment() const
180{
181 return QString();
182}
183
184QString 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
197QString 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
207QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
208{
209 Q_UNUSED(atMSecsSinceEpoch);
210 return QString();
211}
212
213int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
214{
215 return standardTimeOffset(atMSecsSinceEpoch) + daylightTimeOffset(atMSecsSinceEpoch);
216}
217
218int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
219{
220 Q_UNUSED(atMSecsSinceEpoch);
221 return invalidSeconds();
222}
223
224int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
225{
226 Q_UNUSED(atMSecsSinceEpoch);
227 return invalidSeconds();
228}
229
230bool QTimeZonePrivate::hasDaylightTime() const
231{
232 return false;
233}
234
235bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
236{
237 Q_UNUSED(atMSecsSinceEpoch);
238 return false;
239}
240
241QTimeZonePrivate::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
248QTimeZonePrivate::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 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
450bool QTimeZonePrivate::hasTransitions() const
451{
452 return false;
453}
454
455QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
456{
457 Q_UNUSED(afterMSecsSinceEpoch);
458 return invalidData();
459}
460
461QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
462{
463 Q_UNUSED(beforeMSecsSinceEpoch);
464 return invalidData();
465}
466
467QTimeZonePrivate::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
483QByteArray QTimeZonePrivate::systemTimeZoneId() const
484{
485 return QByteArray();
486}
487
488bool 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
495QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds() const
496{
497 return QList<QByteArray>();
498}
499
500QList<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
523QList<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
552void QTimeZonePrivate::serialize(QDataStream &ds) const
553{
554 ds << QString::fromUtf8(m_id);
555}
556#endif // QT_NO_DATASTREAM
557
558// Static Utility Methods
559
560QTimeZonePrivate::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
570QTimeZone::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
580QTimeZone::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 ?
594bool 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
666QString 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
674QByteArray 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
684QByteArray 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
695QByteArray 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
705QList<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
721QList<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
736template<> 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
747QUtcTimeZonePrivate::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
754QUtcTimeZonePrivate::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
768qint64 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
802QUtcTimeZonePrivate::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
814QUtcTimeZonePrivate::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
821QUtcTimeZonePrivate::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
830QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
831{
832}
833
834QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone() const
835{
836 return new QUtcTimeZonePrivate(*this);
837}
838
839QTimeZonePrivate::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
849void QUtcTimeZonePrivate::init(const QByteArray &zoneId)
850{
851 m_id = zoneId;
852}
853
854void 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
866QLocale::Country QUtcTimeZonePrivate::country() const
867{
868 return m_country;
869}
870
871QString QUtcTimeZonePrivate::comment() const
872{
873 return m_comment;
874}
875
876QString 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
889QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
890{
891 Q_UNUSED(atMSecsSinceEpoch);
892 return m_abbreviation;
893}
894
895qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
896{
897 Q_UNUSED(atMSecsSinceEpoch);
898 return m_offsetFromUtc;
899}
900
901qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
902{
903 Q_UNUSED(atMSecsSinceEpoch);
904 return 0;
905}
906
907QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const
908{
909 return utcQByteArray();
910}
911
912bool 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
924QList<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
937QList<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
945QList<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
962void 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
969QT_END_NAMESPACE
970