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(index: i);
107 if (windowsId(windowsData: 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(index: i);
117 if (data->windowsIdKey == windowsIdKey)
118 return windowsId(windowsData: 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(index: i);
173 if (ianaId(zoneData: data).split(sep: ' ').contains(t: 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: offsetFromUtc(atMSecsSinceEpoch));
190
191 if (isDaylightTime(atMSecsSinceEpoch))
192 return displayName(timeType: QTimeZone::DaylightTime, nameType, locale);
193 else
194 return displayName(timeType: 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(forMSecsSinceEpoch: forLocalMSecs - standardTimeOffset(atMSecsSinceEpoch: 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(beforeMSecsSinceEpoch: recent);
334 Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
335 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
336 Data nextTran = nextTransition(afterMSecsSinceEpoch: 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(afterMSecsSinceEpoch: 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 /* So now tran is definitely before ... */
360 Q_ASSERT(forLocalMSecs < 0
361 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
362 // Work out the UTC value it would make sense to return if using tran:
363 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
364 // If we know of no transition after it, the answer is easy:
365 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
366 if (nextStart == invalidMSecs())
367 return tran;
368
369 /*
370 ... and nextTran is either after or only slightly before. We're
371 going to interpret one as standard time, the other as DST
372 (although the transition might in fact be a change in standard
373 offset, or a change in DST offset, e.g. to/from double-DST). Our
374 hint tells us which of those to use (defaulting to standard if no
375 hint): try it first; if that fails, try the other; if both fail,
376 life's tricky.
377 */
378 // Work out the UTC value it would make sense to return if using nextTran:
379 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
380
381 // If both or neither have zero DST, treat the one with lower offset as standard:
382 const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset
383 ? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset;
384 // If that agrees with hint > 0, our first guess is to use nextTran; else tran.
385 const bool nextFirst = nextIsDst == (hint > 0);
386 for (int i = 0; i < 2; i++) {
387 /*
388 On the first pass, the case we consider is what hint told us to expect
389 (except when hint was -1 and didn't actually tell us what to expect),
390 so it's likely right. We only get a second pass if the first failed,
391 by which time the second case, that we're trying, is likely right.
392 */
393 if (nextFirst ? i == 0 : i) {
394 if (nextStart <= nextTran.atMSecsSinceEpoch)
395 return nextTran;
396 } else {
397 // If next is invalid, nextFirst is false, to route us here first:
398 if (nextStart > tran.atMSecsSinceEpoch)
399 return tran;
400 }
401 }
402
403 /*
404 Neither is valid (e.g. in a spring-forward's gap) and
405 nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch, so
406 0 < tran.atMSecsSinceEpoch - nextTran.atMSecsSinceEpoch
407 = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000
408 */
409 int dstStep = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000;
410 Q_ASSERT(dstStep > 0); // How else could we get here ?
411 if (nextFirst) { // hint thought we needed nextTran, so use tran
412 tran.atMSecsSinceEpoch -= dstStep;
413 return tran;
414 }
415 nextTran.atMSecsSinceEpoch += dstStep;
416 return nextTran;
417 }
418 // System has transitions but not for this zone.
419 // Try falling back to offsetFromUtc
420 }
421
422 /* Bracket and refine to discover offset. */
423 qint64 utcEpochMSecs;
424
425 int early = offsetFromUtc(atMSecsSinceEpoch: recent);
426 int late = offsetFromUtc(atMSecsSinceEpoch: imminent);
427 if (early == late) { // > 99% of the time
428 utcEpochMSecs = forLocalMSecs - early * 1000;
429 } else {
430 // Close to a DST transition: early > late is near a fall-back,
431 // early < late is near a spring-forward.
432 const int offsetInDst = qMax(a: early, b: late);
433 const int offsetInStd = qMin(a: early, b: late);
434 // Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
435 const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
436 const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
437 // Best guess at the answer:
438 const qint64 hinted = hint > 0 ? forDst : forStd;
439 if (offsetFromUtc(atMSecsSinceEpoch: hinted) == (hint > 0 ? offsetInDst : offsetInStd)) {
440 utcEpochMSecs = hinted;
441 } else if (hint <= 0 && offsetFromUtc(atMSecsSinceEpoch: forDst) == offsetInDst) {
442 utcEpochMSecs = forDst;
443 } else if (hint > 0 && offsetFromUtc(atMSecsSinceEpoch: forStd) == offsetInStd) {
444 utcEpochMSecs = forStd;
445 } else {
446 // Invalid forLocalMSecs: in spring-forward gap.
447 const int dstStep = daylightTimeOffset(atMSecsSinceEpoch: early < late ? imminent : recent) * 1000;
448 Q_ASSERT(dstStep); // There can't be a transition without it !
449 utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
450 }
451 }
452
453 return data(forMSecsSinceEpoch: utcEpochMSecs);
454}
455
456bool QTimeZonePrivate::hasTransitions() const
457{
458 return false;
459}
460
461QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
462{
463 Q_UNUSED(afterMSecsSinceEpoch)
464 return invalidData();
465}
466
467QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
468{
469 Q_UNUSED(beforeMSecsSinceEpoch)
470 return invalidData();
471}
472
473QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
474 qint64 toMSecsSinceEpoch) const
475{
476 DataList list;
477 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
478 // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec
479 Data next = nextTransition(afterMSecsSinceEpoch: fromMSecsSinceEpoch - 1);
480 while (next.atMSecsSinceEpoch != invalidMSecs()
481 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
482 list.append(t: next);
483 next = nextTransition(afterMSecsSinceEpoch: next.atMSecsSinceEpoch);
484 }
485 }
486 return list;
487}
488
489QByteArray QTimeZonePrivate::systemTimeZoneId() const
490{
491 return QByteArray();
492}
493
494bool QTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray& ianaId) const
495{
496 // Fall-back implementation, can be made faster in subclasses
497 const QList<QByteArray> tzIds = availableTimeZoneIds();
498 return std::binary_search(first: tzIds.begin(), last: tzIds.end(), val: ianaId);
499}
500
501QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds() const
502{
503 return QList<QByteArray>();
504}
505
506QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
507{
508 // Default fall-back mode, use the zoneTable to find Region of know Zones
509 QList<QByteArray> regions;
510
511 // First get all Zones in the Zones table belonging to the Region
512 for (int i = 0; i < zoneDataTableSize; ++i) {
513 if (zoneData(index: i)->country == country)
514 regions += ianaId(zoneData: zoneData(index: i)).split(sep: ' ');
515 }
516
517 std::sort(first: regions.begin(), last: regions.end());
518 regions.erase(afirst: std::unique(first: regions.begin(), last: regions.end()), alast: regions.end());
519
520 // Then select just those that are available
521 const QList<QByteArray> all = availableTimeZoneIds();
522 QList<QByteArray> result;
523 result.reserve(alloc: qMin(a: all.size(), b: regions.size()));
524 std::set_intersection(first1: all.begin(), last1: all.end(), first2: regions.cbegin(), last2: regions.cend(),
525 result: std::back_inserter(x&: result));
526 return result;
527}
528
529QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const
530{
531 // Default fall-back mode, use the zoneTable to find Offset of know Zones
532 QList<QByteArray> offsets;
533 // First get all Zones in the table using the Offset
534 for (int i = 0; i < windowsDataTableSize; ++i) {
535 const QWindowsData *winData = windowsData(index: i);
536 if (winData->offsetFromUtc == offsetFromUtc) {
537 for (int j = 0; j < zoneDataTableSize; ++j) {
538 const QZoneData *data = zoneData(index: j);
539 if (data->windowsIdKey == winData->windowsIdKey)
540 offsets += ianaId(zoneData: data).split(sep: ' ');
541 }
542 }
543 }
544
545 std::sort(first: offsets.begin(), last: offsets.end());
546 offsets.erase(afirst: std::unique(first: offsets.begin(), last: offsets.end()), alast: offsets.end());
547
548 // Then select just those that are available
549 const QList<QByteArray> all = availableTimeZoneIds();
550 QList<QByteArray> result;
551 result.reserve(alloc: qMin(a: all.size(), b: offsets.size()));
552 std::set_intersection(first1: all.begin(), last1: all.end(), first2: offsets.cbegin(), last2: offsets.cend(),
553 result: std::back_inserter(x&: result));
554 return result;
555}
556
557#ifndef QT_NO_DATASTREAM
558void QTimeZonePrivate::serialize(QDataStream &ds) const
559{
560 ds << QString::fromUtf8(str: m_id);
561}
562#endif // QT_NO_DATASTREAM
563
564// Static Utility Methods
565
566QTimeZonePrivate::Data QTimeZonePrivate::invalidData()
567{
568 Data data;
569 data.atMSecsSinceEpoch = invalidMSecs();
570 data.offsetFromUtc = invalidSeconds();
571 data.standardTimeOffset = invalidSeconds();
572 data.daylightTimeOffset = invalidSeconds();
573 return data;
574}
575
576QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
577{
578 QTimeZone::OffsetData offsetData;
579 offsetData.atUtc = QDateTime();
580 offsetData.offsetFromUtc = invalidSeconds();
581 offsetData.standardTimeOffset = invalidSeconds();
582 offsetData.daylightTimeOffset = invalidSeconds();
583 return offsetData;
584}
585
586QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Data &data)
587{
588 QTimeZone::OffsetData offsetData = invalidOffsetData();
589 if (data.atMSecsSinceEpoch != invalidMSecs()) {
590 offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(msecs: data.atMSecsSinceEpoch, spec: Qt::UTC);
591 offsetData.offsetFromUtc = data.offsetFromUtc;
592 offsetData.standardTimeOffset = data.standardTimeOffset;
593 offsetData.daylightTimeOffset = data.daylightTimeOffset;
594 offsetData.abbreviation = data.abbreviation;
595 }
596 return offsetData;
597}
598
599// Is the format of the ID valid ?
600bool QTimeZonePrivate::isValidId(const QByteArray &ianaId)
601{
602 /*
603 Main rules for defining TZ/IANA names as per ftp://ftp.iana.org/tz/code/Theory
604 1. Use only valid POSIX file name components
605 2. Within a file name component, use only ASCII letters, `.', `-' and `_'.
606 3. Do not use digits (except in a [+-]\d+ suffix, when used).
607 4. A file name component must not exceed 14 characters or start with `-'
608 However, the rules are really guidelines - a later one says
609 - Do not change established names if they only marginally violate the
610 above rules.
611 We may, therefore, need to be a bit slack in our check here, if we hit
612 legitimate exceptions in real time-zone databases.
613
614 In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid
615 so we need to accept digits, ':', and '+'; aliases typically have the form
616 of POSIX TZ strings, which allow a suffix to a proper IANA name. A POSIX
617 suffix starts with an offset (as in GMT+7) and may continue with another
618 name (as in EST5EDT, giving the DST name of the zone); a further offset is
619 allowed (for DST). The ("hard to describe and [...] error-prone in
620 practice") POSIX form even allows a suffix giving the dates (and
621 optionally times) of the annual DST transitions. Hopefully, no TZ aliases
622 go that far, but we at least need to accept an offset and (single
623 fragment) DST-name.
624
625 But for the legacy complications, the following would be preferable if
626 QRegExp would work on QByteArrays directly:
627 const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}"
628 "(?:/[a-z+._][a-z+._-]{,13})*"
629 // Optional suffix:
630 "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset
631 // one name fragment (DST):
632 "(?:[a-z+._][a-z+._-]{,13})?)"),
633 Qt::CaseInsensitive);
634 return rx.exactMatch(ianaId);
635 */
636
637 // Somewhat slack hand-rolled version:
638 const int MinSectionLength = 1;
639#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
640 // Android has its own naming of zones.
641 // "Canada/East-Saskatchewan" has a 17-character second component.
642 const int MaxSectionLength = 17;
643#else
644 const int MaxSectionLength = 14;
645#endif
646 int sectionLength = 0;
647 for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) {
648 const char ch = *it;
649 if (ch == '/') {
650 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
651 return false; // violates (4)
652 sectionLength = -1;
653 } else if (ch == '-') {
654 if (sectionLength == 0)
655 return false; // violates (4)
656 } else if (!(ch >= 'a' && ch <= 'z')
657 && !(ch >= 'A' && ch <= 'Z')
658 && !(ch == '_')
659 && !(ch == '.')
660 // Should ideally check these only happen as an offset:
661 && !(ch >= '0' && ch <= '9')
662 && !(ch == '+')
663 && !(ch == ':')) {
664 return false; // violates (2)
665 }
666 }
667 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
668 return false; // violates (4)
669 return true;
670}
671
672QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType mode)
673{
674 if (mode == QTimeZone::ShortName && !offsetFromUtc)
675 return utcQString();
676
677 char sign = '+';
678 if (offsetFromUtc < 0) {
679 sign = '-';
680 offsetFromUtc = -offsetFromUtc;
681 }
682 const int secs = offsetFromUtc % 60;
683 const int mins = (offsetFromUtc / 60) % 60;
684 const int hour = offsetFromUtc / 3600;
685 QString result = QString::asprintf(format: "UTC%c%02d", sign, hour);
686 if (mode != QTimeZone::ShortName || secs || mins)
687 result += QString::asprintf(format: ":%02d", mins);
688 if (mode == QTimeZone::LongName || secs)
689 result += QString::asprintf(format: ":%02d", secs);
690 return result;
691}
692
693QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id)
694{
695 for (int i = 0; i < zoneDataTableSize; ++i) {
696 const QZoneData *data = zoneData(index: i);
697 if (ianaId(zoneData: data).split(sep: ' ').contains(t: id))
698 return toWindowsIdLiteral(windowsIdKey: data->windowsIdKey);
699 }
700 return QByteArray();
701}
702
703QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId)
704{
705 const quint16 windowsIdKey = toWindowsIdKey(winId: windowsId);
706 for (int i = 0; i < windowsDataTableSize; ++i) {
707 const QWindowsData *data = windowsData(index: i);
708 if (data->windowsIdKey == windowsIdKey)
709 return ianaId(windowsData: data);
710 }
711 return QByteArray();
712}
713
714QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId,
715 QLocale::Country country)
716{
717 const QList<QByteArray> list = windowsIdToIanaIds(windowsId, country);
718 if (list.count() > 0)
719 return list.first();
720 else
721 return QByteArray();
722}
723
724QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId)
725{
726 const quint16 windowsIdKey = toWindowsIdKey(winId: windowsId);
727 QList<QByteArray> list;
728
729 for (int i = 0; i < zoneDataTableSize; ++i) {
730 const QZoneData *data = zoneData(index: i);
731 if (data->windowsIdKey == windowsIdKey)
732 list << ianaId(zoneData: data).split(sep: ' ');
733 }
734
735 // Return the full list in alpha order
736 std::sort(first: list.begin(), last: list.end());
737 return list;
738}
739
740QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId,
741 QLocale::Country country)
742{
743 const quint16 windowsIdKey = toWindowsIdKey(winId: windowsId);
744 for (int i = 0; i < zoneDataTableSize; ++i) {
745 const QZoneData *data = zoneData(index: i);
746 // Return the region matches in preference order
747 if (data->windowsIdKey == windowsIdKey && data->country == (quint16) country)
748 return ianaId(zoneData: data).split(sep: ' ');
749 }
750
751 return QList<QByteArray>();
752}
753
754// Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly
755template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone()
756{
757 return d->clone();
758}
759
760/*
761 UTC Offset implementation, used when QT_NO_SYSTEMLOCALE set and ICU is not being used,
762 or for QDateTimes with a Qt:Spec of Qt::OffsetFromUtc.
763*/
764
765// Create default UTC time zone
766QUtcTimeZonePrivate::QUtcTimeZonePrivate()
767{
768 const QString name = utcQString();
769 init(zoneId: utcQByteArray(), offsetSeconds: 0, name, abbreviation: name, country: QLocale::AnyCountry, comment: name);
770}
771
772// Create a named UTC time zone
773QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &id)
774{
775 // Look for the name in the UTC list, if found set the values
776 for (int i = 0; i < utcDataTableSize; ++i) {
777 const QUtcData *data = utcData(index: i);
778 const QByteArray uid = utcId(utcData: data);
779 if (uid == id) {
780 QString name = QString::fromUtf8(str: id);
781 init(zoneId: id, offsetSeconds: data->offsetFromUtc, name, abbreviation: name, country: QLocale::AnyCountry, comment: name);
782 break;
783 }
784 }
785}
786
787qint64 QUtcTimeZonePrivate::offsetFromUtcString(const QByteArray &id)
788{
789 // Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds.
790 // Assumption: id has already been tried as a CLDR UTC offset ID (notably
791 // including plain "UTC" itself) and a system offset ID; it's neither.
792 if (!id.startsWith(c: "UTC") || id.size() < 5)
793 return invalidSeconds(); // Doesn't match
794 const char signChar = id.at(i: 3);
795 if (signChar != '-' && signChar != '+')
796 return invalidSeconds(); // No sign
797 const int sign = signChar == '-' ? -1 : 1;
798
799 const auto offsets = id.mid(index: 4).split(sep: ':');
800 if (offsets.isEmpty() || offsets.size() > 3)
801 return invalidSeconds(); // No numbers, or too many.
802
803 qint32 seconds = 0;
804 int prior = 0; // Number of fields parsed thus far
805 for (const auto &offset : offsets) {
806 bool ok = false;
807 unsigned short field = offset.toUShort(ok: &ok);
808 // Bound hour above at 24, minutes and seconds at 60:
809 if (!ok || field >= (prior ? 60 : 24))
810 return invalidSeconds();
811 seconds = seconds * 60 + field;
812 ++prior;
813 }
814 while (prior++ < 3)
815 seconds *= 60;
816
817 return seconds * sign;
818}
819
820// Create offset from UTC
821QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
822{
823 QString utcId = isoOffsetFormat(offsetFromUtc: offsetSeconds, mode: QTimeZone::ShortName);
824 init(zoneId: utcId.toUtf8(), offsetSeconds, name: utcId, abbreviation: utcId, country: QLocale::AnyCountry, comment: utcId);
825}
826
827QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds,
828 const QString &name, const QString &abbreviation,
829 QLocale::Country country, const QString &comment)
830{
831 init(zoneId, offsetSeconds, name, abbreviation, country, comment);
832}
833
834QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other)
835 : QTimeZonePrivate(other), m_name(other.m_name),
836 m_abbreviation(other.m_abbreviation),
837 m_comment(other.m_comment),
838 m_country(other.m_country),
839 m_offsetFromUtc(other.m_offsetFromUtc)
840{
841}
842
843QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
844{
845}
846
847QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone() const
848{
849 return new QUtcTimeZonePrivate(*this);
850}
851
852QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
853{
854 Data d;
855 d.abbreviation = m_abbreviation;
856 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
857 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
858 d.daylightTimeOffset = 0;
859 return d;
860}
861
862void QUtcTimeZonePrivate::init(const QByteArray &zoneId)
863{
864 m_id = zoneId;
865}
866
867void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name,
868 const QString &abbreviation, QLocale::Country country,
869 const QString &comment)
870{
871 m_id = zoneId;
872 m_offsetFromUtc = offsetSeconds;
873 m_name = name;
874 m_abbreviation = abbreviation;
875 m_country = country;
876 m_comment = comment;
877}
878
879QLocale::Country QUtcTimeZonePrivate::country() const
880{
881 return m_country;
882}
883
884QString QUtcTimeZonePrivate::comment() const
885{
886 return m_comment;
887}
888
889QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
890 QTimeZone::NameType nameType,
891 const QLocale &locale) const
892{
893 Q_UNUSED(timeType)
894 Q_UNUSED(locale)
895 if (nameType == QTimeZone::ShortName)
896 return m_abbreviation;
897 else if (nameType == QTimeZone::OffsetName)
898 return isoOffsetFormat(offsetFromUtc: m_offsetFromUtc);
899 return m_name;
900}
901
902QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
903{
904 Q_UNUSED(atMSecsSinceEpoch)
905 return m_abbreviation;
906}
907
908qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
909{
910 Q_UNUSED(atMSecsSinceEpoch)
911 return m_offsetFromUtc;
912}
913
914qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
915{
916 Q_UNUSED(atMSecsSinceEpoch)
917 return 0;
918}
919
920QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const
921{
922 return utcQByteArray();
923}
924
925bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
926{
927 // Only the zone IDs supplied by CLDR and recognized by constructor.
928 for (int i = 0; i < utcDataTableSize; ++i) {
929 const QUtcData *data = utcData(index: i);
930 if (utcId(utcData: data) == ianaId)
931 return true;
932 }
933 // But see offsetFromUtcString(), which lets us accept some "unavailable" IDs.
934 return false;
935}
936
937QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds() const
938{
939 // Only the zone IDs supplied by CLDR and recognized by constructor.
940 QList<QByteArray> result;
941 result.reserve(alloc: utcDataTableSize);
942 for (int i = 0; i < utcDataTableSize; ++i)
943 result << utcId(utcData: utcData(index: i));
944 // Not guaranteed to be sorted, so sort:
945 std::sort(first: result.begin(), last: result.end());
946 // ### assuming no duplicates
947 return result;
948}
949
950QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
951{
952 // If AnyCountry then is request for all non-region offset codes
953 if (country == QLocale::AnyCountry)
954 return availableTimeZoneIds();
955 return QList<QByteArray>();
956}
957
958QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds) const
959{
960 // Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00
961 // and UTC-00:00 all have the same offset.)
962 QList<QByteArray> result;
963 for (int i = 0; i < utcDataTableSize; ++i) {
964 const QUtcData *data = utcData(index: i);
965 if (data->offsetFromUtc == offsetSeconds)
966 result << utcId(utcData: data);
967 }
968 // Not guaranteed to be sorted, so sort:
969 std::sort(first: result.begin(), last: result.end());
970 // ### assuming no duplicates
971 return result;
972}
973
974#ifndef QT_NO_DATASTREAM
975void QUtcTimeZonePrivate::serialize(QDataStream &ds) const
976{
977 ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(str: m_id) << m_offsetFromUtc << m_name
978 << m_abbreviation << (qint32) m_country << m_comment;
979}
980#endif // QT_NO_DATASTREAM
981
982QT_END_NAMESPACE
983

source code of qtbase/src/corelib/time/qtimezoneprivate.cpp